Browse Source

新增团队成员相关的控制器、服务、路由及DTO定义,支持团队成员的创建、查询、更新、删除及收入管理功能。

wuyi 4 months ago
parent
commit
f63f9952ab

+ 2 - 0
src/app.ts

@@ -14,6 +14,7 @@ import sysConfigRoutes from './routes/sys-config.routes'
 import incomeRecordsRoutes from './routes/income-records.routes'
 import financeRoutes from './routes/finance.routes'
 import teamRoutes from './routes/team.routes'
+import teamMembersRoutes from './routes/team-members.routes'
 import promotionLinkRoutes from './routes/promotion-link.routes'
 
 const options: FastifyEnvOptions = {
@@ -84,6 +85,7 @@ export const createApp = async () => {
   app.register(incomeRecordsRoutes, { prefix: '/api/income' })
   app.register(financeRoutes, { prefix: '/api/finance' })
   app.register(teamRoutes, { prefix: '/api/teams' })
+  app.register(teamMembersRoutes, { prefix: '/api/members' })
   app.register(promotionLinkRoutes, { prefix: '/api/links' })
 
   const dataSource = createDataSource(app)

+ 115 - 0
src/controllers/team-members.controller.ts

@@ -0,0 +1,115 @@
+import { FastifyRequest, FastifyReply, FastifyInstance } from 'fastify'
+import { TeamMembersService } from '../services/team-members.service'
+import {
+  CreateTeamMembersBody,
+  UpdateTeamMembersBody,
+  ListTeamMembersQuery,
+  TeamMembersParams,
+  UpdateRevenueBody
+} from '../dto/team-members.dto'
+
+export class TeamMembersController {
+  private teamMembersService: TeamMembersService
+
+  constructor(app: FastifyInstance) {
+    this.teamMembersService = new TeamMembersService(app)
+  }
+
+  async create(request: FastifyRequest<{ Body: CreateTeamMembersBody }>, reply: FastifyReply) {
+    try {
+      const teamMember = await this.teamMembersService.create(request.body, request.user.id)
+      return reply.code(201).send(teamMember)
+    } catch (error) {
+      return reply.code(500).send({ message: '创建团队成员失败', error })
+    }
+  }
+
+  async findById(request: FastifyRequest<{ Params: TeamMembersParams }>, reply: FastifyReply) {
+    try {
+      const { id } = request.params
+      const teamMember = await this.teamMembersService.findById(id)
+      return reply.send(teamMember)
+    } catch (error) {
+      return reply.code(404).send({ message: '团队成员不存在' })
+    }
+  }
+
+  async findAll(request: FastifyRequest<{ Querystring: ListTeamMembersQuery }>, reply: FastifyReply) {
+    try {
+      const result = await this.teamMembersService.findAll(request.query)
+      return reply.send(result)
+    } catch (error) {
+      return reply.code(500).send({ message: '获取团队成员列表失败', error })
+    }
+  }
+
+  async update(request: FastifyRequest<{ Params: TeamMembersParams; Body: UpdateTeamMembersBody }>, reply: FastifyReply) {
+    try {
+      const { id } = request.params
+      const updateData = { ...request.body, id }
+      
+      try {
+        await this.teamMembersService.findById(id)
+      } catch (error) {
+        return reply.code(404).send({ message: '团队成员不存在' })
+      }
+
+      const updatedMember = await this.teamMembersService.update(updateData)
+      return reply.send(updatedMember)
+    } catch (error) {
+      return reply.code(500).send({ message: '更新团队成员失败', error })
+    }
+  }
+
+  async delete(request: FastifyRequest<{ Params: TeamMembersParams }>, reply: FastifyReply) {
+    try {
+      const { id } = request.params
+      
+      try {
+        await this.teamMembersService.findById(id)
+      } catch (error) {
+        return reply.code(404).send({ message: '团队成员不存在' })
+      }
+
+      await this.teamMembersService.delete(id)
+      return reply.send({ message: '团队成员已删除' })
+    } catch (error) {
+      return reply.code(500).send({ message: '删除团队成员失败', error })
+    }
+  }
+
+  async updateRevenue(request: FastifyRequest<{ Body: UpdateRevenueBody }>, reply: FastifyReply) {
+    try {
+      const { id, amount, type } = request.body
+      
+      try {
+        await this.teamMembersService.findById(id)
+      } catch (error) {
+        return reply.code(404).send({ message: '团队成员不存在' })
+      }
+
+      const updatedMember = await this.teamMembersService.updateRevenue(id, amount, type)
+      return reply.send(updatedMember)
+    } catch (error) {
+      return reply.code(500).send({ message: '更新团队成员收入失败', error })
+    }
+  }
+
+  async resetTodayRevenue(request: FastifyRequest, reply: FastifyReply) {
+    try {
+      await this.teamMembersService.resetTodayRevenue()
+      return reply.send({ message: '今日收入已重置' })
+    } catch (error) {
+      return reply.code(500).send({ message: '重置今日收入失败', error })
+    }
+  }
+
+  async getStatistics(request: FastifyRequest, reply: FastifyReply) {
+    try {
+      const statistics = await this.teamMembersService.getStatistics()
+      return reply.send(statistics)
+    } catch (error) {
+      return reply.code(500).send({ message: '获取统计数据失败', error })
+    }
+  }
+}

+ 36 - 0
src/dto/team-members.dto.ts

@@ -0,0 +1,36 @@
+import { FastifyRequest } from 'fastify'
+import { Pagination } from './common.dto'
+
+export interface CreateTeamMembersBody {
+  name: string
+  teamId: number
+  teamUserId?: number
+  password?: string
+  totalRevenue?: number
+  todayRevenue?: number
+}
+
+export interface UpdateTeamMembersBody {
+  id: number
+  name?: string
+  teamId?: number
+  userId?: number
+  totalRevenue?: number
+  todayRevenue?: number
+}
+
+export interface ListTeamMembersQuery extends Pagination {
+  name?: string
+  teamId?: number
+  userId?: number
+}
+
+export interface TeamMembersParams {
+  id: number
+}
+
+export interface UpdateRevenueBody {
+  id: number
+  amount: number
+  type: 'total' | 'today'
+}

+ 28 - 0
src/entities/team-members.entity.ts

@@ -0,0 +1,28 @@
+import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
+
+@Entity()
+export class TeamMembers {
+  @PrimaryGeneratedColumn()
+  id: number
+
+  @Column()
+  name: string
+
+  @Column({ type: 'decimal', precision: 10, scale: 5, default: 0 })
+  totalRevenue: number
+
+  @Column({ type: 'decimal', precision: 10, scale: 5, default: 0 })
+  todayRevenue: number
+
+  @Column()
+  teamId: number
+
+  @Column()
+  userId: number
+
+  @CreateDateColumn()
+  createdAt: Date
+
+  @UpdateDateColumn()
+  updatedAt: Date
+}

+ 65 - 0
src/routes/team-members.routes.ts

@@ -0,0 +1,65 @@
+import { FastifyInstance } from 'fastify'
+import { TeamMembersController } from '../controllers/team-members.controller'
+import { authenticate, hasRole } from '../middlewares/auth.middleware'
+import { UserRole } from '../entities/user.entity'
+import { CreateTeamMembersBody, UpdateTeamMembersBody, ListTeamMembersQuery, TeamMembersParams, UpdateRevenueBody } from '../dto/team-members.dto'
+
+export default async function teamMembersRoutes(fastify: FastifyInstance) {
+  const teamMembersController = new TeamMembersController(fastify)
+
+  // 创建团队成员
+  fastify.post<{ Body: CreateTeamMembersBody }>(
+    '/',
+    { onRequest: [authenticate, hasRole(UserRole.ADMIN)] },
+    teamMembersController.create.bind(teamMembersController)
+  )
+
+  // 获取团队成员列表
+  fastify.get<{ Querystring: ListTeamMembersQuery }>(
+    '/',
+    { onRequest: [authenticate, hasRole(UserRole.ADMIN)] },
+    teamMembersController.findAll.bind(teamMembersController)
+  )
+
+  // 获取单个团队成员
+  fastify.get<{ Params: TeamMembersParams }>(
+    '/:id',
+    { onRequest: [authenticate, hasRole(UserRole.ADMIN)] },
+    teamMembersController.findById.bind(teamMembersController)
+  )
+
+  // 更新团队成员
+  fastify.put<{ Params: TeamMembersParams; Body: UpdateTeamMembersBody }>(
+    '/:id',
+    { onRequest: [authenticate, hasRole(UserRole.ADMIN)] },
+    teamMembersController.update.bind(teamMembersController)
+  )
+
+  // 更新团队成员收入
+  fastify.put<{ Body: UpdateRevenueBody }>(
+    '/:id/revenue',
+    { onRequest: [authenticate, hasRole(UserRole.ADMIN)] },
+    teamMembersController.updateRevenue.bind(teamMembersController)
+  )
+
+  // 删除团队成员
+  fastify.delete<{ Params: TeamMembersParams }>(
+    '/:id',
+    { onRequest: [authenticate, hasRole(UserRole.ADMIN)] },
+    teamMembersController.delete.bind(teamMembersController)
+  )
+
+  // 重置今日收入
+  fastify.post(
+    '/reset-today-revenue',
+    { onRequest: [authenticate, hasRole(UserRole.ADMIN)] },
+    teamMembersController.resetTodayRevenue.bind(teamMembersController)
+  )
+
+  // 获取统计数据
+  fastify.get(
+    '/statistics/summary',
+    { onRequest: [authenticate, hasRole(UserRole.ADMIN)] },
+    teamMembersController.getStatistics.bind(teamMembersController)
+  )
+}

+ 136 - 0
src/services/team-members.service.ts

@@ -0,0 +1,136 @@
+import { Repository, Like } from 'typeorm'
+import { FastifyInstance } from 'fastify'
+import { TeamMembers } from '../entities/team-members.entity'
+import { PaginationResponse } from '../dto/common.dto'
+import { CreateTeamMembersBody, UpdateTeamMembersBody, ListTeamMembersQuery } from '../dto/team-members.dto'
+import { UserService } from './user.service'
+import { UserRole } from '../entities/user.entity'
+
+export class TeamMembersService {
+  private teamMembersRepository: Repository<TeamMembers>
+  private userService: UserService
+
+  constructor(app: FastifyInstance) {
+    this.teamMembersRepository = app.dataSource.getRepository(TeamMembers)
+    this.userService = new UserService(app)
+  }
+
+  async create(data: CreateTeamMembersBody, creatorId: number): Promise<TeamMembers> {
+    const { password, teamUserId, ...teamMemberData } = data
+
+    const existingUser = await this.userService.findByName(teamMemberData.name)
+    if (existingUser) {
+      throw new Error('团队成员已存在')
+    }
+
+    const userPassword = password || 'password123'
+    const parentId = teamUserId || creatorId
+    const createdUser = await this.userService.create(userPassword, teamMemberData.name, UserRole.USER, parentId)
+
+    const teamMember = this.teamMembersRepository.create({
+      ...teamMemberData,
+      userId: createdUser.id
+    })
+    return this.teamMembersRepository.save(teamMember)
+  }
+
+  async findById(id: number): Promise<TeamMembers> {
+    return this.teamMembersRepository.findOneOrFail({ where: { id } })
+  }
+
+  async findAll(query: ListTeamMembersQuery): Promise<PaginationResponse<TeamMembers>> {
+    const { page, size, name, teamId, userId } = query
+
+    const where: any = {}
+
+    if (name) {
+      where.name = Like(`%${name}%`)
+    }
+
+    if (teamId) {
+      where.teamId = teamId
+    }
+
+    if (userId) {
+      where.userId = userId
+    }
+
+    const [members, total] = await this.teamMembersRepository.findAndCount({
+      where,
+      skip: (Number(page) || 0) * (Number(size) || 20),
+      take: Number(size) || 20,
+      order: { createdAt: 'DESC' }
+    })
+
+    return {
+      content: members,
+      metadata: {
+        total: Number(total),
+        page: Number(page) || 0,
+        size: Number(size) || 20
+      }
+    }
+  }
+
+  async update(data: UpdateTeamMembersBody): Promise<TeamMembers> {
+    const { id, ...updateData } = data
+    await this.teamMembersRepository.update(id, updateData)
+    return this.findById(id)
+  }
+
+  async delete(id: number): Promise<void> {
+    await this.teamMembersRepository.delete(id)
+  }
+
+  async updateRevenue(id: number, amount: number, type: 'total' | 'today'): Promise<TeamMembers> {
+    const member = await this.findById(id)
+
+    if (type === 'total') {
+      member.totalRevenue = Number(member.totalRevenue) + amount
+    } else {
+      member.todayRevenue = Number(member.todayRevenue) + amount
+    }
+
+    return this.teamMembersRepository.save(member)
+  }
+
+  async resetTodayRevenue(): Promise<void> {
+    await this.teamMembersRepository.update({}, { todayRevenue: 0 })
+  }
+
+  async getStatistics(): Promise<{
+    totalMembers: number
+    totalRevenue: number
+    todayRevenue: number
+    topMembers: Array<{ id: number; name: string; totalRevenue: number; todayRevenue: number }>
+  }> {
+    const members = await this.teamMembersRepository.find({
+      select: ['id', 'name', 'totalRevenue', 'todayRevenue']
+    })
+
+    const statistics = {
+      totalMembers: members.length,
+      totalRevenue: 0,
+      todayRevenue: 0,
+      topMembers: [] as Array<{ id: number; name: string; totalRevenue: number; todayRevenue: number }>
+    }
+
+    members.forEach(member => {
+      statistics.totalRevenue += Number(member.totalRevenue)
+      statistics.todayRevenue += Number(member.todayRevenue)
+    })
+
+    // 获取收入前5的成员
+    statistics.topMembers = members
+      .sort((a, b) => Number(b.totalRevenue) - Number(a.totalRevenue))
+      .slice(0, 5)
+      .map(member => ({
+        id: member.id,
+        name: member.name,
+        totalRevenue: Number(member.totalRevenue),
+        todayRevenue: Number(member.todayRevenue)
+      }))
+
+    return statistics
+  }
+}