feat(orders/admin): 입금완료(PAID) 발주도 품목/택배/용차 추가·수정 허용
Deploy momo-erp / deploy (push) Successful in 1m57s

요구: 출고처리(/m/admin/orders) 에서 입금완료 후에도 admin 이 품목과
      택배/용차 라인을 추가/수정할 수 있어야 함. 계산서 발행 전 일괄 정정 케이스.

변경:
- /api/m/orders/items/add: admin 분기 신설 → REQUESTED/APPROVED/PAID 허용
  (USER 는 기존대로 REQUESTED/APPROVED 까지)
- /api/m/orders/items/update: admin 분기에 PAID 포함
- items/update 의 재고 ± 동기화 분기에 PAID 도 포함 — APPROVED 와 동일하게
  momo_stock_moves 의 OUT 이력으로 wh_objid 찾아 차이만큼 재고 조정
- /m/admin/orders StatementPreview: editable 에 PAID 도 true 처리
  → [+ 품목 추가] / 택배·용차 / 수량 입력칸 노출

INVOICED(계산서 발행) / CANCELLED 는 여전히 잠금 — 한 번 더 단계 진행하면
정정 불가.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
chpark
2026-05-21 10:29:31 +09:00
parent e1618fa9d2
commit 474cf79632
3 changed files with 21 additions and 12 deletions
+3 -3
View File
@@ -687,9 +687,9 @@ function StatementPreview({
const statementRef = useRef<HTMLDivElement>(null); const statementRef = useRef<HTMLDivElement>(null);
const lowStock = items.filter((it) => it.KIND === "ITEM" && Number(it.STOCK_QTY) < Number(it.QTY)); const lowStock = items.filter((it) => it.KIND === "ITEM" && Number(it.STOCK_QTY) < Number(it.QTY));
// 입금전 단계만 수정 가능 — REQUESTED(출고요청) / APPROVED(출고완료). // admin(출고관리) 은 입금완료(PAID) 까지 품목/택배/용차 추가 가능.
// 계산서발행(INVOICED) / 입금완료(PAID) / 취소(CANCELED) 는 수정 불가. // 계산서발행(INVOICED) / 취소(CANCELLED) 는 수정 불가.
const editable = order.STATUS === "REQUESTED" || order.STATUS === "APPROVED"; const editable = order.STATUS === "REQUESTED" || order.STATUS === "APPROVED" || order.STATUS === "PAID";
// 거래명세표를 이미지로 캡처 → 공유 또는 다운로드 // 거래명세표를 이미지로 캡처 → 공유 또는 다운로드
const handleCapture = async () => { const handleCapture = async () => {
+9
View File
@@ -57,10 +57,19 @@ export async function POST(req: NextRequest) {
await client.query("ROLLBACK"); await client.query("ROLLBACK");
return NextResponse.json({ success: false, message: "권한이 없습니다." }, { status: 403 }); return NextResponse.json({ success: false, message: "권한이 없습니다." }, { status: 403 });
} }
// admin: 입금완료(PAID) 까지 품목 추가 허용. USER: 출고완료(APPROVED) 까지만.
// 계산서발행(INVOICED) / 취소(CANCELLED) 이후는 누구도 추가 불가.
if (isAdmin) {
if (!["REQUESTED", "APPROVED", "PAID"].includes(order.status)) {
await client.query("ROLLBACK");
return NextResponse.json({ success: false, message: "계산서발행 이후 발주는 품목 추가 불가." }, { status: 400 });
}
} else {
if (order.status !== "REQUESTED" && order.status !== "APPROVED") { if (order.status !== "REQUESTED" && order.status !== "APPROVED") {
await client.query("ROLLBACK"); await client.query("ROLLBACK");
return NextResponse.json({ success: false, message: "입금완료 이후 발주는 품목 추가 불가." }, { status: 400 }); return NextResponse.json({ success: false, message: "입금완료 이후 발주는 품목 추가 불가." }, { status: 400 });
} }
}
// 편집 락 체크 // 편집 락 체크
const myId = String(r.user.objid || r.user.userId); const myId = String(r.user.objid || r.user.userId);
if (order.lock_alive && order.editing_by && order.editing_by !== myId) { if (order.lock_alive && order.editing_by && order.editing_by !== myId) {
+6 -6
View File
@@ -72,12 +72,12 @@ export async function POST(req: NextRequest) {
message: `${name} 담당자가 수정 중입니다. 잠시 후 다시 시도하세요.`, message: `${name} 담당자가 수정 중입니다. 잠시 후 다시 시도하세요.`,
}, { status: 409 }); }, { status: 409 });
} }
// USER: REQUESTED 만. ADMIN: 입금 전 단계만 = REQUESTED/APPROVED. // ADMIN: REQUESTED/APPROVED/PAID 까지 수정 허용. (INVOICED, CANCELLED 는 잠금)
// INVOICED(계산서발행)/PAID(입금완료)/CANCELED 는 수정 불가. // USER: REQUESTED/APPROVED 까지 (입금완료 후 본인은 변경 불가).
if (isAdmin) { if (isAdmin) {
if (order.status !== "REQUESTED" && order.status !== "APPROVED") { if (!["REQUESTED", "APPROVED", "PAID"].includes(order.status)) {
await client.query("ROLLBACK"); await client.query("ROLLBACK");
return NextResponse.json({ success: false, message: "계산서발행 또는 입금완료 이후 발주는 수정할 수 없습니다." }, { status: 400 }); return NextResponse.json({ success: false, message: "계산서발행 이후 발주는 수정할 수 없습니다." }, { status: 400 });
} }
} else { } else {
// USER: 본인 발주의 REQUESTED + APPROVED (입금 완료 전) 모두 수량 수정 가능 // USER: 본인 발주의 REQUESTED + APPROVED (입금 완료 전) 모두 수량 수정 가능
@@ -148,9 +148,9 @@ export async function POST(req: NextRequest) {
const isFree = cur.is_tax_free === "Y"; const isFree = cur.is_tax_free === "Y";
const calc = calcLine({ unitPrice: Number(cur.unit_price), qty: newQty, isTaxFree: isFree }); const calc = calcLine({ unitPrice: Number(cur.unit_price), qty: newQty, isTaxFree: isFree });
// 출고완료(APPROVED) 상태에서 수량 변경 시 재고 ± 동기화 + stock_moves 이력 // 출고완료(APPROVED) / 입금완료(PAID) 상태에서 수량 변경 시 재고 ± 동기화 + stock_moves 이력
// ITEM 종류만 (택배/용차는 재고 미차감이라 skip — 위 cur.kind 검사로 ITEM 확정됨) // ITEM 종류만 (택배/용차는 재고 미차감이라 skip — 위 cur.kind 검사로 ITEM 확정됨)
if (order.status === "APPROVED") { if (order.status === "APPROVED" || order.status === "PAID") {
const oldQty = Number(cur.qty); const oldQty = Number(cur.qty);
const diff = newQty - oldQty; // +면 추가 차감, -면 복원 const diff = newQty - oldQty; // +면 추가 차감, -면 복원
if (diff !== 0) { if (diff !== 0) {