fix: [자재투입] 물리LOT 교차 추적 복원 — physicalAvail 기반 정확한 가용량 계산
- 이전 수정에서 교차 추적 제거 시 동일 stockLotId를 다른 BOM 그룹에서 초과 배정하여 API에서 재고 부족(500) 에러 발생 - physicalAvail: 각 stockLotId의 최대 가용량(lotAvailableQty + max(lotInputtedQty))으로 초기화 - physicalUsed: 그룹 간 누적 사용량 추적하여 실제 물리 LOT 잔량 내에서 배정 - handleAutoFill과 allocations useMemo 모두 동일 로직 적용
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user