[CLAUDE] Contract: KHKK man duyet — bo "(N dong)" + cot STT + ma hang muc (Mig 72)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 7m2s

Anh giao qua anh prod man duyet KHKK, 2 diem khoanh do:
 (1) "Bo 1 dong di nhe" -> go hau to "(N dong)" khoi title Section 2.
 (2) "Hien thi stt va ma hang muc (Du an) - ma hang muc chi tiet"
     -> bang 3->4 cot (+STT cot rieng, anh chot), cot HANG MUC them dong
        ma "<ma du an> - <ma chi tiet>" nam TREN ten.

Mig 72 AddMaHangMucToContractSigningPlanLines (anh chot DENORM co migration):
 - +cot MaHangMuc nvarchar(20) = BANG nguon ContractCatalogEntry.Code.
 - backfill Sql() idempotent (WHERE MaHangMuc IS NULL), COY KHONG loc IsDeleted:
   hang muc xoa mem SAU ngay lap phieu thi dong phieu van phai giu duoc ma —
   do chinh la ly do cot nay la denorm. Loc di la bo trong dung dong can cuu nhat.
 - ten bang doi chieu voi ToTable() cua CA HAI configuration truoc khi viet
   (Mig 60 tung backfill soi NHAM BANG, chay sach, phai de Mig 61 va — gotcha #78/#79).

PeWorkItemCode KHONG denorm — noi join WorkItems san co o Features.cs:929, 0 cot moi.
Chup lai vao KHKK la chup mot thu von da la con tro loose-Guid qua PE (Mig 49).

3 write-site denorm sua DU: :527 moi null · :695 gan lai · :805 tao moi.
Sot 1 cho = de dong co ten ma khong co ma, chi lo ra o luong it ai bam.

FE 2 app SHA IDENTICAL (verify identical @HEAD TRUOC khi copy de).
Build BE 0 warning 0 error · 2 app build sach · 644/644 test pass.
Lint 42 van de = NO CO SAN, 0 cai o 2 file luot nay dung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-08-06 14:47:08 +07:00
parent d081681e04
commit 530af22910
10 changed files with 7245 additions and 14 deletions

View File

@ -87,7 +87,11 @@ public record ContractSigningPlanLineDto(
// [K2 S164] append-CUỐI — hạng mục SP-002 của dòng. `TenHangMuc` là DENORM lúc gán
// (đừng join ngược lấy tên mới: danh mục đổi tên không được viết lại lịch sử phiếu).
Guid? CatalogEntryId,
string? TenHangMuc);
string? TenHangMuc,
// [Mig 72 — S177] append-CUỐI. MÃ hạng mục ("A1-01"), DENORM cùng lúc với `TenHangMuc`
// ngay trên — KHÔNG join ngược `ContractCatalogEntries` lấy mã hiện tại (ghép mã-mới với
// tên-lúc-ký là sai lệch khó thấy hơn cả để trôi cả hai; xem entity `:29-35`).
string? MaHangMuc);
public record ContractSigningPlanDossierItemDto(
Guid Id,
@ -220,7 +224,12 @@ public record ContractSigningPlanDetailDto(
bool EndedByLevelFinalize,
string? FinalizeStepName,
string? FinalizeLevelName,
List<ContractSigningPlanApprovalDto> Approvals);
List<ContractSigningPlanApprovalDto> Approvals,
// [S177] append-CUỐI. MÃ của hạng mục CÔNG VIỆC DỰ ÁN ("DAO_MONG") — cặp mã của
// `PeWorkItemName` ở `:178`. 🔴 KHÔNG denorm: khác `Line.MaHangMuc`, cái này lấy qua
// JOIN SỐNG `WorkItems` ngay tại handler (`:929`) y như cái tên đã lấy — chụp lại vào
// KHKK là chụp một thứ vốn đã là con trỏ loose-Guid qua PE (Mig 49).
string? PeWorkItemCode);
/// Dòng picker "PE đã duyệt — chờ lập kế hoạch ký kết".
public record ApprovedPeAwaitingPlanDto(
@ -525,6 +534,7 @@ public class CreateContractSigningPlanCommandHandler(
// (`ContractSigningPlanWorkflowService.SubmitAsync`) chặn trình khi còn dòng NULL.
CatalogEntryId = null,
TenHangMuc = null,
MaHangMuc = null, // [Mig 72] cặp mã+tên đi liền — mồi thì cùng NULL
PeReferenceAmount = peReference,
// Mồi ProposedAmount = số PE. W2 CHƯA có đường sửa dòng giá (spec §②-2 không
// liệt kê command nào cho Line) ⇒ để 0 thì cả phiếu là 0 và không ai sửa được.
@ -693,6 +703,7 @@ public class UpdateContractSigningPlanLineCommandHandler(
// Denorm tên TẠI THỜI ĐIỂM GÁN (khuôn `SignedByFullName` Mig 26 — entity `:25-27`):
// danh mục đổi tên sau KHÔNG viết lại lịch sử phiếu.
line.TenHangMuc = entry.TenVi;
line.MaHangMuc = entry.Code; // [Mig 72] chụp CÙNG LÚC với tên — xem entity `:29-35`
changes.Add($"hạng mục → '{entry.Code} — {entry.TenVi}'");
}
@ -803,6 +814,7 @@ public class AddContractSigningPlanLineCommandHandler(
SupplierId = request.SupplierId,
CatalogEntryId = entry.Id,
TenHangMuc = entry.TenVi,
MaHangMuc = entry.Code, // [Mig 72] chụp CÙNG LÚC với tên — xem entity `:29-35`
// 🔴 = 0, KHÔNG copy số tham chiếu của NCC. `PeReferenceAmount` là Σ báo giá ĐƯỢC
// CHỌN của CẢ NCC đó trên phiếu PE, đã nằm trọn ở dòng gốc (`:512-515`). Lặp lại ở
// dòng thêm tay = ĐẾM ĐÔI tiền tham chiếu của dự án. Nó cũng là DẤU phân biệt
@ -923,12 +935,16 @@ public class GetContractSigningPlanQueryHandler(IApplicationDbContext db, ICurre
.Select(p => new { p.MaPhieu, p.TenGoiThau, p.WorkItemId })
.FirstOrDefaultAsync(ct);
// `WorkItemId` là loose-Guid (Mig 49 — KHÔNG FK vật lý) ⇒ join tay, null-safe.
var peWorkItemName = pe != null && pe.WorkItemId != null
// [S177] Lấy LUÔN `Code` trong cùng 1 lượt đọc thay vì thêm query thứ hai — anh muốn
// hiện "mã hạng mục (Dự án) - mã hạng mục chi tiết" trên từng dòng bảng hạng mục.
var peWorkItem = pe != null && pe.WorkItemId != null
? await db.WorkItems.AsNoTracking()
.Where(w => w.Id == pe.WorkItemId)
.Select(w => w.Name)
.Select(w => new { w.Name, w.Code })
.FirstOrDefaultAsync(ct)
: null;
var peWorkItemName = peWorkItem?.Name;
var peWorkItemCode = peWorkItem?.Code;
var project = await db.Projects.AsNoTracking()
.Where(p => p.Id == plan.ProjectId)
.Select(p => new { p.Name, p.Code })
@ -1045,7 +1061,7 @@ public class GetContractSigningPlanQueryHandler(IApplicationDbContext db, ICurre
// [K2 S164] Tên hạng mục lấy từ CỘT DENORM của chính dòng, KHÔNG join ngược sang
// `ContractCatalogEntries`: danh mục sửa tên/xoá mềm sau ngày ký không được phép
// viết lại nội dung phiếu đã lập (khuôn `SignedByFullName` Mig 26).
l.CatalogEntryId, l.TenHangMuc))
l.CatalogEntryId, l.TenHangMuc, l.MaHangMuc))
.ToList();
var dossierItems = plan.DossierItems
@ -1144,7 +1160,9 @@ public class GetContractSigningPlanQueryHandler(IApplicationDbContext db, ICurre
// bảo FE "đọc `ApprovedAmount` + khoá ô" thay vì đọc `ProposedAmount`.
plan.Phase == ContractSigningPlanPhase.DaDuyet,
// [S171 BE-1/BE-1b/BE-2] 5 field nuôi panel 3.
wfVersion, endedByLevelFinalize, finalizeStepName, finalizeLevelName, approvals);
wfVersion, endedByLevelFinalize, finalizeStepName, finalizeLevelName, approvals,
// [S177] mã hạng mục CÔNG VIỆC DỰ ÁN — cùng lượt đọc `WorkItems` với `peWorkItemName`.
peWorkItemCode);
}
}