feat: Enhance outbound and receiving update functionalities with inventory adjustments
- Updated the `update` function in the outbound controller to include detailed inventory adjustments when modifying outbound records, ensuring accurate stock management. - Implemented rollback mechanisms for both outbound and receiving updates to maintain data integrity in case of errors. - Enhanced the `deleteOutbound` function to include inventory recovery and historical logging for deleted outbound records. - Introduced a new utility function `adjustInventory` to handle inventory changes consistently across different controllers. - Improved error handling and logging for better traceability during outbound and receiving operations.
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
import type { Response } from "express";
|
||||
import { getPool } from "../database/db";
|
||||
import type { AuthenticatedRequest } from "../types/auth";
|
||||
import { adjustInventory } from "../utils/inventoryUtils";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
// 입고 목록 조회 (헤더-디테일 JOIN, 레거시 호환)
|
||||
@@ -472,6 +473,45 @@ export async function update(req: AuthenticatedRequest, res: Response) {
|
||||
|
||||
await client.query("BEGIN");
|
||||
|
||||
// 변경 전 값 조회 (헤더)
|
||||
const oldHeaderRes = await client.query(
|
||||
`SELECT * FROM inbound_mng WHERE id = $1 AND company_code = $2`,
|
||||
[id, companyCode],
|
||||
);
|
||||
if (oldHeaderRes.rowCount === 0) {
|
||||
await client.query("ROLLBACK");
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "입고 데이터를 찾을 수 없습니다." });
|
||||
}
|
||||
const oldHeader = oldHeaderRes.rows[0];
|
||||
|
||||
// 변경 전 값 조회 (디테일, 있을 경우)
|
||||
let oldDetail: any = null;
|
||||
if (detail_id) {
|
||||
const oldDetailRes = await client.query(
|
||||
`SELECT * FROM inbound_detail WHERE id = $1 AND company_code = $2`,
|
||||
[detail_id, companyCode],
|
||||
);
|
||||
oldDetail = oldDetailRes.rows[0] || null;
|
||||
}
|
||||
|
||||
const oldQty =
|
||||
Number(oldDetail?.inbound_qty ?? oldHeader.inbound_qty) || 0;
|
||||
const oldWhCode = oldHeader.warehouse_code || null;
|
||||
const oldLocCode = oldHeader.location_code || null;
|
||||
const itemCode = oldDetail?.item_number || oldHeader.item_number || null;
|
||||
const inboundNumber = oldHeader.inbound_number;
|
||||
|
||||
const newQty =
|
||||
inbound_qty !== undefined && inbound_qty !== null
|
||||
? Number(inbound_qty)
|
||||
: oldQty;
|
||||
const newWhCode =
|
||||
warehouse_code !== undefined ? warehouse_code : oldWhCode;
|
||||
const newLocCode =
|
||||
location_code !== undefined ? location_code : oldLocCode;
|
||||
|
||||
// 입고 레코드 업데이트 (헤더 + 품목 필드 모두)
|
||||
const headerResult = await client.query(
|
||||
`UPDATE inbound_mng SET
|
||||
@@ -506,13 +546,6 @@ export async function update(req: AuthenticatedRequest, res: Response) {
|
||||
],
|
||||
);
|
||||
|
||||
if (headerResult.rowCount === 0) {
|
||||
await client.query("ROLLBACK");
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "입고 데이터를 찾을 수 없습니다." });
|
||||
}
|
||||
|
||||
// 디테일 업데이트 (inbound_detail) — detail_id가 있으면 디테일 레벨 필드 업데이트
|
||||
let detailRow = null;
|
||||
if (detail_id) {
|
||||
@@ -563,9 +596,67 @@ export async function update(req: AuthenticatedRequest, res: Response) {
|
||||
);
|
||||
}
|
||||
|
||||
// 재고/이력 반영 (append-only): 수량 또는 창고/위치 변경 시
|
||||
const qtyChanged = newQty !== oldQty;
|
||||
const whChanged =
|
||||
(newWhCode || "") !== (oldWhCode || "") ||
|
||||
(newLocCode || "") !== (oldLocCode || "");
|
||||
|
||||
if (itemCode && (qtyChanged || whChanged)) {
|
||||
if (whChanged) {
|
||||
if (oldQty > 0) {
|
||||
await adjustInventory(client, {
|
||||
companyCode,
|
||||
userId,
|
||||
itemCode,
|
||||
whCode: oldWhCode,
|
||||
locCode: oldLocCode,
|
||||
delta: -oldQty,
|
||||
transactionType: "입고취소",
|
||||
remark: `입고수정-창고변경 (${inboundNumber}) ${oldWhCode || ""}→${newWhCode || ""}`,
|
||||
});
|
||||
}
|
||||
if (newQty > 0) {
|
||||
await adjustInventory(client, {
|
||||
companyCode,
|
||||
userId,
|
||||
itemCode,
|
||||
whCode: newWhCode,
|
||||
locCode: newLocCode,
|
||||
delta: newQty,
|
||||
transactionType: "입고수정",
|
||||
remark: `입고수정-창고변경 (${inboundNumber}) ${oldWhCode || ""}→${newWhCode || ""}, 수량 ${oldQty}→${newQty}`,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const delta = newQty - oldQty;
|
||||
if (delta !== 0) {
|
||||
await adjustInventory(client, {
|
||||
companyCode,
|
||||
userId,
|
||||
itemCode,
|
||||
whCode: newWhCode,
|
||||
locCode: newLocCode,
|
||||
delta,
|
||||
transactionType: "입고수정",
|
||||
remark: `입고수정 (${inboundNumber}) 수량 ${oldQty}→${newQty}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await client.query("COMMIT");
|
||||
|
||||
logger.info("입고 수정", { companyCode, userId, id, detail_id });
|
||||
logger.info("입고 수정", {
|
||||
companyCode,
|
||||
userId,
|
||||
id,
|
||||
detail_id,
|
||||
oldQty,
|
||||
newQty,
|
||||
oldWhCode,
|
||||
newWhCode,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
|
||||
Reference in New Issue
Block a user