From 1ded77c5dc9b07289404b8f2f40dfe13dd7b1ccc Mon Sep 17 00:00:00 2001 From: "an.nechaev" Date: Thu, 18 Jun 2026 11:51:34 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B0=20=D0=B1?= =?UTF-8?q?=D0=B0=D0=B3=D0=BE=D0=B2=20=D1=80=D0=B0=D1=81=D1=81=D1=87=D0=B5?= =?UTF-8?q?=D1=82=D0=B0=20=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D1=8F=20=D1=81?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/postgres/init/001_schema.sql | 2 +- docker/postgres/init/002_dump.sql | 9 +++- src/console/calculate_sprint_completion.php | 59 +++++++++++++-------- 3 files changed, 45 insertions(+), 25 deletions(-) diff --git a/docker/postgres/init/001_schema.sql b/docker/postgres/init/001_schema.sql index 32ab0c3..fbcd71a 100644 --- a/docker/postgres/init/001_schema.sql +++ b/docker/postgres/init/001_schema.sql @@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS teams ( team_board_id INT NOT NULL ); -CREATE TYPE role AS ENUM ('analytic', 'backend', 'designer', 'frontend', 'qa'); +CREATE TYPE role AS ENUM ('analytic', 'backend', 'designer', 'frontend', 'qa', 'project'); CREATE TABLE IF NOT EXISTS employees ( id SERIAL PRIMARY KEY, diff --git a/docker/postgres/init/002_dump.sql b/docker/postgres/init/002_dump.sql index b15b62e..dac9943 100644 --- a/docker/postgres/init/002_dump.sql +++ b/docker/postgres/init/002_dump.sql @@ -82,7 +82,9 @@ VALUES (61, 'Иван Монастырский', 'i.monastyrskiy', 'frontend'), (62, 'Дамир Насыров', 'd.nasyrov', 'frontend'), (63, 'Максим Миклушов', 'm.miklushov', 'qa'), - (64, 'Дмитрий Михайлов', 'd.mikhaylov', 'qa'); + (64, 'Дмитрий Михайлов', 'd.mikhaylov', 'qa'), + (67, 'Борис Печорин', 'b.pechorin', 'backend'), + (68, 'Наталья Чеботарева', 'n.telentinova', 'project'); INSERT INTO team_members (team_id, employee_id, start_datetime) VALUES @@ -159,4 +161,7 @@ VALUES (4, 61, '2026-01-01 00:00:00Z'), (4, 62, '2026-01-01 00:00:00Z'), (4, 63, '2026-01-01 00:00:00Z'), - (4, 64, '2026-01-01 00:00:00Z'); + (4, 64, '2026-01-01 00:00:00Z'), + (4, 67, '2026-01-01 00:00:00Z'), + (4, 4, '2026-01-01 00:00:00Z'), + (4, 68, '2026-01-01 00:00:00Z'); diff --git a/src/console/calculate_sprint_completion.php b/src/console/calculate_sprint_completion.php index 217bd9f..616b972 100644 --- a/src/console/calculate_sprint_completion.php +++ b/src/console/calculate_sprint_completion.php @@ -168,6 +168,12 @@ function parseSprintIds(?string $raw): array { */ function wasIssueInSprintAtStart(array $issue, int $jiraSprintId, int $startTs): bool { $sprintIdStr = (string)$jiraSprintId; + $issueCreatedAt = !empty($issue['fields']['created']) ? strtotime($issue['fields']['created']) : false; + + // Задача создалась после начала спринта - она не спринтовая + if ($issueCreatedAt > $startTs) { + return false; + } // Собираем изменения поля "Sprint" с метками времени. $changes = []; @@ -180,39 +186,48 @@ function wasIssueInSprintAtStart(array $issue, int $jiraSprintId, int $startTs): if (($item['field'] ?? null) !== 'Sprint') { continue; } - $changes[] = [ - 'at' => $createdAt, - 'from' => parseSprintIds($item['from'] ?? ''), - 'to' => parseSprintIds($item['to'] ?? ''), + $fromIds = parseSprintIds($item['from'] ?? ''); + $toIds = parseSprintIds($item['to'] ?? ''); + + // Если id спринта и там и там - спринт не менялся нужный + // Если нет id спринта в обоих местах - тоже не интересует + if (in_array($sprintIdStr, $fromIds, true) == in_array($sprintIdStr, $toIds, true)) { + continue; + } + + $changes[$createdAt] = [ + 'from' => $fromIds, + 'to' => $toIds, ]; } } if (empty($changes)) { // Поле "Sprint" не менялось: задача в этом спринте с момента создания. - $issueCreatedAt = !empty($issue['fields']['created']) ? strtotime($issue['fields']['created']) : false; - return $issueCreatedAt !== false && $issueCreatedAt <= $startTs; + return true; } - usort($changes, static fn ($a, $b) => $a['at'] <=> $b['at']); + $timeList = array_keys($changes); + $changesBeforeStart = array_filter($timeList, fn ($change) => $change < $startTs); + $changeBeforeStart = empty($changesBeforeStart) ? 0 : max($changesBeforeStart); - // Восстанавливаем значение поля "Sprint", действовавшее на момент старта спринта. - $valueAtStart = null; - $hasPriorChange = false; - foreach ($changes as $change) { - if ($change['at'] <= $startTs) { - $valueAtStart = $change['to']; - $hasPriorChange = true; - } else { - // Первое изменение уже после старта: значение до него — это его "from". - if (!$hasPriorChange) { - $valueAtStart = $change['from']; - } - break; - } + if ($changeBeforeStart) { + // Если добавили в спринт до начала спринта - то значит спринтовая + // Если нет, то значит удалили из спринта до старта + return in_array($jiraSprintId, $changes[$changeBeforeStart]['to']); } - return $valueAtStart !== null && in_array($sprintIdStr, $valueAtStart, true); + $changesAfterStart = array_filter($timeList, fn ($change) => $change > $startTs); + $changeAfterStart = empty($changesAfterStart) ? 0 : min($changesAfterStart); + + if ($changeAfterStart) { + // Если первое событие - добавление в спринт - не спринтовая + // Если удаление из спринта, значит она там была + return in_array($jiraSprintId, $changes[$changeAfterStart]['from']); + } + + // Если до сюда дошли, значит случилась какая-то хуйня и я это не учел и пора падать + throw new Exception('Беда с задачей '.$issue['key']); } /**