feat: add enterprise order cancel open api
This commit is contained in:
275
server-api/tools/enterprise_order_cancel_mock_test.php
Normal file
275
server-api/tools/enterprise_order_cancel_mock_test.php
Normal file
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->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();
|
||||
}
|
||||
Reference in New Issue
Block a user