[CLAUDE] Contract: K8-blocker Lines-editor KHKK — gan hang muc SP-002 cho dong (command hua-trong-comment chua tung land)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m27s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m27s
- Dry-run K8 bat TRUOC gio chay: auto-gen line moi CatalogEntryId=null + comment hen "UpdateContractSigningPlanLineCommand" (K4b) nhung K4b that ship ?group= => 0 impl/0 route/0 FE; submit-guard va-5 chan null => MOI phieu KHKK tao sau K2 ket Nhap vinh vien (dung cho cicd-K2 khai "chan-ly-rong")
- BE: UpdateContractSigningPlanLineCommand(PlanId,LineId,CatalogEntryId) — EnsureDraftEditable + rao ContractId!=null 409 (gate F-6) + fail-fast entry song/IsActive/dung-nhom 409 + denorm TenHangMuc + changelog EntityType.Line EntityId=line.Id + ten NCC vao Summary (gate F-1)
- API: PUT /contract-signing-plans/{id}/lines/{lineId} policy KeHoachKyKet.Update + UpdateSigningPlanLineBody
- FE x2 app KhkkDetailPage (SHA-pair 0d6147d0): select gan tai bang (Nhap/TraLai, khoa dong da bac cau HD) + option-fallback giu hang-muc-da-luu khi vang options (gate F-2, 3 ca loading/error/ngung-hieu-luc) + dong loi query + amber nhom-0-hang-muc (F-3) + aria-label per-dong (F-8)
- Test +5 Fact: duong gan->submit-QUA guard that · khac-nhom 409 · phase 409 · IsActive=false 409 · gan-DE doi ca Id lan snapshot (gate F-4)
- Gate PASS-WITH-FLAGS-8/0-blocker — disposition tung dong trong sub-reviewer-gate-lineeditor.md; F-5/F-7 ghi no cung goi RowVersion/unique voi no K7 race-2-request
- scripts/backup-sql.ps1 default D:\ -> C:\ (VPS khong co o D — do that B0)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@ -900,4 +900,144 @@ public class ContractSigningPlanGroupAndCatalogTests
|
||||
.Select(p => new PlanScope(p.MaKeHoach!, p.ApprovalGroup, p.ApprovalWorkflowId!.Value))
|
||||
.ToListAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// [S168 K8-blocker] Lines-editor `UpdateContractSigningPlanLineCommand` — mảnh comment
|
||||
// auto-gen HẸN cho K4b nhưng chưa từng land, dry-run K8 bắt: submit-guard vá-5 chặn
|
||||
// line null mà 0 đường gán ⇒ phiếu mới kẹt Nháp vĩnh viễn. 3 phép đo: DƯƠNG đi trọn
|
||||
// (gán → submit QUA — nối thẳng vào guard thật, không đo gán suông) · ÂM sai nhóm ·
|
||||
// ÂM sai phase. Handler THẬT, không mock (khuôn Act-helpers file này).
|
||||
// ===================================================================
|
||||
|
||||
private static async Task AssignLineAsync(
|
||||
KhkkGroupFixture f, TestCurrentUser actor, Guid planId, Guid lineId, Guid catalogId)
|
||||
{
|
||||
await using var db = f.NewDb(actor);
|
||||
var handler = new UpdateContractSigningPlanLineCommandHandler(db, actor);
|
||||
await handler.Handle(
|
||||
new UpdateContractSigningPlanLineCommand(planId, lineId, catalogId), CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GanHangMuc_DungNhom_SnapshotTen_RoiTrinhQuaGuard()
|
||||
{
|
||||
using var f = new KhkkGroupFixture();
|
||||
var drafter = As(await AddUserAsync(f, "BCH lập kế hoạch"));
|
||||
var approver = await AddUserAsync(f, "NV duyệt Cấp 1");
|
||||
var wf = await SeedWorkflowAsync(f, "QT-KHKK-LINE-A", steps: [[[approver.Id]]]);
|
||||
var catalog = await AddCatalogAsync(f, approvalGroup: 4, tenVi: "Xi măng");
|
||||
var planId = await SeedPlanAsync(f, drafter, wf, "KHKK/2026/101",
|
||||
ContractSigningPlanPhase.DangSoanThao, approvalGroup: 4,
|
||||
lines: [new LineSeed(100m, 100m, CatalogId: null)]);
|
||||
|
||||
var lineId = (await ReadPlanAsync(f, planId)).Lines.Single().Id;
|
||||
await AssignLineAsync(f, drafter, planId, lineId, catalog);
|
||||
|
||||
var line = (await ReadPlanAsync(f, planId)).Lines.Single();
|
||||
line.CatalogEntryId.Should().Be(catalog);
|
||||
line.TenHangMuc.Should().Be("Xi măng",
|
||||
"tên hạng mục phải denorm TẠI THỜI ĐIỂM GÁN (khuôn SignedByFullName Mig 26)");
|
||||
|
||||
// Nối trọn vào submit-guard THẬT: gán xong thì trình phải QUA (ca dương của vá-5,
|
||||
// chống thế giới nơi gán ăn nhưng submit vẫn đỏ vì lý do khác).
|
||||
await ActAsync(f, drafter, planId, "submit");
|
||||
(await ReadPlanAsync(f, planId)).Phase.Should().Be(ContractSigningPlanPhase.ChoDuyet);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GanHangMuc_KhacNhomDuyet_BiChan409_LineGiuNguyen()
|
||||
{
|
||||
using var f = new KhkkGroupFixture();
|
||||
var drafter = As(await AddUserAsync(f, "BCH lập kế hoạch"));
|
||||
// ApproverUserId cần ROW Users THẬT (FK Restrict — ghi chú fixture đầu file).
|
||||
var approver = await AddUserAsync(f, "NV duyệt Cấp 1");
|
||||
var wf = await SeedWorkflowAsync(f, "QT-KHKK-LINE-B", steps: [[[approver.Id]]]);
|
||||
var catalogN2 = await AddCatalogAsync(f, approvalGroup: 2, tenVi: "Nhân công cốp pha");
|
||||
var planId = await SeedPlanAsync(f, drafter, wf, "KHKK/2026/102",
|
||||
ContractSigningPlanPhase.DangSoanThao, approvalGroup: 4,
|
||||
lines: [new LineSeed(100m, 100m, CatalogId: null)]);
|
||||
var lineId = (await ReadPlanAsync(f, planId)).Lines.Single().Id;
|
||||
|
||||
var act = () => AssignLineAsync(f, drafter, planId, lineId, catalogN2);
|
||||
await act.Should().ThrowAsync<ConflictException>(
|
||||
"hạng mục nhóm N2 không được gán vào phiếu nhóm N4 — fail-fast tầng-gán, "
|
||||
+ "submit-guard chỉ là chốt cuối");
|
||||
|
||||
(await ReadPlanAsync(f, planId)).Lines.Single().CatalogEntryId.Should().BeNull(
|
||||
"gán fail thì line phải GIỮ NGUYÊN — không ghi dở");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GanHangMuc_EntryNgungHieuLuc_BiChan409()
|
||||
{
|
||||
using var f = new KhkkGroupFixture();
|
||||
var drafter = As(await AddUserAsync(f, "BCH lập kế hoạch"));
|
||||
var approver = await AddUserAsync(f, "NV duyệt Cấp 1");
|
||||
var wf = await SeedWorkflowAsync(f, "QT-KHKK-LINE-D", steps: [[[approver.Id]]]);
|
||||
// Entry ĐÚNG nhóm nhưng IsActive=false — nhánh guard còn lại (gate-lineeditor F-4).
|
||||
Guid inactive;
|
||||
{
|
||||
await using var db = f.NewDb(TestCurrentUser.System());
|
||||
var entry = new ContractCatalogEntry
|
||||
{
|
||||
Id = Guid.NewGuid(), Code = "K2-" + Guid.NewGuid().ToString("N")[..8],
|
||||
TenVi = "Hạng mục đã ngừng", GroupCode = "B1", ApprovalGroup = 4,
|
||||
SignerRole = "TP.PRO", IsActive = false, SortOrder = 1,
|
||||
};
|
||||
db.ContractCatalogEntries.Add(entry);
|
||||
await db.SaveChangesAsync(CancellationToken.None);
|
||||
inactive = entry.Id;
|
||||
}
|
||||
var planId = await SeedPlanAsync(f, drafter, wf, "KHKK/2026/104",
|
||||
ContractSigningPlanPhase.DangSoanThao, approvalGroup: 4,
|
||||
lines: [new LineSeed(100m, 100m, CatalogId: null)]);
|
||||
var lineId = (await ReadPlanAsync(f, planId)).Lines.Single().Id;
|
||||
|
||||
var act = () => AssignLineAsync(f, drafter, planId, lineId, inactive);
|
||||
await act.Should().ThrowAsync<ConflictException>(
|
||||
"entry IsActive=false phải bị chặn dù ĐÚNG nhóm — nhánh guard độc lập với nhánh khác-nhóm");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GanHangMuc_GanDe_DoiCaIdLanTenSnapshot()
|
||||
{
|
||||
using var f = new KhkkGroupFixture();
|
||||
var drafter = As(await AddUserAsync(f, "BCH lập kế hoạch"));
|
||||
var approver = await AddUserAsync(f, "NV duyệt Cấp 1");
|
||||
var wf = await SeedWorkflowAsync(f, "QT-KHKK-LINE-E", steps: [[[approver.Id]]]);
|
||||
var catA = await AddCatalogAsync(f, approvalGroup: 4, tenVi: "Xi măng");
|
||||
var catB = await AddCatalogAsync(f, approvalGroup: 4, tenVi: "Thép hình");
|
||||
var planId = await SeedPlanAsync(f, drafter, wf, "KHKK/2026/105",
|
||||
ContractSigningPlanPhase.DangSoanThao, approvalGroup: 4,
|
||||
lines: [new LineSeed(100m, 100m, CatalogId: null)]);
|
||||
var lineId = (await ReadPlanAsync(f, planId)).Lines.Single().Id;
|
||||
|
||||
await AssignLineAsync(f, drafter, planId, lineId, catA);
|
||||
// Gán ĐÈ là đường hợp lệ (người soạn chọn nhầm rồi chọn lại) — cả Id lẫn snapshot đổi theo.
|
||||
await AssignLineAsync(f, drafter, planId, lineId, catB);
|
||||
|
||||
var line = (await ReadPlanAsync(f, planId)).Lines.Single();
|
||||
line.CatalogEntryId.Should().Be(catB);
|
||||
line.TenHangMuc.Should().Be("Thép hình", "snapshot phải đổi theo lần gán CUỐI");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GanHangMuc_PhieuDaTrinh_BiChanPhase()
|
||||
{
|
||||
using var f = new KhkkGroupFixture();
|
||||
var drafter = As(await AddUserAsync(f, "BCH lập kế hoạch"));
|
||||
// ApproverUserId cần ROW Users THẬT (FK Restrict — ghi chú fixture đầu file).
|
||||
var approver = await AddUserAsync(f, "NV duyệt Cấp 1");
|
||||
var wf = await SeedWorkflowAsync(f, "QT-KHKK-LINE-C", steps: [[[approver.Id]]]);
|
||||
var catalog = await AddCatalogAsync(f, approvalGroup: 4, tenVi: "Thép hình");
|
||||
var planId = await SeedPlanAsync(f, drafter, wf, "KHKK/2026/103",
|
||||
ContractSigningPlanPhase.ChoDuyet, approvalGroup: 4,
|
||||
lines: [new LineSeed(100m, 100m, CatalogId: null)],
|
||||
stepIndex: 0, levelOrder: 1);
|
||||
var lineId = (await ReadPlanAsync(f, planId)).Lines.Single().Id;
|
||||
|
||||
var act = () => AssignLineAsync(f, drafter, planId, lineId, catalog);
|
||||
await act.Should().ThrowAsync<ConflictException>(
|
||||
"phiếu đã trình thì dòng bất biến — chỉ Nháp/Trả lại sửa được (EnsureDraftEditable)");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user