Skip to content

Commit

Permalink
ORV2-1857 - Staff Suspend COmpany (#1200)
Browse files Browse the repository at this point in the history
  • Loading branch information
praju-aot authored Feb 13, 2024
1 parent 115c851 commit 7b59ad7
Show file tree
Hide file tree
Showing 19 changed files with 534 additions and 1 deletion.
13 changes: 13 additions & 0 deletions database/mssql/scripts/versions/revert/v_15_ddl_revert.sql
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,22 @@ SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
GO
BEGIN TRANSACTION
GO

IF @@ERROR <> 0 SET NOEXEC ON
GO

DELETE FROM [access].[ORBC_GROUP_ROLE] WHERE ROLE_TYPE IN (
'ORBC-WRITE-SUSPEND',
'ORBC-READ-SUSPEND'
)
GO

DELETE FROM [access].[ORBC_ROLE_TYPE] WHERE ROLE_TYPE IN (
'ORBC-WRITE-SUSPEND',
'ORBC-READ-SUSPEND'
)
GO

-- Revert history trigger
ALTER TRIGGER ORBC_COMPNY_A_S_IUD_TR ON [dbo].[ORBC_COMPANY]
FOR INSERT,
Expand Down
12 changes: 12 additions & 0 deletions database/mssql/scripts/versions/v_15_ddl.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ GO
IF @@ERROR <> 0 SET NOEXEC ON
GO

-- Add new auth roles
INSERT [access].[ORBC_ROLE_TYPE] ([ROLE_TYPE], [ROLE_DESCRIPTION]) VALUES (N'ORBC-WRITE-SUSPEND', NULL)
INSERT [access].[ORBC_ROLE_TYPE] ([ROLE_TYPE], [ROLE_DESCRIPTION]) VALUES (N'ORBC-READ-SUSPEND', NULL)
GO

-- HQADMIN roles
INSERT [access].[ORBC_GROUP_ROLE] ([USER_AUTH_GROUP_TYPE], [ROLE_TYPE]) VALUES (N'FINANCE', N'ORBC-WRITE-SUSPEND')
INSERT [access].[ORBC_GROUP_ROLE] ([USER_AUTH_GROUP_TYPE], [ROLE_TYPE]) VALUES (N'SYSADMIN', N'ORBC-WRITE-SUSPEND')
INSERT [access].[ORBC_GROUP_ROLE] ([USER_AUTH_GROUP_TYPE], [ROLE_TYPE]) VALUES (N'PPCCLERK', N'ORBC-READ-SUSPEND')
INSERT [access].[ORBC_GROUP_ROLE] ([USER_AUTH_GROUP_TYPE], [ROLE_TYPE]) VALUES (N'FINANCE', N'ORBC-READ-SUSPEND')
INSERT [access].[ORBC_GROUP_ROLE] ([USER_AUTH_GROUP_TYPE], [ROLE_TYPE]) VALUES (N'SYSADMIN', N'ORBC-READ-SUSPEND')

-- Add IS_SUSPENDED flag to ORBC_COMPANY
ALTER TABLE [dbo].[ORBC_COMPANY]
ADD [IS_SUSPENDED] [char](1) NOT NULL
Expand Down
2 changes: 1 addition & 1 deletion dops/src/modules/auth/jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
await this.authService.getCompaniesForUser(access_token);
associatedCompanies = (
companiesForUsersResponse.data as [
{ companyId: number; clientNumber: string; legalName: string },
{ companyId: number; clientNumber: string; legalName: string, email: string, isSuspended: boolean },
]
).map((company) => {
return company.companyId;
Expand Down
2 changes: 2 additions & 0 deletions vehicles/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { getTypeormLogLevel } from './common/helper/logger.helper';
import { ClsModule } from 'nestjs-cls';
import { Request } from 'express';
import { v4 as uuidv4 } from 'uuid';
import { CompanySuspendModule } from './modules/company-user-management/company-suspend/company-suspend.module';

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

Expand Down Expand Up @@ -84,6 +85,7 @@ const envPath = path.resolve(process.cwd() + '/../');
PowerUnitTypesModule,
TrailerTypesModule,
CompanyModule,
CompanySuspendModule,
UsersModule,
CommonModule,
PendingUsersModule,
Expand Down
28 changes: 28 additions & 0 deletions vehicles/src/common/constraint/suspend-comment.constraint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from 'class-validator';
import { SuspendActivity } from '../enum/suspend-activity.enum';

@ValidatorConstraint({ name: 'SuspendComment', async: false })
export class SuspendCommentConstraint implements ValidatorConstraintInterface {
validate(comment: string | undefined, args: ValidationArguments) {
const suspendAcitivity = (
args.object as {
suspendAcitivity?: SuspendActivity;
}
).suspendAcitivity; // Access the searchString property from the same object

// If SuspendActivity.SUSPEND_COMPANY, comment should exists
if (suspendAcitivity === SuspendActivity.SUSPEND_COMPANY && !comment) {
return false;
}

return true;
}

defaultMessage() {
return `Comment is required when activity type is ${SuspendActivity.SUSPEND_COMPANY}`;
}
}
2 changes: 2 additions & 0 deletions vehicles/src/common/enum/roles.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,6 @@ export enum Role {
WRITE_LCV_FLAG = 'ORBC-WRITE-LCV-FLAG',
READ_LOA = 'ORBC-READ-LOA',
WRITE_LOA = 'ORBC-WRITE-LOA',
READ_SUSPEND = 'ORBC-READ-SUSPEND',
WRITE_SUSPEND = 'ORBC-WRITE-SUSPEND',
}
4 changes: 4 additions & 0 deletions vehicles/src/common/enum/suspend-activity.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum SuspendActivity {
SUSPEND_COMPANY = 'SUSPEND',
UNSUSPEND_COMPANY = 'UNSUSPEND',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Controller, Get, Post, Body, Param, Req } from '@nestjs/common';
import { CompanySuspendService } from './company-suspend.service';
import {
ApiTags,
ApiBadRequestResponse,
ApiNotFoundResponse,
ApiMethodNotAllowedResponse,
ApiInternalServerErrorResponse,
ApiBearerAuth,
ApiCreatedResponse,
ApiOkResponse,
} from '@nestjs/swagger';
import { ExceptionDto } from '../../../common/exception/exception.dto';
import { CreateCompanySuspendDto } from './dto/request/create-company-suspend.dto';
import { IUserJWT } from '../../../common/interface/user-jwt.interface';
import { ReadCompanySuspendActivityDto } from './dto/response/read-company-suspend-activity.dto';
import { Request } from 'express';
import { Roles } from '../../../common/decorator/roles.decorator';
import { Role } from '../../../common/enum/roles.enum';

@ApiTags('Company and User Management - Company Suspend')
@ApiBadRequestResponse({
description: 'Bad Request Response',
type: ExceptionDto,
})
@ApiNotFoundResponse({
description: 'The Company Suspend Api Not Found Response',
type: ExceptionDto,
})
@ApiMethodNotAllowedResponse({
description: 'The Company Suspend Api Method Not Allowed Response',
type: ExceptionDto,
})
@ApiInternalServerErrorResponse({
description: 'The Company Suspend Api Internal Server Error Response',
type: ExceptionDto,
})
@ApiBearerAuth()
@Controller('companies/:companyId/company-suspend')
export class CompanySuspendController {
constructor(private readonly companySuspendService: CompanySuspendService) {}

/**
* A POST method defined with the @Post() decorator and a route of /:companyId/suspend
* that suspends a company based on the company ID provided. It also creates a record of
* the suspension activity.
*
* @param createCompanySuspendDto The http request object containing the suspension details.
*
* @returns The details of the suspension activity with
* response object {@link ReadCompanySuspendActivityDto}
*/
@ApiCreatedResponse({
description: 'The Company Suspension Activity Resource',
type: ReadCompanySuspendActivityDto,
})
@Roles(Role.WRITE_SUSPEND)
@Post('suspend')
async suspendCompany(
@Req() request: Request,
@Param('companyId') companyId: number,
@Body() createCompanySuspendDto: CreateCompanySuspendDto,
): Promise<ReadCompanySuspendActivityDto> {
const currentUser = request.user as IUserJWT;
return await this.companySuspendService.suspendCompany(
companyId,
createCompanySuspendDto,
currentUser,
);
}

/**
* A GET method defined with the @Get() decorator and a route of /:companyId/suspend
* that retrieves all suspend activities by companyId.
*
* @param companyId The company Id.
*
* @returns The suspend activities with response object {@link ReadCompanySuspendActivityDto}.
*/
@ApiOkResponse({
description: 'The Company Suspend Activity Resource',
type: ReadCompanySuspendActivityDto,
isArray: true,
})
@Roles(Role.READ_SUSPEND)
@Get('suspend')
async findAllSuspendActivityByCompanyId(
@Param('companyId') companyId: number,
): Promise<ReadCompanySuspendActivityDto[]> {
const suspendActivityList =
await this.companySuspendService.findAllSuspendActivityByCompanyId(
companyId,
);
return suspendActivityList;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { CompanySuspendService } from './company-suspend.service';
import { CompanySuspendController } from './company-suspend.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CompanyModule } from '../company/company.module';
import { CompanySuspendProfile } from './profiles/company-suspend.profile';
import { CompanySuspendActivity } from './entities/company-suspend-activity.entity';

@Module({
imports: [TypeOrmModule.forFeature([CompanySuspendActivity]), CompanyModule],
controllers: [CompanySuspendController],
providers: [CompanySuspendService, CompanySuspendProfile],
})
export class CompanySuspendModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { BadRequestException, Injectable, Logger } from '@nestjs/common';

import { Mapper } from '@automapper/core';
import { InjectMapper } from '@automapper/nestjs';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { LogAsyncMethodExecution } from '../../../common/decorator/log-async-method-execution.decorator';
import { SuspendActivity } from '../../../common/enum/suspend-activity.enum';
import { DataNotFoundException } from '../../../common/exception/data-not-found.exception';
import { IUserJWT } from '../../../common/interface/user-jwt.interface';
import { Company } from '../company/entities/company.entity';
import { IdirUser } from '../users/entities/idir.user.entity';
import { CompanyService } from '../company/company.service';
import { CreateCompanySuspendDto } from './dto/request/create-company-suspend.dto';
import { ReadCompanySuspendActivityDto } from './dto/response/read-company-suspend-activity.dto';
import { CompanySuspendActivity } from './entities/company-suspend-activity.entity';

@Injectable()
export class CompanySuspendService {
private readonly logger = new Logger(CompanySuspendService.name);
constructor(
@InjectRepository(CompanySuspendActivity)
private companySuspendRepository: Repository<CompanySuspendActivity>,
@InjectMapper() private readonly classMapper: Mapper,
private dataSource: DataSource,
private readonly companyService: CompanyService,
) {}

/**
* The update() method retrieves the entity from the database using the
* Repository, maps the DTO object to the entity using the Mapper, sets some
* additional properties on the entity, and saves it back to the database
* using the Repository. It then retrieves the updated entity and returns it
* in a DTO object.
*
*
* @param companyId The company Id.
* @param updateCompanyDto Request object of type {@link UpdateCompanyDto} for
* updating a company.
* @param directory Directory derived from the access token.
*
* @returns The company details as a promise of type {@link ReadCompanyDto}
*/
@LogAsyncMethodExecution()
async suspendCompany(
companyId: number,
createCompanySuspendDtonyDto: CreateCompanySuspendDto,
currentUser: IUserJWT,
): Promise<ReadCompanySuspendActivityDto> {
let savedSuspendAcitivity: CompanySuspendActivity;
const company = await this.companyService.findOne(companyId);

if (!company) {
throw new DataNotFoundException();
}
const toBeSuspended: boolean =
createCompanySuspendDtonyDto.suspendActivityType ===
SuspendActivity.SUSPEND_COMPANY;
if (company.isSuspended === toBeSuspended) {
throw new BadRequestException(
`Company ${companyId} is already in ${company.isSuspended ? SuspendActivity.SUSPEND_COMPANY : SuspendActivity.UNSUSPEND_COMPANY} status`,
);
}

const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const currentDateTime: Date = new Date();

await queryRunner.manager
.createQueryBuilder()
.update(Company)
.set({
isSuspended: toBeSuspended,
updatedUser: currentUser.userName,
updatedDateTime: currentDateTime,
updatedUserDirectory: currentUser.orbcUserDirectory,
updatedUserGuid: currentUser.userGUID,
})
.where('companyId = :companyId', { companyId: companyId })
.execute();

const suspendActivity: CompanySuspendActivity =
new CompanySuspendActivity();

suspendActivity.companyId = companyId;
suspendActivity.suspendActivityType =
createCompanySuspendDtonyDto.suspendActivityType;
suspendActivity.comment = createCompanySuspendDtonyDto.comment;
suspendActivity.suspendActivityDateTime = currentDateTime;
const idirUser = new IdirUser();
idirUser.userGUID = currentUser.userGUID;
suspendActivity.idirUser = idirUser;
suspendActivity.createdUser = currentUser.userName;
suspendActivity.createdUserGuid = currentUser.userGUID;
suspendActivity.createdUserDirectory = currentUser.orbcUserDirectory;
suspendActivity.createdDateTime = currentDateTime;
suspendActivity.updatedUser = currentUser.userName;
suspendActivity.updatedUserGuid = currentUser.userGUID;
suspendActivity.updatedUserDirectory = currentUser.orbcUserDirectory;
suspendActivity.updatedDateTime = currentDateTime;

savedSuspendAcitivity = await queryRunner.manager.save(suspendActivity);

await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.error(error);
throw error;
} finally {
await queryRunner.release();
}

const result = await this.classMapper.mapAsync(
savedSuspendAcitivity,
CompanySuspendActivity,
ReadCompanySuspendActivityDto,
);
result.userName = currentUser.userName?.toUpperCase();
return result;
}

/**
* The findAllSuspendActivityByCompanyId() method retrieves all suspend activities associated with a given company ID.
* It queries the suspend activity repository for records matching the company ID and includes the relation to the IDIR user.
* The results are then mapped from the CompanySuspendActivity entities to ReadCompanySuspendActivityDto objects for presentation.
*
* @param companyId The unique identifier of the company.
*
* @returns A promise containing an array of ReadCompanySuspendActivityDto objects representing the suspend activities for the specified company.
*/
@LogAsyncMethodExecution()
async findAllSuspendActivityByCompanyId(
companyId: number,
): Promise<ReadCompanySuspendActivityDto[]> {
const suspendActivityDbResults = await this.companySuspendRepository.find({
where: { companyId: companyId },
relations: {
idirUser: true,
},
});

return await this.classMapper.mapArrayAsync(
suspendActivityDbResults,
CompanySuspendActivity,
ReadCompanySuspendActivityDto,
);
}
}
Loading

0 comments on commit 7b59ad7

Please sign in to comment.