Files
fdesk/app/common/utils/InquiryFairAssigner.php
T
mzl92 6fa80336a6 feat: 询盘导入改为公平分配——按近10天窗口实际分到数排序(三脚本共用)
新增 InquiryFairAssigner:统计 crm_customer(合塑谷歌/导入/未删/近10天)
中名单内每人的已有条数,按 少者优先 -> 最久未分到优先 -> 名单顺序 排序,
三个导入命令分配前统一调用并打印排序结果(含各人窗口内条数)。
解决:日询盘量少于人数时固定只分给名单前几人的问题;
名单增删(离职/暂停回归)自适应,跨脚本/跨天共享,无额外状态。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-20 15:40:45 +08:00

67 lines
2.5 KiB
PHP

<?php
namespace app\common\utils;
use support\Db;
/**
* 询盘公平分配排序器(合塑谷歌三个导入脚本共用)
*
* 按"最近窗口期内实际分到的条数"升序排用户:分得少的优先、最久未分到的优先、
* 再按传入名单顺序。统计池:crm_customer 中 channel_id=4(合塑谷歌)、type=2(导入)、
* 未删除、窗口期内新建的记录。
*
* 场景自适应:
* - 名单增删(离职移除/新人加入):每次执行按当次名单统计,无残留状态;
* - 暂停一段时间再恢复:暂停期间窗口计数自然衰减,回归后适度优先拉平,
* 不会饿死也不会被灌顶。
*/
class InquiryFairAssigner
{
/** 默认窗口期(天) */
public const DEFAULT_WINDOW_DAYS = 10;
/**
* 计算公平分配顺序
*
* @param array $userIds 本次参与分配的 user_id 列表
* @param int $windowDays 统计窗口(天)
* @return array{ordered: array, counts: array<int,int>, window_days: int}
* ordered=排好序的 user_id 列表;counts=窗口内每人已有条数
*/
public static function fairOrder(array $userIds, int $windowDays = self::DEFAULT_WINDOW_DAYS): array
{
$since = time() - $windowDays * 86400;
$counts = []; $lastIds = [];
foreach (Db::table('crm_customer')
->where('channel_id', 4)
->where('type', 2)
->whereNull('deleted_at')
->whereIn('user_id', $userIds)
->where('created_at', '>=', $since)
->groupBy('user_id')
->selectRaw('user_id, COUNT(*) AS cnt, MAX(id) AS last_id')
->get() as $r) {
$uid = (int)$r->user_id;
$counts[$uid] = (int)$r->cnt;
$lastIds[$uid] = (int)$r->last_id;
}
$pos = array_flip(array_map('intval', $userIds));
$ordered = array_map('intval', $userIds);
usort($ordered, function ($a, $b) use ($counts, $lastIds, $pos) {
$ca = $counts[$a] ?? 0; $cb = $counts[$b] ?? 0;
if ($ca !== $cb) {
return $ca <=> $cb; // 窗口内分到少的优先
}
$la = $lastIds[$a] ?? 0; $lb = $lastIds[$b] ?? 0;
if ($la !== $lb) {
return $la <=> $lb; // 最久未分到的优先
}
return $pos[$a] <=> $pos[$b]; // 名单顺序(稳定)
});
return ['ordered' => $ordered, 'counts' => $counts, 'window_days' => $windowDays];
}
}