« Back to History
tasks.php
|
20260723_000646.php
Initial Domain Snapshot
Copy Code
<?php require_once __DIR__ . '/../auth/auth.php'; require_once __DIR__ . '/../../core/response.php'; require_once __DIR__ . '/../../core/utils.php'; function require_user(){ require_login(); } function middle_rank($pdo, $status, $prev_id, $next_id) { $prev_rank = null; $next_rank = null; if ($prev_id) { $s=$pdo->prepare('SELECT rank FROM dev_tasks WHERE id=? AND status=?'); $s->execute([$prev_id,$status]); $prev_rank=$s->fetchColumn(); } if ($next_id) { $s=$pdo->prepare('SELECT rank FROM dev_tasks WHERE id=? AND status=?'); $s->execute([$next_id,$status]); $next_rank=$s->fetchColumn(); } if ($prev_rank!==false && $prev_rank!==null && $next_rank!==false && $next_rank!==null) { $a=(float)$prev_rank; $b=(float)$next_rank; if ($a==$b) $b+=1.0; return ($a+$b)/2.0; } if ($prev_rank!==false && $prev_rank!==null) return ((float)$prev_rank)+1024.0; if ($next_rank!==false && $next_rank!==null) return ((float)$next_rank)-1024.0; return 1000.0; } global $router; if (!isset($router)) $router = new Router(); /* ---------- Sprints ---------- */ $router->get('/api/dev/sprints', function(){ require_user(); $pdo = DB::pdo(); $q = $pdo->query('SELECT * FROM dev_sprints ORDER BY id DESC'); json($q->fetchAll()); }); $router->post('/api/dev/sprints', function(){ require_user(); $pdo = DB::pdo(); $d = read_json_body(); $ins = $pdo->prepare('INSERT INTO dev_sprints (name, start_date, end_date) VALUES (?,?,?)'); $ins->execute([ $d['name'] ?? 'Sprint', $d['start_date'] ?? null, $d['end_date'] ?? null ]); json(['ok'=>true,'id'=>$pdo->lastInsertId()]); }); /* ---------- Tasks list ---------- */ $router->get('/api/dev/tasks', function(){ require_user(); $pdo = DB::pdo(); $status = $_GET['status'] ?? null; $sprint_id = isset($_GET['sprint_id']) && $_GET['sprint_id']!=='' ? (int)$_GET['sprint_id'] : null; $assignee = isset($_GET['assignee']) && $_GET['assignee']!=='' ? (int)$_GET['assignee'] : null; $search = trim($_GET['search'] ?? ''); $include_archived = isset($_GET['include_archived']) ? (int)$_GET['include_archived'] : 0; $where = []; $vals = []; if ($status) { $where[]='status=?'; $vals[]=$status; } else if (!$include_archived) { $where[] = "status!='archived'"; } if ($sprint_id !== null) { $where[]='sprint_id=?'; $vals[]=$sprint_id; } if ($assignee !== null) { $where[]='assignee=?'; $vals[]=$assignee; } if ($search !== '') { $where[]='(title LIKE ? OR module LIKE ?)'; $vals[]="%$search%"; $vals[]="%$search%"; } $sql = 'SELECT * FROM dev_tasks'; if ($where) $sql .= ' WHERE '.implode(' AND ',$where); $sql .= ' ORDER BY status, rank, id'; $st = $pdo->prepare($sql); $st->execute($vals); json($st->fetchAll()); }); /* ---------- Task create ---------- */ $router->post('/api/dev/tasks', function(){ require_user(); $pdo = DB::pdo(); $d = read_json_body(); $status = $d['status'] ?? 'todo'; $r = $pdo->prepare('SELECT IFNULL(MAX(rank),0)+1024 FROM dev_tasks WHERE status=?'); $r->execute([$status]); $rank = (float)$r->fetchColumn(); $ins = $pdo->prepare('INSERT INTO dev_tasks (title, description, module, assignee, points, due, status, sprint_id, rank, created_by, updated_by) VALUES (?,?,?,?,?,?,?,?,?,?,?)'); $ins->execute([ $d['title'] ?? 'Untitled Task', $d['description'] ?? null, $d['module'] ?? null, isset($d['assignee']) ? (int)$d['assignee'] : null, (int)($d['points'] ?? 1), $d['due'] ?? null, $status, isset($d['sprint_id']) ? (int)$d['sprint_id'] : null, $rank, null, null ]); $id = (int)$pdo->lastInsertId(); $h = $pdo->prepare('INSERT INTO dev_history (task_id, field, old_value, new_value, changed_by) VALUES (?,?,?,?,?)'); $h->execute([$id, 'created', null, $status, null]); json(['ok'=>true,'id'=>$id]); }); /* ---------- Task update ---------- */ $router->post('/api/dev/tasks/update', function(){ require_user(); $pdo = DB::pdo(); $d = read_json_body(); if (!isset($d['id'])) json(['ok'=>false,'error'=>'Missing id'], 400); $sel = $pdo->prepare('SELECT * FROM dev_tasks WHERE id=?'); $sel->execute([(int)$d['id']]); $old = $sel->fetch(); if (!$old) json(['ok'=>false,'error'=>'Task not found'], 404); $allowed = ['title','description','module','assignee','points','due','status','sprint_id','rank']; $fields = []; $vals = []; foreach ($allowed as $f) { if (array_key_exists($f, $d)) { $fields[] = "$f=?"; $v = $d[$f]; if ($f==='assignee' || $f==='sprint_id') $v = ($v===''? null : (isset($v)? (int)$v : null)); if ($f==='points') $v = (int)$v; if ($f==='rank') $v = (float)$v; $vals[] = $v; } } if (!$fields) json(['ok'=>false,'error'=>'No fields to update'], 400); $vals[] = (int)$d['id']; $sql = 'UPDATE dev_tasks SET '.implode(',', $fields).' WHERE id=?'; $pdo->prepare($sql)->execute($vals); $h = $pdo->prepare('INSERT INTO dev_history (task_id, field, old_value, new_value, changed_by) VALUES (?,?,?,?,?)'); foreach ($allowed as $f) { if (array_key_exists($f, $d)) { $oldv = isset($old[$f]) ? strval($old[$f]) : null; $newv = isset($d[$f]) ? strval($d[$f]) : null; if ($oldv !== $newv) $h->execute([(int)$d['id'], $f, $oldv, $newv, null]); } } json(['ok'=>true]); }); /* ---------- Reorder / move ---------- */ $router->post('/api/dev/tasks/reorder', function(){ require_user(); $pdo = DB::pdo(); $d = read_json_body(); $id = (int)($d['id'] ?? 0); if (!$id) json(['ok'=>false,'error'=>'Missing id'], 400); $to_status = $d['to_status'] ?? null; $prev_id = !empty($d['prev_id']) ? (int)$d['prev_id'] : null; $next_id = !empty($d['next_id']) ? (int)$d['next_id'] : null; $sel = $pdo->prepare('SELECT * FROM dev_tasks WHERE id=?'); $sel->execute([$id]); $task = $sel->fetch(); if (!$task) json(['ok'=>false,'error'=>'Task not found'], 404); $status = $to_status ?: $task['status']; $new_rank = middle_rank($pdo, $status, $prev_id, $next_id); $upd = $pdo->prepare('UPDATE dev_tasks SET status=?, rank=? WHERE id=?'); $upd->execute([$status, $new_rank, $id]); if ($status !== $task['status']) { $h = $pdo->prepare('INSERT INTO dev_history (task_id, field, old_value, new_value, changed_by) VALUES (?,?,?,?,?)'); $h->execute([$id,'status',$task['status'],$status,null]); } json(['ok'=>true,'rank'=>$new_rank]); }); /* ---------- Archive ---------- */ $router->post('/api/dev/tasks/archive', function(){ require_user(); $pdo = DB::pdo(); $d = read_json_body(); if (!isset($d['id'])) json(['ok'=>false,'error'=>'Missing id'], 400); $id = (int)$d['id']; $old = $pdo->prepare('SELECT status FROM dev_tasks WHERE id=?'); $old->execute([$id]); $old_status = $old->fetchColumn(); if ($old_status === false) json(['ok'=>false,'error'=>'Task not found'], 404); $pdo->prepare("UPDATE dev_tasks SET status='archived' WHERE id=?")->execute([$id]); $h = $pdo->prepare('INSERT INTO dev_history (task_id, field, old_value, new_value, changed_by) VALUES (?,?,?,?,?)'); $h->execute([$id,'status',$old_status,'archived',null]); json(['ok'=>true]); }); /* ---------- Hard delete ---------- */ $router->post('/api/dev/tasks/delete', function(){ require_user(); $pdo = DB::pdo(); $d = read_json_body(); if (!isset($d['id'])) json(['ok'=>false,'error'=>'Missing id'], 400); $id = (int)$d['id']; $pdo->prepare('DELETE FROM dev_tasks WHERE id=?')->execute([$id]); $h = $pdo->prepare('INSERT INTO dev_history (task_id, field, old_value, new_value, changed_by) VALUES (?,?,?,?,?)'); $h->execute([$id,'deleted',null,'1',null]); json(['ok'=>true]); }); /* ---------- Metrics ---------- */ $router->get('/api/dev/metrics', function(){ require_user(); $pdo = DB::pdo(); $sprint_id = isset($_GET['sprint_id']) && $_GET['sprint_id']!=='' ? (int)$_GET['sprint_id'] : null; $where = '1=1'; $vals = []; if ($sprint_id) { $where .= ' AND sprint_id=?'; $vals[] = $sprint_id; } $rows = $pdo->prepare("SELECT status, COUNT(*) c, COALESCE(SUM(points),0) p FROM dev_tasks WHERE $where GROUP BY status"); $rows->execute($vals); $status = []; foreach ($rows as $r) $status[$r['status']] = ['count'=>(int)$r['c'],'points'=>(int)$r['p']]; $burndown = ['labels'=>[], 'planned'=>[], 'remaining'=>[]]; if ($sprint_id) { $spr = $pdo->prepare('SELECT start_date,end_date FROM dev_sprints WHERE id=?'); $spr->execute([$sprint_id]); $s = $spr->fetch(); if ($s && $s['start_date'] && $s['end_date']) { $start = new DateTime($s['start_date']); $end = new DateTime($s['end_date']); $days = []; for ($d = clone $start; $d <= $end; $d->modify('+1 day')) $days[] = $d->format('Y-m-d'); $tot = (int)$pdo->query("SELECT COALESCE(SUM(points),0) FROM dev_tasks WHERE sprint_id=".(int)$sprint_id)->fetchColumn(); $n = max(1, count($days)-1); $burndown['labels'] = $days; for ($i=0; $i<count($days); $i++) $burndown['planned'][] = max(0, $tot - (int)round($tot*$i/$n)); $doneBy = $pdo->prepare("SELECT DATE(changed_at) d, COALESCE(SUM(t.points),0) pts FROM dev_history h JOIN dev_tasks t ON t.id=h.task_id WHERE h.field='status' AND h.new_value='done' AND t.sprint_id=? GROUP BY DATE(changed_at)"); $doneBy->execute([$sprint_id]); $map = []; foreach ($doneBy as $r) $map[$r['d']] = (int)$r['pts']; $cum = 0; foreach ($days as $d) { if (isset($map[$d])) $cum += $map[$d]; $burndown['remaining'][] = max(0, $tot - $cum); } } } json(['status'=>$status, 'burndown'=>$burndown]); });