diff --git a/app/Modules/Inventory/Contracts/ProductServiceInterface.php b/app/Modules/Inventory/Contracts/ProductServiceInterface.php index a4cd9ff..7f00167 100644 --- a/app/Modules/Inventory/Contracts/ProductServiceInterface.php +++ b/app/Modules/Inventory/Contracts/ProductServiceInterface.php @@ -38,4 +38,44 @@ interface ProductServiceInterface * @return \Illuminate\Database\Eloquent\Collection */ public function findByCodes(array $codes); + + /** + * 建立新商品。 + * + * @param array $data + * @return \App\Modules\Inventory\Models\Product + */ + public function createProduct(array $data); + + /** + * 更新現有商品。 + * + * @param \App\Modules\Inventory\Models\Product $product + * @param array $data + * @return \App\Modules\Inventory\Models\Product + */ + public function updateProduct(\App\Modules\Inventory\Models\Product $product, array $data); + + /** + * 生成隨機 8 碼代號 (大寫英文+數字)。 + * + * @return string + */ + public function generateRandomCode(); + + /** + * 生成隨機 13 碼條碼 (純數字)。 + * + * @return string + */ + public function generateRandomBarcode(); + + /** + * 根據條碼或代號查找商品。 + * + * @param string|null $barcode + * @param string|null $code + * @return \App\Modules\Inventory\Models\Product|null + */ + public function findByBarcodeOrCode(?string $barcode, ?string $code); } diff --git a/app/Modules/Inventory/Controllers/InventoryController.php b/app/Modules/Inventory/Controllers/InventoryController.php index ab7311e..9c26b5f 100644 --- a/app/Modules/Inventory/Controllers/InventoryController.php +++ b/app/Modules/Inventory/Controllers/InventoryController.php @@ -186,8 +186,11 @@ class InventoryController extends Controller ]); return DB::transaction(function () use ($validated, $warehouse) { + // 修正時間精度:手動入庫亦補上當下時分秒 + $inboundDateTime = $validated['inboundDate'] . ' ' . date('H:i:s'); + $this->inventoryService->processIncomingInventory($warehouse, $validated['items'], [ - 'inboundDate' => $validated['inboundDate'], + 'inboundDate' => $inboundDateTime, 'reason' => $validated['reason'], 'notes' => $validated['notes'] ?? '', ]); diff --git a/app/Modules/Inventory/Controllers/ProductController.php b/app/Modules/Inventory/Controllers/ProductController.php index def05c6..77a100c 100644 --- a/app/Modules/Inventory/Controllers/ProductController.php +++ b/app/Modules/Inventory/Controllers/ProductController.php @@ -16,6 +16,12 @@ use App\Modules\Inventory\Imports\ProductImport; class ProductController extends Controller { + protected $productService; + + public function __construct(\App\Modules\Inventory\Contracts\ProductServiceInterface $productService) + { + $this->productService = $productService; + } /** * 顯示資源列表。 */ @@ -195,15 +201,7 @@ class ProductController extends Controller 'is_active' => 'boolean', ]); - if (empty($validated['code'])) { - $validated['code'] = $this->generateRandomCode(); - } - - if (empty($validated['barcode'])) { - $validated['barcode'] = $this->generateRandomBarcode(); - } - - $product = Product::create($validated); + $product = $this->productService->createProduct($validated); return redirect()->route('products.index')->with('success', '商品已建立'); } @@ -262,15 +260,7 @@ class ProductController extends Controller 'is_active' => 'boolean', ]); - if (empty($validated['code'])) { - $validated['code'] = $this->generateRandomCode(); - } - - if (empty($validated['barcode'])) { - $validated['barcode'] = $this->generateRandomBarcode(); - } - - $product->update($validated); + $this->productService->updateProduct($product, $validated); if ($request->input('from') === 'show') { return redirect()->route('products.show', $product->id)->with('success', '商品已更新'); @@ -320,39 +310,4 @@ class ProductController extends Controller return redirect()->back()->withErrors(['file' => '匯入失敗: ' . $e->getMessage()]); } } - - /** - * 生成隨機 8 碼代號 (大寫英文+數字) - */ - private function generateRandomCode(): string - { - $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - $code = ''; - - do { - $code = ''; - for ($i = 0; $i < 8; $i++) { - $code .= $characters[rand(0, strlen($characters) - 1)]; - } - } while (Product::where('code', $code)->exists()); - - return $code; - } - - /** - * 生成隨機 13 碼條碼 (純數字) - */ - private function generateRandomBarcode(): string - { - $barcode = ''; - - do { - $barcode = ''; - for ($i = 0; $i < 13; $i++) { - $barcode .= rand(0, 9); - } - } while (Product::where('barcode', $barcode)->exists()); - - return $barcode; - } } diff --git a/app/Modules/Inventory/Imports/InventoryImport.php b/app/Modules/Inventory/Imports/InventoryImport.php index c3c5294..aba1726 100644 --- a/app/Modules/Inventory/Imports/InventoryImport.php +++ b/app/Modules/Inventory/Imports/InventoryImport.php @@ -23,7 +23,12 @@ class InventoryImport implements ToModel, WithHeadingRow, WithValidation, WithMa { HeadingRowFormatter::default('none'); $this->warehouse = $warehouse; - $this->inboundDate = $inboundDate; + + // 修正時間精度:將選定的日期與「現在的時分秒」結合 + // 這樣既能保留使用者選的日期,又能提供精確的紀錄時點排順序 + $currentTime = date('H:i:s'); + $this->inboundDate = $inboundDate . ' ' . $currentTime; + $this->notes = $notes; } @@ -95,7 +100,7 @@ class InventoryImport implements ToModel, WithHeadingRow, WithValidation, WithMa // 更新單價與總價值 $inventory->unit_cost = $unitCost; $inventory->total_value = $inventory->quantity * $unitCost; - $inventory->save(); + $inventory->saveQuietly(); // 記錄交易歷史 $inventory->transactions()->create([ diff --git a/app/Modules/Inventory/Imports/ProductImport.php b/app/Modules/Inventory/Imports/ProductImport.php index 4dafbaf..227cda5 100644 --- a/app/Modules/Inventory/Imports/ProductImport.php +++ b/app/Modules/Inventory/Imports/ProductImport.php @@ -16,6 +16,7 @@ class ProductImport implements ToModel, WithHeadingRow, WithValidation, WithMapp { private $categories; private $units; + private $productService; public function __construct() { @@ -25,6 +26,7 @@ class ProductImport implements ToModel, WithHeadingRow, WithValidation, WithMapp // 快取所有類別與單位,避免 N+1 查詢 $this->categories = Category::pluck('id', 'name'); $this->units = Unit::pluck('id', 'name'); + $this->productService = app(\App\Modules\Inventory\Contracts\ProductServiceInterface::class); } /** @@ -65,15 +67,8 @@ class ProductImport implements ToModel, WithHeadingRow, WithValidation, WithMapp $code = $row['商品代號'] ?? null; $barcode = $row['條碼'] ?? null; - // Upsert 邏輯:優先以條碼查找,次之以商品代號查找 - $product = null; - if (!empty($barcode)) { - $product = Product::where('barcode', $barcode)->first(); - } - - if (!$product && !empty($code)) { - $product = Product::where('code', $code)->first(); - } + // Upsert 邏輯:透過 Service 統一查找與處理 + $product = $this->productService->findByBarcodeOrCode($barcode, $code); $data = [ 'name' => $row['商品名稱'], @@ -91,58 +86,16 @@ class ProductImport implements ToModel, WithHeadingRow, WithValidation, WithMapp ]; if ($product) { - // 更新現有商品 - $product->update($data); - return null; // 返回 null 以避免 Maatwebsite/Excel 嘗試再次 insert + $this->productService->updateProduct($product, $data); + } else { + if (!empty($code)) $data['code'] = $code; + if (!empty($barcode)) $data['barcode'] = $barcode; + $this->productService->createProduct($data); } - // 建立新商品:處理代碼與條碼自動生成 - if (empty($code)) { - $code = $this->generateRandomCode(); - } - if (empty($barcode)) { - $barcode = $this->generateRandomBarcode(); - } - - $data['code'] = $code; - $data['barcode'] = $barcode; - - return new Product($data); + return null; // 返回 null,因為 Service 已經處理完儲存 } - /** - * 生成隨機 8 碼代號 (大寫英文+數字) - */ - private function generateRandomCode(): string - { - $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - $code = ''; - - do { - $code = ''; - for ($i = 0; $i < 8; $i++) { - $code .= $characters[rand(0, strlen($characters) - 1)]; - } - } while (Product::where('code', $code)->exists()); - - return $code; - } - - /** - * 生成隨機 13 碼條碼 (純數字) - */ - private function generateRandomBarcode(): string - { - $barcode = ''; - - do { - $barcode = ''; - for ($i = 0; $i < 13; $i++) { - $barcode .= rand(0, 9); - } - } while (Product::where('barcode', $barcode)->exists()); - - return $barcode; } public function rules(): array diff --git a/app/Modules/Inventory/Services/ProductService.php b/app/Modules/Inventory/Services/ProductService.php index 5b11e82..e1b2466 100644 --- a/app/Modules/Inventory/Services/ProductService.php +++ b/app/Modules/Inventory/Services/ProductService.php @@ -110,4 +110,72 @@ class ProductService implements ProductServiceInterface { return Product::whereIn('code', $codes)->get(); } + + public function createProduct(array $data) + { + if (empty($data['code'])) { + $data['code'] = $this->generateRandomCode(); + } + + if (empty($data['barcode'])) { + $data['barcode'] = $this->generateRandomBarcode(); + } + + return Product::create($data); + } + + public function updateProduct(Product $product, array $data) + { + if (empty($data['code'])) { + $data['code'] = $this->generateRandomCode(); + } + + if (empty($data['barcode'])) { + $data['barcode'] = $this->generateRandomBarcode(); + } + + $product->update($data); + return $product; + } + + public function generateRandomCode() + { + $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $code = ''; + + do { + $code = ''; + for ($i = 0; $i < 8; $i++) { + $code .= $characters[rand(0, strlen($characters) - 1)]; + } + } while (Product::where('code', $code)->exists()); + + return $code; + } + + public function generateRandomBarcode() + { + $barcode = ''; + + do { + $barcode = ''; + for ($i = 0; $i < 13; $i++) { + $barcode .= rand(0, 9); + } + } while (Product::where('barcode', $barcode)->exists()); + + return $barcode; + } + + public function findByBarcodeOrCode(?string $barcode, ?string $code) + { + $product = null; + if (!empty($barcode)) { + $product = Product::where('barcode', $barcode)->first(); + } + if (!$product && !empty($code)) { + $product = Product::where('code', $code)->first(); + } + return $product; + } } diff --git a/resources/js/Components/ActivityLog/ActivityDetailDialog.tsx b/resources/js/Components/ActivityLog/ActivityDetailDialog.tsx index 9fc49ab..2c1abfa 100644 --- a/resources/js/Components/ActivityLog/ActivityDetailDialog.tsx +++ b/resources/js/Components/ActivityLog/ActivityDetailDialog.tsx @@ -336,7 +336,7 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P if ((key === 'order_date' || key === 'expected_delivery_date' || key === 'invoice_date' || key === 'arrival_date' || key === 'expiry_date' || key === 'received_date' || key === 'production_date') && typeof value === 'string') { return value.split('T')[0].split(' ')[0]; } - if ((key === 'snapshot_date' || key === 'completed_at' || key === 'posted_at' || key === 'created_at' || key === 'updated_at') && typeof value === 'string') { + if ((key === 'snapshot_date' || key === 'completed_at' || key === 'posted_at' || key === 'created_at' || key === 'updated_at' || key === 'actual_time') && typeof value === 'string') { try { const date = new Date(value); return date.toLocaleString('zh-TW', {