Files
star-erp/app/Modules/Inventory/Controllers/AdjustDocController.php
sky121113 2eb136d280 feat(inventory): 完善庫存盤調更新與日誌邏輯,新增「無需盤調」狀態判定
1. 修正 AdjustDocController 缺失 update 方法導致的錯誤。
2. 修正 ActivityDetailDialog 前端 map 渲染 undefined 的 TypeError。
3. 優化盤調單「過帳」日誌,現在會同步包含當時的商品明細快照。
4. 實作盤點單「無需盤調」(no_adjust) 自動判定邏輯:
   - 當盤點數量與庫存完全一致時,自動標記為 no_adjust 結案。
   - 更新前端標籤樣式與操作按鈕對應邏輯。
   - 限制 no_adjust 單據不可重複建立盤調單。
5. 統一盤點單與盤調單的日誌配置,優化 ID 轉名稱顯示。
2026-02-04 16:56:08 +08:00

231 lines
8.3 KiB
PHP

<?php
namespace App\Modules\Inventory\Controllers;
use App\Http\Controllers\Controller;
use App\Modules\Inventory\Models\InventoryAdjustDoc;
use App\Modules\Inventory\Models\InventoryCountDoc;
use App\Modules\Inventory\Models\Warehouse;
use App\Modules\Inventory\Services\AdjustService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
class AdjustDocController extends Controller
{
protected $adjustService;
public function __construct(AdjustService $adjustService)
{
$this->adjustService = $adjustService;
}
public function index(Request $request)
{
$query = InventoryAdjustDoc::query()
->with(['createdBy', 'postedBy', 'warehouse']);
// 搜尋
if ($request->filled('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('doc_no', 'like', "%{$search}%")
->orWhere('reason', 'like', "%{$search}%")
->orWhere('remarks', 'like', "%{$search}%");
});
}
if ($request->filled('warehouse_id')) {
$query->where('warehouse_id', $request->warehouse_id);
}
$perPage = $request->input('per_page', 10);
$docs = $query->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,
'reason' => $doc->reason,
'created_at' => $doc->created_at->format('Y-m-d H:i'),
'posted_at' => $doc->posted_at ? $doc->posted_at->format('Y-m-d H:i') : '-',
'created_by' => $doc->createdBy?->name,
'remarks' => $doc->remarks,
];
});
return Inertia::render('Inventory/Adjust/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)
{
// 模式 1: 從盤點單建立
if ($request->filled('count_doc_id')) {
$countDoc = InventoryCountDoc::findOrFail($request->count_doc_id);
if ($countDoc->status !== 'completed') {
$errorMsg = $countDoc->status === 'no_adjust'
? '此盤點單無庫存差異,無需建立盤調單'
: '只有已完成盤點的單據可以建立盤調單';
return redirect()->back()->with('error', $errorMsg);
}
// 檢查是否已存在對應的盤調單 (避免重複建立)
if (InventoryAdjustDoc::where('count_doc_id', $countDoc->id)->exists()) {
return redirect()->back()->with('error', '此盤點單已建立過盤調單');
}
$doc = $this->adjustService->createFromCountDoc($countDoc, auth()->id());
return redirect()->route('inventory.adjust.show', [$doc->id])
->with('success', '已從盤點單生成盤調單');
}
// 模式 2: 一般手動調整 (保留原始邏輯但更新訊息)
$validated = $request->validate([
'warehouse_id' => 'required',
'reason' => 'required|string',
'remarks' => 'nullable|string',
]);
$doc = $this->adjustService->createDoc(
$validated['warehouse_id'],
$validated['reason'],
$validated['remarks'],
auth()->id()
);
return redirect()->route('inventory.adjust.show', [$doc->id])
->with('success', '已建立盤調單');
}
/**
* API: 獲取可盤調的已完成盤點單 (支援掃描單號)
*/
public function getPendingCounts(Request $request)
{
$query = InventoryCountDoc::where('status', 'completed')
->whereNotExists(function ($query) {
$query->select(DB::raw(1))
->from('inventory_adjust_docs')
->whereColumn('inventory_adjust_docs.count_doc_id', 'inventory_count_docs.id');
});
if ($request->filled('search')) {
$search = $request->search;
$query->where('doc_no', 'like', "%{$search}%");
}
$counts = $query->limit(10)->get()->map(function($c) {
return [
'id' => (string)$c->id,
'doc_no' => $c->doc_no,
'warehouse_name' => $c->warehouse->name,
'completed_at' => $c->completed_at->format('Y-m-d H:i'),
];
});
return response()->json($counts);
}
public function update(Request $request, InventoryAdjustDoc $doc)
{
$action = $request->input('action', 'update');
if ($action === 'post') {
if ($doc->status !== 'draft') {
return redirect()->back()->with('error', '只有草稿狀態的單據可以過帳');
}
$this->adjustService->post($doc, auth()->id());
return redirect()->back()->with('success', '單據已過帳');
}
if ($action === 'void') {
if ($doc->status !== 'draft') {
return redirect()->back()->with('error', '只有草稿狀態的單據可以作廢');
}
$this->adjustService->void($doc, auth()->id());
return redirect()->back()->with('success', '單據已作廢');
}
// 一般更新 (更新品項與基本資訊)
if ($doc->status !== 'draft') {
return redirect()->back()->with('error', '只有草稿狀態的單據可以修改');
}
$request->validate([
'reason' => 'required|string',
'remarks' => 'nullable|string',
'items' => 'required|array|min:1',
'items.*.product_id' => 'required',
'items.*.adjust_qty' => 'required|numeric',
]);
$doc->update([
'reason' => $request->reason,
'remarks' => $request->remarks,
]);
$this->adjustService->updateItems($doc, $request->items);
return redirect()->back()->with('success', '單據已更新');
}
public function show(InventoryAdjustDoc $doc)
{
$doc->load(['items.product.baseUnit', 'createdBy', 'postedBy', 'warehouse', 'countDoc']);
$docData = [
'id' => (string) $doc->id,
'doc_no' => $doc->doc_no,
'warehouse_id' => (string) $doc->warehouse_id,
'warehouse_name' => $doc->warehouse->name,
'status' => $doc->status,
'reason' => $doc->reason,
'remarks' => $doc->remarks,
'created_at' => $doc->created_at->format('Y-m-d H:i'),
'created_by' => $doc->createdBy?->name,
'count_doc_id' => $doc->count_doc_id ? (string)$doc->count_doc_id : null,
'count_doc_no' => $doc->countDoc?->doc_no,
'items' => $doc->items->map(function ($item) {
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,
'qty_before' => (float) $item->qty_before,
'adjust_qty' => (float) $item->adjust_qty,
'notes' => $item->notes,
];
}),
];
return Inertia::render('Inventory/Adjust/Show', [
'doc' => $docData,
]);
}
public function destroy(InventoryAdjustDoc $doc)
{
if ($doc->status !== 'draft') {
return redirect()->back()->with('error', '只能刪除草稿狀態的單據');
}
$doc->items()->delete();
$doc->delete();
return redirect()->route('inventory.adjust.index')
->with('success', '盤調單已刪除');
}
}