feat(orders/admin): 입금완료(PAID) 발주도 품목/택배/용차 추가·수정 허용
Deploy momo-erp / deploy (push) Successful in 1m57s
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:
@@ -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 () => {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user