管理採購單的商品金額修正
This commit is contained in:
@@ -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'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user