[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:
@ -102,6 +102,18 @@ public class ContractSigningPlansController(
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// [S168 K8-blocker] Gán hạng mục SP-002 cho DÒNG — mảnh Lines-editor (comment auto-gen hẹn
|
||||
// K4b, chưa từng land; submit-guard vá-5 chặn null ⇒ thiếu route này phiếu mới kẹt Nháp).
|
||||
// Ghi vào phiếu ⇒ policy Update (khuôn dossier-items dưới — "tạo" ở module này = tạo PHIẾU).
|
||||
[HttpPut("{id:guid}/lines/{lineId:guid}")]
|
||||
[Authorize(Policy = "KeHoachKyKet.Update")]
|
||||
public async Task<IActionResult> UpdateLine(
|
||||
Guid id, Guid lineId, [FromBody] UpdateSigningPlanLineBody body, CancellationToken ct)
|
||||
{
|
||||
await mediator.Send(new UpdateContractSigningPlanLineCommand(id, lineId, body.CatalogEntryId), ct);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize(Policy = "KeHoachKyKet.Delete")]
|
||||
public async Task<IActionResult> Delete(
|
||||
@ -257,6 +269,9 @@ public class ContractSigningPlansController(
|
||||
string? Comment = null,
|
||||
bool? ApplyLevelFinalize = null);
|
||||
|
||||
// [S168 K8-blocker] Body gán hạng mục cho dòng — PlanId/LineId lấy từ route (1 nguồn sự thật).
|
||||
public record UpdateSigningPlanLineBody(Guid CatalogEntryId);
|
||||
|
||||
// [K7 S167] Cầu → HĐ. `PlanId` lấy từ route (không nhận trong body — 1 nguồn sự thật).
|
||||
// `LineIds` nullable ở BODY để client gửi thiếu ra 400 của validator ("chọn ít nhất 1 dòng")
|
||||
// thay vì 500 NullReference ở tầng dưới.
|
||||
|
||||
@ -574,6 +574,84 @@ public class UpdateContractSigningPlanDraftCommandHandler(
|
||||
}
|
||||
}
|
||||
|
||||
// [S168 K8-blocker] Gán hạng mục SP-002 cho DÒNG kế hoạch — mảnh "Lines-editor" mà comment
|
||||
// auto-gen (`:481-484`) HẸN nhưng chưa từng land (build-order K4b đổi việc sang `?group=`):
|
||||
// submit-guard vá-5 đã CHẶN line CatalogEntryId=null ⇒ thiếu command này thì MỌI phiếu tạo
|
||||
// sau K2 kẹt Nháp vĩnh viễn. Dry-run K8 bắt lỗ này trước giờ chạy.
|
||||
public record UpdateContractSigningPlanLineCommand(
|
||||
Guid PlanId,
|
||||
Guid LineId,
|
||||
Guid CatalogEntryId) : IRequest;
|
||||
|
||||
public class UpdateContractSigningPlanLineCommandValidator
|
||||
: AbstractValidator<UpdateContractSigningPlanLineCommand>
|
||||
{
|
||||
public UpdateContractSigningPlanLineCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.PlanId).NotEmpty();
|
||||
RuleFor(x => x.LineId).NotEmpty();
|
||||
RuleFor(x => x.CatalogEntryId).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateContractSigningPlanLineCommandHandler(
|
||||
IApplicationDbContext db,
|
||||
ICurrentUser currentUser) : IRequestHandler<UpdateContractSigningPlanLineCommand>
|
||||
{
|
||||
public async Task Handle(UpdateContractSigningPlanLineCommand request, CancellationToken ct)
|
||||
{
|
||||
var plan = await db.ContractSigningPlans
|
||||
.Include(x => x.Lines)
|
||||
.FirstOrDefaultAsync(x => x.Id == request.PlanId, ct)
|
||||
?? throw new NotFoundException("ContractSigningPlan", request.PlanId);
|
||||
|
||||
ContractSigningPlanScope.EnsureDraftEditable(plan, currentUser);
|
||||
|
||||
var line = plan.Lines.FirstOrDefault(l => l.Id == request.LineId)
|
||||
?? throw new NotFoundException("ContractSigningPlanLine", request.LineId);
|
||||
|
||||
// [gate-lineeditor F-6] Rào BE thật (FE khoá chỉ là hiển thị): dòng đã bắc cầu HĐ
|
||||
// thì hạng mục bất biến — đổi sau khi có HĐ = đổi lịch sử pháp lý của HĐ đã sinh.
|
||||
if (line.ContractId != null)
|
||||
throw new ConflictException("Dòng đã được đưa vào hợp đồng — không đổi hạng mục được nữa.");
|
||||
|
||||
// Fail-fast tầng-gán (submit-guard vẫn là chốt CUỐI chống data cũ/lách API):
|
||||
// hạng mục phải CÒN SỐNG + đang hoạt động + ĐÚNG nhóm duyệt của phiếu.
|
||||
var entry = await db.ContractCatalogEntries.AsNoTracking()
|
||||
.FirstOrDefaultAsync(c => c.Id == request.CatalogEntryId, ct)
|
||||
?? throw new NotFoundException("ContractCatalogEntry", request.CatalogEntryId);
|
||||
if (!entry.IsActive || entry.ApprovalGroup != plan.ApprovalGroup)
|
||||
throw new ConflictException(
|
||||
$"Hạng mục không hợp lệ — phải đang hoạt động và thuộc nhóm duyệt N{plan.ApprovalGroup} của phiếu.");
|
||||
|
||||
line.CatalogEntryId = entry.Id;
|
||||
// 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;
|
||||
|
||||
// [gate-lineeditor F-1] Phiếu liên danh N dòng ⇒ nhật ký PHẢI trỏ được DÒNG NÀO:
|
||||
// EntityId = line.Id (quy ước mọi changelog thực-thể-con của file) + tên NCC vào Summary.
|
||||
var supplierName = await db.Suppliers.AsNoTracking()
|
||||
.Where(x => x.Id == line.SupplierId)
|
||||
.Select(x => x.Name)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
db.ContractSigningPlanChangelogs.Add(new ContractSigningPlanChangelog
|
||||
{
|
||||
ContractSigningPlanId = plan.Id,
|
||||
EntityType = ContractSigningPlanEntityType.Line,
|
||||
EntityId = line.Id,
|
||||
Action = ChangelogAction.Update,
|
||||
PhaseAtChange = plan.Phase,
|
||||
UserId = currentUser.UserId,
|
||||
UserName = currentUser.FullName ?? currentUser.Email,
|
||||
Summary = $"Gán hạng mục '{entry.Code} — {entry.TenVi}' cho dòng NCC {supplierName ?? "(không rõ tên)"}",
|
||||
});
|
||||
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================ Region 3: GET detail ==================================
|
||||
|
||||
public record GetContractSigningPlanQuery(Guid Id) : IRequest<ContractSigningPlanDetailDto>;
|
||||
|
||||
Reference in New Issue
Block a user