ERP-node/backend-node/src/services/YardLayoutService.ts

398 lines
11 KiB
TypeScript

import { getPool } from "../database/db";
export class YardLayoutService {
// 모든 야드 레이아웃 목록 조회
async getAllLayouts() {
const query = `
SELECT
yl.id,
yl.name,
yl.description,
yl.created_by,
yl.created_at,
yl.updated_at,
COUNT(ymp.id) as placement_count
FROM yard_layout yl
LEFT JOIN yard_material_placement ymp ON yl.id = ymp.yard_layout_id
GROUP BY yl.id
ORDER BY yl.updated_at DESC
`;
const pool = getPool();
const result = await pool.query(query);
return result.rows;
}
// 특정 야드 레이아웃 상세 조회
async getLayoutById(id: number) {
const query = `
SELECT
id,
name,
description,
created_by,
created_at,
updated_at
FROM yard_layout
WHERE id = $1
`;
const pool = getPool();
const result = await pool.query(query, [id]);
return result.rows[0] || null;
}
// 새 야드 레이아웃 생성
async createLayout(data: {
name: string;
description?: string;
created_by?: string;
}) {
const query = `
INSERT INTO yard_layout (name, description, created_by)
VALUES ($1, $2, $3)
RETURNING *
`;
const pool = getPool();
const result = await pool.query(query, [
data.name,
data.description || null,
data.created_by || null,
]);
return result.rows[0];
}
// 야드 레이아웃 수정 (이름, 설명만)
async updateLayout(
id: number,
data: { name?: string; description?: string }
) {
const query = `
UPDATE yard_layout
SET
name = COALESCE($1, name),
description = COALESCE($2, description),
updated_at = CURRENT_TIMESTAMP
WHERE id = $3
RETURNING *
`;
const pool = getPool();
const result = await pool.query(query, [
data.name || null,
data.description || null,
id,
]);
return result.rows[0] || null;
}
// 야드 레이아웃 삭제
async deleteLayout(id: number) {
const query = `DELETE FROM yard_layout WHERE id = $1 RETURNING *`;
const pool = getPool();
const result = await pool.query(query, [id]);
return result.rows[0] || null;
}
// 특정 야드의 모든 배치 자재 조회
async getPlacementsByLayoutId(layoutId: number) {
const query = `
SELECT
id,
yard_layout_id,
material_code,
material_name,
quantity,
unit,
position_x,
position_y,
position_z,
size_x,
size_y,
size_z,
color,
data_source_type,
data_source_config,
data_binding,
memo,
created_at,
updated_at
FROM yard_material_placement
WHERE yard_layout_id = $1
ORDER BY created_at ASC
`;
const pool = getPool();
const result = await pool.query(query, [layoutId]);
return result.rows;
}
// 야드에 자재 배치 추가 (빈 요소 또는 설정된 요소)
async addMaterialPlacement(layoutId: number, data: any) {
const query = `
INSERT INTO yard_material_placement (
yard_layout_id,
material_code,
material_name,
quantity,
unit,
position_x,
position_y,
position_z,
size_x,
size_y,
size_z,
color,
data_source_type,
data_source_config,
data_binding,
memo
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
RETURNING *
`;
const pool = getPool();
// NaN 방지를 위한 안전한 변환 함수
const safeParseInt = (
value: any,
defaultValue: number | null = null
): number | null => {
if (!value && value !== 0) return defaultValue;
const parsed = parseInt(String(value), 10);
return isNaN(parsed) ? defaultValue : parsed;
};
const safeParseFloat = (value: any, defaultValue: number): number => {
if (!value && value !== 0) return defaultValue;
const parsed = parseFloat(String(value));
return isNaN(parsed) ? defaultValue : parsed;
};
const result = await pool.query(query, [
layoutId,
data.material_code || null,
data.material_name || null,
safeParseInt(data.quantity, null),
data.unit || null,
safeParseFloat(data.position_x, 0),
safeParseFloat(data.position_y, 0),
safeParseFloat(data.position_z, 0),
safeParseFloat(data.size_x, 5),
safeParseFloat(data.size_y, 5),
safeParseFloat(data.size_z, 5),
data.color || "#9ca3af", // 미설정 시 회색
data.data_source_type || null,
data.data_source_config ? JSON.stringify(data.data_source_config) : null,
data.data_binding ? JSON.stringify(data.data_binding) : null,
data.memo || null,
]);
return result.rows[0];
}
// 배치 정보 수정 (위치, 크기, 색상, 데이터 바인딩 등)
async updatePlacement(placementId: number, data: any) {
const query = `
UPDATE yard_material_placement
SET
material_code = COALESCE($1, material_code),
material_name = COALESCE($2, material_name),
quantity = COALESCE($3, quantity),
unit = COALESCE($4, unit),
position_x = COALESCE($5, position_x),
position_y = COALESCE($6, position_y),
position_z = COALESCE($7, position_z),
size_x = COALESCE($8, size_x),
size_y = COALESCE($9, size_y),
size_z = COALESCE($10, size_z),
color = COALESCE($11, color),
data_source_type = COALESCE($12, data_source_type),
data_source_config = COALESCE($13, data_source_config),
data_binding = COALESCE($14, data_binding),
memo = COALESCE($15, memo),
updated_at = CURRENT_TIMESTAMP
WHERE id = $16
RETURNING *
`;
const pool = getPool();
// NaN 방지를 위한 안전한 변환 함수
const safeParseInt = (value: any): number | null => {
if (value === null || value === undefined) return null;
const parsed = parseInt(String(value), 10);
return isNaN(parsed) ? null : parsed;
};
const safeParseFloat = (value: any): number | null => {
if (value === null || value === undefined) return null;
const parsed = parseFloat(String(value));
return isNaN(parsed) ? null : parsed;
};
const result = await pool.query(query, [
data.material_code !== undefined ? data.material_code : null,
data.material_name !== undefined ? data.material_name : null,
data.quantity !== undefined ? safeParseInt(data.quantity) : null,
data.unit !== undefined ? data.unit : null,
data.position_x !== undefined ? safeParseFloat(data.position_x) : null,
data.position_y !== undefined ? safeParseFloat(data.position_y) : null,
data.position_z !== undefined ? safeParseFloat(data.position_z) : null,
data.size_x !== undefined ? safeParseFloat(data.size_x) : null,
data.size_y !== undefined ? safeParseFloat(data.size_y) : null,
data.size_z !== undefined ? safeParseFloat(data.size_z) : null,
data.color !== undefined ? data.color : null,
data.data_source_type !== undefined ? data.data_source_type : null,
data.data_source_config !== undefined
? JSON.stringify(data.data_source_config)
: null,
data.data_binding !== undefined
? JSON.stringify(data.data_binding)
: null,
data.memo !== undefined ? data.memo : null,
placementId,
]);
return result.rows[0] || null;
}
// 배치 해제 (자재는 삭제되지 않음)
async removePlacement(placementId: number) {
const query = `DELETE FROM yard_material_placement WHERE id = $1 RETURNING *`;
const pool = getPool();
const result = await pool.query(query, [placementId]);
return result.rows[0] || null;
}
// 여러 배치 일괄 업데이트
async batchUpdatePlacements(layoutId: number, placements: any[]) {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
const results = [];
for (const placement of placements) {
const query = `
UPDATE yard_material_placement
SET
position_x = $1,
position_y = $2,
position_z = $3,
size_x = $4,
size_y = $5,
size_z = $6,
color = $7,
updated_at = CURRENT_TIMESTAMP
WHERE id = $8 AND yard_layout_id = $9
RETURNING *
`;
const result = await client.query(query, [
placement.position_x,
placement.position_y,
placement.position_z,
placement.size_x,
placement.size_y,
placement.size_z,
placement.color,
placement.id,
layoutId,
]);
if (result.rows[0]) {
results.push(result.rows[0]);
}
}
await client.query("COMMIT");
return results;
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
}
// 야드 레이아웃 복제
async duplicateLayout(id: number, newName: string) {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
// 원본 레이아웃 조회
const layoutQuery = `SELECT * FROM yard_layout WHERE id = $1`;
const layoutResult = await client.query(layoutQuery, [id]);
const originalLayout = layoutResult.rows[0];
if (!originalLayout) {
throw new Error("Layout not found");
}
// 새 레이아웃 생성
const newLayoutQuery = `
INSERT INTO yard_layout (name, description, created_by)
VALUES ($1, $2, $3)
RETURNING *
`;
const newLayoutResult = await client.query(newLayoutQuery, [
newName,
originalLayout.description,
originalLayout.created_by,
]);
const newLayout = newLayoutResult.rows[0];
// 배치 자재 복사
const placementsQuery = `SELECT * FROM yard_material_placement WHERE yard_layout_id = $1`;
const placementsResult = await client.query(placementsQuery, [id]);
for (const placement of placementsResult.rows) {
await client.query(
`
INSERT INTO yard_material_placement (
yard_layout_id, material_code, material_name,
quantity, unit, position_x, position_y, position_z,
size_x, size_y, size_z, color,
data_source_type, data_source_config, data_binding, memo
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
`,
[
newLayout.id,
placement.material_code,
placement.material_name,
placement.quantity,
placement.unit,
placement.position_x,
placement.position_y,
placement.position_z,
placement.size_x,
placement.size_y,
placement.size_z,
placement.color,
placement.data_source_type,
placement.data_source_config,
placement.data_binding,
placement.memo,
]
);
}
await client.query("COMMIT");
return newLayout;
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
}
}
export default new YardLayoutService();