Переглянути джерело

✨ feat(order): 实现订单相关功能模块

- 添加订单、订单商品、订单退款相关API路由
- 创建订单、订单商品、订单退款的CRUD服务
- 完善订单和订单商品的数据验证Schema
- 注册订单相关实体到数据源配置

✨ feat(api): 注册订单相关API路由

- 导入并注册订单、订单商品、订单退款路由
- 导出对应的路由类型定义
- 配置路由的搜索字段和关联关系
- 添加认证中间件和用户跟踪功能
yourname 4 місяців тому
батько
коміт
4a931ce6c6

+ 9 - 0
src/server/api.ts

@@ -20,6 +20,9 @@ import merchantRoutes from './api/merchants/index'
 import userCardRoutes from './api/user-cards/index'
 import userCardBalanceRecordRoutes from './api/user-card-balance-records/index'
 import deliveryAddressRoutes from './api/delivery-address/index'
+import orderRoutes from './api/orders/index'
+import orderGoodsRoutes from './api/orders-goods/index'
+import orderRefundRoutes from './api/orders-refund/index'
 import { AuthContext } from './types/context'
 import { AppDataSource } from './data-source'
 import { Hono } from 'hono'
@@ -133,6 +136,9 @@ const merchantApiRoutes = api.route('/api/v1/merchants', merchantRoutes)
 const userCardApiRoutes = api.route('/api/v1/user-cards', userCardRoutes)
 const userCardBalanceRecordApiRoutes = api.route('/api/v1/user-card-balance-records', userCardBalanceRecordRoutes)
 const deliveryAddressApiRoutes = api.route('/api/v1/delivery-addresses', deliveryAddressRoutes)
+const orderApiRoutes = api.route('/api/v1/orders', orderRoutes)
+const orderGoodsApiRoutes = api.route('/api/v1/orders-goods', orderGoodsRoutes)
+const orderRefundApiRoutes = api.route('/api/v1/orders-refund', orderRefundRoutes)
 
 export type AuthRoutes = typeof authRoutes
 export type UserRoutes = typeof userRoutes
@@ -153,6 +159,9 @@ export type MerchantRoutes = typeof merchantApiRoutes
 export type UserCardRoutes = typeof userCardApiRoutes
 export type UserCardBalanceRecordRoutes = typeof userCardBalanceRecordApiRoutes
 export type DeliveryAddressRoutes = typeof deliveryAddressApiRoutes
+export type OrderRoutes = typeof orderApiRoutes
+export type OrderGoodsRoutes = typeof orderGoodsApiRoutes
+export type OrderRefundRoutes = typeof orderRefundApiRoutes
 
 app.route('/', api)
 export default app

+ 21 - 0
src/server/api/orders-goods/index.ts

@@ -0,0 +1,21 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { OrderGoods } from '@/server/modules/orders/order-goods.entity';
+import { OrderGoodsSchema, CreateOrderGoodsDto, UpdateOrderGoodsDto } from '@/server/modules/orders/order-goods.schema';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const orderGoodsRoutes = createCrudRoutes({
+  entity: OrderGoods,
+  createSchema: CreateOrderGoodsDto,
+  updateSchema: UpdateOrderGoodsDto,
+  getSchema: OrderGoodsSchema,
+  listSchema: OrderGoodsSchema,
+  searchFields: ['goodsName', 'orderNo'],
+  relations: ['order', 'goods', 'supplier'],
+  middleware: [authMiddleware],
+  userTracking: {
+    createdByField: 'createdBy',
+    updatedByField: 'updatedBy'
+  }
+});
+
+export default orderGoodsRoutes;

+ 21 - 0
src/server/api/orders-refund/index.ts

@@ -0,0 +1,21 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { OrderRefund } from '@/server/modules/orders/order-refund.entity';
+import { OrderRefundSchema, CreateOrderRefundDto, UpdateOrderRefundDto } from '@/server/modules/orders/order-refund.schema';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const orderRefundRoutes = createCrudRoutes({
+  entity: OrderRefund,
+  createSchema: CreateOrderRefundDto,
+  updateSchema: UpdateOrderRefundDto,
+  getSchema: OrderRefundSchema,
+  listSchema: OrderRefundSchema,
+  searchFields: ['orderNo', 'refundOrderNo'],
+  relations: ['order'],
+  middleware: [authMiddleware],
+  userTracking: {
+    createdByField: 'createdBy',
+    updatedByField: 'updatedBy'
+  }
+});
+
+export default orderRefundRoutes;

