일단 야드관리 3d 드래그앤 드랍까지
This commit is contained in:
111
backend-node/src/services/MaterialService.ts
Normal file
111
backend-node/src/services/MaterialService.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { getPool } from "../database/db";
|
||||
|
||||
export class MaterialService {
|
||||
// 임시 자재 마스터 목록 조회
|
||||
async getTempMaterials(params: {
|
||||
search?: string;
|
||||
category?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}) {
|
||||
const { search, category, page = 1, limit = 20 } = params;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
let whereConditions: string[] = ["is_active = true"];
|
||||
const queryParams: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
if (search) {
|
||||
whereConditions.push(
|
||||
`(material_code ILIKE $${paramIndex} OR material_name ILIKE $${paramIndex})`
|
||||
);
|
||||
queryParams.push(`%${search}%`);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (category) {
|
||||
whereConditions.push(`category = $${paramIndex}`);
|
||||
queryParams.push(category);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
const whereClause =
|
||||
whereConditions.length > 0
|
||||
? `WHERE ${whereConditions.join(" AND ")}`
|
||||
: "";
|
||||
|
||||
const pool = getPool();
|
||||
|
||||
// 전체 개수 조회
|
||||
const countQuery = `SELECT COUNT(*) as total FROM temp_material_master ${whereClause}`;
|
||||
const countResult = await pool.query(countQuery, queryParams);
|
||||
const total = parseInt(countResult.rows[0].total);
|
||||
|
||||
// 데이터 조회
|
||||
const dataQuery = `
|
||||
SELECT
|
||||
id,
|
||||
material_code,
|
||||
material_name,
|
||||
category,
|
||||
unit,
|
||||
default_color,
|
||||
description,
|
||||
created_at
|
||||
FROM temp_material_master
|
||||
${whereClause}
|
||||
ORDER BY material_code ASC
|
||||
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
||||
`;
|
||||
|
||||
queryParams.push(limit, offset);
|
||||
const dataResult = await pool.query(dataQuery, queryParams);
|
||||
|
||||
return {
|
||||
data: dataResult.rows,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 특정 자재 상세 조회
|
||||
async getTempMaterialByCode(materialCode: string) {
|
||||
const query = `
|
||||
SELECT
|
||||
id,
|
||||
material_code,
|
||||
material_name,
|
||||
category,
|
||||
unit,
|
||||
default_color,
|
||||
description,
|
||||
created_at
|
||||
FROM temp_material_master
|
||||
WHERE material_code = $1 AND is_active = true
|
||||
`;
|
||||
|
||||
const pool = getPool();
|
||||
const result = await pool.query(query, [materialCode]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
// 카테고리 목록 조회
|
||||
async getCategories() {
|
||||
const query = `
|
||||
SELECT DISTINCT category
|
||||
FROM temp_material_master
|
||||
WHERE is_active = true AND category IS NOT NULL
|
||||
ORDER BY category ASC
|
||||
`;
|
||||
|
||||
const pool = getPool();
|
||||
const result = await pool.query(query);
|
||||
return result.rows.map((row) => row.category);
|
||||
}
|
||||
}
|
||||
|
||||
export default new MaterialService();
|
||||
337
backend-node/src/services/YardLayoutService.ts
Normal file
337
backend-node/src/services/YardLayoutService.ts
Normal file
@@ -0,0 +1,337 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user