only([ 'search', 'status', 'start_date', 'end_date', 'sort_by', 'sort_dir', 'per_page', 'page', ]); $receivings = $this->service->index($params); return ApiResponse::success($receivings, __('message.fetched')); } /** * 입고 통계 */ public function stats() { $stats = $this->service->stats(); return ApiResponse::success($stats, __('message.fetched')); } /** * 입고 등록 */ public function store(StoreReceivingRequest $request) { $receiving = $this->service->store($request->validated()); return ApiResponse::success($receiving, __('message.created'), [], 201); } /** * 입고 상세 */ public function show(int $id) { $receiving = $this->service->show($id); return ApiResponse::success($receiving, __('message.fetched')); } /** * 입고 수정 */ public function update(int $id, UpdateReceivingRequest $request) { $receiving = $this->service->update($id, $request->validated()); return ApiResponse::success($receiving, __('message.updated')); } /** * 입고 삭제 */ public function destroy(int $id) { $this->service->destroy($id); return ApiResponse::success(null, __('message.deleted')); } /** * 입고처리 (상태 변경 + 입고 정보 입력) */ public function process(int $id, ProcessReceivingRequest $request) { $receiving = $this->service->process($id, $request->validated()); return ApiResponse::success($receiving, __('message.receiving.processed')); } /** * [개발전용] 입고 강제 생성 (입고 + 재고 + 수입검사 한번에) * * POST /api/v1/dev/force-receiving * Body: { item_id: number, qty?: number } */ public function forceCreate(Request $request) { $request->validate([ 'item_id' => 'required|integer', 'qty' => 'nullable|integer|min:1|max:10000', ]); $tenantId = app('tenant_id'); $itemId = $request->input('item_id'); $qty = $request->input('qty', 100); $userId = auth()->id() ?? 33; $date = now()->toDateString(); $item = Item::withoutGlobalScopes() ->where('tenant_id', $tenantId) ->where('id', $itemId) ->first(); if (! $item) { return ApiResponse::error(__('error.not_found'), 404); } $result = DB::transaction(function () use ($item, $tenantId, $qty, $userId, $date) { $datePrefix = date('Ymd', strtotime($date)); $dateShort = date('ymd', strtotime($date)); // 채번 $receivingSeq = (Receiving::withoutGlobalScopes()->where('tenant_id', $tenantId) ->where('receiving_number', 'LIKE', "RV{$datePrefix}%")->count()) + 1; $lotSeq = (StockLot::withoutGlobalScopes()->where('tenant_id', $tenantId) ->where('lot_no', 'LIKE', "{$dateShort}-%")->count()) + 1; $inspSeq = (Inspection::withoutGlobalScopes()->where('tenant_id', $tenantId) ->where('inspection_no', 'LIKE', "IQC-{$dateShort}-%")->count()) + 1; $receivingNumber = 'RV'.$datePrefix.str_pad($receivingSeq, 4, '0', STR_PAD_LEFT); $lotNo = $dateShort.'-'.str_pad($lotSeq, 2, '0', STR_PAD_LEFT); $orderNo = 'PO-'.$dateShort.'-DEV'.str_pad(rand(1, 999), 3, '0', STR_PAD_LEFT); // 1. Receiving 생성 $receiving = Receiving::withoutGlobalScopes()->create([ 'tenant_id' => $tenantId, 'receiving_number' => $receivingNumber, 'order_no' => $orderNo, 'order_date' => $date, 'item_id' => $item->id, 'item_code' => $item->code, 'item_name' => $item->name, 'supplier' => '(주)테스트공급업체', 'order_qty' => $qty, 'order_unit' => $item->unit ?: 'EA', 'due_date' => $date, 'receiving_qty' => $qty, 'receiving_date' => $date, 'lot_no' => $lotNo, 'supplier_lot' => 'DEV-'.rand(1000, 9999), 'receiving_location' => 'A-01-01', 'receiving_manager' => '개발자', 'status' => 'completed', 'remark' => '[개발전용] 강제 생성 입고', 'options' => [ 'inspection_status' => '적', 'inspection_date' => $date, 'inspection_result' => '합격', 'force_created' => true, ], 'created_by' => $userId, 'updated_by' => $userId, ]); // 2. Stock 생성/갱신 $itemTypeMap = ['FG' => 'purchased_part', 'PT' => 'bent_part', 'SM' => 'sub_material', 'RM' => 'raw_material', 'CS' => 'consumable']; $stockType = $itemTypeMap[$item->item_type] ?? 'raw_material'; $stock = Stock::withoutGlobalScopes() ->where('tenant_id', $tenantId) ->where('item_id', $item->id) ->first(); if ($stock) { $stock->stock_qty += $qty; $stock->available_qty += $qty; $stock->lot_count += 1; $stock->last_receipt_date = $date; $stock->status = 'normal'; $stock->updated_by = $userId; $stock->save(); } else { $stock = Stock::withoutGlobalScopes()->create([ 'tenant_id' => $tenantId, 'item_id' => $item->id, 'item_code' => $item->code, 'item_name' => $item->name, 'item_type' => $stockType, 'unit' => $item->unit ?: 'EA', 'stock_qty' => $qty, 'safety_stock' => 10, 'reserved_qty' => 0, 'available_qty' => $qty, 'lot_count' => 1, 'oldest_lot_date' => $date, 'location' => 'A-01-01', 'status' => 'normal', 'last_receipt_date' => $date, 'created_by' => $userId, ]); } // 3. StockLot 생성 $nextFifo = (StockLot::withoutGlobalScopes()->where('stock_id', $stock->id)->max('fifo_order') ?? 0) + 1; StockLot::withoutGlobalScopes()->create([ 'tenant_id' => $tenantId, 'stock_id' => $stock->id, 'lot_no' => $lotNo, 'fifo_order' => $nextFifo, 'receipt_date' => $date, 'qty' => $qty, 'reserved_qty' => 0, 'available_qty' => $qty, 'unit' => $item->unit ?: 'EA', 'supplier' => '(주)테스트공급업체', 'supplier_lot' => $receiving->supplier_lot, 'po_number' => $orderNo, 'location' => 'A-01-01', 'status' => 'available', 'receiving_id' => $receiving->id, 'created_by' => $userId, ]); // 4. IQC 수입검사 생성 (합격) $inspectionNo = 'IQC-'.$dateShort.'-'.str_pad($inspSeq, 4, '0', STR_PAD_LEFT); Inspection::withoutGlobalScopes()->create([ 'tenant_id' => $tenantId, 'inspection_no' => $inspectionNo, 'inspection_type' => 'IQC', 'status' => 'completed', 'result' => 'pass', 'request_date' => $date, 'inspection_date' => $date, 'item_id' => $item->id, 'lot_no' => $lotNo, 'meta' => [ 'quantity' => $qty, 'unit' => $item->unit ?: 'EA', 'item_code' => $item->code, 'item_name' => $item->name, 'force_created' => true, ], 'items' => [ ['item' => '외관검사', 'standard' => '이상 없을 것', 'result' => '양호', 'judgment' => '적'], ['item' => '치수검사', 'standard' => '규격 일치', 'result' => '양호', 'judgment' => '적'], ], 'extra' => ['remarks' => '[개발전용] 자동 합격', 'opinion' => '양호'], 'created_by' => $userId, 'updated_by' => $userId, ]); return [ 'receiving_number' => $receivingNumber, 'lot_no' => $lotNo, 'item_code' => $item->code, 'item_name' => $item->name, 'qty' => $qty, 'available_qty' => $stock->available_qty, ]; }); return ApiResponse::success($result, '입고 데이터가 강제 생성되었습니다.'); } }