Files
links/patches/application/common/service/SplitTicketRuleService.php
T

196 lines
6.2 KiB
PHP
Raw Normal View History

2026-06-09 03:36:30 +08:00
<?php
declare(strict_types=1);
namespace app\common\service;
use app\admin\model\split\Number;
use app\admin\model\split\Ticket;
/**
* 工单与号码业务规则(单号上限、下号比率、时间窗口、完成量自动开关)
*/
class SplitTicketRuleService
{
/**
* 同步后应用全部规则并写回工单/号码
*/
public function applyAfterSync(Ticket $ticket, int $completeCount): void
{
$this->applyTicketStatusRules($ticket, $completeCount);
$fresh = Ticket::get((int) $ticket['id']);
if ($fresh) {
if ((string) $fresh['status'] === 'hidden') {
$this->cascadeTicketClosedToNumbers($fresh);
}
$this->applyNumberRules($fresh);
}
}
/**
* 同步流程:工单因时间/完成量等原因关闭时,联动关闭非手动号码
*/
public function cascadeTicketClosedToNumbers(Ticket $ticket): void
{
if ((string) ($ticket['status'] ?? 'hidden') !== 'hidden') {
return;
}
Number::where('admin_id', (int) $ticket['admin_id'])
->where('split_link_id', (int) $ticket['split_link_id'])
->where('ticket_name', (string) $ticket['ticket_name'])
->where('manual_manage', 0)
->update([
'status' => 'hidden',
'updatetime' => time(),
]);
}
/**
* 手动切换工单状态时,联动非手动管理的号码
*/
public function syncNumbersWithTicketStatus(Ticket $ticket): void
{
$ticketStatus = (string) ($ticket['status'] ?? 'hidden');
if ($ticketStatus === 'hidden') {
$this->cascadeTicketClosedToNumbers($ticket);
return;
}
$this->applyNumberRules($ticket);
}
/**
* 工单处于开启状态时,将云控在线的非手动号码设为开启
*
* @deprecated 请使用 applyNumberRules(会校验单号上限/下号比率)
*/
public function syncOnlineNumbersWhenTicketOpen(Ticket $ticket): void
{
$this->applyNumberRules($ticket);
}
/**
* 单号上限、下号比率、云控在线状态、工单开关综合决定号码状态
*/
public function applyNumberRules(Ticket $ticket): void
{
$orderLimit = (int) ($ticket['order_limit'] ?? 0);
$assignRatio = (int) ($ticket['assign_ratio'] ?? 0);
$numbers = Number::where('admin_id', (int) $ticket['admin_id'])
->where('split_link_id', (int) $ticket['split_link_id'])
->where('ticket_name', (string) $ticket['ticket_name'])
->select();
foreach ($numbers as $number) {
$visitCount = (int) $number['visit_count'];
$inboundCount = (int) $number['inbound_count'];
$lastVisit = (int) ($number['last_sync_visit_count'] ?? 0);
$lastInbound = (int) ($number['last_sync_inbound_count'] ?? 0);
$streak = (int) ($number['no_inbound_click_streak'] ?? 0);
if ($visitCount > $lastVisit && $inboundCount <= $lastInbound) {
$streak += ($visitCount - $lastVisit);
} elseif ($inboundCount > $lastInbound) {
$streak = 0;
}
$update = [
'no_inbound_click_streak' => $streak,
'last_sync_visit_count' => $visitCount,
'last_sync_inbound_count' => $inboundCount,
'updatetime' => time(),
];
// 手动管理(含用户手动关闭)的号码:仅更新统计字段,不改状态
if ((int) $number['manual_manage'] === 1) {
Number::where('id', (int) $number['id'])->update($update);
continue;
}
$update['status'] = $this->resolveAutomatedStatus(
$ticket,
(string) ($number['platform_status'] ?? 'unknown'),
$inboundCount,
$streak,
$orderLimit,
$assignRatio
);
Number::where('id', (int) $number['id'])->update($update);
}
}
/**
* 非手动管理号码的自动开关判定
*/
private function resolveAutomatedStatus(
Ticket $ticket,
string $platformStatus,
int $inboundCount,
int $streak,
int $orderLimit,
int $assignRatio
): string {
if ((string) ($ticket['status'] ?? 'hidden') !== 'normal') {
return 'hidden';
}
if ($platformStatus !== 'online') {
return 'hidden';
}
if ($orderLimit > 0 && $inboundCount >= $orderLimit) {
return 'hidden';
}
if ($assignRatio > 0 && $streak >= $assignRatio) {
return 'hidden';
}
return 'normal';
}
/**
* 完成量、时间窗口决定工单开关(定时与手动同步均适用)
*/
public function applyTicketStatusRules(Ticket $ticket, int $completeCount): void
{
$status = $this->resolveTicketStatus($ticket, $completeCount);
if ($status !== (string) ($ticket['status'] ?? 'hidden')) {
Ticket::where('id', (int) $ticket['id'])->update([
'status' => $status,
'updatetime' => time(),
]);
$ticket['status'] = $status;
}
}
/**
* 是否处于允许同步/开启的时间窗口
*/
public function isWithinTimeWindow(Ticket $ticket, ?int $now = null): bool
{
$now = $now ?? time();
$start = $ticket['start_time'] ?? null;
$end = $ticket['end_time'] ?? null;
if ($start !== null && $start !== '' && (int) $start > 0 && $now < (int) $start) {
return false;
}
if ($end !== null && $end !== '' && (int) $end > 0 && $now > (int) $end) {
return false;
}
return true;
}
private function resolveTicketStatus(Ticket $ticket, int $completeCount): string
{
if (!$this->isWithinTimeWindow($ticket)) {
return 'hidden';
}
$ticketTotal = (int) ($ticket['ticket_total'] ?? 0);
if ($ticketTotal > 0) {
return $completeCount >= $ticketTotal ? 'hidden' : 'normal';
}
return 'normal';
}
}