Merge origin/main into lhj - 대시보드 기능 통합
- 달력-할일-긴급지시 Context 연동 (lhj) - 창고 현황 3D 위젯 추가 (main) - 대시보드 저장 모달 개선 (main) - 메뉴 할당 모달 추가 (main) - 그리드 스냅 기능 유지 - DashboardProvider 통합
This commit is contained in:
@@ -55,6 +55,7 @@ import riskAlertRoutes from "./routes/riskAlertRoutes"; // 리스크/알림 관
|
||||
import todoRoutes from "./routes/todoRoutes"; // To-Do 관리
|
||||
import bookingRoutes from "./routes/bookingRoutes"; // 예약 요청 관리
|
||||
import mapDataRoutes from "./routes/mapDataRoutes"; // 지도 데이터 관리
|
||||
import warehouseRoutes from "./routes/warehouseRoutes"; // 창고 관리
|
||||
import { BatchSchedulerService } from "./services/batchSchedulerService";
|
||||
// import collectionRoutes from "./routes/collectionRoutes"; // 임시 주석
|
||||
// import batchRoutes from "./routes/batchRoutes"; // 임시 주석
|
||||
@@ -204,6 +205,7 @@ app.use("/api/risk-alerts", riskAlertRoutes); // 리스크/알림 관리
|
||||
app.use("/api/todos", todoRoutes); // To-Do 관리
|
||||
app.use("/api/bookings", bookingRoutes); // 예약 요청 관리
|
||||
app.use("/api/map-data", mapDataRoutes); // 지도 데이터 조회
|
||||
app.use("/api/warehouse", warehouseRoutes); // 창고 관리
|
||||
// app.use("/api/collections", collectionRoutes); // 임시 주석
|
||||
// app.use("/api/batch", batchRoutes); // 임시 주석
|
||||
// app.use('/api/users', userRoutes);
|
||||
@@ -249,7 +251,9 @@ app.listen(PORT, HOST, async () => {
|
||||
|
||||
// 리스크/알림 자동 갱신 시작
|
||||
try {
|
||||
const { RiskAlertCacheService } = await import('./services/riskAlertCacheService');
|
||||
const { RiskAlertCacheService } = await import(
|
||||
"./services/riskAlertCacheService"
|
||||
);
|
||||
const cacheService = RiskAlertCacheService.getInstance();
|
||||
cacheService.startAutoRefresh();
|
||||
logger.info(`⏰ 리스크/알림 자동 갱신이 시작되었습니다. (10분 간격)`);
|
||||
|
||||
97
backend-node/src/controllers/WarehouseController.ts
Normal file
97
backend-node/src/controllers/WarehouseController.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Request, Response } from "express";
|
||||
import { WarehouseService } from "../services/WarehouseService";
|
||||
|
||||
export class WarehouseController {
|
||||
private warehouseService: WarehouseService;
|
||||
|
||||
constructor() {
|
||||
this.warehouseService = new WarehouseService();
|
||||
}
|
||||
|
||||
// 창고 및 자재 데이터 조회
|
||||
getWarehouseData = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const data = await this.warehouseService.getWarehouseData();
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
warehouses: data.warehouses,
|
||||
materials: data.materials,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("창고 데이터 조회 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "창고 데이터를 불러오는데 실패했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 특정 창고 정보 조회
|
||||
getWarehouseById = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const warehouse = await this.warehouseService.getWarehouseById(id);
|
||||
|
||||
if (!warehouse) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "창고를 찾을 수 없습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: warehouse,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("창고 조회 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "창고 정보를 불러오는데 실패했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 창고별 자재 목록 조회
|
||||
getMaterialsByWarehouse = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { warehouseId } = req.params;
|
||||
const materials =
|
||||
await this.warehouseService.getMaterialsByWarehouse(warehouseId);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: materials,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("자재 목록 조회 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "자재 목록을 불러오는데 실패했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 창고 통계 조회
|
||||
getWarehouseStats = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const stats = await this.warehouseService.getWarehouseStats();
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: stats,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("창고 통계 조회 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "창고 통계를 불러오는데 실패했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
22
backend-node/src/routes/warehouseRoutes.ts
Normal file
22
backend-node/src/routes/warehouseRoutes.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Router } from "express";
|
||||
import { WarehouseController } from "../controllers/WarehouseController";
|
||||
|
||||
const router = Router();
|
||||
const warehouseController = new WarehouseController();
|
||||
|
||||
// 창고 및 자재 데이터 조회
|
||||
router.get("/data", warehouseController.getWarehouseData);
|
||||
|
||||
// 특정 창고 정보 조회
|
||||
router.get("/:id", warehouseController.getWarehouseById);
|
||||
|
||||
// 창고별 자재 목록 조회
|
||||
router.get(
|
||||
"/:warehouseId/materials",
|
||||
warehouseController.getMaterialsByWarehouse
|
||||
);
|
||||
|
||||
// 창고 통계 조회
|
||||
router.get("/stats/summary", warehouseController.getWarehouseStats);
|
||||
|
||||
export default router;
|
||||
170
backend-node/src/services/WarehouseService.ts
Normal file
170
backend-node/src/services/WarehouseService.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import pool from "../database/db";
|
||||
|
||||
export class WarehouseService {
|
||||
// 창고 및 자재 데이터 조회
|
||||
async getWarehouseData() {
|
||||
try {
|
||||
// 창고 목록 조회
|
||||
const warehousesResult = await pool.query(`
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
position_x,
|
||||
position_y,
|
||||
position_z,
|
||||
size_x,
|
||||
size_y,
|
||||
size_z,
|
||||
color,
|
||||
capacity,
|
||||
current_usage,
|
||||
status,
|
||||
description,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM warehouse
|
||||
WHERE status = 'active'
|
||||
ORDER BY id
|
||||
`);
|
||||
|
||||
// 자재 목록 조회
|
||||
const materialsResult = await pool.query(`
|
||||
SELECT
|
||||
id,
|
||||
warehouse_id,
|
||||
name,
|
||||
material_code,
|
||||
quantity,
|
||||
unit,
|
||||
position_x,
|
||||
position_y,
|
||||
position_z,
|
||||
size_x,
|
||||
size_y,
|
||||
size_z,
|
||||
color,
|
||||
status,
|
||||
last_updated,
|
||||
created_at
|
||||
FROM warehouse_material
|
||||
ORDER BY warehouse_id, id
|
||||
`);
|
||||
|
||||
return {
|
||||
warehouses: warehousesResult,
|
||||
materials: materialsResult,
|
||||
};
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 특정 창고 정보 조회
|
||||
async getWarehouseById(id: string) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
position_x,
|
||||
position_y,
|
||||
position_z,
|
||||
size_x,
|
||||
size_y,
|
||||
size_z,
|
||||
color,
|
||||
capacity,
|
||||
current_usage,
|
||||
status,
|
||||
description,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM warehouse
|
||||
WHERE id = $1
|
||||
`,
|
||||
[id]
|
||||
);
|
||||
|
||||
return result[0] || null;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 창고별 자재 목록 조회
|
||||
async getMaterialsByWarehouse(warehouseId: string) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
id,
|
||||
warehouse_id,
|
||||
name,
|
||||
material_code,
|
||||
quantity,
|
||||
unit,
|
||||
position_x,
|
||||
position_y,
|
||||
position_z,
|
||||
size_x,
|
||||
size_y,
|
||||
size_z,
|
||||
color,
|
||||
status,
|
||||
last_updated,
|
||||
created_at
|
||||
FROM warehouse_material
|
||||
WHERE warehouse_id = $1
|
||||
ORDER BY id
|
||||
`,
|
||||
[warehouseId]
|
||||
);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 창고 통계 조회
|
||||
async getWarehouseStats() {
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT
|
||||
COUNT(DISTINCT w.id) as total_warehouses,
|
||||
COUNT(m.id) as total_materials,
|
||||
SUM(w.capacity) as total_capacity,
|
||||
SUM(w.current_usage) as total_usage,
|
||||
ROUND(AVG((w.current_usage::numeric / NULLIF(w.capacity, 0)) * 100), 2) as avg_usage_percent
|
||||
FROM warehouse w
|
||||
LEFT JOIN warehouse_material m ON w.id = m.warehouse_id
|
||||
WHERE w.status = 'active'
|
||||
`);
|
||||
|
||||
// 상태별 자재 수
|
||||
const statusResult = await pool.query(`
|
||||
SELECT
|
||||
status,
|
||||
COUNT(*) as count
|
||||
FROM warehouse_material
|
||||
GROUP BY status
|
||||
`);
|
||||
|
||||
const statusCounts = statusResult.reduce(
|
||||
(acc: Record<string, number>, row: any) => {
|
||||
acc[row.status] = parseInt(row.count);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>
|
||||
);
|
||||
|
||||
return {
|
||||
...result[0],
|
||||
materialsByStatus: statusCounts,
|
||||
};
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user