Files
solution-erp/.claude/workflows/runs/2026-07-28-S157-ke-hoach-ky-ket-hd/sub-invest-kehoach-1.md
2026-07-28 10:58:47 +07:00

22 KiB
Raw Blame History

sub-invest-kehoach-1 — "Kế hoạch ký kết Hợp đồng" (PDF b.7→12) — quy trình + wire + wave

Run: 2026-07-28-S157-ke-hoach-ky-ket-hd · agent: investigator-codebase (/fable-real, effort max) Trạng thái file: GHI TRONG LÚC LÀM — mục chưa điền ghi [PENDING]. Propose-only; lead ghi spec sau verify. Nguồn: run.md S157 (§1 grounding lead) · src-QT-TRINH-KY-HD.txt · src-FO-002.01 · run.md S156 · sub-invest-bch-1.md · review-synthesis.md §G. Luật đo: mọi claim load-bearing neo file:line TỰ ĐỌC. Doc/skill = giả thuyết. Đĩa thắng. 🔴 OWNER CHỐT GIỮA CHỪNG (run.md §1.7, nhận qua lead lúc đang điều tra): (O-1) "Đúng chính xác, cấu trúc trình ký giống, chỉ khác nội dụng thôi" ⇒ phiếu KH TÁI DÙNG khung duyệt V2 (ApprovalWorkflow>Step>Level OR-of-N + LevelOpinions UPSERT); DỰNG MỚI = NỘI DUNG phiếu. Trục cơ-chế Q2 ĐÓNG; option-space §D(6) chỉ còn trục NỘI DUNG (phiếu độc lập ⟂ mở rộng PE ⟂ entity con Contract). (O-2) "Cái này để tham khảo thôi, ko vấn đề j, đa số là trễ" ⇒ SLA = hiển thị tham khảo; KHÔNG SLA-engine, KHÔNG auto-approve-on-timeout, KHÔNG wave SLA; giữ hiển thị deadline; AddDays(7) hardcode = 1 dòng ghi nhận.

§A — QUY TRÌNH CHI TIẾT khúc còn thiếu (hoà giải 3 nguồn)

A.0 Khung — khoảng trống nằm giữa ISO-1 và ISO-2 (xác nhận Phát hiện B của lead từ nguồn gốc)

Em tự đối chiếu: ISO doc bảng 2 (src-QT-TRINH-KY-HD.txt:60-61) đi THẲNG "Lựa chọn NTP/NCC" → "Soạn thảo hợp đồng", 0 bước trung gian. PDF sơ đồ (bản 14/2026, runs/...S156/run.md bảng 21 bước) chèn b.7→12 đúng chỗ đó. FO-002.01 là tờ cover của KHÚC HĐ (ISO bước 5-7), không nói gì khúc 7→12. ⇒ PDF là nguồn DUY NHẤT có khúc này — spec ra từ đây vừa là spec phần mềm vừa là đề xuất bổ sung ISO. Vật mang trong hệ thống = 1 PHIẾU "Kế hoạch ký kết Hợp đồng" (owner đặt tên, đầu ra = CON SỐ có phê duyệt), vòng đời 5-trạng-thái mirror PE, duyệt bằng khung V2 có sẵn (O-1).

A.1 Bảng bước b.7→12 → ngôn ngữ hệ thống

Role hệ thống có thật (AppRoles.cs:5-16): Procurement (PMH/PRO) · CostControl (CCM) · Director+AuthorizedSigner (BOD/NĐUQ) · ProjectManager/Drafter (phía BCH) · HrAdmin · Admin. "BCH dự án X" không có model (Q1 S156 — Department.ManagerUserId 0-row, PositionLevel NULL) → phiếu KH né bằng: BCH = người tạo phiếu (CreatedBy), PMH/CCM/CEO = đích danh user trong Level của workflow V2 (đúng triết lý V2, 0 model mới).

