132 lines
8.7 KiB
PHP
132 lines
8.7 KiB
PHP
<?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";
|