stockService = $stockService; } /** * 库存列表(分页,支持搜索、仓库筛选、预警筛选) */ public function index(Request $request) { $query = Stock::with(['warehouse', 'sku']); if ($request->filled('sku_code')) { $query->where('sku_code', 'like', "%{$request->sku_code}%"); } if ($request->filled('warehouse_id')) { $query->where('warehouse_id', $request->warehouse_id); } if ($request->boolean('low_stock')) { $query->whereRaw('quantity - locked_quantity <= warning_threshold'); } if ($request->filled('sku_name')) { $query->where('sku_name', 'like', "%{$request->sku_name}%"); } $perPage = $request->input('limit', 15); $stocks = $query->orderBy('updated_at', 'desc')->paginate($perPage); // 格式化可用库存 $stocks->getCollection()->transform(function ($stock) { return [ 'id' => $stock->id, 'sku_code' => $stock->sku_code, 'sku_name' => $stock->sku_name, 'warehouse_id' => $stock->warehouse_id, 'warehouse_name' => $stock->warehouse->name ?? null, 'quantity' => $stock->quantity, 'locked_quantity' => $stock->locked_quantity, 'available_quantity' => $stock->available_quantity, 'defective_quantity' => $stock->defective_quantity, 'warning_threshold' => $stock->warning_threshold, 'is_low' => $stock->available_quantity <= $stock->warning_threshold, 'created_at' => $stock->created_at, 'updated_at' => $stock->updated_at, ]; }); return response()->json([ 'code' => 200, 'data' => [ 'list' => $stocks->items(), 'total' => $stocks->total(), 'current_page' => $stocks->currentPage(), 'last_page' => $stocks->lastPage(), ], 'message' => 'success' ]); } /** * 库存详情(按仓库+SKU) */ public function show(Request $request) { $request->validate([ 'sku_code' => 'required|string', 'warehouse_id' => 'required|exists:warehouses,id', ]); $stock = Stock::where('sku_code', $request->sku_code) ->where('warehouse_id', $request->warehouse_id) ->with(['warehouse', 'sku']) ->first(); if (!$stock) { return response()->json(['code' => 404, 'message' => '库存记录不存在'], 404); } return response()->json([ 'code' => 200, 'data' => [ 'sku_code' => $stock->sku_code, 'sku_name' => $stock->sku_name, 'warehouse_id' => $stock->warehouse_id, 'warehouse_name' => $stock->warehouse->name, 'quantity' => $stock->quantity, 'locked_quantity' => $stock->locked_quantity, 'available_quantity' => $stock->available_quantity, 'defective_quantity' => $stock->defective_quantity, 'warning_threshold' => $stock->warning_threshold, ], 'message' => 'success' ]); } /** * 库存流水列表 */ public function logs(Request $request) { $query = StockLog::with(['warehouse', 'order']); if ($request->filled('sku_code')) { $query->where('sku_code', $request->sku_code); } if ($request->filled('warehouse_id')) { $query->where('warehouse_id', $request->warehouse_id); } if ($request->filled('type')) { $query->where('type', $request->type); } if ($request->filled('order_id')) { $query->where('order_id', $request->order_id); } if ($request->filled('delivery_no')) { $query->where('delivery_no', 'like', "%{$request->delivery_no}%"); } if ($request->filled('start_date')) { $query->where('created_at', '>=', $request->start_date); } if ($request->filled('end_date')) { $query->where('created_at', '<=', $request->end_date . ' 23:59:59'); } $perPage = $request->input('limit', 15); $logs = $query->orderBy('created_at', 'desc')->paginate($perPage); $logs->getCollection()->transform(function ($log) { return [ 'id' => $log->id, 'sku_code' => $log->sku_code, 'warehouse_id' => $log->warehouse_id, 'warehouse_name' => $log->warehouse->name ?? null, 'change_quantity' => $log->change_quantity, 'type' => $log->type, 'type_label' => $this->getTypeLabel($log->type), 'related_no' => $log->related_no, 'order_id' => $log->order_id, 'order_short_id' => $log->order->short_id ?? null, 'delivery_no' => $log->delivery_no, 'remark' => $log->remark, 'created_at' => $log->created_at, ]; }); return response()->json([ 'code' => 200, 'data' => [ 'list' => $logs->items(), 'total' => $logs->total(), 'current_page' => $logs->currentPage(), 'last_page' => $logs->lastPage(), ], 'message' => 'success' ]); } private function getTypeLabel($type) { $map = [ 'inbound' => '入库', 'outbound' => '出库', 'lock' => '占用', 'unlock' => '释放占用', 'ship' => '发货出库', 'defective_inbound' => '残次品入库', 'adjust' => '手动调整', ]; return $map[$type] ?? $type; } /** * 更新库存预警阈值 */ public function updateThreshold(Request $request) { $request->validate([ 'sku_code' => 'required|string', 'warehouse_id' => 'required|exists:warehouses,id', 'warning_threshold' => 'required|integer|min:0', ]); $stock = Stock::firstOrCreate( [ 'sku_code' => $request->sku_code, 'warehouse_id' => $request->warehouse_id, ], [ 'sku_name' => $this->getSkuName($request->sku_code), 'quantity' => 0, 'locked_quantity' => 0, 'defective_quantity' => 0, ] ); $stock->warning_threshold = $request->warning_threshold; $stock->save(); return response()->json([ 'code' => 200, 'data' => $stock, 'message' => '更新成功' ]); } private function getSkuName($skuCode) { $sku = ErpSku::where('sku_code', $skuCode)->first(); return $sku ? $sku->name : '未知商品'; } /** * 手动入库 */ public function manualInbound(Request $request) { $request->validate([ 'sku_code' => 'required|string', 'warehouse_id' => 'required|exists:warehouses,id', 'quantity' => 'required|integer|min:1', 'remark' => 'nullable|string', 'related_no' => 'nullable|string', ]); try { $stock = $this->stockService->inbound( $request->sku_code, $request->warehouse_id, $request->quantity, $request->related_no, $request->remark ); return response()->json([ 'code' => 200, 'data' => [ 'sku_code' => $stock->sku_code, 'warehouse_id' => $stock->warehouse_id, 'quantity' => $stock->quantity, 'locked_quantity' => $stock->locked_quantity, 'available_quantity' => $stock->available_quantity, ], 'message' => '入库成功' ]); } catch (\Exception $e) { return response()->json(['code' => 500, 'message' => $e->getMessage()], 500); } } /** * 手动出库 */ public function manualOutbound(Request $request) { $request->validate([ 'sku_code' => 'required|string', 'warehouse_id' => 'required|exists:warehouses,id', 'quantity' => 'required|integer|min:1', 'remark' => 'nullable|string', 'related_no' => 'nullable|string', ]); try { $stock = $this->stockService->outbound( $request->sku_code, $request->warehouse_id, $request->quantity, $request->related_no, $request->remark ); return response()->json([ 'code' => 200, 'data' => [ 'sku_code' => $stock->sku_code, 'warehouse_id' => $stock->warehouse_id, 'quantity' => $stock->quantity, 'locked_quantity' => $stock->locked_quantity, 'available_quantity' => $stock->available_quantity, ], 'message' => '出库成功' ]); } catch (\Exception $e) { return response()->json(['code' => 500, 'message' => $e->getMessage()], 500); } } /** * 残次品入库 */ public function defectiveInbound(Request $request) { $request->validate([ 'sku_code' => 'required|string', 'warehouse_id' => 'required|exists:warehouses,id', 'quantity' => 'required|integer|min:1', 'remark' => 'nullable|string', 'order_id' => 'nullable|exists:orders,id', 'related_no' => 'nullable|string', ]); try { $stock = $this->stockService->defectiveInbound( $request->sku_code, $request->warehouse_id, $request->quantity, $request->related_no, $request->remark, $request->order_id ); return response()->json([ 'code' => 200, 'message' => '残次品入库成功' ]); } catch (\Exception $e) { return response()->json(['code' => 500, 'message' => $e->getMessage()], 500); } } /** * 手动调整库存(盘点校准) */ public function adjust(Request $request) { $request->validate([ 'sku_code' => 'required|string', 'warehouse_id' => 'required|exists:warehouses,id', 'quantity' => 'nullable|integer|min:0', 'locked_quantity' => 'nullable|integer|min:0', 'defective_quantity' => 'nullable|integer|min:0', 'remark' => 'nullable|string', ]); try { $stock = $this->stockService->adjust( $request->sku_code, $request->warehouse_id, $request->quantity, $request->locked_quantity, $request->defective_quantity, $request->remark ); return response()->json([ 'code' => 200, 'data' => [ 'quantity' => $stock->quantity, 'locked_quantity' => $stock->locked_quantity, 'defective_quantity' => $stock->defective_quantity, ], 'message' => '调整成功' ]); } catch (\Exception $e) { return response()->json(['code' => 500, 'message' => $e->getMessage()], 500); } } }