b Việc Ai (role thật) Đầu vào Đầu ra Điều kiện chuyển Nhánh lỗi/trả lại
7 PMH mail chuyển thông tin BCH/NTP/NCC Procurement PE chạm DaDuyet (winners IsWinner, giá SUM Quote.IsSelected, HoSoLink NAS) BCH biết việc → vào lập KH Hệ thống = pull-model: màn "PE đã duyệt — chờ lập KH" lọc Phase==DaDuyet && chưa có plan (khuôn CreateContractFromEvaluationFeatures.cs:173 filter DaDuyet && ContractId==null) + notification in-app. Email ra NGOÀI (NTP/NCC) = ngoài scope (SMTP TODO toàn hệ thống) PE thiếu winner → không hiện trong màn (guard sẵn ở PE)
8 Duyệt mẫu + shopdrawing với TVGS BCH nhập; TVGS = người NGOÀI hệ thống, BCH nhập hộ kết quả (tên TVGS + ngày + scan) — hướng (a) Q7 S156, sơ đồ không đòi account TVGS Mẫu vật liệu + shopdrawing từ TP/NCC (offline) Danh mục CĂN CỨ trong phiếu KH: per-item {Kind: MauVatLieu|Shopdrawing, NCC nào, Status, TvgsName, ngày, file scan} Vòng đời PER-ITEM: ChuaNop→DaNop→TvgsDuyet|TvgsBac; TvgsBac→DaNop (nộp lại) — sơ đồ KHÔNG vẽ vòng lặp này, hệ thống phải có vì thực tế TVGS bác Item bị bác = đổi status + note, KHÔNG đụng state machine phiếu
9 Tổng hợp hồ sơ + so sánh giá → ĐỀ XUẤT giá trị ký HĐ BCH (= Drafter phiếu KH; role thường ProjectManager) Căn cứ b.8 + báo giá PE (snapshot PeReferenceAmount per-winner lúc tạo phiếu — nguyên tắc snapshot Mig 67) Phiếu KH điền đủ: dòng giá trị ĐỀ XUẤT per-winner + file so sánh giá → TRÌNH (DangSoanThao→ChoDuyet) Guard trình: dòng giá đề xuất đủ per-winner (mirror completeness-guard S60 PE PurchaseEvaluationWorkflowService.cs:180-212); căn cứ b.8 chưa đủ TvgsDuyet → soft-warn (tiền lệ S62 vượt-NS-cảnh-báo-mềm) — cứng/mềm = owner quyết (O-Q1 dưới) Ghi chú sơ đồ: "hồ sơ sai sót thiếu khối lượng, sai spec, thiếu phạm vi → gửi mail làm rõ" = comment + item TvgsBac
10 PMH kiểm tra hồ sơ so sánh giá, xác nhận giá trị Procurement (đích danh user trong Level) Phiếu ChoDuyet Bước 1 Duyệt → Bước 2; ý kiến vào LevelOpinions (UPSERT khi duyệt — khuôn ContractWorkflowService.cs:292-316) OR-of-N trong Cấp (ApprovalWorkflow.cs:81-94) Trả lại → TraLai (reset) hoặc 4 return-mode per-level (cờ CÓ SẴN ApprovalWorkflowLevel.cs:116-125, service mirror PE :421-424)
11 CCM kiểm tra CostControl (đích danh trong Level) Bước 2 Duyệt → Bước 3 như trên như trên
12 CEO ký HOẶC CCM đóng dấu approval Director; nhánh "CCM đóng dấu" = cơ chế kết thúc sớm Bước 3 (hoặc kết thúc tại Bước 2) GIÁ TRỊ KÝ KẾT CHỐT per-winner (ApprovedAmount) ghi tại finalize choke-point — mirror ApplyApprovedPriceOnFinalize PE :973-1002 + RULE "mọi nhánh set DaDuyet phải gọi helper" :1008 Terminal DaDuyet Từ chối → TuChoi (terminal); trả lại → TraLai

