[CLAUDE] PurchaseEvaluation: ràng trường bắt buộc — form tạo hiện thiếu gì + checklist xanh/đỏ gửi duyệt (anh Kiệt FDC UAT R3)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m50s

Anh chốt "không chọn quy trình thì không cho tạo phiếu, ràng mấy cái rủi ro lại hết":
- Form TẠO: nút "Tạo phiếu" disabled khi thiếu (Quy trình duyệt / Hạng mục / Dự án) + hint
  ĐỎ liệt kê rõ còn thiếu gì (✓ xanh khi đủ). Defense-in-depth với BE validator NotEmpty (S83).
- Detail (D4): checklist xanh ✓ "đã khai" / đỏ ✗ "chưa khai" cho 4 trường bắt buộc gửi duyệt
  (NCC winner · giá>0 · ngân sách · bảng so sánh) hiện phía trên nút "Lưu & Gửi Duyệt"
  (mirror missingForApproval predicate, khớp annotation anh Kiệt "xanh=đã khai, đỏ=chưa khai").

FE-only 2 component × 2 app SHA-identical. Both build PASS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-23 14:10:38 +07:00
parent a5e936fc0a
commit e3e40ae346
4 changed files with 110 additions and 28 deletions

View File

@ -139,7 +139,15 @@ export function PeWorkspaceCreateView({
onError: e => toast.error(getErrorMessage(e)),
})
const canSubmit = !!form.tenGoiThau && !!form.projectId && !!form.workItemId && !!form.approvalWorkflowId && !create.isPending
// [D4/R3 anh Kiệt — "ràng mấy cái rủi ro lại hết"] Trường BẮT BUỘC để tạo phiếu — thiếu cái
// nào thì liệt kê cho user thấy (nút "Tạo phiếu" disabled + hint đỏ). KHÔNG chọn Quy trình
// duyệt → KHÔNG tạo được phiếu (anh chốt; defense-in-depth với BE validator NotEmpty S83).
const missingCreate = [
!form.approvalWorkflowId && 'Quy trình duyệt',
!form.workItemId && 'Hạng mục công việc (tên gói thầu)',
!form.projectId && 'Dự án',
].filter(Boolean) as string[]
const canSubmit = missingCreate.length === 0 && !!form.tenGoiThau && !create.isPending
return (
<div className="rounded-lg border border-slate-200 bg-white shadow-sm">
@ -298,17 +306,26 @@ export function PeWorkspaceCreateView({
ở Detail tabs sau khi tạo phiếu. */}
</div>
{/* Action bar */}
<div className="flex items-center justify-end gap-2 border-t border-slate-200 bg-slate-50 px-5 py-3">
{onCancel && (
<Button variant="ghost" onClick={onCancel} className="text-xs">Hủy</Button>
)}
<Button
onClick={() => create.mutate()}
disabled={!canSubmit}
>
{create.isPending ? 'Đang tạo…' : 'Tạo phiếu'}
</Button>
{/* Action bar — [D4/R3 anh Kiệt] hint trái hiện RÕ còn thiếu gì (ràng rủi ro), nút phải. */}
<div className="flex items-center justify-between gap-3 border-t border-slate-200 bg-slate-50 px-5 py-3">
<div className="min-w-0 text-[11px]">
{missingCreate.length > 0 ? (
<span className="text-red-600"> Chưa đ đ tạo phiếu còn thiếu: <strong>{missingCreate.join(' · ')}</strong></span>
) : (
<span className="font-medium text-emerald-600"> Đ thông tin bắt buộc thể tạo phiếu</span>
)}
</div>
<div className="flex shrink-0 items-center gap-2">
{onCancel && (
<Button variant="ghost" onClick={onCancel} className="text-xs">Hủy</Button>
)}
<Button
onClick={() => create.mutate()}
disabled={!canSubmit}
>
{create.isPending ? 'Đang tạo…' : 'Tạo phiếu'}
</Button>
</div>
</div>
</div>
)