Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions docs/case-championship-domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,18 @@ Program
**Связи.** Имеет регистрации, заявки и сдачи; связывается с менеджерами,
экспертами, курсами, материалами и legacy-проектами.

**Что хранит.** Описание и изображения, даты начала и завершения, дедлайны
регистрации, legacy-дедлайн подачи проектов и дедлайн оценки, схему
регистрационных данных, настройки конкурсности и доступности проектов.
**Что хранит.** Описание и изображения, даты начала и завершения, дедлайн
регистрации, отдельный дедлайн Application, legacy-дедлайн подачи проектов и
дедлайн оценки, схему регистрационных данных, настройки конкурсности,
доступности проектов, формата участия и размера команды.

**Что не должна хранить.** Ответы конкретного участника, состав команды,
решение или оценку конкретной работы.

**Текущее состояние.** Реализована как `partner_programs.PartnerProgram`.
Специальных настроек формата участия, минимального/максимального размера
команды, дедлайна заявки и отдельного дедлайна решения пока нет.
Формат участия, минимальный/максимальный размер команды и отдельный дедлайн
Application хранятся в модели и защищены validation/constraints. Enforcement
в Application/team service и отдельный дедлайн решения пока отсутствуют.

### Registration

Expand Down Expand Up @@ -615,6 +617,7 @@ Registration.
| Область | Уже реализовано | Частично | Отсутствует | Комментарий |
|---|---|---|---|---|
| Registration | `PartnerProgramUserProfile`, register endpoints, `participating=1`, deadline и uniqueness | Статус Registration и связь с новым flow | Отдельная модель/явный contract Registration | Application create не требует Registration |
| Program participation policy | Форматы `individual_only/team_only/individual_or_team`, размеры Team, application deadline, model/DB validation и admin | Helpers пока вызываются только явно | Enforcement в Application/team service и публичный API | Default `individual_only`, размеры и deadline существующих программ остаются `null` |
| Application model | Program, user, created_by, participation_mode, form_data, nullable Project, timestamps, partial unique constraint | Ownership по-прежнему опирается на Application.user | returned, snapshot, review reason | Default participation_mode временно individual |
| Application API | create/my/detail/patch/submit/withdraw, idempotency create/submit/withdraw, scoped throttle create | Owner/staff access | review endpoints, manager access, deadline/form/team checks | 404 my application корректно означает отсутствие записи |
| Application statuses | Шесть статусов и timestamps submit/approve/reject/withdraw | Переходы draft/submit/withdraw | `returned`, return/approve/reject/cancel actions | approved/rejected могут появиться только вне нового API, например через admin |
Expand All @@ -628,7 +631,7 @@ Registration.
| Notification | Email и chat WebSocket infrastructure | Mailing logs не являются inbox | Доменная Notification и пользовательский центр | Не входит в первый Team PR |
| Evaluation | `Criteria`, `ProjectScore`, `ProjectExpertAssignment` для legacy Project | Эксперты и распределенное оценивание проекта | Evaluation по Submission | Нельзя смешивать с ProjectScore без миграции |
| Result | Legacy scores и пользовательские достижения существуют отдельно | Нет единого результата заявки | Result/ranking/publication contract | Требует решения об источнике итогов |
| Deadlines | Registration и legacy project submission/evaluation dates | Поля Program можно переиспользовать только после решения semantics | Проверки в Application/Submission API, отдельные application/solution deadlines | Сейчас новый flow позволяет действия после дедлайнов |
| Deadlines | Registration, отдельный Application deadline и legacy project submission/evaluation dates | Application helper не подключен к endpoints | Проверки в Application/Submission API и отдельный solution deadline | Application deadline не имеет fallback на legacy-поля |
| Permissions | Owner/staff для новых API; manager/expert permissions есть для legacy flow | Staff имеет расширенный доступ | Captain/member, manager и expert permissions для новых сущностей | Нужен общий domain permission layer |
| Constraints | Registration uniqueness; active individual Application; Team one-to-one/member/captain constraints; Submission version; Project collaborators | Captain/member validation есть только внутри одной Team | Cross-team active participation и team size | Cross-table race требует транзакционного service |
| Admin | PartnerProgram, Registration, Application, Team, TeamMember, Submission, Project, Invite и legacy evaluation зарегистрированы | Admin позволяет ручную диагностику | TeamInvite/Evaluation admin | Admin не заменяет transition services |
Expand Down Expand Up @@ -683,12 +686,12 @@ Registration.
| Team и TeamMember | Модели, admin, constraints и tests реализованы | Добавить транзакционный service и публичный API | P0 | Application/team invariants |
| Только зарегистрированный создает Application | Не проверяется | Domain service должен проверять PartnerProgramUserProfile | P0 | Application eligibility and lifecycle |
| Одна активная заявка на пользователя с учетом Team | Constraint покрывает только `Application.user` | Транзакционная проверка индивидуальных и командных участий | P0 | Application/team invariants |
| Валидация команды перед submit | Есть модель Team, но нет program policy/service | Настройки формата/размера Program и submit validation | P0 | Program participation policy |
| Валидация команды перед submit | Team и Program policy реализованы | Транзакционная проверка accepted-состава и формата перед submit | P0 | Application/team invariants |
| Captain-only actions | Captain хранится, endpoints Team не учитывают | Team-aware permissions для Application и Submission | P0 | Team permissions |
| Team API | Отсутствует | CRUD ограниченного MVP, members, leave/transfer captain | P1 | Team API |
| Полный lifecycle Application | Нет returned/review actions | Добавить return/approve/reject/cancel с reason/audit | P1 | Application review API |
| TeamInvite | Есть только Project Invite | Отдельные token/email/user invites и идемпотентный accept | P1 | TeamInvite model and API |
| Application deadlines | Новый API их не проверяет | Зафиксировать поля Program и проверять create/edit/submit/withdraw | P0 | Application eligibility and lifecycle |
| Application deadlines | Отдельное поле и helper Program реализованы, API их не проверяет | Проверять create/edit/submit/withdraw без fallback на legacy deadlines | P0 | Application eligibility and lifecycle |
| Submission deadlines и роли | Новый API не проверяет сроки, manager/expert/team | Добавить policy и единый permission layer | P1 | Submission lifecycle hardening |
| Return/finalize Submission | Статусы есть, действий нет | Организаторские endpoints и audit fields/reasons | P1 | Submission review API |
| Semantics версий Submission | Несколько записей и auto-next уже есть | Определить current/final/evaluated version и поведение after return | P1 | Submission version workflow |
Expand All @@ -704,8 +707,9 @@ Registration.
1. **`feature/application-team-model` — реализовано.** Добавлены
`participation_mode`, Team и TeamMember с миграцией, admin, модельными
ограничениями и тестами; без API.
2. **Program participation policy.** Зафиксировать разрешенные форматы,
минимальный/максимальный размер команды и реальные дедлайны Application.
2. **`feature/program-participation-policy` — реализовано.** Добавлены
разрешенные форматы, минимальный/максимальный размер команды и отдельный
дедлайн Application; enforcement в API намеренно не включен.
3. **Application/team invariants.** Ввести транзакционный service для проверки
Registration, конфликтов активного участия, капитана и accepted-состава.
4. **Team permissions and API.** Добавить owner/captain/member/manager access,
Expand Down
88 changes: 88 additions & 0 deletions docs/program-participation-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Program Participation Policy

