From b568a8858a4038f875a1d9869fb45a6f2ffd97eb Mon Sep 17 00:00:00 2001 From: chpark Date: Thu, 14 May 2026 14:43:03 +0900 Subject: [PATCH] =?UTF-8?q?feat(procurements+inbounds):=20=EB=B0=9C?= =?UTF-8?q?=EC=A3=BC=EC=84=9C=20=EB=82=A9=ED=92=88=EC=A1=B0=EA=B1=B4=201?= =?UTF-8?q?=EB=B2=88=20=EC=82=AD=EC=A0=9C=20+=20=EC=9E=85=EA=B3=A0=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=EB=A6=AC=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 매입 발주서 납품조건: - '상기 품목의 납기 지연 시...' 1번 항목 삭제. 검수/수량변경 2개만 유지. 입고 처리 체크리스트 (memo 컬럼에 텍스트로 저장 — 스키마 변경 없음): 1) 발주수량 vs 입고수량 일치 (체크박스) 2) 1카톤 N개 일치 (체크박스 + 카톤 단위 입력) 3) 소비기한 (date) 4) 물류창고 입고 최종완료자 (물류팀 4명 select) 5) 특이사항 (textarea) 저장 시 momo_inbounds.memo 에 줄단위 텍스트로 박힘: [수량 일치] Y ✓ [카톤 일치] Y ✓ (1카톤 12개) [소비기한] 2026-11-30 [입고완료자] 이효철 (물류총괄) [특이사항] ... --- src/app/(main)/m/admin/inbounds/page.tsx | 94 +++++++++++++++++++- src/app/(main)/m/admin/procurements/page.tsx | 1 - 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/src/app/(main)/m/admin/inbounds/page.tsx b/src/app/(main)/m/admin/inbounds/page.tsx index 74c999b..1c05985 100644 --- a/src/app/(main)/m/admin/inbounds/page.tsx +++ b/src/app/(main)/m/admin/inbounds/page.tsx @@ -42,6 +42,23 @@ export default function InboundsPage() { // 라인별 입력 (창고/입고수량/불량수량) const [inputs, setInputs] = useState>({}); + // 입고 체크리스트 + const [checklist, setChecklist] = useState({ + qtyMatch: false, // 1) 발주수량/입고수량 일치 + cartonMatch: false, // 2) 1카톤 N개 일치 + cartonSize: "", // 카톤 단위 + expiryDate: "", // 3) 소비기한 + completedBy: "", // 4) 물류팀 입고 최종완료자 + remark: "", // 5) 특이건 메모 + }); + // 물류팀 4명 — 임직원(user_type='A') 중 momo4763/momo7529 외 2명까지 + const LOGISTICS = [ + { id: "momo4763", name: "이효철 (물류총괄)" }, + { id: "momo7529", name: "유우형 (물류팀장)" }, + { id: "momo9431", name: "강상익 (김포지사 총괄)" }, + { id: "momo5315", name: "배연진 (경영팀장)" }, + ]; + const load = useCallback(async () => { const body: Record = {}; // 입고 화면은 REQUESTED + PARTIAL 만 보이게 @@ -150,6 +167,15 @@ export default function InboundsPage() { }); if (!ok.isConfirmed) return; + // 체크리스트 텍스트화 — memo 에 저장 (스키마 변경 없이) + const checklistMemo = [ + `[수량 일치] ${checklist.qtyMatch ? "Y ✓" : "N"}`, + `[카톤 일치] ${checklist.cartonMatch ? `Y ✓ (1카톤 ${checklist.cartonSize || "?"}개)` : "N"}`, + `[소비기한] ${checklist.expiryDate || "-"}`, + `[입고완료자] ${checklist.completedBy || "-"}`, + `[특이사항] ${checklist.remark || "-"}`, + ].join("\n"); + setBusy(true); let successCnt = 0, failCnt = 0; const errors: string[] = []; @@ -161,6 +187,7 @@ export default function InboundsPage() { procObjid: detail.proc.OBJID, whObjid, lines: whLines, + memo: checklistMemo, }), }); const j = await res.json(); @@ -281,6 +308,9 @@ export default function InboundsPage() { warehouses={warehouses} inputs={inputs} onUpdate={updateInput} + checklist={checklist} + onChecklistChange={(patch) => setChecklist((p) => ({ ...p, ...patch }))} + logistics={LOGISTICS} /> )} @@ -290,11 +320,18 @@ export default function InboundsPage() { ); } -function InboundForm({ detail, warehouses, inputs, onUpdate }: { +interface Checklist { + qtyMatch: boolean; cartonMatch: boolean; cartonSize: string; + expiryDate: string; completedBy: string; remark: string; +} +function InboundForm({ detail, warehouses, inputs, onUpdate, checklist, onChecklistChange, logistics }: { detail: { proc: ProcDetail; items: ProcLine[] }; warehouses: Warehouse[]; inputs: Record; onUpdate: (lineObjid: string, patch: Partial<{ whObjid: string; qtyNormal: number; qtyDefect: number }>) => void; + checklist: Checklist; + onChecklistChange: (patch: Partial) => void; + logistics: { id: string; name: string }[]; }) { const editable = detail.proc.STATUS === "PAID" || detail.proc.STATUS === "PARTIAL"; return ( @@ -386,10 +423,65 @@ function InboundForm({ detail, warehouses, inputs, onUpdate }: { {editable && ( + <>
※ 정상 입고 + 불량은 남은 수량 이하로만 입력 가능합니다. 0으로 두면 그 라인은 입고하지 않습니다.
※ 일부 라인만 입고하면 발주서가 입고중으로 표시되고, 나중에 다시 들어와 마저 입고할 수 있어요.
+ + {/* 입고 체크리스트 — memo 컬럼에 함께 저장 */} +
+
📋 입고 체크리스트
+ + + +
+ + onChecklistChange({ cartonSize: e.target.value })} + placeholder="개수" + className="w-20 h-7 px-2 border border-slate-300 rounded text-[11px] text-right tabular-nums" /> + 개 일치하나요? +
+ +
+ 3) 소비기한 + onChecklistChange({ expiryDate: e.target.value })} + className="h-7 px-2 border border-slate-300 rounded text-[11px]" /> +
+ +
+ 4) 물류창고 입고 최종완료자 + +
+ +
+
5) 특이사항
+