+ 21 - 0
src/server/api/orders/index.ts

@@ -0,0 +1,21 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { Order } from '@/server/modules/orders/order.entity';
+import { OrderSchema, CreateOrderDto, UpdateOrderDto } from '@/server/modules/orders/order.schema';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const orderRoutes = createCrudRoutes({
+  entity: Order,
+  createSchema: CreateOrderDto,
+  updateSchema: UpdateOrderDto,
+  getSchema: OrderSchema,
+  listSchema: OrderSchema,
+  searchFields: ['orderNo', 'userPhone', 'recevierName'],
+  relations: ['user', 'merchant', 'supplier', 'deliveryAddress'],
+  middleware: [authMiddleware],
+  userTracking: {
+    createdByField: 'createdBy',
+    updatedByField: 'updatedBy'
+  }
+});
+
+export default orderRoutes;

+ 4 - 0
src/server/data-source.ts

@@ -21,6 +21,9 @@ import { Merchant } from "./modules/merchant/merchant.entity"
 import { UserCard } from "./modules/user-cards/user-card.entity"
 import { UserCardBalanceRecord } from "./modules/user-card-balance-records/user-card-balance-record.entity"
 import { DeliveryAddress } from "./modules/delivery-address/delivery-address.entity"
+import { Order } from "./modules/orders/order.entity"
+import { OrderGoods } from "./modules/orders/order-goods.entity"
+import { OrderRefund } from "./modules/orders/order-refund.entity"
 
 export const AppDataSource = new DataSource({
   type: "mysql",
@@ -33,6 +36,7 @@ export const AppDataSource = new DataSource({
     User, Role, File, Advertisement, AdvertisementType,
     GoodsCategory, Goods, City, Config, ExpressCompany, Organization, Supplier, Card, Agent, Merchant,
     UserCard, UserCardBalanceRecord, DeliveryAddress,
+    Order, OrderGoods, OrderRefund,
   ],
   migrations: [],
   synchronize: process.env.DB_SYNCHRONIZE !== "false",

+ 77 - 1
src/server/modules/orders/order-goods.schema.ts

@@ -158,4 +158,80 @@ export const CreateOrderGoodsDto = z.object({
     description: '运费',
     example: 10.00
   }),
-  state: z.coerce.number().int().min(0, '状态
+  state: z.coerce.number().int().min(0, '状态最小为0').max(1, '状态最大为1').default(0).optional().openapi({
+    description: '状态(0未发货、1已发货)',
+    example: 0
+  }),
+  expressName: z.string().max(255, '快递名称最多255个字符').nullable().optional().openapi({
+    description: '快递名称',
+    example: '顺丰快递'
+  }),
+  expressNo: z.coerce.number().int().positive().nullable().optional().openapi({
+    description: '快递单号',
+    example: 1234567890
+  })
+});
+
+// 更新订单商品DTO
+export const UpdateOrderGoodsDto = z.object({
+  orderId: z.number().int().positive('订单ID必须是正整数').optional().openapi({
+    description: '订单ID',
+    example: 1
+  }),
+  orderNo: z.string().max(50, '订单号最多50个字符').nullable().optional().openapi({
+    description: '订单号',
+    example: 'ORD20240101123456'
+  }),
+  goodsId: z.number().int().positive('商品ID必须是正整数').optional().openapi({
+    description: '商品ID',
+    example: 1
+  }),
+  goodsName: z.string().max(255, '商品名称最多255个字符').nullable().optional().openapi({
+    description: '商品名称',
+    example: '苹果手机'
+  }),
+  goodsImage: z.string().max(255, '商品图片最多255个字符').nullable().optional().openapi({
+    description: '商品图片',
+    example: 'https://example.com/goods.jpg'
+  }),
+  goodsType: z.coerce.number().int().min(1, '商品类型最小为1').max(2, '商品类型最大为2').optional().openapi({
+    description: '1实物产品2虚拟订单',
+    example: 1
+  }),
+  supplierId: z.coerce.number().int().positive().optional().openapi({
+    description: '供货商ID',
+    example: 1
+  }),
+  costPrice: z.coerce.number().min(0, '成本价不能小于0').max(999999.99, '成本价不能超过999999.99').optional().openapi({
+    description: '成本价',
+    example: 50.00
+  }),
+  price: z.coerce.number().min(0, '售价不能小于0').max(999999.99, '售价不能超过999999.99').optional().openapi({
+    description: '售价',
+    example: 99.99
+  }),
+  num: z.coerce.number().int().min(0, '数量不能小于0').max(99999, '数量不能超过99999').optional().openapi({
+    description: '数量',
+    example: 2
+  }),
+  freightAmount: z.coerce.number().min(0, '运费不能小于0').max(999999.99, '运费不能超过999999.99').optional().openapi({
+    description: '运费',
+    example: 10.00
+  }),
+  state: z.coerce.number().int().min(0, '状态最小为0').max(1, '状态最大为1').optional().openapi({
+    description: '状态(0未发货、1已发货)',
+    example: 0
+  }),
+  expressName: z.string().max(255, '快递名称最多255个字符').nullable().optional().openapi({
+    description: '快递名称',
+    example: '顺丰快递'
+  }),
+  expressNo: z.coerce.number().int().positive().nullable().optional().openapi({
+    description: '快递单号',
+    example: 1234567890
+  })
+});
+
+// 订单商品状态类型
+export type OrderGoodsStatusType = typeof OrderGoodsStatus[keyof typeof OrderGoodsStatus];
+export type GoodsTypeType = typeof GoodsType[keyof typeof GoodsType];

+ 9 - 0
src/server/modules/orders/order-goods.service.ts

@@ -0,0 +1,9 @@
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
+import { DataSource } from 'typeorm';
+import { OrderGoods } from './order-goods.entity';
+
+export class OrderGoodsService extends GenericCrudService<OrderGoods> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, OrderGoods);
+  }
+}

