[CLAUDE] PurchaseEvaluation: ngan sach goi thau theo Excel anh Kiet - bang tong hop 2 block + nhap theo role PRO/CCM + xoa module Budget cu (Mig 50)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m31s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m31s
- Mig 50 ReplaceBudgetModuleWithPeWorkItemBudgets: bang moi PeWorkItemBudgets (1 record/cap Du an x Hang muc, UNIQUE filtered [IsDeleted]=0) + drop 5 bang Budget cu + PE/Contracts drop BudgetId + backfill BudgetManualAmount->BudgetPeriodAmount TRUOC DropColumn (phieu UAT giu so) + DELETE menu/permission Bg_* IN-list children-first
- BE: PUT {id}/budget/pro (role Procurement) + {id}/budget/ccm (role CostControl, Adjustment cho phep AM) fail-closed Forbidden-truoc-side-effect + EnsureTrackedAsync race-safe (catch unique -> re-fetch winner, loi khac rethrow) + auto-create record khi tao phieu + budgetSummary DTO (luy ke trinh-truoc/chon-thau-truoc/de-xuat-ky-nay + full fallback du-tru-PRO + canEdit flags) + submit-guard (3) doi predicate BudgetPeriodAmount -> "chua nhap Ngan sach ky nay" + PATCH budget-adjust absolute-set 2 field moi + Contract GIU BudgetManual* (HD nhap tay khong doi) + ke thua HD map BudgetPeriodAmount
- FE x2 app SHA256 identical: bang "TONG HOP NGAN SACH TRINH KY" block A (full dam + ban hanh + V0 hieu chinh + du tru PRO + ghi chu, editable theo canEditPro/canEditCcm) + block B 9 dong cong thuc Excel (5=1+3, 6=2+4, 7=full-5, 8 tu nhap default 7, 9=4+8) + to mau vuot ngan sach #C00000 / am do / red-soft row8>row7 + "Chua chon" khi count=0 + banner phieu chua gan Hang muc + o "Ngan sach ky nay" o create/header + XOA pages/components/types budgets + routes + menuKeys + Layout staticMap 4-place
- Tests: +22 PeWorkItemBudgetTests (auto-create x3, ensure/race x2, authz matrix PRO x5 + CCM x3, budgetSummary aggregates x5, adjust x4) - 14 BudgetPolicyTests xoa theo module - 1 test via-BudgetId -> 263 PASS (45 Domain + 218 Infra, 0 fail)
- database-agent advise adopted: khong FK vat ly PE/Contracts->Budgets (DropColumn khong can DropForeignKey) + DropIndex truoc DropColumn (SQL 5074) + IN-list thay LIKE Bg_% (underscore wildcard + miss root) + khong Serializable wrap (nested-tx conflict codegen)
- Reviewer PASS-with-minor 0 blocker (verdict-first survived); 2 minor da sua truoc commit (comment adjustMut absolute-set + dead key budgetId); note: F4 approver-edit-budget UI entry tam drafter-only, BE van cho approver scope - cho UAT anh Kiet
- Scaffold-bug caught: EF tu sinh RenameColumn BudgetManualAmount->ExpectedRemainingAmount (SAI semantics) -> thay bang Add+UPDATE+Drop
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@ -18,8 +18,7 @@ import { Select } from '@/components/ui/Select'
|
||||
import { api } from '@/lib/api'
|
||||
import { getErrorMessage } from '@/lib/apiError'
|
||||
import { PurchaseEvaluationTypeLabel } from '@/types/purchaseEvaluation'
|
||||
import { BudgetPhase, type BudgetListItem } from '@/types/budget'
|
||||
import type { Paged, Project } from '@/types/master'
|
||||
import type { Project } from '@/types/master'
|
||||
|
||||
// VND format helpers (mirror PeDetailTabs.tsx — session 20)
|
||||
const parseVnd = (s: string): number => Number(s.replace(/[^\d]/g, '')) || 0
|
||||
@ -59,11 +58,9 @@ export function PeWorkspaceCreateView({
|
||||
diaDiem: '',
|
||||
moTa: '',
|
||||
paymentTerms: '',
|
||||
budgetId: '',
|
||||
// Mig 17 — manual budget fallback
|
||||
budgetManual: false,
|
||||
budgetManualName: '',
|
||||
budgetManualAmount: 0,
|
||||
// [S61 Mig 50] "Ngân sách - kỳ này" — thay budgetId/budgetManual* (module
|
||||
// Budget cũ xóa hẳn; bảng Tổng hợp ngân sách gói thầu ở PeDetailTabs).
|
||||
budgetPeriodAmount: 0,
|
||||
// Mig 23 — Pin quy trình duyệt V2 (User tự chọn lúc tạo)
|
||||
approvalWorkflowId: '',
|
||||
})
|
||||
@ -104,20 +101,9 @@ export function PeWorkspaceCreateView({
|
||||
},
|
||||
})
|
||||
|
||||
const eligibleBudgets = useQuery({
|
||||
queryKey: ['eligible-budgets', form.projectId],
|
||||
queryFn: async () => {
|
||||
const res = await api.get<Paged<BudgetListItem>>('/budgets', {
|
||||
params: { pageSize: 100, projectId: form.projectId, phase: BudgetPhase.DaDuyet },
|
||||
})
|
||||
return res.data.items
|
||||
},
|
||||
enabled: !!form.projectId,
|
||||
})
|
||||
|
||||
const budgetPayload = form.budgetManual
|
||||
? { budgetId: null, budgetManualName: form.budgetManualName || null, budgetManualAmount: form.budgetManualAmount > 0 ? form.budgetManualAmount : null }
|
||||
: { budgetId: form.budgetId || null, budgetManualName: null, budgetManualAmount: null }
|
||||
const budgetPayload = {
|
||||
budgetPeriodAmount: form.budgetPeriodAmount > 0 ? form.budgetPeriodAmount : null,
|
||||
}
|
||||
|
||||
const create = useMutation({
|
||||
mutationFn: async () => {
|
||||
@ -224,7 +210,7 @@ export function PeWorkspaceCreateView({
|
||||
const loc = p?.location ?? ''
|
||||
setForm(f => {
|
||||
const untouched = !f.diaDiem || f.diaDiem === lastAutoLoc.current
|
||||
return { ...f, projectId: id, budgetId: '', diaDiem: untouched ? loc : f.diaDiem }
|
||||
return { ...f, projectId: id, diaDiem: untouched ? loc : f.diaDiem }
|
||||
})
|
||||
lastAutoLoc.current = loc
|
||||
}}
|
||||
@ -258,55 +244,26 @@ export function PeWorkspaceCreateView({
|
||||
value={<span className="text-slate-400">— (sau khi thêm NCC tham gia + chốt winner)</span>}
|
||||
/>
|
||||
|
||||
{/* b. Ngân sách — editable inline (Mig 17 toggle pattern) */}
|
||||
{/* [S61 Mig 50] b. Ngân sách kỳ này — ô đơn thay picker Budget cũ +
|
||||
toggle nhập tay (module Budget xóa hẳn). Số phân bổ cho RIÊNG
|
||||
phiếu này (row 3 bảng "Tổng hợp ngân sách trình ký"). */}
|
||||
<div className="flex gap-3">
|
||||
<span className="w-44 shrink-0 pt-1.5 text-[12px] text-slate-500">b. Ngân sách</span>
|
||||
<div className="min-w-0 flex-1 space-y-2">
|
||||
<label className="inline-flex cursor-pointer items-center gap-1.5 text-[11px] text-slate-600">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.budgetManual}
|
||||
onChange={e => setForm({ ...form, budgetManual: e.target.checked })}
|
||||
className="h-3.5 w-3.5 rounded border-slate-300"
|
||||
<span className="w-44 shrink-0 pt-1.5 text-[12px] text-slate-500">b. Ngân sách kỳ này</span>
|
||||
<div className="min-w-0 flex-1 space-y-1">
|
||||
<div className="relative max-w-xs">
|
||||
<Input
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
value={formatVndInput(form.budgetPeriodAmount)}
|
||||
onChange={e => setForm({ ...form, budgetPeriodAmount: parseVnd(e.target.value) })}
|
||||
placeholder="0"
|
||||
className="pr-10 font-mono text-right text-sm"
|
||||
/>
|
||||
Nhập tay (không link)
|
||||
</label>
|
||||
{!form.budgetManual ? (
|
||||
<>
|
||||
<Select
|
||||
value={form.budgetId}
|
||||
disabled={!form.projectId}
|
||||
onChange={e => setForm({ ...form, budgetId: e.target.value })}
|
||||
className="text-sm"
|
||||
>
|
||||
<option value="">—</option>
|
||||
{eligibleBudgets.data?.map(b => (
|
||||
<option key={b.id} value={b.id}>
|
||||
{b.maNganSach ?? '—'} · {b.tenNganSach} · {b.tongNganSach.toLocaleString('vi-VN')} đ
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<p className="text-[11px] text-slate-500">
|
||||
{!form.projectId
|
||||
? 'Chọn dự án trước để xem ngân sách khả dụng.'
|
||||
: eligibleBudgets.data && eligibleBudgets.data.length === 0
|
||||
? 'Dự án này chưa có ngân sách đã duyệt — bật "Nhập tay" để nhập số tiền trực tiếp.'
|
||||
: 'Chỉ list ngân sách đã duyệt cùng dự án.'}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<div className="relative max-w-xs">
|
||||
<Input
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
value={formatVndInput(form.budgetManualAmount)}
|
||||
onChange={e => setForm({ ...form, budgetManualAmount: parseVnd(e.target.value) })}
|
||||
placeholder="0"
|
||||
className="pr-10 font-mono text-right text-sm"
|
||||
/>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-[12px] font-medium text-slate-500">đ</span>
|
||||
</div>
|
||||
)}
|
||||
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-[12px] font-medium text-slate-500">đ</span>
|
||||
</div>
|
||||
<p className="text-[11px] text-slate-500">
|
||||
Bắt buộc trước khi gửi duyệt. Ngân sách full gói thầu xem ở bảng “Tổng hợp ngân sách trình ký” sau khi tạo phiếu.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user