297 lines
8.2 KiB
PHP
297 lines
8.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\admin\model\split;
|
|
|
|
use think\Db;
|
|
use think\Model;
|
|
|
|
/**
|
|
* 分流工单模型
|
|
*/
|
|
class Ticket extends Model
|
|
{
|
|
protected $name = 'split_ticket';
|
|
|
|
protected $autoWriteTimestamp = 'integer';
|
|
|
|
protected $createTime = 'createtime';
|
|
protected $updateTime = 'updatetime';
|
|
protected $deleteTime = false;
|
|
|
|
protected $append = [
|
|
'ticket_type_text',
|
|
'number_type_text',
|
|
'link_code_text',
|
|
'start_time_text',
|
|
'end_time_text',
|
|
'status_text',
|
|
'click_count',
|
|
'ticket_progress_text',
|
|
'inbound_ratio_text',
|
|
'sync_display_text',
|
|
];
|
|
|
|
/**
|
|
* 工单类型
|
|
*
|
|
* @return array<string, string>
|
|
*/
|
|
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<string, string>
|
|
*/
|
|
public function getStatusList(): array
|
|
{
|
|
return [
|
|
'normal' => __('Status normal'),
|
|
'hidden' => __('Status hidden'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public function getSyncStatusList(): array
|
|
{
|
|
return [
|
|
'success' => __('Sync status success'),
|
|
'error' => __('Sync status error'),
|
|
'pending' => __('Sync status pending'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 号码类型
|
|
*
|
|
* @return array<string, string>
|
|
*/
|
|
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<string, mixed> $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<string, mixed> $data
|
|
*/
|
|
public function getTicketProgressTextAttr($value, $data): string
|
|
{
|
|
$total = (int) ($data['ticket_total'] ?? 0);
|
|
$complete = (int) ($data['complete_count'] ?? 0);
|
|
if ($total <= 0) {
|
|
return '—';
|
|
}
|
|
$percent = round($complete / $total * 100, 2);
|
|
return $percent . '%';
|
|
}
|
|
|
|
/**
|
|
* 进线比例:进线人数 / 点击数(点击数=关联号码 visit_count 之和)
|
|
*
|
|
* @param mixed $value
|
|
* @param array<string, mixed> $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<string, mixed> $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);
|
|
}
|
|
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<string, mixed> $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) : '';
|
|
}
|
|
}
|