Skip to content

Commit c4c6ce8

Browse files
authored
Merge pull request #646 from PROCOLLAB-github/refactor/modules
Refactor/modules
2 parents 30a6871 + fa1c7b6 commit c4c6ce8

19 files changed

Lines changed: 1366 additions & 406 deletions

docs/modules/industries.md

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,138 @@
11
# Industries
22

3-
TODO
3+
## Назначение
4+
5+
Industries - справочник отраслей, который используется для классификации
6+
проектов и фильтрации проектных данных.
7+
8+
Модуль не содержит сложной бизнес-логики: его основная задача - хранить
9+
`Industry` и отдавать список отраслей клиентам и другим модулям.
10+
11+
## Статус модуля
12+
13+
Модуль рабочий и подключен в публичный API через `/industries/`.
14+
15+
Есть небольшой долг:
16+
17+
- в модели нет уникальности `name`, поэтому одинаковые отрасли можно создать
18+
несколько раз.
19+
20+
## Основные возможности
21+
22+
- просмотр списка отраслей;
23+
- просмотр одной отрасли;
24+
- создание отрасли staff-пользователем;
25+
- обновление отрасли staff-пользователем;
26+
- удаление отрасли staff-пользователем;
27+
- управление отраслями через Django admin.
28+
29+
## Архитектура
30+
31+
- `industries/models.py` - модель `Industry`.
32+
- `industries/serializers.py` - `IndustrySerializer`.
33+
- `industries/views.py` - list/create и detail/update/delete endpoints.
34+
- `industries/urls.py` - routes модуля.
35+
- `industries/admin.py` - регистрация модели в Django admin.
36+
- `industries/tests/` - regression-тесты и helpers модуля.
37+
38+
## Основные сущности
39+
40+
### Industry
41+
42+
`Industry` описывает отрасль проекта.
43+
44+
Поля:
45+
46+
- `id` - идентификатор отрасли;
47+
- `name` - название отрасли, максимум 256 символов;
48+
- `datetime_created` - дата создания.
49+
50+
Сортировка по умолчанию: `name`.
51+
52+
Строковое представление:
53+
54+
```text
55+
Industry<id> - name
56+
```
57+
58+
## API
59+
60+
- `GET /industries/` - список отраслей.
61+
- `POST /industries/` - создание отрасли.
62+
- `GET /industries/<id>/` - детали отрасли.
63+
- `PUT /industries/<id>/` - полное обновление отрасли.
64+
- `PATCH /industries/<id>/` - частичное обновление отрасли.
65+
- `DELETE /industries/<id>/` - удаление отрасли.
66+
67+
Permissions:
68+
69+
- read operations доступны всем пользователям;
70+
- anonymous write operations возвращают `401`;
71+
- authenticated non-staff write operations возвращают `403`;
72+
- staff write operations разрешены;
73+
- фактически используется общий permission `core.permissions.IsStaffOrReadOnly`.
74+
75+
Response contract:
76+
77+
```json
78+
{
79+
"id": 1,
80+
"name": "IT",
81+
"datetime_created": "2026-01-01T00:00:00Z"
82+
}
83+
```
84+
85+
## Основные сценарии
86+
87+
### Пользователь выбирает отрасль проекта
88+
89+
Frontend получает список отраслей через `GET /industries/` и использует `id`
90+
отрасли при создании или обновлении проекта.
91+
92+
### Пользователь фильтрует проекты по отрасли
93+
94+
Проекты фильтруются по `industry` через модуль `projects`. Сам справочник
95+
только хранит отрасли и не содержит собственной логики фильтрации проектов.
96+
97+
### Администратор управляет справочником
98+
99+
Staff-пользователь может создавать, обновлять и удалять отрасли через API или
100+
Django admin.
101+
102+
## Связи с другими модулями
103+
104+
- `projects` - `Project.industry` ссылается на `Industry`.
105+
- `vacancy` - detail вакансии отдает проект вместе с отраслью проекта.
106+
- `partner_programs` - serializers программных проектов отдают отрасль проекта.
107+
- `project_rates` - serializers оценок проектов отдают отрасль проекта.
108+
- `news`, `projects` и другие тестовые helpers создают отрасли для связанных
109+
сценариев.
110+
111+
## Ограничения и риски
112+
113+
- `Industry.name` не уникален. Сейчас можно создать несколько отраслей с
114+
одинаковым названием.
115+
- При удалении отрасли у связанных проектов `Project.industry` становится
116+
`NULL`, потому что связь настроена через `on_delete=SET_NULL`.
117+
- Удаление или переименование отрасли может повлиять на фильтрацию и отображение
118+
проектов на frontend.
119+
120+
## Тесты
121+
122+
Текущие тесты лежат в `industries/tests/`.
123+
124+
Проверяется:
125+
126+
- публичный список отраслей через реальный URL;
127+
- публичный detail отрасли через реальный URL;
128+
- 404 для отсутствующей отрасли;
129+
- создание отрасли staff-пользователем;
130+
- запрет создания отрасли anonymous-пользователем;
131+
- запрет создания и обновления отрасли обычным пользователем;
132+
- обновление отрасли staff-пользователем;
133+
- удаление отрасли staff-пользователем;
134+
- отвязка проектов от удаленной отрасли через `on_delete=SET_NULL`;
135+
- ошибка при отсутствующем `name`;
136+
- ошибка при пустом `name`;
137+
- ошибка при слишком длинном `name`;
138+
- строковое представление `Industry`.

