Implement Order Status Integration for Outsourcing Purchase Management
- Added a new endpoint `listOrderStatus` in the `outsourcePurchaseController` to retrieve integrated order status information, including filtering options for source type, order status, and date range. - Updated the `outsourcePurchaseService` to handle the new order status retrieval logic, ensuring proper filtering and data aggregation. - Introduced a new route for accessing the order status information in `outsourcePurchaseRoutes`. - Created a detailed modal for viewing outsourcing purchase order details, enhancing the user interface for better data presentation. - Developed a registration modal for creating and editing outsourcing purchase orders, featuring a tabbed interface for improved user experience. (TASK: ERP-025, ERP-019)
This commit is contained in:
@@ -12,6 +12,17 @@ export interface AdjustInventoryParams {
|
||||
validateStockEnough?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* inventory_stock 차감/증가 + inventory_history 기록.
|
||||
*
|
||||
* 매칭 우선순위:
|
||||
* 1) 사용자 선택 창고+위치 정확히 일치 (strict)
|
||||
* 2) 창고만 일치
|
||||
* 3) 같은 품목의 첫 row (위치/창고 빈값 데이터 fallback)
|
||||
*
|
||||
* 즉, 사용자가 창고를 선택했지만 해당 row가 없고 빈 창고 row만 있는 경우라도
|
||||
* 첫 매칭 row에서 차감되도록 한다. inventory_history에는 실제 차감된 row의 위치 기록.
|
||||
*/
|
||||
export async function adjustInventory(
|
||||
client: PoolClient,
|
||||
params: AdjustInventoryParams,
|
||||
@@ -30,96 +41,96 @@ export async function adjustInventory(
|
||||
|
||||
if (!itemCode || delta === 0) return;
|
||||
|
||||
if (validateStockEnough && delta < 0) {
|
||||
const stockRes = await client.query(
|
||||
`SELECT COALESCE(CAST(NULLIF(current_qty, '') AS numeric), 0) AS cur
|
||||
FROM inventory_stock
|
||||
WHERE company_code = $1 AND item_code = $2
|
||||
AND COALESCE(warehouse_code, '') = COALESCE($3, '')
|
||||
AND ($4 IS NULL OR $4 = '' OR COALESCE(location_code, '') = $4)
|
||||
LIMIT 1`,
|
||||
[companyCode, itemCode, whCode || "", locCode || ""],
|
||||
);
|
||||
const cur = parseFloat(stockRes.rows[0]?.cur || "0");
|
||||
if (cur + delta < 0) {
|
||||
throw new Error(
|
||||
`재고 부족: 품목 ${itemCode} (창고 ${whCode || "미지정"}) — 현재 재고 ${cur}, 차감 요청 ${-delta}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const existing = await client.query(
|
||||
`SELECT id FROM inventory_stock
|
||||
WHERE company_code = $1 AND item_code = $2
|
||||
AND COALESCE(warehouse_code, '') = COALESCE($3, '')
|
||||
AND ($4 IS NULL OR $4 = '' OR COALESCE(location_code, '') = $4)
|
||||
LIMIT 1`,
|
||||
// 1) 매칭 row 탐색 (우선순위 정렬 + LIMIT 1)
|
||||
const matchRes = await client.query(
|
||||
`SELECT id, warehouse_code, location_code,
|
||||
COALESCE(CAST(NULLIF(current_qty, '') AS numeric), 0) AS cur
|
||||
FROM inventory_stock
|
||||
WHERE company_code = $1 AND item_code = $2
|
||||
ORDER BY
|
||||
CASE WHEN COALESCE(warehouse_code, '') = COALESCE($3, '') THEN 0 ELSE 1 END,
|
||||
CASE WHEN COALESCE(location_code, '') = COALESCE($4, '') THEN 0 ELSE 1 END
|
||||
LIMIT 1`,
|
||||
[companyCode, itemCode, whCode || "", locCode || ""],
|
||||
);
|
||||
|
||||
if (existing.rows.length > 0) {
|
||||
let stockId: string | null = matchRes.rows[0]?.id || null;
|
||||
const matchedWh: string | null = matchRes.rows[0]?.warehouse_code ?? null;
|
||||
const matchedLoc: string | null = matchRes.rows[0]?.location_code ?? null;
|
||||
const cur = parseFloat(matchRes.rows[0]?.cur || "0");
|
||||
|
||||
// 2) 재고 부족 검증 (차감 케이스)
|
||||
if (validateStockEnough && delta < 0 && cur + delta < 0) {
|
||||
throw new Error(
|
||||
`재고 부족: 품목 ${itemCode} (창고 ${whCode || "미지정"}) — 현재 재고 ${cur}, 차감 요청 ${-delta}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 3) row가 있으면 UPDATE, 없으면 신규 INSERT (입고 시에만)
|
||||
if (stockId) {
|
||||
if (delta >= 0) {
|
||||
await client.query(
|
||||
`UPDATE inventory_stock
|
||||
SET current_qty = CAST(COALESCE(CAST(NULLIF(current_qty, '') AS numeric), 0) + $1 AS text),
|
||||
last_in_date = NOW(),
|
||||
updated_date = NOW()
|
||||
WHERE id = $2`,
|
||||
[delta, existing.rows[0].id],
|
||||
SET current_qty = CAST(COALESCE(CAST(NULLIF(current_qty, '') AS numeric), 0) + $1 AS text),
|
||||
last_in_date = NOW(),
|
||||
updated_date = NOW()
|
||||
WHERE id = $2`,
|
||||
[delta, stockId],
|
||||
);
|
||||
} else {
|
||||
await client.query(
|
||||
`UPDATE inventory_stock
|
||||
SET current_qty = CAST(GREATEST(COALESCE(CAST(NULLIF(current_qty, '') AS numeric), 0) + $1, 0) AS text),
|
||||
last_out_date = NOW(),
|
||||
updated_date = NOW()
|
||||
WHERE id = $2`,
|
||||
[delta, existing.rows[0].id],
|
||||
SET current_qty = CAST(GREATEST(COALESCE(CAST(NULLIF(current_qty, '') AS numeric), 0) + $1, 0) AS text),
|
||||
last_out_date = NOW(),
|
||||
updated_date = NOW()
|
||||
WHERE id = $2`,
|
||||
[delta, stockId],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const initQty = Math.max(delta, 0);
|
||||
await client.query(
|
||||
const insRes = await client.query(
|
||||
`INSERT INTO inventory_stock (
|
||||
id, company_code, item_code, warehouse_code, location_code,
|
||||
current_qty, safety_qty, last_in_date, last_out_date,
|
||||
created_date, updated_date, writer
|
||||
) VALUES (
|
||||
gen_random_uuid()::text, $1, $2, $3, $4,
|
||||
$5, '0',
|
||||
${delta > 0 ? "NOW()" : "NULL"},
|
||||
${delta < 0 ? "NOW()" : "NULL"},
|
||||
NOW(), NOW(), $6
|
||||
)`,
|
||||
id, company_code, item_code, warehouse_code, location_code,
|
||||
current_qty, safety_qty, last_in_date, last_out_date,
|
||||
created_date, updated_date, writer
|
||||
) VALUES (
|
||||
gen_random_uuid()::text, $1, $2, $3, $4,
|
||||
$5, '0',
|
||||
${delta > 0 ? "NOW()" : "NULL"},
|
||||
${delta < 0 ? "NOW()" : "NULL"},
|
||||
NOW(), NOW(), $6
|
||||
) RETURNING id`,
|
||||
[companyCode, itemCode, whCode, locCode, String(initQty), userId],
|
||||
);
|
||||
stockId = insRes.rows[0].id;
|
||||
}
|
||||
|
||||
// 4) 차감 후 잔량 (이력 기록용)
|
||||
const afterRes = await client.query(
|
||||
`SELECT current_qty FROM inventory_stock
|
||||
WHERE company_code = $1 AND item_code = $2
|
||||
AND COALESCE(warehouse_code, '') = COALESCE($3, '')
|
||||
AND ($4 IS NULL OR $4 = '' OR COALESCE(location_code, '') = $4)
|
||||
LIMIT 1`,
|
||||
[companyCode, itemCode, whCode || "", locCode || ""],
|
||||
`SELECT current_qty FROM inventory_stock WHERE id = $1`,
|
||||
[stockId],
|
||||
);
|
||||
const afterQty = afterRes.rows[0]?.current_qty || "0";
|
||||
|
||||
// 5) inventory_history 기록 — 실제 차감/증가된 row의 창고/위치를 기록
|
||||
const historyWh = matchedWh ?? whCode;
|
||||
const historyLoc = matchedLoc ?? locCode;
|
||||
await client.query(
|
||||
`INSERT INTO inventory_history (
|
||||
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,
|
||||
$5, NOW(), $6, $7, $8,
|
||||
$9, 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,
|
||||
$5, NOW(), $6, $7, $8,
|
||||
$9, NOW()
|
||||
)`,
|
||||
[
|
||||
companyCode,
|
||||
itemCode,
|
||||
whCode,
|
||||
locCode,
|
||||
historyWh,
|
||||
historyLoc,
|
||||
transactionType,
|
||||
(delta > 0 ? "+" : "") + String(delta),
|
||||
afterQty,
|
||||
|
||||
Reference in New Issue
Block a user