feat: 標準化全系統數值輸入欄位與擴充商品價格功能
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Has been skipped
Koori-ERP-Deploy-System / deploy-production (push) Successful in 1m0s

1. UI 標準化:
   - 針對全系統數值輸入欄位統一加上 step='any' 以支援小數點。
   - 表格形式 (Table) 的數值輸入欄位統一加上 text-right 靠右對齊。
   - 修正 Components 與 Pages 中所有涉及金額與數量的輸入框。

2. 功能擴充與修正:
   - 擴充 Product 模型與相關 Dialog 以支援多種價格設定。
   - 修正 Inventory/GoodsReceipt/Create.tsx 未使用的變數錯誤。
   - 優化庫存相關頁面的 UI 一致性。

3. 其他:
   - 更新相關的 Type 定義與 Controller 邏輯。
This commit is contained in:
2026-02-05 11:45:08 +08:00
parent 04f3891275
commit 3ce96537b3
40 changed files with 774 additions and 212 deletions

View File

@@ -181,6 +181,16 @@ class AdjustDocController extends Controller
{
$doc->load(['items.product.baseUnit', 'createdBy', 'postedBy', 'warehouse', 'countDoc']);
// Pre-fetch relevant Inventory information (mainly for expiry date)
$inventoryMap = \App\Modules\Inventory\Models\Inventory::withTrashed()
->where('warehouse_id', $doc->warehouse_id)
->whereIn('product_id', $doc->items->pluck('product_id'))
->whereIn('batch_number', $doc->items->pluck('batch_number'))
->get()
->mapWithKeys(function ($inv) {
return [$inv->product_id . '-' . $inv->batch_number => $inv];
});
$docData = [
'id' => (string) $doc->id,
'doc_no' => $doc->doc_no,
@@ -193,13 +203,15 @@ class AdjustDocController extends Controller
'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) {
'items' => $doc->items->map(function ($item) use ($inventoryMap) {
$inv = $inventoryMap->get($item->product_id . '-' . $item->batch_number);
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,
'expiry_date' => $inv && $inv->expiry_date ? $inv->expiry_date->format('Y-m-d') : null,
'unit' => $item->product->baseUnit?->name,
'qty_before' => (float) $item->qty_before,
'adjust_qty' => (float) $item->adjust_qty,

View File

@@ -94,6 +94,16 @@ class CountDocController extends Controller
{
$doc->load(['items.product.baseUnit', 'createdBy', 'completedBy', 'warehouse']);
// 預先抓取相關的 Inventory 資訊 (主要為了取得效期)
$inventoryMap = \App\Modules\Inventory\Models\Inventory::withTrashed()
->where('warehouse_id', $doc->warehouse_id)
->whereIn('product_id', $doc->items->pluck('product_id'))
->whereIn('batch_number', $doc->items->pluck('batch_number'))
->get()
->mapWithKeys(function ($inv) {
return [$inv->product_id . '-' . $inv->batch_number => $inv];
});
$docData = [
'id' => (string) $doc->id,
'doc_no' => $doc->doc_no,
@@ -103,12 +113,16 @@ class CountDocController extends Controller
'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) {
'items' => $doc->items->map(function ($item) use ($inventoryMap) {
$key = $item->product_id . '-' . $item->batch_number;
$inv = $inventoryMap->get($key);
return [
'id' => (string) $item->id,
'product_name' => $item->product->name,
'product_code' => $item->product->code,
'batch_number' => $item->batch_number,
'expiry_date' => $inv && $inv->expiry_date ? $inv->expiry_date->format('Y-m-d') : null, // 新增效期
'unit' => $item->product->baseUnit?->name,
'system_qty' => (float) $item->system_qty,
'counted_qty' => is_null($item->counted_qty) ? '' : (float) $item->counted_qty,

View File

@@ -131,16 +131,18 @@ class InventoryController extends Controller
{
// ... (unchanged) ...
$products = Product::with(['baseUnit', 'largeUnit'])
->select('id', 'name', 'code', 'base_unit_id', 'large_unit_id', 'conversion_rate')
->select('id', 'name', 'code', 'barcode', 'base_unit_id', 'large_unit_id', 'conversion_rate', 'cost_price')
->get()
->map(function ($product) {
return [
'id' => (string) $product->id,
'name' => $product->name,
'code' => $product->code,
'barcode' => $product->barcode,
'baseUnit' => $product->baseUnit?->name ?? '個',
'largeUnit' => $product->largeUnit?->name, // 可能為 null
'conversionRate' => (float) $product->conversion_rate,
'costPrice' => (float) $product->cost_price,
];
});

View File

@@ -96,6 +96,10 @@ class ProductController extends Controller
] : null,
'conversionRate' => (float) $product->conversion_rate,
'location' => $product->location,
'cost_price' => (float) $product->cost_price,
'price' => (float) $product->price,
'member_price' => (float) $product->member_price,
'wholesale_price' => (float) $product->wholesale_price,
];
});
@@ -126,7 +130,12 @@ class ProductController extends Controller
'large_unit_id' => 'nullable|exists:units,id',
'conversion_rate' => 'required_with:large_unit_id|nullable|numeric|min:0.0001',
'purchase_unit_id' => 'nullable|exists:units,id',
'purchase_unit_id' => 'nullable|exists:units,id',
'location' => 'nullable|string|max:255',
'cost_price' => 'nullable|numeric|min:0',
'price' => 'nullable|numeric|min:0',
'member_price' => 'nullable|numeric|min:0',
'wholesale_price' => 'nullable|numeric|min:0',
], [
'code.required' => '商品代號為必填',
'code.max' => '商品代號最多 8 碼',
@@ -142,6 +151,14 @@ class ProductController extends Controller
'conversion_rate.required_with' => '填寫大單位時,換算率為必填',
'conversion_rate.numeric' => '換算率必須為數字',
'conversion_rate.min' => '換算率最小為 0.0001',
'cost_price.numeric' => '成本價必須為數字',
'cost_price.min' => '成本價不能小於 0',
'price.numeric' => '售價必須為數字',
'price.min' => '售價不能小於 0',
'member_price.numeric' => '會員價必須為數字',
'member_price.min' => '會員價不能小於 0',
'wholesale_price.numeric' => '批發價必須為數字',
'wholesale_price.min' => '批發價不能小於 0',
]);
$product = Product::create($validated);
@@ -165,7 +182,12 @@ class ProductController extends Controller
'large_unit_id' => 'nullable|exists:units,id',
'conversion_rate' => 'required_with:large_unit_id|nullable|numeric|min:0.0001',
'purchase_unit_id' => 'nullable|exists:units,id',
'purchase_unit_id' => 'nullable|exists:units,id',
'location' => 'nullable|string|max:255',
'cost_price' => 'nullable|numeric|min:0',
'price' => 'nullable|numeric|min:0',
'member_price' => 'nullable|numeric|min:0',
'wholesale_price' => 'nullable|numeric|min:0',
], [
'code.required' => '商品代號為必填',
'code.max' => '商品代號最多 8 碼',
@@ -181,6 +203,14 @@ class ProductController extends Controller
'conversion_rate.required_with' => '填寫大單位時,換算率為必填',
'conversion_rate.numeric' => '換算率必須為數字',
'conversion_rate.min' => '換算率最小為 0.0001',
'cost_price.numeric' => '成本價必須為數字',
'cost_price.min' => '成本價不能小於 0',
'price.numeric' => '售價必須為數字',
'price.min' => '售價不能小於 0',
'member_price.numeric' => '會員價必須為數字',
'member_price.min' => '會員價不能小於 0',
'wholesale_price.numeric' => '批發價必須為數字',
'wholesale_price.min' => '批發價不能小於 0',
]);
$product->update($validated);

View File

@@ -125,6 +125,7 @@ class TransferOrderController extends Controller
'product_name' => $item->product->name,
'product_code' => $item->product->code,
'batch_number' => $item->batch_number,
'expiry_date' => $stock && $stock->expiry_date ? $stock->expiry_date->format('Y-m-d') : null,
'unit' => $item->product->baseUnit?->name,
'quantity' => (float) $item->quantity,
'max_quantity' => $item->snapshot_quantity ? (float) $item->snapshot_quantity : ($stock ? (float) $stock->quantity : 0.0),

View File

@@ -5,7 +5,7 @@ namespace App\Modules\Inventory\Exports;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use Maatwebsite\Excel\Concerns\WithHeadings;
// use Maatwebsite\Excel\Concerns\WithHeadings;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class ProductTemplateExport implements WithHeadings, WithColumnFormatting
@@ -22,6 +22,10 @@ class ProductTemplateExport implements WithHeadings, WithColumnFormatting
'基本單位',
'大單位',
'換算率',
'成本價',
'售價',
'會員價',
'批發價',
];
}

View File

@@ -74,6 +74,10 @@ class ProductImport implements ToModel, WithHeadingRow, WithValidation, WithMapp
'large_unit_id' => $largeUnitId,
'conversion_rate' => $row['換算率'] ?? null,
'purchase_unit_id' => null,
'cost_price' => $row['成本價'] ?? null,
'price' => $row['售價'] ?? null,
'member_price' => $row['會員價'] ?? null,
'wholesale_price' => $row['批發價'] ?? null,
]);
}
@@ -100,6 +104,10 @@ class ProductImport implements ToModel, WithHeadingRow, WithValidation, WithMapp
}],
'換算率' => ['nullable', 'numeric', 'min:0.0001', 'required_with:大單位'],
'成本價' => ['nullable', 'numeric', 'min:0'],
'售價' => ['nullable', 'numeric', 'min:0'],
'會員價' => ['nullable', 'numeric', 'min:0'],
'批發價' => ['nullable', 'numeric', 'min:0'],
];
}
}

View File

@@ -27,6 +27,10 @@ class Product extends Model
'conversion_rate',
'purchase_unit_id',
'location',
'cost_price',
'price',
'member_price',
'wholesale_price',
];
protected $casts = [