338 lines
8.5 KiB
TypeScript
338 lines
8.5 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,
|
|
external_material_id,
|
|
material_code,
|
|
material_name,
|
|
quantity,
|
|
unit,
|
|
position_x,
|
|
position_y,
|
|
position_z,
|
|
size_x,
|
|
size_y,
|
|
size_z,
|
|
color,
|
|
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,
|
|
external_material_id,
|
|
material_code,
|
|
material_name,
|
|
quantity,
|
|
unit,
|
|
position_x,
|
|
position_y,
|
|
position_z,
|
|
size_x,
|
|
size_y,
|
|
size_z,
|
|
color,
|
|
memo
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
|
RETURNING *
|
|
`;
|
|
|
|
const pool = getPool();
|
|
const result = await pool.query(query, [
|
|
layoutId,
|
|
data.external_material_id,
|
|
data.material_code,
|
|
data.material_name,
|
|
data.quantity,
|
|
data.unit,
|
|
data.position_x || 0,
|
|
data.position_y || 0,
|
|
data.position_z || 0,
|
|
data.size_x || 5,
|
|
data.size_y || 5,
|
|
data.size_z || 5,
|
|
data.color || "#3b82f6",
|
|
data.memo || null,
|
|
]);
|
|
|
|
return result.rows[0];
|
|
}
|
|
|
|
// 배치 정보 수정 (위치, 크기, 색상, 메모만)
|
|
async updatePlacement(placementId: number, data: any) {
|
|
const query = `
|
|
UPDATE yard_material_placement
|
|
SET
|
|
position_x = COALESCE($1, position_x),
|
|
position_y = COALESCE($2, position_y),
|
|
position_z = COALESCE($3, position_z),
|
|
size_x = COALESCE($4, size_x),
|
|
size_y = COALESCE($5, size_y),
|
|
size_z = COALESCE($6, size_z),
|
|
color = COALESCE($7, color),
|
|
memo = COALESCE($8, memo),
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $9
|
|
RETURNING *
|
|
`;
|
|
|
|
const pool = getPool();
|
|
const result = await pool.query(query, [
|
|
data.position_x,
|
|
data.position_y,
|
|
data.position_z,
|
|
data.size_x,
|
|
data.size_y,
|
|
data.size_z,
|
|
data.color,
|
|
data.memo,
|
|
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,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $7 AND yard_layout_id = $8
|
|
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.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, external_material_id, material_code, material_name,
|
|
quantity, unit, position_x, position_y, position_z,
|
|
size_x, size_y, size_z, color, memo
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
|
`,
|
|
[
|
|
newLayout.id,
|
|
placement.external_material_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.memo,
|
|
]
|
|
);
|
|
}
|
|
|
|
await client.query("COMMIT");
|
|
return newLayout;
|
|
} catch (error) {
|
|
await client.query("ROLLBACK");
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
}
|
|
|
|
export default new YardLayoutService();
|