"use client"; // 구매관리 > 발주서관리 — english 양식 등록/수정 다이얼로그 // wace 원본: purchaseOrder/purchaseOrderFormPopup_english.jsp // - 헤더: 로고 110px + "R P S CO., LTD." + 영문 회사정보 (주소/Tel/Fax/E-mail/Purchasing Team Manager) // - 타이틀: "Purchase Order" (밑줄) + 부제 "We are pleased to issue Purchase Order ..." // - 좌+우 2열 5행 필드: Messrs./Shipment, Attn.to/Payment, Date/Packing, Ref.NO/Validity, (빈)/Remarks // - 그리드 8 visible: Item No. / Commodity & Description / Unit / Q'ty / Currency / Unit Price / Amount / Delivery // - TOTAL 한 행 + 하단 "Look forward to your soonest delivery..." + 서명영역 (stamp_seal.png 65x65) import React, { useEffect, useMemo, useState } from "react"; import { Dialog, DialogContent, DialogTitle, DialogDescription } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Plus, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { CommCodeSelect } from "@/components/common/CommCodeSelect"; import { SmartSelect, SmartSelectOption } from "@/components/common/SmartSelect"; import { DateInput } from "@/components/common/DateInput"; import { NumberInput } from "@/components/common/NumberInput"; import { purchaseApi, OptionItem } from "@/lib/api/purchase"; interface Props { open: boolean; onClose: () => void; onSaved?: (result: { objid: string; purchase_order_no: string }) => void; pomObjid?: string; proposalObjid?: string; } interface PartRow { rowKey: string; objid: string; part_objid: string; part_no: string; part_name: string; spec: string; order_qty: number | ""; qty: number | ""; unit: string; currency: string; partner_price: number | ""; supply_unit_price: number; delivery_request_date: string; _src?: string; } interface MasterState { objid: string; purchase_order_no: string; purchase_date: string; partner_objid: string; payment_terms: string; shipment: string; attn_to: string; packing: string; validity: string; remark: string; sales_mng_user_id: string; manager_name: string; manager_position: string; manager_phone: string; manager_email: string; sales_request_objid: string; contract_mgmt_objid: string; form_type: string; status: string; appr_status: string; } const EMPTY_MASTER: MasterState = { objid: "", purchase_order_no: "", purchase_date: "", partner_objid: "", payment_terms: "", shipment: "", attn_to: "", packing: "", validity: "", remark: "", sales_mng_user_id: "", manager_name: "", manager_position: "", manager_phone: "", manager_email: "", sales_request_objid: "", contract_mgmt_objid: "", form_type: "english", status: "create", appr_status: "", }; const UNIT_GROUP_ID = "0001399"; const CURRENCY_GROUP_ID = "0001533"; // wace currency_cd (0001534=USD default) const DEFAULT_CURRENCY = "0001534"; let _rk = 0; const nextKey = () => `r${++_rk}_${Date.now()}`; const toNum = (v: any): number => { if (v == null || v === "") return 0; const n = Number(String(v).replace(/,/g, "")); return Number.isFinite(n) ? n : 0; }; const fmt2 = (n: number) => n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); interface UserOptionExt extends OptionItem { name?: string; position?: string; phone?: string; email?: string; } export function PurchaseOrderEnglishFormDialog({ open, onClose, onSaved, pomObjid, proposalObjid, }: Props) { const isEdit = !!pomObjid; const [master, setMaster] = useState(EMPTY_MASTER); const [parts, setParts] = useState([]); const [deletedPartIds, setDeletedPartIds] = useState([]); const [checkedRowKeys, setCheckedRowKeys] = useState([]); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [vendorOpts, setVendorOpts] = useState([]); const [userOpts, setUserOpts] = useState([]); useEffect(() => { if (!open) return; setMaster({ ...EMPTY_MASTER }); setParts([]); setDeletedPartIds([]); setCheckedRowKeys([]); (async () => { try { const [vs, us] = await Promise.all([ purchaseApi.listVendors(), purchaseApi.listUsers(), ]); setVendorOpts(vs.map((v) => ({ code: v.code, label: v.label }))); setUserOpts(us as UserOptionExt[]); } catch {/* skip */} })(); setLoading(true); (async () => { try { if (isEdit && pomObjid) { const r = await purchaseApi.getOrderForm(pomObjid); applyServerData(r.master ?? {}, r.parts ?? []); } else if (proposalObjid) { const r = await purchaseApi.initOrderForm(proposalObjid); applyServerData(r.master ?? {}, r.parts ?? []); } } catch (e: any) { toast.error(e?.response?.data?.message ?? e?.message ?? "초기 로드 실패"); } finally { setLoading(false); } })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, isEdit, pomObjid, proposalObjid]); const applyServerData = (m: Record, ps: Record[]) => { setMaster({ objid: String(m.objid ?? ""), purchase_order_no: String(m.purchase_order_no ?? ""), purchase_date: String(m.purchase_date ?? ""), partner_objid: String(m.partner_objid ?? ""), payment_terms: String(m.payment_terms ?? ""), shipment: String(m.shipment ?? ""), attn_to: String(m.attn_to ?? ""), packing: String(m.packing ?? ""), validity: String(m.validity ?? ""), remark: String(m.remark ?? ""), sales_mng_user_id: String(m.sales_mng_user_id ?? ""), manager_name: String(m.manager_name ?? ""), manager_position: String(m.manager_position ?? ""), manager_phone: String(m.manager_phone ?? ""), manager_email: String(m.manager_email ?? ""), sales_request_objid: String(m.sales_request_objid ?? m.proposal_objid ?? ""), contract_mgmt_objid: String(m.contract_mgmt_objid ?? ""), form_type: "english", status: String(m.status ?? "create"), appr_status: String(m.appr_status ?? ""), }); setParts(ps.map((p) => ({ rowKey: nextKey(), objid: String(p.objid ?? ""), part_objid: String(p.part_objid ?? ""), part_no: String(p.part_no ?? ""), part_name: String(p.part_name ?? ""), spec: String(p.spec ?? ""), order_qty: p.order_qty === "" || p.order_qty == null ? "" : Number(p.order_qty), qty: p.qty === "" || p.qty == null ? (p.order_qty === "" || p.order_qty == null ? "" : Number(p.order_qty)) : Number(p.qty), unit: String(p.unit || "0001400"), currency: String(p.currency || DEFAULT_CURRENCY), partner_price: p.partner_price === "" || p.partner_price == null ? "" : Number(p.partner_price), supply_unit_price: toNum(p.supply_unit_price ?? toNum(p.order_qty) * toNum(p.partner_price)), delivery_request_date: String(p.delivery_request_date ?? ""), _src: p._src, }))); }; const totalSupplyPrice = useMemo( () => parts.reduce((sum, p) => sum + toNum(p.supply_unit_price), 0), [parts], ); const isReadOnly = useMemo(() => { const a = master.appr_status; return a === "결재중" || a === "결재완료" || master.status === "cancel"; }, [master.appr_status, master.status]); const handleDownload = () => window.print(); const updateRow = (rowKey: string, patch: Partial) => { setParts((prev) => prev.map((r) => { if (r.rowKey !== rowKey) return r; const merged: PartRow = { ...r, ...patch }; const q = toNum(merged.order_qty); const u = toNum(merged.partner_price); merged.qty = merged.order_qty; merged.supply_unit_price = q * u; return merged; })); }; const handleAddRow = () => { setParts((prev) => [...prev, { rowKey: nextKey(), objid: "", part_objid: "", part_no: "", part_name: "", spec: "", order_qty: "", qty: "", unit: "0001400", currency: DEFAULT_CURRENCY, partner_price: "", supply_unit_price: 0, delivery_request_date: "", }]); }; const handleDeleteSelectedRows = () => { if (checkedRowKeys.length === 0) { toast.info("Select rows to delete"); return; } setParts((prev) => { const remaining: PartRow[] = []; const deletedObjids: string[] = []; for (const r of prev) { if (checkedRowKeys.includes(r.rowKey)) { if (r.objid) deletedObjids.push(r.objid); } else { remaining.push(r); } } if (deletedObjids.length > 0) { setDeletedPartIds((d) => Array.from(new Set([...d, ...deletedObjids]))); } return remaining; }); setCheckedRowKeys([]); }; const toggleRowCheck = (rowKey: string, checked: boolean) => { setCheckedRowKeys((prev) => checked ? [...prev, rowKey] : prev.filter((k) => k !== rowKey)); }; const toggleAllCheck = (checked: boolean) => { setCheckedRowKeys(checked ? parts.map((p) => p.rowKey) : []); }; const handleSave = async () => { if (!master.partner_objid) { toast.warning("Select supplier (Messrs.)"); return; } if (parts.length === 0) { toast.warning("No items"); return; } setSaving(true); try { const payload = { master: { ...master, form_type: "english", total_supply_price: String(totalSupplyPrice), total_supply_unit_price: String(totalSupplyPrice), total_price: String(totalSupplyPrice), }, parts: parts.map((p) => ({ objid: p.objid, part_objid: p.part_objid, part_no: p.part_no, part_name: p.part_name, spec: p.spec, order_qty: toNum(p.order_qty), qty: toNum(p.qty || p.order_qty), unit: p.unit, currency: p.currency, partner_price: toNum(p.partner_price), supply_unit_price: toNum(p.supply_unit_price), delivery_request_date: p.delivery_request_date, })), deletedPartObjids: deletedPartIds, }; const res = await purchaseApi.saveOrderForm(payload); toast.success(`Saved (${res.purchase_order_no})`); onSaved?.(res); onClose(); } catch (e: any) { toast.error(e?.response?.data?.message ?? e?.message ?? "Save failed"); } finally { setSaving(false); } }; return ( { if (!v) onClose(); }}> Purchase Order (English) wace English Purchase Order PDF form
{/* 헤더 */}
RPS Logo
R P S CO., LTD.
www.rps-korea.com
8, Gukjegwahak 10-ro, Yuseong-gu, Daejeon, Republic of Korea
Tel : +82-42-602-3300 / Fax : +82-42-672-3399 / E-mail : ady1225@rps-korea.com
Purchasing Team Manager, An-Dong-Yoon
Purchase Order

