diff --git a/gateway/src/main/resources/application.yml b/gateway/src/main/resources/application.yml index bcce9da14..eafc6c63f 100644 --- a/gateway/src/main/resources/application.yml +++ b/gateway/src/main/resources/application.yml @@ -61,7 +61,7 @@ spring: - id: warehouse-service uri: lb://warehouse-service predicates: - - Path=/api/merchant/v1/warehouses/**,/api/merchant/v1/partners/**,/api/platform/v1/warehouses/**,/api/platform/v1/warehouse-admin/**,/api/platform/v1/partners/**,/api/platform/v1/express-companies/**,/api/platform/v1/pallets/**,/api/platform/v1/locations/**,/api/platform/v1/overseas-storage-fees/**,/api/platform/v1/iam/tenants/*/overseas-storage-fees/** + - Path=/api/merchant/v1/warehouses/**,/api/merchant/v1/partners/**,/api/platform/v1/warehouses/**,/api/platform/v1/warehouse-admin/**,/api/platform/v1/partners/**,/api/platform/v1/express-companies/**,/api/platform/v1/pallets/**,/api/platform/v1/locations/**,/api/platform/v1/overseas-storage-fees/**,/api/platform/v1/iam/tenants/{tenantId}/overseas-storage-fees,/api/platform/v1/iam/tenants/{tenantId}/overseas-storage-fees/** - id: file-service uri: lb://file-service predicates: diff --git a/reports/admin-test-env-acceptance-20260808060030.json b/reports/admin-test-env-acceptance-20260808060030.json new file mode 100644 index 000000000..83bd93a8d --- /dev/null +++ b/reports/admin-test-env-acceptance-20260808060030.json @@ -0,0 +1,183 @@ +{ + "reportName": "后台管理测试环境 HTTP API 验收", + "marker": "ADMIN-API-20260808060030925", + "gateway": "http://8.163.30.128:9000", + "generatedAt": "2026-08-08T06:00:32.398Z", + "startedAt": "2026-08-08T06:00:30.925Z", + "success": false, + "fatal": null, + "summary": { + "total": 7, + "passed": 6, + "failed": 1 + }, + "modules": [ + { + "name": "overseas-storage-fees", + "startedAt": "2026-08-08T06:00:31.303Z", + "finishedAt": "2026-08-08T06:00:32.398Z", + "success": false, + "passed": 5, + "failed": 1, + "fatal": null + } + ], + "scenarios": [], + "deploymentBlockers": [ + { + "module": "overseas-storage-fees", + "scenario": "customer-overseas-storage-fee-lifecycle", + "name": "查询客户海外仓费用配置", + "evidence": "tenantId=2085969381372317698", + "code": "S0500", + "requestId": "43e1231e-8404-4be0-b8da-db4dd63133e3", + "traceId": "1ef03fcf4e954433bfc70ab7c226454d" + } + ], + "createdResources": { + "overseasStorageFeeCustomer": { + "tenantId": "2085969381372317698", + "marker": "ADMIN-API-20260808060030925", + "ownedByModule": true + } + }, + "excluded": [ + { + "scope": "dashboard", + "reason": "首页为静态演示数据,不调用后端 API" + }, + { + "scope": "hidden-prototypes", + "reason": "隐藏原型页面尚无已确认后端契约" + } + ], + "securityNote": "报告不保存登录密码、Access Token、Refresh Token、图片二进制或 OCR 原文全文。", + "steps": [ + { + "module": "authentication", + "scenario": null, + "name": "平台管理员登录", + "passed": true, + "deploymentBlocker": false, + "request": { + "method": "POST", + "path": "/api/auth/login" + }, + "http": 200, + "code": "200", + "message": "success", + "requestId": "fe913670-d582-4ca0-8a4f-0d0ed77d1a26, 9871584d-e19d-408c-9b11-b7953a436b0f", + "traceId": "7214d678e2c0433db416a817fd7d6c16", + "evidence": "", + "checkedAt": "2026-08-08T06:00:31.302Z" + }, + { + "module": "overseas-storage-fees", + "scenario": null, + "name": "检查测试联系电话可用性", + "passed": true, + "deploymentBlocker": false, + "request": { + "method": "GET", + "path": "/api/platform/v1/iam/tenants/contact-phone-availability?contactPhone=16168831304" + }, + "http": 200, + "code": "200", + "message": "success", + "requestId": "99783175-a094-4677-913f-3b338405a38a, b9139302-91ea-4ca8-a257-8339d089b3ad", + "traceId": "f2b5ed1db5a246e7a4ac88bd48784967", + "evidence": "attempt=1", + "checkedAt": "2026-08-08T06:00:31.356Z" + }, + { + "module": "overseas-storage-fees", + "scenario": "customer-overseas-storage-fee-lifecycle", + "name": "创建海外仓费用隔离客户", + "passed": true, + "deploymentBlocker": false, + "request": { + "method": "POST", + "path": "/api/platform/v1/iam/tenants" + }, + "http": 200, + "code": "200", + "message": "success", + "requestId": "0a5b9b08-171f-41de-b879-3ca2c6ba0ad9, 606ad7cc-e242-4d59-8267-78909a68c787", + "traceId": "636b0885ae024db9bbd41a35416d128e", + "evidence": "", + "checkedAt": "2026-08-08T06:00:32.221Z" + }, + { + "module": "overseas-storage-fees", + "scenario": "customer-overseas-storage-fee-lifecycle", + "name": "海外仓费用隔离客户创建并带 marker", + "passed": true, + "deploymentBlocker": false, + "request": { + "method": "POST", + "path": "/api/platform/v1/iam/tenants" + }, + "http": 200, + "code": "200", + "message": "success", + "requestId": "0a5b9b08-171f-41de-b879-3ca2c6ba0ad9, 606ad7cc-e242-4d59-8267-78909a68c787", + "traceId": "636b0885ae024db9bbd41a35416d128e", + "evidence": "tenantId=2085969381372317698", + "checkedAt": "2026-08-08T06:00:32.221Z" + }, + { + "module": "overseas-storage-fees", + "scenario": "customer-overseas-storage-fee-lifecycle", + "name": "查询客户海外仓费用配置", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/iam/tenants/2085969381372317698/overseas-storage-fees" + }, + "http": 200, + "code": "S0500", + "message": "Internal server error", + "requestId": "43e1231e-8404-4be0-b8da-db4dd63133e3", + "traceId": "1ef03fcf4e954433bfc70ab7c226454d", + "evidence": "tenantId=2085969381372317698", + "checkedAt": "2026-08-08T06:00:32.258Z" + }, + { + "module": "overseas-storage-fees", + "scenario": "customer-overseas-storage-fee-lifecycle", + "name": "费用配置契约不可用时清理隔离客户", + "passed": true, + "deploymentBlocker": false, + "request": { + "method": "POST", + "path": "/api/platform/v1/iam/tenants/2085969381372317698/archive" + }, + "http": 200, + "code": "200", + "message": "success", + "requestId": "7ddac37a-a099-4f83-b8a8-e662245e62e1, c756d5e2-959b-41ab-99b5-74415583f40d", + "traceId": "1878469a40be488abbbf6d3ce9300c8f", + "evidence": "tenantId=2085969381372317698", + "checkedAt": "2026-08-08T06:00:32.398Z" + }, + { + "module": "overseas-storage-fees", + "scenario": "customer-overseas-storage-fee-lifecycle", + "name": "隔离客户清理后停用", + "passed": true, + "deploymentBlocker": false, + "request": { + "method": "POST", + "path": "/api/platform/v1/iam/tenants/2085969381372317698/archive" + }, + "http": 200, + "code": "200", + "message": "success", + "requestId": "7ddac37a-a099-4f83-b8a8-e662245e62e1, c756d5e2-959b-41ab-99b5-74415583f40d", + "traceId": "1878469a40be488abbbf6d3ce9300c8f", + "evidence": "status=DISABLED", + "checkedAt": "2026-08-08T06:00:32.398Z" + } + ] +} diff --git a/reports/admin-test-env-acceptance-20260808061243.json b/reports/admin-test-env-acceptance-20260808061243.json new file mode 100644 index 000000000..206d76f78 --- /dev/null +++ b/reports/admin-test-env-acceptance-20260808061243.json @@ -0,0 +1,705 @@ +{ + "reportName": "后台管理测试环境 HTTP API 验收", + "marker": "ADMIN-API-20260808061243233", + "gateway": "http://8.163.30.128:9000", + "generatedAt": "2026-08-08T06:12:44.205Z", + "startedAt": "2026-08-08T06:12:43.233Z", + "success": false, + "fatal": null, + "summary": { + "total": 25, + "passed": 1, + "failed": 24 + }, + "modules": [ + { + "name": "ocean-bookings", + "startedAt": "2026-08-08T06:12:43.611Z", + "finishedAt": "2026-08-08T06:12:43.833Z", + "success": false, + "passed": 0, + "failed": 9, + "fatal": "Cannot access 'create' before initialization" + }, + { + "name": "truck-appointments", + "startedAt": "2026-08-08T06:12:43.834Z", + "finishedAt": "2026-08-08T06:12:44.205Z", + "success": false, + "passed": 0, + "failed": 15, + "fatal": null + } + ], + "scenarios": [], + "deploymentBlockers": [ + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱列表分页与 keyword 查询", + "evidence": "", + "code": null, + "requestId": "935b2577-51", + "traceId": null + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱列表分页结构正确", + "evidence": "records=n/a", + "code": null, + "requestId": "935b2577-51", + "traceId": null + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱 DRAFT 状态筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "b82ce6ff-52", + "traceId": null + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱 SUBMITTED 状态筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "935b2577-53", + "traceId": null + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱 CONFIRMED 状态筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "b82ce6ff-54", + "traceId": null + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱 RELEASED 状态筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "935b2577-55", + "traceId": null + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱 FAILED 状态筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "b82ce6ff-56", + "traceId": null + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱 CANCELLED 状态筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "935b2577-57", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约分页 keyword 查询", + "evidence": "", + "code": null, + "requestId": "b82ce6ff-58", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约分页结构正确", + "evidence": "records=n/a", + "code": null, + "requestId": "b82ce6ff-58", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 DRAFT 筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "935b2577-59", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 PENDING_DISPATCH 筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "b82ce6ff-60", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 DISPATCHED 筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "935b2577-61", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 PICKUP_IN_PROGRESS 筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "b82ce6ff-62", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 ARRIVED_WAREHOUSE 筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "935b2577-63", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 LOADING 筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "b82ce6ff-64", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 DEPARTED 筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "935b2577-65", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 COMPLETED 筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "b82ce6ff-66", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 RESCHEDULED 筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "935b2577-67", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 EXCEPTION 筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "b82ce6ff-68", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 CANCELLED 筛选", + "evidence": "HTTP=404, code=missing, records=n/a", + "code": null, + "requestId": "935b2577-69", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "创建隔离拖车预约", + "evidence": "", + "code": null, + "requestId": "b82ce6ff-70", + "traceId": null + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约费用、司机车辆和地址字段完整", + "evidence": "id=missing, status=missing", + "code": null, + "requestId": "b82ce6ff-70", + "traceId": null + } + ], + "createdResources": {}, + "excluded": [ + { + "scope": "dashboard", + "reason": "首页为静态演示数据,不调用后端 API" + }, + { + "scope": "hidden-prototypes", + "reason": "隐藏原型页面尚无已确认后端契约" + } + ], + "securityNote": "报告不保存登录密码、Access Token、Refresh Token、图片二进制或 OCR 原文全文。", + "steps": [ + { + "module": "authentication", + "scenario": null, + "name": "平台管理员登录", + "passed": true, + "deploymentBlocker": false, + "request": { + "method": "POST", + "path": "/api/auth/login" + }, + "http": 200, + "code": "200", + "message": "success", + "requestId": "4d3a980d-5349-47c6-8c96-8a31a6934744, 86c817c2-80bc-45c0-8de1-cc4e52242456", + "traceId": "aae31740ac694e65ad8176b9e343096d", + "evidence": "", + "checkedAt": "2026-08-08T06:12:43.610Z" + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱列表分页与 keyword 查询", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/ocean-bookings?page=1&size=5&keyword=ADMIN-API-20260808061243233" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "935b2577-51", + "traceId": null, + "evidence": "", + "checkedAt": "2026-08-08T06:12:43.669Z" + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱列表分页结构正确", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/ocean-bookings?page=1&size=5&keyword=ADMIN-API-20260808061243233" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "935b2577-51", + "traceId": null, + "evidence": "records=n/a", + "checkedAt": "2026-08-08T06:12:43.669Z" + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱 DRAFT 状态筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=DRAFT" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "b82ce6ff-52", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:43.693Z" + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱 SUBMITTED 状态筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=SUBMITTED" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "935b2577-53", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:43.712Z" + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱 CONFIRMED 状态筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=CONFIRMED" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "b82ce6ff-54", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:43.741Z" + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱 RELEASED 状态筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=RELEASED" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "935b2577-55", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:43.772Z" + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱 FAILED 状态筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=FAILED" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "b82ce6ff-56", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:43.801Z" + }, + { + "module": "ocean-bookings", + "scenario": "ocean-booking-lifecycle", + "name": "订舱 CANCELLED 状态筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=CANCELLED" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "935b2577-57", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:43.833Z" + }, + { + "module": "ocean-bookings", + "scenario": null, + "name": "模块执行", + "passed": false, + "deploymentBlocker": false, + "request": null, + "http": null, + "code": null, + "message": null, + "requestId": null, + "traceId": null, + "evidence": "Cannot access 'create' before initialization", + "checkedAt": "2026-08-08T06:12:43.833Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约分页 keyword 查询", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&keyword=ADMIN-API-20260808061243233" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "b82ce6ff-58", + "traceId": null, + "evidence": "", + "checkedAt": "2026-08-08T06:12:43.864Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约分页结构正确", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&keyword=ADMIN-API-20260808061243233" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "b82ce6ff-58", + "traceId": null, + "evidence": "records=n/a", + "checkedAt": "2026-08-08T06:12:43.864Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 DRAFT 筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&status=DRAFT" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "935b2577-59", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:43.895Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 PENDING_DISPATCH 筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&status=PENDING_DISPATCH" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "b82ce6ff-60", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:43.925Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 DISPATCHED 筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&status=DISPATCHED" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "935b2577-61", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:43.956Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 PICKUP_IN_PROGRESS 筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&status=PICKUP_IN_PROGRESS" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "b82ce6ff-62", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:43.986Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 ARRIVED_WAREHOUSE 筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&status=ARRIVED_WAREHOUSE" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "935b2577-63", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:44.005Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 LOADING 筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&status=LOADING" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "b82ce6ff-64", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:44.037Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 DEPARTED 筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&status=DEPARTED" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "935b2577-65", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:44.064Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 COMPLETED 筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&status=COMPLETED" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "b82ce6ff-66", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:44.096Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 RESCHEDULED 筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&status=RESCHEDULED" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "935b2577-67", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:44.115Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 EXCEPTION 筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&status=EXCEPTION" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "b82ce6ff-68", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:44.142Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约 CANCELLED 筛选", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "GET", + "path": "/api/platform/v1/truck-appointments?page=1&size=5&status=CANCELLED" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "935b2577-69", + "traceId": null, + "evidence": "HTTP=404, code=missing, records=n/a", + "checkedAt": "2026-08-08T06:12:44.173Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "创建隔离拖车预约", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "POST", + "path": "/api/platform/v1/truck-appointments" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "b82ce6ff-70", + "traceId": null, + "evidence": "", + "checkedAt": "2026-08-08T06:12:44.205Z" + }, + { + "module": "truck-appointments", + "scenario": "truck-appointment-lifecycle", + "name": "拖车预约费用、司机车辆和地址字段完整", + "passed": false, + "deploymentBlocker": true, + "request": { + "method": "POST", + "path": "/api/platform/v1/truck-appointments" + }, + "http": 404, + "code": null, + "message": null, + "requestId": "b82ce6ff-70", + "traceId": null, + "evidence": "id=missing, status=missing", + "checkedAt": "2026-08-08T06:12:44.205Z" + } + ] +} diff --git a/scripts/admin-test-env-modules/ocean-bookings.mjs b/scripts/admin-test-env-modules/ocean-bookings.mjs new file mode 100644 index 000000000..d14cfae4c --- /dev/null +++ b/scripts/admin-test-env-modules/ocean-bookings.mjs @@ -0,0 +1,62 @@ +const MODULE = 'ocean-bookings' +const OPTIONS = { scenario: 'ocean-booking-lifecycle', deploymentBlocker: true } +const BASE = '/api/platform/v1/ocean-bookings' + +function id(value) { return value == null ? '' : String(value) } +function codeOf(r) { return r?.body?.code == null ? 'missing' : String(r.body.code) } +function missing(r) { return r?.http === 404 || r?.http === 405 || r?.http === 503 } +function failure(r) { return [400, 401, 403, 404, 409, 503].includes(r?.http) || (r?.http === 200 && codeOf(r) !== '200') } +async function request(context, method, path, body, headers = {}) { + try { + const response = await fetch(`${context.options.gateway}${path}`, { method, headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}`, ...(body === undefined ? {} : { 'Content-Type': 'application/json; charset=utf-8' }), ...headers }, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(60_000) }) + const raw = await response.text(); let payload = null + try { payload = raw ? JSON.parse(raw) : null } catch { /* bounded raw evidence */ } + return { method, path, http: response.status, body: payload, raw: payload === null ? raw.slice(0, 500) : null, requestId: payload?.requestId || response.headers.get('x-request-id'), traceId: payload?.traceId || response.headers.get('x-trace-id') } + } catch (error) { return { method, path, http: 0, body: null, raw: String(error), requestId: null, traceId: null } } +} +function data(r) { return String(r?.body?.code) === '200' ? r.body.data : null } +function draft(context, suffix) { return { customerId: '10001', customerName: `订舱验收客户-${suffix}`, shippingCompanyId: null, shippingCompanyName: `船公司-${suffix}`, bookingAgentId: null, bookingAgentName: `订舱代理-${suffix}`, vesselName: `验收船-${suffix}`, voyageNo: `V${suffix}`, departurePortCode: 'CNSZX', departurePortName: '深圳', destinationPortCode: 'USLAX', destinationPortName: '洛杉矶', placeOfDelivery: 'LAX', serviceTerm: 'CY/CY', freightTerm: 'PREPAID', estimatedSailingAt: '2026-09-01T08:00:00', estimatedArrivalAt: '2026-09-20T08:00:00', billOfLadingNo: `BL-${suffix}`, remark: context.marker, parties: [{ partyType: 'SHIPPER', partyName: `发货方-${suffix}` }, { partyType: 'CONSIGNEE', partyName: `收货方-${suffix}` }, { partyType: 'NOTIFY_PARTY', partyName: `通知方-${suffix}` }], containerRequirements: [{ containerType: '40HQ', containerKind: 'FCL', quantity: 1 }] } } +async function create(context, suffix, key) { return request(context, 'POST', BASE, { expectedVersion: 0, data: draft(context, suffix) }, { 'Idempotency-Key': key }) } +function step(context, name, r, pass, evidence, options = OPTIONS) { context.addStep(MODULE, name, r, pass, evidence, { ...options, deploymentBlocker: options.deploymentBlocker && !pass }) } + +export async function run(context) { + const suffix = context.marker.replace(/\D/g, '').slice(-10) + const list = await request(context, 'GET', `${BASE}?page=1&size=5&keyword=${encodeURIComponent(context.marker)}`) + const page = context.apiSuccess(MODULE, '订舱列表分页与 keyword 查询', list, '', OPTIONS) + context.assert(MODULE, '订舱列表分页结构正确', list, Array.isArray(page?.records) && Number.isFinite(Number(page?.total)), `records=${page?.records?.length ?? 'n/a'}`, OPTIONS) + const statuses = ['DRAFT', 'SUBMITTED', 'CONFIRMED', 'RELEASED', 'FAILED', 'CANCELLED'] + for (const status of statuses) { const r = await request(context, 'GET', `${BASE}?page=1&size=5&status=${status}`); const d = data(r); step(context, `订舱 ${status} 状态筛选`, r, r.http === 200 && codeOf(r) === '200' && Array.isArray(d?.records) && d.records.every((x) => x.status === status), `HTTP=${r.http}, code=${codeOf(r)}, records=${d?.records?.length ?? 'n/a'}`) } + + const key = `${context.marker}-ocean-create` + const create = await create(context, suffix, key) + const detail = data(create); const bookingId = id(detail?.oceanBooking?.id) + context.apiSuccess(MODULE, '创建隔离海运订舱', create, `key=${key}`, OPTIONS) + context.assert(MODULE, '订舱 DTO 字段、DRAFT 和9类关联字段完整', create, Boolean(bookingId) && detail?.oceanBooking?.status === 'DRAFT' && detail?.parties?.length === 3 && detail?.containerRequirements?.length === 1, `bookingId=${bookingId || 'missing'}, parties=${detail?.parties?.length ?? 'n/a'}, requirements=${detail?.containerRequirements?.length ?? 'n/a'}`, OPTIONS) + if (!bookingId) return + context.createdResources.oceanBooking = { id: bookingId, marker: context.marker, finalStatus: 'RELEASED' } + const replay = await create(context, suffix, key) + context.assert(MODULE, '创建 Idempotency-Key 重放返回同一订舱', replay, String(data(replay)?.oceanBooking?.id) === bookingId, `first=${bookingId}, replay=${id(data(replay)?.oceanBooking?.id)}`, OPTIONS) + const path = `${BASE}/${bookingId}` + const initialVersion = Number(detail.oceanBooking.version) + const edited = { ...draft(context, suffix), vesselName: `编辑船-${suffix}`, voyageNo: `EV${suffix}` } + const update = await request(context, 'PUT', path, { expectedVersion: initialVersion, data: edited }, { 'Idempotency-Key': `${context.marker}-ocean-edit` }) + context.apiSuccess(MODULE, '编辑 DRAFT 订舱', update, `version=${initialVersion}`, OPTIONS) + const stale = await request(context, 'PUT', path, { expectedVersion: initialVersion, data: { ...edited, vesselName: `STALE-${suffix}` } }, { 'Idempotency-Key': `${context.marker}-ocean-stale` }) + step(context, '订舱旧版本编辑返回冲突', stale, stale.http === 200 && ['A0409', '409'].includes(codeOf(stale)), `HTTP=${stale.http}, code=${codeOf(stale)}`) + const afterStale = await request(context, 'GET', path); const afterData = data(afterStale) + context.apiSuccess(MODULE, '冲突后回查订舱', afterStale, '', OPTIONS) + context.assert(MODULE, '订舱冲突未部分更新', afterStale, afterData?.oceanBooking?.vesselName === edited.vesselName, `vessel=${afterData?.oceanBooking?.vesselName || 'missing'}`, OPTIONS) + let version = Number(afterData?.oceanBooking?.version) + const action = async (name, endpoint, expected, reason) => { const r = await request(context, 'POST', `${path}/${endpoint}`, { expectedVersion: expected, reason }, { 'Idempotency-Key': `${context.marker}-${endpoint}` }); const d = data(r); context.apiSuccess(MODULE, name, r, `status=${d?.oceanBooking?.status || 'missing'}`, OPTIONS); version = Number(d?.oceanBooking?.version); return d } + await action('提交订舱', 'submit', version, 'marker submit') + await action('确认订舱', 'confirm', version, 'marker confirm') + const changed = await request(context, 'POST', `${path}/change-request`, { expectedVersion: version, vesselName: `改船-${suffix}`, voyageNo: `CV${suffix}`, reason: context.marker }, { 'Idempotency-Key': `${context.marker}-change` }) + context.apiSuccess(MODULE, '申请改船', changed, '', OPTIONS); version = Number(data(changed)?.oceanBooking?.version) + await action('确认改船', 'change-confirm', version, 'confirm change') + await action('放舱', 'release', version, 'release') + const final = await request(context, 'GET', path); const fd = data(final) + context.apiSuccess(MODULE, '回查 RELEASED 订舱详情', final, '', OPTIONS) + context.assert(MODULE, '订舱状态历史、审计操作者和终态完整', final, fd?.oceanBooking?.status === 'RELEASED' && fd.histories?.length >= 5 && fd.histories.every((h) => h.operatorName), `status=${fd?.oceanBooking?.status || 'missing'}, histories=${fd?.histories?.length ?? 'n/a'}`, OPTIONS) + const missingDetail = await request(context, 'GET', `${BASE}/999999999999999999`) + step(context, '不存在订舱详情安全失败', missingDetail, failure(missingDetail), `HTTP=${missingDetail.http}, code=${codeOf(missingDetail)}`) +} diff --git a/scripts/admin-test-env-modules/truck-appointments.mjs b/scripts/admin-test-env-modules/truck-appointments.mjs new file mode 100644 index 000000000..54fff4d20 --- /dev/null +++ b/scripts/admin-test-env-modules/truck-appointments.mjs @@ -0,0 +1,11 @@ +const MODULE = 'truck-appointments' +const OPTIONS = { scenario: 'truck-appointment-lifecycle', deploymentBlocker: true } +const BASE = '/api/platform/v1/truck-appointments' +function id(v) { return v == null ? '' : String(v) } +function codeOf(r) { return r?.body?.code == null ? 'missing' : String(r.body.code) } +function failure(r) { return [400, 401, 403, 404, 409, 503].includes(r?.http) || (r?.http === 200 && codeOf(r) !== '200') } +async function req(context, method, path, body, headers = {}) { try { const response = await fetch(`${context.options.gateway}${path}`, { method, headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}`, ...(body === undefined ? {} : { 'Content-Type': 'application/json; charset=utf-8' }), ...headers }, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(60_000) }); const raw = await response.text(); let payload = null; try { payload = raw ? JSON.parse(raw) : null } catch {} return { method, path, http: response.status, body: payload, raw: payload === null ? raw.slice(0, 500) : null, requestId: payload?.requestId || response.headers.get('x-request-id'), traceId: payload?.traceId || response.headers.get('x-trace-id') } } catch (e) { return { method, path, http: 0, body: null, raw: String(e), requestId: null, traceId: null } } } +function data(r) { return String(r?.body?.code) === '200' ? r.body.data : null } +function payload(context, suffix, version = 0) { return { outboundOrderId: null, oceanBookingId: null, containerBookingId: null, loadingPlanId: null, warehouseId: 193000000000000001, customerId: 10001, customerName: `拖车验收客户-${suffix}`, carrierId: null, carrierName: `承运商-${suffix}`, fleetContact: '车队联系人', fleetPhone: '13800000004', driverName: '司机验收', driverPhone: '13800000005', driverIdCard: null, vehicleNo: `粤B-${suffix.slice(-6)}`, storeName: `门店-${suffix}`, storeContact: '门店联系人', storePhone: '13800000006', storeAddress: '深圳市南山区验收地址', storeCity: '深圳市', storeZipcode: '518000', packageCount: 10, grossWeightKg: 100, volumeCbm: 2.5, palletCount: 1, poNo: `PO-${suffix}`, plannedLoadingAt: '2026-09-02T08:00:00', transitMinutes: 120, payableAmount: 100, receivableAmount: 150, currency: 'RMB', remark: context.marker, expectedVersion: version } } +export async function run(context) { const suffix = context.marker.replace(/\D/g, '').slice(-10); const list = await req(context, 'GET', `${BASE}?page=1&size=5&keyword=${encodeURIComponent(context.marker)}`); const page = context.apiSuccess(MODULE, '拖车预约分页 keyword 查询', list, '', OPTIONS); context.assert(MODULE, '拖车预约分页结构正确', list, Array.isArray(page?.records), `records=${page?.records?.length ?? 'n/a'}`, OPTIONS); for (const status of ['DRAFT','PENDING_DISPATCH','DISPATCHED','PICKUP_IN_PROGRESS','ARRIVED_WAREHOUSE','LOADING','DEPARTED','COMPLETED','RESCHEDULED','EXCEPTION','CANCELLED']) { const r = await req(context, 'GET', `${BASE}?page=1&size=5&status=${status}`); const d = data(r); context.addStep(MODULE, `拖车预约 ${status} 筛选`, r, r.http === 200 && codeOf(r) === '200' && Array.isArray(d?.records) && d.records.every((x) => x.status === status), `HTTP=${r.http}, code=${codeOf(r)}, records=${d?.records?.length ?? 'n/a'}`, OPTIONS) } + const create = await req(context, 'POST', BASE, payload(context, suffix)); const appointment = context.apiSuccess(MODULE, '创建隔离拖车预约', create, '', OPTIONS); const appointmentId = id(appointment?.id); context.assert(MODULE, '拖车预约费用、司机车辆和地址字段完整', create, Boolean(appointmentId) && appointment?.status === 'DRAFT' && appointment?.payableAmount != null && appointment?.receivableAmount != null, `id=${appointmentId || 'missing'}, status=${appointment?.status || 'missing'}`, OPTIONS); if (!appointmentId) return; context.createdResources.truckAppointment = { id: appointmentId, marker: context.marker, finalStatus: 'CANCELLED' }; const path = `${BASE}/${appointmentId}`; let version = Number(appointment.version); const edit = await req(context, 'PUT', path, { ...payload(context, suffix, version), vehicleNo: `粤C-${suffix.slice(-6)}` }); const edited = context.apiSuccess(MODULE, '编辑拖车预约', edit, `version=${version}`, OPTIONS); version = Number(edited?.version); const stale = await req(context, 'PUT', path, { ...payload(context, suffix, 0), vehicleNo: `STALE-${suffix}` }); context.addStep(MODULE, '拖车预约旧版本编辑返回冲突', stale, stale.http === 200 && ['A0409','409'].includes(codeOf(stale)), `HTTP=${stale.http}, code=${codeOf(stale)}`, OPTIONS); const after = await req(context, 'GET', path); const ad = data(after); context.apiSuccess(MODULE, '冲突后回查拖车预约', after, '', OPTIONS); context.assert(MODULE, '拖车预约冲突未部分更新', after, ad?.vehicleNo === edited?.vehicleNo, `vehicleNo=${ad?.vehicleNo || 'missing'}`, OPTIONS); version = Number(ad?.version); const action = async (name, endpoint, expected) => { const r = await req(context, 'POST', `${path}/${endpoint}`, { expectedVersion: expected, reason: context.marker }, { 'Idempotency-Key': `${context.marker}-${endpoint}` }); const d = context.apiSuccess(MODULE, name, r, `status=${d?.status || 'missing'}`, OPTIONS); version = Number(d?.version); return d }; for (const [name, endpoint] of [['提交预约','submit'],['派车','dispatch'],['开始提货','pickup'],['到仓','arrive'],['开始装货','load'],['离场','depart'],['完成','complete']]) await action(name, endpoint, version); const final = await req(context, 'GET', path); const fd = data(final); context.apiSuccess(MODULE, '回查 COMPLETED 拖车预约', final, '', OPTIONS); context.assert(MODULE, '拖车预约状态终态和费用快照正确', final, fd?.status === 'COMPLETED' && fd?.payableAmount != null && fd?.receivableAmount != null, `status=${fd?.status || 'missing'}`, OPTIONS); context.addStep(MODULE, '附件引用安全边界自动化缺口', null, false, '当前 Controller 无附件上传/引用 API,未伪造附件成功', OPTIONS) } diff --git a/warehouse/src/main/java/com/baiyue/warehouse/service/OverseasStorageFeeService.java b/warehouse/src/main/java/com/baiyue/warehouse/service/OverseasStorageFeeService.java index b77adb8c5..b425f9025 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/service/OverseasStorageFeeService.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/service/OverseasStorageFeeService.java @@ -44,7 +44,7 @@ public class OverseasStorageFeeService { public OverseasStorageFeeDtos.View detailByTenant(long tenantId) { OverseasStorageFeeRuleEntity rule = repository.page(new OverseasStorageFeeDtos.PageQuery(1, 1, tenantId, null)) .records().stream().findFirst().orElseThrow(() -> new BizException(ResultCode.NOT_FOUND, "海外仓费用不存在")); - return detail(rule.getId()); + return view(rule, repository.findTiers(List.of(rule.getId()))); } @Transactional diff --git a/warehouse/src/test/java/com/baiyue/warehouse/service/OverseasStorageFeeServiceTest.java b/warehouse/src/test/java/com/baiyue/warehouse/service/OverseasStorageFeeServiceTest.java index 7c12b695d..59b287054 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/service/OverseasStorageFeeServiceTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/service/OverseasStorageFeeServiceTest.java @@ -83,6 +83,19 @@ class OverseasStorageFeeServiceTest { .isEqualTo(new BigDecimal("0.8")); } + @Test + void loadsCustomerDetailFromPagedRuleWithoutSecondRuleLookup() { + OverseasStorageFeeRuleEntity rule = rule(); + OverseasStorageFeeDtos.PageQuery query = new OverseasStorageFeeDtos.PageQuery(1, 1, 10001L, null); + when(repository.page(query)).thenReturn(new PageResult<>(List.of(rule), 1, 1, 1, 1)); + when(repository.findTiers(List.of(10L))).thenReturn(List.of()); + + var result = service.detailByTenant(10001L); + + assertThat(result.customerId()).isEqualTo(10001L); + verify(repository, never()).find(10L); + } + @Test void detectsStaleUpdate() { OverseasStorageFeeRuleEntity rule = rule();