From 3088959c7c784c08613ca9bd040014f257ebed42 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Thu, 8 Jan 2026 17:51:06 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=8E=A1=E8=B3=BC=E5=96=AE?= =?UTF-8?q?=E7=9A=84=E5=95=86=E5=93=81=E9=87=91=E9=A1=8D=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/PurchaseOrderController.php | 30 ++++++++------ .../PurchaseOrder/PurchaseOrderItemsTable.tsx | 39 +++++++++---------- resources/js/Pages/PurchaseOrder/Create.tsx | 1 + resources/js/hooks/usePurchaseOrderForm.ts | 23 +++++++---- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/app/Http/Controllers/PurchaseOrderController.php b/app/Http/Controllers/PurchaseOrderController.php index 9f40598..26ea08a 100644 --- a/app/Http/Controllers/PurchaseOrderController.php +++ b/app/Http/Controllers/PurchaseOrderController.php @@ -97,8 +97,8 @@ class PurchaseOrderController extends Controller 'items' => 'required|array|min:1', '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 + 'items.*.subtotal' => 'required|numeric|min:0', // 總金額 + 'items.*.unitId' => 'nullable|exists:units,id', ]); try { @@ -122,7 +122,7 @@ class PurchaseOrderController extends Controller $totalAmount = 0; foreach ($validated['items'] as $item) { - $totalAmount += $item['quantity'] * $item['unitPrice']; + $totalAmount += $item['subtotal']; } // Simple tax calculation (e.g., 5%) @@ -157,12 +157,15 @@ class PurchaseOrderController extends Controller ]); foreach ($validated['items'] as $item) { + // 反算單價 + $unitPrice = $item['quantity'] > 0 ? $item['subtotal'] / $item['quantity'] : 0; + $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'], + 'unit_id' => $item['unitId'] ?? null, + 'unit_price' => $unitPrice, + 'subtotal' => $item['subtotal'], ]); } @@ -310,8 +313,8 @@ class PurchaseOrderController extends Controller 'items' => 'required|array|min:1', '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 + 'items.*.subtotal' => 'required|numeric|min:0', // 總金額 + 'items.*.unitId' => 'nullable|exists:units,id', ]); try { @@ -319,7 +322,7 @@ class PurchaseOrderController extends Controller $totalAmount = 0; foreach ($validated['items'] as $item) { - $totalAmount += $item['quantity'] * $item['unitPrice']; + $totalAmount += $item['subtotal']; } // Simple tax calculation (e.g., 5%) @@ -340,12 +343,15 @@ class PurchaseOrderController extends Controller // Sync items $order->items()->delete(); foreach ($validated['items'] as $item) { + // 反算單價 + $unitPrice = $item['quantity'] > 0 ? $item['subtotal'] / $item['quantity'] : 0; + $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'], + 'unit_id' => $item['unitId'] ?? null, + 'unit_price' => $unitPrice, + 'subtotal' => $item['subtotal'], ]); } diff --git a/resources/js/Components/PurchaseOrder/PurchaseOrderItemsTable.tsx b/resources/js/Components/PurchaseOrder/PurchaseOrderItemsTable.tsx index e1d7912..8da4164 100644 --- a/resources/js/Components/PurchaseOrder/PurchaseOrderItemsTable.tsx +++ b/resources/js/Components/PurchaseOrder/PurchaseOrderItemsTable.tsx @@ -50,8 +50,7 @@ export function PurchaseOrderItemsTable({ 數量 單位 換算基本單位 - 金額 - 小計 + 總金額 單價 / 基本單位 {!isReadOnly && } @@ -60,7 +59,7 @@ export function PurchaseOrderItemsTable({ {items.length === 0 ? ( {isDisabled ? "請先選擇供應商後才能新增商品" : "尚未新增任何商品項"} @@ -69,9 +68,12 @@ export function PurchaseOrderItemsTable({ ) : ( items.map((item, index) => { // 計算換算後的單價 (基本單位單價) + // unitPrice is derived from subtotal / quantity + const currentUnitPrice = item.unitPrice; + const convertedUnitPrice = item.selectedUnit === 'large' && item.conversion_rate - ? item.unitPrice / item.conversion_rate - : item.unitPrice; + ? currentUnitPrice / item.conversion_rate + : currentUnitPrice; return ( @@ -162,44 +164,39 @@ export function PurchaseOrderItemsTable({ - {/* 單價 */} + {/* 總金額 (主要輸入欄位) */} {isReadOnly ? ( - {formatCurrency(item.unitPrice)} + {formatCurrency(item.subtotal)} ) : (
- onItemChange?.(index, "unitPrice", Number(e.target.value)) + onItemChange?.(index, "subtotal", Number(e.target.value)) } disabled={isDisabled} 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-gray-200" }`} /> - {/* 錯誤提示 (保留必填提示) */} - {item.quantity > 0 && (!item.unitPrice || item.unitPrice <= 0) && ( + {/* 錯誤提示 */} + {item.quantity > 0 && (!item.subtotal || item.subtotal <= 0) && (

- ❌ 請填寫金額 + ❌ 請填寫總金額

)}
)}
- {/* 小計 */} - - {formatCurrency(item.subtotal)} - - - {/* 換算採購單價 / 基本單位 */} + {/* 換算採購單價 / 基本單位 (顯示換算結果) */}
diff --git a/resources/js/Pages/PurchaseOrder/Create.tsx b/resources/js/Pages/PurchaseOrder/Create.tsx index 525e927..99c37ba 100644 --- a/resources/js/Pages/PurchaseOrder/Create.tsx +++ b/resources/js/Pages/PurchaseOrder/Create.tsx @@ -115,6 +115,7 @@ export default function CreatePurchaseOrder({ quantity: item.quantity, unitPrice: item.unitPrice, unitId: item.unitId, + subtotal: item.subtotal, })), }; diff --git a/resources/js/hooks/usePurchaseOrderForm.ts b/resources/js/hooks/usePurchaseOrderForm.ts index 516dcb9..47e139a 100644 --- a/resources/js/hooks/usePurchaseOrderForm.ts +++ b/resources/js/hooks/usePurchaseOrderForm.ts @@ -87,7 +87,6 @@ export function usePurchaseOrderForm({ order, suppliers }: UsePurchaseOrderFormP item.previousPrice = product.lastPrice; // 決定預設單位 - // 若有採購單位且等於大單位,預設為大單位 const isPurchaseUnitLarge = product.purchase_unit_id && product.large_unit_id && product.purchase_unit_id === product.large_unit_id; if (isPurchaseUnitLarge) { @@ -97,6 +96,9 @@ export function usePurchaseOrderForm({ order, suppliers }: UsePurchaseOrderFormP item.selectedUnit = 'base'; item.unitId = product.base_unit_id; } + + // 初始小計 = 數量 * 單價 + item.subtotal = calculateSubtotal(Number(item.quantity), Number(item.unitPrice)); } } else if (field === "selectedUnit") { // @ts-ignore @@ -106,17 +108,24 @@ export function usePurchaseOrderForm({ order, suppliers }: UsePurchaseOrderFormP } else { 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 { // @ts-ignore item[field] = value; } - // 計算小計 - if (field === "quantity" || field === "unitPrice" || field === "productId") { - item.subtotal = calculateSubtotal( - Number(item.quantity), - Number(item.unitPrice) - ); + // 重新計算 (Always derive UnitPrice from Subtotal and Quantity) + // 除了剛剛已經算過 subtotal 的 productId case + if (field !== "productId") { + if (item.quantity > 0) { + item.unitPrice = Number(item.subtotal) / Number(item.quantity); + } else { + item.unitPrice = 0; + } } newItems[index] = item;