Просмотр исходного кода

✨ feat(coupon-logs): implement coupon log management module

- 创建CouponLog实体类和数据库模型
- 实现Zod Schema定义用于数据验证
- 注册实体到数据源
- 创建继承GenericCrudService的服务类
- 配置通用CRUD路由并添加认证中间件
- 注册API路由到主应用
- 实现客户端API调用方法
- 更新通用CRUD开发文档

✨ feat(client): add coupon log client API

- 导入CouponLogRoutes类型定义
- 创建couponLogClient实例
- 提供api.v1['coupon-logs']接口调用能力

✨ feat(server): register coupon log API routes

- 导入couponLogRoutes模块
- 注册/api/v1/coupon-logs路由
- 导出CouponLogRoutes类型定义
yourname 6 месяцев назад
Родитель
Сommit
25949b16a7

+ 11 - 1
.roo/commands/generic-crud.md

@@ -2,4 +2,14 @@
 description: "通用curd开发指令"
 description: "通用curd开发指令"
 ---
 ---
 
 
-按通用curd开发规范开发
+按通用curd开发规范开发
+
+创建实体类 your-entity.entity.ts
+创建实体Zod Schema定义 your-entity.schema.ts
+注册实体到数据源
+创建服务类继承GenericCrudService
+创建通用CRUD路由
+注册路由到API
+创建客户端API调用方法
+创建管理后台领券日志页面
+注册路由和菜单

+ 6 - 1
src/client/api.ts

@@ -8,6 +8,7 @@ import type { WechatPayConfigRoutes } from '@/server/api'
 import type { WechatCouponStockRoutes } from '@/server/api'
 import type { WechatCouponStockRoutes } from '@/server/api'
 import type { WechatCouponRoutes } from '@/server/api'
 import type { WechatCouponRoutes } from '@/server/api'
 import type { WechatPayRoutes } from '@/server/api'
 import type { WechatPayRoutes } from '@/server/api'
