Add preview functionality for outsource purchase order numbers

- Introduced a new endpoint `/preview-order-no` to generate order numbers based on registered numbering rules without incrementing the sequence.
- Implemented the `previewOrderNo` function in the `outsourcePurchaseController` to handle the logic for generating the preview order number.
- Updated the `outsourcePurchaseRoutes` to include the new route for previewing order numbers.
- Enhanced the `RegistrationModal` component to automatically fill the order number input with the preview value when the modal opens.
- Added a new API function `previewOrderNo` in the frontend to call the backend endpoint.

(TASK:ERP-019)
This commit is contained in:
kjs
2026-05-06 18:33:23 +09:00
parent 970a8f708a
commit fa0a28df42
5 changed files with 251 additions and 8 deletions

View File

@@ -182,6 +182,22 @@ export async function autoProcesses(
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 외주발주번호 미리보기 — 모달 진입 시 input 자동 채움용 (시퀀스 증가 없음)
// ─────────────────────────────────────────────────────────────────────────────
export async function previewOrderNo(
req: AuthenticatedRequest,
res: Response,
) {
try {
const companyCode = req.user!.companyCode;
const code = await svc.previewOrderNo(companyCode);
return ok(res, { generatedCode: code || "" });
} catch (e: any) {
return fail(res, 500, e?.message || "외주발주번호 미리보기 실패", e);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 공정 자재투입(material_input) 자동 채움
// — 사급자재 체크 시 해당 공정의 자재 목록을 외주발주 자재 형식으로 반환

View File

@@ -14,6 +14,9 @@ router.use(authenticateToken);
// 헬퍼 (정적 경로 — :id 라우트보다 위에)
router.get("/auto-processes", ctrl.autoProcesses);
// 외주발주번호 미리보기 (시퀀스 증가 없음)
router.get("/preview-order-no", ctrl.previewOrderNo);
// 공정 자재투입 자동 채움 (사급자재 체크 시)
router.get("/process-materials", ctrl.getProcessMaterials);

View File

@@ -11,6 +11,7 @@
import { PoolClient } from "pg";
import { getPool, transaction } from "../database/db";
import { logger } from "../utils/logger";
import { numberingRuleService } from "./numberingRuleService";
// ─────────────────────────────────────────────────────────────────────────────
// 타입 정의
@@ -67,9 +68,51 @@ export interface ListFilter {
}
// ─────────────────────────────────────────────────────────────────────────────
// 채번 (회사+년도 단위) — OPO-YYYY-NNN
// 채번 — 우선순위:
// 1. table_type_columns 매핑된 numbering_rules.allocateCode (관리자 옵션설정 화면에서 등록)
// 2. 폴백: nextOrderNoFallback (OPO-YYYY-NNN, 회사+년도 단위)
// ─────────────────────────────────────────────────────────────────────────────
async function nextOrderNo(client: PoolClient, companyCode: string): Promise<string> {
/** 등록된 채번 규칙 조회 — table_type_columns.detail_settings.numberingRuleId */
async function findOrderNoRuleId(
client: { query: (q: string, p?: any[]) => Promise<any> },
companyCode: string,
): Promise<string | null> {
const r = await client.query(
`SELECT detail_settings
FROM table_type_columns
WHERE company_code = $1
AND table_name = 'outsource_purchase_order'
AND column_name = 'order_no'
AND input_type = 'numbering'
LIMIT 1`,
[companyCode],
);
const raw = r.rows[0]?.detail_settings;
if (!raw) return null;
try {
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
return parsed?.numberingRuleId || null;
} catch {
return null;
}
}
/** 미리보기 — 시퀀스 증가 없음. 모달 진입 시 input 자동 채움용 */
export async function previewOrderNo(companyCode: string): Promise<string | null> {
const pool = getPool();
const ruleId = await findOrderNoRuleId(pool, companyCode);
if (!ruleId) return null;
try {
return await numberingRuleService.previewCode(ruleId, companyCode);
} catch (e: any) {
logger.warn("외주발주 채번 미리보기 실패 — 폴백 사용", { error: e?.message, companyCode });
return null;
}
}
/** 폴백 채번 (회사+년도 단위) — OPO-YYYY-NNN */
async function nextOrderNoFallback(client: PoolClient, companyCode: string): Promise<string> {
const yyyy = new Date().getFullYear();
const prefix = `OPO-${yyyy}-`;
@@ -233,7 +276,21 @@ export async function createOrder(
payload: OPOInput
) {
return transaction(async (client) => {
const orderNo = payload.order_no || (await nextOrderNo(client, companyCode));
// 채번: 사용자 입력값 우선 → 등록된 채번 규칙 allocate → 폴백 nextOrderNoFallback
let orderNo = (payload.order_no || "").trim();
if (!orderNo) {
const ruleId = await findOrderNoRuleId(client, companyCode);
if (ruleId) {
try {
orderNo = await numberingRuleService.allocateCode(ruleId, companyCode);
} catch (e: any) {
logger.warn("외주발주 채번 allocate 실패 — 폴백 사용", { error: e?.message, ruleId, companyCode });
}
}
if (!orderNo) {
orderNo = await nextOrderNoFallback(client, companyCode);
}
}
// 마스터 INSERT
const m = await client.query(