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);
|
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)
|
@UseGuards(AdminAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@Permissions(Permission.MANAGE_USERS)
|
@Permissions(Permission.MANAGE_USERS)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
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 {
|
export class CreateUserDto {
|
||||||
@ApiProperty({ description: "User's phone number", example: '09121234567' })
|
@ApiProperty({ description: "User's phone number", example: '09121234567' })
|
||||||
@@ -37,4 +37,15 @@ export class CreateUserDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
avatarUrl?: string;
|
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';
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class UpdateUserDto {
|
export class UpdateUserDto {
|
||||||
@@ -32,4 +32,15 @@ export class UpdateUserDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
avatarUrl?: string;
|
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);
|
group.count = Math.max(0, group.count - 1);
|
||||||
return group;
|
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 { ImportUsersResult } from '../interfaces/import-users.interface';
|
||||||
import { assertExcelMimeType, parseUsersExcel } from '../utils/import-users-excel.util';
|
import { assertExcelMimeType, parseUsersExcel } from '../utils/import-users-excel.util';
|
||||||
import { AuthMessage, UserImportMessage } from 'src/common/enums/message.enum';
|
import { AuthMessage, UserImportMessage } from 'src/common/enums/message.enum';
|
||||||
|
import { UserGroupService } from './user-group.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
@@ -35,6 +36,7 @@ export class UserService {
|
|||||||
private readonly walletService: WalletService,
|
private readonly walletService: WalletService,
|
||||||
private readonly em: EntityManager,
|
private readonly em: EntityManager,
|
||||||
private readonly userRestaurantRepository: UserRestaurantRepository,
|
private readonly userRestaurantRepository: UserRestaurantRepository,
|
||||||
|
private readonly userGroupService: UserGroupService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async findOrCreateByPhone(phone: string): Promise<User> {
|
async findOrCreateByPhone(phone: string): Promise<User> {
|
||||||
@@ -189,6 +191,10 @@ export class UserService {
|
|||||||
this.em.persist(userRestaurant);
|
this.em.persist(userRestaurant);
|
||||||
await this.em.flush();
|
await this.em.flush();
|
||||||
|
|
||||||
|
if (dto.groupIds?.length) {
|
||||||
|
await this.userGroupService.assignUserToGroups(restId, user.id, dto.groupIds);
|
||||||
|
}
|
||||||
|
|
||||||
await this.em.populate(userRestaurant, ['user']);
|
await this.em.populate(userRestaurant, ['user']);
|
||||||
return userRestaurant;
|
return userRestaurant;
|
||||||
}
|
}
|
||||||
@@ -202,7 +208,26 @@ export class UserService {
|
|||||||
throw new NotFoundException(`User with ID ${userId} not found.`);
|
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> {
|
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.`);
|
throw new NotFoundException(`User with ID ${userId} not found.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { groupIds: _groupIds, ...userFields } = dto;
|
||||||
|
|
||||||
// Normalize date strings into Date objects if provided
|
// Normalize date strings into Date objects if provided
|
||||||
const assignData: Partial<User> = { ...dto } as Partial<User>;
|
const assignData: Partial<User> = { ...userFields } as Partial<User>;
|
||||||
if (dto.birthDate) {
|
if (userFields.birthDate) {
|
||||||
assignData.birthDate = new Date(dto.birthDate);
|
assignData.birthDate = new Date(userFields.birthDate);
|
||||||
}
|
}
|
||||||
if (dto.marriageDate) {
|
if (userFields.marriageDate) {
|
||||||
assignData.marriageDate = new Date(dto.marriageDate);
|
assignData.marriageDate = new Date(userFields.marriageDate);
|
||||||
}
|
}
|
||||||
// get restuarant
|
|
||||||
this.em.assign(user, assignData);
|
this.em.assign(user, assignData);
|
||||||
await this.em.flush();
|
await this.em.flush();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user