22 KiB
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)
AllowApproverFinalizeper-slot opt-out — PE:870-890; (iii)AllowApproverSkipToFinalper-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 | CÓ | Đố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 CeoApprovalThresholdApprovalWorkflow.cs:43+ 9 cờ per-slotApprovalWorkflowLevel.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ônProposalFeatures.cs556 dòng):CreateContractSigningPlanCommand(PeId, ApprovalWorkflowId, GhiChu?)— validator: PE tồn tại +Phase==DaDuyet(mirror cầuCreateContractFromEvaluationFeatures.cs:53-54) + chưa có plan sống (AnyAsync(p.PeId==x && p.Phase!=TuChoi)→ Conflict; cho tạo lại sau TuChoi) + workflowApplicableType==ContractSigningPlanmatch (mirror Proposal:258-265). Handler auto-sinh Lines từ winners:pe.Suppliers.Where(IsWinner)+PeReferenceAmount = SUM Quote.IsSelectedper winner (copy đúng phép tính cầu:56-62+:88-90) — snapshot 1 lần.UpdateContractSigningPlanDraftCommand— guardPhase ∈ {DangSoanThao, TraLai}+ cho re-pin ApprovalWorkflowId (mirror PEPurchaseEvaluationFeatures.csre-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 PEResolveV2InboxIdsAsync— bẫy S156: Contract THIẾU cái này nên approver không có inboxContractFeatures.cs:363-372legacy-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— copyContractWorkflowService.ApproveV2Async:217-394(B.1) + adapt 3 chỗ: (1) terminal →Phase=DaDuyet+ApplyApprovedValuesOnFinalize: mọi LineApprovedAmount = ApprovedAmount ?? ProposedAmount(người duyệt cấp cuối được sửa trước khi duyệt nếu bậtAllowApproverEditBudget-tương-tự; khuôn choke-point + RULE grep-4-sitePurchaseEvaluationWorkflowService.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 ProposedAmountcác Line); (3) Reject → 4 return-mode per-level (cờ sẵnApprovalWorkflowLevel.cs:116-125, logic mirror PEApplyReturnModeAsync: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: mirrorLogTransitionAsyncnotify 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ểuContractsController.cs:12-13class-trần (lỗ A1 S156). Policy TỰ SINH khi key vàoMenuKeys.All(Program.cs:82-89foreach 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 PEPurchaseEvaluationFeatures.cs:1404).
- per-action
B.4 Menu + permission + FE 2 app (Pattern 16-bis, twin Off_DeXuat đo thật)
- BE:
MenuKeys.cs+4 const kiểuCsp/Csp_List/Csp_Create/Csp_Inbox(khuônOffDeXuat*:109-112) + đưa cả 4 vàoAll:156-174(khuôn:168— Off_DeXuat 4 key ĐỀU trong All ⇒ policy sinh đủ; ĐỪNG theo kiểuPe_*factory-ngoài-All:142-144vì per-action policy sẽ chưa tồn tại — bẫy S155 "policy-chưa-tồn-tại"). ⇒ 2 row canonicaldocs/STATUS.md(Menu keys · Policies DERIVED |All|×4) PHẢI cập nhật cùng lúc. - Seed:
DbInitializer.csmenu 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ằngIsVisible=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.tsxstaticMap (khuôn:95-97Off_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 PEblockedByV2Level). - Designer admin nhận type 10: fe-admin
ApprovalWorkflowsV2Page.tsxmap typeCode→int:143+ các site BEApprovalWorkflowV2AdminFeatures.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ấyfe-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.ApprovedAmountkhớpw.SupplierIdthay 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) +paramApprovalWorkflowId?pin V2 cho HĐ (hiện pin V1-only:68-71,:108— HĐ V2-less trình xong KẸT CỨNGConflictExceptionContractWorkflowService.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]