feat: закрыть остаточные P1/P2 по профилю, когорте и ведомости

- профиль приведён к docs/09: variant/level, updatedBy/name, лимит попыток
- V14: NOT NULL и полная уникальность когорты, preflight legacy-групп
- возврат группы из архива и история членства на странице группы
- пагинация групп и плоского списка материалов
- пакетные сдачи ведомости одним запросом
- XLSX-preflight импорта и audit-тесты ростера
- LDD: 66 claims, 398 тестов, release PASS
This commit is contained in:
2026-10-29 20:51:27 +03:00
parent 7ef5c866a3
commit 82394cc688
48 changed files with 1196 additions and 156 deletions

View File

@@ -105,6 +105,7 @@ log "Применяю миграции ветки..."
cat services/identity-service/src/main/resources/db/migration/V11__refresh_tokens.sql
cat services/identity-service/src/main/resources/db/migration/V12__refresh_absolute.sql
cat services/identity-service/src/main/resources/db/migration/V13__audit_keyset_indexes.sql
cat services/identity-service/src/main/resources/db/migration/V14__cohort_constraints.sql
echo 'SET search_path TO academic;'
cat services/academic-service/src/main/resources/db/migration/V6__courses.sql
cat services/academic-service/src/main/resources/db/migration/V7__labs_course.sql
@@ -130,6 +131,13 @@ SELECT 'audit_keyset_idx=' || count(*) FROM pg_indexes
WHERE schemaname='identity' AND indexname='audit_events_occurred_id_idx';
SELECT 'students=' || count(*) FROM students;
SELECT 'groups=' || count(*) FROM groups;
SELECT 'cohort_not_null=' || count(*) FROM information_schema.columns
WHERE table_schema='identity' AND table_name='groups'
AND column_name IN ('stream','degree','course','number','enrollment_year')
AND is_nullable='NO';
SELECT 'cohort_unique_full=' || count(*) FROM pg_indexes
WHERE schemaname='identity' AND indexname='groups_cohort_unique'
AND indexdef NOT LIKE '%WHERE%';
SELECT 'history=' || count(*) FROM membership_history;
SET search_path TO academic;
SELECT 'labs_course_id=' || count(*) FROM information_schema.columns
@@ -151,21 +159,27 @@ SQL
)
RESULT=$(docker exec -i lms-postgres psql -U lms -d "$DB" -At -v ON_ERROR_STOP=1 -c "$CHECK_SQL")
# V13 повторно: идемпотентность.
# V13/V14 повторно: идемпотентность.
if { echo 'SET search_path TO progress;'; cat services/progress-service/src/main/resources/db/migration/V13__profile_authors.sql; } | apply; then
V13_TWICE="passed"
else
V13_TWICE="failed"
FAILED=1
fi
if { echo 'SET search_path TO identity;'; cat services/identity-service/src/main/resources/db/migration/V14__cohort_constraints.sql; } | apply; then
V14_TWICE="passed"
else
V14_TWICE="failed"
FAILED=1
fi
printf '%s\n' "$RESULT" | sed 's/^/ /'
python3 - "$EVIDENCE" "$RESULT" "$V13_TWICE" <<'PY'
python3 - "$EVIDENCE" "$RESULT" "$V13_TWICE" "$V14_TWICE" <<'PY'
import json
import sys
out, result, v13 = sys.argv[1], sys.argv[2], sys.argv[3]
out, result, v13, v14 = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
required = {
"audit_events=1",
"refresh_tokens=1",
@@ -173,6 +187,8 @@ required = {
"audit_keyset_idx=1",
"students=1",
"groups=1",
"cohort_not_null=5",
"cohort_unique_full=1",
"history=1",
"labs_course_id=1",
"chapters_course_id=1",
@@ -186,16 +202,19 @@ required = {
}
lines = {line.strip() for line in result.splitlines() if line.strip()}
missing = sorted(required - lines)
failed = bool(missing) or v13 != "passed"
failed = bool(missing) or v13 != "passed" or v14 != "passed"
if missing:
status = "failed"
detail = "postflight не подтвердил: " + ", ".join(missing)
elif v13 != "passed":
status = "failed"
detail = "V13 не применилась повторно"
elif v14 != "passed":
status = "failed"
detail = "V14 не применилась повторно"
else:
status = "passed"
detail = "master->branch: все postflight-проверки и повторная V13 успешны"
detail = "master->branch: postflight, повторные V13 и V14 успешны"
checks = [{"id": "migration.master_upgrade", "status": status, "detail": detail}]
with open(out, "w", encoding="utf-8") as fh:
now = __import__("datetime").datetime.now(__import__("datetime").UTC).isoformat()