docs/modules/invites.md

Lines changed: 217 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,219 @@
11
# Invites
22

3-
TODO
3+
## Назначение
4+
5+
Invites отвечает за приглашения пользователей в команду проекта.
6+
7+
Модуль закрывает сценарий, когда лидер проекта приглашает пользователя на роль в
8+
проекте, а пользователь принимает или отклоняет приглашение.
9+
10+
## Статус модуля
11+
12+
Модуль рабочий и подключен в публичный API через `/invites/`.
13+
14+
Приглашения доступны приглашенному пользователю, лидеру проекта и
15+
staff/superuser. Изменять или удалять приглашение может лидер проекта. Принять
16+
или отклонить приглашение может приглашенный пользователь.
17+
18+
## Основные возможности
19+
20+
- создание приглашения лидером проекта;
21+
- просмотр списка активных приглашений;
22+
- фильтрация приглашений по проекту и пользователю;
23+
- просмотр, обновление и удаление приглашения;
24+
- принятие приглашения пользователем;
25+
- отклонение приглашения пользователем;
26+
- автоматическое добавление пользователя в collaborators проекта после принятия;
27+
- запрет приглашения лидера проекта;
28+
- запрет приглашения пользователя, который уже состоит в проекте;
29+
- запрет повторного активного приглашения в тот же проект;
30+
- проверка участия пользователя в партнерской программе, если проект привязан к
31+
программе.
32+
33+
## Архитектура
34+
35+
- `invites/models.py` - модель `Invite`.
36+
- `invites/serializers.py` - serializers создания и чтения приглашений.
37+
- `invites/views.py` - API endpoints и основная orchestration logic.
38+
- `invites/filters.py` - фильтры списка приглашений.
39+
- `invites/managers.py` - queryset helper для списка.
40+
- `invites/querysets.py` - queryset видимых приглашений для текущего
41+
пользователя.
42+
- `invites/permissions.py` - object-level permissions для detail и
43+
accept/decline.
44+
- `invites/urls.py` - routes модуля.
45+
- `invites/admin.py` - регистрация приглашений в Django admin.
46+
- `invites/tests/` - regression-тесты и helpers модуля.
47+
48+
## Основные сущности
49+
50+
### Invite
51+
52+
`Invite` хранит приглашение пользователя в проект.
53+
54+
Поля:
55+
56+
- `project` - проект, в который приглашают пользователя;
57+
- `user` - приглашенный пользователь;
58+
- `motivational_letter` - текст приглашения;
59+
- `role` - роль пользователя в проекте после принятия;
60+
- `specialization` - специализация пользователя после принятия;
61+
- `is_accepted` - статус приглашения:
62+
- `None` - ожидает решения;
63+
- `True` - принято;
64+
- `False` - отклонено;
65+
- `datetime_created` - дата создания;
66+
- `datetime_updated` - дата обновления.
67+
68+
Сортировка по умолчанию: новые приглашения выше.
69+
70+
## API
71+
72+
- `GET /invites/` - список активных приглашений.
73+
- `POST /invites/` - создание приглашения.
74+
- `GET /invites/<id>/` - детали приглашения.
75+
- `PUT /invites/<id>/` - полное обновление приглашения.
76+
- `PATCH /invites/<id>/` - частичное обновление приглашения.
77+
- `DELETE /invites/<id>/` - удаление приглашения.
78+
- `POST /invites/<id>/accept/` - принять приглашение.
79+
- `POST /invites/<id>/decline/` - отклонить приглашение.
80+
81+
Фильтры списка:
82+
83+
- `project` - фильтр по проекту;
84+
- `user` - фильтр по пользователю;
85+
- `user=any` - staff-only фильтр для отключения пользовательского фильтра.
86+
87+
Список всегда ограничен активными приглашениями: `is_accepted IS NULL`.
88+
89+
## Доступ и права
90+
91+
- список приглашений доступен только authenticated пользователю;
92+
- приглашенный пользователь видит свои приглашения;
93+
- лидер проекта видит приглашения своего проекта через `project=<id>`;
94+
- staff/superuser может получить все активные приглашения через `user=any`;
95+
- `user=<id>` работает только внутри уже разрешенной области видимости;
96+
- `user=any` запрещен для обычных пользователей и лидеров проекта;
97+
- detail приглашения видят только приглашенный пользователь, лидер проекта и
98+
staff/superuser;
99+
- обновить или удалить приглашение может только лидер проекта;
100+
- принять или отклонить приглашение может только приглашенный пользователь;
101+
- повторный `accept` или `decline` по уже обработанному приглашению возвращает
102+
`409 Conflict` и не меняет финальный статус.
103+
104+
## Основные сценарии
105+
106+
### 1. Лидер проекта создает приглашение
107+
108+
Лидер проекта отправляет `POST /invites/` с проектом, пользователем, ролью,
109+
специализацией и текстом приглашения.
110+
111+
При создании:
112+
113+
- serializer проверяет, что пользователь не является лидером проекта;
114+
- serializer проверяет, что пользователь еще не collaborator проекта;
115+
- serializer запрещает повторное активное приглашение в тот же проект;
116+
- если проект привязан к партнерской программе, пользователь должен быть
117+
участником этой программы;
118+
- view отдельно проверяет, что текущий пользователь является лидером проекта.
119+
120+
### 2. Пользователь видит свои приглашения
121+
122+
Пользователь получает список через `GET /invites/`.
123+
124+
По умолчанию фильтр `user` подставляется из `request.user`, поэтому
125+
приглашенный пользователь видит свои активные приглашения. Лидер проекта
126+
получает активные приглашения проекта через `GET /invites/?project=<id>`. Staff
127+
или superuser может работать со всем списком через `GET /invites/?user=any`.
128+
129+
### 3. Пользователь принимает приглашение
130+
131+
Пользователь вызывает `POST /invites/<id>/accept/`.
132+
133+
При успешном принятии:
134+
135+
- проверяется, что приглашение принимает именно приглашенный пользователь;
136+
- если приглашение уже принято или отклонено, возвращается conflict;
137+
- пользователь добавляется в `Collaborator` проекта;
138+
- в collaborator переносятся `role` и `specialization` из приглашения;
139+
- приглашение получает `is_accepted=True`.
140+
141+
### 4. Пользователь отклоняет приглашение
142+
143+
Пользователь вызывает `POST /invites/<id>/decline/`.
144+
145+
При успешном отклонении:
146+
147+
- проверяется, что приглашение отклоняет именно приглашенный пользователь;
148+
- если приглашение уже принято или отклонено, возвращается conflict;
149+
- приглашение получает `is_accepted=False`.
150+
151+
### 5. Приглашение влияет на доступ к проекту
152+
153+
В `projects.permissions` приглашенный пользователь считается вовлеченным в
154+
проект. Это дает доступ к непубличному или draft-проекту до принятия
155+
приглашения.
156+
157+
## Связи с другими модулями
158+
159+
- `projects` - приглашение всегда связано с проектом; после принятия создается
160+
`Collaborator`.
161+
- `users` - приглашение связано с приглашенным пользователем и лидером проекта.
162+
- `partner_programs` - если проект связан с партнерской программой, приглашать
163+
можно только участника этой программы.
164+
- `projects.permissions` - invite участвует в проверках доступа к непубличным и
165+
draft-проектам.
166+
167+
## Ограничения и риски
168+
169+
- Проверка участия в партнерской программе смотрит только первую связь
170+
`project.program_links.first()`. Если проект может быть связан с несколькими
171+
программами, этот контракт нужно уточнить.
172+
- Нет DB-level constraint для запрета нескольких активных приглашений одного
173+
пользователя в один проект. Сейчас это защищено только serializer validation.
174+
- Вся orchestration logic находится во views/serializers, отдельного service
175+
layer пока нет.
176+
177+
## Тесты
178+
179+
Текущие тесты лежат в `invites/tests/`.
180+
181+
Проверяется:
182+
183+
- создание приглашения лидером проекта;
184+
- создание приглашения без motivational letter;
185+
- запрет создания приглашения пользователем, который не является лидером
186+
проекта;
187+
- ошибка при пустом payload;
188+
- запрет приглашения лидера проекта;
189+
- запрет приглашения существующего collaborator;
190+
- запрет повторного активного приглашения в тот же проект;
191+
- запрет приглашения пользователя, который не является участником программы
192+
проекта;
193+
- создание приглашения для участника программы, если проект привязан к
194+
программе;
195+
- список активных приглашений текущего пользователя;
196+
- фильтр списка по проекту: лидер проекта видит все активные приглашения этого
197+
проекта;
198+
- фильтр списка по пользователю внутри разрешенной области видимости;
199+
- `user=any` доступен только staff/superuser;
200+
- запрет anonymous list;
201+
- права чтения detail: приглашенный пользователь, лидер проекта и staff имеют
202+
доступ, outsider и anonymous не имеют;
203+
- права update/delete: только лидер проекта может менять или удалять
204+
приглашение;
205+
- принятие приглашения приглашенным пользователем с созданием collaborator и
206+
переносом `role` / `specialization`;
207+
- отклонение приглашения приглашенным пользователем;
208+
- запрет accept/decline чужим пользователем;
209+
- запрет accept/decline лидером проекта вместо приглашенного пользователя;
210+
- conflict при повторном accept уже обработанного приглашения;
211+
- conflict при повторном decline уже обработанного приглашения;
212+
- защита decline после accept: статус остается принятым, collaborator остается
213+
в проекте;
214+
- строковое представление `Invite`.
215+
216+
Сейчас не покрыты:
217+
218+
- DB-level constraint для повторных активных приглашений;
219+
- сценарий проекта, связанного с несколькими партнерскими программами.

0 commit comments

Comments
 (0)