feat: 新增商品 Excel 匯入功能與修復 HTTPS 混合內容問題
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Has been skipped
Koori-ERP-Deploy-System / deploy-production (push) Successful in 1m4s

1. 新增商品 Excel 匯入功能 (ProductImport, Export Template)
2. 調整商品代號驗證規則為 1-5 碼 (Controller & Import)
3. 修正 HTTPS Mixed Content 問題 (AppServiceProvider)
This commit is contained in:
2026-02-02 14:39:13 +08:00
parent df3db38dd4
commit 6204f0d915
13 changed files with 1352 additions and 10 deletions

View File

@@ -0,0 +1,105 @@
<?php
namespace App\Modules\Inventory\Imports;
use App\Modules\Inventory\Models\Category;
use App\Modules\Inventory\Models\Product;
use App\Modules\Inventory\Models\Unit;
use Illuminate\Validation\Rule;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithValidation;
use Maatwebsite\Excel\Imports\HeadingRowFormatter;
class ProductImport implements ToModel, WithHeadingRow, WithValidation, WithMapping
{
private $categories;
private $units;
public function __construct()
{
// 禁用標題格式化,保留中文標題
HeadingRowFormatter::default('none');
// 快取所有類別與單位,避免 N+1 查詢
$this->categories = Category::pluck('id', 'name');
$this->units = Unit::pluck('id', 'name');
}
/**
* @param mixed $row
*
* @return array
*/
public function map($row): array
{
// 強制將代號與條碼轉為字串,避免純數字被當作整數處理導致 max:5 驗證錯誤
if (isset($row['商品代號'])) {
$row['商品代號'] = (string) $row['商品代號'];
}
if (isset($row['條碼'])) {
$row['條碼'] = (string) $row['條碼'];
}
return $row;
}
/**
* @param array $row
*
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
// 查找關聯 ID
$categoryId = $this->categories[$row['類別名稱']] ?? null;
$baseUnitId = $this->units[$row['基本單位']] ?? null;
$largeUnitId = isset($row['大單位']) ? ($this->units[$row['大單位']] ?? null) : null;
// 若必要關聯找不到,理論上 Validation 會攔截,但此處做防禦性編程
if (!$categoryId || !$baseUnitId) {
return null;
}
return new Product([
'code' => $row['商品代號'],
'barcode' => $row['條碼'],
'name' => $row['商品名稱'],
'category_id' => $categoryId,
'brand' => $row['品牌'] ?? null,
'specification' => $row['規格'] ?? null,
'base_unit_id' => $baseUnitId,
'large_unit_id' => $largeUnitId,
'conversion_rate' => $row['換算率'] ?? null,
'purchase_unit_id' => null,
]);
}
public function rules(): array
{
return [
'商品代號' => ['required', 'string', 'min:1', 'max:5', 'unique:products,code'],
'條碼' => ['required', 'string', 'unique:products,barcode'],
'商品名稱' => ['required', 'string'],
'類別名稱' => ['required', function($attribute, $value, $fail) {
if (!isset($this->categories[$value])) {
$fail("找不到類別: " . $value);
}
}],
'基本單位' => ['required', function($attribute, $value, $fail) {
if (!isset($this->units[$value])) {
$fail("找不到單位: " . $value);
}
}],
'大單位' => ['nullable', function($attribute, $value, $fail) {
if ($value && !isset($this->units[$value])) {
$fail("找不到單位: " . $value);
}
}],
'換算率' => ['nullable', 'numeric', 'min:0.0001', 'required_with:大單位'],
];
}
}