Cơ chế rẽ b.12 "hoặc" — 4 đường ĐỀU CÓ SẴN trong khung V2 (O-1), 0 migration: (i) CeoApprovalThreshold + CCM tích "duyệt done miễn CEO" — PE service :892-929 (fail-closed: đòi threshold

  • role CostControl + giá < ngưỡng); (ii) AllowApproverFinalize per-slot opt-out — PE :870-890; (iii) AllowApproverSkipToFinal per-slot (CEO vẫn ký thật) — Contract :320-352; (iv) 2 workflow (có/không Step CEO) admin pin. Điều kiện rẽ THẬT (ngưỡng? loại gói?) = cả 3 nguồn im lặng → Q3 owner còn treo, nhưng chọn đường nào cũng không đổi schema.

Tiếp giáp ra (b.12→13): phiếu KH DaDuyet → cầu POST /purchase-evaluations/{id}/create-contract hiện có (CreateContractFromEvaluationFeatures.cs) tạo HĐ draft DangSoanThao — sửa cầu đọc ApprovedAmount từ KH thay SUM Quote.IsSelected (:88-90) + audit lệch PE-vs-KH + gate "chưa có KH duyệt thì cảnh báo/chặn" (Q11 mềm/cứng). Khúc b.13→21 = verdict LAI S156, KHÔNG bàn lại.

A.2 b.8-9 là GÌ: bảng con trong phiếu KH — không phải phiếu con, không phải module riêng

Repo 0-hit shopdrawing|TVGS|duyệt mẫu (verify S156 §0, em không đo lại — không có gì để đo). Chọn satellite table DossierItems + attachments trong phiếu KH vì: (1) owner phát biểu b.8-9 = CĂN CỨ của con số, không phải đối tượng duyệt riêng; (2) PDF chỉ vẽ MỘT chuỗi duyệt (10-12) cho cả khúc — phiếu con riêng = đẻ chuỗi duyệt thứ 2 không có trên sơ đồ; (3) vòng đời mẫu/shopdrawing là per-item status (nộp/duyệt/bác/nộp lại), không phải state machine phiếu; (4) TVGS ngoài hệ thống → chỉ cần ghi vết, không cần workflow. Module riêng chỉ đáng khi mẫu/shopdrawing cần quản lý ĐỘC LẬP với gói thầu (chưa có yêu cầu).

A.3 Mâu thuẫn / im lặng giữa 3 nguồn (bảng tra)

# Điểm ISO doc PDF sơ đồ FO-002.01 Xử lý
1 Khúc b.7→12 KHÔNG CÓ (1→2 thẳng) CÓ (duy nhất) không nói Spec = đề xuất bổ sung ISO
2 SLA khúc 7→12 im lặng im lặng (10-14d là b.1-4; 3d + 7-10d là b.13-18) im lặng O-2: tham khảo — không hoà giải, không engine
3 SLA khúc HĐ (13→19) 07/07/07/07/03/01 ngày 3d + 7-10d "mỗi bộ phận 01 ngày, quá hạn XEM NHƯ ĐÃ THÔNG QUA" (auto-pass!) 3 nguồn 3 kiểu; auto-pass mâu thuẫn mô hình duyệt chặn V2 (V2 không có auto-approve; V1 từng có System AutoApprove) — O-2 → chỉ ghi nhận
4 Điều kiện rẽ b.12 CEO vs CCM không có bước "hoặc" — KHÔNG điều kiện không nói Q3 owner; 4 cơ chế sẵn (A.1)
5 Nhánh trả lại 10/11/12 không vẽ không vẽ không vẽ Hệ thống có sẵn TraLai + 4 return-mode per-level
6 Bypass Chủ đầu tư → GĐ trực tiếp không nói không nói CÓ ("chuyển trực tiếp Giám đốc") = Contract.BypassProcurementAndCCM (đã có trong code) — KHÔNG áp phiếu KH (KH chỉ sinh từ PE = TP/NCC)
7 Nội dung kiểm tra cấu trúc per-phòng (PRO: điều khoản/thanh toán/rủi ro pháp lý · CCM: giá vs NS/rủi ro) mô tả lời không CÓ (checkbox từng mục) Hệ thống chỉ có LevelOpinions.Comment tự do — checklist cấu trúc = GAP ghi nhận, không blocking (owner quyết có số hoá checkbox không — O-Q2)
8 Tên phòng/vai PRO/CCM/BOD/NĐUQ/HRA Procurement/CCM/CEO PB-DA/PRO/CCM/GĐ Khớp AppRoles: Procurement/CostControl/Director+AuthorizedSigner/HrAdmin — nhất quán, 0 mâu thuẫn
9 Chuỗi ký FO-002.01 (ĐỀ XUẤT→PRO→CCM→GĐ) ≈ bước 5-7 ≈ b.9→10→11→12 CÙNG HÌNH Đối chứng giấy cho O-1: khung Step(Phòng)×Level+Opinions gánh được cả 2 khúc

