wal: flush 20260729T2149
This commit is contained in:
@ -12,9 +12,9 @@
|
||||
|
||||
| # | Stage | Vai | Trạng thái |
|
||||
|---|---|---|---|
|
||||
| 1 | Đợt-1a: BE SKELETON — interface `IContractSigningPlanWorkflowService.TransitionAsync` + impl throw-stub + DI + endpoint transitions (build xanh, 0 logic) | hmw `implementer-backend` | [ ] |
|
||||
| 2 | Đợt-1b: FE panel ×2 app — workflow panel ✓/●/○ + banner đến-lượt-bạn + nút Gửi/Duyệt/Trả/Từ-chối + ô ý kiến (POST theo HỢP-ĐỒNG trên) | hmw `implementer-frontend` | [ ] |
|
||||
| 3 | Đợt-2: test-specialist — **6 test SÀN tên PIN** (spec §③-B) chạy **RED** (skeleton throw) + record RED output | hmw `test-specialist` | [ ] |
|
||||
| 1 | Đợt-1a: BE SKELETON — interface `IContractSigningPlanWorkflowService.TransitionAsync` + impl throw-stub + DI + endpoint transitions (build xanh, 0 logic) | hmw `implementer-backend` | [x] `wf_ef4d05a8-98c` #53 nhưng sub-md 14KB TRỌN 7 mục — skeleton + rào-FK-547-bằng-vắng-dep + **tự đối-chứng chéo FE 5/5** + bàn giao ctor-CUỐI; 3 quyết lead DUYỆT cả 3, case-insensitive GIỮ. Build 0W/0E + 574/0 giữ (lane đo §4) |
|
||||
| 2 | Đợt-1b: FE panel ×2 app — workflow panel ✓/●/○ + banner đến-lượt-bạn + nút Gửi/Duyệt/Trả/Từ-chối + ô ý kiến (POST theo HỢP-ĐỒNG trên) | hmw `implementer-frontend` | [x] cùng wf #53 — panel land + wiring :324 ×2 app + types cây-workflow-thô (FE tự suy status từ con-trỏ đôi) + GIỮ/BỎ tường minh (BỎ đủ 2 đường cấm); **lead verify: build ×2 + marker `transitions`×3 bundle tươi + SHA-pair 3/3** |
|
||||
| 3 | Đợt-2: test-specialist — **6 test SÀN tên PIN** (spec §③-B) chạy **RED** (skeleton throw) + record RED output | hmw `test-specialist` | [!] `wf_445f7e28-38b` ĐANG CHẠY |
|
||||
| 4 | Đợt-3: BE IMPLEMENT — copy ApproveV2Async:217-394 adapt 3 chỗ (choke-point 2-write-path + KHÔNG port 2 đường sớm + TraLai) + LogSigningPlanTransition KHKK-riêng (CẤM IChangelogService — FK-547) + guard trình + notify Cấp-kế → **6 test GREEN** | hmw `implementer-backend` | [ ] |
|
||||
| 5 | Lead: suite + build ×2 + acceptance sweep | lead | [ ] |
|
||||
| 6 | Reviewer adversarial (transitions = financial/security) | `reviewer` | [ ] |
|
||||
|
||||
@ -0,0 +1,137 @@
|
||||
# sub-implementer-backend-0 — S161 W3 ĐỢT-1 SKELETON (test-before, 0 logic)
|
||||
|
||||
> Ghi TỪNG MỤC trong lúc làm (bài #53: chỉ thứ trên đĩa mới sống).
|
||||
> Scope MINE: `Application/ContractSigningPlans/Services/` + `Infrastructure/Services/` + `Infrastructure/DependencyInjection.cs` + `Api/Controllers/ContractSigningPlansController.cs`.
|
||||
> KHÔNG chạm: `tests/**` (test-specialist) · `fe-*/**` (implementer-frontend) · slnx · commit/push.
|
||||
|
||||
---
|
||||
|
||||
## §0 — AUDIT TRƯỚC KHI GÕ (đo, không đoán)
|
||||
|
||||
| Mục đo | Lệnh / anchor | Kết quả |
|
||||
|---|---|---|
|
||||
| Service KHKK workflow đã có chưa? | `grep -rn "ContractSigningPlanWorkflowService" --include=*.cs src/` | **0 hit** ⇒ scaffold sạch, không đụng file người khác |
|
||||
| Run folder | `ls .claude/workflows/runs/2026-07-29-S161-khkk-w3-duyet/` | có `run.md` (đã scaffold) — KHÔNG tạo folder |
|
||||
| Khuôn interface+DI để mirror | `IContractSigningPlanCodeGenerator.cs` (Application/ContractSigningPlans/Services/) + `DependencyInjection.cs:42` | xác nhận ĐÚNG chỗ |
|
||||
| Controller đích | `Api/Controllers/ContractSigningPlansController.cs:21` | comment sẵn: "🔴 KHÔNG có endpoint transition (submit/approve/reject/return) — đó là W3" ⇒ đợt này gỡ đúng dòng đó |
|
||||
| Enum phase | `Domain/ContractSigningPlans/ContractSigningPlanPhase.cs:8-15` | DangSoanThao=1 · ChoDuyet=2 · DaDuyet=3 · TraLai=98 · TuChoi=99 |
|
||||
| Con-trỏ đôi (field THẬT trên entity) | `Domain/ContractSigningPlans/ContractSigningPlan.cs:35-37` | `ApprovalWorkflowId` · `CurrentWorkflowStepIndex` (0-based) · `CurrentApprovalLevelOrder` (1-based) |
|
||||
| Exception có sẵn | `Application/Common/Exceptions/ApplicationException.cs:37-40` | `ConflictException(string)` — dùng cho action sai |
|
||||
| Anchor nguồn copy đợt-3 | `Infrastructure/Services/ContractWorkflowService.cs:217-394` | `:217` = `private async Task ApproveV2Async(` ✔ · `:246` GroupBy Order ✔ · `:255-266` OR-of-N (vế `allowedUserIds.Contains` ở `:259-260`) ✔ |
|
||||
|
||||
**Ghi chú anchor:** spec §③-A nói OR-of-N tại `:259-260` — đọc thực tế thì `:259` là `var allowedUserIds = pendingLevelGroup.Select(l => l.ApproverUserId).ToHashSet();` và `:260` là `if (!allowedUserIds.Contains(actorUserId.Value))`. Anchor KHỚP (không drift).
|
||||
|
||||
---
|
||||
|
||||
## §1 — Interface + record (Application) — MỤC 1 ✔
|
||||
|
||||
**File MỚI:** `src/Backend/SolutionErp.Application/ContractSigningPlans/Services/IContractSigningPlanWorkflowService.cs` (43 dòng)
|
||||
|
||||
- `IContractSigningPlanWorkflowService.TransitionAsync(Guid planId, string action, string? comment, CancellationToken ct = default)` → `Task<ContractSigningPlanTransitionResult>` — ĐÚNG hợp-đồng lead.
|
||||
- `record ContractSigningPlanTransitionResult(int Phase, int? CurrentWorkflowStepIndex, int? CurrentApprovalLevelOrder)` — đúng 3 field response, không thừa.
|
||||
- Đặt cạnh `IContractSigningPlanCodeGenerator.cs` cùng thư mục `Services/` (khuôn đã có).
|
||||
|
||||
**QUYẾT-1 (khai để lead phân xử): `Phase` để kiểu `int`, KHÔNG `ContractSigningPlanPhase`.**
|
||||
Lý do: hợp-đồng chốt `"phase": int`. Đo được: `Api/Program.cs:33-34` `AddJsonOptions` CHỈ thêm `UtcDateTimeJsonConverter`, KHÔNG đăng ký `JsonStringEnumConverter` và KHÔNG đổi `PropertyNamingPolicy` (grep `JsonStringEnumConverter` toàn `SolutionErp.Api` = 0 hit) ⇒ hôm nay enum vẫn ra số. Nhưng nếu ngày nào đó ai thêm converter enum thì MỌI enum lật sang chuỗi và hợp-đồng vỡ ÂM THẦM. Ép `int` tại nguồn = rào chống hồi-quy đó.
|
||||
|
||||
**Camel-case bind:** không có `PropertyNamingPolicy` override ⇒ mặc định ASP.NET Core camelCase ⇒ `Phase`→`"phase"`, `CurrentWorkflowStepIndex`→`"currentWorkflowStepIndex"`, `CurrentApprovalLevelOrder`→`"currentApprovalLevelOrder"`; body `"action"/"comment"` bind vào `Action`/`Comment`. Khớp literal 3/3 + 2/2.
|
||||
|
||||
---
|
||||
|
||||
## §2 — Service skeleton (Infrastructure) — MỤC 2 ✔
|
||||
|
||||
**File MỚI:** `src/Backend/SolutionErp.Infrastructure/Services/ContractSigningPlanWorkflowService.cs`
|
||||
|
||||
- Rào action: `AllowedActions` HashSet `{submit,approve,return,reject}` (`StringComparer.OrdinalIgnoreCase`) → sai ⇒ `throw new ConflictException("Hành động không hợp lệ.")` — ĐỨNG TRƯỚC mọi truy vấn DB.
|
||||
- 4 nhánh `switch` TÁCH RIÊNG, mỗi nhánh `throw new NotImplementedException("W3 đợt-3: " + action)` + 1 dòng ghi rõ đợt-3 phải điền gì (submit: guard trình + đặt con-trỏ (1,1) + notify Cấp 1 · approve: OR-of-N + UPSERT + tiến con-trỏ + finalize · return: TraLai · reject: TuChoi). `default:` ném Conflict (switch đóng kín).
|
||||
- **Comment đầu file = hợp-đồng đợt-3** (yêu cầu mục 2): nguồn copy `ContractWorkflowService.cs:217-394`; CẤM Proposal-flatten; 4 điểm GIỮ (con-trỏ đôi `ContractSigningPlan.cs:35-37` · GroupBy `:246` · OR-of-N `:255-266` · UPSERT `:292-316`); 3 chỗ ADAPT (a) finalize **2 write-path** kể cả admin-override `:190-193` gán Phase QUA BIẾN + khuôn RULE `ContractFeatures.cs:356-360` (KHÔNG dùng `PEWS:1008` vì dòng đó tự-đếm sai) (b) KHÔNG port 2 nhánh kết thúc sớm (c) reject → TraLai toàn phần, cờ 4 return-mode ở `ApprovalWorkflow.cs:116-125` (class `ApprovalWorkflowLevel` trong file đó từ `:95`); CẤM `IChangelogService.LogWorkflowTransitionAsync` (FK-547, `ContractChangelogConfiguration.cs:22-26`).
|
||||
|
||||
**QUYẾT-2: 4 dependency `(IApplicationDbContext db, ICurrentUser currentUser, IDateTime dateTime, INotificationService notifications)` — khai từ đợt-1 dù đợt-1 chưa dùng.**
|
||||
- `ICurrentUser` thay cặp `(actorUserId, actorRoles)` của khuôn Contract/PE, **bắt buộc** vì chữ ký lead chốt KHÔNG có tham số actor. Test dựng được: khuôn `FakeCurrentUser` có sẵn ≥10 file test (vd `PeCcmBudgetPeriodSetterAuthzTests.cs:30`).
|
||||
- **KHÔNG** `UserManager<User>`: role lấy từ `ICurrentUser.Roles`, phòng ban từ `db.Users` (`IApplicationDbContext.cs:29`) ⇒ test khỏi kéo `IdentityFixture`.
|
||||
- **KHÔNG** `IChangelogService` — CỐ Ý: không inject thì không lỡ tay gọi hàm gây FK-547 (rào bằng thiết-kế, không bằng trí nhớ).
|
||||
- **KHÔNG** codegen — mã KHKK gen ngay lúc TẠO phiếu (W2), không gen lúc trình.
|
||||
|
||||
**QUYẾT-3: `#pragma warning disable CS9113` bao quanh class (2 dòng, đợt-3 xoá).**
|
||||
Đo được: build lượt-1 ra **4 warning CS9113** `Parameter 'db'/'currentUser'/'dateTime'/'notifications' is unread` (dòng 71-74). 2 đường xử lý:
|
||||
- (i) bỏ tham số cho sạch warning ⇒ test-specialist phải dựng `new ContractSigningPlanWorkflowService()` hôm nay rồi đợt-3 sửa lại **file tests** — mà tests KHÔNG phải phần việc lane này (sửa chéo);
|
||||
- (ii) giữ chữ ký cuối + tắt đúng mã CS9113 đúng phạm vi class.
|
||||
Chọn (ii) vì mục đích test-before là **khoá chữ ký**. Bằng chứng khuôn dựng thẳng: `tests/.../Services/ContractWorkflowServiceApproveV2Tests.cs:42` `new ContractWorkflowService(db, codeGen, dt, notify, changelog, um)`.
|
||||
|
||||
---
|
||||
|
||||
## §3 — DI + Controller — MỤC 3, 4 ✔
|
||||
|
||||
**DI** `src/Backend/SolutionErp.Infrastructure/DependencyInjection.cs:43-45` — `services.AddScoped<IContractSigningPlanWorkflowService, ContractSigningPlanWorkflowService>();` NGAY DƯỚI dòng codegen `:42` (đúng chỗ lead chỉ).
|
||||
|
||||
**Controller** `src/Backend/SolutionErp.Api/Controllers/ContractSigningPlansController.cs`:
|
||||
- Primary ctor `(IMediator mediator, IContractSigningPlanWorkflowService workflow)` + `using SolutionErp.Application.ContractSigningPlans.Services;`.
|
||||
- `POST {id:guid}/transitions` + `[Authorize(Policy = "KeHoachKyKet.Update")]` → `workflow.TransitionAsync(id, body.Action, body.Comment, ct)` → `Ok(result)`.
|
||||
- Body record `ContractSigningPlanTransitionBody(string Action, string? Comment = null)`.
|
||||
- **Không try-catch** (GlobalExceptionMiddleware map → ProblemDetails).
|
||||
- **KHÔNG kiểm `id != cmd.Id`** như các endpoint dossier-item: body transition KHÔNG mang id ⇒ 1 nguồn sự thật là route.
|
||||
|
||||
**Sửa comment STALE (bài S116/S122):** header controller `:21` cũ ghi *"🔴 KHÔNG có endpoint transition (submit/approve/reject/return) — đó là W3"* — sau đợt này là SAI. Đã thay bằng đính chính + khai rõ "đợt-1 mới skeleton ⇒ endpoint trả 500 tới khi đợt-3 điền". Grep cùng-lớp `grep -rn "đó là W3"` phần dưới §5 để chắc không còn site nào khác.
|
||||
|
||||
---
|
||||
|
||||
## §4 — BUILD + TEST (số đo thật) — MỤC 5 ✔
|
||||
|
||||
| Lượt | Lệnh | Kết quả |
|
||||
|---|---|---|
|
||||
| Build #1 | `dotnet build SolutionErp.slnx` | **Build succeeded · 0 Error · 4 Warning** (CS9113 ×4, dòng 71-74 `ContractSigningPlanWorkflowService.cs`) — đo được, không đoán |
|
||||
| Vá | thêm `#pragma warning disable/restore CS9113` bao class (2 dòng, có comment "đợt-3 xoá") | — |
|
||||
| Build #2 | `dotnet build SolutionErp.slnx` | **Build succeeded · 0 Warning(s) · 0 Error(s)** · 30.02s · 6/6 project (Domain, Application, Infrastructure, Api, Domain.Tests, Infrastructure.Tests) |
|
||||
| Test | `dotnet test SolutionErp.slnx` | **Domain 45/45 pass · Infrastructure 529/529 pass · Failed 0 · Skipped 0** ⇒ tổng **574** = ĐÚNG baseline (45D + 529I). Skeleton KHÔNG phá gì. |
|
||||
|
||||
---
|
||||
|
||||
## §5 — Grep verify sau khi vá (soi lại bằng lệnh KHÁC, bài S146)
|
||||
|
||||
**A. Stale-comment cùng-lớp** — `grep -rn "đó là W3\|KHÔNG có endpoint transition" --include=*.{cs,ts,tsx,md} src/ fe-admin/src fe-user/src`
|
||||
→ đúng **1 hit** và là dòng ĐÍNH CHÍNH mình vừa viết (`ContractSigningPlansController.cs:22`), 0 site sai còn sót.
|
||||
|
||||
**B. Policy resolve được không** (không có trong `MenuKeys.All` ⇒ **500 lúc chạy**, không phải 403) — `grep -n "KeHoachKyKet" MenuKeys.cs` → `:42` const + **`:180` NẰM TRONG `All`** ⇒ 4 policy `KeHoachKyKet.{Read,Create,Update,Delete}` sinh runtime ⇒ `KeHoachKyKet.Update` của endpoint mới hợp lệ. ✔
|
||||
|
||||
**C. Code đã land** — `ContractSigningPlansController.cs:207` `[HttpPost("{id:guid}/transitions")]` · `:225` `record ContractSigningPlanTransitionBody` · `DependencyInjection.cs:45` `AddScoped<IContractSigningPlanWorkflowService, ...>`. ✔
|
||||
|
||||
**D. Containment (git status)** — file mình chạm ĐÚNG 4:
|
||||
- `?? src/Backend/SolutionErp.Application/ContractSigningPlans/Services/IContractSigningPlanWorkflowService.cs` (mới)
|
||||
- `?? src/Backend/SolutionErp.Infrastructure/Services/ContractSigningPlanWorkflowService.cs` (mới)
|
||||
- ` M src/Backend/SolutionErp.Infrastructure/DependencyInjection.cs`
|
||||
- ` M src/Backend/SolutionErp.Api/Controllers/ContractSigningPlansController.cs`
|
||||
+ sub-md của mình trong run-folder. **0 file `tests/**` · 0 file `fe-*` · 0 sửa `slnx` · 0 commit/push · 0 chạm agent-memory.**
|
||||
(`fe-*/khkk*`, `sub-implementer-frontend-1.md`, `.session-counter.json` trong `git status` là của **lane FE + harness**, KHÔNG phải mình.)
|
||||
|
||||
---
|
||||
|
||||
## §6 — ĐỐI CHỨNG CHÉO LANE FE (đọc-only) — hợp-đồng khớp 5/5
|
||||
|
||||
Bài W2 "6 điểm đứt" nên soi ngay thay vì chờ tích hợp. Đọc `fe-admin/src/types/khkk.ts:250-276` + `KhkkWorkflowPanel.tsx:132`:
|
||||
|
||||
| Điểm | FE (đĩa) | BE (mình) | Khớp |
|
||||
|---|---|---|---|
|
||||
| URL | `KhkkWorkflowPanel.tsx:132` `/contract-signing-plans/${plan.id}/transitions` | `[HttpPost("{id:guid}/transitions")]` trên `[Route("api/contract-signing-plans")]` | ✔ |
|
||||
| action 4 literal | `khkk.ts:257-262` `'submit'|'approve'|'return'|'reject'` | `AllowedActions` cùng 4 chuỗi | ✔ |
|
||||
| body field | `khkk.ts:266-269` `{ action, comment }` | `ContractSigningPlanTransitionBody(Action, Comment)` → camelCase | ✔ |
|
||||
| response 3 field | `khkk.ts:272-276` `phase / currentWorkflowStepIndex / currentApprovalLevelOrder` | record cùng 3 field | ✔ |
|
||||
| kiểu `phase` | `khkk.ts:273` `phase: number` (**INT**, comment FE ghi rõ) | `int Phase` (QUYẾT-1) | ✔ — 2 lane độc lập cùng chốt INT |
|
||||
|
||||
⇒ QUYẾT-1 (ép `int` thay enum) được lane FE xác nhận độc lập, không phải mình tự diễn giải.
|
||||
|
||||
---
|
||||
|
||||
## §7 — BÀN GIAO
|
||||
|
||||
**Cho test-specialist (viết 6 test RED ngay được):**
|
||||
- Dựng service: `new ContractSigningPlanWorkflowService(db, currentUser, dateTime, notifications)` — chữ ký này ĐÃ CUỐI, đợt-3 không đổi. `currentUser` dùng khuôn `FakeCurrentUser` (`PeCcmBudgetPeriodSetterAuthzTests.cs:30`).
|
||||
- Gọi: `await svc.TransitionAsync(planId, "approve", "ý kiến", ct)` → hiện ném `NotImplementedException("W3 đợt-3: approve")` ⇒ **6 test sàn ĐỎ THẬT** (đúng ý test-before), KHÔNG đỏ vì lỗi biên dịch.
|
||||
- Action lạ (vd `"finalize"`) → `ConflictException("Hành động không hợp lệ.")` — hành vi này ĐÃ THẬT ở đợt-1, muốn test luôn cũng được.
|
||||
- `Inbox_OnlyPendingLevelApprover_SeesPlan` không chạm service này (đi qua query handler W2).
|
||||
|
||||
**Cho đợt-3 (điền logic):** đọc block comment đầu `ContractSigningPlanWorkflowService.cs` — đã chép sẵn 4 điểm GIỮ + 3 chỗ ADAPT + 2 lệnh CẤM (Proposal-flatten, `IChangelogService.LogWorkflowTransitionAsync`) kèm `file:line`. Việc đầu tiên: **xoá 2 dòng `#pragma CS9113`** (4 dep sẽ được dùng thật).
|
||||
|
||||
**Cần lead phân xử (3 quyết đã khai ở §1-§2):** QUYẾT-1 `phase` kiểu `int` · QUYẾT-2 bộ 4 dependency (đặc biệt `ICurrentUser` thay tham số actor, và **cố ý không inject `IChangelogService`** làm rào FK-547) · QUYẾT-3 pragma CS9113 tạm 2 dòng.
|
||||
|
||||
**Ambiguity còn lại (khai, không tự quyết):** rào action nhận **không phân biệt hoa/thường** (`"Submit"` cũng qua). Hợp-đồng ghi chữ thường; FE gửi chữ thường (`khkk.ts:257-262`) nên nới này KHÔNG ảnh hưởng acceptance nào — nếu lead muốn STRICT ordinal thì đổi 1 chỗ (`AllowedActions` bỏ `OrdinalIgnoreCase` + bỏ `ToLowerInvariant`).
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
# sub-implementer-frontend-1 — W3 ĐỢT-1b: FE panel duyệt KHKK ×2 app (S161, 2026-07-29)
|
||||
|
||||
> Ghi TỪNG MỤC trong lúc làm (chống #53). Task: panel `KhkkWorkflowPanel.tsx` + gắn DetailPage + types transitions, SHA-pair ×2 app, build ×2.
|
||||
> BE logic W3 CHƯA có (task 1 skeleton song song) ⇒ bấm nút sẽ 404/500 — CHẤP NHẬN đợt này.
|
||||
|
||||
## MỤC 0 — Baseline SHA (trước khi sửa)
|
||||
|
||||
```
|
||||
340e64d0… fe-user/src/types/khkk.ts = fe-admin (PAIR OK)
|
||||
9b91606e… fe-user/src/pages/khkk/KhkkDetailPage.tsx = fe-admin (PAIR OK)
|
||||
```
|
||||
|
||||
## MỤC 1 — RECON: field THẬT của detail DTO (BE đĩa, KHÔNG suy)
|
||||
|
||||
Nguồn: `src/Backend/SolutionErp.Application/ContractSigningPlans/ContractSigningPlanFeatures.cs`
|
||||
|
||||
| Cái | Bằng chứng | Ghi chú |
|
||||
|---|---|---|
|
||||
| `ContractSigningPlanDetailDto` | `:119-147` | field CUỐI = `List<ContractSigningPlanWorkflowStepDto> WorkflowSteps` `:147` |
|
||||
| `ContractSigningPlanWorkflowStepDto(Id, Order, Name, DepartmentId, Levels)` | `:112-117` | KHÔNG có `DepartmentName`, KHÔNG có `Status` |
|
||||
| `ContractSigningPlanWorkflowLevelDto(Id, Order, Name, ApproverUserId, ApproverFullName)` | `:105-110` | KHÔNG có `Status`, KHÔNG có `allowApproverFinalize` (W1 rào type-10) |
|
||||
| `ContractSigningPlanLevelOpinionDto` | `:93-103` | có `ApproverUserId` (`:99`) — FE types W2 **THIẾU** field này ⇒ bổ sung |
|
||||
| con-trỏ đôi | `:136-137` `CurrentWorkflowStepIndex` + `CurrentApprovalLevelOrder` | |
|
||||
| Tree build | `:537-566` | Steps sort `Order`, Levels sort `Order`, `ApproverFullName` join Users `:549-552` |
|
||||
|
||||
🔴 **KHÁC PE CĂN BẢN — đây là điểm dễ đứt nhất của wave này:** PE trả `approvalFlow` đã có sẵn
|
||||
`status: 'Done'|'Current'|'Pending'` per Bước/Cấp (BE precompute). **KHKK trả CÂY THÔ, KHÔNG status**
|
||||
⇒ FE **phải tự suy** trạng thái từ con-trỏ đôi. Không có field `status` nào để đọc.
|
||||
|
||||
🔴 **Ngữ nghĩa con-trỏ (BE tự khai, `:224-226`):** `CurrentWorkflowStepIndex` là **INDEX 0-based** vào
|
||||
list Steps đã sort theo Order — **KHÔNG phải Order**. `CurrentApprovalLevelOrder` là **GIÁ TRỊ** `Level.Order`.
|
||||
Luật so khớp OR-of-N canonical BE `:259-260`:
|
||||
`steps[idx].Levels.Any(l => l.Order == CurrentApprovalLevelOrder && l.ApproverUserId == userId)`
|
||||
⇒ FE mirror ĐÚNG vị ngữ này (nhiều Level **cùng Order** = OR-of-N; lấy `.some()`, KHÔNG `.find()` 1 người).
|
||||
|
||||
Rào XEM BE `:184-195` (`EnsureCanViewAsync`): Admin ∨ drafter ∨ (phase ≠ DangSoanThao ∧ approver-của-workflow-đã-pin).
|
||||
⇒ phiếu **Nháp** approver KHÔNG đọc được; **TraLai** thì đọc được.
|
||||
|
||||
Endpoint transitions: **CHƯA có trong controller** (`ContractSigningPlansController.cs` grep `HttpPost` → chỉ
|
||||
`/`, `/{id}/dossier-items`, `/{id}/attachments`). Đúng như đề bài: đợt-1 FE đi trước BE.
|
||||
|
||||
## MỤC 2 — Khuôn PE panel: cái GIỮ / cái BỎ
|
||||
|
||||
Nguồn `fe-user/src/components/pe/PeWorkflowPanel.tsx` (1063 dòng).
|
||||
|
||||
GIỮ (mirror): sơ đồ Bước→Cấp ✓/●/○ `:381-478` · banner đến-lượt `:489-508` (`actorInV2Level`/`blockedByV2Level`
|
||||
`:109-114`) · nút 3 màu emerald/amber/red `:546-562` · Dialog + ô ý kiến `:869-870` · `useAuth()` lấy actor `:66-67`
|
||||
· `qc.invalidateQueries` + `toast.error(getErrorMessage(e))` `:288`.
|
||||
|
||||
BỎ (đơn-giản-hoá theo lệnh): price-picker `:794-820` · budget/pendingPriors `:170-180` · file-đính-kèm-khi-duyệt
|
||||
`:823-868` · 4 return-mode radio `:635-713` (W3 = TraLai toàn phần, spec §②-3(c)) · `skipToFinal`/`finalizeByCcm`/
|
||||
`applyLevelFinalize` `:57-63` (spec §②-3(b): **CẤM** port 2 đường kết thúc sớm) · xóa-mềm-bởi-approver `:295-312`.
|
||||
Reference in New Issue
Block a user