大更新
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Has been skipped
Koori-ERP-Deploy-System / deploy-production (push) Successful in 58s

This commit is contained in:
2026-01-08 16:32:10 +08:00
parent 7848976a06
commit 0b60dab208
25 changed files with 661 additions and 392 deletions

View File

@@ -10,6 +10,7 @@ class InventoryController extends Controller
{
$warehouse->load([
'inventories.product.category',
'inventories.product.baseUnit',
'inventories.lastIncomingTransaction',
'inventories.lastOutgoingTransaction'
]);
@@ -20,7 +21,7 @@ class InventoryController extends Controller
return [
'id' => (string) $product->id, // Frontend expects string
'name' => $product->name,
'type' => $product->category ? $product->category->name : '其他', // 暫時用 Category Name 當 Type
'type' => $product->category?->name ?? '其他', // 暫時用 Category Name 當 Type
];
});
@@ -32,9 +33,9 @@ class InventoryController extends Controller
'id' => (string) $inv->id,
'warehouseId' => (string) $inv->warehouse_id,
'productId' => (string) $inv->product_id,
'productName' => $inv->product->name,
'productCode' => $inv->product->code ?? 'N/A',
'unit' => $inv->product->base_unit ?? '個',
'productName' => $inv->product?->name ?? '未知商品',
'productCode' => $inv->product?->code ?? 'N/A',
'unit' => $inv->product?->baseUnit?->name ?? '個',
'quantity' => (float) $inv->quantity,
'safetyStock' => $inv->safety_stock !== null ? (float) $inv->safety_stock : null,
'status' => '正常', // 前端會根據 quantity 與 safetyStock 重算,但後端亦可提供基礎狀態
@@ -53,8 +54,8 @@ class InventoryController extends Controller
'id' => 'ss-' . $inv->id,
'warehouseId' => (string) $inv->warehouse_id,
'productId' => (string) $inv->product_id,
'productName' => $inv->product->name,
'productType' => $inv->product->category ? $inv->product->category->name : '其他',
'productName' => $inv->product?->name ?? '未知商品',
'productType' => $inv->product?->category?->name ?? '其他',
'safetyStock' => (float) $inv->safety_stock,
'createdAt' => $inv->created_at->toIso8601String(),
'updatedAt' => $inv->updated_at->toIso8601String(),
@@ -72,11 +73,13 @@ class InventoryController extends Controller
public function create(\App\Models\Warehouse $warehouse)
{
// 取得所有商品供前端選單使用
$products = \App\Models\Product::select('id', 'name', 'base_unit')->get()->map(function ($product) {
$products = \App\Models\Product::with(['baseUnit', 'largeUnit'])->select('id', 'name', 'base_unit_id', 'large_unit_id', 'conversion_rate')->get()->map(function ($product) {
return [
'id' => (string) $product->id,
'name' => $product->name,
'unit' => $product->base_unit,
'baseUnit' => $product->baseUnit?->name ?? '個',
'largeUnit' => $product->largeUnit?->name, // 可能為 null
'conversionRate' => (float) $product->conversion_rate,
];
});
@@ -145,7 +148,7 @@ class InventoryController extends Controller
'id' => (string) $inventory->id,
'warehouseId' => (string) $inventory->warehouse_id,
'productId' => (string) $inventory->product_id,
'productName' => $inventory->product->name,
'productName' => $inventory->product?->name ?? '未知商品',
'quantity' => (float) $inventory->quantity,
'batchNumber' => 'BATCH-' . $inventory->id, // Mock
'expiryDate' => '2099-12-31', // Mock
@@ -315,8 +318,8 @@ class InventoryController extends Controller
'warehouse' => $warehouse,
'inventory' => [
'id' => (string) $inventory->id,
'productName' => $inventory->product->name,
'productCode' => $inventory->product->code,
'productName' => $inventory->product?->name ?? '未知商品',
'productCode' => $inventory->product?->code ?? 'N/A',
'quantity' => (float) $inventory->quantity,
],
'transactions' => $transactions

View File

@@ -54,7 +54,7 @@ class PurchaseOrderController extends Controller
public function create()
{
$vendors = Vendor::with('products')->get()->map(function ($vendor) {
$vendors = Vendor::with(['products.baseUnit', 'products.largeUnit', 'products.purchaseUnit'])->get()->map(function ($vendor) {
return [
'id' => (string) $vendor->id,
'name' => $vendor->name,
@@ -62,9 +62,11 @@ class PurchaseOrderController extends Controller
return [
'productId' => (string) $product->id,
'productName' => $product->name,
'unit' => $product->purchase_unit ?: ($product->large_unit ?: $product->base_unit), // 優先使用採購單位 > 大單位 > 基本單位
'base_unit' => $product->base_unit,
'purchase_unit' => $product->purchase_unit ?: $product->large_unit, // 若無採購單位,預設為大單位
'base_unit_id' => $product->base_unit_id,
'base_unit_name' => $product->baseUnit?->name,
'large_unit_id' => $product->large_unit_id,
'large_unit_name' => $product->largeUnit?->name,
'purchase_unit_id' => $product->purchase_unit_id,
'conversion_rate' => (float) $product->conversion_rate,
'lastPrice' => (float) ($product->pivot->last_price ?? 0),
];
@@ -96,6 +98,7 @@ class PurchaseOrderController extends Controller
'items.*.productId' => 'required|exists:products,id',
'items.*.quantity' => 'required|numeric|min:0.01',
'items.*.unitPrice' => 'required|numeric|min:0',
'items.*.unitId' => 'nullable|exists:units,id', // 驗證單位ID
]);
try {
@@ -157,6 +160,7 @@ class PurchaseOrderController extends Controller
$order->items()->create([
'product_id' => $item['productId'],
'quantity' => $item['quantity'],
'unit_id' => $item['unitId'] ?? null, // 儲存單位ID
'unit_price' => $item['unitPrice'],
'subtotal' => $item['quantity'] * $item['unitPrice'],
]);
@@ -174,20 +178,39 @@ class PurchaseOrderController extends Controller
public function show($id)
{
$order = PurchaseOrder::with(['vendor', 'warehouse', 'user', 'items.product'])->findOrFail($id);
$order = PurchaseOrder::with(['vendor', 'warehouse', 'user', 'items.product.baseUnit', 'items.product.largeUnit'])->findOrFail($id);
// Transform items to include product details needed for frontend calculation
$order->items->transform(function ($item) {
$order->items->transform(function ($item) use ($order) {
$product = $item->product;
if ($product) {
// 手動附加 productName 和 unit (因為已從 $appends 移除)
// 手動附加所有必要的屬性
$item->productId = (string) $product->id;
$item->productName = $product->name;
$item->productId = $product->id;
$item->base_unit = $product->base_unit;
$item->purchase_unit = $product->purchase_unit ?: $product->large_unit; // Fallback logic same as Create
$item->base_unit_id = $product->base_unit_id;
$item->base_unit_name = $product->baseUnit?->name;
$item->large_unit_id = $product->large_unit_id;
$item->large_unit_name = $product->largeUnit?->name;
$item->purchase_unit_id = $product->purchase_unit_id;
$item->conversion_rate = (float) $product->conversion_rate;
// 優先使用採購單位 > 大單位 > 基本單位
$item->unit = $product->purchase_unit ?: ($product->large_unit ?: $product->base_unit);
// Fetch last price
$lastPrice = DB::table('product_vendor')
->where('vendor_id', $order->vendor_id)
->where('product_id', $product->id)
->value('last_price');
$item->previousPrice = (float) ($lastPrice ?? 0);
// 設定當前選中的單位 ID (from saved item)
$item->unitId = $item->unit_id;
// 決定 selectedUnit (用於 UI 顯示)
if ($item->unitId && $item->large_unit_id && $item->unitId == $item->large_unit_id) {
$item->selectedUnit = 'large';
} else {
$item->selectedUnit = 'base';
}
$item->unitPrice = (float) $item->unit_price;
}
return $item;
@@ -202,7 +225,7 @@ class PurchaseOrderController extends Controller
{
$order = PurchaseOrder::with(['items.product'])->findOrFail($id);
$vendors = Vendor::with('products')->get()->map(function ($vendor) {
$vendors = Vendor::with(['products.baseUnit', 'products.largeUnit', 'products.purchaseUnit'])->get()->map(function ($vendor) {
return [
'id' => (string) $vendor->id,
'name' => $vendor->name,
@@ -210,9 +233,11 @@ class PurchaseOrderController extends Controller
return [
'productId' => (string) $product->id,
'productName' => $product->name,
'unit' => $product->purchase_unit ?: ($product->large_unit ?: $product->base_unit),
'base_unit' => $product->base_unit,
'purchase_unit' => $product->purchase_unit ?: $product->large_unit,
'base_unit_id' => $product->base_unit_id,
'base_unit_name' => $product->baseUnit?->name,
'large_unit_id' => $product->large_unit_id,
'large_unit_name' => $product->largeUnit?->name,
'purchase_unit_id' => $product->purchase_unit_id,
'conversion_rate' => (float) $product->conversion_rate,
'lastPrice' => (float) ($product->pivot->last_price ?? 0),
];
@@ -228,17 +253,38 @@ class PurchaseOrderController extends Controller
});
// Transform items for frontend form
$order->items->transform(function ($item) {
// Transform items for frontend form
$vendorId = $order->vendor_id;
$order->items->transform(function ($item) use ($vendorId) {
$product = $item->product;
if ($product) {
// 手動附加所有必要的屬性 (因為已從 $appends 移除)
$item->productId = (string) $product->id; // Ensure consistent ID type
// 手動附加所有必要的屬性
$item->productId = (string) $product->id;
$item->productName = $product->name;
$item->base_unit = $product->base_unit;
$item->purchase_unit = $product->purchase_unit ?: $product->large_unit;
$item->base_unit_id = $product->base_unit_id;
$item->base_unit_name = $product->baseUnit?->name;
$item->large_unit_id = $product->large_unit_id;
$item->large_unit_name = $product->largeUnit?->name;
$item->conversion_rate = (float) $product->conversion_rate;
// 優先使用採購單位 > 大單位 > 基本單位
$item->unit = $product->purchase_unit ?: ($product->large_unit ?: $product->base_unit);
// Fetch last price
$lastPrice = DB::table('product_vendor')
->where('vendor_id', $vendorId)
->where('product_id', $product->id)
->value('last_price');
$item->previousPrice = (float) ($lastPrice ?? 0);
// 設定當前選中的單位 ID
$item->unitId = $item->unit_id; // 資料庫中的 unit_id
// 決定 selectedUnit (用於 UI 狀態)
if ($item->unitId && $item->large_unit_id && $item->unitId == $item->large_unit_id) {
$item->selectedUnit = 'large';
} else {
$item->selectedUnit = 'base';
}
$item->unitPrice = (float) $item->unit_price;
}
return $item;
@@ -265,6 +311,7 @@ class PurchaseOrderController extends Controller
'items.*.productId' => 'required|exists:products,id',
'items.*.quantity' => 'required|numeric|min:0.01',
'items.*.unitPrice' => 'required|numeric|min:0',
'items.*.unitId' => 'nullable|exists:units,id', // 驗證單位ID
]);
try {
@@ -296,6 +343,7 @@ class PurchaseOrderController extends Controller
$order->items()->create([
'product_id' => $item['productId'],
'quantity' => $item['quantity'],
'unit_id' => $item['unitId'] ?? null, // 儲存單位ID
'unit_price' => $item['unitPrice'],
'subtotal' => $item['quantity'] * $item['unitPrice'],
]);

View File

@@ -18,7 +18,7 @@ class SafetyStockController extends Controller
{
$warehouse->load(['inventories.product.category']);
$allProducts = Product::with('category')->get();
$allProducts = Product::with(['category', 'baseUnit'])->get();
// 準備可選商品列表
$availableProducts = $allProducts->map(function ($product) {
@@ -26,7 +26,7 @@ class SafetyStockController extends Controller
'id' => (string) $product->id,
'name' => $product->name,
'type' => $product->category ? $product->category->name : '其他',
'unit' => $product->base_unit,
'unit' => $product->baseUnit?->name ?? '個',
];
});
@@ -51,7 +51,7 @@ class SafetyStockController extends Controller
'productName' => $inv->product->name,
'productType' => $inv->product->category ? $inv->product->category->name : '其他',
'safetyStock' => (float) $inv->safety_stock,
'unit' => $inv->product->base_unit,
'unit' => $inv->product->baseUnit?->name ?? '個',
'updatedAt' => $inv->updated_at->toIso8601String(),
];
})->values();

View File

@@ -97,7 +97,7 @@ class TransferOrderController extends Controller
public function getWarehouseInventories(Warehouse $warehouse)
{
$inventories = $warehouse->inventories()
->with(['product:id,name,base_unit,category_id', 'product.category'])
->with(['product.baseUnit', 'product.category'])
->where('quantity', '>', 0) // 只回傳有庫存的
->get()
->map(function ($inv) {
@@ -106,7 +106,7 @@ class TransferOrderController extends Controller
'productName' => $inv->product->name,
'batchNumber' => 'BATCH-' . $inv->id, // 模擬批號
'availableQty' => (float) $inv->quantity,
'unit' => $inv->product->base_unit,
'unit' => $inv->product->baseUnit?->name ?? '個',
];
});

View File

@@ -51,10 +51,10 @@ class VendorController extends Controller
*/
public function show(Vendor $vendor): \Inertia\Response
{
$vendor->load('products');
$vendor->load(['products.baseUnit', 'products.largeUnit']);
return \Inertia\Inertia::render('Vendor/Show', [
'vendor' => $vendor,
'products' => \App\Models\Product::all(),
'products' => \App\Models\Product::with('baseUnit')->get(),
]);
}

View File

@@ -72,9 +72,25 @@ class WarehouseController extends Controller
public function destroy(Warehouse $warehouse)
{
// 真實刪除
$warehouse->delete();
// 檢查是否有相關聯的採購單
if ($warehouse->purchaseOrders()->exists()) {
return redirect()->back()->with('error', '無法刪除:該倉庫有相關聯的採購單,請先處理採購單。');
}
return redirect()->back()->with('success', '倉庫已刪除');
\Illuminate\Support\Facades\DB::transaction(function () use ($warehouse) {
// 刪除庫存異動紀錄 (透過庫存關聯)
foreach ($warehouse->inventories as $inventory) {
// 刪除該庫存的所有異動紀錄
$inventory->transactions()->delete();
}
// 刪除庫存項目
$warehouse->inventories()->delete();
// 刪除倉庫
$warehouse->delete();
});
return redirect()->back()->with('success', '倉庫及其庫存與紀錄已刪除');
}
}

View File

@@ -14,6 +14,7 @@ class PurchaseOrderItem extends Model
'purchase_order_id',
'product_id',
'quantity',
'unit_id', // 新增單位ID欄位
'unit_price',
'subtotal',
'received_quantity',
@@ -26,25 +27,33 @@ class PurchaseOrderItem extends Model
'received_quantity' => 'decimal:2',
];
// 移除 $appends 以避免自動附加導致的錯誤
// 這些屬性將在 Controller 中需要時手動附加
// protected $appends = ['productName', 'unit'];
public function getProductNameAttribute(): string
{
return $this->product?->name ?? '';
}
public function getUnitAttribute(): string
// 關聯單位
public function unit(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
// 優先使用採購單位 > 大單位 > 基本單位
// 與 PurchaseOrderController 的邏輯保持一致
return $this->belongsTo(Unit::class);
}
public function getUnitNameAttribute(): string
{
// 優先使用關聯的 unit
if ($this->unit) {
return $this->unit->name;
}
if (!$this->product) {
return '';
}
return $this->product->purchase_unit
?: ($this->product->large_unit ?: $this->product->base_unit);
// Fallback: 嘗試從 Product 的關聯單位獲取
return $this->product->purchaseUnit?->name
?? $this->product->largeUnit?->name
?? $this->product->baseUnit?->name
?? '';
}
public function purchaseOrder(): BelongsTo