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)