feat: Enhance process and work standard management functionalities
- Updated the processInfoController to allow for flexible use_yn filtering, supporting both "Y" and "N" values along with their corresponding "USE_Y" and "USE_N" mappings. - Modified the processWorkStandardController to include selected_bom_items in work item details, enabling better management of BOM data. - Improved the productionPlanService to handle order summaries more effectively, incorporating legacy and detail data through a unified query structure. - Enhanced the receiving and outbound pages with new filtering and grouping functionalities, improving user experience and data handling. These changes aim to streamline process management and improve overall functionality across various modules.
This commit is contained in:
@@ -40,8 +40,15 @@ export async function getProcessList(req: AuthenticatedRequest, res: Response) {
|
||||
params.push(processType);
|
||||
}
|
||||
if (useYn) {
|
||||
conditions.push(`use_yn = $${idx++}`);
|
||||
params.push(useYn);
|
||||
// "Y" → "USE_Y"도 매칭, "N" → "USE_N"도 매칭
|
||||
const useYnValue = String(useYn);
|
||||
if (useYnValue === "Y" || useYnValue === "N") {
|
||||
conditions.push(`use_yn IN ($${idx++}, $${idx++})`);
|
||||
params.push(useYnValue, `USE_${useYnValue}`);
|
||||
} else {
|
||||
conditions.push(`use_yn = $${idx++}`);
|
||||
params.push(useYnValue);
|
||||
}
|
||||
}
|
||||
|
||||
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
||||
|
||||
@@ -463,7 +463,7 @@ export async function getWorkItemDetails(req: AuthenticatedRequest, res: Respons
|
||||
SELECT id, work_item_id, detail_type, content, is_required, sort_order, remark,
|
||||
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
||||
duration_minutes, input_type, lookup_target, display_fields,
|
||||
created_date
|
||||
selected_bom_items, created_date
|
||||
FROM process_work_item_detail
|
||||
WHERE work_item_id = $1 AND company_code = $2
|
||||
ORDER BY sort_order, created_date
|
||||
@@ -492,6 +492,7 @@ export async function createWorkItemDetail(req: AuthenticatedRequest, res: Respo
|
||||
work_item_id, detail_type, content, is_required, sort_order, remark,
|
||||
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
||||
duration_minutes, input_type, lookup_target, display_fields,
|
||||
selected_bom_items,
|
||||
} = req.body;
|
||||
|
||||
if (!work_item_id || !content) {
|
||||
@@ -514,11 +515,14 @@ export async function createWorkItemDetail(req: AuthenticatedRequest, res: Respo
|
||||
INSERT INTO process_work_item_detail
|
||||
(company_code, work_item_id, detail_type, content, is_required, sort_order, remark, writer,
|
||||
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
||||
duration_minutes, input_type, lookup_target, display_fields)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
|
||||
duration_minutes, input_type, lookup_target, display_fields, selected_bom_items)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
// selected_bom_items: 배열이면 JSON 문자열로 변환
|
||||
const bomItemsJson = Array.isArray(selected_bom_items) ? JSON.stringify(selected_bom_items) : selected_bom_items || null;
|
||||
|
||||
const result = await getPool().query(query, [
|
||||
companyCode,
|
||||
work_item_id,
|
||||
@@ -537,6 +541,7 @@ export async function createWorkItemDetail(req: AuthenticatedRequest, res: Respo
|
||||
input_type || null,
|
||||
lookup_target || null,
|
||||
display_fields || null,
|
||||
bomItemsJson,
|
||||
]);
|
||||
|
||||
logger.info("작업 항목 상세 생성", { companyCode, id: result.rows[0].id });
|
||||
@@ -562,8 +567,11 @@ export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Respo
|
||||
detail_type, content, is_required, sort_order, remark,
|
||||
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
||||
duration_minutes, input_type, lookup_target, display_fields,
|
||||
selected_bom_items,
|
||||
} = req.body;
|
||||
|
||||
const bomItemsJson = Array.isArray(selected_bom_items) ? JSON.stringify(selected_bom_items) : selected_bom_items ?? null;
|
||||
|
||||
const query = `
|
||||
UPDATE process_work_item_detail
|
||||
SET detail_type = COALESCE($1, detail_type),
|
||||
@@ -580,6 +588,7 @@ export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Respo
|
||||
input_type = $14,
|
||||
lookup_target = $15,
|
||||
display_fields = $16,
|
||||
selected_bom_items = $17,
|
||||
updated_date = NOW()
|
||||
WHERE id = $6 AND company_code = $7
|
||||
RETURNING *
|
||||
@@ -602,6 +611,7 @@ export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Respo
|
||||
input_type || null,
|
||||
lookup_target || null,
|
||||
display_fields || null,
|
||||
bomItemsJson,
|
||||
]);
|
||||
|
||||
if (result.rowCount === 0) {
|
||||
@@ -889,7 +899,22 @@ export async function registerItemsBatch(req: AuthenticatedRequest, res: Respons
|
||||
RETURNING *`,
|
||||
[screenCode, item.itemId, item.itemCode || null, companyCode, req.user?.userId || null]
|
||||
);
|
||||
if (result.rows[0]) inserted.push(result.rows[0]);
|
||||
if (result.rows[0]) {
|
||||
inserted.push(result.rows[0]);
|
||||
// 기본 라우팅 버전이 없으면 자동 생성
|
||||
const itemCode = item.itemCode || item.itemId;
|
||||
const existingVersion = await client.query(
|
||||
`SELECT id FROM item_routing_version WHERE item_code = $1 AND company_code = $2 LIMIT 1`,
|
||||
[itemCode, companyCode]
|
||||
);
|
||||
if (existingVersion.rowCount === 0) {
|
||||
await client.query(
|
||||
`INSERT INTO item_routing_version (id, company_code, item_code, version_name, description, is_default, writer)
|
||||
VALUES (gen_random_uuid()::text, $1, $2, '기본', '자동 생성된 기본 라우팅', true, $3)`,
|
||||
[companyCode, itemCode, req.user?.userId || null]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await client.query("COMMIT");
|
||||
|
||||
@@ -63,18 +63,52 @@ export async function getOrderSummary(
|
||||
),`;
|
||||
|
||||
const query = `
|
||||
WITH order_summary AS (
|
||||
WITH all_orders AS (
|
||||
-- 레거시: sales_order_mng에 part_code가 직접 있는 경우
|
||||
SELECT
|
||||
so.part_code AS item_code,
|
||||
COALESCE(so.part_name, so.part_code) AS item_name,
|
||||
SUM(COALESCE(so.order_qty::numeric, 0)) AS total_order_qty,
|
||||
SUM(COALESCE(so.ship_qty::numeric, 0)) AS total_ship_qty,
|
||||
SUM(COALESCE(so.balance_qty::numeric, 0)) AS total_balance_qty,
|
||||
COUNT(*) AS order_count,
|
||||
MIN(so.due_date) AS earliest_due_date
|
||||
so.part_code,
|
||||
so.part_name,
|
||||
so.company_code,
|
||||
COALESCE(so.order_qty::numeric, 0) AS order_qty,
|
||||
COALESCE(so.ship_qty::numeric, 0) AS ship_qty,
|
||||
COALESCE(so.balance_qty::numeric, 0) AS balance_qty,
|
||||
so.due_date
|
||||
FROM sales_order_mng so
|
||||
WHERE ${whereClause}
|
||||
GROUP BY so.part_code, so.part_name
|
||||
AND so.part_code IS NOT NULL AND so.part_code != ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sales_order_detail sd
|
||||
WHERE sd.order_no = so.order_no AND sd.company_code = so.company_code
|
||||
)
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- 마스터-디테일: sales_order_detail에 품목이 있는 경우
|
||||
SELECT
|
||||
sd.part_code,
|
||||
sd.part_name,
|
||||
sd.company_code,
|
||||
COALESCE(sd.qty::numeric, 0) AS order_qty,
|
||||
COALESCE(sd.ship_qty::numeric, 0) AS ship_qty,
|
||||
COALESCE(sd.balance_qty::numeric, sd.qty::numeric - COALESCE(sd.ship_qty::numeric, 0), 0) AS balance_qty,
|
||||
sd.due_date::date
|
||||
FROM sales_order_detail sd
|
||||
INNER JOIN sales_order_mng so ON sd.order_no = so.order_no AND sd.company_code = so.company_code
|
||||
WHERE sd.company_code = $1
|
||||
AND sd.part_code IS NOT NULL AND sd.part_code != ''
|
||||
),
|
||||
order_summary AS (
|
||||
SELECT
|
||||
ao.part_code AS item_code,
|
||||
COALESCE(NULLIF(ao.part_name, ''), ii.item_name, ao.part_code) AS item_name,
|
||||
SUM(ao.order_qty) AS total_order_qty,
|
||||
SUM(ao.ship_qty) AS total_ship_qty,
|
||||
SUM(ao.balance_qty) AS total_balance_qty,
|
||||
COUNT(*) AS order_count,
|
||||
MIN(ao.due_date) AS earliest_due_date
|
||||
FROM all_orders ao
|
||||
LEFT JOIN item_info ii ON ao.part_code = ii.item_number AND ao.company_code = ii.company_code
|
||||
GROUP BY ao.part_code, COALESCE(NULLIF(ao.part_name, ''), ii.item_name, ao.part_code)
|
||||
),
|
||||
${itemLeadTimeCte}
|
||||
stock_info AS (
|
||||
@@ -125,17 +159,34 @@ export async function getOrderSummary(
|
||||
|
||||
const result = await pool.query(query, params);
|
||||
|
||||
// 그룹별 상세 수주 데이터도 함께 조회
|
||||
// 그룹별 상세 수주 데이터도 함께 조회 (레거시 + 디테일 UNION)
|
||||
const detailWhere = conditions.map(c => c.replace(/so\./g, "")).join(" AND ");
|
||||
const detailQuery = `
|
||||
SELECT
|
||||
id, order_no, part_code, part_name,
|
||||
SELECT id::text, order_no, part_code, part_name,
|
||||
COALESCE(order_qty::numeric, 0) AS order_qty,
|
||||
COALESCE(ship_qty::numeric, 0) AS ship_qty,
|
||||
COALESCE(balance_qty::numeric, 0) AS balance_qty,
|
||||
due_date, status, partner_id, manager_name
|
||||
FROM sales_order_mng
|
||||
WHERE ${detailWhere}
|
||||
AND part_code IS NOT NULL AND part_code != ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sales_order_detail sd
|
||||
WHERE sd.order_no = sales_order_mng.order_no AND sd.company_code = sales_order_mng.company_code
|
||||
)
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT sd.id::text, sd.order_no, sd.part_code, sd.part_name,
|
||||
COALESCE(sd.qty::numeric, 0) AS order_qty,
|
||||
COALESCE(sd.ship_qty::numeric, 0) AS ship_qty,
|
||||
COALESCE(sd.balance_qty::numeric, COALESCE(sd.qty::numeric, 0) - COALESCE(sd.ship_qty::numeric, 0), 0) AS balance_qty,
|
||||
sd.due_date::date, so.status, so.partner_id, so.manager_name
|
||||
FROM sales_order_detail sd
|
||||
INNER JOIN sales_order_mng so ON sd.order_no = so.order_no AND sd.company_code = so.company_code
|
||||
WHERE sd.company_code = $1
|
||||
AND sd.part_code IS NOT NULL AND sd.part_code != ''
|
||||
|
||||
ORDER BY part_code, due_date;
|
||||
`;
|
||||
const detailResult = await pool.query(detailQuery, params);
|
||||
|
||||
Reference in New Issue
Block a user