[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

- 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:
pqhuy1987
2026-08-01 17:08:47 +07:00
parent 7d0867011c
commit 0bfadc2011
6 changed files with 392 additions and 11 deletions

View File

@ -167,6 +167,31 @@ export function KhkkDetailPage() {
onError: (e) => toast.error(getErrorMessage(e)),
})
// [S168 K8-blocker — Lines-editor] Gán hạng mục SP-002 cho dòng: options lọc ĐÚNG nhóm
// duyệt của phiếu (BE fail-fast tầng-gán + submit-guard chốt cuối). Chỉ tải khi phiếu
// còn sửa được (Nháp/Trả lại) — tránh 1 request thừa trên phiếu đã trình/duyệt.
const planDraftEditable =
plan.data?.phase === KhkkPhase.DangSoanThao || plan.data?.phase === KhkkPhase.TraLai
const catalogOptions = useQuery({
queryKey: ['contract-catalog', plan.data?.approvalGroup],
queryFn: async () =>
(
await api.get<{ id: string; code: string; tenVi: string }[]>('/catalogs/contract-catalog', {
params: { approvalGroup: plan.data!.approvalGroup },
})
).data,
enabled: planDraftEditable && plan.data?.approvalGroup != null,
})
const assignLine = useMutation({
mutationFn: async (p: { lineId: string; catalogEntryId: string }) =>
api.put(`/contract-signing-plans/${id}/lines/${p.lineId}`, { catalogEntryId: p.catalogEntryId }),
onSuccess: () => {
toast.success('Đã gán hạng mục')
invalidate()
},
onError: (e) => toast.error(getErrorMessage(e)),
})
const upsertItem = useMutation({
mutationFn: async (body: UpsertKhkkDossierItemInput) => {
if (!body.name.trim()) throw new Error('Vui lòng nhập tên căn cứ')
@ -503,9 +528,9 @@ export function KhkkDetailPage() {
<thead className="border-b border-slate-200 bg-slate-50/70">
<tr>
<th className="label-eyebrow px-3 py-2 text-left">Nhà cung cấp</th>
{/* [K2 S164] Hạng mục SP-002 của dòng — HIỂN THỊ-ONLY lượt này.
Đường GÁN (Lines-editor) = **K4b**; K2 chỉ đẻ cột + cho thấy dòng
nào còn trống (dòng trống KHÔNG trình duyệt được submit-guard BE). */}
{/* [K2 S164 → S168] Hạng mục SP-002 của dòng — đường GÁN (Lines-editor) land
@S168 (mảnh hẹn-K4b bị rơi, dry-run K8 bắt): Nháp/Trả lại gán tại bảng,
dòng trống KHÔNG trình duyệt được (submit-guard BE). */}
<th className="label-eyebrow px-3 py-2 text-left">Hạng mục</th>
<th className="label-eyebrow px-3 py-2 text-right">Giá tham chiếu (phiếu NCC)</th>
<th className="label-eyebrow px-3 py-2 text-right">Giá đ xuất</th>
@ -532,7 +557,55 @@ export function KhkkDetailPage() {
)}
</td>
<td className="px-3 py-2 text-xs text-slate-700">
{l.tenHangMuc ? l.tenHangMuc : <span className="text-slate-300"></span>}
{/* Dòng đã bắc cầu HĐ thì khoá (đổi hạng mục sau khi có HĐ = đổi lịch sử). */}
{isDraft && !l.contractId ? (
<>
<select
value={l.catalogEntryId ?? ''}
onChange={(e) =>
e.target.value &&
assignLine.mutate({ lineId: l.id, catalogEntryId: e.target.value })
}
disabled={assignLine.isPending}
aria-label={`Gán hạng mục SP-002 cho dòng ${l.supplierName ?? l.id}`}
className="h-8 w-full min-w-[12rem] rounded-md border border-slate-200 bg-white px-2 text-xs text-slate-700 focus-visible:border-brand-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500/30"
>
<option value=""> Chọn hạng mục </option>
{/* [gate-lineeditor F-2] Hạng mục ĐÃ LƯU mà không có trong options
(đang tải / query lỗi / entry hết hiệu lực) ⇒ chèn option đại diện
từ dữ liệu đã lưu — không thì select rơi về placeholder và dòng
đã gán TRÔNG như chưa gán (vắng-mặt trông giống ổn). */}
{l.catalogEntryId &&
!(catalogOptions.data ?? []).some((c) => c.id === l.catalogEntryId) && (
<option value={l.catalogEntryId}>
{l.tenHangMuc ?? '(hạng mục đã gán)'}
</option>
)}
{(catalogOptions.data ?? []).map((c) => (
<option key={c.id} value={c.id}>
{c.code} {c.tenVi}
</option>
))}
</select>
{catalogOptions.isError && (
<div className="mt-1 text-[11px] text-red-600">
Không tải đưc danh mục hạng mục {' '}
<button type="button" className="underline" onClick={() => catalogOptions.refetch()}>
thử lại
</button>
</div>
)}
{catalogOptions.isSuccess && (catalogOptions.data?.length ?? 0) === 0 && (
<div className="mt-1 text-[11px] text-amber-700">
Nhóm duyệt N{k.approvalGroup} chưa hạng mục đang hiệu lực liên hệ quản trị danh mục.
</div>
)}
</>
) : l.tenHangMuc ? (
l.tenHangMuc
) : (
<span className="text-slate-300"></span>
)}
</td>
<td className="px-3 py-2 text-right tabular-nums text-slate-600">{formatVnd(l.peReferenceAmount)}</td>
<td className="px-3 py-2 text-right tabular-nums text-brand-800">{formatVnd(l.proposedAmount)}</td>
@ -545,7 +618,7 @@ export function KhkkDetailPage() {
)}
<div className="mt-3 text-xs text-muted-foreground">
Giá tham chiếu SNAPSHOT tổng báo giá đưc chọn của NCC trên phiếu Duyệt NCC tại thời điểm lập kế hoạch.
{' '}Cột Hạng mục hiện chưa gán đưc từ màn này (ô gán thuộc bước Lines-editor).
{' '}Cột Hạng mục gán ngay tại bảng khi phiếu Nháp/Trả lại mọi dòng phải gán xong mới trình duyệt đưc.
</div>
</SectionCard>