fix: [자재투입] 물리LOT 교차 추적 복원 — physicalAvail 기반 정확한 가용량 계산

- 이전 수정에서 교차 추적 제거 시 동일 stockLotId를 다른 BOM 그룹에서
  초과 배정하여 API에서 재고 부족(500) 에러 발생
- physicalAvail: 각 stockLotId의 최대 가용량(lotAvailableQty + max(lotInputtedQty))으로 초기화
- physicalUsed: 그룹 간 누적 사용량 추적하여 실제 물리 LOT 잔량 내에서 배정
- handleAutoFill과 allocations useMemo 모두 동일 로직 적용
This commit is contained in:
김보곤
2026-03-22 17:37:50 +09:00
parent cc6786d791
commit e9a6c64953

View File

@@ -199,10 +199,23 @@ export function MaterialInputModal({
return group.alreadyInputted > 0 ? group.requiredQty : group.effectiveRequiredQty;
}, []);
// 배정 수량 계산 (manual 우선 → 나머지 FIFO 자동배분, 그룹별 독립)
// 배정 수량 계산 (manual 우선 → 나머지 FIFO 자동배분, 물리LOT 교차 추적)
const allocations = useMemo(() => {
const result = new Map<string, number>();
// 물리 LOT별 최대 가용량 초기화
const physicalAvail = new Map<number, number>();
for (const group of materialGroups) {
for (const lot of group.lots) {
if (!lot.stockLotId) continue;
const itemInput = lot as unknown as MaterialForItemInput;
const totalAvail = lot.lotAvailableQty + (itemInput.lotInputtedQty ?? 0);
const current = physicalAvail.get(lot.stockLotId) ?? 0;
if (totalAvail > current) physicalAvail.set(lot.stockLotId, totalAvail);
}
}
const physicalUsed = new Map<number, number>();
for (const group of materialGroups) {
const targetQty = getGroupTargetQty(group);
let remaining = targetQty;
@@ -214,18 +227,23 @@ export function MaterialInputModal({
const val = manualAllocations.get(lotKey)!;
result.set(lotKey, val);
remaining -= val;
physicalUsed.set(lot.stockLotId, (physicalUsed.get(lot.stockLotId) || 0) + val);
}
}
// 2차: non-manual 선택 로트 FIFO 자동배분 (그룹 내 독립 계산)
// 2차: non-manual 선택 로트 FIFO 자동배분 (물리LOT 교차 추적)
for (const lot of group.lots) {
const lotKey = getLotKey(lot, group.groupKey);
if (selectedLotKeys.has(lotKey) && lot.stockLotId && !manualAllocations.has(lotKey)) {
const itemInput = lot as unknown as MaterialForItemInput;
const maxAvail = lot.lotAvailableQty + (itemInput.lotInputtedQty ?? 0);
const alloc = remaining > 0 ? Math.min(maxAvail, remaining) : 0;
const totalAvail = physicalAvail.get(lot.stockLotId) ?? lot.lotAvailableQty;
const used = physicalUsed.get(lot.stockLotId) || 0;
const effectiveAvail = Math.max(0, totalAvail - used);
const alloc = remaining > 0 ? Math.min(effectiveAvail, remaining) : 0;
result.set(lotKey, alloc);
remaining -= alloc;
if (alloc > 0) {
physicalUsed.set(lot.stockLotId, used + alloc);
}
}
}
}
@@ -279,11 +297,31 @@ export function MaterialInputModal({
});
}, []);
// FIFO 자동입력 (그룹별 독립 배정 — 각 그룹의 LOT 가용량 독립 계산)
// FIFO 자동입력 (물리LOT 가용량 교차 추적 — 동일 stockLotId의 실제 잔량 공유)
const handleAutoFill = useCallback(() => {
const newSelected = new Set<string>();
const newAllocations = new Map<string, number>();
// 물리 LOT별 실제 가용량 추적 (lotAvailableQty 기준, 기투입분 포함 복원량으로 초기화)
const physicalAvail = new Map<number, number>();
// 1차: 물리 LOT별 최대 가용량 초기화 (lotAvailableQty + 전체 기투입량)
for (const group of materialGroups) {
for (const lot of group.lots) {
if (!lot.stockLotId) continue;
const itemInput = lot as unknown as MaterialForItemInput;
const totalAvail = lot.lotAvailableQty + (itemInput.lotInputtedQty ?? 0);
// 같은 stockLotId의 최대값 유지 (기투입이 가장 큰 값이 실제 원래 가용량)
const current = physicalAvail.get(lot.stockLotId) ?? 0;
if (totalAvail > current) {
physicalAvail.set(lot.stockLotId, totalAvail);
}
}
}
// 물리 LOT 사용량 추적
const physicalUsed = new Map<number, number>();
// 2차: 그룹별 FIFO 배정
for (const group of materialGroups) {
const targetQty = getGroupTargetQty(group);
if (targetQty <= 0) continue;
@@ -292,14 +330,15 @@ export function MaterialInputModal({
for (const lot of group.lots) {
if (!lot.stockLotId || remaining <= 0) continue;
const lotKey = getLotKey(lot, group.groupKey);
const itemInput = lot as unknown as MaterialForItemInput;
// 해당 그룹에서의 LOT 가용량 = 현재 가용 + 이 그룹에서 기투입된 수량
const maxAvail = lot.lotAvailableQty + (itemInput.lotInputtedQty ?? 0);
const alloc = Math.min(maxAvail, remaining);
const totalAvail = physicalAvail.get(lot.stockLotId) ?? lot.lotAvailableQty;
const used = physicalUsed.get(lot.stockLotId) || 0;
const effectiveAvail = Math.max(0, totalAvail - used);
const alloc = Math.min(effectiveAvail, remaining);
if (alloc > 0) {
newSelected.add(lotKey);
newAllocations.set(lotKey, alloc);
remaining -= alloc;
physicalUsed.set(lot.stockLotId, used + alloc);
}
}
}