249 lines
9.5 KiB
PHP
249 lines
9.5 KiB
PHP
<?php
|
|
|
|
namespace App\Modules\Inventory\Controllers;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Modules\Inventory\Services\GoodsReceiptService;
|
|
use App\Modules\Inventory\Services\InventoryService;
|
|
use App\Modules\Procurement\Contracts\ProcurementServiceInterface;
|
|
use Illuminate\Http\Request;
|
|
use App\Modules\Procurement\Models\Vendor;
|
|
use Inertia\Inertia;
|
|
use App\Modules\Inventory\Models\GoodsReceipt;
|
|
|
|
class GoodsReceiptController extends Controller
|
|
{
|
|
protected $goodsReceiptService;
|
|
protected $inventoryService;
|
|
protected $procurementService;
|
|
|
|
public function __construct(
|
|
GoodsReceiptService $goodsReceiptService,
|
|
InventoryService $inventoryService,
|
|
ProcurementServiceInterface $procurementService
|
|
) {
|
|
$this->goodsReceiptService = $goodsReceiptService;
|
|
$this->inventoryService = $inventoryService;
|
|
$this->procurementService = $procurementService;
|
|
}
|
|
|
|
public function index(Request $request)
|
|
{
|
|
$query = GoodsReceipt::query()
|
|
->select(['id', 'code', 'type', 'warehouse_id', 'vendor_id', 'received_date', 'status', 'created_at'])
|
|
->with(['warehouse'])
|
|
->withSum('items', 'total_amount');
|
|
|
|
// 關鍵字搜尋(單號)
|
|
if ($request->filled('search')) {
|
|
$search = $request->input('search');
|
|
$query->where('code', 'like', "%{$search}%");
|
|
}
|
|
|
|
// 狀態篩選
|
|
if ($request->filled('status') && $request->input('status') !== 'all') {
|
|
$query->where('status', $request->input('status'));
|
|
}
|
|
|
|
// 倉庫篩選
|
|
if ($request->filled('warehouse_id') && $request->input('warehouse_id') !== 'all') {
|
|
$query->where('warehouse_id', $request->input('warehouse_id'));
|
|
}
|
|
|
|
// 日期範圍篩選
|
|
if ($request->filled('date_start')) {
|
|
$query->whereDate('received_date', '>=', $request->input('date_start'));
|
|
}
|
|
if ($request->filled('date_end')) {
|
|
$query->whereDate('received_date', '<=', $request->input('date_end'));
|
|
}
|
|
|
|
// 每頁筆數
|
|
$perPage = $request->input('per_page', 10);
|
|
|
|
$receipts = $query->orderBy('created_at', 'desc')
|
|
->paginate($perPage)
|
|
->withQueryString();
|
|
|
|
// Manual Hydration for Vendors (Cross-Module)
|
|
$vendorIds = collect($receipts->items())->pluck('vendor_id')->unique()->filter()->toArray();
|
|
$vendors = $this->procurementService->getVendorsByIds($vendorIds)->keyBy('id');
|
|
|
|
$receipts->getCollection()->transform(function ($receipt) use ($vendors) {
|
|
$receipt->vendor = $vendors->get($receipt->vendor_id);
|
|
return $receipt;
|
|
});
|
|
|
|
// 取得倉庫列表用於篩選
|
|
$warehouses = $this->inventoryService->getAllWarehouses();
|
|
|
|
return Inertia::render('Inventory/GoodsReceipt/Index', [
|
|
'receipts' => $receipts,
|
|
'filters' => $request->only(['search', 'status', 'warehouse_id', 'date_start', 'date_end', 'per_page']),
|
|
'warehouses' => $warehouses,
|
|
]);
|
|
}
|
|
|
|
public function show($id)
|
|
{
|
|
$receipt = GoodsReceipt::with([
|
|
'warehouse',
|
|
'items.product.category',
|
|
'items.product.baseUnit'
|
|
])->findOrFail($id);
|
|
|
|
// Manual Hydration for Vendor (Cross-Module)
|
|
if ($receipt->vendor_id) {
|
|
$receipt->vendor = $this->procurementService->getVendorsByIds([$receipt->vendor_id])->first();
|
|
}
|
|
|
|
// 手動計算統計資訊 (如果 Model 沒有定義對應的 Attribute)
|
|
$receipt->items_sum_total_amount = $receipt->items->sum('total_amount');
|
|
|
|
return Inertia::render('Inventory/GoodsReceipt/Show', [
|
|
'receipt' => $receipt
|
|
]);
|
|
}
|
|
|
|
public function create()
|
|
{
|
|
// 取得待進貨的採購單列表(用於標準採購類型選擇)
|
|
$pendingPOs = $this->procurementService->getPendingPurchaseOrders();
|
|
|
|
// 提取所有產品 ID 以便跨模組水和資料
|
|
$productIds = $pendingPOs->flatMap(fn($po) => $po->items->pluck('product_id'))->unique()->filter()->toArray();
|
|
$products = $this->inventoryService->getProductsByIds($productIds)->keyBy('id');
|
|
|
|
// 處理採購單資料,計算剩餘可收貨數量
|
|
$formattedPOs = $pendingPOs->map(function ($po) use ($products) {
|
|
return [
|
|
'id' => $po->id,
|
|
'code' => $po->code,
|
|
'status' => $po->status,
|
|
'vendor_id' => $po->vendor_id,
|
|
'vendor_name' => $po->vendor?->name ?? '',
|
|
'warehouse_id' => $po->warehouse_id,
|
|
'order_date' => $po->order_date,
|
|
'items' => $po->items->map(function ($item) use ($products) {
|
|
$product = $products->get($item->product_id);
|
|
$remaining = max(0, $item->quantity - ($item->received_quantity ?? 0));
|
|
return [
|
|
'id' => $item->id,
|
|
'product_id' => $item->product_id,
|
|
'product_name' => $product?->name ?? '',
|
|
'product_code' => $product?->code ?? '',
|
|
'unit' => $product?->baseUnit?->name ?? '個',
|
|
'quantity' => $item->quantity,
|
|
'received_quantity' => $item->received_quantity ?? 0,
|
|
'remaining' => $remaining,
|
|
'unit_price' => $item->unit_price,
|
|
];
|
|
})->filter(fn($item) => $item['remaining'] > 0)->values(),
|
|
];
|
|
})->filter(fn($po) => $po['items']->count() > 0)->values();
|
|
|
|
// 取得所有廠商列表(用於雜項入庫/其他類型選擇)
|
|
$vendors = $this->procurementService->getAllVendors();
|
|
|
|
return Inertia::render('Inventory/GoodsReceipt/Create', [
|
|
'warehouses' => $this->inventoryService->getAllWarehouses(),
|
|
'pendingPurchaseOrders' => $formattedPOs,
|
|
'vendors' => $vendors,
|
|
]);
|
|
}
|
|
|
|
public function store(Request $request)
|
|
{
|
|
$validated = $request->validate([
|
|
'warehouse_id' => 'required|exists:warehouses,id',
|
|
'type' => 'required|in:standard,miscellaneous,other',
|
|
'purchase_order_id' => 'nullable|required_if:type,standard|exists:purchase_orders,id',
|
|
// Vendor ID is required if standard, but optional/nullable for misc/other?
|
|
// Stick to existing logic: if standard, we infer vendor from PO usually, or frontend sends it.
|
|
// For now let's make vendor_id optional for misc/other or user must select one?
|
|
// "雜項入庫" might not have a vendor. Let's make it nullable.
|
|
'vendor_id' => 'nullable|integer',
|
|
'received_date' => 'required|date',
|
|
'remarks' => 'nullable|string',
|
|
'items' => 'required|array|min:1',
|
|
'items.*.product_id' => 'required|integer|exists:products,id',
|
|
'items.*.purchase_order_item_id' => 'nullable|required_if:type,standard|integer',
|
|
'items.*.quantity_received' => 'required|numeric|min:0',
|
|
'items.*.unit_price' => 'required|numeric|min:0',
|
|
'items.*.batch_number' => 'nullable|string',
|
|
'items.*.expiry_date' => 'nullable|date',
|
|
]);
|
|
|
|
$this->goodsReceiptService->store($validated);
|
|
|
|
return redirect()->route('goods-receipts.index')->with('success', '進貨單已建立');
|
|
}
|
|
|
|
// API to search POs
|
|
public function searchPOs(Request $request)
|
|
{
|
|
$search = $request->input('query');
|
|
if (!$search) {
|
|
return response()->json([]);
|
|
}
|
|
|
|
$pos = $this->procurementService->searchPendingPurchaseOrders($search);
|
|
|
|
return response()->json($pos);
|
|
}
|
|
|
|
// API to search Products for Manual Entry
|
|
public function searchProducts(Request $request)
|
|
{
|
|
$search = $request->input('query');
|
|
if (!$search) {
|
|
return response()->json([]);
|
|
}
|
|
|
|
$products = $this->inventoryService->getProductsByName($search);
|
|
|
|
// Format for frontend
|
|
$mapped = $products->map(function($product) {
|
|
return [
|
|
'id' => $product->id,
|
|
'name' => $product->name,
|
|
'code' => $product->code,
|
|
'unit' => $product->baseUnit?->name ?? '個', // Ensure unit is included
|
|
'price' => $product->purchase_price ?? 0, // Suggest price from product info if available
|
|
];
|
|
});
|
|
|
|
return response()->json($mapped);
|
|
}
|
|
|
|
// API to search Vendors
|
|
public function searchVendors(Request $request)
|
|
{
|
|
$search = $request->input('query');
|
|
if (!$search) {
|
|
return response()->json([]);
|
|
}
|
|
|
|
$vendors = $this->procurementService->searchVendors($search);
|
|
|
|
return response()->json($vendors);
|
|
}
|
|
|
|
/**
|
|
* 刪除進貨單
|
|
*/
|
|
public function destroy(GoodsReceipt $goodsReceipt)
|
|
{
|
|
// 只有有權限的人可以刪除
|
|
if (!auth()->user()->can('goods_receipts.delete')) {
|
|
return redirect()->back()->with('error', '您沒有權限刪除進貨單');
|
|
}
|
|
|
|
// 簡單刪除邏輯:刪除進貨單(品項由資料庫級聯刪除或手動處理)
|
|
// 注意:實務上可能需要處理已入庫的庫存回滾,但在這個簡易 ERP 中通常是行政刪除
|
|
$goodsReceipt->delete();
|
|
|
|
return redirect()->route('goods-receipts.index')->with('success', '進貨單已刪除');
|
|
}
|
|
}
|