Правка багов рассчета процента закрытия спринта

master
an.nechaev 2026-06-18 11:51:34 +04:00
parent 4b7828a47d
commit 1ded77c5dc
3 changed files with 45 additions and 25 deletions

View File

@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS teams (
team_board_id INT NOT NULL 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 ( CREATE TABLE IF NOT EXISTS employees (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,

View File

@ -82,7 +82,9 @@ VALUES
(61, 'Иван Монастырский', 'i.monastyrskiy', 'frontend'), (61, 'Иван Монастырский', 'i.monastyrskiy', 'frontend'),
(62, 'Дамир Насыров', 'd.nasyrov', 'frontend'), (62, 'Дамир Насыров', 'd.nasyrov', 'frontend'),
(63, 'Максим Миклушов', 'm.miklushov', 'qa'), (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) INSERT INTO team_members (team_id, employee_id, start_datetime)
VALUES VALUES
@ -159,4 +161,7 @@ VALUES
(4, 61, '2026-01-01 00:00:00Z'), (4, 61, '2026-01-01 00:00:00Z'),
(4, 62, '2026-01-01 00:00:00Z'), (4, 62, '2026-01-01 00:00:00Z'),
(4, 63, '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');

View File

@ -168,6 +168,12 @@ function parseSprintIds(?string $raw): array {
*/ */
function wasIssueInSprintAtStart(array $issue, int $jiraSprintId, int $startTs): bool { function wasIssueInSprintAtStart(array $issue, int $jiraSprintId, int $startTs): bool {
$sprintIdStr = (string)$jiraSprintId; $sprintIdStr = (string)$jiraSprintId;
$issueCreatedAt = !empty($issue['fields']['created']) ? strtotime($issue['fields']['created']) : false;
// Задача создалась после начала спринта - она не спринтовая
if ($issueCreatedAt > $startTs) {
return false;
}
// Собираем изменения поля "Sprint" с метками времени. // Собираем изменения поля "Sprint" с метками времени.
$changes = []; $changes = [];
@ -180,39 +186,48 @@ function wasIssueInSprintAtStart(array $issue, int $jiraSprintId, int $startTs):
if (($item['field'] ?? null) !== 'Sprint') { if (($item['field'] ?? null) !== 'Sprint') {
continue; continue;
} }
$changes[] = [ $fromIds = parseSprintIds($item['from'] ?? '');
'at' => $createdAt, $toIds = parseSprintIds($item['to'] ?? '');
'from' => parseSprintIds($item['from'] ?? ''),
'to' => 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)) { if (empty($changes)) {
// Поле "Sprint" не менялось: задача в этом спринте с момента создания. // Поле "Sprint" не менялось: задача в этом спринте с момента создания.
$issueCreatedAt = !empty($issue['fields']['created']) ? strtotime($issue['fields']['created']) : false; return true;
return $issueCreatedAt !== false && $issueCreatedAt <= $startTs;
} }
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", действовавшее на момент старта спринта. if ($changeBeforeStart) {
$valueAtStart = null; // Если добавили в спринт до начала спринта - то значит спринтовая
$hasPriorChange = false; // Если нет, то значит удалили из спринта до старта
foreach ($changes as $change) { return in_array($jiraSprintId, $changes[$changeBeforeStart]['to']);
if ($change['at'] <= $startTs) {
$valueAtStart = $change['to'];
$hasPriorChange = true;
} else {
// Первое изменение уже после старта: значение до него — это его "from".
if (!$hasPriorChange) {
$valueAtStart = $change['from'];
}
break;
}
} }
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']);
} }
/** /**