Add class-teacher slots on SchoolClass with owner-only assign/clear.
EOF Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,22 +12,22 @@
|
||||
|
||||
## Задачи
|
||||
|
||||
- [ ] У `SchoolClass` опциональный `classTeacherId` (или эквивалент); сейв в ростере
|
||||
- [ ] Назначить / снять — HTTP через мейлбокс школы; только хозяин; гость `403`
|
||||
- [ ] Кандидат — нанятый с должностью `Teacher`; иначе отказ с текстом, не молчание
|
||||
- [ ] Один учитель может быть классным у нескольких классов
|
||||
- [ ] UI в «Управлении»: список классов со слотом классного (выбор / снять)
|
||||
- [ ] Имя классного видно там, где уже показывают класс (карточка ученика / управление)
|
||||
- [ ] Строки через `t(...)`, обе локали
|
||||
- [ ] Пустой слот — школа живёт как сейчас; собраний ещё нет
|
||||
- [x] У `SchoolClass` опциональный `classTeacherId` (или эквивалент); сейв в ростере
|
||||
- [x] Назначить / снять — HTTP через мейлбокс школы; только хозяин; гость `403`
|
||||
- [x] Кандидат — нанятый с должностью `Teacher`; иначе отказ с текстом, не молчание
|
||||
- [x] Один учитель может быть классным у нескольких классов
|
||||
- [x] UI в «Управлении»: список классов со слотом классного (выбор / снять)
|
||||
- [x] Имя классного видно там, где уже показывают класс (карточка ученика / управление)
|
||||
- [x] Строки через `t(...)`, обе локали
|
||||
- [x] Пустой слот — школа живёт как сейчас; собраний ещё нет
|
||||
|
||||
## Тесты, без которых фаза не закрыта
|
||||
|
||||
- [ ] Назначение учителя класса пишет id в ростер и переживает сейв / reload
|
||||
- [ ] Не-учитель и чужой id отклоняются
|
||||
- [ ] Гость не назначает (API)
|
||||
- [ ] Снятие очищает слот; класс без классного валиден
|
||||
- [ ] Два класса могут указать одного и того же учителя
|
||||
- [x] Назначение учителя класса пишет id в ростер и переживает сейв / reload
|
||||
- [x] Не-учитель и чужой id отклоняются
|
||||
- [x] Гость не назначает (API)
|
||||
- [x] Снятие очищает слот; класс без классного валиден
|
||||
- [x] Два класса могут указать одного и того же учителя
|
||||
|
||||
## Критерий готовности
|
||||
|
||||
|
||||
+39
-1
@@ -333,7 +333,9 @@ tick with the clock. Unknown school is `404` `unknown-school`; unknown person is
|
||||
Family links are other members of the same family: parents and siblings for a child, children
|
||||
and partners for a parent. The client opens another card by id; this response does not nest
|
||||
cards. `classId` is the homeroom class for a pupil and `null` otherwise — the personal timetable
|
||||
grid fetches `GET .../timetable?classId=` with it.
|
||||
grid fetches `GET .../timetable?classId=` with it. `classTeacherId` / `classTeacherName` come from
|
||||
that class's slot when a class teacher is assigned; both are null when the slot is empty or the
|
||||
person has no class.
|
||||
|
||||
`activity` is the ActionDef name currently in progress, or `null` when idle. `activityLabel`
|
||||
is that def in the request locale. HTTP JSON is additive — no protocol version bump.
|
||||
@@ -376,6 +378,8 @@ compute thresholds. There is no school-wide opinions endpoint.
|
||||
"classYear": 5,
|
||||
"classLetter": "А",
|
||||
"classId": "class-classroom-105",
|
||||
"classTeacherId": "f3.p1",
|
||||
"classTeacherName": "Иванова Ольга Михайловна",
|
||||
"body": [{ "id": "Height", "label": "Рост", "value": "164" }],
|
||||
"skills": [{ "id": "Math", "label": "Математика", "value": "62" }],
|
||||
"traits": [{ "defName": "Diligent", "label": "Усидчивый" }],
|
||||
@@ -694,10 +698,23 @@ one.
|
||||
"gradeMax": 11,
|
||||
"hoursPerWeek": 5
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"id": "c5A",
|
||||
"year": 5,
|
||||
"letter": "А",
|
||||
"classTeacherId": "f3.p1",
|
||||
"classTeacherName": "Иванова Ольга Михайловна"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`classes` lists every homeroom with an optional class-teacher slot. `classTeacherId` /
|
||||
`classTeacherName` are null when the slot is empty. Assign and clear (below) return this same
|
||||
payload so the Management panel can refresh the slots without a second GET.
|
||||
|
||||
Applicants here are the same people as in `saves/{id}.people.json`. A parent keeps the same
|
||||
id on the roster; hiring them sets `isStaff` on that person and does not create a second
|
||||
entity. Generated candidates (`aN.p0`) join the roster only when hired. `skills` on an
|
||||
@@ -747,6 +764,27 @@ and the same `?lang=ru|en`.
|
||||
Removes one assignment. Payroll drops when the subject was not the only one. Same success
|
||||
payload and `?lang=ru|en` as the other two. Unknown assignment is `404` `unknown-assignment`.
|
||||
|
||||
### `PUT /api/schools/{id}/classes/{classId}/class-teacher`
|
||||
|
||||
Body: `{ "personId": "f3.p1" }`. Sets the class-teacher slot on that class. Only a hired
|
||||
person with position `Teacher` is accepted — not a new `PositionDef`. One teacher may cover
|
||||
several classes. Goes through the school's mailbox; only the owner may call it (`403`
|
||||
`not-owner`). Success returns the same payload as `GET .../staffing` (including `classes`),
|
||||
so it takes the same `?lang=ru|en`.
|
||||
|
||||
| Status | `code` | When |
|
||||
| --- | --- | --- |
|
||||
| `403` | `not-owner` | The session is not the school's owner. |
|
||||
| `404` | `unknown-school` | No school with that id. |
|
||||
| `404` | `unknown-class` | `classId` is not in the roster. |
|
||||
| `404` | `unknown-person` | `personId` is not on the roster. |
|
||||
| `400` | `not-teacher` | Person is not hired staff with position `Teacher`. |
|
||||
|
||||
### `DELETE /api/schools/{id}/classes/{classId}/class-teacher`
|
||||
|
||||
Clears the class-teacher slot. An empty slot is valid — the class lives as before; parent
|
||||
meetings (later) simply do not start. Same success payload and owner gate as the PUT.
|
||||
|
||||
### `GET /api/schools/{id}/timetable`
|
||||
|
||||
The published lesson table and uncovered hours. Optional `?classId=` or `?personId=` filter
|
||||
|
||||
Reference in New Issue
Block a user