Files
star-erp/app/Modules/Inventory/Controllers/TransferOrderController.php

269 lines
11 KiB
PHP

<?php
namespace App\Modules\Inventory\Controllers;
use App\Http\Controllers\Controller;
use App\Modules\Inventory\Models\InventoryTransferOrder;
use App\Modules\Inventory\Models\Warehouse;
use App\Modules\Inventory\Models\Inventory;
use App\Modules\Inventory\Services\TransferService;
use Illuminate\Http\Request;
use Inertia\Inertia;
class TransferOrderController extends Controller
{
protected $transferService;
public function __construct(TransferService $transferService)
{
$this->transferService = $transferService;
}
public function index(Request $request)
{
$query = InventoryTransferOrder::query()
->with(['fromWarehouse', 'toWarehouse', 'createdBy', 'postedBy']);
// 篩選:若有選定倉庫,則顯示該倉庫作為來源或目的地的調撥單
if ($request->filled('warehouse_id')) {
$query->where(function ($q) use ($request) {
$q->where('from_warehouse_id', $request->warehouse_id)
->orWhere('to_warehouse_id', $request->warehouse_id);
});
}
$perPage = $request->input('per_page', 10);
$orders = $query->orderByDesc('created_at')
->paginate($perPage)
->withQueryString()
->through(function ($order) {
return [
'id' => (string) $order->id,
'doc_no' => $order->doc_no,
'from_warehouse_name' => $order->fromWarehouse->name,
'to_warehouse_name' => $order->toWarehouse->name,
'status' => $order->status,
'created_at' => $order->created_at->format('Y-m-d H:i'),
'posted_at' => $order->posted_at ? $order->posted_at->format('Y-m-d H:i') : '-',
'created_by' => $order->createdBy?->name,
];
});
return Inertia::render('Inventory/Transfer/Index', [
'orders' => $orders,
'warehouses' => Warehouse::all()->map(fn($w) => ['id' => (string)$w->id, 'name' => $w->name]),
'filters' => $request->only(['warehouse_id', 'per_page']),
]);
}
public function store(Request $request)
{
// 兼容前端不同的參數命名 (from/source, to/target)
$fromId = $request->input('from_warehouse_id') ?? $request->input('sourceWarehouseId');
$toId = $request->input('to_warehouse_id') ?? $request->input('targetWarehouseId');
$validated = $request->validate([
'from_warehouse_id' => 'required_without:sourceWarehouseId|exists:warehouses,id',
'to_warehouse_id' => 'required_without:targetWarehouseId|exists:warehouses,id|different:from_warehouse_id',
'remarks' => 'nullable|string',
'notes' => 'nullable|string',
'instant_post' => 'boolean',
// 支援單筆商品直接建立 (撥補單模式)
'product_id' => 'nullable|exists:products,id',
'quantity' => 'nullable|numeric|min:0.01',
'batch_number' => 'nullable|string',
]);
$remarks = $validated['remarks'] ?? $validated['notes'] ?? null;
$order = $this->transferService->createOrder(
$fromId,
$toId,
$remarks,
auth()->id()
);
// 記錄活動
activity()
->performedOn($order)
->causedBy(auth()->user())
->event('created')
->withProperties([
'attributes' => $order->toArray(),
'snapshot' => [
'doc_no' => $order->doc_no,
'from_warehouse_name' => $order->fromWarehouse?->name,
'to_warehouse_name' => $order->toWarehouse?->name,
]
])
->log('created');
// 如果請求包含單筆商品資訊
if ($request->has('product_id')) {
$this->transferService->updateItems($order, [[
'product_id' => $validated['product_id'],
'quantity' => $validated['quantity'],
'batch_number' => $validated['batch_number'] ?? null,
]]);
}
// 如果是撥補單,執行直接過帳
if ($request->input('instant_post') === true) {
try {
$this->transferService->post($order, auth()->id());
// 記錄過帳活動
activity()
->performedOn($order)
->causedBy(auth()->user())
->event('posted')
->withProperties([
'attributes' => ['status' => 'posted'],
'old' => ['status' => 'draft'],
'snapshot' => [
'doc_no' => $order->doc_no,
'from_warehouse_name' => $order->fromWarehouse?->name,
'to_warehouse_name' => $order->toWarehouse?->name,
]
])
->log('posted');
return redirect()->back()->with('success', '撥補成功,庫存已更新');
} catch (\Exception $e) {
// 如果過帳失敗,雖然單據已建立,但應回報錯誤
return redirect()->back()->withErrors(['items' => $e->getMessage()]);
}
}
return redirect()->route('inventory.transfer.show', [$order->id])
->with('success', '已建立調撥單');
}
public function show(InventoryTransferOrder $order)
{
$order->load(['items.product.baseUnit', 'fromWarehouse', 'toWarehouse', 'createdBy', 'postedBy']);
$orderData = [
'id' => (string) $order->id,
'doc_no' => $order->doc_no,
'from_warehouse_id' => (string) $order->from_warehouse_id,
'from_warehouse_name' => $order->fromWarehouse->name,
'to_warehouse_id' => (string) $order->to_warehouse_id,
'to_warehouse_name' => $order->toWarehouse->name,
'status' => $order->status,
'remarks' => $order->remarks,
'created_at' => $order->created_at->format('Y-m-d H:i'),
'created_by' => $order->createdBy?->name,
'items' => $order->items->map(function ($item) use ($order) {
// 獲取來源倉庫的當前庫存
$stock = Inventory::where('warehouse_id', $order->from_warehouse_id)
->where('product_id', $item->product_id)
->where('batch_number', $item->batch_number)
->first();
return [
'id' => (string) $item->id,
'product_id' => (string) $item->product_id,
'product_name' => $item->product->name,
'product_code' => $item->product->code,
'batch_number' => $item->batch_number,
'unit' => $item->product->baseUnit?->name,
'quantity' => (float) $item->quantity,
'max_quantity' => $stock ? (float) $stock->quantity : 0.0,
'notes' => $item->notes,
];
}),
];
return Inertia::render('Inventory/Transfer/Show', [
'order' => $orderData,
]);
}
public function update(Request $request, InventoryTransferOrder $order)
{
if ($order->status !== 'draft') {
return redirect()->back()->with('error', '只能修改草稿狀態的單據');
}
if ($request->input('action') === 'post') {
try {
$this->transferService->post($order, auth()->id());
// 記錄活動
activity()
->performedOn($order)
->causedBy(auth()->user())
->event('posted')
->withProperties([
'attributes' => ['status' => 'posted'],
'old' => ['status' => 'draft'],
'snapshot' => [
'doc_no' => $order->doc_no,
'from_warehouse_name' => $order->fromWarehouse?->name,
'to_warehouse_name' => $order->toWarehouse?->name,
]
])
->log('posted');
return redirect()->route('inventory.transfer.index')
->with('success', '調撥單已過帳完成');
} catch (\Exception $e) {
return redirect()->back()->withErrors(['items' => $e->getMessage()]);
}
}
$validated = $request->validate([
'items' => 'array',
'items.*.product_id' => 'required|exists:products,id',
'items.*.quantity' => 'required|numeric|min:0.01',
'items.*.batch_number' => 'nullable|string',
'items.*.notes' => 'nullable|string',
]);
if ($request->has('items')) {
$this->transferService->updateItems($order, $validated['items']);
}
$order->update($request->only(['remarks']));
return redirect()->back()->with('success', '儲存成功');
}
public function destroy(InventoryTransferOrder $order)
{
if ($order->status !== 'draft') {
return redirect()->back()->with('error', '只能刪除草稿狀態的單據');
}
$order->items()->delete();
$order->delete();
return redirect()->route('inventory.transfer.index')
->with('success', '調撥單已刪除');
}
/**
* 獲取特定倉庫的庫存列表 (API) - 保留給前端選擇商品用
*/
public function getWarehouseInventories(Warehouse $warehouse)
{
$inventories = $warehouse->inventories()
->with(['product.baseUnit', 'product.category'])
->where('quantity', '>', 0)
->get()
->map(function ($inv) {
return [
'product_id' => (string) $inv->product_id,
'product_name' => $inv->product->name,
'product_code' => $inv->product->code, // Added code
'batch_number' => $inv->batch_number,
'quantity' => (float) $inv->quantity,
'unit_cost' => (float) $inv->unit_cost,
'unit_name' => $inv->product->baseUnit?->name ?? '個',
'expiry_date' => $inv->expiry_date ? $inv->expiry_date->format('Y-m-d') : null,
];
});
return response()->json($inventories);
}
}