Anh giao: dieu tra quy trinh CON THIEU giua Duyet NCC va HD NTP/NCC, ra spec day du + chia wave. Chay /fable-real invest (Fable deep-pass) roi /fable-clone reviewer 4 lang kinh. PHAT HIEN NEN (lead do, invest + reviewer xac nhan tu nguon goc): - 7 phase [LEGACY] cua ContractPhase CHINH LA quy trinh ISO 9 buoc ma-hoa cung (khop 1:1 dung thu tu). Khung V2 sinh ra de bieu dien chinh quy trinh do. - Khoang trong nam DUNG giua ISO-1 va ISO-2, va ISO doc CUNG KHONG CO no => thieu o CA HAI tang (doc + code) => spec vua la spec phan mem vua la de xuat bo sung ISO. OWNER CHOT trong cua: (O-1) cau truc trinh ky GIONG, chi khac NOI DUNG => tai dung khung duyet V2, 0 bang approval moi => truc tranh luan Q2 hom qua DONG. (O-2) SLA chi tham khao => khong SLA-engine, khong wave SLA. (O-3) "cho hop dong cu tu tu" => lo hong an ninh HD NGOAI SCOPE. REVIEW 4/4 lane sach 0 chet (dong goi C2 an), 112 diem. Claim nen DUNG VUNG. Bat 7 loi NANG, da va het trong ban nay: 1. Ten menu key `Csp*` != tien to policy `ContractSigningPlans.*` => Program.cs khong sinh policy => HTTP 500 luc chay. Repo DA CO test bat (AuthorizePolicyRegressionTests.cs:138-139). Day la loi TU-VAP-BAY-MINH-VUA- NHAN-DIEN: §2.4 bat dung bay Pe_* roi §2.3 van vap bang co che khac. 2. W2 day rieng tao duoc 0 phieu (wiring Designer type-10 roi khoi moi wave) => keo vao W1, tai pham dung §F.0 S155. 3. Thieu bang Approvals ma service duoc lenh copy CO ghi vao. 4. Changelogs thieu 4 cot => copy service khong bien dich. 5. Thieu 3 site ApprovalWorkflowV2AdminFeatures => FK Restrict no 500. 6. Status enum bat dau tu 0 => bay falsy JS. 7. 4 anchor lech, nang nhat ApprovalWorkflowLevel.cs KHONG TON TAI. Kem: bo tieu chi dark-launch sai co che (IsVisible chi pass-through, filter la CanRead) - thay "sqlcmd dem policy" (khong do duoc, policy in-memory) bang test regression - OR-of-N nang tu 1 ve len 4 ve co ca am - cat CodeSequences (tai dung WorkflowAppCodeSequence dung chung 4 module Office). Su co #53: invest luot 1 skeleton-ruot-rong (202K tok, dia 1.661B khung rong) => ghi-dia-trong-luc-lam la CAN KHONG DU, phai verify RUOT. Luot 2 resume cat con 2 muc => CLEAN 23.085B. Luot 3 tien-trinh CLI thoat, §C/§D chua ghi => lead tu viet theo H21 (engine propose-only, spec do lead ghi). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
193 lines
16 KiB
Markdown
193 lines
16 KiB
Markdown
# LĂNG KÍNH 3/4 — SCHEMA 7 BẢNG (reviewer, S157)
|
||
|
||
Đích chấm: `spec-ke-hoach-ky-ket-hd-28-07-2026.md` §②.2 (dòng 113-136).
|
||
Nguồn đối chiếu ĐÃ ĐỌC: `src/Backend/SolutionErp.Domain/ApprovalWorkflowsV2/ApprovalWorkflow.cs` ·
|
||
`src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluation.cs` (+ grep xác minh trên config/service/enum).
|
||
|
||
## Verdict
|
||
|
||
**GO-WITH-FIXES** — hình dạng 7 bảng ĐÚNG HƯỚNG (loose-Guid, decimal(18,2), query-filter, FK Cascade/Restrict
|
||
đều khớp twin thật), nhưng **5 điểm HIGH phải đóng TRƯỚC Wave 1** vì chúng làm migration/CQRS/FE sai IM LẶNG
|
||
(build vẫn xanh): thiếu bảng lịch-sử-transition mà chính service được lệnh copy có ghi vào, thiếu đăng ký ở 3 site
|
||
cross-module `ApprovalWorkflowV2AdminFeatures`, thiếu 4 cột Changelog, không khai base-class cho bảng #2-#7, và
|
||
bộ số enum phase đụng khuôn FE đang được copy.
|
||
|
||
Phân bố: **5 HIGH · 7 MAJOR · 3 MEDIUM · 7 CỦNG CỐ = 22 điểm**.
|
||
|
||
## Bảng THỪA
|
||
|
||
**T1 [MAJOR] — #7 `ContractSigningPlanCodeSequences` gộp được, KHÔNG mất gì.**
|
||
Repo đã có **4 bảng giống hệt nhau về cấu trúc** `(Prefix string PK · LastSeq int · UpdatedAt)`:
|
||
`Contracts/ContractCodeSequence.cs:6-11` · `Office/ProposalCodeSequence.cs:8-13` ·
|
||
`Office/WorkflowAppCodeSequence.cs:14-19` · (+ `PurchaseEvaluationCodeSequence`, mirror khai ở
|
||
`ProposalCodeSequence.cs:4`). Trong đó `WorkflowAppCodeSequence` **đã là bảng DÙNG CHUNG cho 4 module Office**,
|
||
phân biệt nhau bằng Prefix có không-gian-tên: `"DT/LR/2026"` (nghỉ phép) · `"DT/OT/2026"` (OT) ·
|
||
`"DT/CT/2026"` (công tác) · `"DX/XE/2026"` (đặt xe) — xem `WorkflowAppCodeSequence.cs:8-11`.
|
||
Prefix `"KHKK/{YYYY}"` nhét vào bảng đó là **0 va chạm** (khoá là chuỗi, đã namespaced sẵn).
|
||
⇒ Cắt #7 ⇒ còn **6 bảng**, giảm 1 CreateTable + 1 configuration + 1 DbSet, **không mất tính atomic**.
|
||
|
||
**T2 [MAJOR] — lý do bác biến-thể-5-bảng đứng NỬA, đổ NỬA.**
|
||
- Nửa ĐỨNG: "giữ #6 Changelogs" có bằng chứng thật — Mig 60/61 phải backfill từ changelog (CLAUDE.md ghi
|
||
Mig 60 soi nhầm bảng Approvals → Mig 61 mới đúng Changelogs). Thứ tự ưu tiên spec đưa ("cắt #7 trước,
|
||
giữ #6") là ĐÚNG.
|
||
- Nửa ĐỔ: mệnh đề *"mất mã phiếu atomic"* **không suy ra được**. Tính atomic đến từ **transaction
|
||
SERIALIZABLE trong CodeGen service**, không đến từ việc bảng đó thuộc riêng module — nói rõ trong chính
|
||
comment của cả 3 bảng: `ContractCodeSequence.cs:5` · `ProposalCodeSequence.cs:7` ·
|
||
`WorkflowAppCodeSequence.cs:12-13`. Vậy "cắt #7" ⇏ "mã tay"; nó chỉ là "dùng chung bảng sequence sẵn có".
|
||
- Các bảng còn lại **KHÔNG gộp được** (đã xét từng cặp): #2 Lines (multi-winner, per-NCC) không nhét vào
|
||
header; #3 DossierItems không nhét vào #5 Attachments vì trạng thái `ChuaNop` là hàng **không có file** —
|
||
gộp là phải đẻ attachment giả; #4 LevelOpinions bị máy-móc admin V2 truy vấn theo `(phiếu × Level)` nên
|
||
phải là bảng riêng có UNIQUE.
|
||
|
||
## Bảng THIẾU
|
||
|
||
**H1 [HIGH] — không có `ContractSigningPlanApprovals` (lịch sử transition), trong khi service được lệnh copy CÓ ghi vào bảng đó.**
|
||
`ContractWorkflowService.cs:151` ghi thẳng: `db.ContractApprovals.Add(new ContractApproval { ContractId = …,
|
||
FromPhase = fromPhase, ToPhase = fromPhase, … })`. Twin PE cũng có nhánh này: `PurchaseEvaluation.cs:137`
|
||
(`List<PurchaseEvaluationApproval> Approvals`) + index chuyên dụng `(PurchaseEvaluationId, ApprovedAt)` ở
|
||
`PurchaseEvaluationConfiguration.cs:168`; `PurchaseEvaluationApproval.cs:6-7` ghi rõ *"Lịch sử phê duyệt —
|
||
giống ContractApproval pattern"*. Spec §2.1 bắt copy `ApproveV2Async:217-394` ⇒ đoạn copy sẽ trỏ vào một
|
||
bảng KHÔNG TỒN TẠI trong danh sách 7 bảng.
|
||
⇒ Chọn 1 trong 2 và **ghi vào spec**: (i) thêm bảng #8 `…Approvals`, hoặc (ii) tuyên bố "Changelog gánh vai
|
||
Approvals" **kèm** bù cột `FromPhase/ToPhase/Decision/Comment/ApprovalWorkflowLevelId` (hiện #6 chỉ có MỘT
|
||
trường phase là `PhaseAtChange`, không có cặp from→to, không có Decision).
|
||
|
||
**H2 [HIGH] — module thứ 8 phải đăng ký ở 3 site liệt-kê-thủ-công trong `ApprovalWorkflowV2AdminFeatures.cs`; spec chỉ nói chung chung.**
|
||
File này liệt kê **từng bảng `*LevelOpinions` một** ở 3 chỗ:
|
||
`:911` + `:929` (Merge "chữ ký còn sống" — `db.ProposalLevelOpinions…` / `db.TravelRequestLevelOpinions…`) ·
|
||
`:971` + `:992` (rows.AddRange dựng `RetainedOpinionRef`) · `:1044` + `:1056` (dọn chữ ký MỒ CÔI).
|
||
Chuỗi guard S155 đứng trên dữ liệu này: `:651` *"F-5/F-5a — chặn LỊCH SỰ trước khi FK nổ"* và `:733`
|
||
*"Dọn rác chữ ký MỒ CÔI trước khi xoá dòng"*.
|
||
⇒ Nếu `ContractSigningPlanLevelOpinions` không được thêm vào **cả 3 site**, admin sửa quy trình `type = 10`
|
||
sẽ **không nhìn thấy** chữ ký của phiếu KHKK, xoá Level đang có chữ ký → FK Restrict nổ **500** — đúng lớp lỗi
|
||
S155 vừa vá xong. Spec §2.4 chỉ viết *"các site BE `ApprovalWorkflowV2AdminFeatures.cs`"* (không nêu 3 site này,
|
||
chỉ nêu map typeCode) ⇒ chưa đủ để người làm không sót.
|
||
|
||
## Cột sai
|
||
|
||
**C1 [HIGH] — #6 Changelogs thiếu 4 cột so với twin.**
|
||
`PurchaseEvaluationChangelog.cs:14-22` có: `EntityType` · `EntityId` · `Action` · `PhaseAtChange` · `UserId` ·
|
||
`UserName` · `Summary` · `FieldChangesJson` · `ContextNote`. Spec #6 chỉ liệt `Action · PhaseAtChange · UserId ·
|
||
Summary · ContextNote` ⇒ **thiếu `EntityType`, `EntityId`, `UserName`, `FieldChangesJson`**.
|
||
Hệ quả đo được: index twin `(PurchaseEvaluationId, EntityType)` (`PurchaseEvaluationConfiguration.cs:188`) mất
|
||
chỗ dựa; `LogTransitionAsync` (`ContractWorkflowService.cs:405`) gọi `changelog.LogWorkflowTransitionAsync(...)`
|
||
— copy service changelog sang module mới sẽ không biên dịch nếu entity thiếu trường; và mất khả năng ghi diff
|
||
trường (`FieldChangesJson`, twin khai `nvarchar(max)` ở `PurchaseEvaluationConfiguration.cs:185`).
|
||
|
||
**C2 [MAJOR] — `Status int` bắt đầu từ **0** (`ChuaNop=0`) là lệch quy ước + bẫy JavaScript.**
|
||
Mọi enum trong repo bắt đầu ở 1: `Office/Enums.cs:9` (`Confirmed=1`) · `:19` (`Nhap=1`) · `:31` (`Nhap=1`) ·
|
||
`:41` (`Hardware=1`) · `:50` (`Low=1`) · `:58` (`Open=1`); `ApprovalWorkflow.cs:55` (`DuyetNcc=1`).
|
||
Số 0 là giá trị **falsy** trong JS ⇒ `status ? A : B`, `if (filter.status)`, `?status=0` trên query-string đều
|
||
rơi im lặng ở FE. Đề nghị `ChuaNop = 1` (giữ `Khac = 99` của `Kind` — cái đó ĐÚNG khuôn `ItTicketCategory.Other = 99`,
|
||
`Office/Enums.cs:45`).
|
||
|
||
**C3 [MAJOR] — ~13 cột chuỗi không khai độ dài ⇒ EF sinh `nvarchar(max)`.**
|
||
Spec chỉ khai 2 độ dài (`HoSoLink 1000`, `Comment 2000`). Không khai: `MaKeHoach`, `GhiChu`, `Name` (#3), `Note`
|
||
(#2/#3), `TvgsName`, `Summary`, `ContextNote`, `SignedByFullName`, `FileName/Path/ContentType` (#5), `Prefix` (#7).
|
||
Twin khai đủ: `PurchaseEvaluationConfiguration.cs:22` (`HoSoLink` 1000) · `:184` (`ContextNote` 2000) ·
|
||
`ContractLevelOpinionConfiguration.cs:21-22` (`Comment` 2000, `SignedByFullName` 200 **IsRequired**).
|
||
`nvarchar(max)` không index được và không so sánh được trong filtered-unique.
|
||
|
||
**C4 [MAJOR] — `SlaDeadline?` chưa có người đọc ⇒ đúng lớp "cột có mà 0 ai đọc" mà chính spec cấm (dòng 136).**
|
||
Thiếu `SlaWarningSent` (twin `PurchaseEvaluation.cs:26`) và thiếu index `SlaDeadline` (twin
|
||
`PurchaseEvaluationConfiguration.cs:62`). Job SLA hiện chỉ đọc/ghi cờ này trên nhánh Contract:
|
||
`SlaExpiryJob.cs:139` (`.Where(c => !c.SlaWarningSent …)`) và `:169` (`c.SlaWarningSent = true;`).
|
||
⇒ Hoặc bỏ `SlaDeadline`, hoặc thêm cờ + index + khai rõ wave nào đăng ký module vào `SlaExpiryJob`.
|
||
|
||
**C5 [MAJOR] — header thiếu `DepartmentId`, làm vế "DeptManager" của guard trình mất chỗ neo.**
|
||
Twin có: `PurchaseEvaluation.cs:17` (`Guid? DepartmentId`). Guard spec §2.3 (dòng 164) =
|
||
`CreatedBy == actor ∨ DeptManager ∨ Admin`. Không có phòng trên phiếu thì "DeptManager" không so được với gì
|
||
⇒ **bất kỳ ai mang vai DeptManager trình được phiếu của phòng khác**. `ApprovalWorkflowStep.DepartmentId`
|
||
(`ApprovalWorkflow.cs:75`) chỉ là *hint* của quy trình, không thay được phòng của phiếu.
|
||
|
||
**C6 [MAJOR] — không có đường quay ngược Kế-hoạch → Hợp-đồng sau b.13.**
|
||
Twin có `PurchaseEvaluation.ContractId` (`:31`) + index (`PurchaseEvaluationConfiguration.cs:65`).
|
||
Ở đây HĐ sinh **theo từng NCC thắng**, mà #2 Lines cũng **theo từng NCC** ⇒ chỗ tự nhiên là
|
||
`ContractSigningPlanLines.ContractId?` (+ IX). Thiếu nó thì: không trả lời được "dòng này đã ra HĐ chưa",
|
||
không chặn được tạo HĐ hai lần cho cùng một NCC, và §2.5(a) không có chỗ ghi kết quả bắc cầu.
|
||
*(Lưu ý: đây KHÁC với "cột `SigningPlanId` trên PE" mà spec bác — bác cột đó là đúng, xem G6.)*
|
||
|
||
**C7 [MEDIUM] — `PeReferenceAmount`: GIỮ snapshot (đúng), nhưng thiếu mốc thời gian + luật ghi lại.**
|
||
Xem G5 để biết vì sao snapshot thắng "đọc live". Hai chỗ hở: (i) không có `PeReferenceAmountAt` —
|
||
twin dùng `ApprovedBudgetSnapshotAt` làm **cờ "đã chốt"** (`PurchaseEvaluation.cs:132`, ghi rõ ở `:120`);
|
||
(ii) spec nói *"snapshot 1 lần"* lúc tạo nhưng không nói điều gì xảy ra khi Drafter **re-pin workflow** hay
|
||
sửa nháp (§2.3 cho phép re-pin) — có sinh lại Lines không, có tính lại số không.
|
||
|
||
## Ràng buộc sai
|
||
|
||
**R1 [HIGH] — không khai lớp cơ sở (`BaseEntity` vs `AuditableEntity`) cho bảng #2-#7, trong khi #2 lại đặt UNIQUE lọc `WHERE IsDeleted=0`.**
|
||
Bộ lọc đó **đòi** cột `IsDeleted`, tức #2 buộc phải là `AuditableEntity`. Nhưng twin gần nhất của #2 là
|
||
`PurchaseEvaluationSupplier.cs:9` — `: BaseEntity` (không có xoá mềm) — và UNIQUE của nó **không lọc**:
|
||
`PurchaseEvaluationConfiguration.cs:105` `HasIndex(x => new { x.PurchaseEvaluationId, x.SupplierId }).IsUnique()`.
|
||
⇒ Nếu người làm mirror twin (BaseEntity) thì migration sinh filter trỏ vào cột không tồn tại; nếu mirror spec
|
||
(AuditableEntity) thì lệch khuôn con-của-phiếu. Spec phải khai **base class cho từng bảng**.
|
||
|
||
**R2 [MEDIUM] — bất nhất nội bộ #2 (lọc) vs #4 (không lọc), và #4 kế thừa một vi phạm #57 tiềm ẩn.**
|
||
#4 không lọc là **khớp cả 6 twin**: `ContractLevelOpinionConfiguration.cs:34` · `ProposalLevelOpinionConfiguration.cs:28` ·
|
||
`TravelRequestLevelOpinionConfiguration.cs:28` · `OtRequestLevelOpinionConfiguration.cs:28` (+ Leave, PE).
|
||
Nhưng các entity đó **là** `AuditableEntity`: `PurchaseEvaluationLevelOpinion.cs:24`, `TravelRequestLevelOpinion.cs:16`
|
||
⇒ cả họ đang ở thế "xoá mềm + UNIQUE không lọc" = gotcha #57 dạng ngủ (chỉ chưa cắn vì đường ghi là UPSERT,
|
||
không soft-delete). Kế thừa thì kế thừa **có ý thức**: ghi 1 dòng lý do trong spec, nếu không lần sau ai đó
|
||
thêm đường soft-delete chữ ký là kẹt không ký lại được.
|
||
|
||
**R3 [MEDIUM] — loose-Guid đúng convention nhưng thiếu index đi kèm.**
|
||
Spec chỉ khai IX cho `PurchaseEvaluationId`. Twin đánh index cho **mọi** loose-Guid dùng để lọc:
|
||
`ProjectId` (`:58`) · `WorkItemId` (`:61`) · `ApprovalWorkflowId` (`:64`) · `ContractId` (`:65`) ·
|
||
`SlaDeadline` (`:62`); và ở bảng con: `SupplierId` (`:106`), `PurchaseEvaluationSupplierId` (`:206`).
|
||
⇒ Bổ sung IX: `ProjectId`, `ApprovalWorkflowId`, `DrafterUserId` (header) + `SupplierId` (#2) + `PlanId` (#4/#6/#7 theo cặp).
|
||
|
||
## Điểm CỦNG CỐ
|
||
|
||
**G1 — trích dẫn `PurchaseEvaluationConfiguration.cs:84` CHÍNH XÁC ĐẾN DÒNG:** `b.HasQueryFilter(x => !x.IsDeleted);`
|
||
nằm đúng dòng 84. (Đã kiểm bằng cách đọc file, không tin lời spec.)
|
||
|
||
**G2 — `decimal(18,2)` đúng quy ước tiền tệ của repo:** `PurchaseEvaluationConfiguration.cs:23-30`
|
||
(`HasColumnType("decimal(18,2)")` cho các cột ngân sách/giá).
|
||
|
||
**G3 — "loose-Guid không FK vật lý sang PE/Supplier/Project" là quy ước THẬT, không phải suy diễn:**
|
||
`PurchaseEvaluation.cs:16` (comment `WorkItemId` — *"scalar loose-Guid (KHÔNG navigation, KHÔNG FK vật lý —
|
||
convention PE giống ProjectId/SelectedSupplierId)"*) + `PurchaseEvaluationConfiguration.cs:59-61`
|
||
(*"index lọc query, KHÔNG HasOne/FK vật lý (convention PE: chỉ ApprovalWorkflowId có FK)"*) + `:72-73`
|
||
(FK duy nhất ra ngoài module là `ApprovalWorkflowId`, `DeleteBehavior.Restrict`).
|
||
|
||
**G4 — bảng #4 mirror ĐÚNG 100% khuôn `ContractLevelOpinions`:** FK Cascade phiếu
|
||
(`ContractLevelOpinionConfiguration.cs:25-27`) + FK **Restrict** Level (`:30-32`) + UNIQUE `(phiếu, Level)` (`:34`)
|
||
+ `Comment` 2000 (`:21`). Đây là phần chắc tay nhất của §②.2. *(Kèm nghĩa vụ H2 — chọn Restrict thì phải vào 3 site admin.)*
|
||
|
||
**G5 — `PeReferenceAmount` snapshot: BÁC phương án "đọc live", giữ snapshot.** Ba căn cứ:
|
||
(i) tiền lệ cùng repo — `ApprovedPriceAmount` (`PurchaseEvaluation.cs:97`) và 11 cột `ApprovedBudget*`
|
||
(`:122-132`, Mig 67) đều là ảnh chụp tại thời điểm chốt;
|
||
(ii) nguồn live **biến động được dưới chân phiếu KHKK**: cờ F3 `AllowApproverEditDetails` cho người duyệt sửa
|
||
Hạng mục/NCC/Báo giá lúc đang duyệt (`ApprovalWorkflowLevel.cs:127-129`), cờ F6 `AllowApproverDelete` cho xoá
|
||
mềm phiếu (`:156-169`) — mà phiếu PE xoá mềm thì `HasQueryFilter` làm số live **biến mất**, snapshot thì còn;
|
||
(iii) spec **đã tự cài máy dò lệch** ở §2.5(a) (ghi ContextNote *"giá KH x vs SUM-PE y"*) ⇒ snapshot không
|
||
biến thành số mồ côi không ai đối chiếu.
|
||
|
||
**G6 — "KHÔNG thêm cột `SigningPlanId` lên PE" là quyết định ĐÚNG** (tra ngược bằng `Plans.Where(PeId == x)`);
|
||
đây đúng là lớp lỗi cột-phái-sinh-kẹt-stale (gotcha #81, `docs/gotchas.md:1343`).
|
||
|
||
**G7 (câu e, vế 2) — `ApplicableType = 10` TRỐNG THẬT, xác nhận bằng đọc mã chứ không bằng lời spec:**
|
||
`ApprovalWorkflow.cs:53-67` — enum dừng ở `TravelRequest = 9`, không có giá trị 10. Quét cả `src/` với mẫu
|
||
`= 10,` chỉ ra **một** kết quả duy nhất và nó thuộc enum KHÁC: `PurchaseEvaluationPhase.cs:24` `ChoDuyet = 10`.
|
||
⇒ Claim của spec ĐÚNG, append-only an toàn.
|
||
|
||
## Enum — điểm HIGH riêng
|
||
|
||
**E1 [HIGH] — `ContractSigningPlanPhase = 1/2/3/98/99` là bộ số THỨ BA, và nó đụng đúng khuôn FE mà §2.4 bắt copy.**
|
||
Repo đang sống hai quy ước:
|
||
- Họ PE/Contract: `PurchaseEvaluationPhase.cs:24` `ChoDuyet = 10` (+ TraLai 98 / TuChoi 99).
|
||
- Họ Office/Proposal — **chính là khuôn FE spec chọn** (§2.4: *"khuôn bộ Proposal ~978 LOC/app"*):
|
||
`Office/Enums.cs:17-24` `ProposalStatus { Nhap=1, DaGuiDuyet=2, TraLai=3, TuChoi=4, DaDuyet=5 }` và
|
||
`:29-36` `WorkflowAppStatus` y hệt, **dùng chung cho 5 module** (LeaveRequest + OtRequest + TravelRequest +
|
||
VehicleBooking + ItTicket — `:27`).
|
||
|
||
Va chạm cụ thể, đo được: **giá trị 3** = `DaDuyet` ở enum mới, nhưng = `TraLai` ở khuôn FE Proposal đang được
|
||
copy. Bảng map badge/nhãn trạng thái copy nguyên si sẽ **render sai mà không có lỗi biên dịch** — đúng lớp
|
||
"xanh-giả". Lý do spec đưa (*"giữ 98/99 cho quen mắt FE badge"*) chỉ đúng với FE PE/Contract, **không đúng với
|
||
FE khuôn đã chọn**.
|
||
⇒ Hai lối ra, chọn 1 và ghi vào spec: (a) dùng đúng bộ `WorkflowAppStatus` 1-5 (hết va chạm, thêm cơ hội tái
|
||
dùng chip/badge có sẵn), hoặc (b) giữ 1/2/3/98/99 nhưng **khai nghĩa vụ viết lại toàn bộ map trạng thái** khi
|
||
copy trang Proposal, kèm 1 dòng kiểm ở checklist wave.
|
||
|
||
<!-- END · TOTAL=22 DIEM · VERDICT=GO-WITH-FIXES (5 HIGH chặn Wave 1) -->
|