feat: 업무(Task) 모듈 4단계 — CRUD·상태머신·진행률 집계·역할 기반 상세 UI
백엔드
- Task/ChecklistItem 엔티티(저장소 내 순번 seq, jsonb content, assignees ManyToMany, checklist)
- GET/POST/PATCH/DELETE /repos/:repoId/tasks[/:taskId] + POST .../status
- 상태 머신(todo→prog→review→done, review→changes, changes→{review,prog}) + 전이별 역할 검증
- 담당자=저장소 멤버만 지정, 승인 완료(→done) 시 체크리스트 일괄 완료(영속)
- 저장소 doneCount/totalCount·멤버 taskCount 실집계(RepoModule→TaskModule)
- Task.repo FK 컬럼명 snake_case 고정(@JoinColumn), task.service 단위 테스트
프론트
- types/task·useTask·task.store, format.ts 라벨 파생(상태/마감/dday)
- RepoDetailPage 업무 목록, TaskCreatePage 생성/수정 겸용(편집 라우트)
- TaskDetailPage 역할 기반 액션 분기(지시자 승인/수정요청, 담당자 시작/승인요청)
- 액션 카드·상태 뱃지 디자인 시안 정렬, 작성폼/목록 UI 정리
문서: api-contract·integration-progress 4단계 반영
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+56
-26
@@ -149,44 +149,73 @@ status 매핑보다 우선해 그대로 표준 응답으로 내려보내고, 프
|
||||
|
||||
| 메서드 | 경로 | 요청 | 응답 | 권한 |
|
||||
|---|---|---|---|---|
|
||||
| GET | `.../members` | — | `RepoMember[]` | member+ |
|
||||
| POST | `.../members` | `{ email, roleType, subRole? }` | `RepoMember` | admin |
|
||||
| PATCH | `.../members/:userId` | `{ roleType?, subRole? }` | `RepoMember` | admin |
|
||||
| GET | `.../members` | — | `RepoMember[]` | 인증(조직 모델상 누구나 열람) |
|
||||
| POST | `.../members` | `{ email, roleType }` | `RepoMember` | admin |
|
||||
| PATCH | `.../members/:userId` | `{ roleType? }` | `RepoMember` | admin |
|
||||
| DELETE | `.../members/:userId` | — | `null` | admin (owner 제거 불가) |
|
||||
|
||||
`RepoMember`: `{ user: User, email, subRole, taskCount, roleType('admin'|'member'), owner }`
|
||||
(`isYou` 는 프론트가 현재 사용자와 비교해 산출)
|
||||
`RepoMember`: `{ user: User, email, taskCount, roleType('admin'|'member'), owner }`
|
||||
(`isYou` 는 프론트가 현재 사용자와 비교해 산출. `taskCount` 는 4단계에서 사용자별 담당 업무 수로 실집계)
|
||||
> **가시성 정책**: 멤버십은 **쓰기/권한과 업무 담당 자격**에만 쓰이고, 저장소·멤버·업무의 **조회는 인증 사용자면 누구나 가능**(단일 조직 모델). 비멤버를 가리는 가시성 가드는 도입하지 않음.
|
||||
> 매핑 페이지: RepoMembersPage(초대 모달, 역할 변경, 제거).
|
||||
|
||||
#### 사용자 검색 (`/api/users`) — 멤버 초대 자동완성
|
||||
|
||||
| 메서드 | 경로 | 요청 | 응답 | 권한 |
|
||||
|---|---|---|---|---|
|
||||
| GET | `/users` | `?q=<검색어>` | `User[]`(PublicUser) | 인증 |
|
||||
|
||||
- `q` 있으면 **이메일/이름 부분일치(대소문자 무시)**, 없으면 전체 목록(이름순, 최대 50). 응답은 `PublicUser`(`{ id, email, name, role }`).
|
||||
- 초대 모달의 이메일 입력에서 호출: 포커스 시 전체, 입력 시 부분일치 목록을 띄우고 선택하면 이메일이 채워진다. **이미 저장소 멤버인 사용자는 프론트가 목록에서 제외**(현재 멤버 목록과 id 비교).
|
||||
|
||||
---
|
||||
|
||||
## 4. 업무 (`/api/repos/:repoId/tasks`) — **4단계**
|
||||
## 4. 업무 (`/api/repos/:repoId/tasks`) — **4단계 ✅ 구현 완료**
|
||||
|
||||
| 메서드 | 경로 | 요청 | 응답 |
|
||||
|---|---|---|---|
|
||||
| GET | `.../tasks` | `?status&assignee` | `RepoTask[]` |
|
||||
| POST | `.../tasks` | `CreateTaskDto` | `TaskDetail` |
|
||||
| GET | `.../tasks/:taskId` | — | `TaskDetail` |
|
||||
| PATCH | `.../tasks/:taskId` | `UpdateTaskDto` | `TaskDetail` |
|
||||
| DELETE | `.../tasks/:taskId` | — | `null` |
|
||||
| POST | `.../tasks/:taskId/status` | `{ status }` | `TaskDetail` |
|
||||
| 메서드 | 경로 | 요청 | 응답 | 권한 |
|
||||
|---|---|---|---|---|
|
||||
| GET | `.../tasks` | `?status&assignee` | `RepoTask[]` | 인증 |
|
||||
| POST | `.../tasks` | `CreateTaskDto` | `TaskDetail` | admin |
|
||||
| GET | `.../tasks/:taskId` | — | `TaskDetail` | 인증 |
|
||||
| PATCH | `.../tasks/:taskId` | `UpdateTaskDto` | `TaskDetail` | admin |
|
||||
| DELETE | `.../tasks/:taskId` | — | `null` | admin |
|
||||
| POST | `.../tasks/:taskId/status` | `{ status }` | `TaskDetail` | 멤버(전이별 역할 검증) |
|
||||
|
||||
- `:taskId` 는 **저장소 내 순번(seq)** — 정수(`ParseIntPipe`). 전역 PK 는 별도 uuid.
|
||||
- 목록 정렬: seq 내림차순(최신 우선). `?status`/`?assignee`(userId) 필터.
|
||||
|
||||
상태(`TaskStatus`): `todo | prog | review | done | changes`. 전이 규칙(상태 머신):
|
||||
|
||||
```
|
||||
todo ─시작→ prog ─승인요청→ review ─승인→ done
|
||||
▲ └─수정요청→ changes ─재요청→ review
|
||||
└──────────────────────────────────┘
|
||||
└──────────(changes→prog)───────────┘
|
||||
```
|
||||
|
||||
- `POST .../status` 는 일반 상태 변경.
|
||||
- **승인 워크플로우**(5단계와 함께): 담당자 "승인 요청"(prog→review), 지시자 "승인"(review→done) / "수정 요청"(review→changes).
|
||||
전이 허용 맵: `todo→[prog]`, `prog→[review]`, `review→[done,changes]`, `changes→[review,prog]`, `done→[]`.
|
||||
허용 외 전이는 `BIZ_001`(409).
|
||||
|
||||
`CreateTaskDto`: `{ title, content?: string[], assigneeIds: uuid[], dueDate?: ISO, checklist?: {text}[] }`
|
||||
`RepoTask`(목록 행): `{ id, title, status, dueDate, checklist: [done,total], commentCount, assignees: User[] }`
|
||||
`TaskDetail`: mock 의 `TaskDetail` 인터페이스와 동일 — 단 라벨류는 원시값으로:
|
||||
`{ id, repoId, repoName, title, status, content[], checklist[], comments[], activities[], assignees[], issuer, dueDate, files[], approval?, createdAt }`
|
||||
> 매핑 페이지: RepoDetailPage(목록), TaskCreatePage, TaskDetailPage(지시자), TaskAssigneePage(담당자, state 분기).
|
||||
**역할 검증**(`POST .../status`):
|
||||
- 승인/수정요청(**review 출발**: review→done, review→changes) = **지시자(issuer) 또는 admin**.
|
||||
- 그 외 작업 전이(시작 todo→prog, 승인요청 prog→review, 재요청 changes→review/prog) = **담당자(assignee) 또는 지시자/admin**.
|
||||
- 비멤버는 전이 불가(403). `from === next` 면 no-op(현재 상태 반환).
|
||||
|
||||
**부수효과**:
|
||||
- **담당자 지정은 저장소 멤버만 가능** — `assigneeIds` 에 비멤버 포함 시 `BIZ_001`(400, "담당자는 저장소 멤버만 지정할 수 있습니다.").
|
||||
- **승인 완료(→done) 시 체크리스트 항목 done 을 모두 true 로 영속 처리**(승인=완료의 부수효과).
|
||||
- 생성 시 status 는 항상 `todo`. 저장소 `doneCount/totalCount` 는 업무 status 집계(`done` 수/전체)로 실시간 반영, 멤버 `taskCount` 는 사용자별 담당 업무 수로 집계.
|
||||
|
||||
`CreateTaskDto`: `{ title, content?: string[], assigneeIds: uuid[](≥1), dueDate?: ISO, checklist?: {text}[] }`
|
||||
`UpdateTaskDto`: `{ title?, content?: string[], assigneeIds?: uuid[], dueDate?: ISO|null }` — 제공된 필드만 갱신(체크리스트 항목 편집은 5단계).
|
||||
`RepoTask`(목록 행): `{ id(seq), title, status, dueDate: ISO|null, checklist: [done,total], commentCount, assignees: User[] }`
|
||||
`TaskDetail`: `{ id(seq), repoId, repoName, title, status, content[], checklist: {id,text,done}[], assignees[], issuer: User|null, dueDate: ISO|null, createdAt, comments[], activities[], files[] }`
|
||||
> **comments/activities/files 는 5·6단계 영역** — 4단계 응답에서는 빈 배열로 내려간다. `approval` 객체도 5단계(현재 미포함).
|
||||
|
||||
> **프론트 매핑/동작**:
|
||||
> - 목록 진입은 항상 `…/tasks/:taskId`(**TaskDetailPage**) — 이 화면이 **현재 사용자 역할로 액션을 분기**한다(지시자: 승인/수정요청, 담당자: 업무 시작/승인 요청/재요청·승인 대기 중). `isIssuer`/`isAssignee` 로 판별.
|
||||
> - **수정**: 생성 폼(TaskCreatePage)을 편집 모드로 재사용 — 프론트 라우트 `…/tasks/:taskId/edit`(API 는 동일 PATCH). 기존 값 미리 채움, 체크리스트는 읽기 전용.
|
||||
> - 라벨류(dueLabel/dday/statusLabel)는 `format.ts`(`taskStatusLabel`/`taskDueInfo`)에서 원시값으로 파생.
|
||||
> - `TaskAssigneePage`(`/view`)는 mock '내 업무' 전용 잔존(7단계 정리). 댓글/AI 패널/첨부 업로드는 로컬 데모(5단계 실연동).
|
||||
|
||||
---
|
||||
|
||||
@@ -258,10 +287,11 @@ todo ─시작→ prog ─승인요청→ review ─승인→ done
|
||||
|
||||
```
|
||||
src/modules/
|
||||
auth/ (1) — User 엔티티, JWT 전략, Guard, 쿠키
|
||||
repo/ (2) — Repo 엔티티
|
||||
member/ (3) — RepoMember 조인 엔티티 (repo 모듈에 흡수 가능)
|
||||
task/ (4,5) — Task / ChecklistItem / Comment / Reply / Attachment
|
||||
auth/ (1) ✅ — User 엔티티, JWT 전략, Guard, 쿠키
|
||||
repo/ (2) ✅ — Repo 엔티티 (+ member 흡수, Gitea 동기화)
|
||||
member/ (3) ✅ — RepoMember 조인 엔티티 (repo 모듈에 흡수됨)
|
||||
task/ (4) ✅ — Task / ChecklistItem (Comment / Reply / Attachment 는 5단계)
|
||||
activity/ (6) — Activity 이벤트 (다른 모듈에서 emit)
|
||||
my-tasks/ (7) — 집계 전용 (task 모듈에 흡수 가능)
|
||||
```
|
||||
> 실제 배치: `member` 는 `repo` 모듈에 흡수(`MemberService`/`MemberController` + `RepoMember` 재사용). `task` 모듈은 4단계 완료(엔티티·CRUD·상태머신·집계), 5단계에서 Comment/Reply/Attachment 추가 예정. `RepoModule` 이 진행률·taskCount 집계를 위해 `TaskModule` 을 import(엔티티만 참조해 순환 없음).
|
||||
|
||||
Reference in New Issue
Block a user