erp-backend/app/Http/Controllers/StockController.php
2026-04-01 17:07:04 +08:00

372 lines
12 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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);
}
}
}