690329:1621 Fixing superadmin by GPT-5.3 #01
CI / CD Pipeline / build (push) Successful in 14m25s
CI / CD Pipeline / deploy (push) Successful in 4m41s

This commit is contained in:
2026-03-29 16:21:57 +07:00
parent 2074654c18
commit 65aaae9d90
22 changed files with 3145 additions and 15 deletions
@@ -19,6 +19,7 @@ import { FileStorageService } from '../../common/file-storage/file-storage.servi
import { UuidResolverService } from '../../common/services/uuid-resolver.service';
import { NotificationService } from '../notification/notification.service';
import { UpdateCorrespondenceDto } from './dto/update-correspondence.dto';
import { CreateCorrespondenceDto } from './dto/create-correspondence.dto';
import { User } from '../user/entities/user.entity';
describe('CorrespondenceService', () => {
@@ -336,4 +337,95 @@ describe('CorrespondenceService', () => {
).toHaveBeenCalled();
});
});
describe('create', () => {
it('should allow system.manage_all user without primaryOrganizationId when originatorId is provided', async () => {
const mockUser = {
user_id: 1,
primaryOrganizationId: null,
} as unknown as User;
const createDto: CreateCorrespondenceDto = {
projectId: 'project-uuid',
typeId: 1,
subject: 'Test Subject',
originatorId: 'originator-uuid',
recipients: [{ organizationId: 'recipient-uuid', type: 'TO' }],
};
const userService = testingModule.get<UserService>(UserService);
const typeRepo = testingModule.get<Repository<CorrespondenceType>>(
getRepositoryToken(CorrespondenceType)
);
const statusRepo = testingModule.get<Repository<CorrespondenceStatus>>(
getRepositoryToken(CorrespondenceStatus)
);
const uuidResolver =
testingModule.get<UuidResolverService>(UuidResolverService);
(userService.findOne as jest.Mock).mockResolvedValue({
user_id: 1,
primaryOrganizationId: null,
});
(userService.getUserPermissions as jest.Mock).mockResolvedValue([
'system.manage_all',
]);
(uuidResolver.resolveProjectId as jest.Mock).mockResolvedValue(100);
(uuidResolver.resolveOrganizationId as jest.Mock).mockImplementation(
(value: number | string) => {
if (value === 'originator-uuid') return 10;
if (value === 'recipient-uuid') return 20;
return 0;
}
);
(typeRepo.findOne as jest.Mock).mockResolvedValue({
id: 1,
typeCode: 'LTR',
});
(statusRepo.findOne as jest.Mock).mockResolvedValue({
id: 1,
statusCode: 'DRAFT',
});
(numberingService.generateNextNumber as jest.Mock).mockResolvedValue({
number: 'DOC-001',
});
mockDataSource.manager.findOne
.mockResolvedValueOnce({ id: 10, organizationCode: 'ORG' })
.mockResolvedValueOnce({ id: 20, organizationCode: 'REC' });
const queryRunner = {
connect: jest.fn(),
startTransaction: jest.fn(),
commitTransaction: jest.fn(),
rollbackTransaction: jest.fn(),
release: jest.fn(),
manager: {
create: jest.fn(
(_entity: unknown, payload: Record<string, unknown>) => payload
),
save: jest
.fn()
.mockResolvedValueOnce({ id: 999, publicId: 'corr-uuid' })
.mockResolvedValueOnce({ id: 1000 })
.mockResolvedValueOnce([]),
findOne: jest.fn(),
},
};
(mockDataSource.createQueryRunner as jest.Mock).mockReturnValue(
queryRunner
);
await service.create(createDto, mockUser);
expect(queryRunner.manager.create).toHaveBeenCalledWith(
Correspondence,
expect.objectContaining({ originatorId: 10 })
);
});
});
});
@@ -50,6 +50,11 @@ interface ResolvedRecipient {
export class CorrespondenceService {
private readonly logger = new Logger(CorrespondenceService.name);
private async hasSystemManageAllPermission(userId: number): Promise<boolean> {
const permissions = await this.userService.getUserPermissions(userId);
return permissions.includes('system.manage_all');
}
constructor(
@InjectRepository(Correspondence)
private correspondenceRepo: Repository<Correspondence>,
@@ -92,9 +97,22 @@ export class CorrespondenceService {
}
if (!userOrgId) {
throw new BadRequestException(
'User must belong to an organization to create documents'
);
if (createDto.originatorId) {
const canManageAll = await this.hasSystemManageAllPermission(
user.user_id
);
if (canManageAll) {
userOrgId = await this.uuidResolver.resolveOrganizationId(
createDto.originatorId
);
}
}
if (!userOrgId) {
throw new BadRequestException(
'User must belong to an organization to create documents'
);
}
}
// For impersonation, use the specified originator
@@ -187,10 +205,10 @@ export class CorrespondenceService {
// Impersonation Logic
if (resolvedOriginatorId && resolvedOriginatorId !== userOrgId) {
const permissions = await this.userService.getUserPermissions(
const canManageAll = await this.hasSystemManageAllPermission(
user.user_id
);
if (!permissions.includes('system.manage_all')) {
if (!canManageAll) {
throw new ForbiddenException(
'You do not have permission to create documents on behalf of other organizations.'
);