Add salesperson commissions on issued invoices and a cheque register.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsEmail,
|
||||
IsIn,
|
||||
IsNumber,
|
||||
IsString,
|
||||
Max,
|
||||
MaxLength,
|
||||
Min,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
|
||||
@@ -31,3 +35,11 @@ export class UpdateUserRoleDto {
|
||||
@IsIn(ALL_ROLES)
|
||||
role!: (typeof ALL_ROLES)[number];
|
||||
}
|
||||
|
||||
export class UpdateUserCommissionDto {
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(100)
|
||||
commissionPercent!: number;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
import { Roles } from '../auth/roles.decorator';
|
||||
import { RolesGuard } from '../auth/roles.guard';
|
||||
import { InviteUserDto, UpdateUserRoleDto } from './dto';
|
||||
import { InviteUserDto, UpdateUserCommissionDto, UpdateUserRoleDto } from './dto';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
@Controller('users')
|
||||
@@ -34,6 +34,21 @@ export class UsersController {
|
||||
return this.usersService.invite(user.organizationId, user.role, dto);
|
||||
}
|
||||
|
||||
@Patch(':id/commission')
|
||||
@Roles('owner', 'admin')
|
||||
updateCommission(
|
||||
@CurrentUser() user: { organizationId: string; role: UserRole },
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateUserCommissionDto,
|
||||
) {
|
||||
return this.usersService.updateCommission(
|
||||
user.organizationId,
|
||||
user.role,
|
||||
id,
|
||||
dto.commissionPercent,
|
||||
);
|
||||
}
|
||||
|
||||
@Patch(':id/role')
|
||||
@Roles('owner', 'admin')
|
||||
updateRole(
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import type { InviteUserRequest, OrgUserDto, UserRole } from '@mento/shared';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
@@ -56,6 +57,29 @@ export class UsersService {
|
||||
return this.toDto(row);
|
||||
}
|
||||
|
||||
async updateCommission(
|
||||
organizationId: string,
|
||||
actorRole: UserRole,
|
||||
targetId: string,
|
||||
percent: number,
|
||||
): Promise<OrgUserDto> {
|
||||
if (actorRole !== 'owner' && actorRole !== 'admin') {
|
||||
throw new ForbiddenException('فقط مالک یا مدیر میتواند پورسانت را تعیین کند');
|
||||
}
|
||||
if (percent < 0 || percent > 100) {
|
||||
throw new BadRequestException('درصد پورسانت باید بین ۰ تا ۱۰۰ باشد');
|
||||
}
|
||||
const target = await this.prisma.user.findFirst({
|
||||
where: { id: targetId, organizationId },
|
||||
});
|
||||
if (!target) throw new NotFoundException('کاربر پیدا نشد');
|
||||
const row = await this.prisma.user.update({
|
||||
where: { id: target.id },
|
||||
data: { commissionPercent: new Prisma.Decimal(percent) },
|
||||
});
|
||||
return this.toDto(row);
|
||||
}
|
||||
|
||||
async updateRole(
|
||||
organizationId: string,
|
||||
actor: { userId: string; role: UserRole },
|
||||
@@ -100,6 +124,7 @@ export class UsersService {
|
||||
email: string;
|
||||
fullName: string;
|
||||
role: UserRole;
|
||||
commissionPercent?: { toString(): string } | number;
|
||||
createdAt: Date;
|
||||
}): OrgUserDto {
|
||||
return {
|
||||
@@ -107,6 +132,7 @@ export class UsersService {
|
||||
email: row.email,
|
||||
fullName: row.fullName,
|
||||
role: row.role,
|
||||
commissionPercent: Number(row.commissionPercent ?? 0),
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user