Files
solution-erp/.claude/workflows/runs/2026-07-29-S160-khkk-dryrun-plan/spec-wave-w2-crud-nhap-29-07-2026.md
2026-07-29 16:54:56 +07:00

5.4 KiB
Raw Blame History

SPEC WAVE-2 — CRUD phiếu nháp + căn cứ b.8-9 + FE 3 page ×2 app (GĐ2 KHKK)

Nguồn RATIFIED: spec cũ §2.3 (CQRS) + §2.4 (FE 4-place) + §③ WAVE-2 (:310-339). Chạy SAU W1.

① TÍNH NĂNG / ĐỀ BÀI

BCH tạo được phiếu nháp "Kế hoạch ký kết HĐ" từ PE DaDuyet: dòng giá đề xuất per-winner (snapshot PeReferenceAmount), danh mục căn cứ b.8-9 (mẫu/shopdrawing TVGS per-item status), đính kèm — chưa trình được (trình = W3). Người dùng thấy: 6 leaf Khkk_* hết ComingSoon, thành trang thật.

② CÁCH IMPLEMENT

  1. Gate 5-anchor (§③-A).
  2. BE Application/ContractSigningPlans/ContractSigningPlanFeatures.cs (mega-file khuôn ProposalFeatures.cs — CHỈ CRUD/DTO, 🔴 CẤM copy phần duyệt của Proposal, bẫy §2.1 spec cũ):
    • CreateContractSigningPlanCommand(PeId, ApprovalWorkflowId, GhiChu?): validator PE tồn tại + Phase==DaDuyet (mirror PurchaseEvaluations/CreateContractFromEvaluationFeatures.cs:53-54 — đường dẫn ĐẦY ĐỦ, file KHÔNG nằm ở Contracts/) + chưa-có-plan-sống (AnyAsync(p.PeId==x && p.Phase != TuChoi) → 409; cho tạo lại sau TuChoi) + workflow ApplicableType==10 (mirror Proposal :258-267 — copy CẢ guard :266-267, đừng chỉ copy phần nạp). Handler auto-sinh Lines từ winners: pe.Suppliers.Where(IsWinner), PeReferenceAmount = SUM(Quotes WHERE IsSelected) per-winner (copy phép tính :56-62,:88-90) — snapshot 1 lần. Sinh MaKeHoach = KHKK/{YYYY}/{Seq:D3} (W1 CodeGen).
    • UpdateContractSigningPlanDraftCommand: guard Phase ∈ {DangSoanThao, TraLai} + cho re-pin ApprovalWorkflowId.
    • Get/List/pendingMe: detail Include Lines+DossierItems+Attachments+LevelOpinions+workflow-tree (khuôn Proposal :161-228); inbox V2 precompute TỪ NGÀY 1 (mirror PE ResolveV2InboxIdsAsync) — wire sẵn cho W3.
    • UpsertDossierItemCommand/DeleteDossierItemCommand: chỉ mở DangSoanThao|TraLai. Attachment mở mọi phase (triết lý PE S147).
    • DeleteContractSigningPlanCommand: allow-list DangSoanThao|TuChoimở NGAY W2 (spec cũ M6: thiếu nó, tạo nhầm 1 phiếu là PE bị khoá 409 vĩnh viễn).
    • Màn nguồn "PE đã duyệt — chờ lập KH": query mirror picker ListApprovedPurchaseEvaluationsQuery:173 đổi vị ngữ && chưa-có-plan-sống.
  3. Controller ContractSigningPlansController.cs🔴 authz 2 TẦNG: class [Authorize] + per-action [Authorize(Policy="KeHoachKyKet.{Read|Create|Update|Delete}")] (Δ W1: prefix KeHoachKyKet, KHÔNG phải ContractSigningPlans như spec cũ :202). Endpoints: GET list/inbox · GET {id} · POST · PUT {id} · dossier-items CRUD · attachments · DELETE {id}. (transitions = W3.)
  4. FE ×2 app (duplicate CÓ CHỦ ĐÍCH, 4-place mirror Pattern 16-bis): pages/khkk/ 3 page (List ~252 + Create ~245 + Detail ~386 + types ~95 — khuôn bộ Proposal) · App.tsx routes · Layout.tsx staticMap re-point 6 leaf Khkk_* khỏi /coming-soon (List→list · Create→create · Pending→list?filter=ChoDuyet · Approved→list?filter=DaDuyet · Deleted→deleted-view · WfView→matrix type=10) · lib/menuKeys.ts. Detail: bảng Lines (đề xuất per-NCC) + bảng DossierItems (chip status + TVGS + scan) + attachments; panel workflow + banner = W3.
  5. Build ×2 + test + commit.

③ CHECKLIST

A. Gate 5-anchor: ProposalFeatures.cs:427-439 (bẫy flatten — để BIẾT mà né) · CreateContractFromEvaluationFeatures.cs:53-62 (guard+phép tính snapshot) · ProposalFeatures.cs:258-267 (guard workflow-type trọn) · DbInitializer.cs:2207 khu staticMap comment Khkk_* (leaf đang ComingSoon) · MenuKeys.cs All (KeHoachKyKet ĐÃ vào từ W1). B. Acceptance (spec cũ W2 :322-337 + Δ):

  • POST /api/contract-signing-plans từ PE DaDuyet → 201; DB N Lines = số winner (đếm đối chiếu PurchaseEvaluationSuppliers WHERE IsWinner=1).
  • PeReferenceAmount mỗi Line == SUM Quotes IsSelected đúng NCC (2 câu đếm so khớp).
  • Tạo lần 2 cùng PE → 409. · [ ] PE chưa DaDuyet → 400/409, KHÔNG 500.
  • 🔴 Authz 2 tầng: user thiếu quyền POST → 403 (không 200/401/500); có quyền → 201. Gate rẻ: grep -c "Authorize(Policy" ContractSigningPlansController.cs ≥ 4.
  • DELETE ở DangSoanThao → 204; sau đó tạo lại plan cho CÙNG PE → 201 (chứng thoát khoá 409).
  • dotnet test ≥ baseline + 2, PASS 0 fail, tên khuôn repo (English-predicate): ContractSigningPlansController_EveryWriteEndpoint_HasAuthorizePolicy + CreateContractSigningPlan_FromNonApprovedPe_Throws.
  • 🔴 Grant ăn (review F-04): sau seed, Permission KeHoachKyKetCanCreate=1 cho role đích (đo CỜ — row đã tồn tại nên đếm-row 0-bit); user role đó POST → 201, user không-cấp → 403 (KHÔNG 500 — chứng policy KeHoachKyKet.* sinh đúng, khớp key ∈ All).
  • FE build ×2 PASS · SHA256 file mirror IDENTICAL · 6 leaf Khkk_* hết trỏ coming-soon: grep "Khkk_" fe-user/src/components/Layout.tsx | grep -c "coming-soon" = 0 (cùng lệnh fe-admin).
  • cicd 3-chân-kiềng + byte-marker bundle (control ÂM độc quyền — chọn chuỗi trước khi đo). C. Rủi ro: [ ] HasQueryFilter hoạt động (phiếu xóa mềm không hiện list) · [ ] tạo-lại-sau-xóa không bị chặn oan (gotcha #57).