initTestData(); } protected function tearDown(): void { $this->cleanupTestData(); parent::tearDown(); } private function initTestData() { $this->cleanupTestData(); DB::table('s_department')->insert([ 'id' => 9999, 'department_name' => '测试科室', 'department_number' => 'TEST001', 'department_status' => 1, 'is_del' => 0, ]); DB::table('s_appointment_type')->insert([ 'id' => 9999, 'name' => '测试渠道', 'jiancheng' => '测试', 'status' => 1, 'is_del' => 0, ]); DB::table('s_period')->insert([ 'id' => 9999, 'period_name' => '测试时段', 'period_begin_time' => '08:00:00', 'period_end_time' => '10:00:00', 'department_id' => 9999, 'date_type' => 1, 'period_status' => 1, ]); DB::table('s_department_resources')->insert([ 'id' => 9999, 'department_id' => 9999, 'department_resources_name' => '测试资源', 'is_del' => 0, ]); DB::table('s_source_roster_detail')->insert([ 'id' => 9999, 'department_id' => 9999, 'resources_id' => 9999, 'period_id' => 9999, 'device_id' => '1', 'date' => date('Y-m-d', strtotime('+1 day')), 'begin_time' => '08:00:00', 'end_time' => '10:00:00', 'end_reservation_time' => '23:59:59', 'patient_type' => '0,1,2,3', 'weekname' => '1', 'status' => 1, 'is_del' => 0, ]); DB::table('s_source_roster_detail_count')->insert([ 'id' => 9999, 'roster_detail_id' => 9999, 'appointment_type_id' => 9999, 'count' => 10, 'used_count' => 3, 'locked_count' => 0, 'version' => 1, ]); } private function cleanupTestData() { DB::table('s_list')->where('reg_num', 'like', 'TEST_REG_%')->delete(); DB::table('s_list_log')->where('reg_num', 'like', 'TEST_REG_%')->delete(); $tables = [ 's_source_roster_detail_count', 's_source_roster_detail', 's_department_resources', 's_period', 's_appointment_type', 's_department' ]; foreach ($tables as $table) { DB::table($table)->where('id', '>=', 9999)->delete(); } } private function createTestList($status = 1, $detail = 'default') { if ($detail === 'default') { $detail = json_encode([['roster_detail_count_id' => 9999, 'count' => 2]]); } return DB::table('s_list')->insertGetId([ 'list_status' => $status, 'reg_num' => 'TEST_REG_' . time() . '_' . rand(1000, 9999), 'user_name' => '测试患者', 'entrust' => '测试检查项目', 'entrust_code' => 'TEST001', 'patient_type' => 1, 'roster_id' => 9999, 'appointment_type_id' => 9999, 'appointment_use_plan_detail' => $detail, 'reservation_date' => date('Y-m-d', strtotime('+1 day')), 'reservation_time' => 9999, 'is_del' => 0, 'is_nullify' => 0, ]); } public function test_release_source_from_detail() { $countBefore = DB::table('s_source_roster_detail_count')->where('id', 9999)->first(); $this->assertEquals(3, $countBefore->used_count); $detail = json_encode([['roster_detail_count_id' => 9999, 'count' => 2]]); $listId = $this->createTestList(1, $detail); $service = new PlanListService(); $result = $service->releaseSourceFromDetail($listId); $this->assertTrue($result); $countAfter = DB::table('s_source_roster_detail_count')->where('id', 9999)->first(); $this->assertEquals(1, $countAfter->used_count); $this->assertEquals(2, $countAfter->version); echo "\n✅ 测试1通过: releaseSourceFromDetail 精确释放号源成功\n"; echo " 释放前 used_count=3, 释放后 used_count=1, version从1变为2\n"; } public function test_release_source_with_version_check() { DB::table('s_source_roster_detail_count')->where('id', 9999)->update(['version' => 5]); $detail = json_encode([['roster_detail_count_id' => 9999, 'count' => 1]]); $listId = $this->createTestList(1, $detail); $service = new PlanListService(); $result = $service->releaseSourceFromDetail($listId); $this->assertTrue($result); $countAfter = DB::table('s_source_roster_detail_count')->where('id', 9999)->first(); $this->assertEquals(6, $countAfter->version); echo "\n✅ 测试2通过: 乐观锁version正确递增\n"; echo " 释放前 version=5, 释放后 version=6\n"; } public function test_release_source_fallback_when_no_detail() { DB::table('s_source_roster_detail_count')->where('id', 9999)->update(['used_count' => 3, 'version' => 1]); DB::table('s_list')->where('reg_num', 'like', 'TEST_REG_%')->delete(); $listId = $this->createTestList(1, null); $mainInfo = DB::table('s_list')->where('id', $listId)->first(); $countBefore = DB::table('s_source_roster_detail_count')->where('id', 9999)->first(); $this->assertEquals(3, $countBefore->used_count, '初始used_count应为3'); $service = new PlanListService(); $result = $service->releaseSourceFromDetail($listId, $mainInfo); $this->assertTrue($result, 'releaseSourceFromDetail应返回true'); $countAfter = DB::table('s_source_roster_detail_count')->where('id', 9999)->first(); $this->assertEquals(2, $countAfter->used_count, '释放后used_count应为2'); echo "\n✅ 测试3通过: 无明细时fallback降级释放成功\n"; echo " 无appointment_use_plan_detail时,按roster_id+appointment_type_id释放1个名额\n"; } public function test_release_source_multiple_channels() { DB::table('s_source_roster_detail_count')->insert([ 'id' => 10000, 'roster_detail_id' => 9999, 'appointment_type_id' => 10000, 'count' => 5, 'used_count' => 3, 'locked_count' => 0, 'version' => 1, ]); $detail = json_encode([ ['roster_detail_count_id' => 9999, 'count' => 2], ['roster_detail_count_id' => 10000, 'count' => 1], ]); $listId = $this->createTestList(1, $detail); $service = new PlanListService(); $result = $service->releaseSourceFromDetail($listId); $this->assertTrue($result); $count1 = DB::table('s_source_roster_detail_count')->where('id', 9999)->first(); $count2 = DB::table('s_source_roster_detail_count')->where('id', 10000)->first(); $this->assertEquals(1, $count1->used_count); $this->assertEquals(2, $count2->used_count); DB::table('s_source_roster_detail_count')->where('id', 10000)->delete(); echo "\n✅ 测试4通过: 多渠道合并号源精确释放成功\n"; echo " 渠道9999: used_count从3变为1\n"; echo " 渠道10000: used_count从3变为2\n"; } public function test_release_source_with_insufficient_used_count() { DB::table('s_source_roster_detail_count')->where('id', 9999)->update(['used_count' => 1]); $detail = json_encode([['roster_detail_count_id' => 9999, 'count' => 5]]); $listId = $this->createTestList(1, $detail); $service = new PlanListService(); $result = $service->releaseSourceFromDetail($listId); $this->assertTrue($result); $countAfter = DB::table('s_source_roster_detail_count')->where('id', 9999)->first(); $this->assertEquals(0, $countAfter->used_count); echo "\n✅ 测试5通过: 释放数量大于used_count时,置零并记录警告日志\n"; echo " 尝试释放5个,实际只有1个,已置零并记录日志\n"; } public function test_cancel_yu_yue_clears_detail() { $detail = json_encode([['roster_detail_count_id' => 9999, 'count' => 2]]); $listId = $this->createTestList(1, $detail); $listInfo = DB::table('s_list')->where('id', $listId)->first(); $service = new PlanListService(); $result = $service->CancelYuYue($listId, $listInfo->reg_num); $this->assertTrue($result['status']); $listAfter = DB::table('s_list')->where('id', $listId)->first(); $this->assertEquals(0, $listAfter->list_status); $this->assertNull($listAfter->appointment_use_plan_detail); $countAfter = DB::table('s_source_roster_detail_count')->where('id', 9999)->first(); $this->assertEquals(1, $countAfter->used_count); echo "\n✅ 测试6通过: CancelYuYue正确释放号源并清空明细\n"; echo " list_status变为0, appointment_use_plan_detail被清空\n"; echo " used_count从3变为1\n"; } public function test_optimistic_lock_conflict_detection() { $countRecord = DB::table('s_source_roster_detail_count')->where('id', 9999)->first(); $oldVersion = $countRecord->version; $affected = DB::table('s_source_roster_detail_count') ->where(['id' => 9999, 'version' => $oldVersion]) ->update([ 'used_count' => DB::raw('used_count + 1'), 'version' => DB::raw('version + 1'), ]); $this->assertEquals(1, $affected); $conflictAffected = DB::table('s_source_roster_detail_count') ->where(['id' => 9999, 'version' => $oldVersion]) ->update([ 'used_count' => DB::raw('used_count + 1'), 'version' => DB::raw('version + 1'), ]); $this->assertEquals(0, $conflictAffected); echo "\n✅ 测试7通过: 乐观锁冲突检测生效\n"; echo " 第一次更新成功(affected=1)\n"; echo " 第二次使用旧version更新失败(affected=0)\n"; } }