[CLAUDE] Workflow: W1 gỡ 2 bom per-type type-3 — deactivate per-Code (R-6 tập tường minh) + overview family; test-before RED→GREEN +4 test (672/672)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-08-10 18:20:40 +07:00
parent 5a77f4f127
commit 7bad4ae186
2 changed files with 361 additions and 14 deletions

View File

@ -82,7 +82,22 @@ public record AwDefinitionDto(
public record AwTypeSummaryDto(
int ApplicableType,
string ApplicableTypeLabel,
// `Active` = bản đang áp dụng ĐẦU TIÊN của loại (giữ nguyên nghĩa từ Mig 22 — FE đọc
// thẳng ô này: ApprovalWorkflowsV2Page.tsx thẻ landing + panel "đang áp dụng").
// ⚠️ Với loại per-Code-family, đây chỉ là MỘT trong N bản đang áp dụng (bản Version cao
// nhất gặp trước), KHÔNG đại diện cho `Code` nào cả ⇒ panel per-Code phải tự cắt theo
// `Code` (KHKK đang làm vậy: ApprovalWorkflowsV2Page.tsx:321-339), đừng đọc ô này.
AwDefinitionDto? Active,
// [S187 W1b] Các bản ĐANG ÁP DỤNG CÒN LẠI của loại — KHÔNG lặp lại bản trong `Active`.
// Tập đầy đủ những bản đang áp dụng = [Active] ActiveOthers.
//
// Vì sao cần: loại per-Code-family có NHIỀU quy trình cùng IsActive trong CÙNG một
// ApplicableType (type-10 KHKK 8 nhóm N1..N8 · type-3 HĐ 7 loại). Trước bản này overview
// chỉ trả 1 và N-1 bản còn lại chỉ còn đường ra qua `History` — mà `History` là KHO PHIÊN
// BẢN, FE dựng mục "phiên bản cũ" bằng `history.filter(d => !d.isActive)` ⇒ chúng KHÔNG
// xuất hiện ở đâu trên màn hình. Loại 1-active-per-type thì danh sách này RỖNG (không đổi gì).
List<AwDefinitionDto> ActiveOthers,
// KHO PHIÊN BẢN — MỌI version của loại, kể cả bản đang áp dụng (không lọc `!IsActive`).
List<AwDefinitionDto> History);
public record AwAdminOverviewDto(List<AwTypeSummaryDto> Types);
@ -206,10 +221,26 @@ public class GetAwAdminOverviewQueryHandler(
.Select(type =>
{
var versions = definitions.Where(d => d.ApplicableType == type).Select(ToDto).ToList();
// 🔴 [S187 W1b] Trả ĐỦ mọi bản đang áp dụng của loại, không chỉ bản đầu.
//
// Loại per-Code-family (type-10 KHKK 8 nhóm · type-3 HĐ 7 loại) có N bản cùng
// IsActive; bản trước lấy `versions.FirstOrDefault(v => v.IsActive)` ⇒ N-1 bản kia
// rơi hết vào `History`, nơi FE lọc `!isActive` để dựng danh sách phiên bản cũ ⇒
// chúng biến mất khỏi UI, im lặng.
//
// Chia 2 ô thay vì gộp 1: `Active` PHẢI giữ nguyên giá trị cũ (FE đang đọc thẳng),
// nên `ActiveOthers` gánh phần còn lại — hợp của 2 ô = trọn tập, KHÔNG phần tử nào
// xuất hiện hai lần. Không hỏi ApplicableType ở đây là CỐ Ý: luật là "có mấy bản
// đang áp dụng thì trả bấy nhiêu", tự đúng cho loại family thêm sau này; loại
// 1-active-per-type giữ y hệt hành vi cũ (`ActiveOthers` rỗng).
var actives = versions.Where(v => v.IsActive).ToList();
return new AwTypeSummaryDto(
(int)type,
AwLabels.Type.GetValueOrDefault(type, type.ToString()),
versions.FirstOrDefault(v => v.IsActive),
actives.FirstOrDefault(),
actives.Skip(1).ToList(),
versions);
})
.ToList();
@ -328,6 +359,22 @@ public class CreateAwDefinitionCommandValidator : AbstractValidator<CreateAwDefi
public class CreateAwDefinitionCommandHandler(IApplicationDbContext db)
: IRequestHandler<CreateAwDefinitionCommand, Guid>
{
// 🔴 [S187 W1a] Những loại tắt bản active theo phạm vi **per-CODE** — TẬP TYPE TƯỜNG MINH.
//
// Tiêu chí vào danh sách: loại đó có NHIỀU quy trình sống song song trong CÙNG một
// ApplicableType, mỗi cái một `Code` riêng, và mỗi cái phải tự giữ bản active của mình:
// • type-10 `ContractSigningPlan` (KHKK) — 8 nhóm duyệt `KHKK-N1..N8`
// (`DbInitializer.SeedKhkkGroupWorkflowsAsync`, seed cả 8 với IsActive=1);
// • type-3 `Contract` (HĐ) — 7 loại hợp đồng, mỗi loại một quy trình riêng.
//
// Thêm loại vào đây = quyết định về NGỮ NGHĨA của loại đó, không phải về hình dạng mã:
// mọi `Code` của loại đều đi nhánh per-Code, kể cả mã ngoài họ (R-6).
private static readonly ApprovalWorkflowApplicableType[] PerCodeDeactivationTypes =
[
ApprovalWorkflowApplicableType.ContractSigningPlan,
ApprovalWorkflowApplicableType.Contract,
];
public async Task<Guid> Handle(CreateAwDefinitionCommand request, CancellationToken ct)
{
var typeEnum = (ApprovalWorkflowApplicableType)request.ApplicableType;
@ -353,21 +400,26 @@ public class CreateAwDefinitionCommandHandler(IApplicationDbContext db)
.MaxAsync(w => (int?)w.Version, ct) ?? 0;
nextVersion++;
// Tắt bản đang active — PHẠM VI phụ thuộc loại (bản trước: LUÔN per-type).
// Tắt bản đang active — PHẠM VI phụ thuộc loại (bản gốc Mig 22: LUÔN per-type).
//
// • type-10 `ContractSigningPlan` (KHKK): **per-CODE**. Loại này có 8 quy trình
// SỐNG SONG SONG cùng type mỗi nhóm duyệt N1..N8 một `Code` riêng
// (`DbInitializer.SeedKhkkGroupWorkflowsAsync:609`, seed cả 8 với IsActive=1).
// Tắt theo type ⇒ tạo version mới cho MỘT nhóm sẽ hạ IsActive của CẢ 7 nhóm còn
// lại (mỗi nhóm mất quy trình active ⇒ FE hết auto-pin ⇒ không tạo được phiếu).
// • các loại khác: **per-TYPE** như cũ — 1 active/type là bất biến của chúng.
// • Loại nằm trong `PerCodeDeactivationTypes` (khai báo + tiêu chí ở ĐẦU class này):
// **per-CODE**. Chúng có NHIỀU quy trình SỐNG SONG SONG trong cùng một type, mỗi
// cái một `Code`. Tắt theo type ⇒ tạo version mới cho MỘT cái sẽ hạ IsActive của
// TẤT CẢ những cái còn lại (mỗi cái mất quy trình active ⇒ FE hết auto-pin ⇒
// không lập được phiếu), im lặng và KHÔNG có endpoint bật lại.
// • các loại còn lại: **per-TYPE** như cũ — với CHÚNG, 1 active/type vẫn là bất biến.
// 🔴 Đây là câu tự-khai từng SAI: bản trước viết "các loại khác: per-TYPE như cũ —
// 1 active/type là bất biến của chúng" khi tập per-Code chỉ có type-10, rồi type-3
// mọc thêm 7 quy trình `HD-*` sống song song ⇒ câu đó bao luôn type-3 và thành lời
// khai sai. Nay câu này neo vào TẬP TYPE tường minh, không neo vào con số.
//
// 🔴 [S182 R-6] Vị ngữ type-10 KHOÁ đúng 3 vế `Code == request.Code &&
// ApplicableType == typeEnum && IsActive`. CẤM thay bằng biến-thể regex
// `^KHKK-N[1-8]$`: `Code` type-10 nằm ngoài họ đó (vd `QT-KHKK-V2-001`) sẽ rơi
// lại nhánh per-type và nuke đúng 8 nhóm mà rào này sinh ra để cứu.
// (`nextVersion` `:334-337` vốn đã tính per-Code ⇒ nhánh này làm 2 vế khớp nhau.)
var actives = typeEnum == ApprovalWorkflowApplicableType.ContractSigningPlan
// 🔴 [S182 R-6 · giữ nguyên hiệu lực @S187] Vị ngữ per-Code KHOÁ đúng 3 vế `Code == code
// && ApplicableType == typeEnum && IsActive`. CẤM thay bằng biến-thể regex trên `Code`
// (`^KHKK-N[1-8]$`, `^HD-...$`): mã nằm ngoài họ (vd `QT-KHKK-V2-001`, `QT-HD-V2-001`)
// sẽ rơi lại nhánh per-type và nuke đúng tập mà rào này sinh ra để cứu. Phân loại theo
// ApplicableType, KHÔNG theo hình dạng mã. (`nextVersion` phía trên vốn đã tính
// per-Code toàn cục ⇒ nhánh này làm 2 vế khớp nhau.)
var actives = PerCodeDeactivationTypes.Contains(typeEnum)
? await db.ApprovalWorkflows
.Where(w => w.Code == code && w.ApplicableType == typeEnum && w.IsActive)
.ToListAsync(ct)