391 lines
13 KiB
PHP
391 lines
13 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers;
|
||
|
||
use App\Models\ErpSku;
|
||
use App\Models\Order;
|
||
use App\Models\DeliveryRecord;
|
||
use App\Models\PrintLog;
|
||
use App\Models\BatchPrint;
|
||
use App\Jobs\SyncDeliveryToPlatform;
|
||
use App\Services\StockService;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Illuminate\Support\Str;
|
||
|
||
class DeliveryController extends Controller
|
||
{
|
||
protected $stockService;
|
||
|
||
public function __construct(StockService $stockService)
|
||
{
|
||
$this->stockService = $stockService;
|
||
// 已删除 $this->middleware('auth:api');
|
||
}
|
||
|
||
/**
|
||
* 待发货订单列表(批量打印)
|
||
* 仅显示当前用户仓库的订单(若用户有仓库)
|
||
*/
|
||
public function pendingDelivery(Request $request)
|
||
{
|
||
$user = auth()->user();
|
||
$query = Order::with(['platformOrder', 'items.erpSku'])
|
||
->where('audit_status', 'approved')
|
||
->where('delivery_status', 'pending')
|
||
->where('print_status', 0)
|
||
->orderBy('audit_time', 'asc');
|
||
|
||
if ($user->warehouse_id) {
|
||
$query->where('warehouse_id', $user->warehouse_id);
|
||
} else {
|
||
// 无仓库权限的用户直接返回空结果
|
||
return response()->json(Order::paginate(0));
|
||
}
|
||
|
||
$orders = $query->paginate($request->get('per_page', 20));
|
||
return response()->json($orders);
|
||
}
|
||
|
||
/**
|
||
* 标记打印(支持批量)
|
||
*/
|
||
public function markPrinted(Request $request)
|
||
{
|
||
$request->validate([
|
||
'order_ids' => 'required|array',
|
||
'order_ids.*' => 'exists:orders,id',
|
||
'batch_no' => 'nullable|string|max:64',
|
||
'print_type' => 'in:normal,reprint',
|
||
'status' => 'in:success,failed',
|
||
'reason' => 'nullable|string|max:255'
|
||
]);
|
||
|
||
$orderIds = $request->order_ids;
|
||
$printType = $request->input('print_type', 'normal');
|
||
$status = $request->input('status', 'success'); // 默认成功
|
||
$reason = $request->input('reason');
|
||
|
||
$user = auth()->user();
|
||
|
||
// 校验订单是否属于当前用户仓库(如果用户有仓库限制)
|
||
if ($user->warehouse_id) {
|
||
$invalidOrders = Order::whereIn('id', $orderIds)
|
||
->where('warehouse_id', '!=', $user->warehouse_id)
|
||
->pluck('id')
|
||
->toArray();
|
||
if (!empty($invalidOrders)) {
|
||
return response()->json([
|
||
'error' => '部分订单不属于您的仓库,无法打印',
|
||
'invalid_order_ids' => $invalidOrders
|
||
], 403);
|
||
}
|
||
}
|
||
|
||
// 校验订单状态是否允许打印(已审核、待发货)
|
||
$invalidStateOrders = Order::whereIn('id', $orderIds)
|
||
->where(function ($q) {
|
||
$q->where('audit_status', '!=', 'approved')
|
||
->orWhere('delivery_status', '!=', 'pending');
|
||
})
|
||
->pluck('id')
|
||
->toArray();
|
||
if (!empty($invalidStateOrders)) {
|
||
return response()->json([
|
||
'error' => '部分订单状态不允许打印',
|
||
'invalid_order_ids' => $invalidStateOrders
|
||
], 422);
|
||
}
|
||
|
||
// 生成或使用批次号
|
||
$batchNo = $request->input('batch_no', 'BATCH_' . Str::random(12));
|
||
|
||
// 打印状态:0=未打印, 1=打印成功, 2=打印失败
|
||
$printStatus = $status === 'failed' ? 2 : 1;
|
||
|
||
DB::beginTransaction();
|
||
try {
|
||
// 批量更新订单打印状态(失败时保持待发货,不改变订单状态)
|
||
Order::whereIn('id', $orderIds)->update([
|
||
'print_status' => $printStatus,
|
||
'print_time' => $status === 'success' ? now() : null
|
||
]);
|
||
|
||
// 批量插入打印日志(记录成功或失败原因)
|
||
$printLogs = [];
|
||
foreach ($orderIds as $orderId) {
|
||
$printLogs[] = [
|
||
'order_id' => $orderId,
|
||
'batch_no' => $batchNo,
|
||
'operator_id' => $user->id,
|
||
'print_time' => now(),
|
||
'print_type' => $printType,
|
||
'reason' => $status === 'failed' ? $reason : null,
|
||
];
|
||
}
|
||
PrintLog::insert($printLogs);
|
||
|
||
// 批量打印记录(批次统计)
|
||
if (count($orderIds) > 1) {
|
||
BatchPrint::updateOrCreate(
|
||
['batch_no' => $batchNo],
|
||
[
|
||
'operator_id' => $user->id,
|
||
'order_count' => count($orderIds),
|
||
'print_time' => now()
|
||
]
|
||
);
|
||
}
|
||
|
||
DB::commit();
|
||
|
||
if ($status === 'failed') {
|
||
return response()->json([
|
||
'code' => 200,
|
||
'message' => '已记录打印失败',
|
||
'data' => ['batch_no' => $batchNo, 'count' => count($orderIds), 'reason' => $reason]
|
||
]);
|
||
}
|
||
|
||
return response()->json([
|
||
'code' => 200,
|
||
'message' => '标记打印成功',
|
||
'data' => ['batch_no' => $batchNo, 'count' => count($orderIds)]
|
||
]);
|
||
} catch (\Exception $e) {
|
||
DB::rollBack();
|
||
Log::error('标记打印失败', [
|
||
'error' => $e->getMessage(),
|
||
'trace' => $e->getTraceAsString()
|
||
]);
|
||
return response()->json(['error' => '标记失败:' . $e->getMessage()], 500);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 重打订单(单个)
|
||
*/
|
||
public function reprint(Request $request, $id)
|
||
{
|
||
$request->validate([
|
||
'reason' => 'required|string|max:255',
|
||
]);
|
||
|
||
$order = Order::findOrFail($id);
|
||
$user = auth()->user();
|
||
|
||
// 仓库权限校验
|
||
if ($user->warehouse_id && $user->warehouse_id != $order->warehouse_id) {
|
||
return response()->json(['error' => '无权操作该订单'], 403);
|
||
}
|
||
|
||
if ($order->delivery_status !== 'pending') {
|
||
return response()->json(['error' => '只有待发货的订单可以重打'], 400);
|
||
}
|
||
|
||
DB::beginTransaction();
|
||
try {
|
||
$order->print_time = now();
|
||
$order->print_status = 1;
|
||
$order->save();
|
||
|
||
PrintLog::create([
|
||
'order_id' => $id,
|
||
'batch_no' => null,
|
||
'operator_id' => $user->id,
|
||
'print_time' => now(),
|
||
'print_type' => 'reprint',
|
||
'reason' => $request->reason,
|
||
]);
|
||
|
||
DB::commit();
|
||
return response()->json(['message' => '重打成功']);
|
||
} catch (\Exception $e) {
|
||
DB::rollBack();
|
||
Log::error('重打失败', ['order_id' => $id, 'error' => $e->getMessage()]);
|
||
return response()->json(['error' => '重打失败:' . $e->getMessage()], 500);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取打印日志列表
|
||
*/
|
||
public function printLogs(Request $request)
|
||
{
|
||
$query = PrintLog::with(['order', 'operator'])->orderBy('id', 'desc');
|
||
|
||
if ($request->filled('batch_no')) {
|
||
$query->where('batch_no', $request->batch_no);
|
||
}
|
||
if ($request->filled('order_id')) {
|
||
$query->where('order_id', $request->order_id);
|
||
}
|
||
if ($request->filled('print_type')) {
|
||
$query->where('print_type', $request->print_type);
|
||
}
|
||
|
||
$logs = $query->paginate($request->get('per_page', 15));
|
||
return response()->json($logs);
|
||
}
|
||
|
||
/**
|
||
* 获取批量打印记录(批次统计)
|
||
*/
|
||
public function batchPrintLogs(Request $request)
|
||
{
|
||
$batches = BatchPrint::orderBy('id', 'desc')
|
||
->paginate($request->get('per_page', 15));
|
||
|
||
return response()->json($batches);
|
||
}
|
||
|
||
/**
|
||
* 发货并回传(集成库存扣减)
|
||
*/
|
||
public function ship(Request $request)
|
||
{
|
||
$request->validate([
|
||
'order_id' => 'required|exists:orders,id',
|
||
'tracking_no' => 'required|string|max:100',
|
||
'logistics_company' => 'required|string|max:100'
|
||
]);
|
||
|
||
Log::info('发货请求开始', ['order_id' => $request->order_id]);
|
||
|
||
// 用户认证检查(假设路由已加 auth 中间件,但为了安全仍显式判断)
|
||
$user = auth()->user();
|
||
if (!$user) {
|
||
return response()->json(['error' => '未认证'], 401);
|
||
}
|
||
|
||
$order = Order::with(['items.erpSku', 'warehouse'])->find($request->order_id);
|
||
if (!$order) {
|
||
Log::error('订单不存在', ['order_id' => $request->order_id]);
|
||
return response()->json(['error' => '订单不存在'], 404);
|
||
}
|
||
|
||
// 仓库权限校验(如果用户有仓库限制)
|
||
if ($user->warehouse_id && $user->warehouse_id != $order->warehouse_id) {
|
||
return response()->json(['error' => '无权操作该订单'], 403);
|
||
}
|
||
|
||
// 订单状态校验
|
||
if ($order->delivery_status !== 'pending') {
|
||
return response()->json(['error' => '订单状态不允许发货(非待发货)'], 400);
|
||
}
|
||
|
||
if (!$order->warehouse_id) {
|
||
return response()->json(['error' => '订单未设置发货仓库'], 400);
|
||
}
|
||
|
||
// 前置检查:商品是否都有ERP SKU且库存充足
|
||
foreach ($order->items as $item) {
|
||
if (!$item->erp_sku_id) {
|
||
return response()->json([
|
||
'error' => '订单商品未绑定ERP SKU,SKU: ' . ($item->platform_sku ?? '未知')
|
||
], 422);
|
||
}
|
||
$erpSku = ErpSku::find($item->erp_sku_id);
|
||
if (!$erpSku) {
|
||
return response()->json([
|
||
'error' => 'ERP SKU不存在,ID: ' . $item->erp_sku_id
|
||
], 422);
|
||
}
|
||
// 检查可用库存
|
||
try {
|
||
$available = $this->stockService->checkStock($erpSku->sku_code, $order->warehouse_id);
|
||
} catch (\Exception $e) {
|
||
Log::error('库存检查失败', ['sku' => $erpSku->sku_code, 'error' => $e->getMessage()]);
|
||
return response()->json(['error' => '库存检查失败:' . $e->getMessage()], 500);
|
||
}
|
||
if ($available < $item->quantity) {
|
||
return response()->json([
|
||
'error' => '库存不足,SKU: ' . $erpSku->sku_code . ' 可用库存: ' . $available . ',需扣减: ' . $item->quantity
|
||
], 422);
|
||
}
|
||
}
|
||
|
||
DB::beginTransaction();
|
||
try {
|
||
// 扣减库存(事务内再次检查并扣减,依赖 StockService 内部的原子性)
|
||
foreach ($order->items as $item) {
|
||
$erpSku = ErpSku::find($item->erp_sku_id); // 已在循环内查过,可复用变量,但简单起见再查一次
|
||
$this->stockService->ship(
|
||
$erpSku->sku_code,
|
||
$order->warehouse_id,
|
||
$item->quantity,
|
||
$order->id,
|
||
$request->tracking_no
|
||
);
|
||
}
|
||
|
||
// 更新订单状态
|
||
$order->update([
|
||
'express_company' => $request->logistics_company,
|
||
'express_no' => $request->tracking_no,
|
||
'delivery_status' => 'delivered',
|
||
'delivery_time' => now(),
|
||
'order_status' => 'shipped',
|
||
'sync_status' => 0,
|
||
]);
|
||
|
||
// 保存发货记录
|
||
DeliveryRecord::create([
|
||
'order_id' => $order->id,
|
||
'tracking_no' => $request->tracking_no,
|
||
'logistics_company'=> $request->logistics_company,
|
||
'sync_status' => 0
|
||
]);
|
||
|
||
DB::commit();
|
||
|
||
// 异步回传平台
|
||
SyncDeliveryToPlatform::dispatch($order->id);
|
||
|
||
Log::info('发货成功', ['order_id' => $order->id]);
|
||
|
||
return response()->json(['message' => '发货成功']);
|
||
} catch (\Exception $e) {
|
||
DB::rollBack();
|
||
Log::error('发货失败', [
|
||
'order_id' => $order->id,
|
||
'error' => $e->getMessage(),
|
||
'trace' => $e->getTraceAsString()
|
||
]);
|
||
return response()->json(['error' => '发货失败:' . $e->getMessage()], 500);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 回传失败列表
|
||
*/
|
||
public function syncFailed(Request $request)
|
||
{
|
||
$orders = Order::with('platformOrder')
|
||
->where('sync_status', 2)
|
||
->paginate($request->get('per_page', 15));
|
||
|
||
return response()->json($orders);
|
||
}
|
||
|
||
/**
|
||
* 重试回传
|
||
*/
|
||
public function retrySync($id)
|
||
{
|
||
$order = Order::findOrFail($id);
|
||
if ($order->sync_status != 2) {
|
||
return response()->json(['error' => '订单不是回传失败状态'], 422);
|
||
}
|
||
|
||
$order->sync_status = 0;
|
||
$order->save();
|
||
|
||
SyncDeliveryToPlatform::dispatch($order->id);
|
||
|
||
Log::info('重试回传已加入队列', ['order_id' => $order->id]);
|
||
|
||
return response()->json(['message' => '已加入重试队列']);
|
||
}
|
||
} |