feat: 整合門市領料日誌、API 文件存取、修改庫存與併發編號問題、供應商商品內聯編輯及日誌 UI 優化
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 1m0s

This commit is contained in:
2026-03-02 16:42:12 +08:00
parent 7dac2d1f77
commit 0a955fb993
33 changed files with 1424 additions and 853 deletions

View File

@@ -26,8 +26,7 @@ class InventoryImport implements ToModel, WithHeadingRow, WithValidation, WithMa
// 修正時間精度:將選定的日期與「現在的時分秒」結合
// 這樣既能保留使用者選的日期,又能提供精確的紀錄時點排順序
$currentTime = date('H:i:s');
$this->inboundDate = $inboundDate . ' ' . $currentTime;
$this->inboundDate = \Illuminate\Support\Carbon::parse($inboundDate)->setTimeFrom(now())->toDateTimeString();
$this->notes = $notes;
}

View File

@@ -9,10 +9,43 @@ use Illuminate\Validation\Rule;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
use Maatwebsite\Excel\Concerns\WithValidation;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Imports\HeadingRowFormatter;
class ProductImport implements ToModel, WithHeadingRow, WithValidation, WithMapping
/**
* 商品匯入主類別
*
* 實作 WithMultipleSheets 以限定只讀取第一個工作表(資料頁),
* 跳過第二個工作表(填寫說明頁),避免說明頁的資料被誤匯入並觸發驗證錯誤。
*/
class ProductImport implements WithMultipleSheets
{
public function __construct()
{
// 禁用標題格式化,保留中文標題
HeadingRowFormatter::default('none');
}
/**
* 指定只處理第一個工作表 (index 0)
*/
public function sheets(): array
{
return [
0 => new ProductDataSheetImport(),
];
}
}
/**
* 商品匯入 - 資料工作表處理類別
*
* 負責實際的資料解析、驗證與儲存邏輯。
* 只會被套用到 Excel 的第一個工作表(資料頁)。
*/
class ProductDataSheetImport implements ToModel, WithHeadingRow, WithValidation, WithMapping, SkipsEmptyRows
{
private $categories;
private $units;
@@ -20,9 +53,6 @@ class ProductImport implements ToModel, WithHeadingRow, WithValidation, WithMapp
public function __construct()
{
// 禁用標題格式化,保留中文標題
HeadingRowFormatter::default('none');
// 快取所有類別與單位,避免 N+1 查詢
$this->categories = Category::pluck('id', 'name');
$this->units = Unit::pluck('id', 'name');
@@ -30,28 +60,37 @@ class ProductImport implements ToModel, WithHeadingRow, WithValidation, WithMapp
}
/**
* @param mixed $row
*
* @return array
*/
* 資料映射:將 Excel 原始標題(含「(選填)」)對應到乾淨的鍵名
*
* 注意WithValidation 驗證的是 map() 之前的原始資料,
* 因此 rules() 中的鍵名必須匹配 Excel 的原始標題。
* map() 的返回值只影響 model() 接收到的資料。
*/
public function map($row): array
{
// 強制將代號與條碼轉為字串,避免純數字被當作整數處理導致 max:5 驗證錯誤
if (isset($row['商品代號'])) {
$row['商品代號'] = (string) $row['商品代號'];
}
if (isset($row['條碼'])) {
$row['條碼'] = (string) $row['條碼'];
}
return $row;
$code = $row['商品代號(選填)'] ?? $row['商品代號'] ?? null;
$barcode = $row['條碼(選填)'] ?? $row['條碼'] ?? null;
return [
'商品代號' => $code !== null ? (string)$code : null,
'條碼' => $barcode !== null ? (string)$barcode : null,
'商品名稱' => $row['商品名稱'] ?? null,
'類別名稱' => $row['類別名稱'] ?? null,
'品牌' => $row['品牌'] ?? null,
'規格' => $row['規格'] ?? null,
'基本單位' => $row['基本單位'] ?? null,
'大單位' => $row['大單位'] ?? null,
'換算率' => isset($row['換算率']) ? (float)$row['換算率'] : null,
'成本價' => isset($row['成本價']) ? (float)$row['成本價'] : null,
'售價' => isset($row['售價']) ? (float)$row['售價'] : null,
'會員價' => isset($row['會員價']) ? (float)$row['會員價'] : null,
'批發價' => isset($row['批發價']) ? (float)$row['批發價'] : null,
];
}
/**
* @param array $row
*
* @return \Illuminate\Database\Eloquent\Model|null
*/
* @param array $row (map() 回傳的乾淨鍵名陣列)
*/
public function model(array $row)
{
// 查找關聯 ID
@@ -96,13 +135,17 @@ class ProductImport implements ToModel, WithHeadingRow, WithValidation, WithMapp
return null; // 返回 null因為 Service 已經處理完儲存
}
}
/**
* 驗證規則
*
* 鍵名必須匹配 Excel 原始標題(含「(選填)」後綴),
* 因為 WithValidation 驗證的是 map() 之前的原始資料。
*/
public function rules(): array
{
return [
'商品代號' => ['nullable', 'string', 'min:2', 'max:8'],
'條碼' => ['nullable', 'string'],
'商品代號(選填)' => ['nullable', 'string', 'min:2', 'max:8'],
'條碼(選填)' => ['nullable', 'string'],
'商品名稱' => ['required', 'string'],
'類別名稱' => ['required', function($attribute, $value, $fail) {
if (!isset($this->categories[$value])) {
@@ -127,4 +170,16 @@ class ProductImport implements ToModel, WithHeadingRow, WithValidation, WithMapp
'批發價' => ['nullable', 'numeric', 'min:0'],
];
}
/**
* 自訂驗證錯誤訊息的欄位名稱
* 把含 "(選填)" 後綴的欄位顯示為友善名稱
*/
public function customValidationAttributes(): array
{
return [
'商品代號(選填)' => '商品代號',
'條碼(選填)' => '條碼',
];
}
}