[CLAUDE] PurchaseEvaluation: go field "Dieu khoan thanh toan" khoi TAT CA form phieu (UAT vong 5 - anh chot "bo not ra luon tat ca cac form")
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled

- PeWorkspaceCreateView: bo Select preset + Textarea custom + PAYMENT_PRESETS/
  PAYMENT_CUSTOM/paymentMode drop, payload paymentTerms: null.
- PeHeaderForm + PeDetailTabs inline-edit: bo Textarea; state paymentTerms GIU
  (load tu phieu cu + save giu nguyen -> data cu KHONG mat, van hien read-only).
- GIU cot "Dieu khoan TT" per-NCC trong bang so sanh (data tung NCC, khac field).
- SHA256 mirror x2 app IDENTICAL, build tsc+vite x2 PASS.
This commit is contained in:
pqhuy1987
2026-06-11 18:33:33 +07:00
parent 69997da74f
commit 80b64dd514
6 changed files with 20 additions and 142 deletions

View File

@ -13,7 +13,6 @@ import { Input } from '@/components/ui/Input'
import { Label } from '@/components/ui/Label' import { Label } from '@/components/ui/Label'
import { SearchableSelect } from '@/components/ui/SearchableSelect' import { SearchableSelect } from '@/components/ui/SearchableSelect'
import { Select } from '@/components/ui/Select' import { Select } from '@/components/ui/Select'
import { Textarea } from '@/components/ui/Textarea'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { getErrorMessage } from '@/lib/apiError' import { getErrorMessage } from '@/lib/apiError'
import { cn } from '@/lib/cn' import { cn } from '@/lib/cn'
@ -722,16 +721,9 @@ function InfoTab({ ev, readOnly, autoEdit }: { ev: PeDetailBundle; readOnly: boo
placeholder="Phương án A: ..." placeholder="Phương án A: ..."
/> />
</div> </div>
<div className="md:col-span-2"> {/* S59 vòng 5: field "Điều khoản thanh toán" GỠ khỏi inline-edit (anh chốt
<Label className="text-[11px]">Điều khoản thanh toán</Label> "bỏ nốt ra luôn tất cả các form"). State paymentTerms giữ — save giữ nguyên
{/* S59 UAT "nhập tay chỉ được 1 dòng?" → Textarea đa dòng (render đã pre-wrap). */} data cũ; phiếu đã nhập vẫn hiển thị read-only ở header info. */}
<Textarea
rows={3}
value={paymentTerms}
onChange={e => setPaymentTerms(e.target.value)}
placeholder={'Nhập điều khoản — Enter để xuống dòng'}
/>
</div>
</div> </div>
<div className="flex items-center justify-end gap-2"> <div className="flex items-center justify-end gap-2">
<Button <Button

View File

@ -307,15 +307,8 @@ export function PeHeaderForm({
<Textarea rows={3} value={form.moTa} onChange={e => setForm({ ...form, moTa: e.target.value })} /> <Textarea rows={3} value={form.moTa} onChange={e => setForm({ ...form, moTa: e.target.value })} />
</div> </div>
<div> {/* S59 vòng 5: field "Điều khoản thanh toán" GỠ khỏi form (anh chốt). State
<Label>Điều khoản thanh toán (JSON hoặc text)</Label> paymentTerms giữ — load từ phiếu cũ + save giữ nguyên, data cũ KHÔNG mất. */}
<Textarea
rows={3}
value={form.paymentTerms}
onChange={e => setForm({ ...form, paymentTerms: e.target.value })}
placeholder='{"tamUng":"10%","thanhToanTam":"100% W.done","quyetToan":"Final Account","baoHanh":"5%"}'
/>
</div>
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
{onCancel && ( {onCancel && (

View File

@ -15,7 +15,6 @@ import { Input } from '@/components/ui/Input'
import { Label } from '@/components/ui/Label' import { Label } from '@/components/ui/Label'
import { SearchableSelect } from '@/components/ui/SearchableSelect' import { SearchableSelect } from '@/components/ui/SearchableSelect'
import { Select } from '@/components/ui/Select' import { Select } from '@/components/ui/Select'
import { Textarea } from '@/components/ui/Textarea'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { getErrorMessage } from '@/lib/apiError' import { getErrorMessage } from '@/lib/apiError'
import { PurchaseEvaluationTypeLabel } from '@/types/purchaseEvaluation' import { PurchaseEvaluationTypeLabel } from '@/types/purchaseEvaluation'
@ -36,21 +35,10 @@ type WorkItemOption = {
isActive?: boolean isActive?: boolean
} }
// Preset điều khoản thanh toán phổ biến — user chọn 1 trong list, hoặc "Khác" // S59 UAT vòng 5 (anh chốt "điều khoản thanh toán bỏ nốt ra luôn tất cả các form"):
// để nhập tay. Save as plain text (không JSON như cũ — code-style không phù // field Điều khoản TT phiếu-level GỠ khỏi mọi form (Create/HeaderForm/inline-edit).
// hợp UI cho end-user). User 2026-05-07 chỉnh. // PAYMENT_PRESETS + PAYMENT_CUSTOM drop. Cột "Điều khoản TT" per-NCC trong bảng
const PAYMENT_PRESETS = [ // so sánh GIỮ (data của từng NCC, khác field). Phiếu cũ đã nhập → display read-only.
'100% sau khi nghiệm thu',
'Tạm ứng 30% / Thanh toán 70% sau nghiệm thu',
'Tạm ứng 50% / Thanh toán 50% sau nghiệm thu',
'TGN-30 ngày (Thanh toán giao nhận 30 ngày)',
'TGN-45 ngày',
'TGN-60 ngày',
'Tiến độ theo từng đợt',
'Bảo hành 5% trong 12 tháng',
] as const
const PAYMENT_CUSTOM = '__custom__'
export function PeWorkspaceCreateView({ export function PeWorkspaceCreateView({
defaultType, defaultType,
@ -80,8 +68,6 @@ export function PeWorkspaceCreateView({
approvalWorkflowId: '', approvalWorkflowId: '',
}) })
// Payment terms: select preset OR "Khác" → text input // Payment terms: select preset OR "Khác" → text input
const [paymentMode, setPaymentMode] = useState<string>('') // '' / preset / __custom__
const isPaymentCustom = paymentMode === PAYMENT_CUSTOM
const projects = useQuery({ const projects = useQuery({
queryKey: ['all-projects'], queryKey: ['all-projects'],
@ -142,7 +128,7 @@ export function PeWorkspaceCreateView({
workItemId: form.workItemId || null, workItemId: form.workItemId || null,
diaDiem: form.diaDiem || null, diaDiem: form.diaDiem || null,
moTa: form.moTa || null, moTa: form.moTa || null,
paymentTerms: form.paymentTerms || null, paymentTerms: null, // S59 vòng 5: field gỡ khỏi form
approvalWorkflowId: form.approvalWorkflowId || null, approvalWorkflowId: form.approvalWorkflowId || null,
...budgetPayload, ...budgetPayload,
}) })
@ -261,38 +247,6 @@ export function PeWorkspaceCreateView({
placeholder="Phương án A: ..." placeholder="Phương án A: ..."
/> />
</div> </div>
<div className="md:col-span-2">
<Label className="text-[11px]">Điều khoản thanh toán</Label>
<Select
value={paymentMode}
onChange={e => {
const v = e.target.value
setPaymentMode(v)
if (v === '' || v === PAYMENT_CUSTOM) {
setForm(f => ({ ...f, paymentTerms: '' }))
} else {
setForm(f => ({ ...f, paymentTerms: v }))
}
}}
>
<option value=""> Chọn điều khoản </option>
{PAYMENT_PRESETS.map(p => (
<option key={p} value={p}>{p}</option>
))}
<option value={PAYMENT_CUSTOM}>Khác (nhập tay)</option>
</Select>
{isPaymentCustom && (
/* S59 UAT "nhập tay chỉ được 1 dòng?" → Textarea đa dòng (render
detail đã whitespace-pre-wrap sẵn nên xuống dòng hiển thị đúng). */
<Textarea
rows={3}
value={form.paymentTerms}
onChange={e => setForm({ ...form, paymentTerms: e.target.value })}
placeholder={'Nhập điều khoản tùy chỉnh — Enter để xuống dòng, vd:\n1. Tạm ứng: 10%\n2. Thanh toán hàng tháng: 80% - 45 ngày'}
className="mt-2"
/>
)}
</div>
</div> </div>
</Section> </Section>

View File

@ -13,7 +13,6 @@ import { Input } from '@/components/ui/Input'
import { Label } from '@/components/ui/Label' import { Label } from '@/components/ui/Label'
import { SearchableSelect } from '@/components/ui/SearchableSelect' import { SearchableSelect } from '@/components/ui/SearchableSelect'
import { Select } from '@/components/ui/Select' import { Select } from '@/components/ui/Select'
import { Textarea } from '@/components/ui/Textarea'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { getErrorMessage } from '@/lib/apiError' import { getErrorMessage } from '@/lib/apiError'
import { cn } from '@/lib/cn' import { cn } from '@/lib/cn'
@ -722,16 +721,9 @@ function InfoTab({ ev, readOnly, autoEdit }: { ev: PeDetailBundle; readOnly: boo
placeholder="Phương án A: ..." placeholder="Phương án A: ..."
/> />
</div> </div>
<div className="md:col-span-2"> {/* S59 vòng 5: field "Điều khoản thanh toán" GỠ khỏi inline-edit (anh chốt
<Label className="text-[11px]">Điều khoản thanh toán</Label> "bỏ nốt ra luôn tất cả các form"). State paymentTerms giữ — save giữ nguyên
{/* S59 UAT "nhập tay chỉ được 1 dòng?" → Textarea đa dòng (render đã pre-wrap). */} data cũ; phiếu đã nhập vẫn hiển thị read-only ở header info. */}
<Textarea
rows={3}
value={paymentTerms}
onChange={e => setPaymentTerms(e.target.value)}
placeholder={'Nhập điều khoản — Enter để xuống dòng'}
/>
</div>
</div> </div>
<div className="flex items-center justify-end gap-2"> <div className="flex items-center justify-end gap-2">
<Button <Button

View File

@ -307,15 +307,8 @@ export function PeHeaderForm({
<Textarea rows={3} value={form.moTa} onChange={e => setForm({ ...form, moTa: e.target.value })} /> <Textarea rows={3} value={form.moTa} onChange={e => setForm({ ...form, moTa: e.target.value })} />
</div> </div>
<div> {/* S59 vòng 5: field "Điều khoản thanh toán" GỠ khỏi form (anh chốt). State
<Label>Điều khoản thanh toán (JSON hoặc text)</Label> paymentTerms giữ — load từ phiếu cũ + save giữ nguyên, data cũ KHÔNG mất. */}
<Textarea
rows={3}
value={form.paymentTerms}
onChange={e => setForm({ ...form, paymentTerms: e.target.value })}
placeholder='{"tamUng":"10%","thanhToanTam":"100% W.done","quyetToan":"Final Account","baoHanh":"5%"}'
/>
</div>
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
{onCancel && ( {onCancel && (

View File

@ -15,7 +15,6 @@ import { Input } from '@/components/ui/Input'
import { Label } from '@/components/ui/Label' import { Label } from '@/components/ui/Label'
import { SearchableSelect } from '@/components/ui/SearchableSelect' import { SearchableSelect } from '@/components/ui/SearchableSelect'
import { Select } from '@/components/ui/Select' import { Select } from '@/components/ui/Select'
import { Textarea } from '@/components/ui/Textarea'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { getErrorMessage } from '@/lib/apiError' import { getErrorMessage } from '@/lib/apiError'
import { PurchaseEvaluationTypeLabel } from '@/types/purchaseEvaluation' import { PurchaseEvaluationTypeLabel } from '@/types/purchaseEvaluation'
@ -36,21 +35,10 @@ type WorkItemOption = {
isActive?: boolean isActive?: boolean
} }
// Preset điều khoản thanh toán phổ biến — user chọn 1 trong list, hoặc "Khác" // S59 UAT vòng 5 (anh chốt "điều khoản thanh toán bỏ nốt ra luôn tất cả các form"):
// để nhập tay. Save as plain text (không JSON như cũ — code-style không phù // field Điều khoản TT phiếu-level GỠ khỏi mọi form (Create/HeaderForm/inline-edit).
// hợp UI cho end-user). User 2026-05-07 chỉnh. // PAYMENT_PRESETS + PAYMENT_CUSTOM drop. Cột "Điều khoản TT" per-NCC trong bảng
const PAYMENT_PRESETS = [ // so sánh GIỮ (data của từng NCC, khác field). Phiếu cũ đã nhập → display read-only.
'100% sau khi nghiệm thu',
'Tạm ứng 30% / Thanh toán 70% sau nghiệm thu',
'Tạm ứng 50% / Thanh toán 50% sau nghiệm thu',
'TGN-30 ngày (Thanh toán giao nhận 30 ngày)',
'TGN-45 ngày',
'TGN-60 ngày',
'Tiến độ theo từng đợt',
'Bảo hành 5% trong 12 tháng',
] as const
const PAYMENT_CUSTOM = '__custom__'
export function PeWorkspaceCreateView({ export function PeWorkspaceCreateView({
defaultType, defaultType,
@ -80,8 +68,6 @@ export function PeWorkspaceCreateView({
approvalWorkflowId: '', approvalWorkflowId: '',
}) })
// Payment terms: select preset OR "Khác" → text input // Payment terms: select preset OR "Khác" → text input
const [paymentMode, setPaymentMode] = useState<string>('') // '' / preset / __custom__
const isPaymentCustom = paymentMode === PAYMENT_CUSTOM
const projects = useQuery({ const projects = useQuery({
queryKey: ['all-projects'], queryKey: ['all-projects'],
@ -142,7 +128,7 @@ export function PeWorkspaceCreateView({
workItemId: form.workItemId || null, workItemId: form.workItemId || null,
diaDiem: form.diaDiem || null, diaDiem: form.diaDiem || null,
moTa: form.moTa || null, moTa: form.moTa || null,
paymentTerms: form.paymentTerms || null, paymentTerms: null, // S59 vòng 5: field gỡ khỏi form
approvalWorkflowId: form.approvalWorkflowId || null, approvalWorkflowId: form.approvalWorkflowId || null,
...budgetPayload, ...budgetPayload,
}) })
@ -261,38 +247,6 @@ export function PeWorkspaceCreateView({
placeholder="Phương án A: ..." placeholder="Phương án A: ..."
/> />
</div> </div>
<div className="md:col-span-2">
<Label className="text-[11px]">Điều khoản thanh toán</Label>
<Select
value={paymentMode}
onChange={e => {
const v = e.target.value
setPaymentMode(v)
if (v === '' || v === PAYMENT_CUSTOM) {
setForm(f => ({ ...f, paymentTerms: '' }))
} else {
setForm(f => ({ ...f, paymentTerms: v }))
}
}}
>
<option value=""> Chọn điều khoản </option>
{PAYMENT_PRESETS.map(p => (
<option key={p} value={p}>{p}</option>
))}
<option value={PAYMENT_CUSTOM}>Khác (nhập tay)</option>
</Select>
{isPaymentCustom && (
/* S59 UAT "nhập tay chỉ được 1 dòng?" → Textarea đa dòng (render
detail đã whitespace-pre-wrap sẵn nên xuống dòng hiển thị đúng). */
<Textarea
rows={3}
value={form.paymentTerms}
onChange={e => setForm({ ...form, paymentTerms: e.target.value })}
placeholder={'Nhập điều khoản tùy chỉnh — Enter để xuống dòng, vd:\n1. Tạm ứng: 10%\n2. Thanh toán hàng tháng: 80% - 45 ngày'}
className="mt-2"
/>
)}
</div>
</div> </div>
</Section> </Section>