|
|
|
|
@ -30,6 +30,9 @@ class VenueImportService
|
|
|
|
|
/** @var array<string, string>|null 主题:item_label => item_value(与数据字典 venue_type 同步,按请求缓存) */
|
|
|
|
|
private ?array $themeLabelToValueMapCache = null;
|
|
|
|
|
|
|
|
|
|
/** @var array<string, string>|null 开放模式:item_label => item_value(与数据字典 venue_open_mode 同步,按请求缓存) */
|
|
|
|
|
private ?array $openModeLabelToValueMapCache = null;
|
|
|
|
|
|
|
|
|
|
private const TICKET_TYPE_LABEL = [
|
|
|
|
|
'免费' => 'free',
|
|
|
|
|
'收费' => 'paid',
|
|
|
|
|
@ -46,12 +49,6 @@ class VenueImportService
|
|
|
|
|
'个人不需预约团队需预约' => 'team_required',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
private const OPEN_MODE_LABEL = [
|
|
|
|
|
'常态化全时开放' => 'fulltime',
|
|
|
|
|
'常态化定时开放' => 'scheduled',
|
|
|
|
|
'预约开放' => 'appointment',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return array<string, string> 中文标签 => item_value
|
|
|
|
|
*/
|
|
|
|
|
@ -73,6 +70,27 @@ class VenueImportService
|
|
|
|
|
return $this->themeLabelToValueMapCache;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return array<string, string> 中文标签 => item_value(与后台数据字典「场馆开放模式」一致,勿硬编码以免改文案后导入失败)
|
|
|
|
|
*/
|
|
|
|
|
private function openModeLabelToValueMap(): array
|
|
|
|
|
{
|
|
|
|
|
if ($this->openModeLabelToValueMapCache !== null) {
|
|
|
|
|
return $this->openModeLabelToValueMapCache;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->openModeLabelToValueMapCache = DictItem::query()
|
|
|
|
|
->where('dict_type', 'venue_open_mode')
|
|
|
|
|
->where('is_active', true)
|
|
|
|
|
->orderBy('sort')
|
|
|
|
|
->orderBy('id')
|
|
|
|
|
->get(['item_label', 'item_value'])
|
|
|
|
|
->mapWithKeys(fn ($r) => [trim((string) $r->item_label) => (string) $r->item_value])
|
|
|
|
|
->all();
|
|
|
|
|
|
|
|
|
|
return $this->openModeLabelToValueMapCache;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function buildTemplateSpreadsheet(): Spreadsheet
|
|
|
|
|
{
|
|
|
|
|
$spreadsheet = new Spreadsheet();
|
|
|
|
|
@ -153,9 +171,12 @@ class VenueImportService
|
|
|
|
|
$opt->setCellValue('E4', '个人不需预约团队需预约');
|
|
|
|
|
|
|
|
|
|
$opt->setCellValue('F1', '开放模式');
|
|
|
|
|
$opt->setCellValue('F2', '常态化全时开放');
|
|
|
|
|
$opt->setCellValue('F3', '常态化定时开放');
|
|
|
|
|
$opt->setCellValue('F4', '预约开放');
|
|
|
|
|
$fr = 2;
|
|
|
|
|
foreach (array_keys($this->openModeLabelToValueMap()) as $openLabel) {
|
|
|
|
|
$opt->setCellValue('F' . $fr, $openLabel);
|
|
|
|
|
$fr++;
|
|
|
|
|
}
|
|
|
|
|
$lastF = max(2, $fr - 1);
|
|
|
|
|
|
|
|
|
|
$opt->setCellValue('G1', '是否');
|
|
|
|
|
$opt->setCellValue('G2', '是');
|
|
|
|
|
@ -167,7 +188,7 @@ class VenueImportService
|
|
|
|
|
$this->applyListValidation($sheet, 'D2:D5000', '=选项!$D$2:$D$3'); // 预约类型
|
|
|
|
|
$this->applyListValidation($sheet, 'E2:E5000', '=选项!$C$2:$C$3'); // 门票类型
|
|
|
|
|
$this->applyListValidation($sheet, 'F2:F5000', '=选项!$E$2:$E$4'); // 预约模式
|
|
|
|
|
$this->applyListValidation($sheet, 'G2:G5000', '=选项!$F$2:$F$4'); // 开放模式
|
|
|
|
|
$this->applyListValidation($sheet, 'G2:G5000', '=选项!$F$2:$F$' . $lastF); // 开放模式(与字典 label 一致)
|
|
|
|
|
$this->applyListValidation($sheet, 'O2:O5000', '=选项!$G$2:$G$3'); // 启用
|
|
|
|
|
$this->applyListValidation($sheet, 'P2:P5000', '=选项!$G$2:$G$3'); // 纳入人数统计
|
|
|
|
|
|
|
|
|
|
@ -272,7 +293,8 @@ class VenueImportService
|
|
|
|
|
$ticketType = $rawTicket === '' ? null : (self::TICKET_TYPE_LABEL[$rawTicket] ?? null);
|
|
|
|
|
$appointmentType = $rawApp === '' ? null : (self::APPOINTMENT_LABEL[$rawApp] ?? null);
|
|
|
|
|
$bookingMode = $rawBookingMode === '' ? null : (self::BOOKING_MODE_LABEL[$rawBookingMode] ?? null);
|
|
|
|
|
$openMode = $rawOpen === '' ? null : (self::OPEN_MODE_LABEL[$rawOpen] ?? null);
|
|
|
|
|
$openModeMap = $this->openModeLabelToValueMap();
|
|
|
|
|
$openMode = $rawOpen === '' ? null : ($openModeMap[$rawOpen] ?? null);
|
|
|
|
|
|
|
|
|
|
$lng = $this->parseFloat($g(17));
|
|
|
|
|
$lat = $this->parseFloat($g(18));
|
|
|
|
|
@ -295,8 +317,9 @@ class VenueImportService
|
|
|
|
|
'consultation_hours' => $this->squishPlain($g(11) === '' ? null : $g(11)),
|
|
|
|
|
'contact_phone' => $this->squishPlain($g(12) === '' ? null : $g(12)),
|
|
|
|
|
'sort' => $sort,
|
|
|
|
|
'is_active' => $this->parseBool($g(14)),
|
|
|
|
|
'is_included_in_stats' => $this->parseBool($g(15)),
|
|
|
|
|
// 启用:留空按「是」与常见新建习惯;纳入人数统计:留空须为「否」,否则全表空单元格会全部被 parse 成 true
|
|
|
|
|
'is_active' => $this->parseYesNoWithEmptyDefault($g(14), true),
|
|
|
|
|
'is_included_in_stats' => $this->parseYesNoWithEmptyDefault($g(15), false),
|
|
|
|
|
'address' => $this->squishPlain($g(16) === '' ? null : $g(16)),
|
|
|
|
|
'lng' => $lng,
|
|
|
|
|
'lat' => $lat,
|
|
|
|
|
@ -339,7 +362,7 @@ class VenueImportService
|
|
|
|
|
$errors[] = '预约类型须为「仅团队」「个人团队均可」或留空';
|
|
|
|
|
}
|
|
|
|
|
if (($data['_raw_open_mode_label'] ?? '') !== '' && ($data['open_mode'] ?? null) === null) {
|
|
|
|
|
$errors[] = '开放模式须为三种选项之一或留空';
|
|
|
|
|
$errors[] = '开放模式须与数据字典「场馆开放模式」中的选项名称一致,或留空(可重新下载模板查看当前选项)';
|
|
|
|
|
}
|
|
|
|
|
if (($data['_raw_booking_mode_label'] ?? '') !== '' && ($data['booking_mode'] ?? null) === null) {
|
|
|
|
|
$errors[] = '预约模式须为三种选项之一或留空';
|
|
|
|
|
@ -485,16 +508,22 @@ class VenueImportService
|
|
|
|
|
return is_finite($n) ? $n : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function parseBool(string $s): bool
|
|
|
|
|
/**
|
|
|
|
|
* 解析「是/否」类单元格。空串或无法识别时由 $defaultWhenEmpty 决定,避免与「启用」不同列共用同一套默认导致误纳入统计。
|
|
|
|
|
*/
|
|
|
|
|
private function parseYesNoWithEmptyDefault(string $s, bool $defaultWhenEmpty): bool
|
|
|
|
|
{
|
|
|
|
|
$t = mb_strtolower(trim($s));
|
|
|
|
|
if ($t === '' || in_array($t, ['1', '是', 'y', 'yes', 'true', '启用'], true)) {
|
|
|
|
|
$t = mb_strtolower(trim($s), 'UTF-8');
|
|
|
|
|
if ($t === '') {
|
|
|
|
|
return $defaultWhenEmpty;
|
|
|
|
|
}
|
|
|
|
|
if (in_array($t, ['1', '是', 'y', 'yes', 'true', '启用'], true)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (in_array($t, ['0', '否', 'n', 'no', 'false', '禁用'], true)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
return $defaultWhenEmpty;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|