135 lines
8.8 KiB
PHP
135 lines
8.8 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* 复诊到期企微推送命令(RevisitDueNotify)纯函数单测,不连库、不读 config、不调企微。
|
||
* php server/tests/RevisitDueNotifyTest.php
|
||
* 覆盖:文案拼装(daily 汇总排序/截断/过号标签、self、supervisor)、每日上限与日切判断(selfSentToday / selfQuotaLeft)、
|
||
* 主管抄送去重判断(shouldCcSupervisor 阈值 + wecom_supervisor_at 日切、normalizeSupervisorIds 去重/剔除本人)、
|
||
* 工作时段判断、日切区间。
|
||
*/
|
||
|
||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||
|
||
use app\command\RevisitDueNotify as N;
|
||
use app\common\service\tcm\RevisitDueCalculator as C;
|
||
|
||
date_default_timezone_set('Asia/Shanghai');
|
||
|
||
$checks = 0;
|
||
function expect(bool $condition, string $message): void
|
||
{
|
||
global $checks;
|
||
if (!$condition) {
|
||
fwrite(STDERR, "FAIL: {$message}\n");
|
||
exit(1);
|
||
}
|
||
$checks++;
|
||
}
|
||
|
||
$ts = static fn (string $dt): int => (int) strtotime($dt);
|
||
|
||
// ---------- 日切判断 ----------
|
||
expect(N::isSameDay($ts('2026-10-14 09:00:00'), $ts('2026-10-14 23:59:59')), '同日:同一自然日');
|
||
expect(!N::isSameDay($ts('2026-10-13 23:59:59'), $ts('2026-10-14 00:00:00')), '同日:跨午夜不同日');
|
||
expect(!N::isSameDay(0, $ts('2026-10-14 09:00:00')), '同日:0 视为从未发生');
|
||
|
||
[$s, $e] = N::dayRange('2026-10-14');
|
||
expect(date('Y-m-d H:i:s', $s) === '2026-10-14 00:00:00' && date('Y-m-d H:i:s', $e) === '2026-10-14 23:59:59', 'dayRange 当日起止');
|
||
[$s2] = N::dayRange('bad');
|
||
expect(date('Y-m-d', $s2) === date('Y-m-d'), 'dayRange 非法日期回落今天');
|
||
|
||
// ---------- 每日上限 ----------
|
||
$now = $ts('2026-10-14 10:30:00');
|
||
$row = ['wecom_self_at' => 0, 'wecom_self_count_day' => 0];
|
||
expect(N::selfSentToday($row, $now) === 0 && N::selfQuotaLeft($row, $now, 2) === 2, '从未推送:剩余 = cap');
|
||
|
||
$row = ['wecom_self_at' => $ts('2026-10-14 09:10:00'), 'wecom_self_count_day' => 1];
|
||
expect(N::selfSentToday($row, $now) === 1 && N::selfQuotaLeft($row, $now, 2) === 1, '今日已推 1 次:剩余 1');
|
||
|
||
$row = ['wecom_self_at' => $ts('2026-10-14 09:10:00'), 'wecom_self_count_day' => 2];
|
||
expect(N::selfQuotaLeft($row, $now, 2) === 0, '今日已推 2 次:达上限');
|
||
|
||
$row = ['wecom_self_at' => $ts('2026-10-13 19:50:00'), 'wecom_self_count_day' => 2];
|
||
expect(N::selfSentToday($row, $now) === 0 && N::selfQuotaLeft($row, $now, 2) === 2, '昨日推满:日切重置为 cap');
|
||
|
||
$row = ['wecom_self_at' => $ts('2026-10-14 09:10:00'), 'wecom_self_count_day' => 5];
|
||
expect(N::selfQuotaLeft($row, $now, 2) === 0, '计数超过 cap 不为负');
|
||
expect(N::selfQuotaLeft(['wecom_self_at' => 0, 'wecom_self_count_day' => 0], $now, 0) === 0, 'cap=0 不推');
|
||
|
||
// ---------- 主管抄送判断 ----------
|
||
$row = ['snooze_count' => 5, 'wecom_supervisor_at' => 0];
|
||
expect(N::shouldCcSupervisor($row, $now, 5), '首次达到 5 次且未抄送 → 抄送');
|
||
$row = ['snooze_count' => 4, 'wecom_supervisor_at' => 0];
|
||
expect(!N::shouldCcSupervisor($row, $now, 5), '4 次不抄送');
|
||
$row = ['snooze_count' => 7, 'wecom_supervisor_at' => $ts('2026-10-14 09:00:00')];
|
||
expect(!N::shouldCcSupervisor($row, $now, 5), '今日已抄送 → 不重复');
|
||
$row = ['snooze_count' => 7, 'wecom_supervisor_at' => $ts('2026-10-13 09:00:00')];
|
||
expect(N::shouldCcSupervisor($row, $now, 5), '昨日抄送过 → 今日再抄送一次');
|
||
$row = ['snooze_count' => 3, 'wecom_supervisor_at' => 0];
|
||
expect(N::shouldCcSupervisor($row, $now, 3), '阈值可配置(3)');
|
||
expect(!N::shouldCcSupervisor(['snooze_count' => 0, 'wecom_supervisor_at' => 0], $now, 0), '阈值 0 按 1 处理,0 次不抄送');
|
||
|
||
expect(N::normalizeSupervisorIds([3, '3', 0, -1, 'x', 5, 7], 5) === [3, 7], '主管名单:去重、剔除非正整数、剔除医助本人');
|
||
expect(N::normalizeSupervisorIds('3, 7,,7 9', 0) === [3, 7, 9], '主管名单:逗号/空格字符串');
|
||
expect(N::normalizeSupervisorIds(null) === [] && N::normalizeSupervisorIds([]) === [] && N::normalizeSupervisorIds([5], 5) === [], '主管名单:空 / 仅本人 → 空');
|
||
|
||
// ---------- 超期标签 ----------
|
||
expect(N::overdueLabel(['state' => C::STATE_OVERDUE, 'overdue_days' => 3]) === '超期3天', '标签:超期 3 天');
|
||
expect(N::overdueLabel(['state' => C::STATE_OVERDUE, 'overdue_days' => 0]) === '今日应挂号', '标签:今日应挂号');
|
||
expect(N::overdueLabel(['state' => C::STATE_MISSED, 'overdue_days' => null]) === '过号', '标签:过号无到期日');
|
||
expect(N::overdueLabel(['state' => C::STATE_MISSED, 'overdue_days' => 2]) === '过号·超期2天', '标签:过号且超期');
|
||
expect(N::overdueLabel(['state' => C::STATE_OVERDUE, 'overdue_days' => null]) === '到期', '标签:缺天数兜底');
|
||
|
||
// ---------- daily 文案 ----------
|
||
$items = [
|
||
['patient_name' => '李四', 'state' => C::STATE_OVERDUE, 'overdue_days' => 1],
|
||
['patient_name' => '张三', 'state' => C::STATE_OVERDUE, 'overdue_days' => 3],
|
||
['patient_name' => '王五', 'state' => C::STATE_MISSED, 'overdue_days' => null],
|
||
];
|
||
$text = N::buildDailyText($items, 5);
|
||
expect(str_starts_with($text, '【复诊到期提醒】您有 3 位患者到期未挂号,最久超期 3 天:'), 'daily:头部含人数与最久超期');
|
||
expect(str_contains($text, '张三(超期3天)、李四(超期1天)、王五(过号)'), 'daily:按超期天数降序,过号排末');
|
||
expect(!str_contains($text, '等 3 人') && str_ends_with($text, N::SIGNATURE), 'daily:未超出列数不加「等 N 人」,带落款');
|
||
|
||
$many = [];
|
||
for ($i = 1; $i <= 7; $i++) {
|
||
$many[] = ['patient_name' => '患者' . $i, 'state' => C::STATE_OVERDUE, 'overdue_days' => $i];
|
||
}
|
||
$text = N::buildDailyText($many, 5);
|
||
expect(str_contains($text, '您有 7 位患者') && str_contains($text, '最久超期 7 天'), 'daily:7 人统计');
|
||
expect(str_contains($text, '患者7(超期7天)、患者6(超期6天)、患者5(超期5天)、患者4(超期4天)、患者3(超期3天)…等 7 人'), 'daily:最多列 5 人并加「等 7 人」');
|
||
expect(!str_contains($text, '患者2('), 'daily:第 6 人起不列出');
|
||
|
||
$text = N::buildDailyText([['patient_name' => '', 'state' => C::STATE_OVERDUE, 'overdue_days' => 0]], 5);
|
||
expect(str_contains($text, '您有 1 位患者到期未挂号:患者(今日应挂号)') && !str_contains($text, '最久超期'), 'daily:空姓名兜底「患者」,无超期不写「最久超期」');
|
||
expect(N::buildDailyText([], 5) === '', 'daily:空列表返回空串');
|
||
expect(str_contains(N::buildDailyText($many, 0), '…等 7 人'), 'daily:listMax<=0 按 1 处理');
|
||
|
||
// ---------- self 文案 ----------
|
||
$row = ['patient_name' => '张三', 'current_slot' => 2, 'due_date' => '2026-10-10', 'snooze_count' => 5, 'assistant_id' => 7];
|
||
$st = ['state' => C::STATE_OVERDUE, 'overdue_days' => 4];
|
||
$text = N::buildSelfText($row, $st);
|
||
expect($text === '【复诊到期提醒】患者 张三(第2诊后,预计复诊 10-10)超期4天未挂号,您已延迟 5 次,请尽快联系患者挂号。' . N::SIGNATURE, 'self 文案');
|
||
$text = N::buildSelfText(['patient_name' => '', 'current_slot' => 0, 'due_date' => null, 'snooze_count' => 6], ['state' => C::STATE_MISSED, 'overdue_days' => null]);
|
||
expect($text === '【复诊到期提醒】患者 患者(待复诊)过号未挂号,您已延迟 6 次,请尽快联系患者挂号。' . N::SIGNATURE, 'self 文案:缺姓名/诊次/到期日');
|
||
|
||
// ---------- supervisor 文案 ----------
|
||
$row = ['patient_name' => '张三', 'current_slot' => 2, 'due_date' => '2026-10-10', 'snooze_count' => 5, 'contact_count' => 1, 'assistant_id' => 7];
|
||
$text = N::buildSupervisorText('李医助', $row, $st);
|
||
expect($text === '【复诊到期升级】医助 李医助 的患者 张三 超期4天未挂号(第2诊后,预计复诊 10-10),已延迟 5 次、已联系 1 次,请关注。' . N::SIGNATURE, 'supervisor 文案:含医助、患者、超期天数、延迟/联系次数');
|
||
$text = N::buildSupervisorText('', ['patient_name' => '张三', 'current_slot' => 1, 'due_date' => '2026-10-10', 'snooze_count' => 5, 'contact_count' => 0, 'assistant_id' => 7], $st);
|
||
expect(str_contains($text, '医助 #7 的患者') && !str_contains($text, '已联系'), 'supervisor 文案:医助缺名用 #id,无联系次数不写');
|
||
|
||
// ---------- 工作时段 ----------
|
||
$wh = ['09:00', '20:00'];
|
||
expect(N::withinWorkHours($ts('2026-10-14 09:00:00'), $wh), '工作时段:09:00 含起点');
|
||
expect(N::withinWorkHours($ts('2026-10-14 19:59:59'), $wh), '工作时段:19:59 在内');
|
||
expect(!N::withinWorkHours($ts('2026-10-14 20:00:00'), $wh), '工作时段:20:00 不含终点');
|
||
expect(!N::withinWorkHours($ts('2026-10-14 08:59:59'), $wh), '工作时段:08:59 在外');
|
||
expect(N::withinWorkHours($ts('2026-10-14 03:00:00'), ['00:00', '24:00']), '工作时段:全天配置');
|
||
expect(!N::withinWorkHours($ts('2026-10-14 21:00:00'), 'bad'), '工作时段:非法配置回落 09:00~20:00');
|
||
|
||
echo "RevisitDueNotifyTest OK ({$checks} checks)\n";
|