223 lines
8.1 KiB
PHP
223 lines
8.1 KiB
PHP
<?php
|
|
|
|
namespace App\Modules\Inventory\Controllers;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Modules\Inventory\Models\InventoryCountDoc;
|
|
use App\Modules\Inventory\Models\Warehouse;
|
|
use App\Modules\Inventory\Services\CountService;
|
|
use Illuminate\Http\Request;
|
|
use Inertia\Inertia;
|
|
|
|
class CountDocController extends Controller
|
|
{
|
|
protected $countService;
|
|
|
|
public function __construct(CountService $countService)
|
|
{
|
|
$this->countService = $countService;
|
|
}
|
|
|
|
public function index(Request $request)
|
|
{
|
|
$query = InventoryCountDoc::query()
|
|
->with(['createdBy', 'completedBy', 'warehouse']);
|
|
|
|
if ($request->filled('warehouse_id')) {
|
|
$query->where('warehouse_id', $request->warehouse_id);
|
|
}
|
|
|
|
if ($request->filled('search')) {
|
|
$search = $request->search;
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('doc_no', 'like', "%{$search}%")
|
|
->orWhere('remarks', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
$perPage = $request->input('per_page', 10);
|
|
if (!in_array($perPage, [10, 20, 50, 100])) {
|
|
$perPage = 10;
|
|
}
|
|
|
|
$countQuery = function ($query) {
|
|
$query->whereNotNull('counted_qty');
|
|
};
|
|
|
|
$docs = $query->withCount(['items', 'items as counted_items_count' => $countQuery])
|
|
->orderByDesc('created_at')
|
|
->paginate($perPage)
|
|
->withQueryString()
|
|
->through(function ($doc) {
|
|
return [
|
|
'id' => (string) $doc->id,
|
|
'doc_no' => $doc->doc_no,
|
|
'status' => $doc->status,
|
|
'warehouse_name' => $doc->warehouse->name,
|
|
'snapshot_date' => $doc->snapshot_date ? $doc->snapshot_date->format('Y-m-d H:i') : '-',
|
|
'completed_at' => $doc->completed_at ? $doc->completed_at->format('Y-m-d H:i') : '-',
|
|
'created_by' => $doc->createdBy?->name,
|
|
'remarks' => $doc->remarks,
|
|
'total_items' => $doc->items_count,
|
|
'counted_items' => $doc->counted_items_count,
|
|
];
|
|
});
|
|
|
|
return Inertia::render('Inventory/Count/Index', [
|
|
'docs' => $docs,
|
|
'warehouses' => Warehouse::all()->map(fn($w) => ['id' => (string)$w->id, 'name' => $w->name]),
|
|
'filters' => $request->only(['warehouse_id', 'search', 'per_page']),
|
|
]);
|
|
}
|
|
|
|
public function store(Request $request)
|
|
{
|
|
$validated = $request->validate([
|
|
'warehouse_id' => 'required|exists:warehouses,id',
|
|
'remarks' => 'nullable|string|max:255',
|
|
]);
|
|
|
|
$doc = $this->countService->createDoc(
|
|
$validated['warehouse_id'],
|
|
$validated['remarks'] ?? null,
|
|
auth()->id()
|
|
);
|
|
|
|
// 自動執行快照
|
|
$this->countService->snapshot($doc);
|
|
|
|
return redirect()->route('inventory.count.show', [$doc->id])
|
|
->with('success', '已建立盤點單並完成庫存快照');
|
|
}
|
|
|
|
public function show(InventoryCountDoc $doc)
|
|
{
|
|
$doc->load(['items.product.baseUnit', 'createdBy', 'completedBy', 'warehouse']);
|
|
|
|
$docData = [
|
|
'id' => (string) $doc->id,
|
|
'doc_no' => $doc->doc_no,
|
|
'warehouse_id' => (string) $doc->warehouse_id,
|
|
'warehouse_name' => $doc->warehouse->name,
|
|
'status' => $doc->status,
|
|
'remarks' => $doc->remarks,
|
|
'snapshot_date' => $doc->snapshot_date ? $doc->snapshot_date->format('Y-m-d H:i') : null,
|
|
'created_by' => $doc->createdBy?->name,
|
|
'items' => $doc->items->map(function ($item) {
|
|
return [
|
|
'id' => (string) $item->id,
|
|
'product_name' => $item->product->name,
|
|
'product_code' => $item->product->code,
|
|
'batch_number' => $item->batch_number,
|
|
'unit' => $item->product->baseUnit?->name,
|
|
'system_qty' => (float) $item->system_qty,
|
|
'counted_qty' => is_null($item->counted_qty) ? '' : (float) $item->counted_qty,
|
|
'diff_qty' => (float) $item->diff_qty,
|
|
'notes' => $item->notes,
|
|
];
|
|
}),
|
|
];
|
|
|
|
return Inertia::render('Inventory/Count/Show', [
|
|
'doc' => $docData,
|
|
]);
|
|
}
|
|
|
|
public function print(InventoryCountDoc $doc)
|
|
{
|
|
$doc->load(['items.product.baseUnit', 'createdBy', 'completedBy', 'warehouse']);
|
|
|
|
$docData = [
|
|
'id' => (string) $doc->id,
|
|
'doc_no' => $doc->doc_no,
|
|
'warehouse_name' => $doc->warehouse->name,
|
|
'snapshot_date' => $doc->snapshot_date ? $doc->snapshot_date->format('Y-m-d') : date('Y-m-d'), // Use date only
|
|
'created_at' => $doc->created_at->format('Y-m-d'),
|
|
'print_date' => date('Y-m-d'),
|
|
'created_by' => $doc->createdBy?->name,
|
|
'items' => $doc->items->map(function ($item) {
|
|
return [
|
|
'id' => (string) $item->id,
|
|
'product_name' => $item->product->name,
|
|
'product_code' => $item->product->code,
|
|
'specification' => $item->product->specification,
|
|
'unit' => $item->product->baseUnit?->name,
|
|
'quantity' => (float) ($item->counted_qty ?? $item->system_qty), // Default to system qty if counted is null, or just counted? User wants "Count Sheet" -> maybe blank if not counted?
|
|
// Actually, if it's "Completed", we show counted. If it's "Pending", we usually show blank or system.
|
|
// The 'Show' page logic suggests we show counted_qty.
|
|
'counted_qty' => $item->counted_qty,
|
|
'notes' => $item->notes,
|
|
];
|
|
}),
|
|
];
|
|
|
|
return Inertia::render('Inventory/Count/Print', [
|
|
'doc' => $docData,
|
|
]);
|
|
}
|
|
|
|
public function update(Request $request, InventoryCountDoc $doc)
|
|
{
|
|
if ($doc->status === 'completed') {
|
|
return redirect()->back()->with('error', '此盤點單已完成,無法修改');
|
|
}
|
|
|
|
$validated = $request->validate([
|
|
'items' => 'array',
|
|
'items.*.id' => 'required|exists:inventory_count_items,id',
|
|
'items.*.counted_qty' => 'nullable|numeric|min:0',
|
|
'items.*.notes' => 'nullable|string',
|
|
]);
|
|
|
|
if (isset($validated['items'])) {
|
|
$this->countService->updateCount($doc, $validated['items']);
|
|
}
|
|
|
|
// 如果是按了 "完成盤點"
|
|
if ($request->input('action') === 'complete') {
|
|
$this->countService->complete($doc, auth()->id());
|
|
return redirect()->route('inventory.count.index')
|
|
->with('success', '盤點單已完成');
|
|
}
|
|
|
|
return redirect()->back()->with('success', '暫存成功');
|
|
}
|
|
|
|
public function reopen(InventoryCountDoc $doc)
|
|
{
|
|
if ($doc->status !== 'completed') {
|
|
return redirect()->back()->with('error', '只有已核准的盤點單可以取消核准');
|
|
}
|
|
|
|
// TODO: Move logic to Service if complex
|
|
$doc->update([
|
|
'status' => 'counting', // Revert to counting (draft)
|
|
'completed_at' => null,
|
|
'completed_by' => null,
|
|
]);
|
|
|
|
return redirect()->route('inventory.count.show', [$doc->id])
|
|
->with('success', '已取消核准,單據回復為盤點中狀態');
|
|
}
|
|
|
|
// 記錄活動
|
|
activity()
|
|
->performedOn($doc)
|
|
->causedBy(auth()->user())
|
|
->event('deleted')
|
|
->withProperties([
|
|
'snapshot' => [
|
|
'doc_no' => $doc->doc_no,
|
|
'warehouse_name' => $doc->warehouse?->name,
|
|
]
|
|
])
|
|
->log('deleted');
|
|
|
|
$doc->items()->delete();
|
|
$doc->delete();
|
|
|
|
return redirect()->route('inventory.count.index')
|
|
->with('success', '盤點單已刪除');
|
|
}
|
|
}
|