[1], 'start' => '22:00', 'end' => '02:00', 'member_admin_ids' => [1]]; $monday = strtotime('2026-08-31 22:00:00 Asia/Shanghai'); $tuesday = strtotime('2026-09-01 01:59:00 Asia/Shanghai'); $end = strtotime('2026-09-01 02:00:00 Asia/Shanghai'); promotionCheck(QywxPromotionConfig::matches($slot, $monday), '开始边界应包含'); promotionCheck(QywxPromotionConfig::matches($slot, $tuesday), '跨日时段应按开始星期匹配'); promotionCheck(!QywxPromotionConfig::matches($slot, $end), '结束边界应排除'); promotionCheck(!QywxPromotionConfig::matches($slot, strtotime('2026-08-31 01:00:00 Asia/Shanghai')), '不能把周一凌晨算入周一晚班'); $config = QywxPromotionConfig::normalize([ 'reception_mode' => 'scheduled', 'reception_schedule' => [$slot], 'backup_member_admin_ids' => [2], 'welcome_mode' => 'channel', 'welcome' => ['text' => '你好 {customer_name}'], ]); $config['backup_userids'] = ['backup']; $config['reception_schedule'][0]['member_userids'] = ['main']; $members = [ ['userid' => 'main', 'enabled' => 1, 'today_date' => '2026-08-31', 'today_count' => 0, 'daily_limit' => 1], ['userid' => 'backup', 'enabled' => 1], ]; $range = QywxPromotionMemberRange::evaluate($members, '2026-08-31', $monday, $config); promotionCheck($range['userids'] === ['main'], '主接待在线时不能分给备用员工'); $range = QywxPromotionMemberRange::evaluate($members, '2026-09-01', $end, $config); promotionCheck($range['userids'] === ['backup'] && $range['using_backup'], '下班后切换备用员工'); $members[0]['today_count'] = 1; $range = QywxPromotionMemberRange::evaluate($members, '2026-08-31', $monday, $config); promotionCheck($range['userids'] === ['backup'], '达到上限后切换备用员工'); $range = QywxPromotionMemberRange::evaluate($members, '2026-09-01', $tuesday, $config); promotionCheck($range['userids'] === ['main'], '跨日清零上限后主接待应恢复'); $members[1]['enabled'] = 0; $range = QywxPromotionMemberRange::evaluate($members, '2026-08-31', $monday, $config); promotionCheck($range['userids'] === [] && !$range['using_backup'], '备用停用后不能偷偷恢复超额主成员'); $sunday = ['weekdays' => [7], 'start' => '22:00', 'end' => '02:00']; promotionCheck(QywxPromotionConfig::matches($sunday, strtotime('2026-08-31 01:00:00 Asia/Shanghai')), '跨周午夜规则应回到周日'); foreach ([ ['reception_mode' => 'scheduled', 'reception_schedule' => [$slot]], ['reception_schedule' => [array_replace($slot, ['start' => '25:00'])]], ['reception_schedule' => [array_replace($slot, ['weekdays' => [8]])]], ['tags_enabled' => true], ['tags_enabled' => true, 'tag_ids' => ['tag_1', 'tag_2']], ['tags_enabled' => false, 'tag_ids' => ['tag_1', 'tag_2']], ['tag_ids' => ['tag_1', 'tag_1']], ['welcome_mode' => 'channel'], ['welcome_mode' => 'invalid'], ['welcome' => ['text' => str_repeat('😀', 1001)]], ['welcome_schedule' => [ $slot + ['text' => '晚班'], ['weekdays' => [2], 'start' => '01:00', 'end' => '03:00', 'text' => '冲突'], ]], ] as $invalid) { $failed = false; try { QywxPromotionConfig::normalize($invalid); } catch (RuntimeException) { $failed = true; } promotionCheck($failed, '非法配置必须被拒绝:' . json_encode($invalid)); } $rendered = QywxPromotionConfig::render('{customer_name}-{employee_name}-{add_time}', '小王', '李医生', $monday, 20); promotionCheck($rendered === '小王-李医生-2026-08-31', '模板变量和中国时区'); promotionCheck(mb_strlen(QywxPromotionConfig::render('{customer_name}', str_repeat('王', 30), '', $monday, 20)) === 20, '备注不能超过企微长度限制'); $singleTag = QywxPromotionConfig::normalize(['tags_enabled' => true, 'tag_ids' => [' tag_1 ']]); promotionCheck($singleTag['tag_ids'] === ['tag_1'], '单选仍保留tag_ids数组契约'); promotionCheck(QywxPromotionConfig::normalize(['tags_enabled' => false, 'tag_ids' => []])['tag_ids'] === [], '关闭标签允许空选择'); $legacyTags = QywxPromotionConfig::decode(['tags_enabled' => true, 'tag_ids' => ['tag_1', 'tag_2']]); promotionCheck($legacyTags['tag_ids'] === ['tag_1', 'tag_2'], '读取旧方案不得静默截掉多选,保存时明确拒绝'); echo "QYWX_PROMOTION_AUTOMATION_CONFIG_OK\n";