diff --git a/app/command/HesuGoogleAiInquiryImportCommand.php b/app/command/HesuGoogleAiInquiryImportCommand.php index 8a84d45..5efa086 100644 --- a/app/command/HesuGoogleAiInquiryImportCommand.php +++ b/app/command/HesuGoogleAiInquiryImportCommand.php @@ -3,6 +3,7 @@ namespace app\command; use app\common\model\CrmCustomer; +use app\common\utils\InquiryFairAssigner; use app\common\utils\email\InquiryDigestMail; use app\common\utils\email\MailSend; use app\crm\model\Country; @@ -324,10 +325,14 @@ class HesuGoogleAiInquiryImportCommand extends Command $output->writeln('未匹配到国家(将留空): ' . implode('、', array_keys($countryMiss)) . ''); } - // 轮询分配(按行序 i % n) - $n = count($targetUserIds); + // 公平分配:按近10天窗口内实际分到数升序(少者优先、最久未分到优先),再按序轮询 + $fair = InquiryFairAssigner::fairOrder($targetUserIds); + $orderedUsers = $fair['ordered']; + $output->writeln('分配顺序(近' . $fair['window_days'] . '天已有条数):' + . implode('、', array_map(fn($u) => $u . '(' . ($fair['counts'][$u] ?? 0) . ')', $orderedUsers)) . ''); + $n = count($orderedUsers); foreach ($keep as $i => &$row) { - $row['new_user_id'] = $targetUserIds[$i % $n]; + $row['new_user_id'] = $orderedUsers[$i % $n]; } unset($row); $groups = []; diff --git a/app/command/HesuGoogleInquiryImportCommand.php b/app/command/HesuGoogleInquiryImportCommand.php index c1b4350..bcfa61e 100644 --- a/app/command/HesuGoogleInquiryImportCommand.php +++ b/app/command/HesuGoogleInquiryImportCommand.php @@ -3,6 +3,7 @@ namespace app\command; use app\common\model\CrmCustomer; +use app\common\utils\InquiryFairAssigner; use app\common\utils\email\InquiryDigestMail; use app\common\utils\email\MailSend; use PhpOffice\PhpSpreadsheet\IOFactory; @@ -191,10 +192,14 @@ class HesuGoogleInquiryImportCommand extends Command $output->writeln(' 无联系方式 行' . $row['excel_row'] . ' ' . $row['contacts'] . ''); } - // 轮询分配(按行序 i % n) - $n = count($targetUserIds); + // 公平分配:按近10天窗口内实际分到数升序(少者优先、最久未分到优先),再按序轮询 + $fair = InquiryFairAssigner::fairOrder($targetUserIds); + $orderedUsers = $fair['ordered']; + $output->writeln('分配顺序(近' . $fair['window_days'] . '天已有条数):' + . implode('、', array_map(fn($u) => $u . '(' . ($fair['counts'][$u] ?? 0) . ')', $orderedUsers)) . ''); + $n = count($orderedUsers); foreach ($keep as $i => &$row) { - $row['new_user_id'] = $targetUserIds[$i % $n]; + $row['new_user_id'] = $orderedUsers[$i % $n]; } unset($row); $groups = []; diff --git a/app/command/HesuGoogleWhatsAppImportCommand.php b/app/command/HesuGoogleWhatsAppImportCommand.php index afdc2ad..f04bb1c 100644 --- a/app/command/HesuGoogleWhatsAppImportCommand.php +++ b/app/command/HesuGoogleWhatsAppImportCommand.php @@ -4,6 +4,7 @@ namespace app\command; use app\common\model\CrmCustomer; use app\common\service\OssService; +use app\common\utils\InquiryFairAssigner; use app\common\utils\email\InquiryDigestMail; use app\common\utils\email\MailSend; use PhpOffice\PhpSpreadsheet\IOFactory; @@ -219,10 +220,14 @@ class HesuGoogleWhatsAppImportCommand extends Command } $output->writeln('截图: 找到 ' . $shotFound . ' / 缺失 ' . $shotMissing . '(目录: ' . $imagesDir . ')'); - // 轮询分配(按行序 i % n) - $n = count($targetUserIds); + // 公平分配:按近10天窗口内实际分到数升序(少者优先、最久未分到优先),再按序轮询 + $fair = InquiryFairAssigner::fairOrder($targetUserIds); + $orderedUsers = $fair['ordered']; + $output->writeln('分配顺序(近' . $fair['window_days'] . '天已有条数):' + . implode('、', array_map(fn($u) => $u . '(' . ($fair['counts'][$u] ?? 0) . ')', $orderedUsers)) . ''); + $n = count($orderedUsers); foreach ($keep as $i => &$row) { - $row['new_user_id'] = $targetUserIds[$i % $n]; + $row['new_user_id'] = $orderedUsers[$i % $n]; } unset($row); $groups = []; diff --git a/app/common/utils/InquiryFairAssigner.php b/app/common/utils/InquiryFairAssigner.php new file mode 100644 index 0000000..46e9011 --- /dev/null +++ b/app/common/utils/InquiryFairAssigner.php @@ -0,0 +1,66 @@ +, 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]; + } +}