safeLoad(); use app\support\EnterpriseOrderService; use support\think\Db; Db::setConfig(require dirname(__DIR__) . '/config/think-orm.php'); function assertTrue(bool $condition, string $message): void { if (!$condition) { throw new RuntimeException($message); } } function cleanupMockData(): void { $orderIds = Db::name('orders')->whereLike('order_no', 'EOCMOCK%')->column('id'); if ($orderIds) { Db::name('enterprise_order_events')->whereIn('order_id', $orderIds)->delete(); Db::name('enterprise_customer_order_refs')->whereIn('order_id', $orderIds)->delete(); Db::name('message_logs')->where('biz_type', 'order')->whereIn('biz_id', $orderIds)->delete(); Db::name('user_messages')->where('biz_type', 'order')->whereIn('biz_id', $orderIds)->delete(); Db::name('appraisal_tasks')->whereIn('order_id', $orderIds)->delete(); $logisticsIds = Db::name('order_logistics')->whereIn('order_id', $orderIds)->column('id'); if ($logisticsIds) { Db::name('order_logistics_nodes')->whereIn('logistics_id', $logisticsIds)->delete(); } Db::name('order_logistics')->whereIn('order_id', $orderIds)->delete(); Db::name('order_timelines')->whereIn('order_id', $orderIds)->delete(); Db::name('order_shipping_targets')->whereIn('order_id', $orderIds)->delete(); Db::name('order_return_addresses')->whereIn('order_id', $orderIds)->delete(); Db::name('order_extras')->whereIn('order_id', $orderIds)->delete(); Db::name('order_products')->whereIn('order_id', $orderIds)->delete(); Db::name('orders')->whereIn('id', $orderIds)->delete(); } $customerIds = Db::name('enterprise_customers')->whereLike('customer_code', 'EOCMOCK%')->column('id'); if ($customerIds) { Db::name('enterprise_customer_apps')->whereIn('customer_id', $customerIds)->delete(); Db::name('enterprise_order_events')->whereIn('customer_id', $customerIds)->delete(); Db::name('enterprise_customer_order_refs')->whereIn('customer_id', $customerIds)->delete(); Db::name('enterprise_customers')->whereIn('id', $customerIds)->delete(); } $userIds = Db::name('users')->whereLike('mobile', '1399920%')->column('id'); if ($userIds) { Db::name('user_auths')->whereIn('user_id', $userIds)->delete(); Db::name('user_addresses')->whereIn('user_id', $userIds)->delete(); Db::name('users')->whereIn('id', $userIds)->delete(); } } function createMockCustomer(string $suffix): array { $now = date('Y-m-d H:i:s'); $userId = (int)Db::name('users')->insertGetId([ 'nickname' => '第三方取消测试客户', 'avatar' => '', 'mobile' => '1399920' . str_pad((string)random_int(1, 9999), 4, '0', STR_PAD_LEFT), 'password' => '', 'status' => 'enabled', 'last_login_at' => null, 'created_at' => $now, 'updated_at' => $now, ]); $customerId = (int)Db::name('enterprise_customers')->insertGetId([ 'customer_code' => 'EOCMOCK' . $suffix, 'customer_name' => '第三方取消测试客户', 'contact_name' => '', 'contact_mobile' => '', 'contact_email' => '', 'settlement_type' => 'monthly', 'user_id' => $userId, 'webhook_url' => '', 'webhook_enabled' => 0, 'status' => 'enabled', 'remark' => '第三方取消订单 mock 测试', 'created_at' => $now, 'updated_at' => $now, ]); return Db::name('enterprise_customers')->where('id', $customerId)->find(); } function createMockOrder(array $customer, string $suffix, string $status = 'pending_shipping', bool $withInboundLogistics = false): array { $now = date('Y-m-d H:i:s'); $orderNo = 'EOCMOCK' . $suffix; $orderId = (int)Db::name('orders')->insertGetId([ 'order_no' => $orderNo, 'appraisal_no' => 'EOC-MOCK-' . $suffix, 'user_id' => (int)$customer['user_id'], 'service_mode' => 'physical', 'service_provider' => 'anxinyan', 'price_package_id' => null, 'price_package_name' => '测试套餐', 'price_package_code' => 'mock_basic', 'price_package_price' => 0, 'payment_status' => 'paid', 'order_status' => $status, 'display_status' => $status === 'pending_shipping' ? '待寄送商品' : '鉴定中心已收货', 'estimated_finish_time' => date('Y-m-d H:i:s', strtotime('+48 hours')), 'source_channel' => 'enterprise_push', 'source_customer_id' => (string)$customer['customer_code'], 'pay_amount' => 0, 'paid_at' => $now, 'cancelled_at' => null, 'created_at' => $now, 'updated_at' => $now, ]); Db::name('enterprise_customer_order_refs')->insert([ 'customer_id' => (int)$customer['id'], 'external_order_no' => 'EXT-' . $suffix, 'order_id' => $orderId, 'order_no' => $orderNo, 'appraisal_no' => 'EOC-MOCK-' . $suffix, 'payload_hash' => '', 'created_at' => $now, 'updated_at' => $now, ]); Db::name('order_products')->insert([ 'order_id' => $orderId, 'category_id' => null, 'category_name' => '测试品类', 'brand_id' => null, 'brand_name' => '测试品牌', 'color' => '', 'size_spec' => '', 'serial_no' => '', 'product_name' => '第三方取消测试商品', 'product_cover' => '', 'created_at' => $now, 'updated_at' => $now, ]); Db::name('order_extras')->insert([ 'order_id' => $orderId, 'purchase_channel' => '', 'purchase_price' => 0, 'purchase_date' => null, 'usage_status' => '', 'condition_desc' => '', 'has_accessories' => 0, 'accessories_json' => json_encode([], JSON_UNESCAPED_UNICODE), 'remark' => '', 'created_at' => $now, 'updated_at' => $now, ]); Db::name('appraisal_tasks')->insert([ 'order_id' => $orderId, 'task_stage' => 'first_review', 'service_provider' => 'anxinyan', 'status' => 'pending', 'assignee_id' => null, 'assignee_name' => '未分配', 'started_at' => null, 'submitted_at' => null, 'sla_deadline' => date('Y-m-d H:i:s', strtotime('+48 hours')), 'is_overtime' => 0, 'created_at' => $now, 'updated_at' => $now, ]); Db::name('order_timelines')->insert([ 'order_id' => $orderId, 'node_code' => 'pending_shipping', 'node_text' => '待寄送商品', 'node_desc' => '请将商品寄送至鉴定中心', 'operator_type' => 'system', 'operator_id' => null, 'occurred_at' => $now, 'created_at' => $now, ]); if ($withInboundLogistics) { $logisticsId = (int)Db::name('order_logistics')->insertGetId([ 'order_id' => $orderId, 'logistics_type' => 'send_to_center', 'express_company' => '顺丰速运', 'tracking_no' => 'SF' . $suffix, 'tracking_status' => 'submitted', 'latest_desc' => '客户已提交寄送运单', 'latest_time' => $now, 'created_at' => $now, 'updated_at' => $now, ]); Db::name('order_logistics_nodes')->insert([ 'logistics_id' => $logisticsId, 'node_time' => $now, 'node_desc' => '客户已提交寄送运单', 'node_location' => '第三方', 'created_at' => $now, ]); } return [ 'order_id' => $orderId, 'external_order_no' => 'EXT-' . $suffix, ]; } try { cleanupMockData(); $customer = createMockCustomer('CUSTOMER'); $otherCustomer = createMockCustomer('OTHER'); $service = new EnterpriseOrderService(); $cancelTarget = createMockOrder($customer, 'SUCCESS'); $cancel = $service->cancelOrder($customer, [ 'external_order_no' => $cancelTarget['external_order_no'], 'cancel_reason' => '客户取消鉴定', ]); assertTrue(($cancel['cancelled'] ?? null) === true, 'cancel should mark first request as cancelled'); assertTrue(($cancel['order']['order_status'] ?? '') === 'cancelled', 'cancel should return cancelled order status'); assertTrue((int)Db::name('appraisal_tasks')->where('order_id', (int)$cancelTarget['order_id'])->count() === 0, 'cancel should delete pending appraisal task'); assertTrue((int)Db::name('order_timelines')->where('order_id', (int)$cancelTarget['order_id'])->where('node_code', 'cancelled')->count() === 1, 'cancel should write cancelled timeline'); $repeat = $service->cancelOrder($customer, [ 'external_order_no' => $cancelTarget['external_order_no'], ]); assertTrue(($repeat['cancelled'] ?? null) === false, 'repeat cancel should be idempotent'); assertTrue((int)Db::name('order_timelines')->where('order_id', (int)$cancelTarget['order_id'])->where('node_code', 'cancelled')->count() === 1, 'repeat cancel should not duplicate cancelled timeline'); $shippedTarget = createMockOrder($customer, 'SHIPPED', 'pending_shipping', true); $shippingRejected = false; try { $service->cancelOrder($customer, [ 'external_order_no' => $shippedTarget['external_order_no'], ]); } catch (InvalidArgumentException $e) { $shippingRejected = str_contains($e->getMessage(), '寄入运单'); } assertTrue($shippingRejected, 'cancel should reject orders with inbound logistics'); $receivedTarget = createMockOrder($customer, 'RECEIVED', 'received'); $statusRejected = false; try { $service->cancelOrder($customer, [ 'external_order_no' => $receivedTarget['external_order_no'], ]); } catch (InvalidArgumentException $e) { $statusRejected = str_contains($e->getMessage(), '状态不可取消'); } assertTrue($statusRejected, 'cancel should reject non pending_shipping order'); $crossCustomerRejected = false; try { $service->cancelOrder($otherCustomer, [ 'external_order_no' => $receivedTarget['external_order_no'], ]); } catch (RuntimeException $e) { $crossCustomerRejected = str_contains($e->getMessage(), '订单不存在'); } assertTrue($crossCustomerRejected, 'cancel should reject external order from another customer'); echo "ENTERPRISE_ORDER_CANCEL_MOCK_TEST_OK\n"; } catch (Throwable $e) { fwrite(STDERR, "ENTERPRISE_ORDER_CANCEL_MOCK_TEST_FAIL: " . $e->getMessage() . "\n"); exit(1); } finally { cleanupMockData(); }