feat: Enhance outbound and receiving functionalities

- Updated inventory history insertion logic in both outbound and receiving controllers to use consistent field names and types.
- Added a new endpoint for retrieving warehouse locations, improving the ability to manage inventory locations.
- Enhanced the outbound page to include location selection based on the selected warehouse, improving user experience and data accuracy.
- Implemented validation for warehouse code duplication during new warehouse registration in the warehouse management page.

These changes aim to streamline inventory management processes and enhance the overall functionality of the logistics module.
This commit is contained in:
kjs
2026-04-03 17:38:14 +09:00
parent b3498677ab
commit e25ca7beca
8 changed files with 354 additions and 140 deletions

View File

@@ -221,10 +221,10 @@ export async function create(req: AuthenticatedRequest, res: Response) {
const afterQty = afterStockRes.rows[0]?.current_qty || '0';
await client.query(
`INSERT INTO inventory_history (
id, company_code, item_number, warehouse_code, location_code,
history_type, history_date, change_qty, after_qty, reason,
created_by, created_date
) VALUES (gen_random_uuid()::text, $1, $2, $3, $4, '출고', NOW()::date, $5, $6, $7, $8, NOW())`,
id, company_code, item_code, warehouse_code, location_code,
transaction_type, transaction_date, quantity, balance_qty, remark,
writer, created_date
) VALUES (gen_random_uuid()::text, $1, $2, $3, $4, '출고', NOW(), $5, $6, $7, $8, NOW())`,
[companyCode, itemCode, whCode, locCode, String(-outQty), afterQty, item.outbound_type || '출고', userId]
);
}
@@ -515,7 +515,7 @@ export async function getWarehouses(req: AuthenticatedRequest, res: Response) {
const result = await pool.query(
`SELECT warehouse_code, warehouse_name, warehouse_type
FROM warehouse_info
WHERE company_code = $1 AND status != '삭제'
WHERE company_code = $1 AND COALESCE(status, '') != '삭제'
ORDER BY warehouse_name`,
[companyCode]
);
@@ -526,3 +526,25 @@ export async function getWarehouses(req: AuthenticatedRequest, res: Response) {
return res.status(500).json({ success: false, message: error.message });
}
}
// 창고별 위치 목록 조회
export async function getLocations(req: AuthenticatedRequest, res: Response) {
try {
const companyCode = req.user!.companyCode;
const warehouseCode = req.query.warehouse_code as string;
const pool = getPool();
const result = await pool.query(
`SELECT location_code, location_name, warehouse_code
FROM warehouse_location
WHERE company_code = $1 ${warehouseCode ? "AND warehouse_code = $2" : ""}
ORDER BY location_code`,
warehouseCode ? [companyCode, warehouseCode] : [companyCode]
);
return res.json({ success: true, data: result.rows });
} catch (error: any) {
logger.error("위치 목록 조회 실패", { error: error.message });
return res.status(500).json({ success: false, message: error.message });
}
}

View File

@@ -266,10 +266,10 @@ export async function create(req: AuthenticatedRequest, res: Response) {
const afterQty = afterStockRes.rows[0]?.current_qty || String(inQty);
await client.query(
`INSERT INTO inventory_history (
id, company_code, item_number, warehouse_code, location_code,
history_type, history_date, change_qty, after_qty, reason,
created_by, created_date
) VALUES (gen_random_uuid()::text, $1, $2, $3, $4, '입고', NOW()::date, $5, $6, $7, $8, NOW())`,
id, company_code, item_code, warehouse_code, location_code,
transaction_type, transaction_date, quantity, balance_qty, remark,
writer, created_date
) VALUES (gen_random_uuid()::text, $1, $2, $3, $4, '입고', NOW(), $5, $6, $7, $8, NOW())`,
[companyCode, itemCode, whCode, locCode, String(inQty), afterQty, item.inbound_type || '입고', userId]
);
}
@@ -548,10 +548,10 @@ export async function deleteReceiving(req: AuthenticatedRequest, res: Response)
const afterQty = afterStockRes.rows[0]?.current_qty || '0';
await client.query(
`INSERT INTO inventory_history (
id, company_code, item_number, warehouse_code, location_code,
history_type, history_date, change_qty, after_qty, reason,
created_by, created_date
) VALUES (gen_random_uuid()::text, $1, $2, $3, $4, '입고취소', NOW()::date, $5, $6, '입고 삭제에 의한 롤백', $7, NOW())`,
id, company_code, item_code, warehouse_code, location_code,
transaction_type, transaction_date, quantity, balance_qty, remark,
writer, created_date
) VALUES (gen_random_uuid()::text, $1, $2, $3, $4, '입고취소', NOW(), $5, $6, '입고 삭제에 의한 롤백', $7, NOW())`,
[companyCode, itemCode, whCode, locCode, String(-inQty), afterQty, userId]
);
}

View File

@@ -19,6 +19,9 @@ router.get("/generate-number", outboundController.generateNumber);
// 창고 목록 조회
router.get("/warehouses", outboundController.getWarehouses);
// 위치 목록 조회
router.get("/locations", outboundController.getLocations);
// 소스 데이터: 출하지시 (판매출고)
router.get("/source/shipment-instructions", outboundController.getShipmentInstructions);