feat(procurements): 입고완료 이후 수정 잠금 + 공급업체 SearchableSelect
Deploy momo-erp / deploy (push) Successful in 2m54s

- 매입 발주서 editable 재조정: OPEN/REQUESTED 만 수정 가능. RECEIVED/
  PARTIAL/PAID/CANCELLED 는 수정 불가. lines/save API 가드도 동일.
- 공급업체 select 를 SearchableSelect 로 교체 (typeahead):
  · 매입 발주서 관리 페이지의 발주서 폼 (page.tsx)
  · 매입 발주 신규 작성 페이지 (new/page.tsx)

운영 DB 의 매입 발주/입고/관련 stock_moves 데이터는 직접 모두 삭제했음
(사용자 명시 요청). UI 에 깨끗한 상태로 보임.
This commit is contained in:
chpark
2026-05-14 00:08:27 +09:00
parent d86a1154a9
commit 4661981da5
3 changed files with 23 additions and 15 deletions
@@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { Trash2, Plus } from "lucide-react";
import Swal from "sweetalert2";
import { SearchableSelect } from "@/components/ui/searchable-select";
interface Vendor { OBJID: string; VENDOR_NAME: string }
interface Item { OBJID: string; ITEM_CODE: string; ITEM_NAME: string; COST_PRICE: number }
@@ -58,10 +59,14 @@ export default function NewProcPage() {
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-xs font-semibold text-slate-600"> *</label>
<select value={vendorObjid} onChange={(e) => setVendorObjid(e.target.value)} className="w-full h-10 px-3 rounded-lg border border-slate-200 mt-1">
<option value=""></option>
{vendors.map((v) => <option key={v.OBJID} value={v.OBJID}>{v.VENDOR_NAME}</option>)}
</select>
<div className="mt-1">
<SearchableSelect
value={vendorObjid}
onChange={setVendorObjid}
options={vendors.map((v) => ({ value: v.OBJID, label: v.VENDOR_NAME }))}
placeholder="공급업체 검색/선택"
/>
</div>
</div>
<div>
<label className="text-xs font-semibold text-slate-600"></label>
+11 -8
View File
@@ -352,8 +352,9 @@ function ProcurementForm({ detail, vendors, onSetVendor, onSetMemo, onSetTerm, o
onUpdateLine: (line: { objid?: string; itemObjid?: string; qty: number; costPrice: number }) => void;
onDeleteLine: (objid: string) => void;
}) {
// 입금완료(PAID)/취소(CANCELLED) 전까지 수정 허용 — 발주요청/입고완료/부분입고 도 수량 수정 가능
const editable = !["PAID", "CANCELLED"].includes(detail.proc.STATUS);
// OPEN(작성중) / REQUESTED(발주요청) 만 수정 가능.
// RECEIVED(입고완료) / PARTIAL(부분입고) / PAID(입금완료) / CANCELLED 는 수정 불가.
const editable = ["OPEN", "REQUESTED"].includes(detail.proc.STATUS);
const formRef = useRef<HTMLDivElement>(null);
const handleCapture = async () => {
@@ -419,12 +420,14 @@ function ProcurementForm({ detail, vendors, onSetVendor, onSetMemo, onSetTerm, o
<th className="border border-slate-400 bg-slate-100 px-2 py-1 text-center"></th>
<td className="border border-slate-400 px-3 py-1">
{editable ? (
<select value={detail.proc.VENDOR_OBJID ?? ""}
onChange={(e) => onSetVendor(e.target.value)}
className="h-7 px-2 rounded border border-slate-300 text-[11px] bg-white">
<option value="">-- --</option>
{vendors.map((v) => <option key={v.OBJID} value={v.OBJID}>{v.VENDOR_NAME}</option>)}
</select>
<div className="max-w-[280px]">
<SearchableSelect
value={detail.proc.VENDOR_OBJID ?? ""}
onChange={onSetVendor}
options={vendors.map((v) => ({ value: v.OBJID, label: v.VENDOR_NAME }))}
placeholder="공급업체 검색/선택"
/>
</div>
) : (
<span className="font-semibold">{detail.proc.VENDOR_NAME ?? "-"}</span>
)}
@@ -34,10 +34,10 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ success: false, message: "발주서를 찾을 수 없습니다." }, { status: 404 });
}
const status = procRes.rows[0].status;
// 입금완료(PAID)/취소(CANCELLED) 외 모든 상태에서 수정 허용
if (status === "PAID" || status === "CANCELLED") {
// OPEN/REQUESTED 만 허용. 입고완료/부분입고/입금완료/취소는 차단.
if (status !== "OPEN" && status !== "REQUESTED") {
await client.query("ROLLBACK");
return NextResponse.json({ success: false, message: "입완료/취소 상태는 수정할 수 없습니다." }, { status: 400 });
return NextResponse.json({ success: false, message: "입완료 이후 상태는 수정할 수 없습니다." }, { status: 400 });
}
for (const ln of lines) {