feat: 標準化全系統數值輸入欄位與擴充商品價格功能
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:
@@ -48,8 +48,10 @@ interface AdjItem {
|
||||
qty_before: number | string;
|
||||
adjust_qty: number | string;
|
||||
notes: string;
|
||||
expiry_date?: string | null;
|
||||
}
|
||||
|
||||
|
||||
interface AdjDoc {
|
||||
id: string;
|
||||
doc_no: string;
|
||||
@@ -155,6 +157,7 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) {
|
||||
qty_before: inv.quantity || 0,
|
||||
adjust_qty: 0,
|
||||
notes: '',
|
||||
expiry_date: inv.expiry_date,
|
||||
});
|
||||
addedCount++;
|
||||
}
|
||||
@@ -409,9 +412,10 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) {
|
||||
onCheckedChange={() => toggleSelectAll()}
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">商品代號</TableHead>
|
||||
|
||||
<TableHead className="font-medium text-grey-600">品名</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">批號</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">效期</TableHead>
|
||||
<TableHead className="text-right font-medium text-grey-600 pr-6">現有庫存</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -447,9 +451,10 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) {
|
||||
onCheckedChange={() => toggleSelect(key)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-sm text-grey-1">{inv.product_code}</TableCell>
|
||||
|
||||
<TableCell className="font-semibold text-grey-0">{inv.product_name}</TableCell>
|
||||
<TableCell className="text-sm font-mono text-grey-2">{inv.batch_number || '-'}</TableCell>
|
||||
<TableCell className="text-sm font-mono text-grey-2">{inv.expiry_date || '-'}</TableCell>
|
||||
<TableCell className="text-right font-bold text-primary-main pr-6">{inv.quantity} {inv.unit_name}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
@@ -532,7 +537,14 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) {
|
||||
<span className="text-xs text-gray-500 font-mono">{item.product_code}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-grey-600 font-mono text-sm">{item.batch_number || '-'}</TableCell>
|
||||
<TableCell className="text-grey-600 font-mono text-sm">
|
||||
<div>{item.batch_number || '-'}</div>
|
||||
{item.expiry_date && (
|
||||
<div className="text-xs text-gray-400 mt-1">
|
||||
效期: {item.expiry_date}
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-center text-grey-500">{item.unit}</TableCell>
|
||||
<TableCell className="text-right font-medium text-grey-400">
|
||||
{item.qty_before}
|
||||
@@ -542,8 +554,8 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) {
|
||||
<div className="flex justify-end pr-2">
|
||||
<Input
|
||||
type="number"
|
||||
step="0.01"
|
||||
className="text-right h-9 w-32 font-medium"
|
||||
step="any"
|
||||
className="h-9 w-32 font-medium text-right"
|
||||
value={item.adjust_qty}
|
||||
onChange={e => updateItem(index, 'adjust_qty', e.target.value)}
|
||||
/>
|
||||
@@ -569,9 +581,9 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) {
|
||||
{!isReadOnly && !doc.count_doc_id && (
|
||||
<TableCell className="text-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 text-red-400 hover:text-red-600 hover:bg-red-50 p-0"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="button-outlined-error h-8 w-8"
|
||||
onClick={() => removeItem(index)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
|
||||
@@ -276,7 +276,14 @@ export default function Show({ doc }: any) {
|
||||
<span className="text-xs text-gray-500 font-mono">{item.product_code}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm font-mono">{item.batch_number || '-'}</TableCell>
|
||||
<TableCell className="text-sm font-mono">
|
||||
<div>{item.batch_number || '-'}</div>
|
||||
{item.expiry_date && (
|
||||
<div className="text-xs text-gray-400 mt-1">
|
||||
效期: {item.expiry_date}
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-medium">{Number(item.system_qty)}</TableCell>
|
||||
<TableCell className="text-right px-1 py-3">
|
||||
{isReadOnly ? (
|
||||
@@ -284,12 +291,12 @@ export default function Show({ doc }: any) {
|
||||
) : (
|
||||
<Input
|
||||
type="number"
|
||||
step="0.01"
|
||||
step="any"
|
||||
value={formItem.counted_qty ?? ''}
|
||||
onChange={(e) => updateItem(index, 'counted_qty', e.target.value)}
|
||||
onWheel={(e: any) => e.target.blur()}
|
||||
disabled={processing}
|
||||
className="h-9 text-right font-medium focus:ring-primary-main"
|
||||
className="h-9 font-medium focus:ring-primary-main text-right"
|
||||
placeholder="盤點..."
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -38,13 +38,7 @@ import { STATUS_CONFIG } from '@/constants/purchase-order';
|
||||
|
||||
|
||||
|
||||
interface BatchItem {
|
||||
inventoryId: string;
|
||||
batchNumber: string;
|
||||
originCountry: string;
|
||||
expiryDate: string | null;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
|
||||
// 待進貨採購單 Item 介面
|
||||
interface PendingPOItem {
|
||||
@@ -207,13 +201,12 @@ export default function GoodsReceiptCreate({ warehouses, pendingPurchaseOrders,
|
||||
};
|
||||
|
||||
// Batch management
|
||||
const [batchesCache, setBatchesCache] = useState<Record<string, BatchItem[]>>({});
|
||||
const [nextSequences, setNextSequences] = useState<Record<string, number>>({});
|
||||
|
||||
// Fetch batches and sequence for a product
|
||||
const fetchProductBatches = async (productId: number, country: string = 'TW', dateStr: string = '') => {
|
||||
if (!data.warehouse_id) return;
|
||||
const cacheKey = `${productId}-${data.warehouse_id}`;
|
||||
// const cacheKey = `${productId}-${data.warehouse_id}`; // Unused
|
||||
|
||||
try {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
@@ -233,13 +226,7 @@ export default function GoodsReceiptCreate({ warehouses, pendingPurchaseOrders,
|
||||
);
|
||||
|
||||
if (response.data) {
|
||||
// Update existing batches list
|
||||
if (response.data.batches) {
|
||||
setBatchesCache(prev => ({
|
||||
...prev,
|
||||
[cacheKey]: response.data.batches
|
||||
}));
|
||||
}
|
||||
// Remove unused batch cache update
|
||||
|
||||
// Update next sequence for new batch generation
|
||||
if (response.data.nextSequence !== undefined) {
|
||||
@@ -645,11 +632,11 @@ export default function GoodsReceiptCreate({ warehouses, pendingPurchaseOrders,
|
||||
<TableCell>
|
||||
<Input
|
||||
type="number"
|
||||
step="1"
|
||||
step="any"
|
||||
min="0"
|
||||
value={item.quantity_received}
|
||||
onChange={(e) => updateItem(index, 'quantity_received', e.target.value)}
|
||||
className={`w-full ${(errors as any)[errorKey] ? 'border-red-500' : ''}`}
|
||||
className={`w-full text-right ${errors && (errors as any)[errorKey] ? 'border-red-500' : ''}`}
|
||||
/>
|
||||
{(errors as any)[errorKey] && (
|
||||
<p className="text-xs text-red-500 mt-1">{(errors as any)[errorKey]}</p>
|
||||
|
||||
@@ -116,6 +116,7 @@ export default function Show({ order }: any) {
|
||||
product_name: inv.product_name,
|
||||
product_code: inv.product_code,
|
||||
batch_number: inv.batch_number,
|
||||
expiry_date: inv.expiry_date,
|
||||
unit: inv.unit_name,
|
||||
quantity: 1, // Default 1
|
||||
max_quantity: inv.quantity, // Max available
|
||||
@@ -371,9 +372,10 @@ export default function Show({ order }: any) {
|
||||
onCheckedChange={() => toggleSelectAll()}
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">商品代號</TableHead>
|
||||
|
||||
<TableHead className="font-medium text-grey-600">品名</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">批號</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">效期</TableHead>
|
||||
<TableHead className="text-right font-medium text-grey-600 pr-6">現有庫存</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -409,9 +411,10 @@ export default function Show({ order }: any) {
|
||||
onCheckedChange={() => toggleSelect(key)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-sm text-grey-1">{inv.product_code}</TableCell>
|
||||
|
||||
<TableCell className="font-semibold text-grey-0">{inv.product_name}</TableCell>
|
||||
<TableCell className="text-sm font-mono text-grey-2">{inv.batch_number || '-'}</TableCell>
|
||||
<TableCell className="text-sm font-mono text-grey-2">{inv.expiry_date || '-'}</TableCell>
|
||||
<TableCell className="text-right font-bold text-primary-main pr-6">{inv.quantity} {inv.unit_name}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
@@ -493,7 +496,14 @@ export default function Show({ order }: any) {
|
||||
<span className="text-xs text-gray-500 font-mono">{item.product_code}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm font-mono">{item.batch_number || '-'}</TableCell>
|
||||
<TableCell className="text-sm font-mono">
|
||||
<div>{item.batch_number || '-'}</div>
|
||||
{item.expiry_date && (
|
||||
<div className="text-xs text-gray-400 mt-1">
|
||||
效期: {item.expiry_date}
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-semibold text-primary-main">
|
||||
{item.max_quantity} {item.unit || item.unit_name}
|
||||
</TableCell>
|
||||
@@ -505,10 +515,10 @@ export default function Show({ order }: any) {
|
||||
<Input
|
||||
type="number"
|
||||
min="0.01"
|
||||
step="0.01"
|
||||
step="any"
|
||||
value={item.quantity}
|
||||
onChange={(e) => handleUpdateItem(index, 'quantity', e.target.value)}
|
||||
className="h-9 w-32 text-right font-medium focus:ring-primary-main"
|
||||
className="h-9 w-32 font-medium focus:ring-primary-main text-right"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -528,7 +538,7 @@ export default function Show({ order }: any) {
|
||||
</TableCell>
|
||||
{!isReadOnly && (
|
||||
<TableCell className="text-center">
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 text-red-400 hover:text-red-600 hover:bg-red-50 p-0" onClick={() => handleRemoveItem(index)}>
|
||||
<Button variant="outline" size="icon" className="button-outlined-error h-8 w-8" onClick={() => handleRemoveItem(index)}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user