Базовый слой командных заявок разделяет три понятия:
Application— заявка на одну партнерскую программу;Team— команда, собранная только для этой заявки;TeamMember— членство пользователя в команде заявки.
Публичного Team API пока нет. Транзакционный Application/Team service создает командный draft вместе с Team и accepted-капитаном и проверяет полный invariant перед submit. Состав команды через публичный API пока не редактируется.
В Application добавлено поле participation_mode:
| Значение | Смысл |
|---|---|
undecided |
Формат участия еще не выбран |
individual |
Индивидуальная заявка |
team |
Командная заявка |
Runtime default временно равен individual: существующие API и frontend не
передают новое поле, поэтому все текущие и исторические Application продолжают
работать как индивидуальные. undecided должен стать default одновременно с
wizard и запретом submit заявки без выбранного формата.
Миграция 0020_team_application_participation_mode_teammember_and_more
добавляет поле с default individual; Django применяет это значение ко всем
существующим строкам Application.
Team содержит:
- one-to-one
applicationсrelated_name="team"; - необязательное на стадии черновика
nameдлиной до 255 символов; captain;created_atиupdated_at.
Team относится к Application, потому что состав команды может различаться в
разных активностях даже при использовании одного Project. Существующий
projects.Collaborator описывает постоянного участника Project и намеренно не
переиспользуется как TeamMember.
В первом MVP Team.captain обязан совпадать с Application.user.
Team.status, invite code, token и настройки размера команды не добавлены:
редактируемость будущего Team flow должна выводиться из Application.status.
TeamMember содержит:
teamиuser;- роль
captainилиmember; - статус
invited,accepted,declined,removedилиleft; - nullable
invited_by; - nullable
joined_at; created_atиupdated_at.
Для первоначального captain member invited_by остается null: капитан не
принимает собственное приглашение, а создается как владелец команды. Статус
invited для обычных участников пока является только модельной заготовкой;
механизм TeamInvite в этом PR отсутствует.
При первом сохранении статуса accepted модель автоматически заполняет
joined_at, если дата не передана. При переходе в removed или left дата не
очищается и сохраняет момент фактического присоединения.
На уровне БД обеспечены:
- одна Team на Application через
OneToOneField; - уникальность
TeamMember(team, user); - не более одного
acceptedTeamMember с рольюcaptainв Team; - check: роль
captainдопустима только со статусомaccepted.
Простым constraint одной таблицы нельзя надежно обеспечить:
- участие пользователя в нескольких Team разных Application одной Program;
- конфликт TeamMember с индивидуальной Application той же Program;
- Registration всех членов команды;
- размер команды;
- блокировку состава после submit.
Эти правила проходят через Application, Team и TeamMember, поэтому требуют отдельного транзакционного domain service с блокировкой строк.
Team.clean() проверяет:
Application.participation_mode == team;Team.captain == Application.user.
Это запрещает Team для individual и undecided Application.
TeamMember.clean() проверяет:
- captain member имеет статус
accepted; - его
userсовпадает сTeam.captain; - для первого принятия заполнен
joined_at.
Наличие captain TeamMember намеренно не проверяется при первом Team.save().
Такая проверка создала бы цикл: Team должна быть сохранена до создания
TeamMember, а TeamMember уже требует сохраненную Team.
Domain service выполняет последовательность атомарно:
- создать или обновить Application с
participation_mode=team; - создать Team с captain, совпадающим с
Application.user; - создать accepted TeamMember с
role=captainи тем же пользователем.
Клиенты используют существующий Application create endpoint с
participation_mode=team и необязательным team_name. При ошибке service
откатывает все три шага. Публичных Team serializers/views/URLs и управления
участниками по-прежнему нет.
Следующие PR должны добавить:
- Team permissions и публичный Team API;
- блокировку состава по Application status;
- TeamInvite, accept/decline/revoke/expire;
- email и внутренние уведомления;
- frontend wizard и вкладку команды.
Legacy Application withdraw, Project/projects.Collaborator,
invites.Invite и Submission flow этим service не изменяются.