[CLAUDE] Domain+App+Infra+Admin: W1 KHKK — Mig 69 ContractSigningPlans 7 bang + policy KeHoachKyKet + seeder UPGRADE-if-exists + CodeGen KHKK + Designer type-10
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m40s

- Domain/ContractSigningPlans: 7 entity + 3 enum (Phase 1/2/3/98/99; DossierItem Kind/Status start=1); ApplicableType += ContractSigningPlan=10 (slot 10, append-only)
- Mig 69 AddContractSigningPlans: 7 CreateTable / 20 CreateIndex / 0 ALTER; UNIQUE filtered (PlanId,SupplierId) WHERE IsDeleted=0 (#57); FK 6 con Cascade 1-hop + 2 Restrict, 0 multiple-cascade-path; database-agent review PASS 8/8 + DB11
- MenuKeys.All += KeHoachKyKet (policy KeHoachKyKet.* x4 sinh runtime; menu-row prod KHONG doi — 142 giu); STATUS canonical 54->55 / 216->220
- DbInitializer.SeedKeHoachKyKetAccessAsync: UPGRADE-if-exists (F-S1 — khong insert-NO-OP) + vế CanDelete (Drafter/Admin), 13/13 role CanCreate+CanUpdate, chay SAU revoke; revoker khong phu Khkk_* (reviewer Truc-1 PASS)
- ContractSigningPlanCodeGenerator: KHKK/{YYYY}/{Seq:D3} (owner OK @S161) qua WorkflowAppCodeSequences dung chung, SERIALIZABLE
- AWV2AdminFeatures: 3 site count/retained/orphan + nhanh ContractSigningPlanLevelOpinions (chong no FK 500 khi xoa Level); +label VN type-10 (F-7); +ep false 2 co early-finalize server-side cho type-10 (F-8 — containment tang BE, FE da ep o levelFlagsPayload)
- fe-admin Designer: deep-link type-10 + an 2 checkbox early-finalize (anh phan "PHA VO" S155) — containment 2 lop render+payload
- tests: +4 (566 total, 0 fail) — schema-invariant 7 bang + codegen format (fault-inject 2 chieu chung co rang)

Reviewer: PASS_WITH_FLAGS 10 issue -> 8 fixed cung commit, 2 giu trang thai (F-1 acceptance đo-CO cho prod; F-4 ke thua y PE prod)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-29 20:04:39 +07:00
parent 909134c922
commit a8027c04fe
30 changed files with 8987 additions and 22 deletions

View File

@ -92,6 +92,9 @@ internal static class AwLabels
[ApprovalWorkflowApplicableType.DuyetNcc] = "Duyệt NCC",
[ApprovalWorkflowApplicableType.DuyetNccPhuongAn] = "Duyệt NCC + Giải pháp",
[ApprovalWorkflowApplicableType.Contract] = "Hợp đồng",
// [W1 S161 — reviewer F-7] Thiếu entry = fallback ToString() tiếng Anh lên tiêu đề
// Designer VÀ ghi vào cột Name DB (useState `Quy trình ${label}` phía FE).
[ApprovalWorkflowApplicableType.ContractSigningPlan] = "Kế hoạch ký kết HĐ",
};
}
@ -370,8 +373,12 @@ public class CreateAwDefinitionCommandHandler(IApplicationDbContext db)
AllowReturnToDrafter = l.AllowReturnToDrafter,
AllowApproverEditDetails = l.AllowApproverEditDetails,
AllowApproverEditBudget = l.AllowApproverEditBudget,
AllowApproverSkipToFinal = l.AllowApproverSkipToFinal,
AllowApproverFinalize = l.AllowApproverFinalize,
// [W1 S161 — reviewer F-8, F-B3 containment TẦNG BE] type=10 KHKK: 2 đường
// early-finalize anh phán "PHÁ VỠ" (S155), W3 không port code path ⇒ ép false
// server-side (FE đã ép ở levelFlagsPayload — đây chặn curl bypass). PUT
// sửa-tại-chỗ đã có echo-so-BẰNG chặn đổi cờ ⇒ chỉ cần guard ở CREATE.
AllowApproverSkipToFinal = typeEnum != ApprovalWorkflowApplicableType.ContractSigningPlan && l.AllowApproverSkipToFinal,
AllowApproverFinalize = typeEnum != ApprovalWorkflowApplicableType.ContractSigningPlan && l.AllowApproverFinalize,
AllowApproverDelete = l.AllowApproverDelete, // [Mig 68 S155] F6
}).ToList(),
})
@ -842,7 +849,9 @@ public class UpdateAwDefinitionCommandHandler(
// được "quy trình vừa bị sửa lúc nào, ai sửa, vì sao".
// ⚠️ KHAI GIỚI HẠN: repo CHƯA có bảng changelog cho quy trình duyệt, mà
// đợt 1 bị chốt "no new table" ⇒ vết cho workflow thuộc 5 module Văn
// phòng số (Proposal/Leave/Ot/Travel/Vehicle) hiện CHỈ có UpdatedAt/
// phòng số (Proposal/Leave/Ot/Travel/Vehicle) + KHKK (W1 S161 — reviewer F-6b:
// `ContractSigningPlanChangelogs` là changelog PHIẾU, không phải changelog QUY-TRÌNH;
// wire nếu cần ở W2/W3) hiện CHỈ có UpdatedAt/
// UpdatedBy ở (a). Bảng `ApprovalWorkflowChangelogs` = việc tách riêng.
if (changes.Count > 0 &&
def.ApplicableType is ApprovalWorkflowApplicableType.DuyetNcc
@ -883,8 +892,8 @@ public class UpdateAwDefinitionCommandHandler(
await tx.CommitAsync(ct);
}
// Đếm chữ ký CÒN HIỆU LỰC trên các dòng Cấp — quét CẢ 7 bảng LevelOpinion đang
// FK vào ApprovalWorkflowLevels (PE + HĐ + 5 module Văn phòng số). Chỉ soi PE là
// Đếm chữ ký CÒN HIỆU LỰC trên các dòng Cấp — quét CẢ 8 bảng LevelOpinion đang
// FK vào ApprovalWorkflowLevels (PE + HĐ + 5 module Văn phòng số + KHKK W1). Chỉ soi PE là
// vẫn nổ FK 547 khi quy trình thuộc module khác (§F.3 blast radius ≥6 module).
// 🔴 `!p.IsDeleted` viết TƯỜNG MINH: chỉ PE + Contract có global query filter,
// 5 bảng Office KHÔNG có ⇒ dựa filter là đúng PE mà sai 5 module kia.
@ -938,6 +947,16 @@ public class UpdateAwDefinitionCommandHandler(
.GroupBy(o => o.ApprovalWorkflowLevelId)
.Select(g => new LevelOpinionCount(g.Key, g.Count())).ToListAsync(ct));
// [W1 Mig 69 — S161] GĐ2 Kế hoạch ký kết HĐ (type=10). Site liệt-kê-thủ-công:
// module mới có LevelOpinions PHẢI vào CẢ 3 danh sách trong file này
// (count-sống · retained-ref · orphan-delete) — thiếu 1 = admin xoá Level nổ FK 500.
// Tự kiểm: grep -n "LevelOpinions.AsNoTracking\|LevelOpinions$" file này.
Merge(await db.ContractSigningPlanLevelOpinions.AsNoTracking()
.Where(o => levelIds.Contains(o.ApprovalWorkflowLevelId) && !o.IsDeleted
&& db.ContractSigningPlans.Any(p => p.Id == o.ContractSigningPlanId && !p.IsDeleted))
.GroupBy(o => o.ApprovalWorkflowLevelId)
.Select(g => new LevelOpinionCount(g.Key, g.Count())).ToListAsync(ct));
return acc;
}
@ -948,7 +967,7 @@ public class UpdateAwDefinitionCommandHandler(
// 🔴 `IgnoreQueryFilters()` LÀ BẮT BUỘC, không phải trang trí: PE và Contract có global
// filter `!IsDeleted` (`PurchaseEvaluationConfiguration.cs:84`). Không tắt filter thì hồ
// sơ xoá mềm biến mất khỏi phép JOIN ⇒ ta lại kết luận "mồ côi" ⇒ đúng cái sai đang chữa.
// (5 bảng Văn phòng số KHÔNG có filter — gọi vẫn vô hại, giữ 7 khối cùng một hình dạng.)
// (5 bảng Văn phòng số + KHKK-LevelOpinions KHÔNG có filter — gọi vẫn vô hại, giữ 8 khối cùng một hình dạng.)
private async Task<Dictionary<Guid, List<RetainedOpinionRef>>> ListRetainedOpinionsAsync(
List<Guid> levelIds, CancellationToken ct)
{
@ -1003,6 +1022,14 @@ public class UpdateAwDefinitionCommandHandler(
.Select(x => new RetainedOpinionRef(x.o.ApprovalWorkflowLevelId, "Đơn xe", x.p.MaDonTu, x.p.IsDeleted))
.ToListAsync(ct));
// [W1 Mig 69 — S161] GĐ2 Kế hoạch ký kết HĐ — mirror nhánh trên (site 2/3).
rows.AddRange(await db.ContractSigningPlanLevelOpinions.AsNoTracking().IgnoreQueryFilters()
.Where(o => levelIds.Contains(o.ApprovalWorkflowLevelId))
.Join(db.ContractSigningPlans, o => o.ContractSigningPlanId, p => p.Id, (o, p) => new { o, p })
.Where(x => x.o.IsDeleted || x.p.IsDeleted)
.Select(x => new RetainedOpinionRef(x.o.ApprovalWorkflowLevelId, "Kế hoạch ký kết HĐ", x.p.MaKeHoach, x.p.IsDeleted))
.ToListAsync(ct));
return rows.GroupBy(r => r.LevelId).ToDictionary(g => g.Key, g => g.ToList());
}
@ -1061,6 +1088,11 @@ public class UpdateAwDefinitionCommandHandler(
.Where(o => levelIds.Contains(o.ApprovalWorkflowLevelId)
&& !db.VehicleBookings.IgnoreQueryFilters().Any(p => p.Id == o.VehicleBookingId))
.ExecuteDeleteAsync(ct);
// [W1 Mig 69 — S161] GĐ2 Kế hoạch ký kết HĐ — mirror nhánh trên (site 3/3).
n += await db.ContractSigningPlanLevelOpinions
.Where(o => levelIds.Contains(o.ApprovalWorkflowLevelId)
&& !db.ContractSigningPlans.IgnoreQueryFilters().Any(p => p.Id == o.ContractSigningPlanId))
.ExecuteDeleteAsync(ct);
return n;
}

View File

@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore;
using SolutionErp.Domain.ApprovalWorkflowsV2;
using SolutionErp.Domain.ContractSigningPlans;
using SolutionErp.Domain.Contracts;
using SolutionErp.Domain.Contracts.Details;
using SolutionErp.Domain.Forms;
@ -141,5 +142,17 @@ public interface IApplicationDbContext
// Phase 11 P11-B Wave 1 (Mig 42) — Số dư phép theo năm (NV × LoạiPhép × Năm).
DbSet<LeaveBalance> LeaveBalances { get; }
// [W1 Mig 69 — S161] GĐ2 "Kế hoạch ký kết HĐ" (ContractSigningPlan) — 7 bảng.
// Workflow V2 ApplicableType = ContractSigningPlan = 10 (`ApprovalWorkflow.cs:66`).
// Mã phiếu "KHKK/{YYYY}/{Seq:D3}" atomic qua `WorkflowAppCodeSequences` DÙNG CHUNG (:136),
// KHÔNG bảng sequence riêng.
DbSet<ContractSigningPlan> ContractSigningPlans { get; }
DbSet<ContractSigningPlanLine> ContractSigningPlanLines { get; }
DbSet<ContractSigningPlanDossierItem> ContractSigningPlanDossierItems { get; }
DbSet<ContractSigningPlanLevelOpinion> ContractSigningPlanLevelOpinions { get; }
DbSet<ContractSigningPlanAttachment> ContractSigningPlanAttachments { get; }
DbSet<ContractSigningPlanChangelog> ContractSigningPlanChangelogs { get; }
DbSet<ContractSigningPlanApproval> ContractSigningPlanApprovals { get; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@ -0,0 +1,14 @@
namespace SolutionErp.Application.ContractSigningPlans.Services;
// [W1 Mig 69 — S161 2026-07-29] Atomic codegen mã phiếu "Kế hoạch ký kết HĐ" (GĐ2)
// — mirror IPurchaseEvaluationCodeGenerator.
// Format anh chốt @S161: KHKK/{YYYY}/{Seq:D3} (VD: KHKK/2026/001, KHKK/2026/002).
// - Sequence per-năm qua bảng WorkflowAppCodeSequences DÙNG CHUNG (per-prefix row,
// IApplicationDbContext) — KHÔNG bảng sequence riêng (spec W1 §②-9, #8-CẮT spec cũ).
// - Seq reset đầu năm tự nhiên (key Prefix "KHKK/{YYYY}" mới).
// Caller: W2 Submit (gen lúc trình duyệt, KHÔNG lúc tạo nháp — mirror quyết spec cũ).
public interface IContractSigningPlanCodeGenerator
{
// Gen mã atomic — transaction SERIALIZABLE chống race (mirror PE/Contract codegen).
Task<string> GenerateAsync(CancellationToken ct = default);
}