feat(procurements+inbounds): 발주서 납품조건 1번 삭제 + 입고 체크리스트
Deploy momo-erp / deploy (push) Successful in 2m16s

매입 발주서 납품조건:
- '상기 품목의 납기 지연 시...' 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
  [입고완료자]  이효철 (물류총괄)
  [특이사항]    ...
This commit is contained in:
chpark
2026-05-14 14:43:03 +09:00
parent 3505148994
commit b568a8858a
2 changed files with 93 additions and 2 deletions
+93 -1
View File
@@ -42,6 +42,23 @@ export default function InboundsPage() {
// 라인별 입력 (창고/입고수량/불량수량)
const [inputs, setInputs] = useState<Record<string, { whObjid: string; qtyNormal: number; qtyDefect: number }>>({});
// 입고 체크리스트
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<string, unknown> = {};
// 입고 화면은 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}
/>
)}
</div>
@@ -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<string, { whObjid: string; qtyNormal: number; qtyDefect: number }>;
onUpdate: (lineObjid: string, patch: Partial<{ whObjid: string; qtyNormal: number; qtyDefect: number }>) => void;
checklist: Checklist;
onChecklistChange: (patch: Partial<Checklist>) => 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 }: {
</table>
{editable && (
<>
<div className="mt-3 text-[11px] text-slate-500">
+ <b> </b> . 0 .<br />
<span className="inline-block px-1.5 py-0.5 rounded bg-orange-100 text-orange-700 font-bold"></span> , .
</div>
{/* 입고 체크리스트 — memo 컬럼에 함께 저장 */}
<div className="mt-4 border border-emerald-200 bg-emerald-50/40 rounded-lg p-3 space-y-2">
<div className="font-bold text-[12px] text-emerald-800 mb-1">📋 </div>
<label className="flex items-center gap-2 text-[12px] cursor-pointer">
<input type="checkbox" className="w-4 h-4 accent-emerald-600"
checked={checklist.qtyMatch}
onChange={(e) => onChecklistChange({ qtyMatch: e.target.checked })} />
<span>1) ?</span>
</label>
<div className="flex items-center gap-2 text-[12px]">
<label className="flex items-center gap-2 cursor-pointer">
<input type="checkbox" className="w-4 h-4 accent-emerald-600"
checked={checklist.cartonMatch}
onChange={(e) => onChecklistChange({ cartonMatch: e.target.checked })} />
<span>2) 1</span>
</label>
<input type="number" min={0}
value={checklist.cartonSize}
onChange={(e) => onChecklistChange({ cartonSize: e.target.value })}
placeholder="개수"
className="w-20 h-7 px-2 border border-slate-300 rounded text-[11px] text-right tabular-nums" />
<span> ?</span>
</div>
<div className="flex items-center gap-2 text-[12px]">
<span className="w-44">3) </span>
<input type="date" value={checklist.expiryDate}
onChange={(e) => onChecklistChange({ expiryDate: e.target.value })}
className="h-7 px-2 border border-slate-300 rounded text-[11px]" />
</div>
<div className="flex items-center gap-2 text-[12px]">
<span className="w-44">4) </span>
<select value={checklist.completedBy}
onChange={(e) => onChecklistChange({ completedBy: e.target.value })}
className="h-7 px-2 border border-slate-300 rounded text-[11px] bg-white">
<option value="">-- --</option>
{logistics.map((p) => <option key={p.id} value={p.name}>{p.name}</option>)}
</select>
</div>
<div className="text-[12px]">
<div className="mb-1">5) </div>
<textarea rows={2}
value={checklist.remark}
onChange={(e) => onChecklistChange({ remark: e.target.value })}
placeholder="특이건이 있으면 입력"
className="w-full px-2 py-1 border border-slate-300 rounded text-[11px] resize-none" />
</div>
</div>
</>
)}
</div>
);
@@ -481,7 +481,6 @@ function ProcurementForm({ detail, vendors, onSetVendor, onSetMemo, onSetTerm, o
<p className="mt-4 font-semibold text-[12px]">2. </p>
<ol className="text-[11px] mt-1 space-y-1 leading-relaxed list-decimal pl-5">
<li> , 1 3/1000 .</li>
<li> , .</li>
<li> , .</li>
</ol>