diff --git a/src/app/(main)/m/admin/orders/page.tsx b/src/app/(main)/m/admin/orders/page.tsx
index 1febf14..8e55241 100644
--- a/src/app/(main)/m/admin/orders/page.tsx
+++ b/src/app/(main)/m/admin/orders/page.tsx
@@ -1,9 +1,11 @@
"use client";
import { useEffect, useMemo, useState, useCallback, useRef } from "react";
-import { Check, Download, X, RefreshCcw, Truck, AlertCircle, Package } from "lucide-react";
+import { Check, Download, X, RefreshCcw, Truck, AlertCircle, Package, PhoneCall } from "lucide-react";
import Swal from "sweetalert2";
import { captureAndShare } from "@/lib/capture-share";
+import { useRouter } from "next/navigation";
+import { SearchableSelect } from "@/components/ui/searchable-select";
interface Order {
OBJID: string; ORDER_NO: string; ORDER_DATE: string;
@@ -246,13 +248,16 @@ export default function AdminOrdersPage() {
발주를 선택하면 거래명세표가 미리보기로 표시됩니다. 체크박스로 다중 선택 후 [출고]를 누르면 일괄 처리됩니다.
-
+
+
+
+
{/* 검색바 — 모바일에선 한 줄에 핵심만, 데스크탑에선 여유 있게 */}
@@ -935,3 +940,62 @@ function QtyInput({ initial, onSave }: { initial: number; onSave: (q: number) =>
/>
);
}
+
+// 수기 발주 작성 — admin 이 전화 요청 등을 받아 거래처 대신 발주 등록
+function ManualOrderButton() {
+ const router = useRouter();
+ const [open, setOpen] = useState(false);
+ const [customers, setCustomers] = useState<{ USER_ID: string; USER_NAME: string }[]>([]);
+ const [selected, setSelected] = useState("");
+
+ useEffect(() => {
+ if (!open || customers.length > 0) return;
+ fetch("/api/m/customers/list", {
+ method: "POST", headers: { "Content-Type": "application/json" }, body: "{}",
+ })
+ .then((r) => r.json())
+ .then((j) => setCustomers(j.RESULTLIST ?? []))
+ .catch(() => {});
+ }, [open, customers.length]);
+
+ const onProceed = () => {
+ if (!selected) { Swal.fire({ icon: "warning", title: "거래처를 선택하세요." }); return; }
+ setOpen(false);
+ router.push(`/m/orders/new?customerObjid=${encodeURIComponent(selected)}`);
+ };
+
+ return (
+ <>
+
+ {open && (
+ setOpen(false)}>
+
e.stopPropagation()}>
+
수기 발주 — 거래처 선택
+
전화 요청 등 거래처를 대신해 발주를 작성합니다. 선택한 거래처 명의로 발주가 등록되며, 그 거래처의 기준 명세표가 적용됩니다.
+
({ value: c.USER_ID, label: `${c.USER_NAME} (${c.USER_ID})` }))}
+ placeholder="거래처 검색/선택"
+ />
+
+
+
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/app/(main)/m/orders/new/page.tsx b/src/app/(main)/m/orders/new/page.tsx
index d2b652b..37cce5b 100644
--- a/src/app/(main)/m/orders/new/page.tsx
+++ b/src/app/(main)/m/orders/new/page.tsx
@@ -1,8 +1,8 @@
"use client";
-import { useEffect, useState, useMemo, useCallback } from "react";
-import { useRouter } from "next/navigation";
-import { Search, ShoppingCart, Plus, Minus, X, Truck, Package, LayoutGrid, List as ListIcon } from "lucide-react";
+import { useEffect, useState, useMemo, useCallback, Suspense } from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+import { Search, ShoppingCart, Plus, Minus, X, Truck, Package, LayoutGrid, List as ListIcon, PhoneCall } from "lucide-react";
import Swal from "sweetalert2";
interface Item {
@@ -29,8 +29,20 @@ const DEFAULT_DELIVERY_PRICE = 4000;
const fmt = (n: number) => Number(n || 0).toLocaleString("ko-KR");
const newKey = () => Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
-export default function ItemsBrowse() {
+export default function ItemsBrowsePage() {
+ return (
+ 로딩 중...}>
+
+
+ );
+}
+
+function ItemsBrowse() {
const router = useRouter();
+ const params = useSearchParams();
+ // admin 이 수기 발주 작성 시 URL ?customerObjid=momoNNN 로 거래처 명시
+ const onBehalfOfCustomer = params.get("customerObjid") || "";
+ const [onBehalfName, setOnBehalfName] = useState("");
const [items, setItems] = useState- ([]);
const [keyword, setKeyword] = useState("");
const [taxFilter, setTaxFilter] = useState<"" | "Y" | "N">("");
@@ -42,12 +54,26 @@ export default function ItemsBrowse() {
const [viewMode, setViewMode] = useState<"card" | "list">("card");
// 현재 사용자의 발주 한도 우회 권한 (관리자 또는 unlimited_qty='Y' 거래처)
const [unlimitedQty, setUnlimitedQty] = useState(false);
+ const [isAdmin, setIsAdmin] = useState(false);
+
+ // 수기 발주 모드일 때 거래처 이름 표시용 조회
+ useEffect(() => {
+ if (!onBehalfOfCustomer) return;
+ fetch("/api/m/customers/list", { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" })
+ .then((r) => r.json())
+ .then((j) => {
+ const c = (j.RESULTLIST ?? []).find((x: { USER_ID: string; USER_NAME: string }) => x.USER_ID === onBehalfOfCustomer);
+ if (c) setOnBehalfName(c.USER_NAME);
+ })
+ .catch(() => {});
+ }, [onBehalfOfCustomer]);
useEffect(() => {
fetch("/api/auth/me").then((r) => r.json()).then((d) => {
if (d?.user) {
- const isAdmin = d.user.role === "ADMIN" || d.user.isAdmin === true || d.user.userType === "A";
- setUnlimitedQty(isAdmin || !!d.user.unlimitedQty);
+ const adm = d.user.role === "ADMIN" || d.user.isAdmin === true || d.user.userType === "A";
+ setIsAdmin(adm);
+ setUnlimitedQty(adm || !!d.user.unlimitedQty);
}
}).catch(() => {});
}, []);
@@ -237,6 +263,8 @@ export default function ItemsBrowse() {
const res = await fetch("/api/m/orders/save", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({
+ // admin 의 수기 발주 — 선택한 거래처 명의로 저장
+ customerObjid: isAdmin && onBehalfOfCustomer ? onBehalfOfCustomer : undefined,
lines: cart.map((c) => ({ itemObjid: c.item.OBJID, qty: c.qty })),
extras: extras.map((e) => ({
kind: e.kind,
@@ -435,6 +463,15 @@ export default function ItemsBrowse() {
현재 재고가 있는 품목을 선택해 상단 장바구니에 담고 [발주 요청] 버튼으로 전송하세요.
+ {isAdmin && onBehalfOfCustomer && (
+
+
+
수기 발주 — 거래처 {onBehalfName || onBehalfOfCustomer} 명의로 저장됩니다
+
+
+ )}
+
diff --git a/src/app/api/m/orders/save/route.ts b/src/app/api/m/orders/save/route.ts
index df7e7f2..0538e08 100644
--- a/src/app/api/m/orders/save/route.ts
+++ b/src/app/api/m/orders/save/route.ts
@@ -23,19 +23,26 @@ interface InputExtraLine {
export async function POST(req: NextRequest) {
const r = await requireMomoUser();
if (r instanceof NextResponse) return r;
- const customerObjid = r.user.objid || r.user.userId;
- if (!customerObjid) {
- return NextResponse.json({ success: false, message: "사용자 식별자를 확인할 수 없습니다." }, { status: 400 });
- }
+ const isAdmin = r.user.isAdmin === true || r.user.role === "ADMIN" || r.user.userType === "A";
let lines: InputItemLine[];
let extras: InputExtraLine[];
let memo: string | undefined;
+ let customerObjid: string;
try {
- const body = await req.json() as { lines: InputItemLine[]; extras?: InputExtraLine[]; memo?: string };
+ const body = await req.json() as { lines: InputItemLine[]; extras?: InputExtraLine[]; memo?: string; customerObjid?: string };
lines = body.lines;
extras = Array.isArray(body.extras) ? body.extras : [];
memo = body.memo;
+ // admin 만 customerObjid 명시 가능 (수기 발주 작성). USER 는 본인 ID 자동.
+ if (isAdmin && body.customerObjid) {
+ customerObjid = body.customerObjid;
+ } else {
+ customerObjid = r.user.objid || r.user.userId;
+ }
+ if (!customerObjid) {
+ return NextResponse.json({ success: false, message: "사용자 식별자를 확인할 수 없습니다." }, { status: 400 });
+ }
} catch {
return NextResponse.json({ success: false, message: "요청 본문을 해석할 수 없습니다." }, { status: 400 });
}