This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../dmenu-admin"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
@@ -215,6 +215,17 @@ export class UsersController {
|
||||
return this.userService.adminUpdateUser(userId, restId, dto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_USERS)
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiOperation({ summary: 'Get user groups for a customer (admin)' })
|
||||
@ApiParam({ name: 'userId', description: 'User ID' })
|
||||
@Get('admin/users/:userId/groups')
|
||||
async adminGetUserGroups(@Param('userId') userId: string, @RestId() restId: string) {
|
||||
return this.userService.getUserGroups(userId, restId);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_USERS)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsBoolean, IsMobilePhone, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import { IsArray, IsBoolean, IsMobilePhone, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateUserDto {
|
||||
@ApiProperty({ description: "User's phone number", example: '09121234567' })
|
||||
@@ -37,4 +37,15 @@ export class CreateUserDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
avatarUrl?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'User group IDs to assign the customer to',
|
||||
type: [String],
|
||||
example: ['01HXABCDEFGHIJKLMNOPQRSTUV'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@IsNotEmpty({ each: true })
|
||||
groupIds?: string[];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IsString, IsOptional, IsBoolean } from 'class-validator';
|
||||
import { IsString, IsOptional, IsBoolean, IsArray, IsNotEmpty } from 'class-validator';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class UpdateUserDto {
|
||||
@@ -32,4 +32,15 @@ export class UpdateUserDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
avatarUrl?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'User group IDs to assign the customer to (replaces current groups)',
|
||||
type: [String],
|
||||
example: ['01HXABCDEFGHIJKLMNOPQRSTUV'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@IsNotEmpty({ each: true })
|
||||
groupIds?: string[];
|
||||
}
|
||||
|
||||
@@ -138,4 +138,42 @@ export class UserGroupService {
|
||||
group.count = Math.max(0, group.count - 1);
|
||||
return group;
|
||||
}
|
||||
|
||||
async assignUserToGroups(restId: string, userId: string, groupIds: string[]): Promise<void> {
|
||||
const uniqueGroupIds = [...new Set(groupIds)];
|
||||
for (const groupId of uniqueGroupIds) {
|
||||
await this.addUsers(restId, groupId, { userIds: [userId] });
|
||||
}
|
||||
}
|
||||
|
||||
async getGroupsForUser(restId: string, userId: string): Promise<UserGroup[]> {
|
||||
const links = await this.userGroupUserRepository.find(
|
||||
{
|
||||
user: { id: userId },
|
||||
userGroup: { restaurant: { id: restId }, deletedAt: null },
|
||||
},
|
||||
{ populate: ['userGroup'], orderBy: { createdAt: 'asc' } },
|
||||
);
|
||||
|
||||
return links.map(link => link.userGroup);
|
||||
}
|
||||
|
||||
async syncUserGroups(restId: string, userId: string, groupIds: string[]): Promise<void> {
|
||||
const targetGroupIds = [...new Set(groupIds)];
|
||||
const currentGroups = await this.getGroupsForUser(restId, userId);
|
||||
const currentGroupIds = new Set(currentGroups.map(group => group.id));
|
||||
const targetGroupIdsSet = new Set(targetGroupIds);
|
||||
|
||||
for (const group of currentGroups) {
|
||||
if (!targetGroupIdsSet.has(group.id)) {
|
||||
await this.removeUser(restId, group.id, userId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const groupId of targetGroupIds) {
|
||||
if (!currentGroupIds.has(groupId)) {
|
||||
await this.addUsers(restId, groupId, { userIds: [userId] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { UserRestaurant } from '../entities/user-restuarant.entity';
|
||||
import { ImportUsersResult } from '../interfaces/import-users.interface';
|
||||
import { assertExcelMimeType, parseUsersExcel } from '../utils/import-users-excel.util';
|
||||
import { AuthMessage, UserImportMessage } from 'src/common/enums/message.enum';
|
||||
import { UserGroupService } from './user-group.service';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
@@ -35,6 +36,7 @@ export class UserService {
|
||||
private readonly walletService: WalletService,
|
||||
private readonly em: EntityManager,
|
||||
private readonly userRestaurantRepository: UserRestaurantRepository,
|
||||
private readonly userGroupService: UserGroupService,
|
||||
) { }
|
||||
|
||||
async findOrCreateByPhone(phone: string): Promise<User> {
|
||||
@@ -189,6 +191,10 @@ export class UserService {
|
||||
this.em.persist(userRestaurant);
|
||||
await this.em.flush();
|
||||
|
||||
if (dto.groupIds?.length) {
|
||||
await this.userGroupService.assignUserToGroups(restId, user.id, dto.groupIds);
|
||||
}
|
||||
|
||||
await this.em.populate(userRestaurant, ['user']);
|
||||
return userRestaurant;
|
||||
}
|
||||
@@ -202,7 +208,26 @@ export class UserService {
|
||||
throw new NotFoundException(`User with ID ${userId} not found.`);
|
||||
}
|
||||
|
||||
return this.updateUser(userId, restId, dto);
|
||||
const { groupIds, ...userDto } = dto;
|
||||
const user = await this.updateUser(userId, restId, userDto);
|
||||
|
||||
if (groupIds !== undefined) {
|
||||
await this.userGroupService.syncUserGroups(restId, userId, groupIds);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async getUserGroups(userId: string, restId: string) {
|
||||
const userRestaurant = await this.userRestaurantRepository.findOne({
|
||||
user: { id: userId },
|
||||
restaurant: { id: restId },
|
||||
});
|
||||
if (!userRestaurant) {
|
||||
throw new NotFoundException(`User with ID ${userId} not found.`);
|
||||
}
|
||||
|
||||
return this.userGroupService.getGroupsForUser(restId, userId);
|
||||
}
|
||||
|
||||
async updateUser(userId: string, restId: string, dto: UpdateUserDto): Promise<User> {
|
||||
@@ -211,15 +236,16 @@ export class UserService {
|
||||
throw new NotFoundException(`User with ID ${userId} not found.`);
|
||||
}
|
||||
|
||||
const { groupIds: _groupIds, ...userFields } = dto;
|
||||
|
||||
// Normalize date strings into Date objects if provided
|
||||
const assignData: Partial<User> = { ...dto } as Partial<User>;
|
||||
if (dto.birthDate) {
|
||||
assignData.birthDate = new Date(dto.birthDate);
|
||||
const assignData: Partial<User> = { ...userFields } as Partial<User>;
|
||||
if (userFields.birthDate) {
|
||||
assignData.birthDate = new Date(userFields.birthDate);
|
||||
}
|
||||
if (dto.marriageDate) {
|
||||
assignData.marriageDate = new Date(dto.marriageDate);
|
||||
if (userFields.marriageDate) {
|
||||
assignData.marriageDate = new Date(userFields.marriageDate);
|
||||
}
|
||||
// get restuarant
|
||||
this.em.assign(user, assignData);
|
||||
await this.em.flush();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user