fix: [production] 자재투입 bom_group_key 개별 저장 — 동일 자재 다중 BOM 그룹 지원

- bom_group_key 컬럼 추가 마이그레이션 (work_order_material_inputs)
- WorkOrderMaterialInput 모델 fillable에 bom_group_key 추가
- MaterialInputForItemRequest에 bom_group_key 검증 + replace 옵션 추가
- WorkOrderService.getMaterialsForItem: stock_lot_id+bom_group_key 복합키 기투입 조회 (하위호환)
- WorkOrderService.registerMaterialInputForItem: bom_group_key 저장 + replace 모드 (기존 삭제→재등록)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 10:36:38 +09:00
parent 8518621432
commit 5ee97c2d74
4 changed files with 79 additions and 2 deletions

View File

@@ -3240,6 +3240,30 @@ public function getMaterialsForItem(int $workOrderId, int $itemId): array
->groupBy('item_id')
->pluck('total_qty', 'item_id');
// LOT별 기투입 수량 조회 (stock_lot_id + bom_group_key별 SUM)
$lotInputtedRaw = WorkOrderMaterialInput::where('tenant_id', $tenantId)
->where('work_order_id', $workOrderId)
->where('work_order_item_id', $itemId)
->whereNotNull('stock_lot_id')
->selectRaw('stock_lot_id, bom_group_key, SUM(qty) as total_qty')
->groupBy('stock_lot_id', 'bom_group_key')
->get();
// bom_group_key 포함 복합키 매핑 + stock_lot_id 단순 매핑 (하위호환)
$lotInputtedByGroup = [];
$lotInputtedByLot = [];
foreach ($lotInputtedRaw as $row) {
$lotId = $row->stock_lot_id;
$groupKey = $row->bom_group_key;
$qty = (float) $row->total_qty;
if ($groupKey) {
$compositeKey = $lotId.'_'.$groupKey;
$lotInputtedByGroup[$compositeKey] = ($lotInputtedByGroup[$compositeKey] ?? 0) + $qty;
}
$lotInputtedByLot[$lotId] = ($lotInputtedByLot[$lotId] ?? 0) + $qty;
}
// 자재별 LOT 조회
$materials = [];
$rank = 1;
@@ -3283,6 +3307,7 @@ public function getMaterialsForItem(int $workOrderId, int $itemId): array
'required_qty' => $matInfo['required_qty'],
'already_inputted' => $alreadyInputted,
'remaining_required_qty' => $remainingRequired,
'lot_inputted_qty' => (float) ($lotInputtedByGroup[$lot->id.'_'.$bomGroupKey] ?? $lotInputtedByLot[$lot->id] ?? 0),
'lot_qty' => (float) $lot->qty,
'lot_available_qty' => (float) $lot->available_qty,
'lot_reserved_qty' => (float) $lot->reserved_qty,
@@ -3310,6 +3335,7 @@ public function getMaterialsForItem(int $workOrderId, int $itemId): array
'required_qty' => $matInfo['required_qty'],
'already_inputted' => $alreadyInputted,
'remaining_required_qty' => $remainingRequired,
'lot_inputted_qty' => 0,
'lot_qty' => 0,
'lot_available_qty' => 0,
'lot_reserved_qty' => 0,
@@ -3328,8 +3354,10 @@ public function getMaterialsForItem(int $workOrderId, int $itemId): array
/**
* 개소별 자재 투입 등록
*
* @param bool $replace true면 기존 투입 이력을 삭제(재고 복원) 후 새로 등록
*/
public function registerMaterialInputForItem(int $workOrderId, int $itemId, array $inputs): array
public function registerMaterialInputForItem(int $workOrderId, int $itemId, array $inputs, bool $replace = false): array
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
@@ -3347,13 +3375,32 @@ public function registerMaterialInputForItem(int $workOrderId, int $itemId, arra
throw new NotFoundHttpException(__('error.not_found'));
}
return DB::transaction(function () use ($inputs, $tenantId, $userId, $workOrderId, $itemId) {
return DB::transaction(function () use ($inputs, $tenantId, $userId, $workOrderId, $itemId, $replace) {
$stockService = app(StockService::class);
$inputResults = [];
// replace 모드: 기존 투입 이력 삭제 + 재고 복원
if ($replace) {
$existingInputs = WorkOrderMaterialInput::where('tenant_id', $tenantId)
->where('work_order_id', $workOrderId)
->where('work_order_item_id', $itemId)
->get();
foreach ($existingInputs as $existing) {
$stockService->increaseToLot(
stockLotId: $existing->stock_lot_id,
qty: (float) $existing->qty,
reason: 'work_order_input_replace',
referenceId: $workOrderId
);
$existing->delete();
}
}
foreach ($inputs as $input) {
$stockLotId = $input['stock_lot_id'] ?? null;
$qty = (float) ($input['qty'] ?? 0);
$bomGroupKey = $input['bom_group_key'] ?? null;
if (! $stockLotId || $qty <= 0) {
continue;
@@ -3378,6 +3425,7 @@ public function registerMaterialInputForItem(int $workOrderId, int $itemId, arra
'work_order_item_id' => $itemId,
'stock_lot_id' => $stockLotId,
'item_id' => $lotItemId ?? 0,
'bom_group_key' => $bomGroupKey,
'qty' => $qty,
'input_by' => $userId,
'input_at' => now(),