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

396 lines
14 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Goods;
use App\Models\Brand;
use App\Models\Supplier;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class GoodsController extends Controller
{
/**
* 商品列表
*/
public function index(Request $request)
{
$query = Goods::with(['brand', 'suppliers']);
// 搜索
if ($request->filled('keyword')) {
$keyword = $request->keyword;
$query->where(function ($q) use ($keyword) {
$q->where('name', 'like', "%{$keyword}%")
->orWhere('code', 'like', "%{$keyword}%")
->orWhere('barcode', 'like', "%{$keyword}%");
});
}
// 类型筛选
if ($request->filled('type')) {
$query->where('type', $request->type);
}
// 品牌筛选
if ($request->filled('brand_id')) {
$query->where('brand_id', $request->brand_id);
}
$perPage = $request->input('limit', 15);
$goods = $query->orderBy('created_at', 'desc')->paginate($perPage);
// 格式化数据
$goods->getCollection()->transform(function ($item) {
return [
'id' => $item->id,
'name' => $item->name,
'code' => $item->code,
'barcode' => $item->barcode,
'type' => $item->type,
'type_label' => $item->type == 'normal' ? '普通商品' : '组套商品',
'retail_price' => $item->retail_price,
'cost_price' => $item->cost_price,
'unit' => $item->unit,
'brand' => $item->brand ? ['id' => $item->brand->id, 'name' => $item->brand->name] : null,
'suppliers' => $item->suppliers->map(function ($s) {
return ['id' => $s->id, 'name' => $s->name];
}),
'weight' => $item->weight,
'packaging_cost' => $item->packaging_cost,
'shipping_packaging_cost' => $item->shipping_packaging_cost,
'volume' => $item->volume,
'length' => $item->length,
'width' => $item->width,
'height' => $item->height,
'batch_management' => $item->batch_management,
'created_at' => $item->created_at,
'updated_at' => $item->updated_at,
];
});
return response()->json([
'code' => 200,
'data' => [
'list' => $goods->items(),
'total' => $goods->total(),
'current_page' => $goods->currentPage(),
'last_page' => $goods->lastPage(),
],
'message' => 'success'
]);
}
/**
* 获取所有商品(用于下拉选择)
*/
public function all(Request $request)
{
$query = Goods::select('id', 'name', 'code', 'retail_price', 'cost_price', 'type');
if ($request->filled('type')) {
$query->where('type', $request->type);
}
$goods = $query->get();
return response()->json([
'code' => 200,
'data' => $goods,
'message' => 'success'
]);
}
/**
* 商品详情
*/
public function show($id)
{
$goods = Goods::with(['brand', 'suppliers', 'comboItems.goods'])->findOrFail($id);
$data = [
'id' => $goods->id,
'name' => $goods->name,
'code' => $goods->code,
'barcode' => $goods->barcode,
'category' => $goods->category,
'unit' => $goods->unit,
'weight' => $goods->weight,
'retail_price' => $goods->retail_price,
'cost_price' => $goods->cost_price,
'stock_warning' => $goods->stock_warning,
'type' => $goods->type,
'custom_id' => $goods->custom_id,
'packaging_cost' => $goods->packaging_cost,
'shipping_packaging_cost' => $goods->shipping_packaging_cost,
'volume' => $goods->volume,
'length' => $goods->length,
'width' => $goods->width,
'height' => $goods->height,
'brand_id' => $goods->brand_id,
'brand' => $goods->brand ? ['id' => $goods->brand->id, 'name' => $goods->brand->name] : null,
'suppliers' => $goods->suppliers->map(function ($s) {
return ['id' => $s->id, 'name' => $s->name];
}),
'batch_management' => $goods->batch_management,
];
if ($goods->type == 'combo') {
$data['combo_items'] = $goods->comboItems->map(function ($item) {
return [
'goods_id' => $item->goods_id,
'goods_name' => $item->goods->name,
'goods_code' => $item->goods->code,
'quantity' => $item->quantity,
];
});
}
return response()->json([
'code' => 200,
'data' => $data,
'message' => 'success'
]);
}
/**
* 创建商品
*/
public function store(Request $request)
{
$rules = [
'name' => 'required|string|max:255',
'barcode' => 'nullable|string|max:100',
'type' => 'required|in:normal,combo',
'unit' => 'required|string|max:20',
'retail_price' => 'required|numeric|min:0',
'cost_price' => 'required|numeric|min:0',
'weight' => 'nullable|numeric|min:0',
'packaging_cost' => 'nullable|numeric|min:0',
'shipping_packaging_cost' => 'nullable|numeric|min:0',
'volume' => 'nullable|numeric|min:0',
'length' => 'nullable|numeric|min:0',
'width' => 'nullable|numeric|min:0',
'height' => 'nullable|numeric|min:0',
'brand_id' => 'nullable|exists:brands,id',
'supplier_ids' => 'nullable|array',
'supplier_ids.*' => 'exists:suppliers,id',
'batch_management' => 'nullable|boolean',
'custom_id' => 'nullable|string|max:100',
'category' => 'nullable|string|max:50',
'stock_warning' => 'nullable|integer|min:0',
];
// 如果是组套商品,需要验证组合项
if ($request->type == 'combo') {
$rules['combo_items'] = 'required|array|min:1';
$rules['combo_items.*.goods_id'] = 'required|exists:goods,id';
$rules['combo_items.*.quantity'] = 'required|integer|min:1';
}
$request->validate($rules);
// 自动生成商品编码(如果未提供)
$code = $request->code;
if (!$code) {
$code = $this->generateGoodsCode();
} else {
// 检查唯一性
if (Goods::where('code', $code)->exists()) {
return response()->json(['code' => 400, 'message' => '商品编码已存在'], 400);
}
}
try {
DB::beginTransaction();
$goods = Goods::create([
'name' => $request->name,
'code' => $code,
'barcode' => $request->barcode,
'category' => $request->category,
'unit' => $request->unit,
'weight' => $request->weight ?? 0,
'retail_price' => $request->retail_price,
'cost_price' => $request->cost_price,
'stock_warning' => $request->stock_warning ?? 0,
'type' => $request->type,
'custom_id' => $request->custom_id,
'packaging_cost' => $request->packaging_cost ?? 0,
'shipping_packaging_cost' => $request->shipping_packaging_cost ?? 0,
'volume' => $request->volume ?? 0,
'length' => $request->length ?? 0,
'width' => $request->width ?? 0,
'height' => $request->height ?? 0,
'brand_id' => $request->brand_id,
'batch_management' => $request->batch_management ?? false,
]);
// 关联供应商
if ($request->has('supplier_ids')) {
$goods->suppliers()->sync($request->supplier_ids);
}
// 如果是组套商品,添加组合项
if ($request->type == 'combo' && $request->has('combo_items')) {
foreach ($request->combo_items as $item) {
// 检查子商品不能是组套商品(避免嵌套)
$childGoods = Goods::find($item['goods_id']);
if ($childGoods->type == 'combo') {
throw new \Exception('组合商品不能包含另一个组合商品');
}
$goods->comboItems()->create([
'goods_id' => $item['goods_id'],
'quantity' => $item['quantity'],
]);
}
}
DB::commit();
return response()->json([
'code' => 200,
'data' => ['id' => $goods->id],
'message' => '创建成功'
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'code' => 500,
'message' => '创建失败:' . $e->getMessage()
], 500);
}
}
/**
* 更新商品
*/
public function update(Request $request, $id)
{
$goods = Goods::findOrFail($id);
$rules = [
'name' => 'sometimes|required|string|max:255',
'barcode' => 'sometimes|required|string|max:100',
'type' => 'sometimes|required|in:normal,combo',
'unit' => 'sometimes|required|string|max:20',
'retail_price' => 'sometimes|required|numeric|min:0',
'cost_price' => 'sometimes|required|numeric|min:0',
'weight' => 'nullable|numeric|min:0',
'packaging_cost' => 'nullable|numeric|min:0',
'shipping_packaging_cost' => 'nullable|numeric|min:0',
'volume' => 'nullable|numeric|min:0',
'length' => 'nullable|numeric|min:0',
'width' => 'nullable|numeric|min:0',
'height' => 'nullable|numeric|min:0',
'brand_id' => 'nullable|exists:brands,id',
'supplier_ids' => 'nullable|array',
'supplier_ids.*' => 'exists:suppliers,id',
'batch_management' => 'nullable|boolean',
'custom_id' => 'nullable|string|max:100',
'category' => 'nullable|string|max:50',
'stock_warning' => 'nullable|integer|min:0',
];
if ($request->has('code') && $request->code != $goods->code) {
$rules['code'] = 'required|string|unique:goods,code,' . $id;
}
if ($request->type == 'combo') {
$rules['combo_items'] = 'sometimes|array|min:1';
$rules['combo_items.*.goods_id'] = 'required|exists:goods,id';
$rules['combo_items.*.quantity'] = 'required|integer|min:1';
}
$request->validate($rules);
try {
DB::beginTransaction();
$updateData = $request->only([
'name', 'code', 'barcode', 'category', 'unit', 'weight',
'retail_price', 'cost_price', 'stock_warning', 'type',
'custom_id', 'packaging_cost', 'shipping_packaging_cost',
'volume', 'length', 'width', 'height', 'brand_id', 'batch_management'
]);
$goods->update($updateData);
// 更新供应商关联
if ($request->has('supplier_ids')) {
$goods->suppliers()->sync($request->supplier_ids);
}
// 更新组合项(如果类型是组套)
if ($request->type == 'combo') {
if ($request->has('combo_items')) {
// 删除旧的组合项
$goods->comboItems()->delete();
// 添加新的
foreach ($request->combo_items as $item) {
$childGoods = Goods::find($item['goods_id']);
if ($childGoods->type == 'combo') {
throw new \Exception('组合商品不能包含另一个组合商品');
}
$goods->comboItems()->create([
'goods_id' => $item['goods_id'],
'quantity' => $item['quantity'],
]);
}
}
} else {
// 如果是普通商品,删除所有组合项
$goods->comboItems()->delete();
}
DB::commit();
return response()->json([
'code' => 200,
'message' => '更新成功'
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'code' => 500,
'message' => '更新失败:' . $e->getMessage()
], 500);
}
}
/**
* 删除商品
*/
public function destroy($id)
{
$goods = Goods::findOrFail($id);
// 检查是否被组合商品引用
if ($goods->parentCombos()->exists()) {
return response()->json([
'code' => 400,
'message' => '该商品已被组合商品引用,无法删除'
], 400);
}
$goods->delete();
return response()->json([
'code' => 200,
'message' => '删除成功'
]);
}
/**
* 生成商品编码
*/
private function generateGoodsCode()
{
$prefix = 'G';
$date = date('Ymd');
$last = Goods::where('code', 'like', $prefix . $date . '%')
->orderBy('code', 'desc')
->first();
if ($last) {
$num = intval(substr($last->code, -4)) + 1;
} else {
$num = 1;
}
return $prefix . $date . str_pad($num, 4, '0', STR_PAD_LEFT);
}
}