add order item manage
This commit is contained in:
@@ -0,0 +1,178 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller\Api\V1;
|
||||||
|
|
||||||
|
use App\Controller\AbstractDataController;
|
||||||
|
use App\Middleware\AuthMiddleware;
|
||||||
|
use App\Middleware\PermissionMiddleware;
|
||||||
|
use App\Model\OrderItem;
|
||||||
|
use Hyperf\HttpServer\Annotation\Controller;
|
||||||
|
use Hyperf\HttpServer\Annotation\Middleware;
|
||||||
|
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单项管理接口
|
||||||
|
*
|
||||||
|
* 返回所有 parsed 字段(OrderItem 无 raw/hash,仅 Normal Category)
|
||||||
|
*/
|
||||||
|
#[OA\Tag(name: 'Order Items', description: '订单项管理')]
|
||||||
|
#[Controller(prefix: "/api/v1/order-items")]
|
||||||
|
#[Middleware(AuthMiddleware::class)]
|
||||||
|
#[Middleware(PermissionMiddleware::class)]
|
||||||
|
class OrderItemController extends AbstractDataController
|
||||||
|
{
|
||||||
|
protected function getModelClass(): string
|
||||||
|
{
|
||||||
|
return OrderItem::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getListFields(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id', 'company_id', 'platform_id', 'store_id', 'order_id',
|
||||||
|
'platform_order_id', 'sub_order_id', 'platform_product_id',
|
||||||
|
'product_sku', 'unit_price', 'quantity', 'discount', 'total',
|
||||||
|
'created_date',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getDetailFields(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id', 'company_id', 'platform_id', 'store_id', 'order_id',
|
||||||
|
'platform_order_id', 'sub_order_id', 'sub_order_type_id',
|
||||||
|
'product_id', 'platform_product_id', 'product_sku', 'product_barcode',
|
||||||
|
'unit_price', 'quantity', 'discount', 'total',
|
||||||
|
'created_date', 'ext', 'created_at', 'updated_at',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getAllowedFilters(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'company_id' => 'exact',
|
||||||
|
'platform_id' => 'exact',
|
||||||
|
'store_id' => 'exact',
|
||||||
|
'platform_order_id' => 'exact',
|
||||||
|
'platform_product_id' => 'exact',
|
||||||
|
'product_sku' => 'exact',
|
||||||
|
'created_date_from' => 'date_from',
|
||||||
|
'created_date_to' => 'date_to',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getDefaultSort(): string
|
||||||
|
{
|
||||||
|
return 'created_date';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单项列表
|
||||||
|
*/
|
||||||
|
#[OA\Get(
|
||||||
|
path: '/order-items',
|
||||||
|
summary: '订单项列表',
|
||||||
|
description: '获取订单项列表,支持分页和多维度筛选。OrderItem 无 raw/hash 字段,仅提供 Normal 接口。',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Order 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: 'platform_order_id', in: 'query', required: false, description: '平台订单 ID 精确筛选', schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'platform_product_id', in: 'query', required: false, description: '平台商品 ID 精确筛选', schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'product_sku', in: 'query', required: false, description: 'SKU 编码精确筛选', 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/OrderItem')),
|
||||||
|
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: '/order-items/{id}',
|
||||||
|
summary: '订单项详情',
|
||||||
|
description: '获取订单项详情,返回完整字段和关联的父订单摘要信息。',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Order 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', type: 'object', allOf: [
|
||||||
|
new OA\Schema(ref: '#/components/schemas/OrderItem'),
|
||||||
|
new OA\Schema(properties: [
|
||||||
|
new OA\Property(property: 'parent_order', type: 'object', nullable: true, description: '父订单摘要信息'),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
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
|
||||||
|
{
|
||||||
|
$result = parent::show($id);
|
||||||
|
|
||||||
|
// 404 响应直接返回
|
||||||
|
if ($result instanceof ResponseInterface) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 追加父订单摘要
|
||||||
|
$order_item = $result['data'];
|
||||||
|
$result['data'] = $order_item->toArray();
|
||||||
|
|
||||||
|
$parent_order = $order_item->getParentOrder();
|
||||||
|
$result['data']['parent_order'] = $parent_order ? [
|
||||||
|
'id' => $parent_order->id,
|
||||||
|
'platform_order_id' => $parent_order->platform_order_id,
|
||||||
|
'order_status_id' => $parent_order->order_status_id,
|
||||||
|
'total_amount' => $parent_order->total_amount,
|
||||||
|
'total_paid' => $parent_order->total_paid,
|
||||||
|
'created_date' => $parent_order->created_date?->toDateTimeString(),
|
||||||
|
'paid_date' => $parent_order->paid_date?->toDateTimeString(),
|
||||||
|
] : null;
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Model;
|
namespace App\Model;
|
||||||
|
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id 主键
|
* @property int $id 主键
|
||||||
* @property int $company_id 公司 ID 与 Tools 保持一致
|
* @property int $company_id 公司 ID 与 Tools 保持一致
|
||||||
@@ -27,6 +29,32 @@ namespace App\Model;
|
|||||||
* @property \Carbon\Carbon $updated_at
|
* @property \Carbon\Carbon $updated_at
|
||||||
* @mixin \App_Model_OrderItem
|
* @mixin \App_Model_OrderItem
|
||||||
*/
|
*/
|
||||||
|
#[OA\Schema(
|
||||||
|
schema: 'OrderItem',
|
||||||
|
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: 'order_id', type: 'integer', example: 1000),
|
||||||
|
new OA\Property(property: 'platform_order_id', type: 'string', example: 'ORD-20260101-001'),
|
||||||
|
new OA\Property(property: 'sub_order_id', type: 'string', nullable: true, example: 'SUB-001'),
|
||||||
|
new OA\Property(property: 'sub_order_type_id', type: 'integer', example: 1),
|
||||||
|
new OA\Property(property: 'product_id', type: 'integer', example: 500),
|
||||||
|
new OA\Property(property: 'platform_product_id', type: 'string', example: 'PROD-001'),
|
||||||
|
new OA\Property(property: 'product_sku', type: 'string', nullable: true, example: 'SKU-ABC-001'),
|
||||||
|
new OA\Property(property: 'product_barcode', type: 'string', nullable: true, example: '6901234567890'),
|
||||||
|
new OA\Property(property: 'unit_price', type: 'number', format: 'decimal', example: 49.99),
|
||||||
|
new OA\Property(property: 'quantity', type: 'integer', example: 2),
|
||||||
|
new OA\Property(property: 'discount', type: 'number', format: 'decimal', example: 5.00),
|
||||||
|
new OA\Property(property: 'total', type: 'number', format: 'decimal', example: 94.98),
|
||||||
|
new OA\Property(property: 'created_date', type: 'string', format: 'date-time'),
|
||||||
|
new OA\Property(property: 'ext', type: 'object', nullable: true, description: '扩展字段'),
|
||||||
|
new OA\Property(property: 'created_at', type: 'string', format: 'date-time'),
|
||||||
|
new OA\Property(property: 'updated_at', type: 'string', format: 'date-time'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
class OrderItem extends Model
|
class OrderItem extends Model
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,416 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace HyperfTest\Cases\Integration\Order;
|
||||||
|
|
||||||
|
use App\Model\OrderItem;
|
||||||
|
use App\Model\Role;
|
||||||
|
use App\Model\User;
|
||||||
|
use HyperfTest\TestCase;
|
||||||
|
use Qbhy\HyperfAuth\AuthManager;
|
||||||
|
|
||||||
|
use function Hyperf\Support\make;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OrderItemController 集成测试
|
||||||
|
*
|
||||||
|
* 覆盖列表分页/筛选、详情含父订单、DataScope 过滤、401/404
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class OrderItemControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function getAdminToken(): string
|
||||||
|
{
|
||||||
|
$admin_role = $this->fetchAdminRole();
|
||||||
|
$user = $this->fetchUser(static function ($query) use ($admin_role): void {
|
||||||
|
$query->where('status', 1)->where('role_id', $admin_role->id);
|
||||||
|
});
|
||||||
|
if (!$user) {
|
||||||
|
$this->markTestSkipped('没有可用的 administrator 用户');
|
||||||
|
}
|
||||||
|
|
||||||
|
$auth = make(AuthManager::class);
|
||||||
|
return $auth->guard('jwt')->login($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function fetchAdminRole(): Role
|
||||||
|
{
|
||||||
|
if (\Swoole\Coroutine::getCid() > 0) {
|
||||||
|
return Role::query()->where('name', 'administrator')->firstOrFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
$role = null;
|
||||||
|
\Swoole\Coroutine\run(static function () use (&$role): void {
|
||||||
|
$role = Role::query()->where('name', 'administrator')->firstOrFail();
|
||||||
|
});
|
||||||
|
return $role;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function fetchUser(?callable $callback = null): ?User
|
||||||
|
{
|
||||||
|
if (\Swoole\Coroutine::getCid() > 0) {
|
||||||
|
$query = User::query();
|
||||||
|
if ($callback !== null) {
|
||||||
|
$callback($query);
|
||||||
|
}
|
||||||
|
return $query->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = null;
|
||||||
|
\Swoole\Coroutine\run(static function () use ($callback, &$user): void {
|
||||||
|
$query = User::query();
|
||||||
|
if ($callback !== null) {
|
||||||
|
$callback($query);
|
||||||
|
}
|
||||||
|
$user = $query->first();
|
||||||
|
});
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function authHeaders(): array
|
||||||
|
{
|
||||||
|
return ['Authorization' => 'Bearer ' . $this->getAdminToken()];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hasOrderItemData(): bool
|
||||||
|
{
|
||||||
|
if (\Swoole\Coroutine::getCid() > 0) {
|
||||||
|
return OrderItem::query()->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = false;
|
||||||
|
\Swoole\Coroutine\run(static function () use (&$exists): void {
|
||||||
|
$exists = OrderItem::query()->exists();
|
||||||
|
});
|
||||||
|
return $exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getFirstOrderItemId(): ?int
|
||||||
|
{
|
||||||
|
if (\Swoole\Coroutine::getCid() > 0) {
|
||||||
|
return OrderItem::query()->value('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = null;
|
||||||
|
\Swoole\Coroutine\run(static function () use (&$id): void {
|
||||||
|
$id = OrderItem::query()->value('id');
|
||||||
|
});
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 列表接口 ==========
|
||||||
|
|
||||||
|
public function test_list_returns_paginated_data(): void
|
||||||
|
{
|
||||||
|
$response = $this->get('/api/v1/order-items', [], $this->authHeaders());
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('code', 0);
|
||||||
|
$response->assertJsonStructure([
|
||||||
|
'code',
|
||||||
|
'message',
|
||||||
|
'data' => [
|
||||||
|
'items',
|
||||||
|
'total',
|
||||||
|
'page',
|
||||||
|
'per_page',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_list_respects_per_page(): void
|
||||||
|
{
|
||||||
|
$response = $this->get('/api/v1/order-items', ['per_page' => 5], $this->authHeaders());
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('data.per_page', 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_list_per_page_max_100(): void
|
||||||
|
{
|
||||||
|
$response = $this->get('/api/v1/order-items', ['per_page' => 999], $this->authHeaders());
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('data.per_page', 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_list_contains_expected_fields(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasOrderItemData()) {
|
||||||
|
$this->markTestSkipped('没有订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/order-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', 'order_id',
|
||||||
|
'platform_order_id', 'sub_order_id', 'platform_product_id',
|
||||||
|
'product_sku', 'unit_price', 'quantity', 'discount', 'total',
|
||||||
|
'created_date',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($expected_keys as $key) {
|
||||||
|
$this->assertArrayHasKey($key, $first_item, "列表缺少字段: {$key}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_list_excludes_detail_only_fields(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasOrderItemData()) {
|
||||||
|
$this->markTestSkipped('没有订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/order-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('product_barcode', $first_item);
|
||||||
|
$this->assertArrayNotHasKey('sub_order_type_id', $first_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 列表筛选 ==========
|
||||||
|
|
||||||
|
public function test_list_filter_by_company_id(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasOrderItemData()) {
|
||||||
|
$this->markTestSkipped('没有订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$company_id = null;
|
||||||
|
if (\Swoole\Coroutine::getCid() > 0) {
|
||||||
|
$company_id = OrderItem::query()->value('company_id');
|
||||||
|
} else {
|
||||||
|
\Swoole\Coroutine\run(static function () use (&$company_id): void {
|
||||||
|
$company_id = OrderItem::query()->value('company_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/order-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_list_filter_by_platform_order_id(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasOrderItemData()) {
|
||||||
|
$this->markTestSkipped('没有订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$platform_order_id = null;
|
||||||
|
if (\Swoole\Coroutine::getCid() > 0) {
|
||||||
|
$platform_order_id = OrderItem::query()->value('platform_order_id');
|
||||||
|
} else {
|
||||||
|
\Swoole\Coroutine\run(static function () use (&$platform_order_id): void {
|
||||||
|
$platform_order_id = OrderItem::query()->value('platform_order_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/order-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_list_filter_by_platform_product_id(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasOrderItemData()) {
|
||||||
|
$this->markTestSkipped('没有订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$platform_product_id = null;
|
||||||
|
if (\Swoole\Coroutine::getCid() > 0) {
|
||||||
|
$platform_product_id = OrderItem::query()->value('platform_product_id');
|
||||||
|
} else {
|
||||||
|
\Swoole\Coroutine\run(static function () use (&$platform_product_id): void {
|
||||||
|
$platform_product_id = OrderItem::query()->value('platform_product_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/order-items', ['platform_product_id' => $platform_product_id], $this->authHeaders());
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$body = json_decode($response->getContent(), true);
|
||||||
|
|
||||||
|
foreach ($body['data']['items'] as $item) {
|
||||||
|
$this->assertSame($platform_product_id, $item['platform_product_id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_list_filter_by_product_sku(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasOrderItemData()) {
|
||||||
|
$this->markTestSkipped('没有订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_sku = null;
|
||||||
|
if (\Swoole\Coroutine::getCid() > 0) {
|
||||||
|
$product_sku = OrderItem::query()->whereNotNull('product_sku')->where('product_sku', '!=', '')->value('product_sku');
|
||||||
|
} else {
|
||||||
|
\Swoole\Coroutine\run(static function () use (&$product_sku): void {
|
||||||
|
$product_sku = OrderItem::query()->whereNotNull('product_sku')->where('product_sku', '!=', '')->value('product_sku');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$product_sku) {
|
||||||
|
$this->markTestSkipped('没有有 SKU 的订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/order-items', ['product_sku' => $product_sku], $this->authHeaders());
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$body = json_decode($response->getContent(), true);
|
||||||
|
|
||||||
|
foreach ($body['data']['items'] as $item) {
|
||||||
|
$this->assertSame($product_sku, $item['product_sku']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_list_filter_by_created_date_range(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasOrderItemData()) {
|
||||||
|
$this->markTestSkipped('没有订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/order-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']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 详情接口 ==========
|
||||||
|
|
||||||
|
public function test_detail_returns_order_item(): void
|
||||||
|
{
|
||||||
|
$id = $this->getFirstOrderItemId();
|
||||||
|
if (!$id) {
|
||||||
|
$this->markTestSkipped('没有订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/order-items/' . $id, [], $this->authHeaders());
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('code', 0);
|
||||||
|
$response->assertJsonPath('data.id', $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_detail_contains_parent_order(): void
|
||||||
|
{
|
||||||
|
$id = $this->getFirstOrderItemId();
|
||||||
|
if (!$id) {
|
||||||
|
$this->markTestSkipped('没有订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/order-items/' . $id, [], $this->authHeaders());
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$body = json_decode($response->getContent(), true);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('parent_order', $body['data'], '订单项详情应包含 parent_order');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_detail_parent_order_has_expected_fields(): void
|
||||||
|
{
|
||||||
|
$id = $this->getFirstOrderItemId();
|
||||||
|
if (!$id) {
|
||||||
|
$this->markTestSkipped('没有订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/order-items/' . $id, [], $this->authHeaders());
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$body = json_decode($response->getContent(), true);
|
||||||
|
|
||||||
|
$parent_order = $body['data']['parent_order'];
|
||||||
|
if ($parent_order === null) {
|
||||||
|
$this->markTestSkipped('该订单项无关联父订单');
|
||||||
|
}
|
||||||
|
|
||||||
|
$expected_keys = ['id', 'platform_order_id', 'order_status_id', 'total_amount', 'total_paid', 'created_date', 'paid_date'];
|
||||||
|
foreach ($expected_keys as $key) {
|
||||||
|
$this->assertArrayHasKey($key, $parent_order, "父订单摘要缺少字段: {$key}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_detail_contains_ext(): void
|
||||||
|
{
|
||||||
|
$id = $this->getFirstOrderItemId();
|
||||||
|
if (!$id) {
|
||||||
|
$this->markTestSkipped('没有订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/order-items/' . $id, [], $this->authHeaders());
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$body = json_decode($response->getContent(), true);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('ext', $body['data']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_detail_contains_full_fields(): void
|
||||||
|
{
|
||||||
|
$id = $this->getFirstOrderItemId();
|
||||||
|
if (!$id) {
|
||||||
|
$this->markTestSkipped('没有订单项数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/order-items/' . $id, [], $this->authHeaders());
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$body = json_decode($response->getContent(), true);
|
||||||
|
|
||||||
|
// 详情应包含完整字段(列表中不含的额外字段)
|
||||||
|
$this->assertArrayHasKey('product_barcode', $body['data']);
|
||||||
|
$this->assertArrayHasKey('sub_order_type_id', $body['data']);
|
||||||
|
$this->assertArrayHasKey('created_at', $body['data']);
|
||||||
|
$this->assertArrayHasKey('updated_at', $body['data']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_detail_not_found_returns_404(): void
|
||||||
|
{
|
||||||
|
$response = $this->get('/api/v1/order-items/999999999', [], $this->authHeaders());
|
||||||
|
|
||||||
|
$response->assertStatus(404);
|
||||||
|
$response->assertJsonPath('code', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 认证拦截 ==========
|
||||||
|
|
||||||
|
public function test_list_without_token_returns_401(): void
|
||||||
|
{
|
||||||
|
$response = $this->get('/api/v1/order-items');
|
||||||
|
|
||||||
|
$response->assertStatus(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_detail_without_token_returns_401(): void
|
||||||
|
{
|
||||||
|
$response = $this->get('/api/v1/order-items/1');
|
||||||
|
|
||||||
|
$response->assertStatus(401);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user