diff --git a/app/Modules/Inventory/Contracts/InventoryServiceInterface.php b/app/Modules/Inventory/Contracts/InventoryServiceInterface.php
index d72538a..f77cd98 100644
--- a/app/Modules/Inventory/Contracts/InventoryServiceInterface.php
+++ b/app/Modules/Inventory/Contracts/InventoryServiceInterface.php
@@ -106,6 +106,16 @@ interface InventoryServiceInterface
*/
public function decreaseInventoryQuantity(int $inventoryId, float $quantity, ?string $reason = null, ?string $referenceType = null, $referenceId = null);
+ /**
+ * Find a specific inventory record by warehouse, product and batch.
+ *
+ * @param int $warehouseId
+ * @param int $productId
+ * @param string|null $batchNumber
+ * @return object|null
+ */
+ public function findInventoryByBatch(int $warehouseId, int $productId, ?string $batchNumber);
+
/**
* Get statistics for the dashboard.
*
diff --git a/app/Modules/Inventory/Controllers/TransferOrderController.php b/app/Modules/Inventory/Controllers/TransferOrderController.php
index 5c1c493..d6fc56d 100644
--- a/app/Modules/Inventory/Controllers/TransferOrderController.php
+++ b/app/Modules/Inventory/Controllers/TransferOrderController.php
@@ -127,7 +127,7 @@ class TransferOrderController extends Controller
'batch_number' => $item->batch_number,
'unit' => $item->product->baseUnit?->name,
'quantity' => (float) $item->quantity,
- 'max_quantity' => $stock ? (float) $stock->quantity : 0.0,
+ 'max_quantity' => $item->snapshot_quantity ? (float) $item->snapshot_quantity : ($stock ? (float) $stock->quantity : 0.0),
'notes' => $item->notes,
];
}),
@@ -154,14 +154,22 @@ class TransferOrderController extends Controller
]);
// 1. 先更新資料
+ $itemsChanged = false;
if ($request->has('items')) {
- $this->transferService->updateItems($order, $validated['items']);
+ $itemsChanged = $this->transferService->updateItems($order, $validated['items']);
}
- $order->fill($request->only(['remarks']));
+ $remarksChanged = $order->remarks !== ($validated['remarks'] ?? null);
- // [IMPORTANT] 使用 touch() 確保即便只有品項異動,也會因為 updated_at 變更而觸發自動日誌
- $order->touch();
+ if ($itemsChanged || $remarksChanged) {
+ $order->remarks = $validated['remarks'] ?? null;
+ // [IMPORTANT] 使用 touch() 確保即便只有品項異動,也會因為 updated_at 變更而觸發自動日誌
+ $order->touch();
+ $message = '儲存成功';
+ } else {
+ $message = '資料未變更';
+ // 如果沒變更,就不執行 touch(),也不會產生 Activity Log
+ }
// 2. 判斷是否需要過帳
if ($request->input('action') === 'post') {
@@ -174,7 +182,7 @@ class TransferOrderController extends Controller
}
}
- return redirect()->back()->with('success', '儲存成功');
+ return redirect()->back()->with('success', $message);
}
public function destroy(InventoryTransferOrder $order)
diff --git a/app/Modules/Inventory/Models/InventoryTransferItem.php b/app/Modules/Inventory/Models/InventoryTransferItem.php
index 6b2386b..43366d2 100644
--- a/app/Modules/Inventory/Models/InventoryTransferItem.php
+++ b/app/Modules/Inventory/Models/InventoryTransferItem.php
@@ -15,6 +15,7 @@ class InventoryTransferItem extends Model
'product_id',
'batch_number',
'quantity',
+ 'snapshot_quantity',
'notes',
];
diff --git a/app/Modules/Inventory/Services/InventoryService.php b/app/Modules/Inventory/Services/InventoryService.php
index d8e0d99..314371d 100644
--- a/app/Modules/Inventory/Services/InventoryService.php
+++ b/app/Modules/Inventory/Services/InventoryService.php
@@ -188,6 +188,14 @@ class InventoryService implements InventoryServiceInterface
});
}
+ public function findInventoryByBatch(int $warehouseId, int $productId, ?string $batchNumber)
+ {
+ return Inventory::where('warehouse_id', $warehouseId)
+ ->where('product_id', $productId)
+ ->where('batch_number', $batchNumber)
+ ->first();
+ }
+
public function getDashboardStats(): array
{
// 庫存總表 join 安全庫存表,計算低庫存
diff --git a/app/Modules/Inventory/Services/TransferService.php b/app/Modules/Inventory/Services/TransferService.php
index 6a0bb16..70a47c5 100644
--- a/app/Modules/Inventory/Services/TransferService.php
+++ b/app/Modules/Inventory/Services/TransferService.php
@@ -31,9 +31,9 @@ class TransferService
/**
* 更新調撥單明細 (支援精確 Diff 與自動日誌整合)
*/
- public function updateItems(InventoryTransferOrder $order, array $itemsData): void
+ public function updateItems(InventoryTransferOrder $order, array $itemsData): bool
{
- DB::transaction(function () use ($order, $itemsData) {
+ return DB::transaction(function () use ($order, $itemsData) {
// 1. 準備舊資料索引 (Key: product_id . '_' . batch_number)
$oldItemsMap = $order->items->mapWithKeys(function ($item) {
$key = $item->product_id . '_' . ($item->batch_number ?? '');
@@ -88,9 +88,13 @@ class TransferService
];
}
} else {
- // 新增
- $diff['added'][] = [
+ // 新增 (使用者需求:顯示為更新,從 0 -> X)
+ $diff['updated'][] = [
'product_name' => $item->product->name,
+ 'old' => [
+ 'quantity' => 0,
+ 'notes' => null,
+ ],
'new' => [
'quantity' => (float)$item->quantity,
'notes' => $item->notes,
@@ -113,10 +117,12 @@ class TransferService
}
// 4. 將 Diff 注入到 Model 的暫存屬性中
- // 如果 Diff 有內容,才注入
- if (!empty($diff['added']) || !empty($diff['removed']) || !empty($diff['updated'])) {
+ $hasChanged = !empty($diff['added']) || !empty($diff['removed']) || !empty($diff['updated']);
+ if ($hasChanged) {
$order->activityProperties['items_diff'] = $diff;
}
+
+ return $hasChanged;
});
}
@@ -149,6 +155,9 @@ class TransferService
$oldSourceQty = $sourceInventory->quantity;
$newSourceQty = $oldSourceQty - $item->quantity;
+
+ // 儲存庫存快照
+ $item->update(['snapshot_quantity' => $oldSourceQty]);
$sourceInventory->quantity = $newSourceQty;
// 更新總值 (假設成本不變)
diff --git a/app/Modules/Procurement/Controllers/ShippingOrderController.php b/app/Modules/Procurement/Controllers/ShippingOrderController.php
new file mode 100644
index 0000000..f817e8a
--- /dev/null
+++ b/app/Modules/Procurement/Controllers/ShippingOrderController.php
@@ -0,0 +1,133 @@
+inventoryService = $inventoryService;
+ $this->coreService = $coreService;
+ $this->shippingService = $shippingService;
+ }
+
+ public function index(Request $request)
+ {
+ return Inertia::render('Common/UnderConstruction', [
+ 'featureName' => '出貨單管理'
+ ]);
+
+ /* 原有邏輯暫存
+ $query = ShippingOrder::query();
+
+ // 搜尋
+ if ($request->search) {
+ $query->where(function($q) use ($request) {
+ $q->where('doc_no', 'like', "%{$request->search}%")
+ ->orWhere('customer_name', 'like', "%{$request->search}%");
+ });
+ }
+
+ // 狀態篩選
+ if ($request->status && $request->status !== 'all') {
+ $query->where('status', $request->status);
+ }
+
+ $perPage = $request->input('per_page', 10);
+ $orders = $query->orderBy('id', 'desc')->paginate($perPage)->withQueryString();
+
+ // 水和倉庫與使用者
+ $warehouses = $this->inventoryService->getAllWarehouses();
+ $userIds = $orders->getCollection()->pluck('created_by')->filter()->unique()->toArray();
+ $users = $this->coreService->getUsersByIds($userIds)->keyBy('id');
+
+ $orders->getCollection()->transform(function ($order) use ($warehouses, $users) {
+ $order->warehouse_name = $warehouses->firstWhere('id', $order->warehouse_id)?->name ?? 'Unknown';
+ $order->creator_name = $users->get($order->created_by)?->name ?? 'System';
+ return $order;
+ });
+
+ return Inertia::render('ShippingOrder/Index', [
+ 'orders' => $orders,
+ 'filters' => $request->only(['search', 'status', 'per_page']),
+ 'warehouses' => $warehouses->map(fn($w) => ['id' => $w->id, 'name' => $w->name]),
+ ]);
+ */
+ }
+
+ public function create()
+ {
+ return Inertia::render('Common/UnderConstruction', [
+ 'featureName' => '出貨單建立'
+ ]);
+
+ /* 原有邏輯暫存
+ $warehouses = $this->inventoryService->getAllWarehouses();
+ $products = $this->inventoryService->getAllProducts();
+
+ return Inertia::render('ShippingOrder/Create', [
+ 'warehouses' => $warehouses->map(fn($w) => ['id' => $w->id, 'name' => $w->name]),
+ 'products' => $products->map(fn($p) => [
+ 'id' => $p->id,
+ 'name' => $p->name,
+ 'code' => $p->code,
+ 'unit_name' => $p->baseUnit?->name,
+ ]),
+ ]);
+ */
+ }
+
+ public function store(Request $request)
+ {
+ return back()->with('error', '出貨單管理功能正在製作中');
+ }
+
+ public function show($id)
+ {
+ return Inertia::render('Common/UnderConstruction', [
+ 'featureName' => '出貨單詳情'
+ ]);
+ }
+
+ public function edit($id)
+ {
+ return Inertia::render('Common/UnderConstruction', [
+ 'featureName' => '出貨單編輯'
+ ]);
+ }
+
+ public function update(Request $request, $id)
+ {
+ return back()->with('error', '出貨單管理功能正在製作中');
+ }
+
+ public function post($id)
+ {
+ return back()->with('error', '出貨單管理功能正在製作中');
+ }
+
+ public function destroy($id)
+ {
+ $order = ShippingOrder::findOrFail($id);
+ if ($order->status !== 'draft') {
+ return back()->withErrors(['error' => '僅能刪除草稿狀態的單據']);
+ }
+ $order->delete();
+ return redirect()->route('delivery-notes.index')->with('success', '出貨單已刪除');
+ }
+}
diff --git a/app/Modules/Procurement/Models/ShippingOrder.php b/app/Modules/Procurement/Models/ShippingOrder.php
new file mode 100644
index 0000000..6a6f358
--- /dev/null
+++ b/app/Modules/Procurement/Models/ShippingOrder.php
@@ -0,0 +1,89 @@
+ 'date',
+ 'posted_at' => 'datetime',
+ 'total_amount' => 'decimal:2',
+ 'tax_amount' => 'decimal:2',
+ 'grand_total' => 'decimal:2',
+ ];
+
+ public function getActivitylogOptions(): LogOptions
+ {
+ return LogOptions::defaults()
+ ->logAll()
+ ->logOnlyDirty()
+ ->dontSubmitEmptyLogs();
+ }
+
+ public function tapActivity(Activity $activity, string $eventName)
+ {
+ $snapshot = $activity->properties['snapshot'] ?? [];
+ $snapshot['doc_no'] = $this->doc_no;
+ $snapshot['customer_name'] = $this->customer_name;
+
+ $activity->properties = $activity->properties->merge([
+ 'snapshot' => $snapshot
+ ]);
+ }
+
+ public function items()
+ {
+ return $this->hasMany(ShippingOrderItem::class);
+ }
+
+ /**
+ * 自動產生單號
+ */
+ protected static function boot()
+ {
+ parent::boot();
+
+ static::creating(function ($model) {
+ if (empty($model->doc_no)) {
+ $today = date('Ymd');
+ $prefix = 'SHP-' . $today . '-';
+
+ $lastDoc = static::where('doc_no', 'like', $prefix . '%')
+ ->orderBy('doc_no', 'desc')
+ ->first();
+
+ if ($lastDoc) {
+ $lastNumber = substr($lastDoc->doc_no, -2);
+ $nextNumber = str_pad((int)$lastNumber + 1, 2, '0', STR_PAD_LEFT);
+ } else {
+ $nextNumber = '01';
+ }
+
+ $model->doc_no = $prefix . $nextNumber;
+ }
+ });
+ }
+}
diff --git a/app/Modules/Procurement/Models/ShippingOrderItem.php b/app/Modules/Procurement/Models/ShippingOrderItem.php
new file mode 100644
index 0000000..58343b4
--- /dev/null
+++ b/app/Modules/Procurement/Models/ShippingOrderItem.php
@@ -0,0 +1,35 @@
+ 'decimal:4',
+ 'unit_price' => 'decimal:4',
+ 'subtotal' => 'decimal:2',
+ ];
+
+ public function shippingOrder()
+ {
+ return $this->belongsTo(ShippingOrder::class);
+ }
+
+ // 注意:在模組化架構下,跨模組關聯應謹慎使用或是直接在 Controller 水和 (Hydration)
+ // 但為了開發便利,暫時保留對 Product 的關聯(如果 Product 在不同模組,可能無法直接 lazy load)
+}
diff --git a/app/Modules/Procurement/Routes/web.php b/app/Modules/Procurement/Routes/web.php
index 5275186..7d948b7 100644
--- a/app/Modules/Procurement/Routes/web.php
+++ b/app/Modules/Procurement/Routes/web.php
@@ -35,4 +35,24 @@ Route::middleware('auth')->group(function () {
Route::put('/purchase-orders/{id}', [PurchaseOrderController::class, 'update'])->middleware('permission:purchase_orders.edit')->name('purchase-orders.update');
Route::delete('/purchase-orders/{id}', [PurchaseOrderController::class, 'destroy'])->middleware('permission:purchase_orders.delete')->name('purchase-orders.destroy');
});
+
+ // 出貨單管理 (Delivery Notes)
+ Route::middleware('permission:delivery_notes.view')->group(function () {
+ Route::get('/delivery-notes', [\App\Modules\Procurement\Controllers\ShippingOrderController::class, 'index'])->name('delivery-notes.index');
+
+ Route::middleware('permission:delivery_notes.create')->group(function () {
+ Route::get('/delivery-notes/create', [\App\Modules\Procurement\Controllers\ShippingOrderController::class, 'create'])->name('delivery-notes.create');
+ Route::post('/delivery-notes', [\App\Modules\Procurement\Controllers\ShippingOrderController::class, 'store'])->name('delivery-notes.store');
+ });
+
+ Route::get('/delivery-notes/{id}', [\App\Modules\Procurement\Controllers\ShippingOrderController::class, 'show'])->name('delivery-notes.show');
+
+ Route::middleware('permission:delivery_notes.edit')->group(function () {
+ Route::get('/delivery-notes/{id}/edit', [\App\Modules\Procurement\Controllers\ShippingOrderController::class, 'edit'])->name('delivery-notes.edit');
+ Route::put('/delivery-notes/{id}', [\App\Modules\Procurement\Controllers\ShippingOrderController::class, 'update'])->name('delivery-notes.update');
+ Route::post('/delivery-notes/{id}/post', [\App\Modules\Procurement\Controllers\ShippingOrderController::class, 'post'])->name('delivery-notes.post');
+ });
+
+ Route::delete('/delivery-notes/{id}', [\App\Modules\Procurement\Controllers\ShippingOrderController::class, 'destroy'])->middleware('permission:delivery_notes.delete')->name('delivery-notes.destroy');
+ });
});
diff --git a/app/Modules/Procurement/Services/ShippingService.php b/app/Modules/Procurement/Services/ShippingService.php
new file mode 100644
index 0000000..68dd8b4
--- /dev/null
+++ b/app/Modules/Procurement/Services/ShippingService.php
@@ -0,0 +1,118 @@
+inventoryService = $inventoryService;
+ }
+
+ public function createShippingOrder(array $data)
+ {
+ return DB::transaction(function () use ($data) {
+ $order = ShippingOrder::create([
+ 'warehouse_id' => $data['warehouse_id'],
+ 'customer_name' => $data['customer_name'] ?? null,
+ 'shipping_date' => $data['shipping_date'],
+ 'status' => 'draft',
+ 'remarks' => $data['remarks'] ?? null,
+ 'created_by' => auth()->id(),
+ 'total_amount' => $data['total_amount'] ?? 0,
+ 'tax_amount' => $data['tax_amount'] ?? 0,
+ 'grand_total' => $data['grand_total'] ?? 0,
+ ]);
+
+ foreach ($data['items'] as $item) {
+ $order->items()->create([
+ 'product_id' => $item['product_id'],
+ 'batch_number' => $item['batch_number'] ?? null,
+ 'quantity' => $item['quantity'],
+ 'unit_price' => $item['unit_price'] ?? 0,
+ 'subtotal' => $item['subtotal'] ?? ($item['quantity'] * ($item['unit_price'] ?? 0)),
+ 'remark' => $item['remark'] ?? null,
+ ]);
+ }
+
+ return $order;
+ });
+ }
+
+ public function updateShippingOrder(ShippingOrder $order, array $data)
+ {
+ return DB::transaction(function () use ($order, $data) {
+ $order->update([
+ 'warehouse_id' => $data['warehouse_id'],
+ 'customer_name' => $data['customer_name'] ?? null,
+ 'shipping_date' => $data['shipping_date'],
+ 'remarks' => $data['remarks'] ?? null,
+ 'total_amount' => $data['total_amount'] ?? 0,
+ 'tax_amount' => $data['tax_amount'] ?? 0,
+ 'grand_total' => $data['grand_total'] ?? 0,
+ ]);
+
+ // 簡單處理:刪除舊項目並新增
+ $order->items()->delete();
+ foreach ($data['items'] as $item) {
+ $order->items()->create([
+ 'product_id' => $item['product_id'],
+ 'batch_number' => $item['batch_number'] ?? null,
+ 'quantity' => $item['quantity'],
+ 'unit_price' => $item['unit_price'] ?? 0,
+ 'subtotal' => $item['subtotal'] ?? ($item['quantity'] * ($item['unit_price'] ?? 0)),
+ 'remark' => $item['remark'] ?? null,
+ ]);
+ }
+
+ return $order;
+ });
+ }
+
+ public function post(ShippingOrder $order)
+ {
+ if ($order->status !== 'draft') {
+ throw new \Exception('該單據已過帳或已取消。');
+ }
+
+ return DB::transaction(function () use ($order) {
+ foreach ($order->items as $item) {
+ // 尋找對應的庫存紀錄
+ $inventory = $this->inventoryService->findInventoryByBatch(
+ $order->warehouse_id,
+ $item->product_id,
+ $item->batch_number
+ );
+
+ if (!$inventory || $inventory->quantity < $item->quantity) {
+ $productName = $this->inventoryService->getProduct($item->product_id)?->name ?? 'Unknown';
+ throw new \Exception("商品 [{$productName}] (批號: {$item->batch_number}) 庫存不足。");
+ }
+
+ // 扣除庫存
+ $this->inventoryService->decreaseInventoryQuantity(
+ $inventory->id,
+ $item->quantity,
+ "出貨扣款: 單號 [{$order->doc_no}]",
+ 'ShippingOrder',
+ $order->id
+ );
+ }
+
+ $order->update([
+ 'status' => 'completed',
+ 'posted_by' => auth()->id(),
+ 'posted_at' => now(),
+ ]);
+
+ return $order;
+ });
+ }
+}
diff --git a/database/migrations/tenant/2026_02_05_085924_add_snapshot_quantity_to_inventory_transfer_items.php b/database/migrations/tenant/2026_02_05_085924_add_snapshot_quantity_to_inventory_transfer_items.php
new file mode 100644
index 0000000..de92e75
--- /dev/null
+++ b/database/migrations/tenant/2026_02_05_085924_add_snapshot_quantity_to_inventory_transfer_items.php
@@ -0,0 +1,28 @@
+decimal('snapshot_quantity', 10, 2)->nullable()->comment('過帳時的來源倉可用庫存快照')->after('quantity');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('inventory_transfer_items', function (Blueprint $table) {
+ $table->dropColumn('snapshot_quantity');
+ });
+ }
+};
diff --git a/database/migrations/tenant/2026_02_05_100000_create_shipping_tables.php b/database/migrations/tenant/2026_02_05_100000_create_shipping_tables.php
new file mode 100644
index 0000000..3e58004
--- /dev/null
+++ b/database/migrations/tenant/2026_02_05_100000_create_shipping_tables.php
@@ -0,0 +1,62 @@
+id();
+ $table->string('doc_no')->unique()->comment('出貨單號');
+ $table->string('customer_name')->nullable()->comment('客戶名稱');
+ $table->unsignedBigInteger('warehouse_id')->comment('來源倉庫');
+ $table->string('status')->default('draft')->comment('狀態: draft, completed, cancelled');
+ $table->date('shipping_date')->comment('出貨日期');
+
+ $table->decimal('total_amount', 15, 2)->default(0)->comment('總金額 (不含稅)');
+ $table->decimal('tax_amount', 15, 2)->default(0)->comment('稅額');
+ $table->decimal('grand_total', 15, 2)->default(0)->comment('總計');
+
+ $table->text('remarks')->nullable()->comment('備註');
+
+ $table->unsignedBigInteger('created_by')->nullable();
+ $table->unsignedBigInteger('posted_by')->nullable();
+ $table->timestamp('posted_at')->nullable();
+
+ $table->timestamps();
+
+ $table->index('warehouse_id');
+ $table->index('status');
+ $table->index('shipping_date');
+ });
+
+ Schema::create('shipping_order_items', function (Blueprint $table) {
+ $table->id();
+ $table->foreignId('shipping_order_id')->constrained('shipping_orders')->onDelete('cascade');
+ $table->unsignedBigInteger('product_id')->comment('商品 ID');
+ $table->string('batch_number')->nullable()->comment('批號');
+ $table->decimal('quantity', 15, 4)->comment('出貨數量');
+ $table->decimal('unit_price', 15, 4)->default(0)->comment('單價');
+ $table->decimal('subtotal', 15, 2)->default(0)->comment('小計');
+ $table->string('remark')->nullable()->comment('項目備註');
+ $table->timestamps();
+
+ $table->index('product_id');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('shipping_order_items');
+ Schema::dropIfExists('shipping_orders');
+ }
+};
diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php
index a05f462..daa946c 100644
--- a/database/seeders/PermissionSeeder.php
+++ b/database/seeders/PermissionSeeder.php
@@ -61,6 +61,12 @@ class PermissionSeeder extends Seeder
'goods_receipts.edit',
'goods_receipts.delete',
+ // 出貨單管理 (Delivery Notes / Shipping Orders)
+ 'delivery_notes.view',
+ 'delivery_notes.create',
+ 'delivery_notes.edit',
+ 'delivery_notes.delete',
+
// 生產工單管理
'production_orders.view',
'production_orders.create',
@@ -138,6 +144,7 @@ class PermissionSeeder extends Seeder
'inventory_adjust.view', 'inventory_adjust.create', 'inventory_adjust.edit', 'inventory_adjust.delete',
'inventory_transfer.view', 'inventory_transfer.create', 'inventory_transfer.edit', 'inventory_transfer.delete',
'goods_receipts.view', 'goods_receipts.create', 'goods_receipts.edit', 'goods_receipts.delete',
+ 'delivery_notes.view', 'delivery_notes.create', 'delivery_notes.edit', 'delivery_notes.delete',
'production_orders.view', 'production_orders.create', 'production_orders.edit', 'production_orders.delete',
'recipes.view', 'recipes.create', 'recipes.edit', 'recipes.delete',
'vendors.view', 'vendors.create', 'vendors.edit', 'vendors.delete',
diff --git a/resources/js/Layouts/AuthenticatedLayout.tsx b/resources/js/Layouts/AuthenticatedLayout.tsx
index 65fdf7c..50f3117 100644
--- a/resources/js/Layouts/AuthenticatedLayout.tsx
+++ b/resources/js/Layouts/AuthenticatedLayout.tsx
@@ -150,13 +150,13 @@ export default function AuthenticatedLayout({
route: "/goods-receipts",
permission: "goods_receipts.view",
},
- // {
- // id: "delivery-note-list",
- // label: "出貨單管理 (開發中)",
- // icon:
+ 我們正在努力完善這個功能,以提供更優質的體驗。 + 這部分可能涉及與其他系統的深度整合,請稍候片刻。 +
+ +| 商品 | +批號 | +數量 | +單價 | +小計 | +操作 | +
|---|---|---|---|---|---|
|
+ |
+
+ |
+ + updateItem(index, { quantity: parseFloat(e.target.value) || 0 })} + min={0.0001} + step={0.0001} + /> + | ++ updateItem(index, { unit_price: parseFloat(e.target.value) || 0 })} + min={0} + /> + | ++ ${item.subtotal.toLocaleString()} + | ++ + | +
+ 建立出貨單並執行過帳扣庫存 +
++ 建立日期: {new Date(order.created_at).toLocaleString()} | 建立者: {order.creator_name} +
+| 商品名稱 / 編號 | +批號 | +數量 | +單價 | +小計 | +
|---|---|---|---|---|
|
+ {item.product_name}
+ {item.product_code}
+ |
+
+ |
+ + {parseFloat(item.quantity).toLocaleString()} + {item.unit_name} + | ++ ${parseFloat(item.unit_price).toLocaleString()} + | ++ ${parseFloat(item.subtotal).toLocaleString()} + | +
提示:尚未過帳
+此單據目前僅為草稿,尚未扣除庫存。確認無誤後請點擊右上角「執行過帳」。
+