*/ public function getTicketTypeList(): array { return [ 'xinghe' => __('Ticket type xinghe'), 'haiwang' => __('Ticket type haiwang'), 'taiji' => __('Ticket type taiji'), 'huojian' => __('Ticket type huojian'), 'ss_channel' => __('Ticket type ss_channel'), 'ss_customer' => __('Ticket type ss_customer'), 'yifafa' => __('Ticket type yifafa'), 'a2c' => __('Ticket type a2c'), 'ceo_scrm' => __('Ticket type ceo_scrm'), 'whatshub' => __('Ticket type whatshub'), 'sihai' => __('Ticket type sihai'), ]; } /** * @return array */ public function getStatusList(): array { return [ 'normal' => __('Status normal'), 'hidden' => __('Status hidden'), ]; } /** * @return array */ public function getSyncStatusList(): array { return [ 'success' => __('Sync status success'), 'error' => __('Sync status error'), 'pending' => __('Sync status pending'), ]; } /** * 号码类型 * * @return array */ public function getNumberTypeList(): array { return [ 'whatsapp' => 'Whatsapp', 'telegram' => 'Telegram', 'line' => 'Line', 'custom' => __('Number type custom'), ]; } /** * 关联分流链接 */ public function splitLink() { // IN 预载入:避免 eagerlyType=0(JOIN) 与列表 field 子查询冲突导致 SQL 1064 return $this->belongsTo(Link::class, 'split_link_id', 'id', [], 'LEFT')->setEagerlyType(1); } /** * 列表子查询注入的点击数,无则按关联号码汇总 * * @param mixed $value * @param array $data */ public function getClickCountAttr($value, $data): int { if (isset($data['click_count']) && $data['click_count'] !== '' && $data['click_count'] !== null) { return (int) $data['click_count']; } return self::sumVisitCountForTicket($data); } /** * 工单进度:完成数量 / 工单总量 * * @param mixed $value * @param array $data */ public function getTicketProgressTextAttr($value, $data): string { $total = (int) ($data['ticket_total'] ?? 0); $complete = (int) ($data['complete_count'] ?? 0); if ($total <= 0) { return '0%'; } $percent = round($complete / $total * 100, 2); return $percent . '%'; } /** * 进线比例:进线人数 / 点击数(点击数=关联号码 visit_count 之和) * * @param mixed $value * @param array $data */ public function getInboundRatioTextAttr($value, $data): string { $clicks = $this->getClickCountAttr(null, $data); $inbound = (int) ($data['inbound_count'] ?? 0); if ($clicks <= 0) { return '—'; } $percent = round($inbound / $clicks * 100, 2); return $percent . '%'; } /** * 同步状态展示文案 * * @param mixed $value * @param array $data */ public function getSyncDisplayTextAttr($value, $data): string { $status = (string) ($data['sync_status'] ?? 'pending'); $online = (int) ($data['online_count'] ?? 0); if ($status === 'success') { return sprintf((string) __('Sync display success'), $online); } if ($status === 'pending') { return (string) __('Sync display pending'); } return (string) __('Sync display error'); } public function setStartTimeAttr($value): ?int { return self::parseTimeValue($value); } public function setEndTimeAttr($value): ?int { return self::parseTimeValue($value); } public function setNumberTypeCustomAttr($value): string { return trim((string) $value); } public function setTicketTotalAttr($value): int { return max(0, (int) $value); } public function getTicketTypeTextAttr($value, $data): string { $key = (string) ($data['ticket_type'] ?? ''); $list = $this->getTicketTypeList(); return $list[$key] ?? $key; } public function getNumberTypeTextAttr($value, $data): string { $type = (string) ($data['number_type'] ?? ''); if ($type === 'custom') { $custom = trim((string) ($data['number_type_custom'] ?? '')); return $custom !== '' ? $custom : (string) __('Number type custom'); } $list = $this->getNumberTypeList(); return $list[$type] ?? $type; } public function getLinkCodeTextAttr($value, $data): string { if ($value !== '' && $value !== null) { return (string) $value; } $linkId = (int) ($data['split_link_id'] ?? 0); if ($linkId <= 0) { return ''; } $code = Link::where('id', $linkId)->value('link_code'); return (string) $code; } public function getStartTimeTextAttr($value, $data): string { return self::formatTimeText($data['start_time'] ?? null); } public function getEndTimeTextAttr($value, $data): string { return self::formatTimeText($data['end_time'] ?? null); } public function getStatusTextAttr($value, $data): string { $key = (string) ($data['status'] ?? ''); $list = $this->getStatusList(); return $list[$key] ?? $key; } /** * 构建列表用 click_count 子查询 SQL 片段 */ public static function buildClickCountSubQuery(string $ticketTableAlias = ''): string { $ticketTable = (new self())->getTable(); $numberTable = (new Number())->getTable(); $t = $ticketTableAlias !== '' ? $ticketTableAlias : $ticketTable; return "(SELECT COALESCE(SUM(n.visit_count), 0) FROM `{$numberTable}` n " . "WHERE n.ticket_name = `{$t}`.ticket_name " . "AND n.split_link_id = `{$t}`.split_link_id " . "AND n.admin_id = `{$t}`.admin_id) AS click_count"; } /** * @param array $data */ private static function sumVisitCountForTicket(array $data): int { $ticketName = (string) ($data['ticket_name'] ?? ''); $linkId = (int) ($data['split_link_id'] ?? 0); $adminId = (int) ($data['admin_id'] ?? 0); if ($ticketName === '' || $linkId <= 0) { return 0; } $sum = Db::name('split_number') ->where('ticket_name', $ticketName) ->where('split_link_id', $linkId) ->where('admin_id', $adminId) ->sum('visit_count'); return (int) $sum; } /** * @param mixed $value */ private static function parseTimeValue($value): ?int { if ($value === '' || $value === null) { return null; } if (is_numeric($value)) { return (int) $value; } $ts = strtotime((string) $value); return $ts === false ? null : $ts; } /** * @param mixed $value */ private static function formatTimeText($value): string { if ($value === '' || $value === null || !is_numeric($value)) { return ''; } $ts = (int) $value; return $ts > 0 ? date('Y-m-d H:i:s', $ts) : ''; } }