From 80d2240a23412b8af1de03c9040202918ddc9454 Mon Sep 17 00:00:00 2001 From: chpark Date: Wed, 13 May 2026 11:29:55 +0900 Subject: [PATCH] =?UTF-8?q?feat(items+einvoices):=20=ED=92=88=EB=AA=A9=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EA=B0=92=20=EC=A0=9C=EA=B1=B0=20+=20?= =?UTF-8?q?=EA=B3=B5=EA=B8=89=EC=97=85=EC=B2=B4/=EA=B1=B0=EB=9E=98?= =?UTF-8?q?=EC=B2=98=20=EC=A1=B0=ED=9A=8C=EC=A1=B0=EA=B1=B4=20+=20?= =?UTF-8?q?=ED=95=A9=EA=B3=84=20=EB=A9=B4=EC=84=B8/=EA=B3=BC=EC=84=B8=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 품목관리(items): · STATUS 컬럼 표시/필터/폼에서 제거 (전부 ACTIVE 가 default — 사용자 사용 안 함) · 조회조건에 공급업체 SearchableSelect 추가 (이미 백엔드 vendorObjid 지원) - 계산서 발행(einvoices): · 조회조건에 거래처 SearchableSelect 추가 (customers list API 사용) · 페이지 하단 tfoot 에 면세 합계 / 과세 합계 / 총 합계 분리 표시 Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app/(main)/m/admin/einvoices/page.tsx | 70 +++++++++++++++++++++-- src/app/(main)/m/admin/items/page.tsx | 36 ++++-------- 2 files changed, 77 insertions(+), 29 deletions(-) diff --git a/src/app/(main)/m/admin/einvoices/page.tsx b/src/app/(main)/m/admin/einvoices/page.tsx index 08f6395..aa0036b 100644 --- a/src/app/(main)/m/admin/einvoices/page.tsx +++ b/src/app/(main)/m/admin/einvoices/page.tsx @@ -1,9 +1,10 @@ "use client"; -import { useEffect, useState, useCallback } from "react"; +import { useEffect, useState, useCallback, useMemo } from "react"; import { FileText, Send, Download, RefreshCcw, AlertCircle } from "lucide-react"; import Swal from "sweetalert2"; import { downloadXlsx } from "@/lib/xlsx-export"; +import { SearchableSelect } from "@/components/ui/searchable-select"; interface Einvoice { OBJID: string; @@ -53,9 +54,13 @@ function defaultRange() { return [s.toISOString().slice(0, 10), e.toISOString().slice(0, 10)]; } +interface Customer { USER_ID: string; USER_NAME: string } + export default function EinvoicesPage() { const [[from, to], setRange] = useState(defaultRange()); const [statusFilter, setStatusFilter] = useState(""); + const [customerFilter, setCustomerFilter] = useState(""); + const [customers, setCustomers] = useState([]); const [list, setList] = useState([]); const [pending, setPending] = useState([]); const [busy, setBusy] = useState(false); @@ -63,10 +68,37 @@ export default function EinvoicesPage() { const load = useCallback(async () => { const res = await fetch("/api/m/einvoices/list", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ dateFrom: from, dateTo: to, status: statusFilter || undefined }), + body: JSON.stringify({ + dateFrom: from, + dateTo: to, + status: statusFilter || undefined, + customerObjid: customerFilter || undefined, + }), }); setList((await res.json()).RESULTLIST ?? []); - }, [from, to, statusFilter]); + }, [from, to, statusFilter, customerFilter]); + + const loadCustomers = useCallback(async () => { + const res = await fetch("/api/m/customers/list", { + method: "POST", headers: { "Content-Type": "application/json" }, body: "{}", + }); + setCustomers((await res.json()).RESULTLIST ?? []); + }, []); + + // 면세/과세/합계 합산 + const summary = useMemo(() => { + let taxFreeAmount = 0, taxableSupply = 0, taxableVat = 0, total = 0; + for (const e of list) { + total += Number(e.TOTAL_AMOUNT) || 0; + if (e.INVOICE_KIND === "TAXFREE") { + taxFreeAmount += Number(e.TOTAL_SUPPLY) || 0; + } else { + taxableSupply += Number(e.TOTAL_SUPPLY) || 0; + taxableVat += Number(e.TOTAL_VAT) || 0; + } + } + return { taxFreeAmount, taxableSupply, taxableVat, taxableTotal: taxableSupply + taxableVat, total }; + }, [list]); const loadPending = useCallback(async () => { // 발주 + 발행이력 동시 조회 후 이미 발행된 건은 제외 @@ -88,6 +120,7 @@ export default function EinvoicesPage() { }, []); useEffect(() => { load(); loadPending(); }, [load, loadPending]); + useEffect(() => { loadCustomers(); }, [loadCustomers]); const issueFromOrder = async (orderObjid: string, kind: "TAX" | "TAXFREE" = "TAX") => { const ok = await Swal.fire({ @@ -217,12 +250,20 @@ export default function EinvoicesPage() {
발행 이력 ({list.length}건) -
+
setRange([e.target.value, to])} className="h-8 px-2 rounded border border-slate-200" /> ~ setRange([from, e.target.value])} className="h-8 px-2 rounded border border-slate-200" /> +
+ ({ value: c.USER_ID, label: c.USER_NAME }))]} + value={customerFilter} + onChange={setCustomerFilter} + placeholder="거래처" + /> +
setFilterStatus(e.target.value)} - className="h-10 px-3 rounded-lg border border-slate-200 text-sm focus:border-emerald-500 outline-none bg-white" - > - - - - +
+ ({ value: v.OBJID, label: v.VENDOR_NAME }))]} + value={filterVendor} + onChange={setFilterVendor} + placeholder="공급업체" + /> +