Câu hỏi owner còn treo sau run này (rút từ 14 câu S156, trừ đã chốt O-1/O-2/Q7a/Q8-ngoài-scope): O-Q1 gate căn cứ b.8 khi trình: mềm (khuyến nghị, tiền lệ S62) hay cứng? · O-Q2 checkbox FO-002.01 số hoá hay comment tự do? · Q3 điều kiện rẽ b.12 (chọn cơ chế i-iv) · Q6 xác nhận giá KH thay SUM-PE khi tạo HĐ (mặc định thiết kế: KH thắng + audit lệch) · Q11 gate tạo-HĐ khi chưa có KH duyệt: mềm/cứng.

§B — CÁCH WIRE vào codebase (twin THẬT, file:line)

O-1 chốt: tái dùng khung duyệt V2 (ApprovalWorkflows/Steps/Levels + cột dùng chung CeoApprovalThreshold ApprovalWorkflow.cs:43 + 9 cờ per-slot ApprovalWorkflowLevel.cs:116-169) — 0 bảng approval mới. Dựng mới = NỘI DUNG phiếu. Twin đo thật: Proposal (BE 849 LOC · FE 1.956 LOC · 4 bảng Mig 38 — em wc/grep trực tiếp) = khuôn CRUD/menu; Contract ApproveV2Async (ContractWorkflowService.cs:217-394) = khuôn service; PE = khuôn finalize/giá-chốt (BE ~6.5K · FE ~14.9K = cận trên, không cần tới).

B.1 ⚠️ Bẫy twin đo được lượt này — CẤM copy Proposal-lite cho phần DUYỆT

ProposalFeatures.cs ApproveProposalHandler đi global-level-order: flatten mọi Level xuyên Steps (:427-429 SelectMany), match ElementAtOrDefault(order-1) 1 row (:433) + so đúng 1 user (:439 currentSlot.Level.ApproverUserId != currentUser.UserId). ⇒ 2 row cùng Order (OR-of-N hợp lệ theo ApprovalWorkflow.cs:81-94) bị biến thành 2 nấc TUẦN TỰ (AND) — phá ngữ nghĩa OR-of-N, và không có con trỏ Step (entity Proposal :27 chỉ có CurrentApprovalLevelOrder, KHÔNG có CurrentWorkflowStepIndex). Phiếu KH cần 3 Bước (PMH→CCM→CEO) ⇒ service PHẢI copy ContractWorkflowService.ApproveV2Async:217-394 (con trỏ đôi StepIndex+LevelOrder, GroupBy Order :246, OR-of-N :259-260, LevelOpinions UPSERT :292-316, skipToFinal :320-352) — Proposal chỉ làm khuôn cho CRUD/DTO/menu/FE.

B.2 Schema — Mig 69 AddContractSigningPlans (mig cuối hiện tại = 68 20260727033522_AddPeAllowApproverDelete.cs, em ls trực tiếp)

7 CreateTable, 0 ALTER bảng cũ ⇒ Down = DropTable ×7, reversible sạch. Convention: PascalCase EN, loose-Guid không FK vật lý sang PE/Supplier (convention PE — PurchaseEvaluation ProjectId/SelectedSupplierId đều loose, ghi ở Mig 49 note), FK vật lý chỉ trong nội bộ module + sang bảng V2 dùng chung.

