diff --git a/public/manual.html b/public/manual.html
index 2ad07b0..d657a5b 100644
--- a/public/manual.html
+++ b/public/manual.html
@@ -561,6 +561,12 @@
💡 공급업체별 품목 일괄 불러오기
품목 모달에서 '현재 발주서 공급업체만' 필터를 켜면 그 공급업체에 등록된 모든 품목이 보여요. 헤더 체크박스로 전체 선택 → [선택한 N개 추가] 누르면 한 번에 다 들어가요. 그 후 필요한 것만 남기고 [×] 로 빼면 돼요.
diff --git a/src/app/(main)/m/admin/procurements/page.tsx b/src/app/(main)/m/admin/procurements/page.tsx
index ae47ad4..2618825 100644
--- a/src/app/(main)/m/admin/procurements/page.tsx
+++ b/src/app/(main)/m/admin/procurements/page.tsx
@@ -1,7 +1,7 @@
"use client";
-import { useEffect, useState, useCallback } from "react";
-import { Plus, Send, Search, RefreshCcw, X } from "lucide-react";
+import { useEffect, useState, useCallback, useRef } from "react";
+import { Plus, Send, Search, RefreshCcw, X, Download, Image as ImageIcon } from "lucide-react";
import Swal from "sweetalert2";
interface ProcRow {
@@ -281,8 +281,53 @@ function ProcurementForm({ detail, vendors, onSetVendor, onSetMemo, onAddPicker,
onDeleteLine: (objid: string) => void;
}) {
const editable = detail.proc.STATUS === "OPEN";
+ const formRef = useRef
(null);
+
+ const captureAndShare = async () => {
+ try {
+ const mod = await import("html-to-image");
+ if (!formRef.current) return;
+ const dataUrl = await mod.toPng(formRef.current, { backgroundColor: "#ffffff", pixelRatio: 2 });
+ const blob = await (await fetch(dataUrl)).blob();
+ const file = new File([blob], `${detail.proc.PROC_NO}_발주서.png`, { type: "image/png" });
+ const navAny = navigator as Navigator & { canShare?: (data: { files: File[] }) => boolean };
+ if (navAny.canShare && navAny.canShare({ files: [file] })) {
+ await navigator.share({ files: [file], title: `${detail.proc.PROC_NO} 발주서`, text: detail.proc.VENDOR_NAME ?? "" });
+ return;
+ }
+ const a = document.createElement("a");
+ a.href = dataUrl;
+ a.download = `${detail.proc.PROC_NO}_발주서.png`;
+ a.click();
+ Swal.fire({ icon: "success", title: "이미지 저장 완료", text: "다운로드한 이미지를 카톡 등에 첨부해 보내세요.", timer: 1500, showConfirmButton: false });
+ } catch (err) {
+ console.error("[capture]", err);
+ Swal.fire({ icon: "error", title: "이미지 캡처 실패", text: "잠시 후 다시 시도하세요." });
+ }
+ };
+
return (
+ {/* 공유/엑셀 버튼 — 캡처 영역 밖 */}
+
+
+
발 주 서
@@ -376,6 +421,7 @@ function ProcurementForm({ detail, vendors, onSetVendor, onSetMemo, onAddPicker,
{detail.proc.MEMO ||
없음}
)}
+
{/* /formRef capture area */}
);
}
diff --git a/src/app/api/m/items/list/route.ts b/src/app/api/m/items/list/route.ts
index 1fbf15c..6c53e16 100644
--- a/src/app/api/m/items/list/route.ts
+++ b/src/app/api/m/items/list/route.ts
@@ -94,7 +94,7 @@ export async function POST(req: NextRequest) {
TO_CHAR(I.regdate, 'YYYY-MM-DD') AS "REGDATE"
FROM momo_items I
LEFT JOIN momo_makers M ON I.maker_objid = M.objid
- LEFT JOIN supply_mng V ON I.vendor_objid = V.objid
+ LEFT JOIN supply_mng V ON I.vendor_objid = V.objid::text
WHERE ${conditions.join(" AND ")}
ORDER BY I.item_name ASC
`;
diff --git a/src/app/api/m/procurements/excel/[id]/route.ts b/src/app/api/m/procurements/excel/[id]/route.ts
new file mode 100644
index 0000000..3e9d408
--- /dev/null
+++ b/src/app/api/m/procurements/excel/[id]/route.ts
@@ -0,0 +1,86 @@
+// 매입 발주서 엑셀 다운로드 — 거래명세표 양식과 별개로 발주서 양식
+import { NextRequest, NextResponse } from "next/server";
+import { queryOne, queryRows } from "@/lib/db";
+import { requireMomoAdmin } from "@/lib/momo-guard";
+import * as XLSX from "xlsx";
+
+export async function GET(_req: NextRequest, ctx: { params: Promise<{ id: string }> }) {
+ const g = await requireMomoAdmin();
+ if (g instanceof NextResponse) return g;
+
+ const { id } = await ctx.params;
+ const proc = await queryOne