## Назначение

Policy хранится в `PartnerProgram` и задает допустимый формат участия,
границы размера команды и отдельный дедлайн подачи `Application`. Эти данные
станут источником истины для будущего транзакционного сервиса создания и
отправки заявок.

В текущем изменении policy валидируется на уровне модели и базы данных, но еще
не применяется существующими Application endpoints. Публичные Program
serializers также намеренно не расширены.

## Формат участия

Поле `participation_format` поддерживает три значения:

| Значение | Разрешенные финальные `Application.participation_mode` |
|---|---|
| `individual_only` | Только `individual` |
| `team_only` | Только `team` |
| `individual_or_team` | `individual` и `team` |

`undecided` остается допустимым состоянием черновика Application, но не
считается разрешенным финальным форматом. Метод
`allows_participation_mode(mode)` возвращает `False` и для `undecided`, и для
неизвестного значения.

Runtime default — `individual_only`. Он сохраняет поведение существующих
программ и не включает командные заявки без явной настройки организатора.
После миграции все существующие строки получают это значение.

## Размер команды

`team_min_size` и `team_max_size` применяются только к `team_only` и
`individual_or_team`. Для этих форматов оба поля обязательны, минимальный
размер не может быть меньше двух, а максимальный — меньше минимального.

Для `individual_only` оба значения должны быть `null`. Существующим программам
произвольные границы не назначаются.