# Bảng Cột chính Ràng buộc
1 ContractSigningPlans (header) MaKeHoach?, PurchaseEvaluationId (loose+IX), ProjectId (denorm lọc cây), Phase int, ApprovalWorkflowId Guid?, CurrentWorkflowStepIndex int?, CurrentApprovalLevelOrder int?, DrafterUserId, SlaDeadline?, SlaWarningSent, HoSoLink nvarchar(1000)? (mirror PE Mig 52), GhiChu AuditableEntity + HasQueryFilter(!IsDeleted) (khuôn PurchaseEvaluationConfiguration.cs:84)
2 ContractSigningPlanLines (giá per-winner) PlanId FK Cascade, SupplierId loose, PeReferenceAmount decimal(18,2) snapshot lúc tạo (nguyên tắc freeze Mig 67 — không để PE sửa sau làm trôi baseline), ProposedAmount decimal(18,2), ApprovedAmount decimal(18,2)? (chỉ ghi tại finalize), Note UNIQUE filtered (PlanId, SupplierId) WHERE IsDeleted=0 (gotcha #57)
3 ContractSigningPlanDossierItems (căn cứ b.8-9) PlanId FK Cascade, Kind int (MauVatLieu=1/Shopdrawing=2/Khac=99), SupplierId? loose, Name, Status int (ChuaNop=0/DaNop=1/TvgsDuyet=2/TvgsBac=3), TvgsName nvarchar(200)?, TvgsResultAt?, Note IX (PlanId)
4 ContractSigningPlanLevelOpinions PlanId + ApprovalWorkflowLevelId, Comment nvarchar(2000), SignedAt, SignedByUserId, SignedByFullName UNIQUE (PlanId, LevelId); FK Cascade Plan + Restrict Level (mirror ContractLevelOpinions Mig 33 / ProposalLevelOpinionConfiguration.cs)
5 ContractSigningPlanAttachments PlanId FK Cascade, FileName/Path/Size/ContentType, Purpose int (MauVatLieu=1/Shopdrawing=2/SoSanhGia=3/Khac=99), DossierItemId? khuôn ProposalAttachment.cs (20 dòng)
6 ContractSigningPlanChangelogs PlanId, Action, PhaseAtChange, UserId, Summary, ContextNote tiền lệ PE: Changelogs = nguồn transition-marker/backfill (Mig 61) — có từ ngày 1
7 ContractSigningPlanCodeSequences Prefix PK, LastSeq khuôn ProposalCodeSequence.cs (13 dòng); format mã đề xuất KHKK/{YYYY}/{Seq:D3}owner chốt format

Biến thể tối thiểu 5 bảng (bỏ #6 Changelogs + #7 CodeSequences) — KHÔNG khuyến nghị: mất audit-trail transition (tiền lệ Mig 60/61 cho thấy cần) và mất mã phiếu atomic.

Enum ngoài bảng: ApprovalWorkflowApplicableType +ContractSigningPlan = 10 — append-only vào ApprovalWorkflow.cs:53-67 (slot 10 trống, em đọc trực tiếp; tiền lệ Mig 37 extend enum). ContractSigningPlanPhase enum MỚI giá trị sạch DangSoanThao=1/ChoDuyet=2/DaDuyet=3/TraLai=98/TuChoi=99 — KHÔNG copy 7/98 của PE (sẹo lịch sử enum PE, phiếu mới không nợ data cũ; 98/99 giữ cho quen mắt FE badge). KHÔNG thêm cột SigningPlanId lên PE — tra ngược bằng query Plans.Where(PurchaseEvaluationId==x); thêm cột = đẻ "cột có mà 0 ai đọc" (lớp lỗi 6-lần của repo).

DbSets: ApplicationDbContext + IApplicationDbContext (khuôn mọi module). 3-file rule mig (Up/Down + Designer

  • Snapshot).

B.3 CQRS + service + controller

  • Application/ContractSigningPlans/ContractSigningPlanFeatures.cs (mega-file khuôn ProposalFeatures.cs 556 dòng):
    • CreateContractSigningPlanCommand(PeId, ApprovalWorkflowId, GhiChu?) — validator: PE tồn tại + Phase==DaDuyet (mirror cầu CreateContractFromEvaluationFeatures.cs:53-54) + chưa có plan sống (AnyAsync(p.PeId==x && p.Phase!=TuChoi) → Conflict; cho tạo lại sau TuChoi) + workflow ApplicableType==ContractSigningPlan match (mirror Proposal :258-265). Handler auto-sinh Lines từ winners: pe.Suppliers.Where(IsWinner) + PeReferenceAmount = SUM Quote.IsSelected per winner (copy đúng phép tính cầu :56-62 + :88-90) — snapshot 1 lần.
    • UpdateContractSigningPlanDraftCommand — guard Phase ∈ {DangSoanThao, TraLai} + cho re-pin ApprovalWorkflowId (mirror PE PurchaseEvaluationFeatures.cs re-pin chỉ Nháp/TraLai; tránh vết Contract UpdateDraft KHÔNG có đường re-pin — S156 §2-bis).
    • Get/List/pendingMe — detail Include Lines+DossierItems+Attachments+LevelOpinions + workflow-tree JOIN (khuôn Proposal :161-228); inbox V2 precompute từ ngày 1 (mirror PE ResolveV2InboxIdsAsync — bẫy S156: Contract THIẾU cái này nên approver không có inbox ContractFeatures.cs:363-372 legacy-only).
    • UpsertDossierItemCommand / DeleteDossierItemCommand — BCH nhập hộ TVGS; mở cả khi ChoDuyet? KHÔNG — chỉ DangSoanThao/TraLai (căn cứ là input của con số đang duyệt); attachment mở mọi phase (triết lý PE S147 BE không phase-guard attachment).
    • Attachment upload/download/delete — khuôn ProposalAttachment + PE attachment features.
  • Infrastructure/Services/ContractSigningPlanWorkflowService.cs — copy ContractWorkflowService.ApproveV2Async:217-394 (B.1) + adapt 3 chỗ: (1) terminal → Phase=DaDuyet + ApplyApprovedValuesOnFinalize: mọi Line ApprovedAmount = ApprovedAmount ?? ProposedAmount (người duyệt cấp cuối được sửa trước khi duyệt nếu bật AllowApproverEditBudget-tương-tự; khuôn choke-point + RULE grep-4-site PurchaseEvaluationWorkflowService.cs:1008); (2) port 2 nhánh kết thúc sớm PE: AllowApproverFinalize :870-890 + finalizeByCcmDelegation+CeoApprovalThreshold :892-929 (tổng so ngưỡng = SUM ProposedAmount các Line); (3) Reject → 4 return-mode per-level (cờ sẵn ApprovalWorkflowLevel.cs:116-125, logic mirror PE ApplyReturnModeAsync:368-547) — tối thiểu TraLai toàn phần nếu muốn gọn wave đầu. Guard trình: CreatedBy==actor DeptManager Admin — CỐ Ý KHÁC khuôn role-Drafter (PurchaseEvaluationWorkflowService.cs:172-178 + ContractWorkflowService.cs:70-79) để né lớp 403-PMH (S156 §G3: role không khớp = kẹt cứng); nếu owner muốn đồng nhất role-based thì BCH users phải mang role Drafter. Notify: mirror LogTransitionAsync notify Drafter (ContractWorkflowService.cs:407-427) + THÊM notify đích danh approvers của Cấp kế (fix lỗ notify-chỉ-Drafter ngay từ ngày 1, đừng chép nợ).
  • Api/Controllers/ContractSigningPlansController.cs — authz 2 tầng (bài học #82/S118): class [Authorize]
    • per-action [Authorize(Policy = "ContractSigningPlans.{Read|Create|Update|Delete}")] — KHÔNG chép kiểu ContractsController.cs:12-13 class-trần (lỗ A1 S156). Policy TỰ SINH khi key vào MenuKeys.All (Program.cs:82-89 foreach All × Actions). Endpoints: GET list/inbox · GET {id} · POST · PUT {id} · POST {id}/transitions (body {targetPhase, decision, comment, returnMode?, skipToFinal?, applyLevelFinalize?, finalizeByCcmDelegation?, approvedLines?}) · dossier-items CRUD · attachments · DELETE {id} (allow-list DangSoanThao|TuChoi mirror PE PurchaseEvaluationFeatures.cs:1404).

B.4 Menu + permission + FE 2 app (Pattern 16-bis, twin Off_DeXuat đo thật)

  • BE: MenuKeys.cs +4 const kiểu Csp/Csp_List/Csp_Create/Csp_Inbox (khuôn OffDeXuat* :109-112) + đưa cả 4 vào All :156-174 (khuôn :168 — Off_DeXuat 4 key ĐỀU trong All ⇒ policy sinh đủ; ĐỪNG theo kiểu Pe_* factory-ngoài-All :142-144 vì per-action policy sẽ chưa tồn tại — bẫy S155 "policy-chưa-tồn-tại"). ⇒ 2 row canonical docs/STATUS.md (Menu keys · Policies DERIVED |All|×4) PHẢI cập nhật cùng lúc.
  • Seed: DbInitializer.cs menu 4 dòng (khuôn Off_DeXuat :1817-1820: root parent + 3 leaf, label VN "Kế hoạch ký kết HĐ"/"Danh sách"/"Tạo mới"/"Inbox duyệt") + admin-permission list (:2383 — grep đủ MỌI site seed-permission khi làm, S155 ghi nhận có 2 site cho vài nhóm); dark-launch được bằng IsVisible=0 (cột Mig 27) rồi bật sau.
  • FE ×2 app (duplicate CÓ CHỦ ĐÍCH): mỗi app 4 chỗ: (1) routes App.tsx (khuôn fe-user :77-79 /proposals ×3 route); (2) components/Layout.tsx staticMap (khuôn :95-97 Off_DeXuat_List: '/proposals'…); (3) lib/menuKeys.ts (khuôn :54-57); (4) pages ×3 (khuôn bộ Proposal 978 LOC/app: List 252 + Create 245 + Detail 386 + types 95) — Detail thêm: bảng Lines (đề xuất/chốt per-NCC) + bảng DossierItems (status chip + TVGS + scan) + panel workflow (mirror PE Panel-3 Steps/Levels ✓/●/○) + banner "Đến lượt bạn" (khuôn PE blockedByV2Level).
  • Designer admin nhận type 10: fe-admin ApprovalWorkflowsV2Page.tsx map typeCode→int :143 + các site BE ApprovalWorkflowV2AdminFeatures.cs đọc ApplicableType (grep "ApplicableType" toàn file khi làm — S155 F6 liệt ~5 dây BE + fe-user matrix-view; path file matrix-view fe-user em grep lượt này KHÔNG thấy fe-user/src/pages/WorkflowMatrixViewPage.tsx — vị trí thật cần grep lại khi implement, đừng tin line S155).
  • Cầu tiếp giáp (sửa 1 file): CreateContractFromEvaluationFeatures.cs — (a) đọc plan DaDuyet của PE: có → giaTri = line.ApprovedAmount khớp w.SupplierId thay SUM :88-90, ghi ContextNote lệch "giá KH x vs SUM-PE y" vào 2 changelog :121-140 (khuôn audit-note D4 S134); không có plan → giữ SUM + cảnh báo mềm (Q11 owner nâng cứng sau); (b) +param ApprovalWorkflowId? pin V2 cho HĐ (hiện pin V1-only :68-71,:108 — HĐ V2-less trình xong KẸT CỨNG ConflictException ContractWorkflowService.cs:115-116, em tự xác minh trên đĩa).
  • 1 dòng bắt buộc nhắc (ngoài scope, treo theo ý anh): lỗ Reject-trước-guard ContractWorkflowService.cs:48-66 + controller HĐ class-trần vẫn SỐNG trên prod — anh chốt 07-28 "từ từ".

§C — CHIA WAVE THI CÔNG + CHECKLIST ĐO ĐƯỢC

[PENDING]

§D — HẤP THỤ 6 việc sửa review-synthesis §G

[PENDING]