實作產品與庫存匯入邏輯 (ProductImport, InventoryImport) 並更新相關 Service 與 Controller
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 56s

This commit is contained in:
2026-03-02 11:58:04 +08:00
parent 649af40919
commit 7dac2d1f77
7 changed files with 138 additions and 114 deletions

View File

@@ -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);
}

View File

@@ -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'] ?? '',
]);

View File

@@ -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;
}
}

View File

@@ -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([

View File

@@ -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

View File

@@ -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;
}
}