diff --git a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx index a2fae223..a850622e 100644 --- a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx @@ -70,8 +70,13 @@ const CONTRACT_RESULTS = [ { value: "0000965", label: "Cancel" }, ]; +// wace estimateAndOrderRegistFormPopup 라인 — 제품구분/S/N/요청납기/반납사유/고객요청사항 포함 const EMPTY_ITEM: OrderItem = { - seq: 1, part_objid: "", part_no: "", part_name: "", quantity: 1, + seq: 1, + product: "", part_objid: "", part_no: "", part_name: "", + serials: [], + due_date: "", return_reason: "", customer_request: "", + quantity: 1, order_quantity: "", order_unit_price: "0", order_supply_price: "0", order_vat: "0", order_total_amount: "0", }; @@ -98,8 +103,18 @@ export default function SalesOrderPage() { items: [], }); - // 품목 검색 모달 + // 품목 검색 모달 (라인별 진입) const [itemDialogOpen, setItemDialogOpen] = useState(false); + const [itemSearchTargetIdx, setItemSearchTargetIdx] = useState(null); + + // S/N 관리 모달 + 연속번호 생성 (wace fn_openItemSnPopup / fn_openItemSequentialSnPopup) + const [serialDialogOpen, setSerialDialogOpen] = useState(false); + const [serialDialogIdx, setSerialDialogIdx] = useState(null); + const [serialDraft, setSerialDraft] = useState([]); + const [serialInput, setSerialInput] = useState(""); + const [seqDialogOpen, setSeqDialogOpen] = useState(false); + const [seqStartNo, setSeqStartNo] = useState(""); + const [seqCount, setSeqCount] = useState(""); // 첨부파일 다이얼로그 (주문서첨부 클립 컬럼 클릭 시) const [attachDialogOpen, setAttachDialogOpen] = useState(false); @@ -363,6 +378,63 @@ export default function SalesOrderPage() { const addItem = () => setForm((prev) => ({ ...prev, items: [...(prev.items ?? []), { ...EMPTY_ITEM, seq: (prev.items?.length ?? 0) + 1 }] })); const removeItem = (idx: number) => setForm((prev) => ({ ...prev, items: (prev.items ?? []).filter((_, i) => i !== idx).map((it, i) => ({ ...it, seq: i + 1 })) })); + // S/N 관리 (wace fn_openItemSnPopup) — 견적관리와 동일 패턴 + const openSerialDialog = (idx: number) => { + const item = form.items?.[idx]; + setSerialDialogIdx(idx); + setSerialDraft([...(item?.serials ?? [])]); + setSerialInput(""); + setSerialDialogOpen(true); + }; + const addSerialDraft = () => { + const v = serialInput.trim(); + if (!v) { toast.warning("S/N을 입력해주세요."); return; } + if (serialDraft.includes(v)) { toast.warning("이미 등록된 S/N입니다."); return; } + setSerialDraft((prev) => [...prev, v]); + setSerialInput(""); + }; + const removeSerialDraft = (i: number) => setSerialDraft((prev) => prev.filter((_, k) => k !== i)); + const applySerialDraft = () => { + if (serialDialogIdx === null) return; + updateItem(serialDialogIdx, "serials", [...serialDraft]); + setSerialDialogOpen(false); + }; + const openSeqDialog = () => { setSeqStartNo(""); setSeqCount(""); setSeqDialogOpen(true); }; + const generateSequentialSn = () => { + const startNo = seqStartNo.trim(); + const count = parseInt(seqCount, 10); + if (!startNo) { toast.warning("시작 번호를 입력해주세요."); return; } + if (!count || count < 1) { toast.warning("생성 개수를 1 이상 입력해주세요."); return; } + if (count > 100) { toast.warning("최대 100개까지만 생성 가능합니다."); return; } + const m = startNo.match(/^(.*?)(\d+)$/); + if (!m) { toast.warning("형식이 올바르지 않습니다. 마지막에 숫자가 있어야 합니다."); return; } + const prefix = m[1]; const startNum = parseInt(m[2], 10); const numLen = m[2].length; + setSerialDraft((prev) => { + const next = [...prev]; + for (let i = 0; i < count; i++) { + const sn = prefix + String(startNum + i).padStart(numLen, "0"); + if (!next.includes(sn)) next.push(sn); + } + return next; + }); + setSeqDialogOpen(false); + }; + + // 라인 합계 자동 계산용 헬퍼 + const formatNum = (v: any) => { + const n = Number(String(v ?? "0").replace(/,/g, "")); + return isNaN(n) ? 0 : n; + }; + const lineTotal = useMemo(() => { + const items = form.items ?? []; + return items.reduce((acc, it) => ({ + qty: acc.qty + formatNum(it.order_quantity), + supply: acc.supply + formatNum(it.order_supply_price), + vat: acc.vat + formatNum(it.order_vat), + total: acc.total + formatNum(it.order_total_amount), + }), { qty: 0, supply: 0, vat: 0, total: 0 }); + }, [form.items]); + return (
{ConfirmDialogComponent} @@ -489,107 +561,194 @@ export default function SalesOrderPage() { /> - + - {dialogMode === "create" ? "주문서 등록" : "주문서 수정"} - 주문 헤더 + 라인을 입력합니다. + 영업관리 _ 주문서관리 _ 수주통합등록 + 수주통합 기본정보 + 품목정보를 입력합니다. (wace estimateAndOrderRegistFormPopup 1:1) + {/* 수주통합 기본정보 — wace 헤더 9개 (영업번호 자동채번 표시 포함) */}
- 주문 헤더 + 수주통합 기본정보
-
-
-
- setForm({ ...form, po_no: e.target.value })} />
-
- setForm({ ...form, order_date: e.target.value })} />
-
- setForm({ ...form, req_del_date: e.target.value })} />
-
- setForm({ ...form, customer_objid: v })} - />
-
- setForm({ ...form, receipt_date: e.target.value })} />
-
- setForm({ ...form, contract_currency: e.target.value })} />
-
- setForm({ ...form, exchange_rate: e.target.value })} />
-
- +
+
+ + setForm({ ...form, category_cd: v })} /> +
+
+ + setForm({ ...form, area_cd: v })} /> +
+
+ + setForm({ ...form, customer_objid: v })} /> +
+
+ +
-
-
-
- setForm({ ...form, pm_user_id: e.target.value })} />
-
- setForm({ ...form, shipping_method: e.target.value })} />
-
-