PDO::ERRMODE_EXCEPTION]); $pdo->exec("CREATE DATABASE `{$database}` CHARACTER SET utf8mb4"); $pdo->exec("USE `{$database}`"); new think\App(); $manager = new think\DbManager(); $manager->setConfig(['default' => 'mysql', 'connections' => ['mysql' => [ 'type' => 'mysql', 'hostname' => '127.0.0.1', 'hostport' => $port, 'database' => $database, 'username' => 'root', 'password' => '', 'charset' => 'utf8mb4', 'prefix' => 'zyt_', 'fields_strict' => true, 'trigger_sql' => false, ]]]); Container::getInstance()->instance('think\DbManager', $manager); Container::getInstance()->instance('config', new think\Config()); $checks = 0; $expect = static function (bool $condition, string $message) use (&$checks): void { if (!$condition) { throw new RuntimeException('FAIL: ' . $message); } $checks++; }; try { $pdo->exec('CREATE TABLE zyt_dept(id INT PRIMARY KEY,pid INT,name VARCHAR(50),sort INT DEFAULT 0,delete_time INT NULL)'); $pdo->exec("INSERT INTO zyt_dept(id,pid,name) VALUES(10,0,'一中心'),(11,10,'一组'),(20,0,'二中心'),(21,20,'二中心甲组'),(22,20,'乙组')"); $pdo->exec('CREATE TABLE zyt_admin(id INT PRIMARY KEY,name VARCHAR(50),delete_time INT NULL)'); $pdo->exec("INSERT INTO zyt_admin VALUES(1,'医助甲',NULL),(2,'医助乙',NULL),(3,'一中心员工',NULL),(4,'已删除',1)"); $pdo->exec('CREATE TABLE zyt_admin_dept(admin_id INT,dept_id INT,PRIMARY KEY(admin_id,dept_id))'); $pdo->exec('INSERT INTO zyt_admin_dept VALUES(1,21),(2,22),(3,11),(4,21)'); $pdo->exec('CREATE TABLE zyt_tcm_diagnosis(id INT PRIMARY KEY,patient_name VARCHAR(50),phone VARCHAR(20),revisit_slot_start_offset INT DEFAULT 0,delete_time INT NULL)'); for ($id = 10; $id <= 17; $id++) { $pdo->prepare('INSERT INTO zyt_tcm_diagnosis(id,patient_name,phone,delete_time) VALUES(?,?,?,?)') ->execute([$id, '虚构患者' . $id, '13800000000', $id === 16 ? 1 : null]); } $pdo->exec('CREATE TABLE zyt_tcm_diagnosis_assign_log(id INT PRIMARY KEY AUTO_INCREMENT,diagnosis_id INT,from_assistant_id INT,to_assistant_id INT,operator_admin_id INT DEFAULT 7,create_time INT,is_inherit INT DEFAULT 0,inherit_visit_slot INT DEFAULT 0)'); $pdo->exec('CREATE TABLE zyt_tcm_diagnosis_auto_assign_log(diagnosis_id INT,rollback_time INT,rollback_admin_id INT)'); $pdo->exec('CREATE TABLE zyt_tcm_prescription_order(id INT PRIMARY KEY AUTO_INCREMENT,order_no VARCHAR(50),diagnosis_id INT,create_time INT,amount DECIMAL(10,2) DEFAULT 100,creator_id INT DEFAULT 1,fulfillment_status INT NULL,delete_time INT NULL)'); $lg = $pdo->prepare('INSERT INTO zyt_tcm_diagnosis_assign_log(diagnosis_id,from_assistant_id,to_assistant_id,create_time,is_inherit) VALUES(?,?,?,?,?)'); foreach ([[10,0,1,'2026-09-01',0],[10,1,2,'2026-09-20',1],[11,0,1,'2026-08-01',0], [12,0,2,'2026-09-03',0],[13,0,3,'2026-09-03',0],[14,0,1,'2026-09-04',0], [15,0,1,'2026-08-01',0],[15,1,2,'2026-09-15',1],[16,0,1,'2026-09-01',0], [17,1,1,'2026-09-01',0]] as [$did,$from,$to,$date,$inherit]) { $lg->execute([$did,$from,$to,strtotime($date),$inherit]); } $o = $pdo->prepare('INSERT INTO zyt_tcm_prescription_order(order_no,diagnosis_id,create_time,fulfillment_status) VALUES(?,?,?,?)'); foreach ([[10,'2026-09-02',3],[10,'2026-09-05',3],[10,'2026-10-01',3],[10,'2026-10-05',3], [11,'2026-08-02',3],[11,'2026-10-01',3],[13,'2026-09-05',3],[13,'2026-09-06',3], [14,'2026-09-05',3],[14,'2026-09-06',9],[15,'2026-08-02',3],[15,'2026-08-03',3], [15,'2026-08-04',3],[15,'2026-09-16',3],[15,'2026-09-17',4],[15,'2026-10-02',3]] as $i => [$did,$date,$status]) { $o->execute(['SYNTHETIC-' . $i,$did,strtotime($date),$status]); } $q = ['start_date' => '2026-09-01', 'end_date' => '2026-09-30']; $invoke = static function (string $class, string $action, array $params): array { $controller = (new ReflectionClass($class))->newInstanceWithoutConstructor(); $request = (new think\Request())->withGet($params); $property = (new ReflectionClass(app\BaseController::class))->getProperty('request'); $property->setAccessible(true); $property->setValue($controller, $request); return $controller->$action()->getData()['data']; }; foreach (['exclude', 'slot'] as $mode) { $legacyParams = ['month' => '2026-09', 'inherit_mode' => $mode]; foreach (['overview', 'assignLines', 'visitOrderLines'] as $action) { $params = $legacyParams + ['slot' => 0]; $expect($invoke(RevisitRateController::class, $action, $params) === Original::$action($params), '原Controller实际响应与原算法一致:' . $mode . '/' . $action); } } $legacyBaseline = Original::overview(['month' => '2026-09', 'inherit_mode' => 'exclude']); $expect($legacyBaseline['total']['assigned_count'] === 3 && $legacyBaseline['total']['visit4_count'] === 1, '原算法保留原自指派分母及当月继承订单分子规则'); $expect($invoke(RevisitRateController::class, 'deptOptions', []) === Original::deptOptions(), '原部门下拉契约不增加新报表字段'); $new = L::overview($q); $expect($invoke(RevisitAssignRateController::class, 'overview', $q + ['cohort_type' => 'inherit'])['cohort_type'] === 'new', '新指派入口固定new且使用独立Controller'); $expect($invoke(RevisitInheritRateController::class, 'overview', $q + ['cohort_type' => 'new'])['cohort_type'] === 'inherit', '继承入口固定inherit'); $expect(Original::overview(['month' => '2026-09', 'inherit_mode' => 'exclude']) === $legacyBaseline, '查询新报表不改变原报表响应'); $expect($new['total']['assigned_count'] === 2 && $new['total']['visit2_count'] === 1, '只圈定周期内新指派,排除区间外、继承、其他中心、自指派、软删、拒收'); $expect($new['total']['visit2_rate'] === 50.0 && $new['total']['inherit_count'] === 0, '新指派接诊率仅用新指派分母'); $inherit = L::overview($q, 'inherit'); $expect($inherit['total']['inherit_count'] === 2 && $inherit['total']['visit2_count'] === 2 && $inherit['total']['visit3_count'] === 2, '继承首单二诊、次单三诊,累计周期后订单且排除取消单'); $expect(L::overview($q + ['assistant_id' => 2])['total']['assigned_count'] === 1, '医助筛选作用于汇总'); $expect(L::overview($q + ['assistant_id' => 2])['total']['visit2_count'] === 0, '医助名下继承成交不进入其新指派分子'); $expect(L::overview($q + ['dept_ids' => '21'])['total']['assigned_count'] === 1, '部门子树过滤'); $expect(L::overview($q + ['dept_ids' => '20'])['total'] === $new['total'], '二中心父级包含各组'); $expect(L::overview($q + ['dept_ids' => '10'])['rows'] === [], '不能越过二中心范围'); $expect(L::overview($q + ['assistant_id' => 3])['rows'] === [], '其他中心医助不能绕过部门范围'); $expect(L::assignLines($q)['count'] === $new['total']['assigned_count'], '新指派患者明细与分母对账'); $expect(L::visitOrderLines($q + ['slot' => 2])['count'] === $new['total']['visit2_count'], '新指派订单明细与分子对账'); $expect(L::assignLines($q, 'inherit')['count'] === $inherit['total']['inherit_count'], '继承患者明细与分母对账'); $expect(L::visitOrderLines($q + ['slot' => 0], 'inherit')['count'] === $inherit['total']['visit_all_count'], '继承合计订单明细与汇总对账'); $expect(L::assignLines($q + ['assistant_id' => 2, 'dept_id' => 21])['count'] === 0, '下钻部门必须与页面医助取交集'); $expect(L::visitOrderLines($q + ['assistant_id' => 2, 'scope_assistant_id' => 1, 'slot' => 0])['count'] === 0, '下钻不能覆盖页面医助筛选'); $options = L::deptOptions(); $optionIds = array_column($options['assistants'], 'id'); sort($optionIds); $expect($optionIds === [1, 2], '下拉排除其他中心及已删除账号'); $expect(count($options['rows']) === 3, '部门选项仍是二中心完整子树'); foreach ([18 => 0, 19 => 1] as $did => $isInherit) { $pdo->prepare('INSERT INTO zyt_tcm_diagnosis(id,patient_name,phone) VALUES(?,?,?)')->execute([$did, '返还患者' . $did, '13800000000']); $lg->execute([$did,0,1,strtotime('2026-08-01'),0]); $lg->execute([$did,1,0,strtotime('2026-08-10'),0]); $lg->execute([$did,1,1,strtotime('2026-09-01'),$isInherit]); $o->execute(['RETURN-OLD-' . $did,$did,strtotime('2026-08-02'),3]); $o->execute(['RETURN-NEW-' . $did,$did,strtotime('2026-09-02'),3]); } $expect(L::overview($q)['total']['assigned_count'] === 3 && L::overview($q)['total']['visit2_count'] === 2, '释放后重新新指派给原医助正常统计'); $expect(L::overview($q, 'inherit')['total']['inherit_count'] === 3 && L::overview($q, 'inherit')['total']['visit2_count'] === 3, '释放后重新继承给原医助正常统计'); // 在实际 MySQL 5.7 验证菜单脚本、幂等性与逐权限继承。 $pdo->exec('CREATE TABLE zyt_system_menu(id INT PRIMARY KEY AUTO_INCREMENT,pid INT,type VARCHAR(1),name VARCHAR(80),icon VARCHAR(80),sort INT,perms VARCHAR(120),paths VARCHAR(120),component VARCHAR(120),selected VARCHAR(120),params VARCHAR(120),is_cache INT,is_show INT,is_disable INT,create_time INT,update_time INT)'); $pdo->exec('CREATE TABLE zyt_system_role_menu(role_id INT,menu_id INT,PRIMARY KEY(role_id,menu_id))'); foreach (['overview','deptOptions','assignLines','visitOrderLines'] as $i => $action) { $pdo->prepare('INSERT INTO zyt_system_menu(pid,type,name,perms) VALUES(100,?,?,?)') ->execute([$i === 0 ? 'C' : 'A', $action, 'stats.revisitRate/' . $action]); } $pdo->exec('INSERT INTO zyt_system_role_menu VALUES(9,1),(9,2),(9,3)'); $originalMenus = $pdo->query('SELECT * FROM zyt_system_menu ORDER BY id')->fetchAll(PDO::FETCH_ASSOC); $migration = file_get_contents(dirname(__DIR__) . '/sql/1.9.20261016/01_add_revisit_inherit_rate_menu.sql'); $migration = preg_replace('/^--.*$/m', '', $migration); for ($repeat = 0; $repeat < 2; $repeat++) { foreach (explode(';', $migration) as $sql) { if (trim($sql) !== '') { $pdo->exec($sql); } } } $expect((int) $pdo->query("SELECT COUNT(*) FROM zyt_system_menu WHERE perms LIKE 'stats.revisitInheritRate/%'")->fetchColumn() === 4, '迁移可重复执行,页面及3个接口各登记一次'); $expect((int) $pdo->query('SELECT COUNT(*) FROM zyt_system_role_menu WHERE role_id=9')->fetchColumn() === 6, '仅继承原有的3项对应权限'); $expect((int) $pdo->query("SELECT COUNT(*) FROM zyt_system_role_menu rm JOIN zyt_system_menu m ON m.id=rm.menu_id WHERE rm.role_id=9 AND m.perms='stats.revisitInheritRate/visitOrderLines'")->fetchColumn() === 0, '不扩大原角色明细权限'); $assignMigration = preg_replace('/^--.*$/m', '', file_get_contents(dirname(__DIR__) . '/sql/1.9.20261017/01_add_revisit_assign_rate_menu.sql')); for ($repeat = 0; $repeat < 2; $repeat++) { foreach (explode(';', $assignMigration) as $sql) { if (trim($sql) !== '') { $pdo->exec($sql); } } } $expect((int) $pdo->query("SELECT COUNT(*) FROM zyt_system_menu WHERE perms LIKE 'stats.revisitAssignRate/%'")->fetchColumn() === 4, '独立指派周期页面及3个接口幂等登记'); $expect((int) $pdo->query('SELECT COUNT(*) FROM zyt_system_role_menu WHERE role_id=9')->fetchColumn() === 9, '两个独立报表均逐权限继承原角色授权'); $expect((int) $pdo->query("SELECT COUNT(*) FROM zyt_system_role_menu rm JOIN zyt_system_menu m ON m.id=rm.menu_id WHERE rm.role_id=9 AND m.perms='stats.revisitAssignRate/visitOrderLines'")->fetchColumn() === 0, '独立指派周期接口不扩大订单明细权限'); $expect($pdo->query('SELECT * FROM zyt_system_menu WHERE id<=4 ORDER BY id')->fetchAll(PDO::FETCH_ASSOC) === $originalMenus, '新增菜单不会改写原菜单及权限记录'); echo "RevisitCohortDatabaseTest OK ({$checks} checks)\n"; } finally { $pdo->exec("DROP DATABASE `{$database}`"); }