diff --git a/db/migrations/026_menu_info_proc_payment_stock_history.sql b/db/migrations/026_menu_info_proc_payment_stock_history.sql new file mode 100644 index 0000000..e5019f2 --- /dev/null +++ b/db/migrations/026_menu_info_proc_payment_stock_history.sql @@ -0,0 +1,19 @@ +-- FITO menu_info 에 매입 입금관리 + 재고이력 메뉴 추가 (사이드바에 노출되도록) +-- 부모: 9000300 매입/입고 + +-- 기존 seq 재정렬 +UPDATE menu_info SET seq = '12' WHERE objid = '9000302'; -- 입고 처리: 11→12 +UPDATE menu_info SET seq = '13' WHERE objid = '9000303'; -- 재고 관리: 12→13 + +-- 매입 입금관리 (seq 11) + 재고이력 (seq 14) — idempotent +INSERT INTO menu_info (objid, menu_type, parent_obj_id, menu_name_kor, menu_name_eng, seq, menu_url, status, system_name, regdate) +VALUES + ('9000304', '1', '9000300', '매입 입금관리', 'Proc Payment', '11', '/m/admin/proc-payments', 'active', 'PMS', NOW()), + ('9000305', '1', '9000300', '재고이력', 'Stock History', '14', '/m/admin/inventory/history', 'active', 'PMS', NOW()) +ON CONFLICT (objid) DO UPDATE SET + menu_name_kor = EXCLUDED.menu_name_kor, + menu_name_eng = EXCLUDED.menu_name_eng, + seq = EXCLUDED.seq, + menu_url = EXCLUDED.menu_url, + status = 'active', + system_name = EXCLUDED.system_name; diff --git a/src/app/(main)/m/admin/inventory/history/page.tsx b/src/app/(main)/m/admin/inventory/history/page.tsx index c63b2ae..847c851 100644 --- a/src/app/(main)/m/admin/inventory/history/page.tsx +++ b/src/app/(main)/m/admin/inventory/history/page.tsx @@ -13,7 +13,9 @@ interface Move { MOVE_TYPE_NAME: string; QTY: number; REF_TYPE: string; + REF_TYPE_LABEL?: string; REF_OBJID: string; + COUNTER_WH_NAME?: string | null; MEMO: string; REGID: string; REGDATE: string; @@ -186,7 +188,14 @@ export default function InventoryHistoryPage() { {m.MOVE_TYPE === "OUT" ? "-" : "+"}{fmt(m.QTY)} - {m.REF_TYPE || "-"} + + {m.REF_TYPE_LABEL || m.REF_TYPE || "-"} + {m.REF_TYPE === "TRANSFER" && m.COUNTER_WH_NAME && ( + + {m.MOVE_TYPE === "OUT" ? `→ ${m.COUNTER_WH_NAME}` : `← ${m.COUNTER_WH_NAME}`} + + )} + {m.MEMO || "-"} {m.REGID || "-"} {m.REGDATE} @@ -215,7 +224,7 @@ export default function InventoryHistoryPage() { {m.MOVE_TYPE_NAME} - {m.REF_TYPE && {m.REF_TYPE}} + {m.REF_TYPE && {m.REF_TYPE_LABEL || m.REF_TYPE}{m.REF_TYPE === "TRANSFER" && m.COUNTER_WH_NAME && (m.MOVE_TYPE === "OUT" ? ` → ${m.COUNTER_WH_NAME}` : ` ← ${m.COUNTER_WH_NAME}`)}}
{m.ITEM_NAME}
{m.ITEM_CODE}
diff --git a/src/app/(main)/m/admin/inventory/page.tsx b/src/app/(main)/m/admin/inventory/page.tsx index 2295ff0..fe736de 100644 --- a/src/app/(main)/m/admin/inventory/page.tsx +++ b/src/app/(main)/m/admin/inventory/page.tsx @@ -348,7 +348,9 @@ interface MoveRow { MOVE_TYPE_NAME?: string; QTY: number; REF_TYPE: string; + REF_TYPE_LABEL?: string; REF_OBJID: string; + COUNTER_WH_NAME?: string | null; MEMO: string | null; REGID: string | null; REGDATE: string; @@ -424,7 +426,14 @@ function StockHistoryModal({ {Number(m.QTY) > 0 ? "+" : ""}{fmt(m.QTY)} - {m.REF_TYPE || "-"} + + {m.REF_TYPE_LABEL || m.REF_TYPE || "-"} + {m.REF_TYPE === "TRANSFER" && m.COUNTER_WH_NAME && ( + + {m.MOVE_TYPE === "OUT" ? `→ ${m.COUNTER_WH_NAME}` : `← ${m.COUNTER_WH_NAME}`} + + )} + {m.MEMO || "-"} {m.REGID || "-"} diff --git a/src/app/(main)/m/orders/new/page.tsx b/src/app/(main)/m/orders/new/page.tsx index 69e48ca..d2b652b 100644 --- a/src/app/(main)/m/orders/new/page.tsx +++ b/src/app/(main)/m/orders/new/page.tsx @@ -34,6 +34,7 @@ export default function ItemsBrowse() { const [items, setItems] = useState([]); const [keyword, setKeyword] = useState(""); const [taxFilter, setTaxFilter] = useState<"" | "Y" | "N">(""); + const [stockFilter, setStockFilter] = useState<"AVAILABLE" | "ALL">("AVAILABLE"); const [loading, setLoading] = useState(false); const [cart, setCart] = useState([]); const [extras, setExtras] = useState([]); @@ -56,14 +57,22 @@ export default function ItemsBrowse() { const res = await fetch("/api/m/items/list", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ keyword, isTaxFree: taxFilter || undefined }), + body: JSON.stringify({ + keyword, + isTaxFree: taxFilter || undefined, + onlyAvailable: stockFilter === "AVAILABLE", + }), }); const j = await res.json(); setItems(j.RESULTLIST ?? []); setLoading(false); - }, [keyword, taxFilter]); + }, [keyword, taxFilter, stockFilter]); - useEffect(() => { fetchItems(); }, []); // eslint-disable-line + // 검색조건 변경 시 즉시 자동 조회 (디바운스 250ms) + useEffect(() => { + const t = setTimeout(() => { fetchItems(); }, 250); + return () => clearTimeout(t); + }, [fetchItems]); // 카트에 택배전용 품목이 있는지 const cartNeedsDelivery = useMemo( @@ -442,9 +451,10 @@ export default function ItemsBrowse() { - + {/* 보기 모드 토글 */}
-
+
+ setDateFrom(e.target.value)} + className="h-10 px-3 rounded-lg border border-slate-200 text-sm" /> + ~ + setDateTo(e.target.value)} + className="h-10 px-3 rounded-lg border border-slate-200 text-sm" /> - + {(dateFrom || dateTo || status) && ( + + )}
diff --git a/src/app/api/m/inventory/history/route.ts b/src/app/api/m/inventory/history/route.ts index d858b83..8ed5242 100644 --- a/src/app/api/m/inventory/history/route.ts +++ b/src/app/api/m/inventory/history/route.ts @@ -65,13 +65,27 @@ export async function POST(req: NextRequest) { END AS "MOVE_TYPE_NAME", SM.qty AS "QTY", SM.ref_type AS "REF_TYPE", + CASE SM.ref_type + WHEN 'INBOUND' THEN '입고' + WHEN 'PROCUREMENT' THEN '매입발주' + WHEN 'ORDER' THEN '출고' + WHEN 'TRANSFER' THEN '이동' + WHEN 'ADJUST' THEN '재고조정' + ELSE COALESCE(SM.ref_type, '-') + END AS "REF_TYPE_LABEL", SM.ref_objid AS "REF_OBJID", + -- TRANSFER 의 경우 ref_objid 는 상대 창고 — 그 창고명 join + CASE WHEN SM.ref_type = 'TRANSFER' + THEN CW.wh_name + ELSE NULL + END AS "COUNTER_WH_NAME", SM.memo AS "MEMO", SM.regid AS "REGID", TO_CHAR(SM.regdate, 'YYYY-MM-DD HH24:MI') AS "REGDATE" FROM momo_stock_moves SM - LEFT JOIN momo_warehouses W ON SM.wh_objid = W.objid - LEFT JOIN momo_items I ON SM.item_objid = I.objid + LEFT JOIN momo_warehouses W ON SM.wh_objid = W.objid + LEFT JOIN momo_warehouses CW ON SM.ref_objid = CW.objid::text AND SM.ref_type = 'TRANSFER' + LEFT JOIN momo_items I ON SM.item_objid = I.objid WHERE ${conditions.join(" AND ")} ORDER BY SM.regdate DESC LIMIT 500`, diff --git a/src/app/api/m/inventory/transfer/route.ts b/src/app/api/m/inventory/transfer/route.ts index 8e3243c..ed04cef 100644 --- a/src/app/api/m/inventory/transfer/route.ts +++ b/src/app/api/m/inventory/transfer/route.ts @@ -51,12 +51,13 @@ export async function POST(req: NextRequest) { [createObjectId(), toWhObjid, itemObjid, qty] ); - // 이동 로그 — 출발(OUT) + 도착(IN) + // 이동 로그 — 출발(OUT)에 ref_objid=도착창고, 도착(IN)에 ref_objid=출발창고 + // (history 조회 시 ref_objid 로 상대 창고 join 해서 "→/←" 표기) await client.query( - `INSERT INTO momo_stock_moves (objid, wh_objid, item_objid, move_type, qty, ref_type, memo, regdate, regid) - VALUES ($1, $2, $3, 'OUT', $4, 'TRANSFER', $5, NOW(), $6), - ($7, $8, $3, 'IN', $4, 'TRANSFER', $5, NOW(), $6)`, - [createObjectId(), fromWhObjid, itemObjid, qty, memo ?? null, userId, createObjectId(), toWhObjid] + `INSERT INTO momo_stock_moves (objid, wh_objid, item_objid, move_type, qty, ref_type, ref_objid, memo, regdate, regid) + VALUES ($1, $2, $3, 'OUT', $4, 'TRANSFER', $9, $5, NOW(), $6), + ($7, $8, $3, 'IN', $4, 'TRANSFER', $2, $5, NOW(), $6)`, + [createObjectId(), fromWhObjid, itemObjid, qty, memo ?? null, userId, createObjectId(), toWhObjid, toWhObjid] ); await client.query("COMMIT");