+import type { CouponLogRoutes } from '@/server/api'
 
 
 export const authClient = hc<AuthRoutes>('/', {
 export const authClient = hc<AuthRoutes>('/', {
   fetch: axiosFetch,
   fetch: axiosFetch,
@@ -39,4 +40,8 @@ export const wechatCouponClient = hc<WechatCouponRoutes>('/', {
 
 
 export const wechatPayClient = hc<WechatPayRoutes>('/', {
 export const wechatPayClient = hc<WechatPayRoutes>('/', {
   fetch: axiosFetch,
   fetch: axiosFetch,
-}).api.v1['wechat-pay']
+}).api.v1['wechat-pay']
+
+export const couponLogClient = hc<CouponLogRoutes>('/', {
+  fetch: axiosFetch,
+}).api.v1['coupon-logs']

+ 3 - 0
src/server/api.ts

@@ -9,6 +9,7 @@ import wechatPayConfigRoutes from './api/wechat-pay-config/index'
 import wechatCouponStockRoutes from './api/wechat-coupon-stocks/index'
 import wechatCouponStockRoutes from './api/wechat-coupon-stocks/index'
 import wechatCouponRoutes from './api/wechat-coupons/index'
 import wechatCouponRoutes from './api/wechat-coupons/index'
 import wechatPayRoutes from './api/wechat-pay/index'
 import wechatPayRoutes from './api/wechat-pay/index'
+import couponLogRoutes from './api/coupon-logs/index'
 import { AuthContext } from './types/context'
 import { AuthContext } from './types/context'
 import { AppDataSource } from './data-source'
 import { AppDataSource } from './data-source'
 import { Hono } from 'hono'
 import { Hono } from 'hono'
@@ -111,6 +112,7 @@ const wechatPayConfigApiRoutes = api.route('/api/v1/wechat-pay-config', wechatPa
 const wechatCouponStockApiRoutes = api.route('/api/v1/wechat-coupon-stocks', wechatCouponStockRoutes)
 const wechatCouponStockApiRoutes = api.route('/api/v1/wechat-coupon-stocks', wechatCouponStockRoutes)
 const wechatCouponApiRoutes = api.route('/api/v1/wechat-coupons', wechatCouponRoutes)
 const wechatCouponApiRoutes = api.route('/api/v1/wechat-coupons', wechatCouponRoutes)
 const wechatPayApiRoutes = api.route('/api/v1/wechat-pay', wechatPayRoutes)
 const wechatPayApiRoutes = api.route('/api/v1/wechat-pay', wechatPayRoutes)
+const couponLogApiRoutes = api.route('/api/v1/coupon-logs', couponLogRoutes)
 
 
 export type AuthRoutes = typeof authRoutes
 export type AuthRoutes = typeof authRoutes
 export type UserRoutes = typeof userRoutes
 export type UserRoutes = typeof userRoutes
@@ -120,6 +122,7 @@ export type WechatPayConfigRoutes = typeof wechatPayConfigApiRoutes
 export type WechatCouponStockRoutes = typeof wechatCouponStockApiRoutes
 export type WechatCouponStockRoutes = typeof wechatCouponStockApiRoutes
 export type WechatCouponRoutes = typeof wechatCouponApiRoutes
 export type WechatCouponRoutes = typeof wechatCouponApiRoutes
 export type WechatPayRoutes = typeof wechatPayApiRoutes
 export type WechatPayRoutes = typeof wechatPayApiRoutes
+export type CouponLogRoutes = typeof couponLogApiRoutes
 
 
 app.route('/', api)
 app.route('/', api)
 export default app
 export default app

+ 20 - 0
src/server/api/coupon-logs/index.ts

@@ -0,0 +1,20 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { CouponLog } from '@/server/modules/coupon-logs/coupon-log.entity';
+import { CouponLogSchema, CreateCouponLogDto, UpdateCouponLogDto } from '@/server/modules/coupon-logs/coupon-log.schema';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const couponLogRoutes = createCrudRoutes({
+  entity: CouponLog,
+  createSchema: CreateCouponLogDto,
+  updateSchema: UpdateCouponLogDto,
+  getSchema: CouponLogSchema,
+  listSchema: CouponLogSchema,
+  searchFields: ['couponId', 'batchId', 'failReason'],
+  middleware: [authMiddleware],
+  userTracking: {
+    createdByField: 'createdBy',
+    updatedByField: 'updatedBy'
+  }
+});
+
+export default couponLogRoutes;

+ 2 - 1
src/server/data-source.ts

@@ -9,6 +9,7 @@ import { File } from "./modules/files/file.entity"
 import { WechatPayConfig } from "./modules/wechat-pay/wechat-pay-config.entity"
 import { WechatPayConfig } from "./modules/wechat-pay/wechat-pay-config.entity"
 import { WechatCouponStock } from "./modules/wechat-pay/wechat-coupon-stock.entity"
 import { WechatCouponStock } from "./modules/wechat-pay/wechat-coupon-stock.entity"
 import { WechatCoupon } from "./modules/wechat-pay/wechat-coupon.entity"
 import { WechatCoupon } from "./modules/wechat-pay/wechat-coupon.entity"
+import { CouponLog } from "./modules/coupon-logs/coupon-log.entity"
 
 
 export const AppDataSource = new DataSource({
 export const AppDataSource = new DataSource({
   type: "mysql",
   type: "mysql",
@@ -18,7 +19,7 @@ export const AppDataSource = new DataSource({
   password: process.env.DB_PASSWORD || "",
   password: process.env.DB_PASSWORD || "",
   database: process.env.DB_DATABASE || "d8dai",
   database: process.env.DB_DATABASE || "d8dai",
   entities: [
   entities: [
-    User, Role, File, WechatPayConfig, WechatCouponStock, WechatCoupon,
+    User, Role, File, WechatPayConfig, WechatCouponStock, WechatCoupon, CouponLog,
   ],
   ],
   migrations: [],
   migrations: [],
   synchronize: process.env.DB_SYNCHRONIZE !== "false",
   synchronize: process.env.DB_SYNCHRONIZE !== "false",

+ 37 - 0
src/server/modules/coupon-logs/coupon-log.entity.ts

@@ -0,0 +1,37 @@
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
+
+@Entity('coupon_logs')
+export class CouponLog {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ name: 'coupon_id', type: 'text', comment: '券ID' })
+  couponId!: string;
+
+  @Column({ name: 'batch_id', type: 'text', comment: '批次号' })
+  batchId!: string;
+
+  @Column({ name: 'batch_category_id', type: 'int', comment: '批次分类ID' })
+  batchCategoryId!: number;
+
+  @Column({ name: 'exchange_code_id', type: 'int', comment: '兑换码ID' })
+  exchangeCodeId!: number;
+
+  @Column({ name: 'user_id', type: 'int', comment: '用户ID' })
+  userId!: number;
+
+  @Column({ name: 'result', type: 'tinyint', comment: '领取结果:0-失败,1-成功' })
+  result!: number;
+
+  @Column({ name: 'fail_reason', type: 'text', nullable: true, comment: '失败原因' })
+  failReason!: string | null;
+
+  @Column({ name: 'is_deleted', type: 'tinyint', default: 0, comment: '是否删除:0-未删除,1-已删除' })
+  isDeleted!: number;
+
+  @CreateDateColumn({ name: 'created_at' })
+  createdAt!: Date;
+
+  @UpdateDateColumn({ name: 'updated_at' })
+  updatedAt!: Date;
+}

+ 35 - 0
src/server/modules/coupon-logs/coupon-log.schema.ts

@@ -0,0 +1,35 @@
+import { z } from '@hono/zod-openapi';
+
+export const CouponLogSchema = z.object({
+  id: z.number().int().positive().openapi({ description: '领券日志ID' }),
+  couponId: z.string().openapi({ description: '券ID', example: 'COUPON123456789' }),
+  batchId: z.string().openapi({ description: '批次号', example: 'BATCH202412010001' }),
+  batchCategoryId: z.number().int().positive().openapi({ description: '批次分类ID', example: 1 }),
+  exchangeCodeId: z.number().int().positive().openapi({ description: '兑换码ID', example: 1001 }),
+  userId: z.number().int().positive().openapi({ description: '用户ID', example: 123 }),
+  result: z.number().int().min(0).max(1).openapi({ description: '领取结果:0-失败,1-成功', example: 1 }),
+  failReason: z.string().nullable().openapi({ description: '失败原因', example: '库存不足' }),
+  isDeleted: z.number().int().min(0).max(1).openapi({ description: '是否删除:0-未删除,1-已删除', example: 0 }),
+  createdAt: z.date().openapi({ description: '创建时间', example: '2024-01-01T12:00:00Z' }),
+  updatedAt: z.date().openapi({ description: '更新时间', example: '2024-01-01T12:00:00Z' })
+});
+
+export const CreateCouponLogDto = z.object({
+  couponId: z.string().openapi({ description: '券ID', example: 'COUPON123456789' }),
+  batchId: z.string().openapi({ description: '批次号', example: 'BATCH202412010001' }),
+  batchCategoryId: z.coerce.number().int().positive().openapi({ description: '批次分类ID', example: 1 }),
+  exchangeCodeId: z.coerce.number().int().positive().openapi({ description: '兑换码ID', example: 1001 }),
+  userId: z.coerce.number().int().positive().openapi({ description: '用户ID', example: 123 }),
+  result: z.coerce.number().int().min(0).max(1).openapi({ description: '领取结果:0-失败,1-成功', example: 1 }),
+  failReason: z.string().nullable().optional().openapi({ description: '失败原因', example: '库存不足' })
+});
+
+export const UpdateCouponLogDto = z.object({
+  couponId: z.string().optional().openapi({ description: '券ID', example: 'COUPON123456789' }),
+  batchId: z.string().optional().openapi({ description: '批次号', example: 'BATCH202412010001' }),
+  batchCategoryId: z.coerce.number().int().positive().optional().openapi({ description: '批次分类ID', example: 1 }),
+  exchangeCodeId: z.coerce.number().int().positive().optional().openapi({ description: '兑换码ID', example: 1001 }),
+  userId: z.coerce.number().int().positive().optional().openapi({ description: '用户ID', example: 123 }),
+  result: z.coerce.number().int().min(0).max(1).optional().openapi({ description: '领取结果:0-失败,1-成功', example: 1 }),
+  failReason: z.string().nullable().optional().openapi({ description: '失败原因', example: '库存不足' })
+});

+ 9 - 0
src/server/modules/coupon-logs/coupon-log.service.ts

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