+ 9 - 0
src/server/modules/orders/order-refund.service.ts

@@ -0,0 +1,9 @@
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
+import { DataSource } from 'typeorm';
+import { OrderRefund } from './order-refund.entity';
+
+export class OrderRefundService extends GenericCrudService<OrderRefund> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, OrderRefund);
+  }
+}

+ 26 - 35
src/server/modules/orders/order.schema.ts

@@ -276,19 +276,19 @@ export const CreateOrderDto = z.object({
     description: '地址',
     example: '北京市朝阳区xxx路xxx号'
   }),
-  orderType: z.coerce.number().int().min(1, '订单类型最小为1').max(2, '订单类型最大为2').default(1).optional().openapi({
+  orderType: z.coerce.number().int().min(1, '订单类型最小为1').max(2, '订单类型最大为2').default(1).openapi({
     description: '订单类型 1实物订单 2虚拟订单',
     example: 1
   }),
-  payType: z.coerce.number().int().min(0, '支付类型最小为0').max(2, '支付类型最大为2').default(0).optional().openapi({
+  payType: z.coerce.number().int().min(0, '支付类型最小为0').max(2, '支付类型最大为2').default(0).openapi({
     description: '支付类型1积分2礼券',
     example: 1
   }),
-  payState: z.coerce.number().int().min(0, '支付状态最小为0').max(5, '支付状态最大为5').default(0).optional().openapi({
+  payState: z.coerce.number().int().min(0, '支付状态最小为0').max(5, '支付状态最大为5').default(0).openapi({
     description: '支付状态 0未支付1支付中2支付成功3已退款4支付失败5订单关闭',
-    example: 0
+    example: 2
   }),
-  state: z.coerce.number().int().min(0, '订单状态最小为0').max(3, '订单状态最大为3').default(0).optional().openapi({
+  state: z.coerce.number().int().min(0, '订单状态最小为0').max(3, '订单状态最大为3').default(0).openapi({
     description: '订单状态 0未发货1已发货2收货成功3已退货',
     example: 0
   }),
@@ -296,7 +296,7 @@ export const CreateOrderDto = z.object({
     description: '用户手机号',
     example: '13800138000'
   }),
-  merchantId: z.coerce.number().int().positive().default(0).optional().openapi({
+  merchantId: z.coerce.number().int().positive().default(0).openapi({
     description: '商户id',
     example: 1
   }),
@@ -304,11 +304,11 @@ export const CreateOrderDto = z.object({
     description: '商户号',
     example: 1001
   }),
-  supplierId: z.coerce.number().int().positive().default(0).optional().openapi({
+  supplierId: z.coerce.number().int().positive().default(0).openapi({
     description: '供货商id',
     example: 1
   }),
-  addressId: z.coerce.number().int().positive().default(0).optional().openapi({
+  addressId: z.coerce.number().int().positive().default(0).openapi({
     description: '地址id',
     example: 1
   }),
@@ -320,19 +320,19 @@ export const CreateOrderDto = z.object({
     description: '收货人姓名',
     example: '张三'
   }),
-  recevierProvince: z.coerce.number().int().positive().default(0).optional().openapi({
+  recevierProvince: z.coerce.number().int().positive().default(0).openapi({
     description: '收货人所在省',
     example: 110000
   }),
-  recevierCity: z.coerce.number().int().positive().default(0).optional().openapi({
+  recevierCity: z.coerce.number().int().positive().default(0).openapi({
     description: '收货人所在市',
     example: 110100
   }),
-  recevierDistrict: z.coerce.number().int().positive().default(0).optional().openapi({
+  recevierDistrict: z.coerce.number().int().positive().default(0).openapi({
     description: '收货人所在区',
     example: 110105
   }),
-  recevierTown: z.coerce.number().int().positive().default(0).optional().openapi({
+  recevierTown: z.coerce.number().int().positive().default(0).openapi({
     description: '收货人所在街道',
     example: 110105001
   }),
@@ -347,15 +347,19 @@ export const CreateOrderDto = z.object({
   remark: z.string().max(255, '管理员备注信息最多255个字符').nullable().optional().openapi({
     description: '管理员备注信息',
     example: '请尽快发货'
-  }),
-  createdBy: z.number().int().positive().nullable().optional().openapi({
-    description: '创建人ID',
-    example: 1
   })
 });
 
-// 更新订单DTO(通常仅包含可更新字段,这里示例保留大部分字段,实际可根据业务调整)
+// 更新订单DTO
 export const UpdateOrderDto = z.object({
+  orderNo: z.string().min(1, '订单号不能为空').max(50, '订单号最多50个字符').optional().openapi({
+    description: '订单号',
+    example: 'ORD20240101123456'
+  }),
+  userId: z.number().int().positive('用户ID必须是正整数').optional().openapi({
+    description: '用户ID',
+    example: 1
+  }),
   authCode: z.string().max(32, '付款码最多32个字符').nullable().optional().openapi({
     description: '付款码',
     example: '12345678901234567890123456789012'
@@ -422,7 +426,7 @@ export const UpdateOrderDto = z.object({
   }),
   state: z.coerce.number().int().min(0, '订单状态最小为0').max(3, '订单状态最大为3').optional().openapi({
     description: '订单状态 0未发货1已发货2收货成功3已退货',
-    example: 1
+    example: 0
   }),
   userPhone: z.string().max(50, '用户手机号最多50个字符').nullable().optional().openapi({
     description: '用户手机号',
@@ -479,28 +483,15 @@ export const UpdateOrderDto = z.object({
   remark: z.string().max(255, '管理员备注信息最多255个字符').nullable().optional().openapi({
     description: '管理员备注信息',
     example: '请尽快发货'
-  }),
-  updatedBy: z.number().int().positive().nullable().optional().openapi({
-    description: '更新人ID',
-    example: 1
   })
 });
 
-// 订单列表响应
+// 订单列表响应Schema
 export const OrderListResponse = z.object({
   data: z.array(OrderSchema),
   pagination: z.object({
-    total: z.number().openapi({
-      example: 100,
-      description: '总记录数'
-    }),
-    current: z.number().openapi({
-      example: 1,
-      description: '当前页码'
-    }),
-    pageSize: z.number().openapi({
-      example: 10,
-      description: '每页数量'
-    })
+    total: z.number().openapi({ example: 100, description: '总记录数' }),
+    current: z.number().openapi({ example: 1, description: '当前页码' }),
+    pageSize: z.number().openapi({ example: 10, description: '每页数量' })
   })
 });

+ 9 - 0
src/server/modules/orders/order.service.ts

@@ -0,0 +1,9 @@
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
+import { DataSource } from 'typeorm';
+import { Order } from './order.entity';
+
+export class OrderService extends GenericCrudService<Order> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, Order);
+  }
+}