Skip to content

Commit 7b59ad7

Browse files
authored
ORV2-1857 - Staff Suspend COmpany (#1200)
1 parent 115c851 commit 7b59ad7

19 files changed

+534
-1
lines changed

database/mssql/scripts/versions/revert/v_15_ddl_revert.sql

+13
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,22 @@ SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
1111
GO
1212
BEGIN TRANSACTION
1313
GO
14+
1415
IF @@ERROR <> 0 SET NOEXEC ON
1516
GO
1617

18+
DELETE FROM [access].[ORBC_GROUP_ROLE] WHERE ROLE_TYPE IN (
19+
'ORBC-WRITE-SUSPEND',
20+
'ORBC-READ-SUSPEND'
21+
)
22+
GO
23+
24+
DELETE FROM [access].[ORBC_ROLE_TYPE] WHERE ROLE_TYPE IN (
25+
'ORBC-WRITE-SUSPEND',
26+
'ORBC-READ-SUSPEND'
27+
)
28+
GO
29+
1730
-- Revert history trigger
1831
ALTER TRIGGER ORBC_COMPNY_A_S_IUD_TR ON [dbo].[ORBC_COMPANY]
1932
FOR INSERT,

database/mssql/scripts/versions/v_15_ddl.sql

+12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ GO
1414
IF @@ERROR <> 0 SET NOEXEC ON
1515
GO
1616

17+
-- Add new auth roles
18+
INSERT [access].[ORBC_ROLE_TYPE] ([ROLE_TYPE], [ROLE_DESCRIPTION]) VALUES (N'ORBC-WRITE-SUSPEND', NULL)
19+
INSERT [access].[ORBC_ROLE_TYPE] ([ROLE_TYPE], [ROLE_DESCRIPTION]) VALUES (N'ORBC-READ-SUSPEND', NULL)
20+
GO
21+
22+
-- HQADMIN roles
23+
INSERT [access].[ORBC_GROUP_ROLE] ([USER_AUTH_GROUP_TYPE], [ROLE_TYPE]) VALUES (N'FINANCE', N'ORBC-WRITE-SUSPEND')
24+
INSERT [access].[ORBC_GROUP_ROLE] ([USER_AUTH_GROUP_TYPE], [ROLE_TYPE]) VALUES (N'SYSADMIN', N'ORBC-WRITE-SUSPEND')
25+
INSERT [access].[ORBC_GROUP_ROLE] ([USER_AUTH_GROUP_TYPE], [ROLE_TYPE]) VALUES (N'PPCCLERK', N'ORBC-READ-SUSPEND')
26+
INSERT [access].[ORBC_GROUP_ROLE] ([USER_AUTH_GROUP_TYPE], [ROLE_TYPE]) VALUES (N'FINANCE', N'ORBC-READ-SUSPEND')
27+
INSERT [access].[ORBC_GROUP_ROLE] ([USER_AUTH_GROUP_TYPE], [ROLE_TYPE]) VALUES (N'SYSADMIN', N'ORBC-READ-SUSPEND')
28+
1729
-- Add IS_SUSPENDED flag to ORBC_COMPANY
1830
ALTER TABLE [dbo].[ORBC_COMPANY]
1931
ADD [IS_SUSPENDED] [char](1) NOT NULL

dops/src/modules/auth/jwt.strategy.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
122122
await this.authService.getCompaniesForUser(access_token);
123123
associatedCompanies = (
124124
companiesForUsersResponse.data as [
125-
{ companyId: number; clientNumber: string; legalName: string },
125+
{ companyId: number; clientNumber: string; legalName: string, email: string, isSuspended: boolean },
126126
]
127127
).map((company) => {
128128
return company.companyId;

vehicles/src/app.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { getTypeormLogLevel } from './common/helper/logger.helper';
3232
import { ClsModule } from 'nestjs-cls';
3333
import { Request } from 'express';
3434
import { v4 as uuidv4 } from 'uuid';
35+
import { CompanySuspendModule } from './modules/company-user-management/company-suspend/company-suspend.module';
3536

3637
const envPath = path.resolve(process.cwd() + '/../');
3738

@@ -84,6 +85,7 @@ const envPath = path.resolve(process.cwd() + '/../');
8485
PowerUnitTypesModule,
8586
TrailerTypesModule,
8687
CompanyModule,
88+
CompanySuspendModule,
8789
UsersModule,
8890
CommonModule,
8991
PendingUsersModule,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {
2+
ValidatorConstraint,
3+
ValidatorConstraintInterface,
4+
ValidationArguments,
5+
} from 'class-validator';
6+
import { SuspendActivity } from '../enum/suspend-activity.enum';
7+
8+
@ValidatorConstraint({ name: 'SuspendComment', async: false })
9+
export class SuspendCommentConstraint implements ValidatorConstraintInterface {
10+
validate(comment: string | undefined, args: ValidationArguments) {
11+
const suspendAcitivity = (
12+
args.object as {
13+
suspendAcitivity?: SuspendActivity;
14+
}
15+
).suspendAcitivity; // Access the searchString property from the same object
16+
17+
// If SuspendActivity.SUSPEND_COMPANY, comment should exists
18+
if (suspendAcitivity === SuspendActivity.SUSPEND_COMPANY && !comment) {
19+
return false;
20+
}
21+
22+
return true;
23+
}
24+
25+
defaultMessage() {
26+
return `Comment is required when activity type is ${SuspendActivity.SUSPEND_COMPANY}`;
27+
}
28+
}

vehicles/src/common/enum/roles.enum.ts

+2
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,6 @@ export enum Role {
4545
WRITE_LCV_FLAG = 'ORBC-WRITE-LCV-FLAG',
4646
READ_LOA = 'ORBC-READ-LOA',
4747
WRITE_LOA = 'ORBC-WRITE-LOA',
48+
READ_SUSPEND = 'ORBC-READ-SUSPEND',
49+
WRITE_SUSPEND = 'ORBC-WRITE-SUSPEND',
4850
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum SuspendActivity {
2+
SUSPEND_COMPANY = 'SUSPEND',
3+
UNSUSPEND_COMPANY = 'UNSUSPEND',
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Controller, Get, Post, Body, Param, Req } from '@nestjs/common';
2+
import { CompanySuspendService } from './company-suspend.service';
3+
import {
4+
ApiTags,
5+
ApiBadRequestResponse,
6+
ApiNotFoundResponse,
7+
ApiMethodNotAllowedResponse,
8+
ApiInternalServerErrorResponse,
9+
ApiBearerAuth,
10+
ApiCreatedResponse,
11+
ApiOkResponse,
12+
} from '@nestjs/swagger';
13+
import { ExceptionDto } from '../../../common/exception/exception.dto';
14+
import { CreateCompanySuspendDto } from './dto/request/create-company-suspend.dto';
15+
import { IUserJWT } from '../../../common/interface/user-jwt.interface';
16+
import { ReadCompanySuspendActivityDto } from './dto/response/read-company-suspend-activity.dto';
17+
import { Request } from 'express';
18+
import { Roles } from '../../../common/decorator/roles.decorator';
19+
import { Role } from '../../../common/enum/roles.enum';
20+
21+
@ApiTags('Company and User Management - Company Suspend')
22+
@ApiBadRequestResponse({
23+
description: 'Bad Request Response',
24+
type: ExceptionDto,
25+
})
26+
@ApiNotFoundResponse({
27+
description: 'The Company Suspend Api Not Found Response',
28+
type: ExceptionDto,
29+
})
30+
@ApiMethodNotAllowedResponse({
31+
description: 'The Company Suspend Api Method Not Allowed Response',
32+
type: ExceptionDto,
33+
})
34+
@ApiInternalServerErrorResponse({
35+
description: 'The Company Suspend Api Internal Server Error Response',
36+
type: ExceptionDto,
37+
})
38+
@ApiBearerAuth()
39+
@Controller('companies/:companyId/company-suspend')
40+
export class CompanySuspendController {
41+
constructor(private readonly companySuspendService: CompanySuspendService) {}
42+
43+
/**
44+
* A POST method defined with the @Post() decorator and a route of /:companyId/suspend
45+
* that suspends a company based on the company ID provided. It also creates a record of
46+
* the suspension activity.
47+
*
48+
* @param createCompanySuspendDto The http request object containing the suspension details.
49+
*
50+
* @returns The details of the suspension activity with
51+
* response object {@link ReadCompanySuspendActivityDto}
52+
*/
53+
@ApiCreatedResponse({
54+
description: 'The Company Suspension Activity Resource',
55+
type: ReadCompanySuspendActivityDto,
56+
})
57+
@Roles(Role.WRITE_SUSPEND)
58+
@Post('suspend')
59+
async suspendCompany(
60+
@Req() request: Request,
61+
@Param('companyId') companyId: number,
62+
@Body() createCompanySuspendDto: CreateCompanySuspendDto,
63+
): Promise<ReadCompanySuspendActivityDto> {
64+
const currentUser = request.user as IUserJWT;
65+
return await this.companySuspendService.suspendCompany(
66+
companyId,
67+
createCompanySuspendDto,
68+
currentUser,
69+
);
70+
}
71+
72+
/**
73+
* A GET method defined with the @Get() decorator and a route of /:companyId/suspend
74+
* that retrieves all suspend activities by companyId.
75+
*
76+
* @param companyId The company Id.
77+
*
78+
* @returns The suspend activities with response object {@link ReadCompanySuspendActivityDto}.
79+
*/
80+
@ApiOkResponse({
81+
description: 'The Company Suspend Activity Resource',
82+
type: ReadCompanySuspendActivityDto,
83+
isArray: true,
84+
})
85+
@Roles(Role.READ_SUSPEND)
86+
@Get('suspend')
87+
async findAllSuspendActivityByCompanyId(
88+
@Param('companyId') companyId: number,
89+
): Promise<ReadCompanySuspendActivityDto[]> {
90+
const suspendActivityList =
91+
await this.companySuspendService.findAllSuspendActivityByCompanyId(
92+
companyId,
93+
);
94+
return suspendActivityList;
95+
}
96+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Module } from '@nestjs/common';
2+
import { CompanySuspendService } from './company-suspend.service';
3+
import { CompanySuspendController } from './company-suspend.controller';
4+
import { TypeOrmModule } from '@nestjs/typeorm';
5+
import { CompanyModule } from '../company/company.module';
6+
import { CompanySuspendProfile } from './profiles/company-suspend.profile';
7+
import { CompanySuspendActivity } from './entities/company-suspend-activity.entity';
8+
9+
@Module({
10+
imports: [TypeOrmModule.forFeature([CompanySuspendActivity]), CompanyModule],
11+
controllers: [CompanySuspendController],
12+
providers: [CompanySuspendService, CompanySuspendProfile],
13+
})
14+
export class CompanySuspendModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
2+
3+
import { Mapper } from '@automapper/core';
4+
import { InjectMapper } from '@automapper/nestjs';
5+
import { InjectRepository } from '@nestjs/typeorm';
6+
import { Repository, DataSource } from 'typeorm';
7+
import { LogAsyncMethodExecution } from '../../../common/decorator/log-async-method-execution.decorator';
8+
import { SuspendActivity } from '../../../common/enum/suspend-activity.enum';
9+
import { DataNotFoundException } from '../../../common/exception/data-not-found.exception';
10+
import { IUserJWT } from '../../../common/interface/user-jwt.interface';
11+
import { Company } from '../company/entities/company.entity';
12+
import { IdirUser } from '../users/entities/idir.user.entity';
13+
import { CompanyService } from '../company/company.service';
14+
import { CreateCompanySuspendDto } from './dto/request/create-company-suspend.dto';
15+
import { ReadCompanySuspendActivityDto } from './dto/response/read-company-suspend-activity.dto';
16+
import { CompanySuspendActivity } from './entities/company-suspend-activity.entity';
17+
18+
@Injectable()
19+
export class CompanySuspendService {
20+
private readonly logger = new Logger(CompanySuspendService.name);
21+
constructor(
22+
@InjectRepository(CompanySuspendActivity)
23+
private companySuspendRepository: Repository<CompanySuspendActivity>,
24+
@InjectMapper() private readonly classMapper: Mapper,
25+
private dataSource: DataSource,
26+
private readonly companyService: CompanyService,
27+
) {}
28+
29+
/**
30+
* The update() method retrieves the entity from the database using the
31+
* Repository, maps the DTO object to the entity using the Mapper, sets some
32+
* additional properties on the entity, and saves it back to the database
33+
* using the Repository. It then retrieves the updated entity and returns it
34+
* in a DTO object.
35+
*
36+
*
37+
* @param companyId The company Id.
38+
* @param updateCompanyDto Request object of type {@link UpdateCompanyDto} for
39+
* updating a company.
40+
* @param directory Directory derived from the access token.
41+
*
42+
* @returns The company details as a promise of type {@link ReadCompanyDto}
43+
*/
44+
@LogAsyncMethodExecution()
45+
async suspendCompany(
46+
companyId: number,
47+
createCompanySuspendDtonyDto: CreateCompanySuspendDto,
48+
currentUser: IUserJWT,
49+
): Promise<ReadCompanySuspendActivityDto> {
50+
let savedSuspendAcitivity: CompanySuspendActivity;
51+
const company = await this.companyService.findOne(companyId);
52+
53+
if (!company) {
54+
throw new DataNotFoundException();
55+
}
56+
const toBeSuspended: boolean =
57+
createCompanySuspendDtonyDto.suspendActivityType ===
58+
SuspendActivity.SUSPEND_COMPANY;
59+
if (company.isSuspended === toBeSuspended) {
60+
throw new BadRequestException(
61+
`Company ${companyId} is already in ${company.isSuspended ? SuspendActivity.SUSPEND_COMPANY : SuspendActivity.UNSUSPEND_COMPANY} status`,
62+
);
63+
}
64+
65+
const queryRunner = this.dataSource.createQueryRunner();
66+
await queryRunner.connect();
67+
await queryRunner.startTransaction();
68+
try {
69+
const currentDateTime: Date = new Date();
70+
71+
await queryRunner.manager
72+
.createQueryBuilder()
73+
.update(Company)
74+
.set({
75+
isSuspended: toBeSuspended,
76+
updatedUser: currentUser.userName,
77+
updatedDateTime: currentDateTime,
78+
updatedUserDirectory: currentUser.orbcUserDirectory,
79+
updatedUserGuid: currentUser.userGUID,
80+
})
81+
.where('companyId = :companyId', { companyId: companyId })
82+
.execute();
83+
84+
const suspendActivity: CompanySuspendActivity =
85+
new CompanySuspendActivity();
86+
87+
suspendActivity.companyId = companyId;
88+
suspendActivity.suspendActivityType =
89+
createCompanySuspendDtonyDto.suspendActivityType;
90+
suspendActivity.comment = createCompanySuspendDtonyDto.comment;
91+
suspendActivity.suspendActivityDateTime = currentDateTime;
92+
const idirUser = new IdirUser();
93+
idirUser.userGUID = currentUser.userGUID;
94+
suspendActivity.idirUser = idirUser;
95+
suspendActivity.createdUser = currentUser.userName;
96+
suspendActivity.createdUserGuid = currentUser.userGUID;
97+
suspendActivity.createdUserDirectory = currentUser.orbcUserDirectory;
98+
suspendActivity.createdDateTime = currentDateTime;
99+
suspendActivity.updatedUser = currentUser.userName;
100+
suspendActivity.updatedUserGuid = currentUser.userGUID;
101+
suspendActivity.updatedUserDirectory = currentUser.orbcUserDirectory;
102+
suspendActivity.updatedDateTime = currentDateTime;
103+
104+
savedSuspendAcitivity = await queryRunner.manager.save(suspendActivity);
105+
106+
await queryRunner.commitTransaction();
107+
} catch (error) {
108+
await queryRunner.rollbackTransaction();
109+
this.logger.error(error);
110+
throw error;
111+
} finally {
112+
await queryRunner.release();
113+
}
114+
115+
const result = await this.classMapper.mapAsync(
116+
savedSuspendAcitivity,
117+
CompanySuspendActivity,
118+
ReadCompanySuspendActivityDto,
119+
);
120+
result.userName = currentUser.userName?.toUpperCase();
121+
return result;
122+
}
123+
124+
/**
125+
* The findAllSuspendActivityByCompanyId() method retrieves all suspend activities associated with a given company ID.
126+
* It queries the suspend activity repository for records matching the company ID and includes the relation to the IDIR user.
127+
* The results are then mapped from the CompanySuspendActivity entities to ReadCompanySuspendActivityDto objects for presentation.
128+
*
129+
* @param companyId The unique identifier of the company.
130+
*
131+
* @returns A promise containing an array of ReadCompanySuspendActivityDto objects representing the suspend activities for the specified company.
132+
*/
133+
@LogAsyncMethodExecution()
134+
async findAllSuspendActivityByCompanyId(
135+
companyId: number,
136+
): Promise<ReadCompanySuspendActivityDto[]> {
137+
const suspendActivityDbResults = await this.companySuspendRepository.find({
138+
where: { companyId: companyId },
139+
relations: {
140+
idirUser: true,
141+
},
142+
});
143+
144+
return await this.classMapper.mapArrayAsync(
145+
suspendActivityDbResults,
146+
CompanySuspendActivity,
147+
ReadCompanySuspendActivityDto,
148+
);
149+
}
150+
}

0 commit comments

Comments
 (0)