*/ function parseRepeatedQueryParam(string $key): array { $values = []; if (isset($_GET[$key]) && is_array($_GET[$key])) { foreach ($_GET[$key] as $item) { if (is_string($item) && $item !== '') { $values[] = $item; } } return array_values($values); } $queryString = $_SERVER['QUERY_STRING'] ?? ''; if ($queryString !== '') { foreach (explode('&', $queryString) as $part) { if ($part === '') { continue; } [$rawName, $rawValue] = array_pad(explode('=', $part, 2), 2, ''); $name = urldecode($rawName); if ($name !== $key && $name !== $key . '[]') { continue; } $value = urldecode($rawValue); if ($value !== '') { $values[] = $value; } } } if ($values === [] && isset($_GET[$key]) && is_string($_GET[$key]) && $_GET[$key] !== '') { return [$_GET[$key]]; } return array_values($values); } /** * @return array */ function parseStringList(mixed $value): array { if ($value === null || $value === '') { return []; } $items = is_array($value) ? $value : [$value]; return array_values(array_filter(array_map('strval', $items), static fn (string $item): bool => $item !== '')); } /** * @return array */ function parseStringListFromQuery(string $key): array { return parseStringList(parseRepeatedQueryParam($key)); } /** * @return array */ function parseIntList(mixed $value): array { $items = parseStringList($value); return array_values(array_filter(array_map('intval', $items), static fn (int $item): bool => $item > 0)); } /** * @return array */ function parseIntListFromQuery(string $key): array { return parseIntList(parseRepeatedQueryParam($key)); } $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; $path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/'; if ($method === 'OPTIONS') { http_response_code(204); exit; } try { $api = new ReportsApi(); if ($method === 'GET' && $path === '/api/teams') { echo json_encode($api->getTeams(), JSON_UNESCAPED_UNICODE); exit; } if ($method === 'GET' && $path === '/api/config') { echo json_encode($api->getPublicConfig(), JSON_UNESCAPED_UNICODE); exit; } if ($method === 'GET' && preg_match('#^/api/teams/(\d+)/sprints$#', $path, $matches)) { echo json_encode($api->getSprintsByTeamId((int) $matches[1]), JSON_UNESCAPED_UNICODE); exit; } if ($method === 'GET' && preg_match('#^/api/teams/(\d+)/tasks/filters$#', $path, $matches)) { $result = $api->getTeamTaskFilterOptions((int) $matches[1]); if ($result === null) { http_response_code(404); echo json_encode(['error' => 'Team not found'], JSON_UNESCAPED_UNICODE); exit; } echo json_encode($result, JSON_UNESCAPED_UNICODE); exit; } if ($method === 'GET' && preg_match('#^/api/teams/(\d+)/tasks/export$#', $path, $matches)) { $result = $api->getAllTasksByTeamId( (int) $matches[1], parseStringListFromQuery('task_type'), parseStringListFromQuery('assignee'), parseIntListFromQuery('sprint_id') ); if ($result === null) { http_response_code(404); echo json_encode(['error' => 'Team not found'], JSON_UNESCAPED_UNICODE); exit; } $fileName = sprintf('ltd_%s_%s.csv', $result['team_id'], date('Y-m-d')); header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $fileName . '"'); $output = fopen('php://output', 'wb'); // BOM для корректного распознавания UTF-8 в Excel. fwrite($output, "\xEF\xBB\xBF"); fputcsv($output, [ 'Задача', 'Название', 'Спринт', 'Тип', 'Исполнитель', 'Роль', 'Оценка (сек)', 'Факт (сек)', 'Lead Time (дни)', 'Переработка (%)', 'Flow Efficiency (%)', 'Ревью', 'Тест', 'Взято в работу', 'Закрыта', ], ';', '"', ''); foreach ($result['items'] as $item) { fputcsv($output, [ $item['task_key'], $item['task_title'], $item['sprint_name'], $item['task_type'], $item['employee_name'] ?? '', $item['employee_role'] ?? '', $item['estimate_seconds'], $item['total_time_seconds'], $item['lead_time_days'] ?? '', $item['recast_percentage'] ?? '', $item['efficiency_percentage'] ?? '', $item['review_count'], $item['test_count'], $item['take_in_work_datetime'] ?? '', $item['resolution_datetime'] ?? '', ], ';', '"', ''); } fclose($output); exit; } if ($method === 'GET' && preg_match('#^/api/teams/(\d+)/tasks$#', $path, $matches)) { $page = max(1, (int) ($_GET['page'] ?? 1)); $perPage = max(1, min(100, (int) ($_GET['per_page'] ?? 50))); $result = $api->getTasksByTeamId( (int) $matches[1], $page, $perPage, parseStringListFromQuery('task_type'), parseStringListFromQuery('assignee'), parseIntListFromQuery('sprint_id') ); if ($result === null) { http_response_code(404); echo json_encode(['error' => 'Team not found'], JSON_UNESCAPED_UNICODE); exit; } echo json_encode($result, JSON_UNESCAPED_UNICODE); exit; } if ($method === 'GET' && preg_match('#^/api/sprints/(\d+)/tasks$#', $path, $matches)) { echo json_encode($api->getTasksBySprintId((int) $matches[1]), JSON_UNESCAPED_UNICODE); exit; } if ($method === 'GET' && preg_match('#^/api/sprints/(\d+)$#', $path, $matches)) { $sprint = $api->getSprintById((int) $matches[1]); if ($sprint === null) { http_response_code(404); echo json_encode(['error' => 'Sprint not found'], JSON_UNESCAPED_UNICODE); exit; } echo json_encode($sprint, JSON_UNESCAPED_UNICODE); exit; } http_response_code(404); echo json_encode(['error' => 'Not found'], JSON_UNESCAPED_UNICODE); } catch (Throwable $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()], JSON_UNESCAPED_UNICODE); }