We are pleased to issue Purchase Order with the terms and condition described as below.

{/* 좌+우 2열 5행 정보 테이블 */} setMaster({ ...master, partner_objid: v })} disabled={isReadOnly} /> } rightLabel="Shipment" rightBg="#ebf1de" rightCell={ setMaster({ ...master, shipment: e.target.value })} disabled={isReadOnly} /> } /> setMaster({ ...master, attn_to: e.target.value })} disabled={isReadOnly} /> } rightLabel="Payment" rightCell={ setMaster({ ...master, payment_terms: e.target.value })} disabled={isReadOnly} /> } /> setMaster({ ...master, purchase_date: v })} disabled={isReadOnly} /> } rightLabel="Packing" rightCell={ setMaster({ ...master, packing: e.target.value })} disabled={isReadOnly} /> } /> {master.purchase_order_no || auto} } rightLabel="Validity" rightCell={ setMaster({ ...master, validity: e.target.value })} disabled={isReadOnly} /> } />  } rightLabel="Remarks" rightCell={ setMaster({ ...master, remark: e.target.value })} disabled={isReadOnly} /> } />
{isReadOnly && (
This order is locked (approval in progress / completed or cancelled). {master.appr_status ? ` (status: ${master.appr_status})` : ""} {master.status === "cancel" ? " (cancelled)" : ""}
)}
{!isReadOnly && ( <> )} {isReadOnly && ( )}
{/* 그리드 */}
{parts.length === 0 ? ( ) : parts.map((row) => { const desc = row.spec ? `${row.part_name}/${row.spec}` : row.part_name; return ( ); })}
0 && checkedRowKeys.length === parts.length} onChange={(e) => toggleAllCheck(e.target.checked)} disabled={isReadOnly} /> Item No. Commodity & Description Unit Q'ty Currency Unit Price Amount Delivery
No items — use "Add Row" to start
toggleRowCheck(row.rowKey, e.target.checked)} disabled={isReadOnly} /> {row.part_no} {desc} updateRow(row.rowKey, { unit: v })} withAll={false} className="h-7" disabled={isReadOnly} /> updateRow(row.rowKey, { order_qty: v })} className="h-7 text-[11px]" disabled={isReadOnly} /> updateRow(row.rowKey, { currency: v })} withAll={false} className="h-7" disabled={isReadOnly} /> updateRow(row.rowKey, { partner_price: v })} className="h-7 text-[11px]" disabled={isReadOnly} /> {fmt2(row.supply_unit_price)} updateRow(row.rowKey, { delivery_request_date: v })} size="sm" disabled={isReadOnly} />
{/* TOTAL */}
TOTAL {fmt2(totalSupplyPrice)}

Look forward to your soonest delivery with good condition.

{/* 서명 영역 */}

Signed by Dong-Heon Lee / President
RPS CO.,LTD
Stamp { (e.currentTarget as HTMLImageElement).style.display = "none"; }} />
); } interface FieldRowProps { leftLabel: string; leftCell: React.ReactNode; rightLabel: string; rightCell: React.ReactNode; rightBg?: string; } function FieldRow({ leftLabel, leftCell, rightLabel, rightCell, rightBg }: FieldRowProps) { return ( {leftLabel || " "} {leftCell} {rightLabel} {rightCell} ); }