1. 新增 StaffCardImportService,整合 OpenSpout 5.x 處理解析與批量更新邏輯。 2. 在 StaffCardController 加入匯入與範例下載 API 端點。 3. 更新員工識別卡 index 視圖,新增「匯入 Excel」按鈕與 Alpine.js 異步匯入彈窗。 4. 註冊相關 Web 路由。 5. 補全繁中、日文、英文多語系翻譯,並修復重複鍵值。 6. 修復 OpenSpout 5.x 相容性問題 (Row::getCells -> Row::toArray)。
137 lines
4.1 KiB
PHP
137 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\StaffCard;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use OpenSpout\Reader\XLSX\Reader;
|
|
use OpenSpout\Writer\XLSX\Writer;
|
|
use OpenSpout\Common\Entity\Row;
|
|
use OpenSpout\Common\Entity\Cell;
|
|
|
|
class StaffCardImportService
|
|
{
|
|
/**
|
|
* Import staff cards from Excel file
|
|
*
|
|
* @param string $filePath
|
|
* @param int|null $companyId
|
|
* @return array
|
|
*/
|
|
public function import(string $filePath, ?int $companyId = null): array
|
|
{
|
|
$reader = new Reader();
|
|
$reader->open($filePath);
|
|
|
|
$results = [
|
|
'success_count' => 0,
|
|
'error_count' => 0,
|
|
'errors' => [],
|
|
];
|
|
|
|
$rowCount = 0;
|
|
foreach ($reader->getSheetIterator() as $sheet) {
|
|
foreach ($sheet->getRowIterator() as $row) {
|
|
$rowCount++;
|
|
|
|
// Skip heading row (Row 1)
|
|
if ($rowCount === 1) {
|
|
continue;
|
|
}
|
|
|
|
$cells = $row->toArray();
|
|
|
|
// Expecting: 0:employee_id, 1:name, 2:card_uid, 3:notes
|
|
$data = [
|
|
'employee_id' => isset($cells[0]) ? (string)$cells[0] : null,
|
|
'name' => isset($cells[1]) ? (string)$cells[1] : null,
|
|
'card_uid' => isset($cells[2]) ? (string)$cells[2] : null,
|
|
'notes' => isset($cells[3]) ? (string)$cells[3] : null,
|
|
'company_id' => $companyId,
|
|
];
|
|
|
|
// Basic validation
|
|
if (empty($data['employee_id']) && empty($data['name']) && empty($data['card_uid'])) {
|
|
continue; // Skip empty rows
|
|
}
|
|
|
|
$validator = Validator::make($data, [
|
|
'employee_id' => 'required|string|max:50',
|
|
'name' => 'required|string|max:100',
|
|
'card_uid' => 'required|string|max:100',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
$results['error_count']++;
|
|
$results['errors'][] = [
|
|
'row' => $rowCount,
|
|
'messages' => $validator->errors()->all(),
|
|
];
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
// Update or create based on card_uid and company_id
|
|
StaffCard::updateOrCreate(
|
|
[
|
|
'company_id' => $data['company_id'],
|
|
'card_uid' => $data['card_uid'],
|
|
],
|
|
[
|
|
'employee_id' => $data['employee_id'],
|
|
'name' => $data['name'],
|
|
'notes' => $data['notes'],
|
|
'status' => 'active',
|
|
]
|
|
);
|
|
$results['success_count']++;
|
|
} catch (\Exception $e) {
|
|
$results['error_count']++;
|
|
$results['errors'][] = [
|
|
'row' => $rowCount,
|
|
'messages' => [$e->getMessage()],
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
$reader->close();
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Download Excel template for staff cards
|
|
*
|
|
* @return void
|
|
*/
|
|
public function downloadTemplate()
|
|
{
|
|
$writer = new Writer();
|
|
$fileName = 'staff_cards_template.xlsx';
|
|
|
|
$writer->openToBrowser($fileName);
|
|
|
|
// Header Row
|
|
$headerRow = Row::fromValues([
|
|
'Employee ID (必填)',
|
|
'Staff Name (必填)',
|
|
'IC Card UID (必填)',
|
|
'Notes (選填)'
|
|
]);
|
|
$writer->addRow($headerRow);
|
|
|
|
// Example Row
|
|
$exampleRow = Row::fromValues([
|
|
'S001',
|
|
'王小明',
|
|
'12345678',
|
|
'這是備註範例'
|
|
]);
|
|
$writer->addRow($exampleRow);
|
|
|
|
$writer->close();
|
|
}
|
|
}
|