Размер команды в будущем рассчитывается только по `TeamMember` со статусом
`accepted`. Капитан имеет accepted-запись `TeamMember` и входит в размер
команды. Фактическое число участников нельзя надежно проверить constraint-ом
одной таблицы: оно зависит от связанных строк и конкурентных изменений.
Поэтому эту проверку должен выполнять будущий транзакционный domain service
непосредственно перед submit Application.

## Дедлайн Application

`datetime_application_ends` — nullable datetime отдельного процесса заявки.
Он не переиспользует и не меняет semantics следующих legacy-полей:

- `datetime_registration_ends`;
- `datetime_project_submission_ends`;
- `datetime_evaluation_ends`.

Значение `null` означает, что отдельный дедлайн Application не настроен. Метод
`is_application_deadline_passed(at=None)` в таком случае возвращает `False`.
Fallback на регистрацию или сдачу проекта намеренно отсутствует: будущий
domain service должен явно определить поведение при `null`.

## Validation и database constraints

`PartnerProgram.clean()` привязывает русскоязычные ошибки к
`team_min_size`/`team_max_size` и проверяет соответствие размеров выбранному
формату.

Database constraints дополнительно гарантируют:

- каждый заданный размер не меньше двух;
- `team_max_size >= team_min_size`, когда заданы оба значения;
- у `individual_only` оба размера равны `null`;
- у форматов с командами оба размера заданы.

Constraints защищают данные при обходе model validation, но не проверяют
фактический состав `Team`.

## Текущие ограничения и следующий шаг

Существующие Application API, Team API и legacy registration/project flow не
изменены. Policy пока не блокирует create/edit/submit Application, не проверяет
Registration и не разрешает конфликты участия между командами.

Следующий PR должен добавить транзакционный Application/team service. Перед
submit он использует policy для проверки Registration, финального
`participation_mode`, accepted-состава Team и дедлайна без изменения legacy
дедлайнов.
22 changes: 20 additions & 2 deletions partner_programs/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,20 +229,28 @@ class Meta:

inlines = [PartnerProgramMaterialInline, PartnerProgramFieldInline]
form = PartnerProgramAdminForm
list_display = ("id", "name", "tag", "city", "datetime_created")
list_display = (
"id",
"name",
"tag",
"city",
"participation_format",
"datetime_created",
)
list_display_links = (
"id",
"name",
"tag",
"city",
"participation_format",
"datetime_created",
)
search_fields = (
"name",
"city",
"tag",
)
list_filter = ("city",)
list_filter = ("city", "participation_format")

autocomplete_fields = ("managers",)
date_hierarchy = "datetime_started"
Expand Down Expand Up @@ -279,6 +287,16 @@ class Meta:
)
},
),
(
"Участие и заявки",
{
"fields": (
"participation_format",
("team_min_size", "team_max_size"),
"datetime_application_ends",
)
},
),
("Менеджеры программы", {"fields": ("managers",)}),
("Служебная информация", {"fields": ("datetime_created", "datetime_updated")}),
)
Expand Down
Loading
Loading