This commit is contained in:
gr
2026-10-15 11:03:54 +08:00
parent 70ddc1a70a
commit bb28d93cec
64 changed files with 12747 additions and 420 deletions
+131
View File
@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
/**
* 复诊到期弹窗逻辑(RevisitDueLogic)纯函数单测,不连库、不读 config。
* php server/tests/RevisitDueLogicTest.php
* 覆盖:延迟间隔阶梯(4h→2h→1h→30min、default 回落、非法配置)、工作时段解析、
* 钳制(早于开始 → 当日开始;晚于结束 → 次日开始;边界 20:00:00 / 20:00:01)、
* nextRemindAfterSnooze 组合、承诺日 09:00、承诺日范围、提醒文案、升级等级、弹窗口径。
*/
require dirname(__DIR__) . '/vendor/autoload.php';
use app\adminapi\logic\tcm\RevisitDueLogic as L;
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);
$fmt = static fn (int $t): string => date('Y-m-d H:i:s', $t);
$cfg = [
'snooze_intervals' => [1 => 14400, 2 => 7200, 3 => 3600, 'default' => 1800],
'work_hours' => ['09:00', '20:00'],
'promise_max_days' => 14,
'contact_max_per_cycle' => 2,
];
// ---------- snoozeInterval ----------
expect(L::snoozeInterval(1, $cfg['snooze_intervals']) === 14400, '第 1 次延迟 4h');
expect(L::snoozeInterval(2, $cfg['snooze_intervals']) === 7200, '第 2 次延迟 2h');
expect(L::snoozeInterval(3, $cfg['snooze_intervals']) === 3600, '第 3 次延迟 1h');
expect(L::snoozeInterval(4, $cfg['snooze_intervals']) === 1800, '第 4 次起回落 default 30min');
expect(L::snoozeInterval(99, $cfg['snooze_intervals']) === 1800, '第 99 次仍 30min');
expect(L::snoozeInterval(1, ['1' => '7200', 'default' => '600']) === 7200, '字符串配置可被归一');
expect(L::snoozeInterval(5, [1 => 100]) === L::DEFAULT_SNOOZE_SECONDS, '缺 default 时回落常量 1800');
expect(L::snoozeInterval(1, null) === L::DEFAULT_SNOOZE_SECONDS, '非数组配置回落 1800');
expect(L::snoozeInterval(1, [1 => 0, 'default' => -5]) === L::DEFAULT_SNOOZE_SECONDS, '非正数回落 1800');
expect(L::snoozeInterval(0, $cfg['snooze_intervals']) === 1800, 'n=0 取 default');
// ---------- parseWorkHours ----------
expect(L::parseWorkHours(['09:00', '20:00']) === [540, 1200], '09:00~20:00 → 540~1200 分钟');
expect(L::parseWorkHours(['08:30', '18:00']) === [510, 1080], '08:30~18:00');
expect(L::parseWorkHours(['09:00', '24:00']) === [540, 1440], '结束允许 24:00');
expect(L::parseWorkHours(['20:00', '09:00']) === [540, 1200], '开始不早于结束 → 默认');
expect(L::parseWorkHours(['9', '20']) === [540, 1200], '非 HH:mm → 默认');
expect(L::parseWorkHours(null) === [540, 1200], 'null → 默认');
expect(L::parseWorkHours(['09:00']) === [540, 1200], '少于两项 → 默认');
// ---------- clampToWorkHours ----------
$wh = $cfg['work_hours'];
expect(L::clampToWorkHours($ts('2026-10-13 14:00:00'), $wh) === $ts('2026-10-13 14:00:00'), '工作时段内原样返回');
expect(L::clampToWorkHours($ts('2026-10-13 20:30:00'), $wh) === $ts('2026-10-14 09:00:00'), '20:30 → 次日 09:00');
expect(L::clampToWorkHours($ts('2026-10-13 20:00:00'), $wh) === $ts('2026-10-13 20:00:00'), '恰好 20:00:00 不顺延');
expect(L::clampToWorkHours($ts('2026-10-13 20:00:01'), $wh) === $ts('2026-10-14 09:00:00'), '20:00:01 顺延次日 09:00');
expect(L::clampToWorkHours($ts('2026-10-13 03:00:00'), $wh) === $ts('2026-10-13 09:00:00'), '凌晨 → 当日 09:00');
expect(L::clampToWorkHours($ts('2026-10-13 08:59:59'), $wh) === $ts('2026-10-13 09:00:00'), '08:59:59 → 09:00:00');
expect(L::clampToWorkHours($ts('2026-10-13 09:00:00'), $wh) === $ts('2026-10-13 09:00:00'), '恰好 09:00 原样');
expect(L::clampToWorkHours($ts('2026-10-31 23:30:00'), $wh) === $ts('2026-11-01 09:00:00'), '跨月顺延');
expect(L::clampToWorkHours($ts('2026-12-31 21:00:00'), $wh) === $ts('2027-01-01 09:00:00'), '跨年顺延');
expect(L::clampToWorkHours($ts('2026-10-13 18:30:00'), ['08:30', '18:00']) === $ts('2026-10-14 08:30:00'), '自定义时段 18:30 → 次日 08:30');
expect(L::clampToWorkHours($ts('2026-10-13 23:59:59'), ['09:00', '24:00']) === $ts('2026-10-13 23:59:59'), '24:00 结束时深夜不顺延');
// ---------- nextRemindAfterSnooze ----------
expect($fmt(L::nextRemindAfterSnooze($ts('2026-10-13 09:30:00'), 1, $cfg)) === '2026-10-13 13:30:00', '09:30 第 1 次 → 13:30');
expect($fmt(L::nextRemindAfterSnooze($ts('2026-10-13 17:30:00'), 1, $cfg)) === '2026-10-14 09:00:00', '17:30 + 4h 越过 20:00 → 次日 09:00');
expect($fmt(L::nextRemindAfterSnooze($ts('2026-10-13 18:30:00'), 3, $cfg)) === '2026-10-13 19:30:00', '18:30 第 3 次 +1h → 19:30');
expect($fmt(L::nextRemindAfterSnooze($ts('2026-10-13 19:50:00'), 4, $cfg)) === '2026-10-14 09:00:00', '19:50 第 4 次 +30min → 次日 09:00');
expect($fmt(L::nextRemindAfterSnooze($ts('2026-10-13 20:30:00'), 1, $cfg)) === '2026-10-14 09:00:00', '20:30 点延迟 → 次日 09:00(验收 §11-4-2)');
expect($fmt(L::nextRemindAfterSnooze($ts('2026-10-13 19:00:00'), 5, $cfg)) === '2026-10-13 19:30:00', '第 5 次 30min 仍在时段内');
expect(L::nextRemindAfterSnooze($ts('2026-10-13 10:00:00'), 1, []) === $ts('2026-10-13 10:30:00'), '空配置:30min + 默认时段');
expect(L::nextRemindAfterSnooze($ts('2026-10-13 10:00:00'), 1, $cfg) > $ts('2026-10-13 10:00:00'), '下次提醒恒晚于 now(条件 UPDATE 幂等前提)');
// ---------- promisedRemindAt ----------
expect($fmt(L::promisedRemindAt('2026-10-16', $wh)) === '2026-10-16 09:00:00', '承诺日 09:00');
expect($fmt(L::promisedRemindAt('2026-10-16', ['08:30', '18:00'])) === '2026-10-16 08:30:00', '承诺日取时段开始');
expect(L::promisedRemindAt('2026-13-40', $wh) === 0, '非法日期 → 0');
expect(L::promisedRemindAt('', $wh) === 0, '空 → 0');
expect($fmt(L::promisedRemindAt('2026-10-16 12:00:00', $wh)) === '2026-10-16 09:00:00', '带时间的串只取日期');
// ---------- isPromisedDateInRange ----------
expect(L::isPromisedDateInRange('2026-10-13', '2026-10-13', 14), '今天可选');
expect(L::isPromisedDateInRange('2026-10-27', '2026-10-13', 14), '今天 +14 可选');
expect(!L::isPromisedDateInRange('2026-10-28', '2026-10-13', 14), '今天 +15 不可选');
expect(!L::isPromisedDateInRange('2026-10-12', '2026-10-13', 14), '昨天不可选');
expect(!L::isPromisedDateInRange('bad', '2026-10-13', 14), '非法日期不可选');
expect(!L::isPromisedDateInRange('2026-02-30', '2026-02-13', 30), '不存在的日期不可选');
expect(L::isPromisedDateInRange('2026-10-13', '2026-10-13', 0), 'max=0 仅今天');
// ---------- remindTimeText ----------
$now = $ts('2026-10-13 10:00:00');
expect(L::remindTimeText($ts('2026-10-13 14:30:00'), $now) === '14:30', '当日 HH:mm');
expect(L::remindTimeText($ts('2026-10-14 09:00:00'), $now) === '明日 09:00', '次日前缀');
expect(L::remindTimeText($ts('2026-10-16 09:00:00'), $now) === '10-16 09:00', '更远 MM-DD HH:mm');
expect(L::remindTimeText(0, $now) === '', '0 → 空');
// ---------- escalationLevel(与 Calculator 一致) ----------
expect(C::escalationLevel(1) === 1 && C::escalationLevel(2) === 1, '1~2 次 → level 1');
expect(C::escalationLevel(3) === 2 && C::escalationLevel(4) === 2, '3~4 次 → level 2');
expect(C::escalationLevel(5) === 3, '第 5 次 → level 3 居中模态');
// ---------- toastEligible ----------
$row = static fn (array $o = []): array => array_replace([
'closed_state' => 0, 'appointment_state' => 0, 'due_date' => '2026-10-10', 'promised_date' => null,
], $o);
expect(L::toastEligible($row(), '2026-10-13'), '到期未挂号进弹窗');
expect(L::toastEligible($row(['due_date' => '2026-10-13']), '2026-10-13'), '今日到期进弹窗');
expect(!L::toastEligible($row(['due_date' => '2026-10-14']), '2026-10-13'), '未到期不进');
expect(L::toastEligible($row(['appointment_state' => 4, 'due_date' => '2026-10-20']), '2026-10-13'), '过号不受到期日限制');
expect(L::toastEligible($row(['appointment_state' => 4, 'due_date' => null]), '2026-10-13'), '过号且缺天数仍进');
expect(!L::toastEligible($row(['due_date' => null]), '2026-10-13'), '缺天数不进');
expect(!L::toastEligible($row(['promised_date' => '2026-10-13']), '2026-10-13'), '承诺日=今天不进');
expect(!L::toastEligible($row(['promised_date' => '2026-10-16']), '2026-10-13'), '承诺日未到不进');
expect(L::toastEligible($row(['promised_date' => '2026-10-12']), '2026-10-13'), '承诺日已过再弹');
expect(!L::toastEligible($row(['closed_state' => 4]), '2026-10-13'), '人工关闭不进');
expect(!L::toastEligible($row(['closed_state' => 2]), '2026-10-13'), '已挂号不进');
expect(L::toastEligible($row(['promised_date' => '0000-00-00']), '2026-10-13'), '0000-00-00 视为无承诺');
echo "RevisitDueLogicTest OK ({$checks} checks)\n";