Add All BOM Substitutes API and Update BOM Management
- Introduced a new API endpoint to retrieve all substitutes for a given BOM ID, allowing for bulk retrieval of substitute items. - Enhanced the BOM service to support the new functionality, including company code filtering and versioning options. - Updated the BOM management page to integrate the new substitute retrieval feature, enabling users to manage substitutes more effectively during the copy process. - Added necessary state management and UI elements for handling substitutes in the copy modal. (TASK: ERP-028)
This commit is contained in:
@@ -306,6 +306,20 @@ export async function getBomDetailSubstitutes(req: Request, res: Response) {
|
||||
}
|
||||
}
|
||||
|
||||
// bom_id 전체의 대체품 일괄 조회 (복사 모달이 detail_id별 매핑 만들 때 사용)
|
||||
export async function getAllBomSubstitutes(req: Request, res: Response) {
|
||||
try {
|
||||
const { bomId } = req.params;
|
||||
const companyCode = (req as any).user?.companyCode || "*";
|
||||
const versionId = (req.query.versionId as string) || undefined;
|
||||
const data = await bomService.getAllBomSubstitutes(bomId, companyCode, versionId);
|
||||
res.json({ success: true, data });
|
||||
} catch (error: any) {
|
||||
logger.error("BOM 전체 대체품 조회 실패", { error: error.message });
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function getBomSubstituteCounts(req: Request, res: Response) {
|
||||
try {
|
||||
const { bomId } = req.params;
|
||||
|
||||
@@ -35,6 +35,7 @@ router.delete("/:bomId/versions/:versionId", bomController.deleteBomVersion);
|
||||
|
||||
// 대체품 (Substitute)
|
||||
router.get("/:bomId/substitute-counts", bomController.getBomSubstituteCounts);
|
||||
router.get("/:bomId/substitutes-all", bomController.getAllBomSubstitutes);
|
||||
router.get("/details/:detailId/substitutes", bomController.getBomDetailSubstitutes);
|
||||
router.post("/details/:detailId/substitutes", bomController.createBomDetailSubstitute);
|
||||
router.put("/substitutes/:id", bomController.updateBomDetailSubstitute);
|
||||
|
||||
@@ -848,6 +848,40 @@ export async function deleteBomVersion(
|
||||
|
||||
// ─── 대체품 (Substitute) ─────────────────────────────
|
||||
|
||||
/**
|
||||
* bom_id 하나에 속한 모든 detail의 대체품을 일괄 조회.
|
||||
* 반환: detail_id 기준으로 grouping된 substitute 행 배열 (회사 단위 필터).
|
||||
* 복사 모달이 트리 전체의 substitute를 한 번에 미리 받아오기 위해 추가 (TASK:ERP-028 옵션 B).
|
||||
*/
|
||||
export async function getAllBomSubstitutes(bomId: string, companyCode: string, versionId?: string) {
|
||||
const params: any[] = [bomId];
|
||||
let sql = `
|
||||
SELECT s.*, d.bom_id, d.version_id,
|
||||
i.item_name AS substitute_item_name,
|
||||
i.item_number AS substitute_item_number,
|
||||
i.unit AS substitute_unit,
|
||||
i.inventory_unit AS substitute_inventory_unit
|
||||
FROM bom_detail_substitute s
|
||||
JOIN bom_detail d ON d.id = s.bom_detail_id
|
||||
LEFT JOIN item_info i ON s.substitute_item_id = i.id
|
||||
WHERE d.bom_id = $1
|
||||
`;
|
||||
if (companyCode !== "*") {
|
||||
params.push(companyCode);
|
||||
sql += ` AND d.company_code = $${params.length}`;
|
||||
}
|
||||
if (versionId) {
|
||||
params.push(versionId);
|
||||
sql += ` AND d.version_id = $${params.length}`;
|
||||
}
|
||||
sql += `
|
||||
ORDER BY
|
||||
CASE WHEN s.priority ~ '^[0-9]+$' THEN s.priority::int ELSE 9999 END ASC,
|
||||
s.created_date ASC
|
||||
`;
|
||||
return query(sql, params);
|
||||
}
|
||||
|
||||
export async function getBomDetailSubstitutes(detailId: string, companyCode: string) {
|
||||
const sql = `
|
||||
SELECT s.*,
|
||||
@@ -971,6 +1005,15 @@ export interface BomTreeNodeInput {
|
||||
processType?: string | null;
|
||||
lossRate?: number | string | null;
|
||||
remark?: string | null;
|
||||
/** 이 자품목의 대체품 목록 — 복사 시 새 detail_id로 INSERT (TASK:ERP-028 옵션 B) */
|
||||
substitutes?: BomSubstituteInput[];
|
||||
}
|
||||
|
||||
export interface BomSubstituteInput {
|
||||
substituteItemId: string;
|
||||
priority?: string | number | null;
|
||||
ratio?: string | number | null;
|
||||
remark?: string | null;
|
||||
}
|
||||
|
||||
export interface CopyBomToItemsParams {
|
||||
@@ -1153,7 +1196,30 @@ export async function insertVersionAndDetails(
|
||||
params.companyCode,
|
||||
],
|
||||
);
|
||||
tempToNewId[node.tempId] = insertRes.rows[0].id;
|
||||
const newDetailId = insertRes.rows[0].id;
|
||||
tempToNewId[node.tempId] = newDetailId;
|
||||
|
||||
// 대체품 INSERT — 페이로드에 substitutes가 있으면 새 detail_id로 매핑하여 추가 (TASK:ERP-028 옵션 B)
|
||||
if (Array.isArray(node.substitutes) && node.substitutes.length > 0) {
|
||||
for (const s of node.substitutes) {
|
||||
if (!s.substituteItemId) continue;
|
||||
await client.query(
|
||||
`INSERT INTO bom_detail_substitute
|
||||
(bom_detail_id, substitute_item_id, priority, ratio, remark, status, company_code, writer)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
||||
[
|
||||
newDetailId,
|
||||
s.substituteItemId,
|
||||
s.priority != null ? String(s.priority) : null,
|
||||
s.ratio != null ? String(s.ratio) : null,
|
||||
s.remark || null,
|
||||
"active",
|
||||
params.companyCode,
|
||||
params.createdBy,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { versionId: newVersionId, insertedCount: params.sortedTree.length };
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user