From 161a2ecb7ff25f78802605b6b44242f8fe79ae0b Mon Sep 17 00:00:00 2001 From: Nick Zeng Date: Fri, 13 Mar 2026 13:50:56 +0800 Subject: [PATCH] add refund items --- .../Api/V1/RawRefundItemController.php | 149 + .../Api/V1/RefundItemController.php | 159 + backend/app/Model/RefundItem.php | 33 + backend/docs/openapi.yaml | 3529 +++++++++++++++++ .../Refund/RefundItemControllerTest.php | 478 +++ 5 files changed, 4348 insertions(+) create mode 100644 backend/app/Controller/Api/V1/RawRefundItemController.php create mode 100644 backend/app/Controller/Api/V1/RefundItemController.php create mode 100644 backend/docs/openapi.yaml create mode 100644 backend/test/Cases/Integration/Refund/RefundItemControllerTest.php diff --git a/backend/app/Controller/Api/V1/RawRefundItemController.php b/backend/app/Controller/Api/V1/RawRefundItemController.php new file mode 100644 index 0000000..71e50e5 --- /dev/null +++ b/backend/app/Controller/Api/V1/RawRefundItemController.php @@ -0,0 +1,149 @@ + []]], + tags: ['Refund Items (Raw)'], + parameters: [ + new OA\Parameter(name: 'page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 1)), + new OA\Parameter(name: 'per_page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15, maximum: 100)), + new OA\Parameter(name: 'company_id', in: 'query', required: false, description: '公司 ID 精确筛选', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'platform_id', in: 'query', required: false, description: '平台 ID 精确筛选', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'store_id', in: 'query', required: false, description: '店铺 ID 精确筛选', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'refund_id', in: 'query', required: false, description: '父退款 ID 精确筛选', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'refund_status_id', in: 'query', required: false, description: '退款状态 ID 精确筛选', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'refund_type_id', in: 'query', required: false, description: '退款类型 ID 精确筛选', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'platform_refund_id', in: 'query', required: false, description: '平台退款子项 ID 精确搜索', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'platform_order_id', in: 'query', required: false, description: '关联平台订单 ID 筛选', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'created_date_from', in: 'query', required: false, description: '创建时间起始(含)', schema: new OA\Schema(type: 'string', format: 'date')), + new OA\Parameter(name: 'created_date_to', in: 'query', required: false, description: '创建时间截止(含)', schema: new OA\Schema(type: 'string', format: 'date')), + ], + responses: [ + new OA\Response( + response: 200, + description: '获取成功', + content: new OA\JsonContent(properties: [ + new OA\Property(property: 'code', type: 'integer', example: 0), + new OA\Property(property: 'message', type: 'string', example: '获取成功'), + new OA\Property(property: 'data', properties: [ + new OA\Property(property: 'items', type: 'array', items: new OA\Items(properties: [ + new OA\Property(property: 'id', type: 'integer'), + new OA\Property(property: 'platform_refund_id', type: 'string'), + new OA\Property(property: 'platform_parent_refund_id', type: 'string'), + new OA\Property(property: 'platform_order_id', type: 'string'), + new OA\Property(property: 'store_id', type: 'integer'), + new OA\Property(property: 'company_id', type: 'integer'), + new OA\Property(property: 'platform_id', type: 'integer'), + new OA\Property(property: 'refund_status_id', type: 'integer'), + new OA\Property(property: 'created_date', type: 'string', format: 'date-time'), + new OA\Property(property: 'updated_at', type: 'string', format: 'date-time'), + ], type: 'object')), + new OA\Property(property: 'total', type: 'integer'), + new OA\Property(property: 'page', type: 'integer'), + new OA\Property(property: 'per_page', type: 'integer'), + ], type: 'object'), + ]) + ), + new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + new OA\Response(response: 403, description: '无权限', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + ] + )] + #[RequestMapping(path: "", methods: "GET")] + public function index(): ResponseInterface|array + { + return parent::index(); + } + + /** + * 退款项详情(Raw) + */ + #[OA\Get( + path: '/raw/refund-items/{id}', + summary: '退款项详情(Raw)', + description: '获取退款项原始数据详情。返回关键标识 + 完整 raw + ext。', + security: [['bearerAuth' => []]], + tags: ['Refund Items (Raw)'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: '退款项 ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: 200, + description: '获取成功', + content: new OA\JsonContent(properties: [ + new OA\Property(property: 'code', type: 'integer', example: 0), + new OA\Property(property: 'message', type: 'string', example: '获取成功'), + new OA\Property(property: 'data', properties: [ + new OA\Property(property: 'id', type: 'integer'), + new OA\Property(property: 'platform_refund_id', type: 'string'), + new OA\Property(property: 'platform_parent_refund_id', type: 'string'), + new OA\Property(property: 'platform_order_id', type: 'string'), + new OA\Property(property: 'store_id', type: 'integer'), + new OA\Property(property: 'company_id', type: 'integer'), + new OA\Property(property: 'platform_id', type: 'integer'), + new OA\Property(property: 'refund_status_id', type: 'integer'), + new OA\Property(property: 'raw', type: 'object', description: '平台原始子项数据'), + new OA\Property(property: 'ext', type: 'object', nullable: true), + new OA\Property(property: 'created_date', type: 'string', format: 'date-time'), + ], type: 'object'), + ]) + ), + new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + new OA\Response(response: 403, description: '无权限', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + new OA\Response(response: 404, description: '数据不存在', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + ] + )] + #[RequestMapping(path: "{id}", methods: "GET")] + public function show(int $id): ResponseInterface|array + { + return parent::show($id); + } +} diff --git a/backend/app/Controller/Api/V1/RefundItemController.php b/backend/app/Controller/Api/V1/RefundItemController.php new file mode 100644 index 0000000..77ee427 --- /dev/null +++ b/backend/app/Controller/Api/V1/RefundItemController.php @@ -0,0 +1,159 @@ + 'exact', + 'platform_id' => 'exact', + 'store_id' => 'exact', + 'refund_id' => 'exact', + 'refund_status_id' => 'exact', + 'refund_type_id' => 'exact', + 'platform_refund_id' => 'exact', + 'platform_order_id' => 'exact', + 'created_date_from' => 'date_from', + 'created_date_to' => 'date_to', + ]; + } + + protected function getDefaultSort(): string + { + return 'created_date'; + } + + /** + * 退款项列表 + */ + #[OA\Get( + path: '/refund-items', + summary: '退款项列表', + description: '获取退款项列表,支持分页、按退款单/退款状态/类型/时间范围筛选。返回业务字段,不含 raw。', + security: [['bearerAuth' => []]], + tags: ['Refund Items'], + parameters: [ + new OA\Parameter(name: 'page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 1)), + new OA\Parameter(name: 'per_page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15, maximum: 100)), + new OA\Parameter(name: 'company_id', in: 'query', required: false, description: '公司 ID 精确筛选', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'platform_id', in: 'query', required: false, description: '平台 ID 精确筛选', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'store_id', in: 'query', required: false, description: '店铺 ID 精确筛选', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'refund_id', in: 'query', required: false, description: '父退款 ID 精确筛选', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'refund_status_id', in: 'query', required: false, description: '退款状态 ID 精确筛选', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'refund_type_id', in: 'query', required: false, description: '退款类型 ID 精确筛选(1=仅退款 2=退货退款 3=补偿退款)', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'platform_refund_id', in: 'query', required: false, description: '平台退款子项 ID 精确搜索', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'platform_order_id', in: 'query', required: false, description: '关联平台订单 ID 筛选', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'created_date_from', in: 'query', required: false, description: '创建时间起始(含)', schema: new OA\Schema(type: 'string', format: 'date', example: '2026-01-01')), + new OA\Parameter(name: 'created_date_to', in: 'query', required: false, description: '创建时间截止(含)', schema: new OA\Schema(type: 'string', format: 'date', example: '2026-12-31')), + ], + responses: [ + new OA\Response( + response: 200, + description: '获取成功', + content: new OA\JsonContent(properties: [ + new OA\Property(property: 'code', type: 'integer', example: 0), + new OA\Property(property: 'message', type: 'string', example: '获取成功'), + new OA\Property(property: 'data', properties: [ + new OA\Property(property: 'items', type: 'array', items: new OA\Items(ref: '#/components/schemas/RefundItem')), + new OA\Property(property: 'total', type: 'integer', example: 100), + new OA\Property(property: 'page', type: 'integer', example: 1), + new OA\Property(property: 'per_page', type: 'integer', example: 15), + ], type: 'object'), + ]) + ), + new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + new OA\Response(response: 403, description: '无权限', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + ] + )] + #[RequestMapping(path: "", methods: "GET")] + public function index(): ResponseInterface|array + { + return parent::index(); + } + + /** + * 退款项详情 + */ + #[OA\Get( + path: '/refund-items/{id}', + summary: '退款项详情', + description: '获取退款项详情,返回所有业务字段和 ext,不含 raw。', + security: [['bearerAuth' => []]], + tags: ['Refund Items'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: '退款项 ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: 200, + description: '获取成功', + content: new OA\JsonContent(properties: [ + new OA\Property(property: 'code', type: 'integer', example: 0), + new OA\Property(property: 'message', type: 'string', example: '获取成功'), + new OA\Property(property: 'data', ref: '#/components/schemas/RefundItem'), + ]) + ), + new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + new OA\Response(response: 403, description: '无权限', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + new OA\Response(response: 404, description: '数据不存在', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + ] + )] + #[RequestMapping(path: "{id}", methods: "GET")] + public function show(int $id): ResponseInterface|array + { + return parent::show($id); + } +} diff --git a/backend/app/Model/RefundItem.php b/backend/app/Model/RefundItem.php index 5d362d4..a2cb498 100644 --- a/backend/app/Model/RefundItem.php +++ b/backend/app/Model/RefundItem.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Model; use Hyperf\DbConnection\Model\Model; +use OpenApi\Attributes as OA; /** * @property int $id 主键 @@ -35,6 +36,38 @@ use Hyperf\DbConnection\Model\Model; * @property \Carbon\Carbon $updated_at * @mixin \App_Model_RefundItem */ +#[OA\Schema( + schema: 'RefundItem', + type: 'object', + properties: [ + new OA\Property(property: 'id', type: 'integer', example: 1), + new OA\Property(property: 'company_id', type: 'integer', example: 1), + new OA\Property(property: 'platform_id', type: 'integer', example: 2), + new OA\Property(property: 'store_id', type: 'integer', example: 100), + new OA\Property(property: 'refund_id', type: 'integer', example: 50), + new OA\Property(property: 'platform_parent_refund_id', type: 'string', nullable: true, example: 'PRF-001'), + new OA\Property(property: 'platform_refund_id', type: 'string', example: 'RF-ITEM-001'), + new OA\Property(property: 'refund_status_id', type: 'integer', example: 1), + new OA\Property(property: 'refund_type_id', type: 'integer', example: 2), + new OA\Property(property: 'reason', type: 'string', nullable: true, example: '商品质量问题'), + new OA\Property(property: 'currency', type: 'string', example: 'CNY'), + new OA\Property(property: 'buyer_user_id', type: 'string', nullable: true, example: 'buyer_123'), + new OA\Property(property: 'platform_order_id', type: 'string', example: 'ORD-20260101-001'), + new OA\Property(property: 'platform_sub_order_id', type: 'string', nullable: true, example: 'SUB-001'), + new OA\Property(property: 'platform_product_id', type: 'string', nullable: true, example: 'PROD-001'), + new OA\Property(property: 'quantity', type: 'integer', example: 1), + new OA\Property(property: 'refund_amount', type: 'number', format: 'decimal', example: 99.99), + new OA\Property(property: 'raw', type: 'object', nullable: true, description: '平台原始子项数据'), + new OA\Property(property: 'ext', type: 'object', nullable: true, description: '扩展字段'), + new OA\Property(property: 'order_created_date', type: 'string', format: 'date-time', nullable: true), + new OA\Property(property: 'order_paid_date', type: 'string', format: 'date-time', nullable: true), + new OA\Property(property: 'created_date', type: 'string', format: 'date-time'), + new OA\Property(property: 'updated_date', type: 'string', format: 'date-time', nullable: true), + new OA\Property(property: 'completed_date', type: 'string', format: 'date-time', nullable: true), + new OA\Property(property: 'created_at', type: 'string', format: 'date-time'), + new OA\Property(property: 'updated_at', type: 'string', format: 'date-time'), + ] +)] class RefundItem extends Model { /** diff --git a/backend/docs/openapi.yaml b/backend/docs/openapi.yaml new file mode 100644 index 0000000..09e3444 --- /dev/null +++ b/backend/docs/openapi.yaml @@ -0,0 +1,3529 @@ +openapi: 3.0.0 +info: + title: 'Datahub API' + description: 'Datahub API documentation' + version: 1.0.0 +servers: + - + url: /api/v1 + description: 'API v1' +paths: + /orders: + get: + tags: + - Orders + summary: 订单列表 + description: '获取订单列表,支持分页、多维度筛选和时间范围筛选。返回业务字段,不含 raw/hash。' + operationId: 7e493d0464138129e7e1f0e7ebee178b + parameters: + - + name: page + in: query + required: false + schema: + type: integer + default: 1 + - + name: per_page + in: query + required: false + schema: + type: integer + default: 15 + maximum: 100 + - + name: company_id + in: query + description: '公司 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_id + in: query + description: '平台 ID 精确筛选' + required: false + schema: + type: integer + - + name: store_id + in: query + description: '店铺 ID 精确筛选' + required: false + schema: + type: integer + - + name: order_status_id + in: query + description: '订单状态 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_order_id + in: query + description: '平台订单 ID 精确搜索' + required: false + schema: + type: string + - + name: created_date_from + in: query + description: 创建时间起始(含) + required: false + schema: + type: string + format: date + example: '2026-01-01' + - + name: created_date_to + in: query + description: 创建时间截止(含) + required: false + schema: + type: string + format: date + example: '2026-12-31' + - + name: paid_date_from + in: query + description: 付款时间起始(含) + required: false + schema: + type: string + format: date + example: '2026-01-01' + - + name: paid_date_to + in: query + description: 付款时间截止(含) + required: false + schema: + type: string + format: date + example: '2026-12-31' + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { items: { type: array, items: { $ref: '#/components/schemas/Order' } }, total: { type: integer, example: 100 }, page: { type: integer, example: 1 }, per_page: { type: integer, example: 15 } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/orders/{id}': + get: + tags: + - Orders + summary: 订单详情 + description: '获取订单详情,返回所有业务字段、ext 和关联的 order_items,不含 raw/hash。' + operationId: aff126ce9a5cef811fea88ceba395fdc + parameters: + - + name: id + in: path + description: '订单 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { type: object, allOf: [{ $ref: '#/components/schemas/Order' }, { properties: { order_items: { description: 关联订单子项, type: array, items: { type: object } } }, type: object }] } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 数据不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /order-items: + get: + tags: + - 'Order Items' + summary: 订单项列表 + description: '获取订单项列表,支持分页和多维度筛选。OrderItem 无 raw/hash 字段,仅提供 Normal 接口。' + operationId: 894b5ea5fad8ecc3159e628333252260 + parameters: + - + name: page + in: query + required: false + schema: + type: integer + default: 1 + - + name: per_page + in: query + required: false + schema: + type: integer + default: 15 + maximum: 100 + - + name: company_id + in: query + description: '公司 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_id + in: query + description: '平台 ID 精确筛选' + required: false + schema: + type: integer + - + name: store_id + in: query + description: '店铺 ID 精确筛选' + required: false + schema: + type: integer + - + name: order_id + in: query + description: '内部订单 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_order_id + in: query + description: '平台订单 ID 精确筛选' + required: false + schema: + type: string + - + name: platform_product_id + in: query + description: '平台商品 ID 精确筛选' + required: false + schema: + type: string + - + name: product_sku + in: query + description: 'SKU 编码精确筛选' + required: false + schema: + type: string + - + name: created_date_from + in: query + description: 创建时间起始(含) + required: false + schema: + type: string + format: date + example: '2026-01-01' + - + name: created_date_to + in: query + description: 创建时间截止(含) + required: false + schema: + type: string + format: date + example: '2026-12-31' + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { items: { type: array, items: { $ref: '#/components/schemas/OrderItem' } }, total: { type: integer, example: 100 }, page: { type: integer, example: 1 }, per_page: { type: integer, example: 15 } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/order-items/{id}': + get: + tags: + - 'Order Items' + summary: 订单项详情 + description: 获取订单项详情,返回完整字段和关联的父订单摘要信息。 + operationId: a808f2d76861f46925baef81c52da14f + parameters: + - + name: id + in: path + description: '订单项 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { type: object, allOf: [{ $ref: '#/components/schemas/OrderItem' }, { properties: { parent_order: { description: 父订单摘要信息, type: object, nullable: true } }, type: object }] } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 数据不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /products: + get: + tags: + - Products + summary: 产品列表 + description: '获取产品列表,支持分页、多维度筛选。返回业务字段,不含 raw/hash。' + operationId: 934e471439896276d2897b4b5132c077 + parameters: + - + name: page + in: query + required: false + schema: + type: integer + default: 1 + - + name: per_page + in: query + required: false + schema: + type: integer + default: 15 + maximum: 100 + - + name: company_id + in: query + description: '公司 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_id + in: query + description: '平台 ID 精确筛选' + required: false + schema: + type: integer + - + name: store_id + in: query + description: '店铺 ID 精确筛选' + required: false + schema: + type: integer + - + name: status_id + in: query + description: '产品状态 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_item_id + in: query + description: '平台商品 ID 精确搜索' + required: false + schema: + type: string + - + name: name + in: query + description: 商品名称模糊搜索 + required: false + schema: + type: string + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { items: { type: array, items: { $ref: '#/components/schemas/Product' } }, total: { type: integer, example: 100 }, page: { type: integer, example: 1 }, per_page: { type: integer, example: 15 } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/products/{id}': + get: + tags: + - Products + summary: 产品详情 + description: '获取产品详情,返回所有业务字段和 ext,不含 raw/hash。' + operationId: 7d7422e8293b23608368a4a9000f8096 + parameters: + - + name: id + in: path + description: '产品 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { $ref: '#/components/schemas/Product' } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 数据不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /raw/orders: + get: + tags: + - 'Orders (Raw)' + summary: 订单列表(Raw) + description: '获取订单原始数据列表。返回关键标识 + hash,不含 raw 字段本身(太大)。筛选参数与 Normal 接口一致。' + operationId: 562f4be5afd7463d1c9c9c653bdd0c35 + parameters: + - + name: page + in: query + required: false + schema: + type: integer + default: 1 + - + name: per_page + in: query + required: false + schema: + type: integer + default: 15 + maximum: 100 + - + name: company_id + in: query + description: '公司 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_id + in: query + description: '平台 ID 精确筛选' + required: false + schema: + type: integer + - + name: store_id + in: query + description: '店铺 ID 精确筛选' + required: false + schema: + type: integer + - + name: order_status_id + in: query + description: '订单状态 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_order_id + in: query + description: '平台订单 ID 精确搜索' + required: false + schema: + type: string + - + name: created_date_from + in: query + description: 创建时间起始(含) + required: false + schema: + type: string + format: date + - + name: created_date_to + in: query + description: 创建时间截止(含) + required: false + schema: + type: string + format: date + - + name: paid_date_from + in: query + description: 付款时间起始(含) + required: false + schema: + type: string + format: date + - + name: paid_date_to + in: query + description: 付款时间截止(含) + required: false + schema: + type: string + format: date + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { items: { type: array, items: { properties: { id: { type: integer }, platform_order_id: { type: string }, store_id: { type: integer }, company_id: { type: integer }, platform_id: { type: integer }, order_status_id: { type: integer }, hash: { type: string }, created_date: { type: string, format: date-time }, updated_at: { type: string, format: date-time } }, type: object } }, total: { type: integer }, page: { type: integer }, per_page: { type: integer } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/raw/orders/{id}': + get: + tags: + - 'Orders (Raw)' + summary: 订单详情(Raw) + description: '获取订单原始数据详情。返回关键标识 + 完整 raw + hash + ext。' + operationId: 6c9b613233d2ef2a708d51523149d84b + parameters: + - + name: id + in: path + description: '订单 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { id: { type: integer }, platform_order_id: { type: string }, store_id: { type: integer }, company_id: { type: integer }, platform_id: { type: integer }, order_status_id: { type: integer }, raw: { description: 平台原始数据, type: object }, hash: { type: string }, ext: { type: object, nullable: true }, created_date: { type: string, format: date-time } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 数据不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /raw/products: + get: + tags: + - 'Products (Raw)' + summary: 产品列表(Raw) + description: '获取产品原始数据列表。返回关键标识 + hash,不含 raw 字段本身(太大)。筛选参数与 Normal 接口一致。' + operationId: 770fd6e9caf314efbc05aa0277c7e811 + parameters: + - + name: page + in: query + required: false + schema: + type: integer + default: 1 + - + name: per_page + in: query + required: false + schema: + type: integer + default: 15 + maximum: 100 + - + name: company_id + in: query + description: '公司 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_id + in: query + description: '平台 ID 精确筛选' + required: false + schema: + type: integer + - + name: store_id + in: query + description: '店铺 ID 精确筛选' + required: false + schema: + type: integer + - + name: status_id + in: query + description: '产品状态 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_item_id + in: query + description: '平台商品 ID 精确搜索' + required: false + schema: + type: string + - + name: name + in: query + description: 商品名称模糊搜索 + required: false + schema: + type: string + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { items: { type: array, items: { properties: { id: { type: integer }, platform_item_id: { type: string }, platform_model_id: { type: string, nullable: true }, name: { type: string, nullable: true }, store_id: { type: integer }, company_id: { type: integer }, platform_id: { type: integer }, hash: { type: string }, updated_date: { type: string, format: date-time, nullable: true }, updated_at: { type: string, format: date-time } }, type: object } }, total: { type: integer }, page: { type: integer }, per_page: { type: integer } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/raw/products/{id}': + get: + tags: + - 'Products (Raw)' + summary: 产品详情(Raw) + description: '获取产品原始数据详情。返回关键标识 + 完整 raw + hash + ext。' + operationId: e0ba90775ff4ceb4fe459b8272a63624 + parameters: + - + name: id + in: path + description: '产品 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { id: { type: integer }, platform_item_id: { type: string }, platform_model_id: { type: string, nullable: true }, name: { type: string, nullable: true }, store_id: { type: integer }, company_id: { type: integer }, platform_id: { type: integer }, raw: { description: 平台原始数据, type: object }, hash: { type: string }, ext: { type: object, nullable: true }, created_date: { type: string, format: date-time, nullable: true }, updated_date: { type: string, format: date-time, nullable: true } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 数据不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /raw/refunds: + get: + tags: + - 'Refunds (Raw)' + summary: 退款列表(Raw) + description: '获取退款原始数据列表。返回关键标识 + hash,不含 raw 字段本身(太大)。筛选参数与 Normal 接口一致。' + operationId: 04e3c7e28df6496b975c24618933c6dd + parameters: + - + name: page + in: query + required: false + schema: + type: integer + default: 1 + - + name: per_page + in: query + required: false + schema: + type: integer + default: 15 + maximum: 100 + - + name: company_id + in: query + description: '公司 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_id + in: query + description: '平台 ID 精确筛选' + required: false + schema: + type: integer + - + name: store_id + in: query + description: '店铺 ID 精确筛选' + required: false + schema: + type: integer + - + name: refund_status_id + in: query + description: '退款状态 ID 精确筛选' + required: false + schema: + type: integer + - + name: refund_type_id + in: query + description: '退款类型 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_refund_id + in: query + description: '平台退款 ID 精确搜索' + required: false + schema: + type: string + - + name: platform_order_id + in: query + description: '关联平台订单 ID 筛选' + required: false + schema: + type: string + - + name: created_date_from + in: query + description: 创建时间起始(含) + required: false + schema: + type: string + format: date + - + name: created_date_to + in: query + description: 创建时间截止(含) + required: false + schema: + type: string + format: date + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { items: { type: array, items: { properties: { id: { type: integer }, platform_refund_id: { type: string }, platform_order_id: { type: string }, store_id: { type: integer }, company_id: { type: integer }, platform_id: { type: integer }, refund_status_id: { type: integer }, hash: { type: string }, created_date: { type: string, format: date-time }, updated_at: { type: string, format: date-time } }, type: object } }, total: { type: integer }, page: { type: integer }, per_page: { type: integer } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/raw/refunds/{id}': + get: + tags: + - 'Refunds (Raw)' + summary: 退款详情(Raw) + description: '获取退款原始数据详情。返回关键标识 + 完整 raw + hash + ext。' + operationId: 884b7753642515815a193abb0cae88c9 + parameters: + - + name: id + in: path + description: '退款 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { id: { type: integer }, platform_refund_id: { type: string }, platform_order_id: { type: string }, store_id: { type: integer }, company_id: { type: integer }, platform_id: { type: integer }, refund_status_id: { type: integer }, raw: { description: 平台原始数据, type: object }, hash: { type: string }, ext: { type: object, nullable: true }, created_date: { type: string, format: date-time } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 数据不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /raw/refund-items: + get: + tags: + - 'Refund Items (Raw)' + summary: 退款项列表(Raw) + description: '获取退款项原始数据列表。返回关键标识,不含 raw 字段本身(太大)。筛选参数与 Normal 接口一致。' + operationId: b1af2757230f49948c934063da9d627a + parameters: + - + name: page + in: query + required: false + schema: + type: integer + default: 1 + - + name: per_page + in: query + required: false + schema: + type: integer + default: 15 + maximum: 100 + - + name: company_id + in: query + description: '公司 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_id + in: query + description: '平台 ID 精确筛选' + required: false + schema: + type: integer + - + name: store_id + in: query + description: '店铺 ID 精确筛选' + required: false + schema: + type: integer + - + name: refund_id + in: query + description: '父退款 ID 精确筛选' + required: false + schema: + type: integer + - + name: refund_status_id + in: query + description: '退款状态 ID 精确筛选' + required: false + schema: + type: integer + - + name: refund_type_id + in: query + description: '退款类型 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_refund_id + in: query + description: '平台退款子项 ID 精确搜索' + required: false + schema: + type: string + - + name: platform_order_id + in: query + description: '关联平台订单 ID 筛选' + required: false + schema: + type: string + - + name: created_date_from + in: query + description: 创建时间起始(含) + required: false + schema: + type: string + format: date + - + name: created_date_to + in: query + description: 创建时间截止(含) + required: false + schema: + type: string + format: date + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { items: { type: array, items: { properties: { id: { type: integer }, platform_refund_id: { type: string }, platform_parent_refund_id: { type: string }, platform_order_id: { type: string }, store_id: { type: integer }, company_id: { type: integer }, platform_id: { type: integer }, refund_status_id: { type: integer }, created_date: { type: string, format: date-time }, updated_at: { type: string, format: date-time } }, type: object } }, total: { type: integer }, page: { type: integer }, per_page: { type: integer } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/raw/refund-items/{id}': + get: + tags: + - 'Refund Items (Raw)' + summary: 退款项详情(Raw) + description: '获取退款项原始数据详情。返回关键标识 + 完整 raw + ext。' + operationId: 6a82a9938eef667456c5a8948b984eb3 + parameters: + - + name: id + in: path + description: '退款项 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { id: { type: integer }, platform_refund_id: { type: string }, platform_parent_refund_id: { type: string }, platform_order_id: { type: string }, store_id: { type: integer }, company_id: { type: integer }, platform_id: { type: integer }, refund_status_id: { type: integer }, raw: { description: 平台原始子项数据, type: object }, ext: { type: object, nullable: true }, created_date: { type: string, format: date-time } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 数据不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /refunds: + get: + tags: + - Refunds + summary: 退款列表 + description: '获取退款列表,支持分页、按退款状态/类型/时间范围筛选。返回业务字段,不含 raw/hash。' + operationId: bd68a8b63be96fc4bc13a7f914db8e6f + parameters: + - + name: page + in: query + required: false + schema: + type: integer + default: 1 + - + name: per_page + in: query + required: false + schema: + type: integer + default: 15 + maximum: 100 + - + name: company_id + in: query + description: '公司 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_id + in: query + description: '平台 ID 精确筛选' + required: false + schema: + type: integer + - + name: store_id + in: query + description: '店铺 ID 精确筛选' + required: false + schema: + type: integer + - + name: refund_status_id + in: query + description: '退款状态 ID 精确筛选' + required: false + schema: + type: integer + - + name: refund_type_id + in: query + description: '退款类型 ID 精确筛选(1=未发货前退款 2=退货退款 3=退货后部分退款 4=无须退货退款 5=闪电退款)' + required: false + schema: + type: integer + - + name: platform_refund_id + in: query + description: '平台退款 ID 精确搜索' + required: false + schema: + type: string + - + name: platform_order_id + in: query + description: '关联平台订单 ID 筛选' + required: false + schema: + type: string + - + name: created_date_from + in: query + description: 创建时间起始(含) + required: false + schema: + type: string + format: date + example: '2026-01-01' + - + name: created_date_to + in: query + description: 创建时间截止(含) + required: false + schema: + type: string + format: date + example: '2026-12-31' + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { items: { type: array, items: { $ref: '#/components/schemas/Refund' } }, total: { type: integer, example: 100 }, page: { type: integer, example: 1 }, per_page: { type: integer, example: 15 } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/refunds/{id}': + get: + tags: + - Refunds + summary: 退款详情 + description: '获取退款详情,返回所有业务字段和 ext,不含 raw/hash。' + operationId: 6009f4876a61b01d12a288fcfe78444c + parameters: + - + name: id + in: path + description: '退款 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { $ref: '#/components/schemas/Refund' } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 数据不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /refund-items: + get: + tags: + - 'Refund Items' + summary: 退款项列表 + description: '获取退款项列表,支持分页、按退款单/退款状态/类型/时间范围筛选。返回业务字段,不含 raw。' + operationId: ad798f6022c7e5b289827884ef582bc2 + parameters: + - + name: page + in: query + required: false + schema: + type: integer + default: 1 + - + name: per_page + in: query + required: false + schema: + type: integer + default: 15 + maximum: 100 + - + name: company_id + in: query + description: '公司 ID 精确筛选' + required: false + schema: + type: integer + - + name: platform_id + in: query + description: '平台 ID 精确筛选' + required: false + schema: + type: integer + - + name: store_id + in: query + description: '店铺 ID 精确筛选' + required: false + schema: + type: integer + - + name: refund_id + in: query + description: '父退款 ID 精确筛选' + required: false + schema: + type: integer + - + name: refund_status_id + in: query + description: '退款状态 ID 精确筛选' + required: false + schema: + type: integer + - + name: refund_type_id + in: query + description: '退款类型 ID 精确筛选(1=仅退款 2=退货退款 3=补偿退款)' + required: false + schema: + type: integer + - + name: platform_refund_id + in: query + description: '平台退款子项 ID 精确搜索' + required: false + schema: + type: string + - + name: platform_order_id + in: query + description: '关联平台订单 ID 筛选' + required: false + schema: + type: string + - + name: created_date_from + in: query + description: 创建时间起始(含) + required: false + schema: + type: string + format: date + example: '2026-01-01' + - + name: created_date_to + in: query + description: 创建时间截止(含) + required: false + schema: + type: string + format: date + example: '2026-12-31' + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { items: { type: array, items: { $ref: '#/components/schemas/RefundItem' } }, total: { type: integer, example: 100 }, page: { type: integer, example: 1 }, per_page: { type: integer, example: 15 } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/refund-items/{id}': + get: + tags: + - 'Refund Items' + summary: 退款项详情 + description: '获取退款项详情,返回所有业务字段和 ext,不含 raw。' + operationId: de6cf91ff904e528cba6f1b43f829a2c + parameters: + - + name: id + in: path + description: '退款项 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { $ref: '#/components/schemas/RefundItem' } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 数据不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /me/api-keys: + get: + tags: + - ApiKeys + summary: 'API Key 列表' + description: '列出当前用户的所有 API Keys' + operationId: c01c1650a1944005fe5b51f72bacbbd4 + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { type: array, items: { properties: { id: { type: integer }, name: { type: string }, key_prefix: { type: string }, last_used_at: { type: string, format: date-time, nullable: true }, expires_at: { type: string, format: date-time, nullable: true }, enabled: { type: boolean }, created_at: { type: string, format: date-time } }, type: object } } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + post: + tags: + - ApiKeys + summary: '生成 API Key' + description: '生成新的 API Key,需用户已启用 api_key_enabled。明文仅在生成时返回一次' + operationId: 12fd3adae0626962960533c85569d1b1 + requestBody: + required: true + content: + application/json: + schema: + required: + - name + properties: + name: + type: string + example: 'Production Key' + maxLength: 100 + expires_at: + description: 过期时间,不传则永不过期 + type: string + format: date-time + nullable: true + type: object + responses: + '200': + description: 生成成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 生成成功 } + data: { properties: { plain_key: { description: '明文 Key,仅此次可见', type: string }, api_key: { properties: { id: { type: integer }, name: { type: string }, key_prefix: { type: string }, expires_at: { type: string, format: date-time, nullable: true }, enabled: { type: boolean }, created_at: { type: string, format: date-time } }, type: object } }, type: object } + type: object + '400': + description: 参数校验失败 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: '未启用 API Key 功能' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/me/api-keys/{id}': + delete: + tags: + - ApiKeys + summary: '删除 API Key' + description: '删除 API Key' + operationId: 7356615676c0ac3874f8657b49154931 + parameters: + - + name: id + in: path + description: 'API Key ID' + required: true + schema: + type: integer + responses: + '200': + description: 删除成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 删除成功 } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 'API Key 不存在' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /register: + post: + tags: + - Auth + summary: 用户注册 + description: 注册新用户,需提供用户名、密码和邮箱 + operationId: b099deca54ae9ddc3ee7e2261b6fc125 + requestBody: + required: true + content: + application/json: + schema: + required: + - username + - password + - email + properties: + username: + type: string + example: new_user + maxLength: 20 + minLength: 3 + password: + type: string + example: Pass_1234 + maxLength: 32 + minLength: 6 + email: + type: string + format: email + example: user@example.com + maxLength: 100 + type: object + responses: + '200': + description: 注册成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 注册成功 } + data: { properties: { id: { type: integer, example: 1 }, username: { type: string, example: new_user }, email: { type: string, example: user@example.com } }, type: object } + type: object + '400': + description: 参数校验失败或唯一性冲突 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /login: + post: + tags: + - Auth + summary: 用户登录 + description: '使用用户名和密码登录,返回 access_token 和 refresh_token' + operationId: 383bcb1269d6dcce4609dc1f5d3ef129 + requestBody: + required: true + content: + application/json: + schema: + required: + - username + - password + properties: + username: + type: string + example: admin + password: + type: string + example: Pass_1234 + type: object + responses: + '200': + description: 登录成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 登录成功 } + data: { properties: { access_token: { type: string }, refresh_token: { type: string }, token_type: { type: string, example: Bearer }, expires_in: { type: integer, example: 7200 }, user: { properties: { id: { type: integer }, username: { type: string }, email: { type: string } }, type: object } }, type: object } + type: object + '400': + description: 参数校验失败 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 用户名或密码错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 账号已被禁用 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /refresh: + get: + tags: + - Auth + summary: '刷新 Access Token' + description: '使用 refresh_token 获取新的 access_token,同时轮换 refresh_token' + operationId: fc18486b361cc4791acbafd8a2f25fff + parameters: + - + name: refresh_token + in: query + description: 'Refresh Token' + required: true + schema: + type: string + responses: + '200': + description: 'Token 刷新成功' + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 'Token 刷新成功' } + data: { properties: { access_token: { type: string }, refresh_token: { type: string }, token_type: { type: string, example: Bearer }, expires_in: { type: integer, example: 7200 } }, type: object } + type: object + '400': + description: '缺少 refresh_token 参数' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 'refresh_token 无效或已过期' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: 账号已被禁用 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /me: + get: + tags: + - Auth + summary: 获取当前用户信息 + description: 获取当前用户信息 + operationId: f03c8d46af839b7dbd0b659647cab574 + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { id: { type: integer, example: 1 }, username: { type: string, example: admin }, email: { type: string, example: admin@example.com }, status: { type: integer, example: 1 }, ext: { type: object, nullable: true }, created_at: { type: string, format: date-time } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /me/profile: + put: + tags: + - Auth + summary: 更新个人信息 + description: '当前用户更新自己的 email 和 ext 字段' + operationId: 01c6d8f425109cd3dfff51d1d69cd55c + requestBody: + required: true + content: + application/json: + schema: + properties: + email: + type: string + format: email + example: new@example.com + maxLength: 100 + ext: + type: object + example: { nickname: user } + nullable: true + type: object + responses: + '200': + description: 更新成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 个人信息更新成功 } + data: { properties: { id: { type: integer }, username: { type: string }, email: { type: string }, status: { type: integer }, ext: { type: object, nullable: true }, created_at: { type: string, format: date-time } }, type: object } + type: object + '400': + description: 参数校验失败或唯一性冲突 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /me/password: + put: + tags: + - Auth + summary: 修改密码 + description: '修改当前用户密码,需验证旧密码。修改成功后清除 refresh_token,需重新登录' + operationId: c3ca37414997ba697e6c11173d9bc483 + requestBody: + required: true + content: + application/json: + schema: + required: + - old_password + - new_password + properties: + old_password: + type: string + example: OldPass_1234 + new_password: + type: string + example: NewPass_5678 + maxLength: 32 + minLength: 6 + type: object + responses: + '200': + description: 密码修改成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 密码修改成功,请重新登录 } + type: object + '400': + description: 参数校验失败或旧密码不正确 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /logout: + get: + tags: + - Auth + summary: 退出登录 + description: '退出登录,清除 refresh_token 并注销当前 JWT token' + operationId: dc5f3d60e870dd8b211f88cd97635158 + responses: + '200': + description: 退出成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 退出成功 } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /companies: + get: + tags: + - Companies + summary: 公司列表 + description: '获取公司列表,支持按 name/label 模糊搜索,受 scope 过滤' + operationId: e3e4d8f38acabbb4c061ba987d6e14dc + parameters: + - + name: name + in: query + description: '公司名称模糊搜索(匹配 name 或 label)' + required: false + schema: + type: string + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { type: array, items: { properties: { id: { type: integer, example: 1 }, name: { type: string, example: acme }, label: { type: string, example: 阿克米公司 }, enabled: { type: boolean, example: true }, ext: { type: object, nullable: true }, created_at: { type: string, format: date-time }, updated_at: { type: string, format: date-time } }, type: object } } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/users/{id}/data-scope': + get: + tags: + - DataScope + summary: 查看用户数据权限 + description: '返回用户的 scope 列表(含实体名称)和解析后的 store_ids' + operationId: 0215f552f5c2315b8b93d030ef72db76 + parameters: + - + name: id + in: path + description: '用户 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { user_id: { type: integer, example: 1 }, role: { type: string, example: admin }, scopes: { type: array, items: { properties: { scope_type: { type: string, example: company }, scope_id: { type: integer, example: 1 }, name: { type: string, example: 示例公司 } }, type: object } }, resolved_store_ids: { type: array, items: { type: integer }, example: [1, 2, 3] } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 用户不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + put: + tags: + - DataScope + summary: 设置用户数据权限 + description: '全量替换用户的 scope 绑定,并重建 bitmap' + operationId: 4ff88235674fa406239c90406f051994 + parameters: + - + name: id + in: path + description: '用户 ID' + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + required: + - scopes + properties: + scopes: + type: array + items: { required: [scope_type, scope_id], properties: { scope_type: { type: string, example: company, enum: [company, platform, store] }, scope_id: { type: integer, example: 1 } }, type: object } + type: object + responses: + '200': + description: 数据权限更新成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 数据权限更新成功 } + type: object + '400': + description: '参数校验失败(无效 scope_type 等)' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 用户不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /platforms: + get: + tags: + - Platforms + summary: 平台列表 + description: 获取全部平台列表(全局数据,不过滤) + operationId: 5ff5cb3822eee3a87c0387f6a5ad4637 + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { type: array, items: { properties: { id: { type: integer, example: 1 }, developer_id: { type: integer, example: 1 }, created_at: { type: string, format: date-time }, updated_at: { type: string, format: date-time } }, type: object } } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /roles: + get: + tags: + - Roles + summary: 角色列表 + description: 获取所有角色,包含每个角色的用户数 + operationId: 9a543d6f94510bc9ca9208afc0b4715c + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { type: array, items: { properties: { id: { type: integer, example: 1 }, name: { type: string, example: editor }, users_count: { type: integer, example: 5 } }, type: object } } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/users/{id}/role': + put: + tags: + - Roles + summary: 分配用户角色 + description: '为指定用户分配角色,administrator 不允许降级自己的角色' + operationId: 49884e2548d00ed9b195e873b2dd3934 + parameters: + - + name: id + in: path + description: '用户 ID' + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + required: + - role_id + properties: + role_id: + description: '角色 ID' + type: integer + example: 2 + type: object + responses: + '200': + description: 角色分配成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 角色分配成功 } + data: { $ref: '#/components/schemas/User' } + type: object + '400': + description: 不允许降级自己的管理员角色 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 用户或角色不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/roles/{id}/route-groups': + get: + tags: + - Roles + summary: 查看角色已授权的路由组 + description: 获取指定角色已授权的路由组列表 + operationId: 25aba1fb2a9a4cc81e1bf06c716deda9 + parameters: + - + name: id + in: path + description: '角色 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { type: array, items: { type: object } } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 角色不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + put: + tags: + - Roles + summary: 设置角色的路由组授权 + description: '全量替换指定角色的路由组授权,administrator 角色不允许修改' + operationId: 1e1f0c23e58bc994ea055407eb469a37 + parameters: + - + name: id + in: path + description: '角色 ID' + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + required: + - group_ids + properties: + group_ids: + type: array + items: { type: integer } + example: [1, 3, 5] + type: object + responses: + '200': + description: 路由组授权更新成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 路由组授权更新成功 } + data: { type: array, items: { type: object } } + type: object + '400': + description: 'administrator 角色不允许修改或参数错误' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 角色不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/roles/{id}/route-overrides': + get: + tags: + - Roles + summary: 查看角色的路由覆盖 + description: 获取指定角色的路由覆盖列表,包含路由详情 + operationId: 7766effa17b431f2bcd67b1f434ee042 + parameters: + - + name: id + in: path + description: '角色 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { type: array, items: { properties: { id: { type: integer }, role_id: { type: integer }, route_id: { type: integer }, allowed: { type: boolean }, route: { type: object } }, type: object } } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 角色不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + put: + tags: + - Roles + summary: 设置角色的路由覆盖 + description: '全量替换指定角色的路由覆盖规则,administrator 角色不允许修改' + operationId: 7ab36e126b6eb4e7631cc7b51b5d2779 + parameters: + - + name: id + in: path + description: '角色 ID' + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + required: + - overrides + properties: + overrides: + type: array + items: { required: [route_id, allowed], properties: { route_id: { description: '路由 ID', type: integer, example: 12 }, allowed: { description: 是否允许, type: boolean, example: false } }, type: object } + type: object + responses: + '200': + description: 路由覆盖更新成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 路由覆盖更新成功 } + data: { type: array, items: { properties: { id: { type: integer }, role_id: { type: integer }, route_id: { type: integer }, allowed: { type: boolean }, route: { type: object } }, type: object } } + type: object + '400': + description: 'route_id 重复、不存在或 administrator 角色不允许修改' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 角色不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /route-groups: + get: + tags: + - RouteGroups + summary: 路由组列表 + description: 返回所有路由组,包含每组的路由数量 + operationId: dc2a90e0b1d08db3762003eac4f93598 + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { type: array, items: { properties: { id: { type: integer, example: 1 }, name: { type: string, example: user-management }, label: { type: string, example: 用户管理, nullable: true }, description: { type: string, example: 用户相关路由, nullable: true }, sort_order: { type: integer, example: 0 }, routes_count: { type: integer, example: 5 }, created_at: { type: string, format: date-time } }, type: object } } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + post: + tags: + - RouteGroups + summary: 创建路由组 + description: 创建路由组 + operationId: 5796b5d8d366ca24bd7b850510fdc77d + requestBody: + required: true + content: + application/json: + schema: + required: + - name + properties: + name: + type: string + example: user-management + maxLength: 100 + label: + type: string + example: 用户管理 + nullable: true + maxLength: 200 + description: + type: string + example: 用户相关路由 + nullable: true + sort_order: + type: integer + example: 0 + default: 0 + type: object + responses: + '200': + description: 创建成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 创建成功 } + data: { properties: { id: { type: integer, example: 1 }, name: { type: string, example: user-management }, label: { type: string, example: 用户管理, nullable: true }, description: { type: string, nullable: true }, sort_order: { type: integer, example: 0 }, created_at: { type: string, format: date-time } }, type: object } + type: object + '400': + description: 参数校验失败或名称重复 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/route-groups/{id}': + put: + tags: + - RouteGroups + summary: 更新路由组 + description: 更新路由组 + operationId: 08ab6c0eb639ed8c3c7b2aa5151f3f8b + parameters: + - + name: id + in: path + description: '路由组 ID' + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string + example: user-management + maxLength: 100 + label: + type: string + example: 用户管理 + nullable: true + maxLength: 200 + description: + type: string + example: 用户相关路由 + nullable: true + sort_order: + type: integer + example: 0 + type: object + responses: + '200': + description: 更新成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 更新成功 } + data: { properties: { id: { type: integer, example: 1 }, name: { type: string, example: user-management }, label: { type: string, example: 用户管理, nullable: true }, description: { type: string, nullable: true }, sort_order: { type: integer, example: 0 }, created_at: { type: string, format: date-time } }, type: object } + type: object + '400': + description: 参数校验失败或名称重复 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 路由组不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + delete: + tags: + - RouteGroups + summary: 删除路由组 + description: '删除路由组,组内路由 group_id 自动设为 NULL,role_route_groups 关联自动级联删除' + operationId: 2b870b541944a9318469fafc2ad83834 + parameters: + - + name: id + in: path + description: '路由组 ID' + required: true + schema: + type: integer + responses: + '200': + description: 删除成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 删除成功 } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 路由组不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /routes: + get: + tags: + - RouteGroups + summary: 路由列表 + description: '获取全部路由列表,含分组信息,支持按 group_id、method、path 筛选' + operationId: 06fcdb734181d39362a999d1599a5e66 + parameters: + - + name: group_id + in: query + description: '路由组 ID,传 0 或 "ungrouped" 筛选未分组路由' + required: false + schema: + type: string + - + name: method + in: query + description: 'HTTP 方法筛选' + required: false + schema: + type: string + - + name: path + in: query + description: 路径模糊搜索 + required: false + schema: + type: string + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { type: array, items: { properties: { id: { type: integer, example: 1 }, method: { type: string, example: GET }, path: { type: string, example: /api/v1/users }, group_id: { type: integer, example: 1, nullable: true }, group: { properties: { id: { type: integer, example: 1 }, name: { type: string, example: user-management }, label: { type: string, example: 用户管理, nullable: true } }, type: object, nullable: true } }, type: object } } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/routes/{id}/group': + put: + tags: + - RouteGroups + summary: 分配路由到路由组 + description: '将路由分配到指定路由组,传 group_id=null 表示从分组中移出' + operationId: 10daee20cc95a281bc0bc3716df103aa + parameters: + - + name: id + in: path + description: '路由 ID' + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + required: + - group_id + properties: + group_id: + description: '路由组 ID,传 null 移出分组' + type: integer + example: 1 + nullable: true + type: object + responses: + '200': + description: 分配成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 分配成功 } + data: { properties: { id: { type: integer, example: 1 }, method: { type: string, example: GET }, path: { type: string, example: /api/v1/users }, group_id: { type: integer, example: 1, nullable: true }, group: { properties: { id: { type: integer, example: 1 }, name: { type: string, example: user-management }, label: { type: string, example: 用户管理, nullable: true } }, type: object, nullable: true } }, type: object } + type: object + '400': + description: 参数校验失败 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 路由或目标路由组不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /stores: + get: + tags: + - Stores + summary: 店铺列表 + description: '获取店铺列表,受 scope 过滤,支持 company_id/platform_id 筛选及 name 模糊搜索' + operationId: 36279ef5f2735b2624e99a82ebc3dccc + parameters: + - + name: company_id + in: query + description: '按公司 ID 筛选' + required: false + schema: + type: integer + - + name: platform_id + in: query + description: '按平台 ID 筛选' + required: false + schema: + type: integer + - + name: name + in: query + description: 按店铺名称模糊搜索 + required: false + schema: + type: string + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { type: array, items: { properties: { id: { type: integer, example: 1 }, company_id: { type: integer, example: 1 }, platform_id: { type: integer, example: 1 }, platform_store_id: { type: string, example: SHOP-001 }, name: { type: string, example: my-store }, label: { type: string, example: 我的店铺 }, enabled: { type: boolean, example: true }, warehouse_id: { type: integer, example: 1 }, currency_id: { type: integer, example: 1 }, timezone: { type: integer, example: 8 }, created_at: { type: string, format: date-time }, updated_at: { type: string, format: date-time } }, type: object } } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + /users: + get: + tags: + - Users + summary: 用户列表 + description: '获取用户列表,支持分页、按 username/email 模糊搜索、按 status 精确筛选' + operationId: 4f028975120b69092c0eae73bb36bcac + parameters: + - + name: page + in: query + required: false + schema: + type: integer + default: 1 + - + name: per_page + in: query + required: false + schema: + type: integer + default: 15 + maximum: 100 + - + name: username + in: query + description: 用户名模糊搜索 + required: false + schema: + type: string + - + name: email + in: query + description: 邮箱模糊搜索 + required: false + schema: + type: string + - + name: status + in: query + description: 状态筛选(0=禁用,1=启用) + required: false + schema: + type: integer + enum: + - 0 + - 1 + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { properties: { items: { type: array, items: { $ref: '#/components/schemas/User' } }, total: { type: integer, example: 100 }, page: { type: integer, example: 1 }, per_page: { type: integer, example: 15 } }, type: object } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + post: + tags: + - Users + summary: 创建用户 + description: 创建用户 + operationId: 0b5969ce7b77ccaa0b2e9b551e302980 + requestBody: + required: true + content: + application/json: + schema: + required: + - username + - password + - email + properties: + username: + type: string + example: new_user + maxLength: 20 + minLength: 3 + password: + type: string + example: Pass_1234 + maxLength: 32 + minLength: 6 + email: + type: string + format: email + example: new@example.com + maxLength: 100 + status: + type: integer + default: 1 + enum: [0, 1] + type: object + responses: + '200': + description: 创建成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 创建成功 } + data: { $ref: '#/components/schemas/User' } + type: object + '400': + description: 参数校验失败或唯一性冲突 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/users/{id}': + get: + tags: + - Users + summary: 用户详情 + description: 用户详情 + operationId: b0770c8ad8eb11c5493fe9643c657673 + parameters: + - + name: id + in: path + description: '用户 ID' + required: true + schema: + type: integer + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 获取成功 } + data: { $ref: '#/components/schemas/User' } + type: object + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 用户不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + put: + tags: + - Users + summary: 更新用户信息 + description: '更新用户的 username、email 或 ext 字段,不支持修改密码' + operationId: c5ee224e653aac218896c93ecc3f9b67 + parameters: + - + name: id + in: path + description: '用户 ID' + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + properties: + username: + type: string + example: updated_user + maxLength: 20 + minLength: 3 + email: + type: string + format: email + example: updated@example.com + maxLength: 100 + ext: + type: object + example: { nickname: Tester } + nullable: true + type: object + responses: + '200': + description: 更新成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 更新成功 } + data: { $ref: '#/components/schemas/User' } + type: object + '400': + description: 参数校验失败或唯一性冲突 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 用户不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] + '/users/{id}/status': + patch: + tags: + - Users + summary: 更新用户状态 + description: 启用或禁用用户 + operationId: c1ad72b12757d083cf0aebbf2f3787fd + parameters: + - + name: id + in: path + description: '用户 ID' + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + required: + - status + properties: + status: + description: 0=禁用,1=启用 + type: integer + enum: [0, 1] + type: object + responses: + '200': + description: 状态更新成功 + content: + application/json: + schema: + properties: + code: { type: integer, example: 0 } + message: { type: string, example: 状态更新成功 } + data: { $ref: '#/components/schemas/User' } + type: object + '400': + description: 参数校验失败 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未认证 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 用户不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + security: + - + bearerAuth: [] +components: + schemas: + Order: + properties: + id: + type: integer + example: 1 + company_id: + type: integer + example: 1 + platform_id: + type: integer + example: 2 + store_id: + type: integer + example: 100 + order_status_id: + type: integer + example: 1 + platform_order_id: + type: string + example: ORD-20260101-001 + buyer_user_id: + type: string + example: buyer_123 + nullable: true + payment_method_id: + type: integer + example: 1 + presale: + type: boolean + example: false + total_amount: + type: number + format: decimal + example: 199.99 + total_paid: + type: number + format: decimal + example: 189.99 + total_discount: + type: number + format: decimal + example: 10 + total_received: + type: number + format: decimal + example: 189.99 + freight_fee: + type: number + format: decimal + example: 0 + tax_fee: + type: number + format: decimal + example: 0 + discount_fee: + type: number + format: decimal + example: 10 + commission_fee: + type: number + format: decimal + example: 5 + coupon_amount: + type: number + format: decimal + example: 0 + voucher_amount: + type: number + format: decimal + example: 0 + order_type_id: + type: integer + example: 1 + hash: + type: string + example: a1b2c3d4e5f6... + raw: + description: 平台原始数据 + type: object + nullable: true + ext: + description: 扩展字段 + type: object + nullable: true + created_date: + type: string + format: date-time + updated_date: + type: string + format: date-time + nullable: true + paid_date: + type: string + format: date-time + nullable: true + shipping_date: + type: string + format: date-time + nullable: true + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + type: object + OrderItem: + properties: + id: + type: integer + example: 1 + company_id: + type: integer + example: 1 + platform_id: + type: integer + example: 2 + store_id: + type: integer + example: 100 + order_id: + type: integer + example: 1000 + platform_order_id: + type: string + example: ORD-20260101-001 + sub_order_id: + type: string + example: SUB-001 + nullable: true + sub_order_type_id: + type: integer + example: 1 + product_id: + type: integer + example: 500 + platform_product_id: + type: string + example: PROD-001 + product_sku: + type: string + example: SKU-ABC-001 + nullable: true + product_barcode: + type: string + example: '6901234567890' + nullable: true + unit_price: + type: number + format: decimal + example: 49.99 + quantity: + type: integer + example: 2 + discount: + type: number + format: decimal + example: 5 + total: + type: number + format: decimal + example: 94.98 + created_date: + type: string + format: date-time + ext: + description: 扩展字段 + type: object + nullable: true + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + type: object + Product: + properties: + id: + type: integer + example: 1 + company_id: + type: integer + example: 1 + platform_id: + type: integer + example: 2 + store_id: + type: integer + example: 100 + status_id: + type: integer + example: 1 + type_id: + type: integer + example: 1 + platform_item_id: + type: string + example: ITEM-001 + platform_model_id: + type: string + example: MODEL-A + nullable: true + name: + type: string + example: 'iPhone 16 Pro' + nullable: true + price: + type: number + format: decimal + example: 99.99 + currency: + type: string + example: CNY + num: + type: integer + example: 100 + hash: + type: string + example: a1b2c3d4e5f6... + raw: + description: 平台原始数据 + type: object + nullable: true + ext: + description: 扩展字段 + type: object + nullable: true + created_date: + type: string + format: date-time + nullable: true + updated_date: + type: string + format: date-time + nullable: true + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + type: object + Refund: + properties: + id: + type: integer + example: 1 + company_id: + type: integer + example: 1 + platform_id: + type: integer + example: 2 + store_id: + type: integer + example: 100 + order_id: + type: integer + example: 500 + nullable: true + platform_order_id: + type: string + example: ORD-20260101-001 + platform_refund_id: + type: string + example: RF-20260115-001 + refund_status_id: + type: integer + example: 1 + refund_type_id: + type: integer + example: 2 + reason: + type: string + example: 商品质量问题 + nullable: true + buyer_user_id: + type: string + example: buyer_123 + nullable: true + refund_amount: + type: number + format: decimal + example: 99.99 + freight_refund: + type: number + format: decimal + example: 10 + refund_total: + type: number + format: decimal + example: 109.99 + currency: + type: string + example: CNY + hash: + type: string + example: a1b2c3d4e5f6... + raw: + description: 平台原始数据 + type: object + nullable: true + ext: + description: 扩展字段 + type: object + nullable: true + order_created_date: + type: string + format: date-time + nullable: true + order_paid_date: + type: string + format: date-time + nullable: true + created_date: + type: string + format: date-time + updated_date: + type: string + format: date-time + nullable: true + completed_date: + type: string + format: date-time + nullable: true + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + type: object + RefundItem: + properties: + id: + type: integer + example: 1 + company_id: + type: integer + example: 1 + platform_id: + type: integer + example: 2 + store_id: + type: integer + example: 100 + refund_id: + type: integer + example: 50 + platform_parent_refund_id: + type: string + example: PRF-001 + nullable: true + platform_refund_id: + type: string + example: RF-ITEM-001 + refund_status_id: + type: integer + example: 1 + refund_type_id: + type: integer + example: 2 + reason: + type: string + example: 商品质量问题 + nullable: true + currency: + type: string + example: CNY + buyer_user_id: + type: string + example: buyer_123 + nullable: true + platform_order_id: + type: string + example: ORD-20260101-001 + platform_sub_order_id: + type: string + example: SUB-001 + nullable: true + platform_product_id: + type: string + example: PROD-001 + nullable: true + quantity: + type: integer + example: 1 + refund_amount: + type: number + format: decimal + example: 99.99 + raw: + description: 平台原始子项数据 + type: object + nullable: true + ext: + description: 扩展字段 + type: object + nullable: true + order_created_date: + type: string + format: date-time + nullable: true + order_paid_date: + type: string + format: date-time + nullable: true + created_date: + type: string + format: date-time + updated_date: + type: string + format: date-time + nullable: true + completed_date: + type: string + format: date-time + nullable: true + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + type: object + User: + properties: + id: + type: integer + example: 1 + username: + type: string + example: user_1234 + email: + type: string + example: user@example.com + status: + type: integer + example: 1 + role_id: + type: integer + example: 1 + nullable: true + api_key_enabled: + type: boolean + example: false + ext: + type: object + example: + nickname: user + nullable: true + refresh_token_expires_at: + type: string + format: date-time + nullable: true + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + type: object + ApiResponse: + properties: + code: + type: integer + example: 0 + message: + type: string + example: success + data: + type: object + nullable: true + type: object + PaginatedData: + properties: + items: + type: array + items: { } + total: + type: integer + example: 0 + page: + type: integer + example: 1 + per_page: + type: integer + example: 15 + type: object + ErrorResponse: + properties: + code: + type: integer + example: 400 + message: + type: string + example: 'Bad request' + data: + type: object + nullable: true + type: object + securitySchemes: + bearerAuth: + type: http + bearerFormat: JWT + scheme: bearer + apiKeyAuth: + type: apiKey + name: X-API-Key + in: header +tags: + - + name: Orders + description: 订单管理 + - + name: 'Order Items' + description: 订单项管理 + - + name: Products + description: 产品管理 + - + name: 'Orders (Raw)' + description: 订单原始数据 + - + name: 'Products (Raw)' + description: 产品原始数据 + - + name: 'Refunds (Raw)' + description: 退款原始数据 + - + name: 'Refund Items (Raw)' + description: 退款项原始数据 + - + name: Refunds + description: 退款管理 + - + name: 'Refund Items' + description: 退款项管理 + - + name: ApiKeys + description: 'API Key 管理' + - + name: Auth + description: 认证与个人信息 + - + name: Companies + description: 公司管理 + - + name: DataScope + description: 用户数据范围管理 + - + name: Platforms + description: 平台管理 + - + name: Roles + description: 角色与授权管理 + - + name: RouteGroups + description: 路由组管理 + - + name: Stores + description: 店铺管理 + - + name: Users + description: 用户管理 diff --git a/backend/test/Cases/Integration/Refund/RefundItemControllerTest.php b/backend/test/Cases/Integration/Refund/RefundItemControllerTest.php new file mode 100644 index 0000000..f97f3b1 --- /dev/null +++ b/backend/test/Cases/Integration/Refund/RefundItemControllerTest.php @@ -0,0 +1,478 @@ +runInCoroutine(static function (): bool { + return RefundItem::query()->exists(); + }); + } + + protected function getFirstRefundItemId(): ?int + { + return $this->runInCoroutine(static function (): ?int { + return RefundItem::query()->value('id'); + }); + } + + // ========== Normal 列表接口 ========== + + public function test_normal_list_returns_paginated_data(): void + { + $response = $this->get('/api/v1/refund-items', [], $this->authHeaders()); + + $response->assertStatus(200); + $response->assertJsonPath('code', 0); + $response->assertJsonStructure([ + 'code', + 'message', + 'data' => [ + 'items', + 'total', + 'page', + 'per_page', + ], + ]); + } + + public function test_normal_list_respects_per_page(): void + { + $response = $this->get('/api/v1/refund-items', ['per_page' => 5], $this->authHeaders()); + + $response->assertStatus(200); + $response->assertJsonPath('data.per_page', 5); + } + + public function test_normal_list_per_page_max_100(): void + { + $response = $this->get('/api/v1/refund-items', ['per_page' => 999], $this->authHeaders()); + + $response->assertStatus(200); + $response->assertJsonPath('data.per_page', 100); + } + + public function test_normal_list_excludes_raw(): void + { + if (!$this->hasRefundItemData()) { + $this->markTestSkipped('没有退款项数据'); + } + + $response = $this->get('/api/v1/refund-items', [], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + $first_item = $body['data']['items'][0] ?? []; + + $this->assertArrayNotHasKey('raw', $first_item); + } + + public function test_normal_list_contains_expected_fields(): void + { + if (!$this->hasRefundItemData()) { + $this->markTestSkipped('没有退款项数据'); + } + + $response = $this->get('/api/v1/refund-items', [], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + $first_item = $body['data']['items'][0]; + + $expected_keys = [ + 'id', 'company_id', 'platform_id', 'store_id', + 'refund_id', 'platform_refund_id', 'platform_order_id', + 'platform_sub_order_id', 'platform_product_id', + 'refund_status_id', 'refund_type_id', + 'quantity', 'refund_amount', 'currency', 'created_date', + ]; + + foreach ($expected_keys as $key) { + $this->assertArrayHasKey($key, $first_item, "列表缺少字段: {$key}"); + } + } + + public function test_normal_list_excludes_detail_only_fields(): void + { + if (!$this->hasRefundItemData()) { + $this->markTestSkipped('没有退款项数据'); + } + + $response = $this->get('/api/v1/refund-items', [], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + $first_item = $body['data']['items'][0] ?? []; + + $this->assertArrayNotHasKey('ext', $first_item); + $this->assertArrayNotHasKey('reason', $first_item); + $this->assertArrayNotHasKey('buyer_user_id', $first_item); + $this->assertArrayNotHasKey('order_created_date', $first_item); + } + + // ========== Normal 列表筛选 ========== + + public function test_normal_list_filter_by_company_id(): void + { + if (!$this->hasRefundItemData()) { + $this->markTestSkipped('没有退款项数据'); + } + + $company_id = $this->runInCoroutine(static function (): mixed { + return RefundItem::query()->value('company_id'); + }); + + $response = $this->get('/api/v1/refund-items', ['company_id' => $company_id], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + foreach ($body['data']['items'] as $item) { + $this->assertSame($company_id, $item['company_id']); + } + } + + public function test_normal_list_filter_by_refund_id(): void + { + if (!$this->hasRefundItemData()) { + $this->markTestSkipped('没有退款项数据'); + } + + $refund_id = $this->runInCoroutine(static function (): mixed { + return RefundItem::query()->value('refund_id'); + }); + + $response = $this->get('/api/v1/refund-items', ['refund_id' => $refund_id], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + foreach ($body['data']['items'] as $item) { + $this->assertSame($refund_id, $item['refund_id']); + } + } + + public function test_normal_list_filter_by_refund_status_id(): void + { + if (!$this->hasRefundItemData()) { + $this->markTestSkipped('没有退款项数据'); + } + + $status_id = $this->runInCoroutine(static function (): mixed { + return RefundItem::query()->value('refund_status_id'); + }); + + $response = $this->get('/api/v1/refund-items', ['refund_status_id' => $status_id], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + foreach ($body['data']['items'] as $item) { + $this->assertSame($status_id, $item['refund_status_id']); + } + } + + public function test_normal_list_filter_by_refund_type_id(): void + { + if (!$this->hasRefundItemData()) { + $this->markTestSkipped('没有退款项数据'); + } + + $type_id = $this->runInCoroutine(static function (): mixed { + return RefundItem::query()->value('refund_type_id'); + }); + + $response = $this->get('/api/v1/refund-items', ['refund_type_id' => $type_id], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + foreach ($body['data']['items'] as $item) { + $this->assertSame($type_id, $item['refund_type_id']); + } + } + + public function test_normal_list_filter_by_platform_refund_id(): void + { + if (!$this->hasRefundItemData()) { + $this->markTestSkipped('没有退款项数据'); + } + + $platform_refund_id = $this->runInCoroutine(static function (): mixed { + return RefundItem::query()->value('platform_refund_id'); + }); + + $response = $this->get('/api/v1/refund-items', ['platform_refund_id' => $platform_refund_id], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + foreach ($body['data']['items'] as $item) { + $this->assertSame($platform_refund_id, $item['platform_refund_id']); + } + } + + public function test_normal_list_filter_by_platform_order_id(): void + { + if (!$this->hasRefundItemData()) { + $this->markTestSkipped('没有退款项数据'); + } + + $platform_order_id = $this->runInCoroutine(static function (): mixed { + return RefundItem::query()->value('platform_order_id'); + }); + + $response = $this->get('/api/v1/refund-items', ['platform_order_id' => $platform_order_id], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + foreach ($body['data']['items'] as $item) { + $this->assertSame($platform_order_id, $item['platform_order_id']); + } + } + + public function test_normal_list_filter_by_created_date_range(): void + { + if (!$this->hasRefundItemData()) { + $this->markTestSkipped('没有退款项数据'); + } + + $response = $this->get('/api/v1/refund-items', [ + 'created_date_from' => '2020-01-01', + 'created_date_to' => '2099-12-31', + ], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + $this->assertGreaterThanOrEqual(0, $body['data']['total']); + } + + // ========== Normal 详情接口 ========== + + public function test_normal_detail_returns_refund_item(): void + { + $id = $this->getFirstRefundItemId(); + if (!$id) { + $this->markTestSkipped('没有退款项数据'); + } + + $response = $this->get('/api/v1/refund-items/' . $id, [], $this->authHeaders()); + + $response->assertStatus(200); + $response->assertJsonPath('code', 0); + $response->assertJsonPath('data.id', $id); + } + + public function test_normal_detail_contains_ext(): void + { + $id = $this->getFirstRefundItemId(); + if (!$id) { + $this->markTestSkipped('没有退款项数据'); + } + + $response = $this->get('/api/v1/refund-items/' . $id, [], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + $this->assertArrayHasKey('ext', $body['data']); + } + + public function test_normal_detail_excludes_raw(): void + { + $id = $this->getFirstRefundItemId(); + if (!$id) { + $this->markTestSkipped('没有退款项数据'); + } + + $response = $this->get('/api/v1/refund-items/' . $id, [], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + $this->assertArrayNotHasKey('raw', $body['data']); + } + + public function test_normal_detail_contains_expected_fields(): void + { + $id = $this->getFirstRefundItemId(); + if (!$id) { + $this->markTestSkipped('没有退款项数据'); + } + + $response = $this->get('/api/v1/refund-items/' . $id, [], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + $expected_keys = [ + 'id', 'company_id', 'platform_id', 'store_id', + 'refund_id', 'platform_parent_refund_id', 'platform_refund_id', + 'refund_status_id', 'refund_type_id', + 'platform_order_id', 'platform_sub_order_id', 'platform_product_id', + 'reason', 'currency', 'buyer_user_id', 'quantity', 'refund_amount', + 'order_created_date', 'order_paid_date', + 'created_date', 'updated_date', 'completed_date', + 'ext', 'created_at', 'updated_at', + ]; + + foreach ($expected_keys as $key) { + $this->assertArrayHasKey($key, $body['data'], "详情缺少字段: {$key}"); + } + } + + public function test_normal_detail_not_found_returns_404(): void + { + $response = $this->get('/api/v1/refund-items/999999999', [], $this->authHeaders()); + + $response->assertStatus(404); + $response->assertJsonPath('code', 404); + } + + // ========== Raw 列表接口 ========== + + public function test_raw_list_returns_paginated_data(): void + { + $response = $this->get('/api/v1/raw/refund-items', [], $this->authHeaders()); + + $response->assertStatus(200); + $response->assertJsonPath('code', 0); + $response->assertJsonStructure([ + 'code', + 'message', + 'data' => [ + 'items', + 'total', + 'page', + 'per_page', + ], + ]); + } + + public function test_raw_list_does_not_contain_raw(): void + { + if (!$this->hasRefundItemData()) { + $this->markTestSkipped('没有退款项数据'); + } + + $response = $this->get('/api/v1/raw/refund-items', [], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + $first_item = $body['data']['items'][0] ?? []; + + $this->assertArrayNotHasKey('raw', $first_item, 'Raw 列表不应包含 raw 字段(太大)'); + } + + public function test_raw_list_excludes_business_fields(): void + { + if (!$this->hasRefundItemData()) { + $this->markTestSkipped('没有退款项数据'); + } + + $response = $this->get('/api/v1/raw/refund-items', [], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + $first_item = $body['data']['items'][0] ?? []; + + $this->assertArrayNotHasKey('refund_amount', $first_item); + $this->assertArrayNotHasKey('quantity', $first_item); + $this->assertArrayNotHasKey('buyer_user_id', $first_item); + $this->assertArrayNotHasKey('reason', $first_item); + } + + // ========== Raw 详情接口 ========== + + public function test_raw_detail_contains_raw_and_ext(): void + { + $id = $this->getFirstRefundItemId(); + if (!$id) { + $this->markTestSkipped('没有退款项数据'); + } + + $response = $this->get('/api/v1/raw/refund-items/' . $id, [], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + $this->assertArrayHasKey('raw', $body['data'], 'Raw 详情应包含 raw 字段'); + $this->assertArrayHasKey('ext', $body['data'], 'Raw 详情应包含 ext 字段'); + } + + public function test_raw_detail_excludes_business_fields(): void + { + $id = $this->getFirstRefundItemId(); + if (!$id) { + $this->markTestSkipped('没有退款项数据'); + } + + $response = $this->get('/api/v1/raw/refund-items/' . $id, [], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + $this->assertArrayNotHasKey('refund_amount', $body['data']); + $this->assertArrayNotHasKey('quantity', $body['data']); + $this->assertArrayNotHasKey('buyer_user_id', $body['data']); + $this->assertArrayNotHasKey('reason', $body['data']); + } + + public function test_raw_detail_not_found_returns_404(): void + { + $response = $this->get('/api/v1/raw/refund-items/999999999', [], $this->authHeaders()); + + $response->assertStatus(404); + $response->assertJsonPath('code', 404); + } + + // ========== 认证拦截 ========== + + public function test_normal_list_without_token_returns_401(): void + { + $response = $this->get('/api/v1/refund-items'); + + $response->assertStatus(401); + } + + public function test_normal_detail_without_token_returns_401(): void + { + $response = $this->get('/api/v1/refund-items/1'); + + $response->assertStatus(401); + } + + public function test_raw_list_without_token_returns_401(): void + { + $response = $this->get('/api/v1/raw/refund-items'); + + $response->assertStatus(401); + } + + public function test_raw_detail_without_token_returns_401(): void + { + $response = $this->get('/api/v1/raw/refund-items/1'); + + $response->assertStatus(401); + } +}