372 lines
12 KiB
PHP
372 lines
12 KiB
PHP
<?php
|
||
namespace App\Http\Controllers;
|
||
|
||
use App\Models\Stock;
|
||
use App\Models\StockLog;
|
||
use App\Models\ErpSku;
|
||
use App\Services\StockService;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
class StockController extends Controller
|
||
{
|
||
protected $stockService;
|
||
|
||
public function __construct(StockService $stockService)
|
||
{
|
||
$this->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);
|
||
}
|
||
}
|
||
} |