Browse Source

更新收入记录控制器和服务,新增用户登录验证逻辑,调整统计方法以支持团队和用户ID,优化查询逻辑并添加索引以提高性能,同时更新路由以支持团队和推广角色的访问权限。

wuyi 3 months ago
parent
commit
0cd10429eb

+ 17 - 7
src/controllers/income-records.controller.ts

@@ -48,12 +48,10 @@ export class IncomeRecordsController {
         !payChannel ||
         !payNo
       ) {
-        return reply
-          .code(400)
-          .send({
-            message:
-              'teamId、userId、incomeAmount、agentName、incomeType、orderType、video、price、tipOrderId、payChannel、payNo 为必填字段'
-          })
+        return reply.code(400).send({
+          message:
+            'teamId、userId、incomeAmount、agentName、incomeType、orderType、video、price、tipOrderId、payChannel、payNo 为必填字段'
+        })
       }
 
       // 验证金额为正数
@@ -163,7 +161,19 @@ export class IncomeRecordsController {
   ) {
     try {
       const { startDate, endDate } = request.query
-      const statistics = await this.incomeRecordsService.getStatistics(startDate, endDate)
+      const user = request.user
+      if (!user) {
+        return reply.code(403).send({ message: '用户未登录' })
+      }
+      let userId: number | undefined
+      let teamId: number | undefined
+      if (user.role === UserRole.TEAM) {
+        const team = await this.teamService.findByUserId(user.id)
+        teamId = team.id
+      } else if (user.role === UserRole.PROMOTER) {
+        userId = user.id
+      }
+      const statistics = await this.incomeRecordsService.getStatistics(startDate, endDate, teamId, userId)
       return reply.send(statistics)
     } catch (error) {
       return reply.code(500).send({ message: '获取统计数据失败', error })

+ 3 - 1
src/entities/income-records.entity.ts

@@ -1,4 +1,4 @@
-import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
+import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn, Index } from 'typeorm'
 
 export enum IncomeType {
   TIP = 'tip',
@@ -15,6 +15,8 @@ export enum OrderType {
 }
 
 @Entity()
+@Index('idx_team_date_type', ['teamId', 'createdAt', 'incomeType'])
+@Index('idx_del_flag', ['delFlag'])
 export class IncomeRecords {
   @PrimaryGeneratedColumn()
   id: number

+ 1 - 1
src/routes/income-records.routes.ts

@@ -57,7 +57,7 @@ export default async function incomeRecordsRoutes(fastify: FastifyInstance) {
   // 获取统计数据
   fastify.get<{ Querystring: { startDate?: string; endDate?: string } }>(
     '/statistics/summary',
-    { onRequest: [authenticate, hasRole(UserRole.ADMIN)] },
+    { onRequest: [authenticate, hasAnyRole(UserRole.ADMIN, UserRole.TEAM, UserRole.PROMOTER)] },
     incomeRecordsController.getStatistics.bind(incomeRecordsController)
   )
 }

+ 108 - 42
src/services/income-records.service.ts

@@ -35,33 +35,33 @@ export class IncomeRecordsService {
 
   async findAll(query: ListIncomeRecordsQuery): Promise<PaginationResponse<IncomeRecords>> {
     const { page, size, teamId, userId, agentName, incomeType, orderType, payChannel, startDate, endDate } = query
-    
+
     const where: any = {}
-    
+
     if (teamId) {
       where.teamId = teamId
     }
-    
+
     if (userId) {
       where.userId = userId
     }
-    
+
     if (agentName) {
       where.agentName = Like(`%${agentName}%`)
     }
-    
+
     if (incomeType) {
       where.incomeType = incomeType
     }
-    
+
     if (orderType) {
       where.orderType = orderType
     }
-    
+
     if (payChannel) {
       where.payChannel = Like(`%${payChannel}%`)
     }
-    
+
     if (startDate && endDate) {
       where.createdAt = Between(new Date(startDate), new Date(endDate))
     }
@@ -97,50 +97,116 @@ export class IncomeRecordsService {
     await this.incomeRecordsRepository.delete(id)
   }
 
-  async getStatistics(startDate?: string, endDate?: string): Promise<{
-    totalAmount: number
-    totalCount: number
-    byType: Record<IncomeType, { amount: number; count: number }>
-    byOrderType: Record<OrderType, { amount: number; count: number }>
+  async getStatistics(
+    startDate?: string,
+    endDate?: string,
+    teamId?: number,
+    userId?: number
+  ): Promise<{
+    dates: string[]
+    teams: Array<{
+      teamId: number
+      data: number[]
+      tip: number[]
+      commission: number[]
+    }>
+    total: number[]
+    totalTip: number[]
+    totalCommission: number[]
   }> {
-    const where: any = {}
-    
-    if (startDate && endDate) {
-      where.createdAt = Between(new Date(startDate), new Date(endDate))
+    if (!startDate || !endDate) {
+      throw new Error('开始时间和结束时间都是必须的')
     }
 
-    const records = await this.incomeRecordsRepository.find({
-      where,
-      select: ['incomeAmount', 'incomeType', 'orderType']
-    })
+    const start = new Date(startDate)
+    const end = new Date(endDate)
 
-    const statistics = {
-      totalAmount: 0,
-      totalCount: records.length,
-      byType: {} as Record<IncomeType, { amount: number; count: number }>,
-      byOrderType: {} as Record<OrderType, { amount: number; count: number }>
+    // 生成日期数组
+    const dates: string[] = []
+    const currentDate = new Date(start)
+    while (currentDate <= end) {
+      dates.push(currentDate.toISOString().split('T')[0])
+      currentDate.setDate(currentDate.getDate() + 1)
     }
 
-    // 初始化统计对象
-    Object.values(IncomeType).forEach(type => {
-      statistics.byType[type] = { amount: 0, count: 0 }
+    // 设置时间范围为整天
+    start.setHours(0, 0, 0, 0)
+    end.setHours(23, 59, 59, 999)
+
+    const teamStats = await this.incomeRecordsRepository
+      .createQueryBuilder('record')
+      .select([
+        'record.teamId as teamId',
+        "DATE_FORMAT(CONVERT_TZ(record.createdAt, '+00:00', '+08:00'), '%Y-%m-%d') as date",
+        'CAST(SUM(CASE WHEN record.incomeType = :tipType THEN CAST(record.incomeAmount AS DECIMAL(10,5)) ELSE 0 END) AS DECIMAL(10,5)) as tipAmount',
+        'CAST(SUM(CASE WHEN record.incomeType = :commissionType THEN CAST(record.incomeAmount AS DECIMAL(10,5)) ELSE 0 END) AS DECIMAL(10,5)) as commissionAmount',
+        'CAST(SUM(CAST(record.incomeAmount AS DECIMAL(10,5))) AS DECIMAL(10,5)) as totalAmount'
+      ])
+      .where('record.createdAt >= :start', { start })
+      .andWhere('record.createdAt <= :end', { end })
+      .andWhere('record.delFlag = :delFlag', { delFlag: false })
+      .setParameter('tipType', IncomeType.TIP)
+      .setParameter('commissionType', IncomeType.COMMISSION)
+      .andWhere(teamId ? 'record.teamId = :teamId' : '1=1', { teamId })
+      .andWhere(userId ? 'record.userId = :userId' : '1=1', { userId })
+      .groupBy('record.teamId')
+      .addGroupBy("DATE_FORMAT(CONVERT_TZ(record.createdAt, '+00:00', '+08:00'), '%Y-%m-%d')")
+      .orderBy('record.teamId', 'ASC')
+      .addOrderBy('date', 'ASC')
+      .getRawMany()
+
+    // 按团队分组处理数据
+    const teamMap = new Map<
+      number,
+      {
+        teamId: number
+        data: number[]
+        tip: number[]
+        commission: number[]
+      }
+    >()
+
+    // 初始化每个团队的数据数组
+    teamStats.forEach(stat => {
+      if (!teamMap.has(stat.teamId)) {
+        teamMap.set(stat.teamId, {
+          teamId: stat.teamId,
+          data: dates.map(() => 0),
+          tip: dates.map(() => 0),
+          commission: dates.map(() => 0)
+        })
+      }
+
+      const dateIndex = dates.indexOf(stat.date)
+      if (dateIndex !== -1) {
+        const team = teamMap.get(stat.teamId)!
+        team.data[dateIndex] = Number(stat.totalAmount)
+        team.tip[dateIndex] = Number(stat.tipAmount)
+        team.commission[dateIndex] = Number(stat.commissionAmount)
+      }
     })
-    
-    Object.values(OrderType).forEach(type => {
-      statistics.byOrderType[type] = { amount: 0, count: 0 }
+
+    const teams = Array.from(teamMap.values())
+
+    // 计算每天的总收入
+    const total = dates.map((_, index) => {
+      return teams.reduce((sum, team) => sum + team.data[index], 0)
+    })
+
+    const totalTip = dates.map((_, index) => {
+      return teams.reduce((sum, team) => sum + team.tip[index], 0)
     })
 
-    // 计算统计数据
-    records.forEach(record => {
-      statistics.totalAmount += Number(record.incomeAmount)
-      
-      statistics.byType[record.incomeType].amount += Number(record.incomeAmount)
-      statistics.byType[record.incomeType].count += 1
-      
-      statistics.byOrderType[record.orderType].amount += Number(record.incomeAmount)
-      statistics.byOrderType[record.orderType].count += 1
+    const totalCommission = dates.map((_, index) => {
+      return teams.reduce((sum, team) => sum + team.commission[index], 0)
     })
 
-    return statistics
+    return {
+      dates,
+      teams,
+      total,
+      totalTip,
+      totalCommission
+    }
   }
 }