feat(assoc): v4.0.1 合伙人关联码、分佣账单与 H5 用户管理

订单佣金只认关联用户;合伙人备注写入独立表;H5 增加用户管理与首页统计。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-30 14:35:39 +08:00
parent 3b669f7e38
commit 9c8d5f2cad
125 changed files with 6355 additions and 1436 deletions
@@ -0,0 +1,122 @@
import { Body, Controller, Get, Param, Post, Put, Query, Res, UseGuards } from '@nestjs/common';
import type { Response } from 'express';
import { IsOptional, IsString, MaxLength } from 'class-validator';
import { JwtAuthGuard, AuthUser } from '../../common/guards/jwt-auth.guard';
import { PartnerPrimaryGuard } from '../../common/guards/partner-primary.guard';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import { PartnerAssocService } from './partner-assoc.service';
class BindPartnerAssocDto {
@IsOptional()
@IsString()
scene?: string;
@IsOptional()
@IsString()
partnerId?: string;
}
class PartnerAssocRemarkDto {
@IsOptional()
@IsString()
@MaxLength(128)
remark?: string | null;
}
@Controller('user/partner-assoc')
@UseGuards(JwtAuthGuard)
export class UserPartnerAssocController {
constructor(private readonly assoc: PartnerAssocService) {}
@Post('bind')
bind(@CurrentUser() user: AuthUser, @Body() dto: BindPartnerAssocDto) {
return this.assoc.bindUser(user.actorId, dto);
}
}
@Controller('partner/assoc')
@UseGuards(JwtAuthGuard, PartnerPrimaryGuard)
export class PartnerAssocController {
constructor(private readonly assoc: PartnerAssocService) {}
@Get()
summary(@CurrentUser() user: AuthUser) {
return this.assoc.getSummary(user.actorId);
}
@Get('stats')
stats(@CurrentUser() user: AuthUser) {
return this.assoc.getStats(user.actorId);
}
@Get('orders')
assocOrders(
@CurrentUser() user: AuthUser,
@Query('page') page = '1',
@Query('pageSize') pageSize = '20',
) {
return this.assoc.listAssocOrders(user.actorId, Number(page) || 1, Number(pageSize) || 20);
}
@Get('users')
users(
@CurrentUser() user: AuthUser,
@Query('page') page = '1',
@Query('pageSize') pageSize = '20',
@Query('keyword') keyword?: string,
@Query('sort') sort?: string,
) {
const allowed = sort === 'createdAt' || sort === 'boundAt' || sort === 'orderCount' ? sort : undefined;
return this.assoc.listUsers(user.actorId, Number(page) || 1, Number(pageSize) || 20, true, {
keyword,
sort: allowed,
});
}
@Get('users/:userId/orders')
userOrders(
@CurrentUser() user: AuthUser,
@Param('userId') userId: string,
@Query('page') page = '1',
@Query('pageSize') pageSize = '20',
) {
return this.assoc.listAssocOrders(
user.actorId,
Number(page) || 1,
Number(pageSize) || 20,
BigInt(userId),
);
}
@Put('users/:userId/remark')
setRemark(
@CurrentUser() user: AuthUser,
@Param('userId') userId: string,
@Body() dto: PartnerAssocRemarkDto,
) {
return this.assoc.setUserRemark(user.actorId, BigInt(userId), dto.remark);
}
@Get('qrcode')
async qrcode(@CurrentUser() user: AuthUser, @Res() res: Response) {
const { buffer, fileName } = await this.assoc.getQrcodeBuffer(user.actorId);
res.setHeader('Content-Type', 'image/png');
res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
res.send(buffer);
}
}
@Controller('partner/commissions')
@UseGuards(JwtAuthGuard, PartnerPrimaryGuard)
export class PartnerCommissionController {
constructor(private readonly assoc: PartnerAssocService) {}
@Get('orders')
orders(
@CurrentUser() user: AuthUser,
@Query('page') page = '1',
@Query('pageSize') pageSize = '20',
) {
return this.assoc.listCommissionOrders(user.actorId, Number(page) || 1, Number(pageSize) || 20);
}
}