管理採購單的商品金額修正
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 29s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped

This commit is contained in:
2026-01-08 17:51:06 +08:00
parent cbd8d11848
commit 3088959c7c
4 changed files with 53 additions and 40 deletions

View File

@@ -97,8 +97,8 @@ class PurchaseOrderController extends Controller
'items' => 'required|array|min:1', 'items' => 'required|array|min:1',
'items.*.productId' => 'required|exists:products,id', 'items.*.productId' => 'required|exists:products,id',
'items.*.quantity' => 'required|numeric|min:0.01', 'items.*.quantity' => 'required|numeric|min:0.01',
'items.*.unitPrice' => 'required|numeric|min:0', 'items.*.subtotal' => 'required|numeric|min:0', // 總金額
'items.*.unitId' => 'nullable|exists:units,id', // 驗證單位ID 'items.*.unitId' => 'nullable|exists:units,id',
]); ]);
try { try {
@@ -122,7 +122,7 @@ class PurchaseOrderController extends Controller
$totalAmount = 0; $totalAmount = 0;
foreach ($validated['items'] as $item) { foreach ($validated['items'] as $item) {
$totalAmount += $item['quantity'] * $item['unitPrice']; $totalAmount += $item['subtotal'];
} }
// Simple tax calculation (e.g., 5%) // Simple tax calculation (e.g., 5%)
@@ -157,12 +157,15 @@ class PurchaseOrderController extends Controller
]); ]);
foreach ($validated['items'] as $item) { foreach ($validated['items'] as $item) {
// 反算單價
$unitPrice = $item['quantity'] > 0 ? $item['subtotal'] / $item['quantity'] : 0;
$order->items()->create([ $order->items()->create([
'product_id' => $item['productId'], 'product_id' => $item['productId'],
'quantity' => $item['quantity'], 'quantity' => $item['quantity'],
'unit_id' => $item['unitId'] ?? null, // 儲存單位ID 'unit_id' => $item['unitId'] ?? null,
'unit_price' => $item['unitPrice'], 'unit_price' => $unitPrice,
'subtotal' => $item['quantity'] * $item['unitPrice'], 'subtotal' => $item['subtotal'],
]); ]);
} }
@@ -310,8 +313,8 @@ class PurchaseOrderController extends Controller
'items' => 'required|array|min:1', 'items' => 'required|array|min:1',
'items.*.productId' => 'required|exists:products,id', 'items.*.productId' => 'required|exists:products,id',
'items.*.quantity' => 'required|numeric|min:0.01', 'items.*.quantity' => 'required|numeric|min:0.01',
'items.*.unitPrice' => 'required|numeric|min:0', 'items.*.subtotal' => 'required|numeric|min:0', // 總金額
'items.*.unitId' => 'nullable|exists:units,id', // 驗證單位ID 'items.*.unitId' => 'nullable|exists:units,id',
]); ]);
try { try {
@@ -319,7 +322,7 @@ class PurchaseOrderController extends Controller
$totalAmount = 0; $totalAmount = 0;
foreach ($validated['items'] as $item) { foreach ($validated['items'] as $item) {
$totalAmount += $item['quantity'] * $item['unitPrice']; $totalAmount += $item['subtotal'];
} }
// Simple tax calculation (e.g., 5%) // Simple tax calculation (e.g., 5%)
@@ -340,12 +343,15 @@ class PurchaseOrderController extends Controller
// Sync items // Sync items
$order->items()->delete(); $order->items()->delete();
foreach ($validated['items'] as $item) { foreach ($validated['items'] as $item) {
// 反算單價
$unitPrice = $item['quantity'] > 0 ? $item['subtotal'] / $item['quantity'] : 0;
$order->items()->create([ $order->items()->create([
'product_id' => $item['productId'], 'product_id' => $item['productId'],
'quantity' => $item['quantity'], 'quantity' => $item['quantity'],
'unit_id' => $item['unitId'] ?? null, // 儲存單位ID 'unit_id' => $item['unitId'] ?? null,
'unit_price' => $item['unitPrice'], 'unit_price' => $unitPrice,
'subtotal' => $item['quantity'] * $item['unitPrice'], 'subtotal' => $item['subtotal'],
]); ]);
} }

View File

@@ -50,8 +50,7 @@ export function PurchaseOrderItemsTable({
<TableHead className="w-[10%] text-left"></TableHead> <TableHead className="w-[10%] text-left"></TableHead>
<TableHead className="w-[12%] text-left"></TableHead> <TableHead className="w-[12%] text-left"></TableHead>
<TableHead className="w-[12%] text-left"></TableHead> <TableHead className="w-[12%] text-left"></TableHead>
<TableHead className="w-[12%] text-left"></TableHead> <TableHead className="w-[15%] text-left"></TableHead>
<TableHead className="w-[12%] text-left"></TableHead>
<TableHead className="w-[15%] text-left"> / </TableHead> <TableHead className="w-[15%] text-left"> / </TableHead>
{!isReadOnly && <TableHead className="w-[5%]"></TableHead>} {!isReadOnly && <TableHead className="w-[5%]"></TableHead>}
</TableRow> </TableRow>
@@ -60,7 +59,7 @@ export function PurchaseOrderItemsTable({
{items.length === 0 ? ( {items.length === 0 ? (
<TableRow> <TableRow>
<TableCell <TableCell
colSpan={isReadOnly ? 7 : 8} colSpan={isReadOnly ? 6 : 7}
className="text-center text-gray-400 py-12 italic" className="text-center text-gray-400 py-12 italic"
> >
{isDisabled ? "請先選擇供應商後才能新增商品" : "尚未新增任何商品項"} {isDisabled ? "請先選擇供應商後才能新增商品" : "尚未新增任何商品項"}
@@ -69,9 +68,12 @@ export function PurchaseOrderItemsTable({
) : ( ) : (
items.map((item, index) => { items.map((item, index) => {
// 計算換算後的單價 (基本單位單價) // 計算換算後的單價 (基本單位單價)
// unitPrice is derived from subtotal / quantity
const currentUnitPrice = item.unitPrice;
const convertedUnitPrice = item.selectedUnit === 'large' && item.conversion_rate const convertedUnitPrice = item.selectedUnit === 'large' && item.conversion_rate
? item.unitPrice / item.conversion_rate ? currentUnitPrice / item.conversion_rate
: item.unitPrice; : currentUnitPrice;
return ( return (
<TableRow key={index}> <TableRow key={index}>
@@ -162,44 +164,39 @@ export function PurchaseOrderItemsTable({
</div> </div>
</TableCell> </TableCell>
{/* 單價 */} {/* 總金額 (主要輸入欄位) */}
<TableCell className="text-left"> <TableCell className="text-left">
{isReadOnly ? ( {isReadOnly ? (
<span className="font-medium text-gray-900">{formatCurrency(item.unitPrice)}</span> <span className="font-bold text-primary">{formatCurrency(item.subtotal)}</span>
) : ( ) : (
<div className="space-y-1"> <div className="space-y-1">
<Input <Input
type="number" type="number"
min="0" min="0"
step="0.1" step="1"
value={item.unitPrice || ""} value={item.subtotal || ""}
onChange={(e) => onChange={(e) =>
onItemChange?.(index, "unitPrice", Number(e.target.value)) onItemChange?.(index, "subtotal", Number(e.target.value))
} }
disabled={isDisabled} disabled={isDisabled}
className={`h-10 text-left w-32 ${ className={`h-10 text-left w-32 ${
// 如果有數量但沒有單價,顯示錯誤樣式 // 如果有數量但沒有金額,顯示錯誤樣式
item.quantity > 0 && (!item.unitPrice || item.unitPrice <= 0) item.quantity > 0 && (!item.subtotal || item.subtotal <= 0)
? "border-red-400 bg-red-50 focus-visible:ring-red-500" ? "border-red-400 bg-red-50 focus-visible:ring-red-500"
: "border-gray-200" : "border-gray-200"
}`} }`}
/> />
{/* 錯誤提示 (保留必填提示) */} {/* 錯誤提示 */}
{item.quantity > 0 && (!item.unitPrice || item.unitPrice <= 0) && ( {item.quantity > 0 && (!item.subtotal || item.subtotal <= 0) && (
<p className="text-[10px] text-red-600 font-medium"> <p className="text-[10px] text-red-600 font-medium">
</p> </p>
)} )}
</div> </div>
)} )}
</TableCell> </TableCell>
{/* 小計 */} {/* 換算採購單價 / 基本單位 (顯示換算結果) */}
<TableCell className="text-left">
<span className="font-bold text-primary">{formatCurrency(item.subtotal)}</span>
</TableCell>
{/* 換算採購單價 / 基本單位 */}
<TableCell className="text-left"> <TableCell className="text-left">
<div className="flex flex-col"> <div className="flex flex-col">
<div className="text-gray-500 font-medium text-sm"> <div className="text-gray-500 font-medium text-sm">

View File

@@ -115,6 +115,7 @@ export default function CreatePurchaseOrder({
quantity: item.quantity, quantity: item.quantity,
unitPrice: item.unitPrice, unitPrice: item.unitPrice,
unitId: item.unitId, unitId: item.unitId,
subtotal: item.subtotal,
})), })),
}; };

View File

@@ -87,7 +87,6 @@ export function usePurchaseOrderForm({ order, suppliers }: UsePurchaseOrderFormP
item.previousPrice = product.lastPrice; item.previousPrice = product.lastPrice;
// 決定預設單位 // 決定預設單位
// 若有採購單位且等於大單位,預設為大單位
const isPurchaseUnitLarge = product.purchase_unit_id && product.large_unit_id && product.purchase_unit_id === product.large_unit_id; const isPurchaseUnitLarge = product.purchase_unit_id && product.large_unit_id && product.purchase_unit_id === product.large_unit_id;
if (isPurchaseUnitLarge) { if (isPurchaseUnitLarge) {
@@ -97,6 +96,9 @@ export function usePurchaseOrderForm({ order, suppliers }: UsePurchaseOrderFormP
item.selectedUnit = 'base'; item.selectedUnit = 'base';
item.unitId = product.base_unit_id; item.unitId = product.base_unit_id;
} }
// 初始小計 = 數量 * 單價
item.subtotal = calculateSubtotal(Number(item.quantity), Number(item.unitPrice));
} }
} else if (field === "selectedUnit") { } else if (field === "selectedUnit") {
// @ts-ignore // @ts-ignore
@@ -106,17 +108,24 @@ export function usePurchaseOrderForm({ order, suppliers }: UsePurchaseOrderFormP
} else { } else {
item.unitId = item.base_unit_id; item.unitId = item.base_unit_id;
} }
// Switch unit doesn't change Total Amount (Subtotal), but implies Unit Price changes?
// Actually if I switch unit, the Quantity is usually for that unit.
// If I have 1 Box ($100), and switch to Pc. Quantity is still 1.
// Total is $100. So Unit Price (per Pc) becomes $100.
// This seems safely consistent with "Total Amount" anchor.
} else { } else {
// @ts-ignore // @ts-ignore
item[field] = value; item[field] = value;
} }
// 計算小計 // 重新計算 (Always derive UnitPrice from Subtotal and Quantity)
if (field === "quantity" || field === "unitPrice" || field === "productId") { // 除了剛剛已經算過 subtotal 的 productId case
item.subtotal = calculateSubtotal( if (field !== "productId") {
Number(item.quantity), if (item.quantity > 0) {
Number(item.unitPrice) item.unitPrice = Number(item.subtotal) / Number(item.quantity);
); } else {
item.unitPrice = 0;
}
} }
newItems[index] = item; newItems[index] = item;