From 83ac3a54569d6df59be7226a1c51cd9c340e7a44 Mon Sep 17 00:00:00 2001 From: chpark Date: Sun, 31 May 2026 22:05:23 +0900 Subject: [PATCH] =?UTF-8?q?feat(daily-order-inventory):=20=EB=8B=A8?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=BC=EC=9E=90=20=E2=86=92=20=EA=B8=B0=EA=B0=84?= =?UTF-8?q?(=EC=8B=9C=EC=9E=91~=EC=A2=85=EB=A3=8C)=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 페이지: date 두 개(시작/종료) + '오늘'·'최근 7일' 빠른 버튼 - API: targetDate 대신 startDate/endDate 사용 (단일 호환 유지), 판매가능 품목은 기간 겹침 조건, 발주수량은 order_date BETWEEN으로 합산 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../m/admin/daily-order-inventory/page.tsx | 41 +++++++++++++++---- .../m/admin/daily-order-inventory/route.ts | 32 ++++++++------- 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/app/(main)/m/admin/daily-order-inventory/page.tsx b/src/app/(main)/m/admin/daily-order-inventory/page.tsx index 67a699b..2d185d5 100644 --- a/src/app/(main)/m/admin/daily-order-inventory/page.tsx +++ b/src/app/(main)/m/admin/daily-order-inventory/page.tsx @@ -25,7 +25,8 @@ const fmt = (n: number) => Number(n || 0).toLocaleString("ko-KR"); const today = () => new Date().toISOString().slice(0, 10); export default function DailyOrderInventoryPage() { - const [targetDate, setTargetDate] = useState(today()); + const [startDate, setStartDate] = useState(today()); + const [endDate, setEndDate] = useState(today()); const [keyword, setKeyword] = useState(""); const [warehouses, setWarehouses] = useState([]); const [items, setItems] = useState([]); @@ -37,7 +38,7 @@ export default function DailyOrderInventoryPage() { const res = await fetch("/api/m/admin/daily-order-inventory", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ targetDate, keyword }), + body: JSON.stringify({ startDate, endDate, keyword }), }); if (res.ok) { const j = await res.json(); @@ -50,7 +51,7 @@ export default function DailyOrderInventoryPage() { } finally { setLoading(false); } - }, [targetDate, keyword]); + }, [startDate, endDate, keyword]); useEffect(() => { load(); }, [load]); @@ -75,7 +76,8 @@ export default function DailyOrderInventoryPage() { { header: "분류", key: "KIND" }, ...items.map((it) => ({ header: it.ITEM_NAME, key: it.ITEM_NAME })), ]; - downloadXlsx(`일자별발주재고_${targetDate}`, data, cols); + const range = startDate === endDate ? startDate : `${startDate}_${endDate}`; + downloadXlsx(`일자별발주재고_${range}`, data, cols); }; return ( @@ -87,23 +89,44 @@ export default function DailyOrderInventoryPage() { 일자별 발주/재고 현황

- 선택일에 판매 가능한 품목을 헤더로, 각 창고를 좌측에 배치한 매트릭스. - 발주수량 = 그 날짜의 발주(REQUESTED는 거래처 default 창고로 가상 배정 / APPROVED 이후는 출고 창고). 재고수량 = 해당 창고 현재고. + 선택 기간에 판매 가능했던 품목을 헤더로, 각 창고를 좌측에 배치한 매트릭스. + 발주수량 = 기간 내 발주 합계(REQUESTED는 거래처 default 창고로 가상 배정 / APPROVED 이후는 출고 창고). 재고수량 = 해당 창고 현재고(기간 무관 현재 시점).

setTargetDate(e.target.value)} + value={startDate} + max={endDate || undefined} + onChange={(e) => setStartDate(e.target.value)} + className="h-9 px-3 rounded border border-slate-300 text-sm" + /> + ~ + setEndDate(e.target.value)} className="h-9 px-3 rounded border border-slate-300 text-sm" /> +
({})); - const targetDate = (body.targetDate as string) || ""; // YYYY-MM-DD, 비우면 오늘 + // 신/구 호환: startDate/endDate 우선, 없으면 targetDate(단일)로 같은 날짜 사용 + const today = new Date().toISOString().slice(0, 10); + const targetDate = (body.targetDate as string) || ""; + let startDate = (body.startDate as string) || targetDate || today; + let endDate = (body.endDate as string) || targetDate || today; + if (startDate > endDate) [startDate, endDate] = [endDate, startDate]; const keyword = (body.keyword as string) || ""; - const date = targetDate || new Date().toISOString().slice(0, 10); - // 1) 활성 창고 const warehouses = await queryRows<{ OBJID: string; WH_CODE: string; WH_NAME: string }>( `SELECT objid AS "OBJID", wh_code AS "WH_CODE", wh_name AS "WH_NAME" @@ -23,18 +26,19 @@ export async function POST(req: NextRequest) { ORDER BY wh_code` ); - // 2) 선택일에 판매가능한 품목 + // 2) 기간 내에 한 번이라도 판매가능했던 품목 + // 품목 판매기간 [sale_start_date, sale_end_date] 과 조회 기간 [startDate, endDate] 이 겹치면 포함 const itemConds: string[] = [ "COALESCE(I.is_del, 'N') != 'Y'", "COALESCE(I.is_hidden, 'N') != 'Y'", "UPPER(COALESCE(I.status, '')) = 'ACTIVE'", - `(I.sale_start_date IS NULL OR $1::date >= I.sale_start_date::date)`, - `(I.sale_end_date IS NULL OR $1::date <= I.sale_end_date::date)`, + `(I.sale_start_date IS NULL OR I.sale_start_date::date <= $2::date)`, + `(I.sale_end_date IS NULL OR I.sale_end_date::date >= $1::date)`, ]; - const params: unknown[] = [date]; + const params: unknown[] = [startDate, endDate]; if (keyword) { params.push(keyword); - itemConds.push(`(I.item_name ILIKE '%' || $2 || '%' OR I.item_code ILIKE '%' || $2 || '%')`); + itemConds.push(`(I.item_name ILIKE '%' || $3 || '%' OR I.item_code ILIKE '%' || $3 || '%')`); } const items = await queryRows<{ @@ -89,12 +93,12 @@ export async function POST(req: NextRequest) { WHERE SM.ref_type = 'ORDER' AND SM.move_type = 'OUT' AND COALESCE(W.is_del,'N') != 'Y' - AND O.order_date = $1::date + AND O.order_date BETWEEN $1::date AND $2::date AND O.status IN ('APPROVED','INVOICED','PAID') AND COALESCE(O.is_del,'N') != 'Y' - AND SM.item_objid IN (${items.map((_, i) => `$${i + 2}`).join(",")}) + AND SM.item_objid IN (${items.map((_, i) => `$${i + 3}`).join(",")}) GROUP BY SM.item_objid, W.wh_code`, - [date, ...items.map((it) => it.OBJID)] + [startDate, endDate, ...items.map((it) => it.OBJID)] ); // REQUESTED 상태(아직 출고 전) 발주는 거래처 default 창고로 배정 (fallback: WH001) @@ -109,10 +113,10 @@ export async function POST(req: NextRequest) { WHERE COALESCE(OI.kind, 'ITEM') = 'ITEM' AND COALESCE(O.is_del, 'N') != 'Y' AND O.status = 'REQUESTED' - AND O.order_date = $1::date - AND OI.item_objid IN (${items.map((_, i) => `$${i + 2}`).join(",")}) + AND O.order_date BETWEEN $1::date AND $2::date + AND OI.item_objid IN (${items.map((_, i) => `$${i + 3}`).join(",")}) GROUP BY OI.item_objid, W.wh_code`, - [date, ...items.map((it) => it.OBJID)] + [startDate, endDate, ...items.map((it) => it.OBJID)] ); // 매트릭스 조립 — 각 품목별 STOCK / ORDER 객체 (wh_code → qty)