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:
@@ -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) 자동 채움
|
||||
// — 사급자재 체크 시 해당 공정의 자재 목록을 외주발주 자재 형식으로 반환
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user