[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:
@ -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 có 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 là 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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user