Enhance Shipping Order and Plan Functionality
- Updated the shipping order controller to improve customer name retrieval by removing unnecessary partner_id fallback. - Implemented shipment plan number allocation logic in the shipping plan controller, ensuring unique numbering based on defined rules or fallback mechanisms. - Enhanced the batch save functionality to include the new shipment plan number in the database insertions. - Added new state management for production and shipment plans in the Cutting Plan page, allowing for better organization and retrieval of related data. - Introduced delivery location field in the sales order page, improving data entry for shipping details. (TASK: ERP-XXX)
This commit is contained in:
@@ -58,7 +58,7 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
|
||||
const query = `
|
||||
SELECT
|
||||
si.*,
|
||||
COALESCE(c.customer_name, si.partner_id, '') AS customer_name,
|
||||
COALESCE(c.customer_name, '') AS customer_name,
|
||||
COALESCE(
|
||||
json_agg(
|
||||
json_build_object(
|
||||
|
||||
@@ -11,6 +11,32 @@ import { Response } from "express";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import { getPool } from "../database/db";
|
||||
import { logger } from "../utils/logger";
|
||||
import { numberingRuleService } from "../services/numberingRuleService";
|
||||
|
||||
// shipment_plan_no 채번 — 채번규칙 우선, 없으면 SP-YYYYMMDD-NNN fallback
|
||||
async function allocateShipmentPlanNo(
|
||||
client: any,
|
||||
companyCode: string,
|
||||
planDate: string | null
|
||||
): Promise<string> {
|
||||
try {
|
||||
const rule = await numberingRuleService.getNumberingRuleByColumn(
|
||||
companyCode, "shipment_plan", "shipment_plan_no"
|
||||
);
|
||||
if (rule) {
|
||||
return await numberingRuleService.allocateCode(
|
||||
rule.ruleId, companyCode, { plan_date: planDate }
|
||||
);
|
||||
}
|
||||
} catch { /* 채번규칙 조회 실패 시 fallback */ }
|
||||
const today = new Date().toISOString().split("T")[0].replace(/-/g, "");
|
||||
const seqRes = await client.query(
|
||||
`SELECT COUNT(*) + 1 AS seq FROM shipment_plan WHERE company_code = $1 AND shipment_plan_no LIKE $2`,
|
||||
[companyCode, `SP-${today}-%`]
|
||||
);
|
||||
const seq = String(seqRes.rows[0].seq).padStart(3, "0");
|
||||
return `SP-${today}-${seq}`;
|
||||
}
|
||||
|
||||
// UUID 포맷 감지 (하이픈 포함 36자)
|
||||
const isUUID = (val: string) =>
|
||||
@@ -95,7 +121,8 @@ async function getNormalizedOrders(
|
||||
dueDate: r.due_date || "",
|
||||
orderQty: Number(r.order_qty || 0),
|
||||
shipQty: Number(r.ship_qty || 0),
|
||||
balanceQty: Number(r.balance_qty || 0),
|
||||
// balance_qty가 NULL/0이면 orderQty - shipQty fallback (수주 등록 시 채워지지 않은 데이터 보정)
|
||||
balanceQty: Number(r.balance_qty) || (Number(r.order_qty || 0) - Number(r.ship_qty || 0)),
|
||||
}));
|
||||
} else {
|
||||
// 마스터 기준 → 거래처 JOIN
|
||||
@@ -139,7 +166,8 @@ async function getNormalizedOrders(
|
||||
dueDate: r.due_date || "",
|
||||
orderQty: Number(r.order_qty || 0),
|
||||
shipQty: Number(r.ship_qty || 0),
|
||||
balanceQty: Number(r.balance_qty || 0),
|
||||
// balance_qty가 NULL/0이면 orderQty - shipQty fallback (수주 등록 시 채워지지 않은 데이터 보정)
|
||||
balanceQty: Number(r.balance_qty) || (Number(r.order_qty || 0) - Number(r.ship_qty || 0)),
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -451,10 +479,11 @@ export async function getAggregate(req: AuthenticatedRequest, res: Response) {
|
||||
.json({ success: false, message: "해당 수주를 찾을 수 없습니다" });
|
||||
}
|
||||
|
||||
// 2) 품목별 그룹핑
|
||||
// 2) 품목별 그룹핑 — part_code가 비어있으면 detail/master ID 단위로 분리해
|
||||
// 품번 없는 직접 입력 품목들이 한 그룹으로 병합되지 않도록 한다
|
||||
const partCodeMap = new Map<string, NormalizedOrder[]>();
|
||||
for (const order of orders) {
|
||||
const key = order.partCode || "UNKNOWN";
|
||||
const key = order.partCode || `__no_part__${order.detailId || order.masterId || Math.random()}`;
|
||||
if (!partCodeMap.has(key)) partCodeMap.set(key, []);
|
||||
partCodeMap.get(key)!.push(order);
|
||||
}
|
||||
@@ -637,12 +666,13 @@ export async function batchSave(req: AuthenticatedRequest, res: Response) {
|
||||
);
|
||||
}
|
||||
|
||||
const planNo = await allocateShipmentPlanNo(client, companyCode, planDateValue);
|
||||
const insertRes = await client.query(
|
||||
`INSERT INTO shipment_plan
|
||||
(company_code, detail_id, sales_order_id, plan_qty, plan_date, status, created_by)
|
||||
VALUES ($1, $2, $3, $4, COALESCE($5::date, CURRENT_DATE), 'READY', $6)
|
||||
(company_code, shipment_plan_no, detail_id, sales_order_id, plan_qty, plan_date, status, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5, COALESCE($6::date, CURRENT_DATE), 'READY', $7)
|
||||
RETURNING *`,
|
||||
[companyCode, sourceId, detail.master_id, planQty, planDateValue, userId]
|
||||
[companyCode, planNo, sourceId, detail.master_id, planQty, planDateValue, userId]
|
||||
);
|
||||
savedPlans.push(insertRes.rows[0]);
|
||||
|
||||
@@ -679,12 +709,13 @@ export async function batchSave(req: AuthenticatedRequest, res: Response) {
|
||||
);
|
||||
}
|
||||
|
||||
const planNo = await allocateShipmentPlanNo(client, companyCode, planDateValue);
|
||||
const insertRes = await client.query(
|
||||
`INSERT INTO shipment_plan
|
||||
(company_code, sales_order_id, plan_qty, plan_date, status, created_by)
|
||||
VALUES ($1, $2, $3, COALESCE($4::date, CURRENT_DATE), 'READY', $5)
|
||||
(company_code, shipment_plan_no, sales_order_id, plan_qty, plan_date, status, created_by)
|
||||
VALUES ($1, $2, $3, $4, COALESCE($5::date, CURRENT_DATE), 'READY', $6)
|
||||
RETURNING *`,
|
||||
[companyCode, masterId, planQty, planDateValue, userId]
|
||||
[companyCode, planNo, masterId, planQty, planDateValue, userId]
|
||||
);
|
||||
savedPlans.push(insertRes.rows[0]);
|
||||
|
||||
|
||||
@@ -30,11 +30,13 @@ export async function getMaterials(companyCode: string, cutType: string) {
|
||||
GROUP BY item_code
|
||||
) inv ON inv.item_code = ii.item_number
|
||||
WHERE ii.company_code = $1
|
||||
-- division(관리품목) 컬럼에 '원자재' 또는 '구매관리' 라벨이 포함된 품목 매칭
|
||||
-- (구매관리 품목도 원판으로 취급하라는 사용자 요청, "구매관리,원자재" 다중 등록도 자동 매칭)
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM category_values cv
|
||||
WHERE cv.table_name = 'item_info'
|
||||
AND cv.column_name = 'division'
|
||||
AND cv.value_label = '원자재'
|
||||
AND cv.value_label IN ('원자재', '구매관리')
|
||||
AND cv.is_active = true
|
||||
AND (cv.company_code = $1 OR cv.company_code IS NULL OR cv.company_code = '')
|
||||
AND cv.value_code = ANY(string_to_array(REPLACE(ii.division, ' ', ''), ','))
|
||||
|
||||
Reference in New Issue
Block a user