Compare commits

..

1 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
f395cce710 fix(script): setup script 2026-01-29 20:49:54 +02:00
159 changed files with 701 additions and 1815 deletions

View File

@@ -35,4 +35,4 @@ WORKDIR /app/packages/server
RUN git clone https://github.com/vishnubob/wait-for-it.git RUN git clone https://github.com/vishnubob/wait-for-it.git
# Once we listen the mysql port run the migration task. # Once we listen the mysql port run the migration task.
CMD ./wait-for-it/wait-for-it.sh mysql:3306 -- sh -c "node dist/cli.js system:migrate:latest && node dist/cli.js tenants:migrate:latest" CMD ./wait-for-it/wait-for-it.sh mysql:3306 -- sh -c "pnpm run system:migrate:latest && pnpm run tenants:migrate:latest"

View File

@@ -75,9 +75,6 @@ COPY --from=builder --chown=nodejs:nodejs /app/packages/server/src/i18n ./packag
COPY --from=builder --chown=nodejs:nodejs /app/packages/server/public ./packages/server/public COPY --from=builder --chown=nodejs:nodejs /app/packages/server/public ./packages/server/public
COPY --from=builder --chown=nodejs:nodejs /app/packages/server/static ./packages/server/static COPY --from=builder --chown=nodejs:nodejs /app/packages/server/static ./packages/server/static
# Copy database migration files (needed for running migrations)
COPY --from=builder --chown=nodejs:nodejs /app/packages/server/src/database ./packages/server/src/database
# Copy built shared packages (dist folders and package.json for module resolution) # Copy built shared packages (dist folders and package.json for module resolution)
COPY --from=builder --chown=nodejs:nodejs /app/shared/bigcapital-utils/dist ./shared/bigcapital-utils/dist COPY --from=builder --chown=nodejs:nodejs /app/shared/bigcapital-utils/dist ./shared/bigcapital-utils/dist
COPY --from=builder --chown=nodejs:nodejs /app/shared/pdf-templates/dist ./shared/pdf-templates/dist COPY --from=builder --chown=nodejs:nodejs /app/shared/pdf-templates/dist ./shared/pdf-templates/dist

View File

@@ -2,23 +2,10 @@
"$schema": "https://json.schemastore.org/nest-cli", "$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics", "collection": "@nestjs/schematics",
"sourceRoot": "src", "sourceRoot": "src",
"entryFile": "main",
"compilerOptions": { "compilerOptions": {
"deleteOutDir": true, "deleteOutDir": true,
"assets": [ "assets": [
{ "include": "i18n/**/*", "watchAssets": true }, { "include": "i18n/**/*", "watchAssets": true }
{ "include": "database/**/*", "exclude": "**/*.ts", "watchAssets": true }
] ]
},
"projects": {
"cli": {
"type": "application",
"root": "src",
"entryFile": "cli",
"sourceRoot": "src",
"compilerOptions": {
"tsConfigPath": "tsconfig.json"
}
}
} }
} }

View File

@@ -6,5 +6,4 @@ export default registerAs('s3', () => ({
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
endpoint: process.env.S3_ENDPOINT, endpoint: process.env.S3_ENDPOINT,
bucket: process.env.S3_BUCKET, bucket: process.env.S3_BUCKET,
forcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true',
})); }));

View File

@@ -9,7 +9,6 @@ import {
ParseIntPipe, ParseIntPipe,
Put, Put,
HttpCode, HttpCode,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AccountsApplication } from './AccountsApplication.service'; import { AccountsApplication } from './AccountsApplication.service';
import { CreateAccountDTO } from './CreateAccount.dto'; import { CreateAccountDTO } from './CreateAccount.dto';
@@ -33,11 +32,6 @@ import {
BulkDeleteDto, BulkDeleteDto,
ValidateBulkDeleteResponseDto, ValidateBulkDeleteResponseDto,
} from '@/common/dtos/BulkDelete.dto'; } from '@/common/dtos/BulkDelete.dto';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { AccountAction } from './Accounts.types';
@Controller('accounts') @Controller('accounts')
@ApiTags('Accounts') @ApiTags('Accounts')
@@ -46,13 +40,11 @@ import { AccountAction } from './Accounts.types';
@ApiExtraModels(GetAccountTransactionResponseDto) @ApiExtraModels(GetAccountTransactionResponseDto)
@ApiExtraModels(ValidateBulkDeleteResponseDto) @ApiExtraModels(ValidateBulkDeleteResponseDto)
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class AccountsController { export class AccountsController {
constructor(private readonly accountsApplication: AccountsApplication) { } constructor(private readonly accountsApplication: AccountsApplication) { }
@Post('validate-bulk-delete') @Post('validate-bulk-delete')
@HttpCode(200) @HttpCode(200)
@RequirePermission(AccountAction.DELETE, AbilitySubject.Account)
@ApiOperation({ @ApiOperation({
summary: summary:
'Validates which accounts can be deleted and returns counts of deletable and non-deletable accounts.', 'Validates which accounts can be deleted and returns counts of deletable and non-deletable accounts.',
@@ -75,7 +67,6 @@ export class AccountsController {
@Post('bulk-delete') @Post('bulk-delete')
@HttpCode(200) @HttpCode(200)
@RequirePermission(AccountAction.DELETE, AbilitySubject.Account)
@ApiOperation({ summary: 'Deletes multiple accounts in bulk.' }) @ApiOperation({ summary: 'Deletes multiple accounts in bulk.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -90,7 +81,6 @@ export class AccountsController {
} }
@Post() @Post()
@RequirePermission(AccountAction.CREATE, AbilitySubject.Account)
@ApiOperation({ summary: 'Create an account' }) @ApiOperation({ summary: 'Create an account' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -101,7 +91,6 @@ export class AccountsController {
} }
@Put(':id') @Put(':id')
@RequirePermission(AccountAction.EDIT, AbilitySubject.Account)
@ApiOperation({ summary: 'Edit the given account.' }) @ApiOperation({ summary: 'Edit the given account.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -122,7 +111,6 @@ export class AccountsController {
} }
@Delete(':id') @Delete(':id')
@RequirePermission(AccountAction.DELETE, AbilitySubject.Account)
@ApiOperation({ summary: 'Delete the given account.' }) @ApiOperation({ summary: 'Delete the given account.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -141,7 +129,6 @@ export class AccountsController {
@Post(':id/activate') @Post(':id/activate')
@HttpCode(200) @HttpCode(200)
@RequirePermission(AccountAction.EDIT, AbilitySubject.Account)
@ApiOperation({ summary: 'Activate the given account.' }) @ApiOperation({ summary: 'Activate the given account.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -160,7 +147,6 @@ export class AccountsController {
@Post(':id/inactivate') @Post(':id/inactivate')
@HttpCode(200) @HttpCode(200)
@RequirePermission(AccountAction.EDIT, AbilitySubject.Account)
@ApiOperation({ summary: 'Inactivate the given account.' }) @ApiOperation({ summary: 'Inactivate the given account.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -178,7 +164,6 @@ export class AccountsController {
} }
@Get('types') @Get('types')
@RequirePermission(AccountAction.VIEW, AbilitySubject.Account)
@ApiOperation({ summary: 'Retrieves the account types.' }) @ApiOperation({ summary: 'Retrieves the account types.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -195,7 +180,6 @@ export class AccountsController {
} }
@Get('transactions') @Get('transactions')
@RequirePermission(AccountAction.VIEW, AbilitySubject.Account)
@ApiOperation({ summary: 'Retrieves the account transactions.' }) @ApiOperation({ summary: 'Retrieves the account transactions.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -214,7 +198,6 @@ export class AccountsController {
} }
@Get(':id') @Get(':id')
@RequirePermission(AccountAction.VIEW, AbilitySubject.Account)
@ApiOperation({ summary: 'Retrieves the account details.' }) @ApiOperation({ summary: 'Retrieves the account details.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -233,7 +216,6 @@ export class AccountsController {
} }
@Get() @Get()
@RequirePermission(AccountAction.VIEW, AbilitySubject.Account)
@ApiOperation({ summary: 'Retrieves the accounts.' }) @ApiOperation({ summary: 'Retrieves the accounts.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -20,7 +20,7 @@ export class AuthenticationMailMesssages {
* @returns {Mail} * @returns {Mail}
*/ */
resetPasswordMessage(user: ModelObject<SystemUser>, token: string) { resetPasswordMessage(user: ModelObject<SystemUser>, token: string) {
const baseURL = this.configService.get('app.baseUrl'); const baseURL = this.configService.get('baseURL');
return new Mail() return new Mail()
.setSubject('Bigcapital - Password Reset') .setSubject('Bigcapital - Password Reset')
@@ -54,7 +54,7 @@ export class AuthenticationMailMesssages {
* @returns {Mail} * @returns {Mail}
*/ */
signupVerificationMail(email: string, fullName: string, token: string) { signupVerificationMail(email: string, fullName: string, token: string) {
const baseURL = this.configService.get('app.baseUrl'); const baseURL = this.configService.get('baseURL');
const verifyUrl = `${baseURL}/auth/email_confirmation?token=${token}&email=${email}`; const verifyUrl = `${baseURL}/auth/email_confirmation?token=${token}&email=${email}`;
return new Mail() return new Mail()

View File

@@ -1,6 +1,10 @@
import { Processor, WorkerHost } from '@nestjs/bullmq'; import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Scope } from '@nestjs/common'; import { Scope } from '@nestjs/common';
import { SendResetPasswordMailQueue } from '../Auth.constants'; import {
SendResetPasswordMailJob,
SendResetPasswordMailQueue,
} from '../Auth.constants';
import { Process } from '@nestjs/bull';
import { Job } from 'bullmq'; import { Job } from 'bullmq';
import { AuthenticationMailMesssages } from '../AuthMailMessages.esrvice'; import { AuthenticationMailMesssages } from '../AuthMailMessages.esrvice';
import { MailTransporter } from '@/modules/Mail/MailTransporter.service'; import { MailTransporter } from '@/modules/Mail/MailTransporter.service';
@@ -19,6 +23,7 @@ export class SendResetPasswordMailProcessor extends WorkerHost {
super(); super();
} }
@Process(SendResetPasswordMailJob)
async process(job: Job<SendResetPasswordMailJobPayload>) { async process(job: Job<SendResetPasswordMailJobPayload>) {
try { try {
await this.authMailMesssages.sendResetPasswordMail( await this.authMailMesssages.sendResetPasswordMail(

View File

@@ -1,7 +1,11 @@
import { Scope } from '@nestjs/common'; import { Scope } from '@nestjs/common';
import { Job } from 'bullmq'; import { Job } from 'bullmq';
import { Process } from '@nestjs/bull';
import { Processor, WorkerHost } from '@nestjs/bullmq'; import { Processor, WorkerHost } from '@nestjs/bullmq';
import { SendSignupVerificationMailQueue } from '../Auth.constants'; import {
SendSignupVerificationMailJob,
SendSignupVerificationMailQueue,
} from '../Auth.constants';
import { MailTransporter } from '@/modules/Mail/MailTransporter.service'; import { MailTransporter } from '@/modules/Mail/MailTransporter.service';
import { AuthenticationMailMesssages } from '../AuthMailMessages.esrvice'; import { AuthenticationMailMesssages } from '../AuthMailMessages.esrvice';
@@ -17,6 +21,7 @@ export class SendSignupVerificationMailProcessor extends WorkerHost {
super(); super();
} }
@Process(SendSignupVerificationMailJob)
async process(job: Job<SendSignupVerificationMailJobPayload>) { async process(job: Job<SendSignupVerificationMailJobPayload>) {
try { try {
await this.authMailMesssages.sendSignupVerificationMail( await this.authMailMesssages.sendSignupVerificationMail(

View File

@@ -16,7 +16,7 @@ import { ToNumber } from '@/common/decorators/Validators';
class BankRuleConditionDto { class BankRuleConditionDto {
@IsNotEmpty() @IsNotEmpty()
@IsIn(['description', 'amount', 'payee']) @IsIn(['description', 'amount'])
field: string; field: string;
@IsNotEmpty() @IsNotEmpty()

View File

@@ -1,9 +1,11 @@
import { Process } from '@nestjs/bull';
import { UseCls } from 'nestjs-cls'; import { UseCls } from 'nestjs-cls';
import { Processor, WorkerHost } from '@nestjs/bullmq'; import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Scope } from '@nestjs/common'; import { Scope } from '@nestjs/common';
import { Job } from 'bullmq'; import { Job } from 'bullmq';
import { import {
PlaidFetchTransitonsEventPayload, PlaidFetchTransitonsEventPayload,
UpdateBankingPlaidTransitionsJob,
UpdateBankingPlaidTransitionsQueueJob, UpdateBankingPlaidTransitionsQueueJob,
} from '../types/BankingPlaid.types'; } from '../types/BankingPlaid.types';
import { PlaidUpdateTransactions } from '../command/PlaidUpdateTransactions'; import { PlaidUpdateTransactions } from '../command/PlaidUpdateTransactions';
@@ -26,6 +28,7 @@ export class PlaidFetchTransactionsProcessor extends WorkerHost {
/** /**
* Triggers the function. * Triggers the function.
*/ */
@Process(UpdateBankingPlaidTransitionsJob)
@UseCls() @UseCls()
async process(job: Job<PlaidFetchTransitonsEventPayload>) { async process(job: Job<PlaidFetchTransitonsEventPayload>) {
const { plaidItemId } = job.data; const { plaidItemId } = job.data;

View File

@@ -15,13 +15,8 @@ export const RecognizeUncategorizedTransactionsJob =
export const RecognizeUncategorizedTransactionsQueue = export const RecognizeUncategorizedTransactionsQueue =
'recognize-uncategorized-transactions-queue'; 'recognize-uncategorized-transactions-queue';
export interface RecognizeUncategorizedTransactionsJobPayload extends TenantJobPayload { export interface RecognizeUncategorizedTransactionsJobPayload extends TenantJobPayload {
ruleId: number, ruleId: number,
transactionsCriteria?: RecognizeTransactionsCriteria; transactionsCriteria: any;
/**
* When true, first reverts recognized transactions before recognizing again.
* Used when a bank rule is edited to ensure transactions previously recognized
* by lower-priority rules are re-evaluated against the updated rule.
*/
shouldRevert?: boolean;
} }

View File

@@ -93,10 +93,6 @@ export class RecognizeTranasctionsService {
q.whereIn('id', rulesIds); q.whereIn('id', rulesIds);
} }
q.withGraphFetched('conditions'); q.withGraphFetched('conditions');
// Order by the 'order' field to ensure higher priority rules (lower order values)
// are matched first.
q.orderBy('order', 'asc');
}); });
const bankRulesByAccountId = transformToMapBy( const bankRulesByAccountId = transformToMapBy(

View File

@@ -69,13 +69,10 @@ export class TriggerRecognizedTransactionsSubscriber {
const tenantPayload = await this.tenancyContect.getTenantJobPayload(); const tenantPayload = await this.tenancyContect.getTenantJobPayload();
const payload = { const payload = {
ruleId: bankRule.id, ruleId: bankRule.id,
shouldRevert: true,
...tenantPayload, ...tenantPayload,
} as RecognizeUncategorizedTransactionsJobPayload; } as RecognizeUncategorizedTransactionsJobPayload;
// Re-recognize the transactions based on the new rules. // Re-recognize the transactions based on the new rules.
// Setting shouldRevert to true ensures that transactions previously recognized
// by this or lower-priority rules are re-evaluated against the updated rule.
await this.recognizeTransactionsQueue.add( await this.recognizeTransactionsQueue.add(
RecognizeUncategorizedTransactionsJob, RecognizeUncategorizedTransactionsJob,
payload, payload,

View File

@@ -3,11 +3,11 @@ import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Scope } from '@nestjs/common'; import { Scope } from '@nestjs/common';
import { ClsService, UseCls } from 'nestjs-cls'; import { ClsService, UseCls } from 'nestjs-cls';
import { RecognizeTranasctionsService } from '../commands/RecognizeTranasctions.service'; import { RecognizeTranasctionsService } from '../commands/RecognizeTranasctions.service';
import { RevertRecognizedTransactionsService } from '../commands/RevertRecognizedTransactions.service';
import { import {
RecognizeUncategorizedTransactionsJobPayload, RecognizeUncategorizedTransactionsJobPayload,
RecognizeUncategorizedTransactionsQueue, RecognizeUncategorizedTransactionsQueue,
} from '../_types'; } from '../_types';
import { Process } from '@nestjs/bull';
@Processor({ @Processor({
name: RecognizeUncategorizedTransactionsQueue, name: RecognizeUncategorizedTransactionsQueue,
@@ -16,12 +16,10 @@ import {
export class RegonizeTransactionsPrcessor extends WorkerHost { export class RegonizeTransactionsPrcessor extends WorkerHost {
/** /**
* @param {RecognizeTranasctionsService} recognizeTranasctionsService - * @param {RecognizeTranasctionsService} recognizeTranasctionsService -
* @param {RevertRecognizedTransactionsService} revertRecognizedTransactionsService -
* @param {ClsService} clsService - * @param {ClsService} clsService -
*/ */
constructor( constructor(
private readonly recognizeTranasctionsService: RecognizeTranasctionsService, private readonly recognizeTranasctionsService: RecognizeTranasctionsService,
private readonly revertRecognizedTransactionsService: RevertRecognizedTransactionsService,
private readonly clsService: ClsService, private readonly clsService: ClsService,
) { ) {
super(); super();
@@ -30,23 +28,15 @@ export class RegonizeTransactionsPrcessor extends WorkerHost {
/** /**
* Triggers sending invoice mail. * Triggers sending invoice mail.
*/ */
@Process(RecognizeUncategorizedTransactionsQueue)
@UseCls() @UseCls()
async process(job: Job<RecognizeUncategorizedTransactionsJobPayload>) { async process(job: Job<RecognizeUncategorizedTransactionsJobPayload>) {
const { ruleId, transactionsCriteria, shouldRevert } = job.data; const { ruleId, transactionsCriteria } = job.data;
this.clsService.set('organizationId', job.data.organizationId); this.clsService.set('organizationId', job.data.organizationId);
this.clsService.set('userId', job.data.userId); this.clsService.set('userId', job.data.userId);
try { try {
// If shouldRevert is true, first revert recognized transactions before re-recognizing.
// This is used when a bank rule is edited to ensure transactions previously recognized
// by lower-priority rules are re-evaluated against the updated rule.
if (shouldRevert) {
await this.revertRecognizedTransactionsService.revertRecognizedTransactions(
ruleId,
transactionsCriteria,
);
}
await this.recognizeTranasctionsService.recognizeTransactions( await this.recognizeTranasctionsService.recognizeTransactions(
ruleId, ruleId,
transactionsCriteria, transactionsCriteria,

View File

@@ -27,7 +27,7 @@ export enum CASHFLOW_DIRECTION {
} }
export enum CASHFLOW_TRANSACTION_TYPE { export enum CASHFLOW_TRANSACTION_TYPE {
OWNERS_DRAWING = 'OwnerDrawing', ONWERS_DRAWING = 'OwnerDrawing',
OWNER_CONTRIBUTION = 'OwnerContribution', OWNER_CONTRIBUTION = 'OwnerContribution',
OTHER_INCOME = 'OtherIncome', OTHER_INCOME = 'OtherIncome',
TRANSFER_FROM_ACCOUNT = 'TransferFromAccount', TRANSFER_FROM_ACCOUNT = 'TransferFromAccount',
@@ -36,7 +36,7 @@ export enum CASHFLOW_TRANSACTION_TYPE {
} }
export const CASHFLOW_TRANSACTION_TYPE_META = { export const CASHFLOW_TRANSACTION_TYPE_META = {
[`${CASHFLOW_TRANSACTION_TYPE.OWNERS_DRAWING}`]: { [`${CASHFLOW_TRANSACTION_TYPE.ONWERS_DRAWING}`]: {
type: 'OwnerDrawing', type: 'OwnerDrawing',
direction: CASHFLOW_DIRECTION.OUT, direction: CASHFLOW_DIRECTION.OUT,
creditType: [ACCOUNT_TYPE.EQUITY], creditType: [ACCOUNT_TYPE.EQUITY],

View File

@@ -7,7 +7,6 @@ import {
Post, Post,
Put, Put,
Query, Query,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { BillPaymentsApplication } from './BillPaymentsApplication.service'; import { BillPaymentsApplication } from './BillPaymentsApplication.service';
import { import {
@@ -27,18 +26,12 @@ import { BillPaymentsPages } from './commands/BillPaymentsPages.service';
import { BillPaymentResponseDto } from './dtos/BillPaymentResponse.dto'; import { BillPaymentResponseDto } from './dtos/BillPaymentResponse.dto';
import { PaginatedResponseDto } from '@/common/dtos/PaginatedResults.dto'; import { PaginatedResponseDto } from '@/common/dtos/PaginatedResults.dto';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { IPaymentMadeAction } from './types/BillPayments.types';
@Controller('bill-payments') @Controller('bill-payments')
@ApiTags('Bill Payments') @ApiTags('Bill Payments')
@ApiExtraModels(BillPaymentResponseDto) @ApiExtraModels(BillPaymentResponseDto)
@ApiExtraModels(PaginatedResponseDto) @ApiExtraModels(PaginatedResponseDto)
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class BillPaymentsController { export class BillPaymentsController {
constructor( constructor(
private billPaymentsApplication: BillPaymentsApplication, private billPaymentsApplication: BillPaymentsApplication,
@@ -46,14 +39,12 @@ export class BillPaymentsController {
) {} ) {}
@Post() @Post()
@RequirePermission(IPaymentMadeAction.Create, AbilitySubject.PaymentMade)
@ApiOperation({ summary: 'Create a new bill payment.' }) @ApiOperation({ summary: 'Create a new bill payment.' })
public createBillPayment(@Body() billPaymentDTO: CreateBillPaymentDto) { public createBillPayment(@Body() billPaymentDTO: CreateBillPaymentDto) {
return this.billPaymentsApplication.createBillPayment(billPaymentDTO); return this.billPaymentsApplication.createBillPayment(billPaymentDTO);
} }
@Delete(':billPaymentId') @Delete(':billPaymentId')
@RequirePermission(IPaymentMadeAction.Delete, AbilitySubject.PaymentMade)
@ApiOperation({ summary: 'Delete the given bill payment.' }) @ApiOperation({ summary: 'Delete the given bill payment.' })
@ApiParam({ @ApiParam({
name: 'billPaymentId', name: 'billPaymentId',
@@ -68,7 +59,6 @@ export class BillPaymentsController {
} }
@Put(':billPaymentId') @Put(':billPaymentId')
@RequirePermission(IPaymentMadeAction.Edit, AbilitySubject.PaymentMade)
@ApiOperation({ summary: 'Edit the given bill payment.' }) @ApiOperation({ summary: 'Edit the given bill payment.' })
@ApiParam({ @ApiParam({
name: 'billPaymentId', name: 'billPaymentId',
@@ -87,7 +77,6 @@ export class BillPaymentsController {
} }
@Get('/new-page/entries') @Get('/new-page/entries')
@RequirePermission(IPaymentMadeAction.View, AbilitySubject.PaymentMade)
@ApiOperation({ @ApiOperation({
summary: summary:
'Retrieves the payable entries of the new page once vendor be selected.', 'Retrieves the payable entries of the new page once vendor be selected.',
@@ -106,7 +95,6 @@ export class BillPaymentsController {
} }
@Get(':billPaymentId/bills') @Get(':billPaymentId/bills')
@RequirePermission(IPaymentMadeAction.View, AbilitySubject.PaymentMade)
@ApiOperation({ summary: 'Retrieves the bills of the given bill payment.' }) @ApiOperation({ summary: 'Retrieves the bills of the given bill payment.' })
@ApiParam({ @ApiParam({
name: 'billPaymentId', name: 'billPaymentId',
@@ -119,7 +107,6 @@ export class BillPaymentsController {
} }
@Get('/:billPaymentId/edit-page') @Get('/:billPaymentId/edit-page')
@RequirePermission(IPaymentMadeAction.View, AbilitySubject.PaymentMade)
@ApiOperation({ @ApiOperation({
summary: 'Retrieves the edit page of the given bill payment.', summary: 'Retrieves the edit page of the given bill payment.',
}) })
@@ -139,7 +126,6 @@ export class BillPaymentsController {
} }
@Get(':billPaymentId') @Get(':billPaymentId')
@RequirePermission(IPaymentMadeAction.View, AbilitySubject.PaymentMade)
@ApiOperation({ summary: 'Retrieves the bill payment details.' }) @ApiOperation({ summary: 'Retrieves the bill payment details.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -159,7 +145,6 @@ export class BillPaymentsController {
} }
@Get() @Get()
@RequirePermission(IPaymentMadeAction.View, AbilitySubject.PaymentMade)
@ApiOperation({ summary: 'Retrieves the bill payments list.' }) @ApiOperation({ summary: 'Retrieves the bill payments list.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -17,7 +17,6 @@ import {
Get, Get,
Query, Query,
HttpCode, HttpCode,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { BillsApplication } from './Bills.application'; import { BillsApplication } from './Bills.application';
import { IBillsFilter } from './Bills.types'; import { IBillsFilter } from './Bills.types';
@@ -29,11 +28,6 @@ import {
BulkDeleteDto, BulkDeleteDto,
ValidateBulkDeleteResponseDto, ValidateBulkDeleteResponseDto,
} from '@/common/dtos/BulkDelete.dto'; } from '@/common/dtos/BulkDelete.dto';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { BillAction } from './Bills.types';
@Controller('bills') @Controller('bills')
@ApiTags('Bills') @ApiTags('Bills')
@@ -41,12 +35,10 @@ import { BillAction } from './Bills.types';
@ApiExtraModels(PaginatedResponseDto) @ApiExtraModels(PaginatedResponseDto)
@ApiCommonHeaders() @ApiCommonHeaders()
@ApiExtraModels(ValidateBulkDeleteResponseDto) @ApiExtraModels(ValidateBulkDeleteResponseDto)
@UseGuards(AuthorizationGuard, PermissionGuard)
export class BillsController { export class BillsController {
constructor(private billsApplication: BillsApplication) { } constructor(private billsApplication: BillsApplication) { }
@Post('validate-bulk-delete') @Post('validate-bulk-delete')
@RequirePermission(BillAction.Delete, AbilitySubject.Bill)
@ApiOperation({ @ApiOperation({
summary: 'Validate which bills can be deleted and return the results.', summary: 'Validate which bills can be deleted and return the results.',
}) })
@@ -66,7 +58,6 @@ export class BillsController {
} }
@Post('bulk-delete') @Post('bulk-delete')
@RequirePermission(BillAction.Delete, AbilitySubject.Bill)
@ApiOperation({ summary: 'Deletes multiple bills.' }) @ApiOperation({ summary: 'Deletes multiple bills.' })
@HttpCode(200) @HttpCode(200)
@ApiResponse({ @ApiResponse({
@@ -82,14 +73,12 @@ export class BillsController {
} }
@Post() @Post()
@RequirePermission(BillAction.Create, AbilitySubject.Bill)
@ApiOperation({ summary: 'Create a new bill.' }) @ApiOperation({ summary: 'Create a new bill.' })
createBill(@Body() billDTO: CreateBillDto) { createBill(@Body() billDTO: CreateBillDto) {
return this.billsApplication.createBill(billDTO); return this.billsApplication.createBill(billDTO);
} }
@Put(':id') @Put(':id')
@RequirePermission(BillAction.Edit, AbilitySubject.Bill)
@ApiOperation({ summary: 'Edit the given bill.' }) @ApiOperation({ summary: 'Edit the given bill.' })
@ApiParam({ @ApiParam({
name: 'id', name: 'id',
@@ -102,7 +91,6 @@ export class BillsController {
} }
@Delete(':id') @Delete(':id')
@RequirePermission(BillAction.Delete, AbilitySubject.Bill)
@ApiOperation({ summary: 'Delete the given bill.' }) @ApiOperation({ summary: 'Delete the given bill.' })
@ApiParam({ @ApiParam({
name: 'id', name: 'id',
@@ -115,7 +103,6 @@ export class BillsController {
} }
@Get() @Get()
@RequirePermission(BillAction.View, AbilitySubject.Bill)
@ApiOperation({ summary: 'Retrieves the bills.' }) @ApiOperation({ summary: 'Retrieves the bills.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -145,7 +132,6 @@ export class BillsController {
} }
@Get(':id/payment-transactions') @Get(':id/payment-transactions')
@RequirePermission(BillAction.View, AbilitySubject.Bill)
@ApiOperation({ @ApiOperation({
summary: 'Retrieve the specific bill associated payment transactions.', summary: 'Retrieve the specific bill associated payment transactions.',
}) })
@@ -160,7 +146,6 @@ export class BillsController {
} }
@Get(':id') @Get(':id')
@RequirePermission(BillAction.View, AbilitySubject.Bill)
@ApiOperation({ summary: 'Retrieves the bill details.' }) @ApiOperation({ summary: 'Retrieves the bill details.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -180,7 +165,6 @@ export class BillsController {
} }
@Patch(':id/open') @Patch(':id/open')
@RequirePermission(BillAction.Edit, AbilitySubject.Bill)
@ApiOperation({ summary: 'Open the given bill.' }) @ApiOperation({ summary: 'Open the given bill.' })
@ApiParam({ @ApiParam({
name: 'id', name: 'id',
@@ -193,7 +177,6 @@ export class BillsController {
} }
@Get('due') @Get('due')
@RequirePermission(BillAction.View, AbilitySubject.Bill)
@ApiOperation({ summary: 'Retrieves the due bills.' }) @ApiOperation({ summary: 'Retrieves the due bills.' })
getDueBills(@Body('vendorId') vendorId?: number) { getDueBills(@Body('vendorId') vendorId?: number) {
return this.billsApplication.getDueBills(vendorId); return this.billsApplication.getDueBills(vendorId);

View File

@@ -1,8 +1,6 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto'; import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto';
import { AttachmentLinkDto } from '@/modules/Attachments/dtos/Attachment.dto'; import { AttachmentLinkDto } from '@/modules/Attachments/dtos/Attachment.dto';
import { BranchResponseDto } from '@/modules/Branches/dtos/BranchResponse.dto';
import { DiscountType } from '@/common/types/Discount'; import { DiscountType } from '@/common/types/Discount';
export class BillResponseDto { export class BillResponseDto {
@@ -91,14 +89,6 @@ export class BillResponseDto {
}) })
branchId?: number; branchId?: number;
@ApiProperty({
description: 'Branch details',
type: () => BranchResponseDto,
required: false,
})
@Type(() => BranchResponseDto)
branch?: BranchResponseDto;
@ApiProperty({ @ApiProperty({
description: 'The ID of the project', description: 'The ID of the project',
example: 301, example: 301,

View File

@@ -30,7 +30,6 @@ export class BillTransformer extends Transformer {
'taxes', 'taxes',
'entries', 'entries',
'attachments', 'attachments',
'branch',
]; ];
}; };

View File

@@ -31,12 +31,6 @@ import { ValidateBranchExistance } from './integrations/ValidateBranchExistance'
import { ManualJournalBranchesValidator } from './integrations/ManualJournals/ManualJournalsBranchesValidator'; import { ManualJournalBranchesValidator } from './integrations/ManualJournals/ManualJournalsBranchesValidator';
import { CashflowTransactionsActivateBranches } from './integrations/Cashflow/CashflowActivateBranches'; import { CashflowTransactionsActivateBranches } from './integrations/Cashflow/CashflowActivateBranches';
import { ExpensesActivateBranches } from './integrations/Expense/ExpensesActivateBranches'; import { ExpensesActivateBranches } from './integrations/Expense/ExpensesActivateBranches';
import { BillActivateBranches } from './integrations/Purchases/BillBranchesActivate';
import { VendorCreditActivateBranches } from './integrations/Purchases/VendorCreditBranchesActivate';
import { BillPaymentsActivateBranches } from './integrations/Purchases/PaymentMadeBranchesActivate';
import { BillBranchesActivateSubscriber } from './subscribers/Activate/BillBranchesActivateSubscriber';
import { VendorCreditBranchesActivateSubscriber } from './subscribers/Activate/VendorCreditBranchesActivateSubscriber';
import { PaymentMadeActivateBranchesSubscriber } from './subscribers/Activate/PaymentMadeBranchesActivateSubscriber';
import { FeaturesModule } from '../Features/Features.module'; import { FeaturesModule } from '../Features/Features.module';
@Module({ @Module({
@@ -72,13 +66,7 @@ import { FeaturesModule } from '../Features/Features.module';
ValidateBranchExistance, ValidateBranchExistance,
ManualJournalBranchesValidator, ManualJournalBranchesValidator,
CashflowTransactionsActivateBranches, CashflowTransactionsActivateBranches,
ExpensesActivateBranches, ExpensesActivateBranches
BillActivateBranches,
VendorCreditActivateBranches,
BillPaymentsActivateBranches,
BillBranchesActivateSubscriber,
VendorCreditBranchesActivateSubscriber,
PaymentMadeActivateBranchesSubscriber
], ],
exports: [ exports: [
BranchesSettingsService, BranchesSettingsService,

View File

@@ -1,14 +1,11 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { Bill } from '@/modules/Bills/models/Bill'; import { Bill } from '@/modules/Bills/models/Bill';
@Injectable() @Injectable()
export class BillActivateBranches { export class BillActivateBranches {
constructor( constructor(private readonly billModel: TenantModelProxy<typeof Bill>) {}
@Inject(Bill.name)
private readonly billModel: TenantModelProxy<typeof Bill>,
) {}
/** /**
* Updates all bills transactions with the primary branch. * Updates all bills transactions with the primary branch.
@@ -20,7 +17,7 @@ export class BillActivateBranches {
primaryBranchId: number, primaryBranchId: number,
trx?: Knex.Transaction, trx?: Knex.Transaction,
) => { ) => {
// Updates the bills with primary branch. // Updates the sale invoice with primary branch.
await this.billModel().query(trx).update({ branchId: primaryBranchId }); await Bill.query(trx).update({ branchId: primaryBranchId });
}; };
} }

View File

@@ -1,12 +1,11 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { BillPayment } from '@/modules/BillPayments/models/BillPayment'; import { BillPayment } from '@/modules/BillPayments/models/BillPayment';
import { Injectable } from '@nestjs/common';
@Injectable() @Injectable()
export class BillPaymentsActivateBranches { export class BillPaymentsActivateBranches {
constructor( constructor(
@Inject(BillPayment.name)
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>, private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
) {} ) {}

View File

@@ -1,12 +1,11 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit'; import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit';
@Injectable() @Injectable()
export class VendorCreditActivateBranches { export class VendorCreditActivateBranches {
constructor( constructor(
@Inject(VendorCredit.name)
private readonly vendorCreditModel: TenantModelProxy<typeof VendorCredit>, private readonly vendorCreditModel: TenantModelProxy<typeof VendorCredit>,
) {} ) {}

View File

@@ -1,28 +0,0 @@
import { IBranchesActivatedPayload } from '../../Branches.types';
import { events } from '@/common/events/events';
import { Injectable } from '@nestjs/common';
import { BillActivateBranches } from '../../integrations/Purchases/BillBranchesActivate';
import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class BillBranchesActivateSubscriber {
constructor(
private readonly billActivateBranches: BillActivateBranches,
) { }
/**
* Updates bills transactions with the primary branch once
* the multi-branches is activated.
* @param {IBranchesActivatedPayload}
*/
@OnEvent(events.branch.onActivated)
async updateBillsWithBranchOnActivated({
primaryBranch,
trx,
}: IBranchesActivatedPayload) {
await this.billActivateBranches.updateBillsWithBranch(
primaryBranch.id,
trx,
);
}
}

View File

@@ -1,28 +0,0 @@
import { IBranchesActivatedPayload } from '../../Branches.types';
import { events } from '@/common/events/events';
import { Injectable } from '@nestjs/common';
import { VendorCreditActivateBranches } from '../../integrations/Purchases/VendorCreditBranchesActivate';
import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class VendorCreditBranchesActivateSubscriber {
constructor(
private readonly vendorCreditActivateBranches: VendorCreditActivateBranches,
) { }
/**
* Updates vendor credits transactions with the primary branch once
* the multi-branches is activated.
* @param {IBranchesActivatedPayload}
*/
@OnEvent(events.branch.onActivated)
async updateVendorCreditsWithBranchOnActivated({
primaryBranch,
trx,
}: IBranchesActivatedPayload) {
await this.vendorCreditActivateBranches.updateVendorCreditsWithBranch(
primaryBranch.id,
trx,
);
}
}

View File

@@ -22,7 +22,6 @@ export abstract class BaseCommand extends CommandRunner {
}, },
migrations: { migrations: {
directory: this.configService.get('systemDatabase.migrationDir'), directory: this.configService.get('systemDatabase.migrationDir'),
loadExtensions: ['.js'],
}, },
seeds: { seeds: {
directory: this.configService.get('systemDatabase.seedsDir'), directory: this.configService.get('systemDatabase.seedsDir'),
@@ -44,7 +43,6 @@ export abstract class BaseCommand extends CommandRunner {
}, },
migrations: { migrations: {
directory: this.configService.get('tenantDatabase.migrationsDir') || './src/database/migrations', directory: this.configService.get('tenantDatabase.migrationsDir') || './src/database/migrations',
loadExtensions: ['.js'],
}, },
seeds: { seeds: {
directory: this.configService.get('tenantDatabase.seedsDir') || './src/database/seeds/core', directory: this.configService.get('tenantDatabase.seedsDir') || './src/database/seeds/core',

View File

@@ -1,35 +1,20 @@
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
Body,
Controller,
Delete,
Get,
Param,
Post,
UseGuards,
} from '@nestjs/common';
import { ICreditNoteRefundDTO } from '../CreditNotes/types/CreditNotes.types'; import { ICreditNoteRefundDTO } from '../CreditNotes/types/CreditNotes.types';
import { CreditNotesRefundsApplication } from './CreditNotesRefundsApplication.service'; import { CreditNotesRefundsApplication } from './CreditNotesRefundsApplication.service';
import { RefundCreditNote } from './models/RefundCreditNote'; import { RefundCreditNote } from './models/RefundCreditNote';
import { CreditNoteRefundDto } from './dto/CreditNoteRefund.dto'; import { CreditNoteRefundDto } from './dto/CreditNoteRefund.dto';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { CreditNoteAction } from '../CreditNotes/types/CreditNotes.types';
@Controller('credit-notes') @Controller('credit-notes')
@ApiTags('Credit Note Refunds') @ApiTags('Credit Note Refunds')
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class CreditNoteRefundsController { export class CreditNoteRefundsController {
constructor( constructor(
private readonly creditNotesRefundsApplication: CreditNotesRefundsApplication, private readonly creditNotesRefundsApplication: CreditNotesRefundsApplication,
) {} ) {}
@Get(':creditNoteId/refunds') @Get(':creditNoteId/refunds')
@RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Retrieve the credit note graph.' }) @ApiOperation({ summary: 'Retrieve the credit note graph.' })
getCreditNoteRefunds(@Param('creditNoteId') creditNoteId: number) { getCreditNoteRefunds(@Param('creditNoteId') creditNoteId: number) {
return this.creditNotesRefundsApplication.getCreditNoteRefunds( return this.creditNotesRefundsApplication.getCreditNoteRefunds(
@@ -44,7 +29,6 @@ export class CreditNoteRefundsController {
* @returns {Promise<RefundCreditNote>} * @returns {Promise<RefundCreditNote>}
*/ */
@Post(':creditNoteId/refunds') @Post(':creditNoteId/refunds')
@RequirePermission(CreditNoteAction.Refund, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Create a refund for the given credit note.' }) @ApiOperation({ summary: 'Create a refund for the given credit note.' })
createRefundCreditNote( createRefundCreditNote(
@Param('creditNoteId') creditNoteId: number, @Param('creditNoteId') creditNoteId: number,
@@ -62,7 +46,6 @@ export class CreditNoteRefundsController {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
@Delete('refunds/:refundCreditId') @Delete('refunds/:refundCreditId')
@RequirePermission(CreditNoteAction.Refund, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Delete a refund for the given credit note.' }) @ApiOperation({ summary: 'Delete a refund for the given credit note.' })
deleteRefundCreditNote( deleteRefundCreditNote(
@Param('refundCreditId') refundCreditId: number, @Param('refundCreditId') refundCreditId: number,

View File

@@ -18,7 +18,6 @@ import {
Put, Put,
Query, Query,
Res, Res,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { CreditNoteApplication } from './CreditNoteApplication.service'; import { CreditNoteApplication } from './CreditNoteApplication.service';
import { ICreditNotesQueryDTO } from './types/CreditNotes.types'; import { ICreditNotesQueryDTO } from './types/CreditNotes.types';
@@ -31,11 +30,6 @@ import {
ValidateBulkDeleteResponseDto, ValidateBulkDeleteResponseDto,
} from '@/common/dtos/BulkDelete.dto'; } from '@/common/dtos/BulkDelete.dto';
import { AcceptType } from '@/constants/accept-type'; import { AcceptType } from '@/constants/accept-type';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { CreditNoteAction } from './types/CreditNotes.types';
@Controller('credit-notes') @Controller('credit-notes')
@ApiTags('Credit Notes') @ApiTags('Credit Notes')
@@ -43,7 +37,6 @@ import { CreditNoteAction } from './types/CreditNotes.types';
@ApiExtraModels(PaginatedResponseDto) @ApiExtraModels(PaginatedResponseDto)
@ApiExtraModels(ValidateBulkDeleteResponseDto) @ApiExtraModels(ValidateBulkDeleteResponseDto)
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class CreditNotesController { export class CreditNotesController {
/** /**
* @param {CreditNoteApplication} creditNoteApplication - The credit note application service. * @param {CreditNoteApplication} creditNoteApplication - The credit note application service.
@@ -51,7 +44,6 @@ export class CreditNotesController {
constructor(private creditNoteApplication: CreditNoteApplication) { } constructor(private creditNoteApplication: CreditNoteApplication) { }
@Post() @Post()
@RequirePermission(CreditNoteAction.Create, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Create a new credit note' }) @ApiOperation({ summary: 'Create a new credit note' })
@ApiResponse({ status: 201, description: 'Credit note successfully created' }) @ApiResponse({ status: 201, description: 'Credit note successfully created' })
@ApiResponse({ status: 400, description: 'Invalid input data' }) @ApiResponse({ status: 400, description: 'Invalid input data' })
@@ -60,7 +52,6 @@ export class CreditNotesController {
} }
@Get('state') @Get('state')
@RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Get credit note state' }) @ApiOperation({ summary: 'Get credit note state' })
@ApiResponse({ status: 200, description: 'Returns the credit note state' }) @ApiResponse({ status: 200, description: 'Returns the credit note state' })
getCreditNoteState() { getCreditNoteState() {
@@ -68,7 +59,6 @@ export class CreditNotesController {
} }
@Get(':id') @Get(':id')
@RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Get a specific credit note by ID' }) @ApiOperation({ summary: 'Get a specific credit note by ID' })
@ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' }) @ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' })
@ApiResponse({ @ApiResponse({
@@ -102,7 +92,6 @@ export class CreditNotesController {
} }
@Get() @Get()
@RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Get all credit notes' }) @ApiOperation({ summary: 'Get all credit notes' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -126,7 +115,6 @@ export class CreditNotesController {
} }
@Put(':id') @Put(':id')
@RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Update a credit note' }) @ApiOperation({ summary: 'Update a credit note' })
@ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' }) @ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' })
@ApiResponse({ status: 200, description: 'Credit note successfully updated' }) @ApiResponse({ status: 200, description: 'Credit note successfully updated' })
@@ -143,7 +131,6 @@ export class CreditNotesController {
} }
@Put(':id/open') @Put(':id/open')
@RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Open a credit note' }) @ApiOperation({ summary: 'Open a credit note' })
@ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' }) @ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' })
@ApiResponse({ status: 200, description: 'Credit note successfully opened' }) @ApiResponse({ status: 200, description: 'Credit note successfully opened' })
@@ -153,7 +140,6 @@ export class CreditNotesController {
} }
@Post('validate-bulk-delete') @Post('validate-bulk-delete')
@RequirePermission(CreditNoteAction.Delete, AbilitySubject.CreditNote)
@ApiOperation({ @ApiOperation({
summary: summary:
'Validates which credit notes can be deleted and returns the results.', 'Validates which credit notes can be deleted and returns the results.',
@@ -175,7 +161,6 @@ export class CreditNotesController {
} }
@Post('bulk-delete') @Post('bulk-delete')
@RequirePermission(CreditNoteAction.Delete, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Deletes multiple credit notes.' }) @ApiOperation({ summary: 'Deletes multiple credit notes.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -188,7 +173,6 @@ export class CreditNotesController {
} }
@Delete(':id') @Delete(':id')
@RequirePermission(CreditNoteAction.Delete, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Delete a credit note' }) @ApiOperation({ summary: 'Delete a credit note' })
@ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' }) @ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' })
@ApiResponse({ status: 200, description: 'Credit note successfully deleted' }) @ApiResponse({ status: 200, description: 'Credit note successfully deleted' })

View File

@@ -1,31 +1,15 @@
import { import { Body, Controller, Get, Param, Post } from '@nestjs/common';
Body,
Controller,
Get,
Param,
Post,
UseGuards,
} from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { GetCreditNoteAssociatedAppliedInvoices } from './queries/GetCreditNoteAssociatedAppliedInvoices.service'; import { GetCreditNoteAssociatedAppliedInvoices } from './queries/GetCreditNoteAssociatedAppliedInvoices.service';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { CreditNoteAction } from '../CreditNotes/types/CreditNotes.types';
@Controller('credit-notes') @Controller('credit-notes')
@ApiTags('Credit Notes Apply Invoice') @ApiTags('Credit Notes Apply Invoice')
@ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class CreditNotesApplyInvoiceController { export class CreditNotesApplyInvoiceController {
constructor( constructor(
private readonly getCreditNoteAssociatedAppliedInvoicesService: GetCreditNoteAssociatedAppliedInvoices, private readonly getCreditNoteAssociatedAppliedInvoicesService: GetCreditNoteAssociatedAppliedInvoices,
) {} ) {}
@Get(':creditNoteId/applied-invoices') @Get(':creditNoteId/applied-invoices')
@RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Applied credit note to invoices' }) @ApiOperation({ summary: 'Applied credit note to invoices' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -40,7 +24,6 @@ export class CreditNotesApplyInvoiceController {
} }
@Post(':creditNoteId/apply-invoices') @Post(':creditNoteId/apply-invoices')
@RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Apply credit note to invoices' }) @ApiOperation({ summary: 'Apply credit note to invoices' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -7,7 +7,6 @@ import {
Post, Post,
Put, Put,
Query, Query,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { CustomersApplication } from './CustomersApplication.service'; import { CustomersApplication } from './CustomersApplication.service';
import { CustomerOpeningBalanceEditDto } from './dtos/CustomerOpeningBalanceEdit.dto'; import { CustomerOpeningBalanceEditDto } from './dtos/CustomerOpeningBalanceEdit.dto';
@@ -27,22 +26,15 @@ import {
ValidateBulkDeleteCustomersResponseDto, ValidateBulkDeleteCustomersResponseDto,
} from './dtos/BulkDeleteCustomers.dto'; } from './dtos/BulkDeleteCustomers.dto';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { CustomerAction } from './types/Customers.types';
@Controller('customers') @Controller('customers')
@ApiTags('Customers') @ApiTags('Customers')
@ApiExtraModels(CustomerResponseDto) @ApiExtraModels(CustomerResponseDto)
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class CustomersController { export class CustomersController {
constructor(private customersApplication: CustomersApplication) { } constructor(private customersApplication: CustomersApplication) { }
@Get(':id') @Get(':id')
@RequirePermission(CustomerAction.View, AbilitySubject.Customer)
@ApiOperation({ summary: 'Retrieves the customer details.' }) @ApiOperation({ summary: 'Retrieves the customer details.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -54,7 +46,6 @@ export class CustomersController {
} }
@Get() @Get()
@RequirePermission(CustomerAction.View, AbilitySubject.Customer)
@ApiOperation({ summary: 'Retrieves the customers paginated list.' }) @ApiOperation({ summary: 'Retrieves the customers paginated list.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -69,7 +60,6 @@ export class CustomersController {
} }
@Post() @Post()
@RequirePermission(CustomerAction.Create, AbilitySubject.Customer)
@ApiOperation({ summary: 'Create a new customer.' }) @ApiOperation({ summary: 'Create a new customer.' })
@ApiResponse({ @ApiResponse({
status: 201, status: 201,
@@ -81,7 +71,6 @@ export class CustomersController {
} }
@Put(':id') @Put(':id')
@RequirePermission(CustomerAction.Edit, AbilitySubject.Customer)
@ApiOperation({ summary: 'Edit the given customer.' }) @ApiOperation({ summary: 'Edit the given customer.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -96,7 +85,6 @@ export class CustomersController {
} }
@Delete(':id') @Delete(':id')
@RequirePermission(CustomerAction.Delete, AbilitySubject.Customer)
@ApiOperation({ summary: 'Delete the given customer.' }) @ApiOperation({ summary: 'Delete the given customer.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -107,7 +95,6 @@ export class CustomersController {
} }
@Put(':id/opening-balance') @Put(':id/opening-balance')
@RequirePermission(CustomerAction.Edit, AbilitySubject.Customer)
@ApiOperation({ summary: 'Edit the opening balance of the given customer.' }) @ApiOperation({ summary: 'Edit the opening balance of the given customer.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -125,7 +112,6 @@ export class CustomersController {
} }
@Post('validate-bulk-delete') @Post('validate-bulk-delete')
@RequirePermission(CustomerAction.Delete, AbilitySubject.Customer)
@ApiOperation({ @ApiOperation({
summary: summary:
'Validates which customers can be deleted and returns counts of deletable and non-deletable customers.', 'Validates which customers can be deleted and returns counts of deletable and non-deletable customers.',
@@ -145,7 +131,6 @@ export class CustomersController {
} }
@Post('bulk-delete') @Post('bulk-delete')
@RequirePermission(CustomerAction.Delete, AbilitySubject.Customer)
@ApiOperation({ summary: 'Deletes multiple customers in bulk.' }) @ApiOperation({ summary: 'Deletes multiple customers in bulk.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -7,7 +7,6 @@ import {
Post, Post,
Put, Put,
Query, Query,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { ExpensesApplication } from './ExpensesApplication.service'; import { ExpensesApplication } from './ExpensesApplication.service';
import { IExpensesFilter } from './Expenses.types'; import { IExpensesFilter } from './Expenses.types';
@@ -26,11 +25,6 @@ import {
BulkDeleteDto, BulkDeleteDto,
ValidateBulkDeleteResponseDto, ValidateBulkDeleteResponseDto,
} from '@/common/dtos/BulkDelete.dto'; } from '@/common/dtos/BulkDelete.dto';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { ExpenseAction } from './Expenses.types';
@Controller('expenses') @Controller('expenses')
@ApiTags('Expenses') @ApiTags('Expenses')
@@ -40,12 +34,10 @@ import { ExpenseAction } from './Expenses.types';
ValidateBulkDeleteResponseDto, ValidateBulkDeleteResponseDto,
) )
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class ExpensesController { export class ExpensesController {
constructor(private readonly expensesApplication: ExpensesApplication) { } constructor(private readonly expensesApplication: ExpensesApplication) { }
@Post('validate-bulk-delete') @Post('validate-bulk-delete')
@RequirePermission(ExpenseAction.Delete, AbilitySubject.Expense)
@ApiOperation({ @ApiOperation({
summary: 'Validate which expenses can be deleted and return the results.', summary: 'Validate which expenses can be deleted and return the results.',
}) })
@@ -66,7 +58,6 @@ export class ExpensesController {
} }
@Post('bulk-delete') @Post('bulk-delete')
@RequirePermission(ExpenseAction.Delete, AbilitySubject.Expense)
@ApiOperation({ summary: 'Deletes multiple expenses.' }) @ApiOperation({ summary: 'Deletes multiple expenses.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -85,7 +76,6 @@ export class ExpensesController {
* @param {IExpenseCreateDTO} expenseDTO * @param {IExpenseCreateDTO} expenseDTO
*/ */
@Post() @Post()
@RequirePermission(ExpenseAction.Create, AbilitySubject.Expense)
@ApiOperation({ summary: 'Create a new expense transaction.' }) @ApiOperation({ summary: 'Create a new expense transaction.' })
public createExpense(@Body() expenseDTO: CreateExpenseDto) { public createExpense(@Body() expenseDTO: CreateExpenseDto) {
return this.expensesApplication.createExpense(expenseDTO); return this.expensesApplication.createExpense(expenseDTO);
@@ -97,7 +87,6 @@ export class ExpensesController {
* @param {IExpenseEditDTO} expenseDTO * @param {IExpenseEditDTO} expenseDTO
*/ */
@Put(':id') @Put(':id')
@RequirePermission(ExpenseAction.Edit, AbilitySubject.Expense)
@ApiOperation({ summary: 'Edit the given expense transaction.' }) @ApiOperation({ summary: 'Edit the given expense transaction.' })
public editExpense( public editExpense(
@Param('id') expenseId: number, @Param('id') expenseId: number,
@@ -111,7 +100,6 @@ export class ExpensesController {
* @param {number} expenseId * @param {number} expenseId
*/ */
@Delete(':id') @Delete(':id')
@RequirePermission(ExpenseAction.Delete, AbilitySubject.Expense)
@ApiOperation({ summary: 'Delete the given expense transaction.' }) @ApiOperation({ summary: 'Delete the given expense transaction.' })
public deleteExpense(@Param('id') expenseId: number) { public deleteExpense(@Param('id') expenseId: number) {
return this.expensesApplication.deleteExpense(expenseId); return this.expensesApplication.deleteExpense(expenseId);
@@ -122,7 +110,6 @@ export class ExpensesController {
* @param {number} expenseId * @param {number} expenseId
*/ */
@Post(':id/publish') @Post(':id/publish')
@RequirePermission(ExpenseAction.Edit, AbilitySubject.Expense)
@ApiOperation({ summary: 'Publish the given expense transaction.' }) @ApiOperation({ summary: 'Publish the given expense transaction.' })
public publishExpense(@Param('id') expenseId: number) { public publishExpense(@Param('id') expenseId: number) {
return this.expensesApplication.publishExpense(expenseId); return this.expensesApplication.publishExpense(expenseId);
@@ -132,7 +119,6 @@ export class ExpensesController {
* Get the expense transaction details. * Get the expense transaction details.
*/ */
@Get() @Get()
@RequirePermission(ExpenseAction.View, AbilitySubject.Expense)
@ApiOperation({ summary: 'Get the expense transactions.' }) @ApiOperation({ summary: 'Get the expense transactions.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -160,7 +146,6 @@ export class ExpensesController {
* @param {number} expenseId * @param {number} expenseId
*/ */
@Get(':id') @Get(':id')
@RequirePermission(ExpenseAction.View, AbilitySubject.Expense)
@ApiOperation({ summary: 'Get the expense transaction details.' }) @ApiOperation({ summary: 'Get the expense transaction details.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -1,5 +1,5 @@
import { Response } from 'express'; import { Response } from 'express';
import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
import { APAgingSummaryApplication } from './APAgingSummaryApplication'; import { APAgingSummaryApplication } from './APAgingSummaryApplication';
import { AcceptType } from '@/constants/accept-type'; import { AcceptType } from '@/constants/accept-type';
import { import {
@@ -11,21 +11,14 @@ import {
import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto';
import { APAgingSummaryResponseExample } from './APAgingSummary.swagger'; import { APAgingSummaryResponseExample } from './APAgingSummary.swagger';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { ReportsAction } from '../../types/Report.types';
@Controller('reports/payable-aging-summary') @Controller('reports/payable-aging-summary')
@ApiTags('Reports') @ApiTags('Reports')
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class APAgingSummaryController { export class APAgingSummaryController {
constructor(private readonly APAgingSummaryApp: APAgingSummaryApplication) { } constructor(private readonly APAgingSummaryApp: APAgingSummaryApplication) { }
@Get() @Get()
@RequirePermission(ReportsAction.READ_AP_AGING_SUMMARY, AbilitySubject.Report)
@ApiOperation({ summary: 'Get payable aging summary' }) @ApiOperation({ summary: 'Get payable aging summary' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -1,4 +1,5 @@
import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { Controller, Get, Headers } from '@nestjs/common';
import { Query, Res } from '@nestjs/common';
import { ARAgingSummaryApplication } from './ARAgingSummaryApplication'; import { ARAgingSummaryApplication } from './ARAgingSummaryApplication';
import { AcceptType } from '@/constants/accept-type'; import { AcceptType } from '@/constants/accept-type';
import { Response } from 'express'; import { Response } from 'express';
@@ -11,21 +12,14 @@ import {
import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto';
import { ARAgingSummaryResponseExample } from './ARAgingSummary.swagger'; import { ARAgingSummaryResponseExample } from './ARAgingSummary.swagger';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { ReportsAction } from '../../types/Report.types';
@Controller('reports/receivable-aging-summary') @Controller('reports/receivable-aging-summary')
@ApiTags('Reports') @ApiTags('Reports')
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class ARAgingSummaryController { export class ARAgingSummaryController {
constructor(private readonly ARAgingSummaryApp: ARAgingSummaryApplication) {} constructor(private readonly ARAgingSummaryApp: ARAgingSummaryApplication) {}
@Get() @Get()
@RequirePermission(ReportsAction.READ_AR_AGING_SUMMARY, AbilitySubject.Report)
@ApiOperation({ summary: 'Get receivable aging summary' }) @ApiOperation({ summary: 'Get receivable aging summary' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -1,5 +1,5 @@
import { Response } from 'express'; import { Response } from 'express';
import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
import { AcceptType } from '@/constants/accept-type'; import { AcceptType } from '@/constants/accept-type';
import { BalanceSheetApplication } from './BalanceSheetApplication'; import { BalanceSheetApplication } from './BalanceSheetApplication';
import { import {
@@ -11,16 +11,10 @@ import {
import { BalanceSheetQueryDto } from './BalanceSheet.dto'; import { BalanceSheetQueryDto } from './BalanceSheet.dto';
import { BalanceSheetResponseExample } from './BalanceSheet.swagger'; import { BalanceSheetResponseExample } from './BalanceSheet.swagger';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { ReportsAction } from '../../types/Report.types';
@Controller('/reports/balance-sheet') @Controller('/reports/balance-sheet')
@ApiTags('Reports') @ApiTags('Reports')
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class BalanceSheetStatementController { export class BalanceSheetStatementController {
constructor(private readonly balanceSheetApp: BalanceSheetApplication) {} constructor(private readonly balanceSheetApp: BalanceSheetApplication) {}
@@ -31,7 +25,6 @@ export class BalanceSheetStatementController {
* @param {string} acceptHeader - Accept header. * @param {string} acceptHeader - Accept header.
*/ */
@Get('') @Get('')
@RequirePermission(ReportsAction.READ_BALANCE_SHEET, AbilitySubject.Report)
@ApiOperation({ summary: 'Get balance sheet statement' }) @ApiOperation({ summary: 'Get balance sheet statement' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -24,14 +24,14 @@ export class BalanceSheetQueryDto extends FinancialSheetBranchesQueryDto {
displayColumnsType: 'total' | 'date_periods' = 'total'; displayColumnsType: 'total' | 'date_periods' = 'total';
@ApiProperty({ @ApiProperty({
enum: ['day', 'month', 'year', 'quarter'], enum: ['day', 'month', 'year'],
default: 'year', default: 'year',
description: 'Time period for column display', description: 'Time period for column display',
}) })
@IsString() @IsString()
@IsOptional() @IsOptional()
@IsEnum(['day', 'month', 'year', 'quarter']) @IsEnum(['day', 'month', 'year'])
displayColumnsBy: 'day' | 'month' | 'year' | 'quarter' = 'year'; displayColumnsBy: 'day' | 'month' | 'year' = 'year';
@ApiProperty({ @ApiProperty({
description: 'Start date for the balance sheet period', description: 'Start date for the balance sheet period',

View File

@@ -34,13 +34,13 @@ export class CashFlowStatementQueryDto extends FinancialSheetBranchesQueryDto {
@ApiProperty({ @ApiProperty({
description: 'Display columns by time period', description: 'Display columns by time period',
required: false, required: false,
enum: ['day', 'month', 'year', 'quarter'], enum: ['day', 'month', 'year'],
default: 'year', default: 'year',
}) })
@IsString() @IsString()
@IsOptional() @IsOptional()
@IsEnum(['day', 'month', 'year', 'quarter']) @IsEnum(['day', 'month', 'year'])
displayColumnsBy: 'day' | 'month' | 'year' | 'quarter' = 'year'; displayColumnsBy: 'day' | 'month' | 'year' = 'year';
@ApiProperty({ @ApiProperty({
description: 'Type of column display', description: 'Type of column display',

View File

@@ -1,5 +1,5 @@
import { Response } from 'express'; import { Response } from 'express';
import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
import { AcceptType } from '@/constants/accept-type'; import { AcceptType } from '@/constants/accept-type';
import { CashflowSheetApplication } from './CashflowSheetApplication'; import { CashflowSheetApplication } from './CashflowSheetApplication';
import { import {
@@ -11,21 +11,14 @@ import {
import { CashFlowStatementQueryDto } from './CashFlowStatementQuery.dto'; import { CashFlowStatementQueryDto } from './CashFlowStatementQuery.dto';
import { CashflowStatementResponseExample } from './CashflowStatement.swagger'; import { CashflowStatementResponseExample } from './CashflowStatement.swagger';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { ReportsAction } from '../../types/Report.types';
@Controller('reports/cashflow-statement') @Controller('reports/cashflow-statement')
@ApiTags('Reports') @ApiTags('Reports')
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class CashflowController { export class CashflowController {
constructor(private readonly cashflowSheetApp: CashflowSheetApplication) { } constructor(private readonly cashflowSheetApp: CashflowSheetApplication) { }
@Get() @Get()
@RequirePermission(ReportsAction.READ_CASHFLOW, AbilitySubject.Report)
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Cashflow statement report', description: 'Cashflow statement report',

View File

@@ -5,29 +5,22 @@ import {
ApiTags, ApiTags,
} from '@nestjs/swagger'; } from '@nestjs/swagger';
import { Response } from 'express'; import { Response } from 'express';
import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
import { GeneralLedgerApplication } from './GeneralLedgerApplication'; import { GeneralLedgerApplication } from './GeneralLedgerApplication';
import { AcceptType } from '@/constants/accept-type'; import { AcceptType } from '@/constants/accept-type';
import { GeneralLedgerQueryDto } from './GeneralLedgerQuery.dto'; import { GeneralLedgerQueryDto } from './GeneralLedgerQuery.dto';
import { GeneralLedgerResponseExample } from './GeneralLedger.swagger'; import { GeneralLedgerResponseExample } from './GeneralLedger.swagger';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { ReportsAction } from '../../types/Report.types';
@Controller('/reports/general-ledger') @Controller('/reports/general-ledger')
@ApiTags('Reports') @ApiTags('Reports')
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class GeneralLedgerController { export class GeneralLedgerController {
constructor( constructor(
private readonly generalLedgerApplication: GeneralLedgerApplication, private readonly generalLedgerApplication: GeneralLedgerApplication,
) {} ) {}
@Get() @Get()
@RequirePermission(ReportsAction.READ_GENERAL_LEDGET, AbilitySubject.Report)
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'General ledger report', description: 'General ledger report',

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
import { Response } from 'express'; import { Response } from 'express';
import { AcceptType } from '@/constants/accept-type'; import { AcceptType } from '@/constants/accept-type';
import { JournalSheetApplication } from './JournalSheetApplication'; import { JournalSheetApplication } from './JournalSheetApplication';
@@ -11,21 +11,14 @@ import {
import { JournalSheetQueryDto } from './JournalSheetQuery.dto'; import { JournalSheetQueryDto } from './JournalSheetQuery.dto';
import { JournalSheetResponseExample } from './JournalSheet.swagger'; import { JournalSheetResponseExample } from './JournalSheet.swagger';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { ReportsAction } from '../../types/Report.types';
@Controller('/reports/journal') @Controller('/reports/journal')
@ApiTags('Reports') @ApiTags('Reports')
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class JournalSheetController { export class JournalSheetController {
constructor(private readonly journalSheetApp: JournalSheetApplication) {} constructor(private readonly journalSheetApp: JournalSheetApplication) {}
@Get() @Get()
@RequirePermission(ReportsAction.READ_JOURNAL, AbilitySubject.Report)
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Journal report', description: 'Journal report',

View File

@@ -1,5 +1,5 @@
import { Response } from 'express'; import { Response } from 'express';
import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
import { ProfitLossSheetApplication } from './ProfitLossSheetApplication'; import { ProfitLossSheetApplication } from './ProfitLossSheetApplication';
import { AcceptType } from '@/constants/accept-type'; import { AcceptType } from '@/constants/accept-type';
import { import {
@@ -11,16 +11,10 @@ import {
import { ProfitLossSheetQueryDto } from './ProfitLossSheetQuery.dto'; import { ProfitLossSheetQueryDto } from './ProfitLossSheetQuery.dto';
import { ProfitLossSheetResponseExample } from './ProfitLossSheet.swagger'; import { ProfitLossSheetResponseExample } from './ProfitLossSheet.swagger';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { ReportsAction } from '../../types/Report.types';
@Controller('/reports/profit-loss-sheet') @Controller('/reports/profit-loss-sheet')
@ApiTags('Reports') @ApiTags('Reports')
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class ProfitLossSheetController { export class ProfitLossSheetController {
constructor( constructor(
private readonly profitLossSheetApp: ProfitLossSheetApplication, private readonly profitLossSheetApp: ProfitLossSheetApplication,
@@ -33,7 +27,6 @@ export class ProfitLossSheetController {
* @param {string} acceptHeader * @param {string} acceptHeader
*/ */
@Get('/') @Get('/')
@RequirePermission(ReportsAction.READ_PROFIT_LOSS, AbilitySubject.Report)
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Profit & loss statement', description: 'Profit & loss statement',

View File

@@ -64,10 +64,10 @@ export class ProfitLossSheetQueryDto extends FinancialSheetBranchesQueryDto {
displayColumnsType: 'total' | 'date_periods'; displayColumnsType: 'total' | 'date_periods';
@IsString() @IsString()
@IsEnum(['day', 'month', 'year', 'quarter']) @IsEnum(['day', 'month', 'year'])
@IsOptional() @IsOptional()
@ApiProperty({ description: 'How to display columns' }) @ApiProperty({ description: 'How to display columns' })
displayColumnsBy: 'day' | 'month' | 'year' | 'quarter' = 'year'; displayColumnsBy: 'day' | 'month' | 'year' = 'year';
@Transform(({ value }) => parseBoolean(value, false)) @Transform(({ value }) => parseBoolean(value, false))
@IsBoolean() @IsBoolean()

View File

@@ -172,11 +172,8 @@ export class TrialBalanceSheet extends FinancialSheet {
private filterNoneTransactions = ( private filterNoneTransactions = (
accountNode: ITrialBalanceAccount accountNode: ITrialBalanceAccount
): boolean => { ): boolean => {
const depsAccountsIds = const accountLedger = this.repository.totalAccountsLedger.whereAccountId(
this.repository.accountsDepGraph.dependenciesOf(accountNode.id); accountNode.id,
const accountLedger = this.repository.totalAccountsLedger.whereAccountsIds(
[accountNode.id, ...depsAccountsIds]
); );
return !accountLedger.isEmpty(); return !accountLedger.isEmpty();
}; };
@@ -244,8 +241,8 @@ export class TrialBalanceSheet extends FinancialSheet {
*/ */
private accountsSection(accounts: ModelObject<Account>[]) { private accountsSection(accounts: ModelObject<Account>[]) {
return R.compose( return R.compose(
this.accountsFilter,
this.nestedAccountsNode, this.nestedAccountsNode,
this.accountsFilter,
this.accountsMapper this.accountsMapper
)(accounts); )(accounts);
} }
@@ -253,6 +250,7 @@ export class TrialBalanceSheet extends FinancialSheet {
/** /**
* Retrieve trial balance sheet statement data. * Retrieve trial balance sheet statement data.
* Note: Retruns null in case there is no transactions between the given date periods. * Note: Retruns null in case there is no transactions between the given date periods.
*
* @return {ITrialBalanceSheetData} * @return {ITrialBalanceSheetData}
*/ */
public reportData(): ITrialBalanceSheetData { public reportData(): ITrialBalanceSheetData {

View File

@@ -14,7 +14,6 @@ import {
Post, Post,
Put, Put,
Query, Query,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { InventoryAdjustmentsApplicationService } from './InventoryAdjustmentsApplication.service'; import { InventoryAdjustmentsApplicationService } from './InventoryAdjustmentsApplication.service';
import { IInventoryAdjustmentsFilter } from './types/InventoryAdjustments.types'; import { IInventoryAdjustmentsFilter } from './types/InventoryAdjustments.types';
@@ -22,24 +21,17 @@ import { InventoryAdjustment } from './models/InventoryAdjustment';
import { CreateQuickInventoryAdjustmentDto } from './dtos/CreateQuickInventoryAdjustment.dto'; import { CreateQuickInventoryAdjustmentDto } from './dtos/CreateQuickInventoryAdjustment.dto';
import { InventoryAdjustmentResponseDto } from './dtos/InventoryAdjustmentResponse.dto'; import { InventoryAdjustmentResponseDto } from './dtos/InventoryAdjustmentResponse.dto';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { InventoryAdjustmentAction } from './types/InventoryAdjustments.types';
@Controller('inventory-adjustments') @Controller('inventory-adjustments')
@ApiTags('Inventory Adjustments') @ApiTags('Inventory Adjustments')
@ApiExtraModels(InventoryAdjustmentResponseDto) @ApiExtraModels(InventoryAdjustmentResponseDto)
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class InventoryAdjustmentsController { export class InventoryAdjustmentsController {
constructor( constructor(
private readonly inventoryAdjustmentsApplicationService: InventoryAdjustmentsApplicationService, private readonly inventoryAdjustmentsApplicationService: InventoryAdjustmentsApplicationService,
) {} ) {}
@Post('quick') @Post('quick')
@RequirePermission(InventoryAdjustmentAction.CREATE, AbilitySubject.InventoryAdjustment)
@ApiOperation({ summary: 'Create a quick inventory adjustment.' }) @ApiOperation({ summary: 'Create a quick inventory adjustment.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -54,7 +46,6 @@ export class InventoryAdjustmentsController {
} }
@Delete(':id') @Delete(':id')
@RequirePermission(InventoryAdjustmentAction.DELETE, AbilitySubject.InventoryAdjustment)
@ApiOperation({ summary: 'Delete the given inventory adjustment.' }) @ApiOperation({ summary: 'Delete the given inventory adjustment.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -69,7 +60,6 @@ export class InventoryAdjustmentsController {
} }
@Get() @Get()
@RequirePermission(InventoryAdjustmentAction.VIEW, AbilitySubject.InventoryAdjustment)
@ApiOperation({ summary: 'Retrieves the inventory adjustments.' }) @ApiOperation({ summary: 'Retrieves the inventory adjustments.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -88,7 +78,6 @@ export class InventoryAdjustmentsController {
} }
@Get(':id') @Get(':id')
@RequirePermission(InventoryAdjustmentAction.VIEW, AbilitySubject.InventoryAdjustment)
@ApiOperation({ summary: 'Retrieves the inventory adjustment details.' }) @ApiOperation({ summary: 'Retrieves the inventory adjustment details.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -105,7 +94,6 @@ export class InventoryAdjustmentsController {
} }
@Put(':id/publish') @Put(':id/publish')
@RequirePermission(InventoryAdjustmentAction.EDIT, AbilitySubject.InventoryAdjustment)
@ApiOperation({ summary: 'Publish the given inventory adjustment.' }) @ApiOperation({ summary: 'Publish the given inventory adjustment.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -7,7 +7,11 @@ import * as moment from 'moment';
import { TenantJobPayload } from '@/interfaces/Tenant'; import { TenantJobPayload } from '@/interfaces/Tenant';
import { InventoryComputeCostService } from '../commands/InventoryComputeCost.service'; import { InventoryComputeCostService } from '../commands/InventoryComputeCost.service';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { ComputeItemCostQueue } from '../types/InventoryCost.types'; import {
ComputeItemCostQueue,
ComputeItemCostQueueJob,
} from '../types/InventoryCost.types';
import { Process } from '@nestjs/bull';
interface ComputeItemCostJobPayload extends TenantJobPayload { interface ComputeItemCostJobPayload extends TenantJobPayload {
itemId: number; itemId: number;
@@ -35,6 +39,7 @@ export class ComputeItemCostProcessor extends WorkerHost {
* Process the compute item cost job. * Process the compute item cost job.
* @param {Job<ComputeItemCostJobPayload>} job - The job to process * @param {Job<ComputeItemCostJobPayload>} job - The job to process
*/ */
@Process(ComputeItemCostQueueJob)
@UseCls() @UseCls()
async process(job: Job<ComputeItemCostJobPayload>) { async process(job: Job<ComputeItemCostJobPayload>) {
const { itemId, startingDate, organizationId, userId } = job.data; const { itemId, startingDate, organizationId, userId } = job.data;

View File

@@ -1,4 +1,8 @@
import { WriteInventoryTransactionsGLEntriesQueue } from '../types/InventoryCost.types'; import { Process } from '@nestjs/bull';
import {
WriteInventoryTransactionsGLEntriesQueue,
WriteInventoryTransactionsGLEntriesQueueJob,
} from '../types/InventoryCost.types';
import { Processor, WorkerHost } from '@nestjs/bullmq'; import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Scope } from '@nestjs/common'; import { Scope } from '@nestjs/common';
@@ -11,5 +15,6 @@ export class WriteInventoryTransactionsGLEntriesProcessor extends WorkerHost {
super(); super();
} }
@Process(WriteInventoryTransactionsGLEntriesQueueJob)
async process() {} async process() {}
} }

View File

@@ -9,7 +9,6 @@ import {
Put, Put,
Query, Query,
HttpCode, HttpCode,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { TenantController } from '../Tenancy/Tenant.controller'; import { TenantController } from '../Tenancy/Tenant.controller';
import { ItemsApplicationService } from './ItemsApplication.service'; import { ItemsApplicationService } from './ItemsApplication.service';
@@ -35,12 +34,6 @@ import {
BulkDeleteItemsDto, BulkDeleteItemsDto,
ValidateBulkDeleteItemsResponseDto, ValidateBulkDeleteItemsResponseDto,
} from './dtos/BulkDeleteItems.dto'; } from './dtos/BulkDeleteItems.dto';
import { ItemApiErrorResponseDto } from './dtos/ItemErrorResponse.dto';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { ItemAction } from '@/interfaces/Item';
@Controller('/items') @Controller('/items')
@ApiTags('Items') @ApiTags('Items')
@@ -52,16 +45,13 @@ import { ItemAction } from '@/interfaces/Item';
@ApiExtraModels(ItemEstimatesResponseDto) @ApiExtraModels(ItemEstimatesResponseDto)
@ApiExtraModels(ItemReceiptsResponseDto) @ApiExtraModels(ItemReceiptsResponseDto)
@ApiExtraModels(ValidateBulkDeleteItemsResponseDto) @ApiExtraModels(ValidateBulkDeleteItemsResponseDto)
@ApiExtraModels(ItemApiErrorResponseDto)
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class ItemsController extends TenantController { export class ItemsController extends TenantController {
constructor(private readonly itemsApplication: ItemsApplicationService) { constructor(private readonly itemsApplication: ItemsApplicationService) {
super(); super();
} }
@Get() @Get()
@RequirePermission(ItemAction.VIEW, AbilitySubject.Item)
@ApiOperation({ summary: 'Retrieves the item list.' }) @ApiOperation({ summary: 'Retrieves the item list.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -152,19 +142,11 @@ export class ItemsController extends TenantController {
* @returns The updated item id. * @returns The updated item id.
*/ */
@Put(':id') @Put(':id')
@RequirePermission(ItemAction.EDIT, AbilitySubject.Item)
@ApiOperation({ summary: 'Edit the given item (product or service).' }) @ApiOperation({ summary: 'Edit the given item (product or service).' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'The item has been successfully updated.', description: 'The item has been successfully updated.',
}) })
@ApiResponse({
status: 400,
description: 'Validation error. Possible error types: ITEM_NAME_EXISTS, INVENTORY_ACCOUNT_CANNOT_MODIFIED, TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS, etc.',
schema: {
$ref: getSchemaPath(ItemApiErrorResponseDto),
},
})
@ApiResponse({ status: 404, description: 'The item not found.' }) @ApiResponse({ status: 404, description: 'The item not found.' })
@ApiParam({ @ApiParam({
name: 'id', name: 'id',
@@ -183,7 +165,6 @@ export class ItemsController extends TenantController {
@Post('validate-bulk-delete') @Post('validate-bulk-delete')
@HttpCode(200) @HttpCode(200)
@RequirePermission(ItemAction.DELETE, AbilitySubject.Item)
@ApiOperation({ @ApiOperation({
summary: summary:
'Validates which items can be deleted and returns counts of deletable and non-deletable items.', 'Validates which items can be deleted and returns counts of deletable and non-deletable items.',
@@ -204,7 +185,6 @@ export class ItemsController extends TenantController {
@Post('bulk-delete') @Post('bulk-delete')
@HttpCode(200) @HttpCode(200)
@RequirePermission(ItemAction.DELETE, AbilitySubject.Item)
@ApiOperation({ summary: 'Deletes multiple items in bulk.' }) @ApiOperation({ summary: 'Deletes multiple items in bulk.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -219,19 +199,11 @@ export class ItemsController extends TenantController {
} }
@Post() @Post()
@RequirePermission(ItemAction.CREATE, AbilitySubject.Item)
@ApiOperation({ summary: 'Create a new item (product or service).' }) @ApiOperation({ summary: 'Create a new item (product or service).' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'The item has been successfully created.', description: 'The item has been successfully created.',
}) })
@ApiResponse({
status: 400,
description: 'Validation error. Possible error types: ITEM_NAME_EXISTS, ITEM_CATEOGRY_NOT_FOUND, COST_ACCOUNT_NOT_COGS, SELL_ACCOUNT_NOT_INCOME, INVENTORY_ACCOUNT_NOT_INVENTORY, INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM, COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM, etc.',
schema: {
$ref: getSchemaPath(ItemApiErrorResponseDto),
},
})
// @UsePipes(new ZodValidationPipe(createItemSchema)) // @UsePipes(new ZodValidationPipe(createItemSchema))
async createItem( async createItem(
@Body() createItemDto: CreateItemDto, @Body() createItemDto: CreateItemDto,
@@ -242,19 +214,11 @@ export class ItemsController extends TenantController {
} }
@Delete(':id') @Delete(':id')
@RequirePermission(ItemAction.DELETE, AbilitySubject.Item)
@ApiOperation({ summary: 'Delete the given item (product or service).' }) @ApiOperation({ summary: 'Delete the given item (product or service).' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'The item has been successfully deleted.', description: 'The item has been successfully deleted.',
}) })
@ApiResponse({
status: 400,
description: 'Cannot delete item. Possible error types: ITEM_HAS_ASSOCIATED_TRANSACTINS, ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT, etc.',
schema: {
$ref: getSchemaPath(ItemApiErrorResponseDto),
},
})
@ApiResponse({ status: 404, description: 'The item not found.' }) @ApiResponse({ status: 404, description: 'The item not found.' })
@ApiParam({ @ApiParam({
name: 'id', name: 'id',
@@ -268,7 +232,6 @@ export class ItemsController extends TenantController {
} }
@Patch(':id/inactivate') @Patch(':id/inactivate')
@RequirePermission(ItemAction.EDIT, AbilitySubject.Item)
@ApiOperation({ summary: 'Inactivate the given item (product or service).' }) @ApiOperation({ summary: 'Inactivate the given item (product or service).' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -287,7 +250,6 @@ export class ItemsController extends TenantController {
} }
@Patch(':id/activate') @Patch(':id/activate')
@RequirePermission(ItemAction.EDIT, AbilitySubject.Item)
@ApiOperation({ summary: 'Activate the given item (product or service).' }) @ApiOperation({ summary: 'Activate the given item (product or service).' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -306,7 +268,6 @@ export class ItemsController extends TenantController {
} }
@Get(':id') @Get(':id')
@RequirePermission(ItemAction.VIEW, AbilitySubject.Item)
@ApiOperation({ summary: 'Get the given item (product or service).' }) @ApiOperation({ summary: 'Get the given item (product or service).' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -328,7 +289,6 @@ export class ItemsController extends TenantController {
} }
@Get(':id/invoices') @Get(':id/invoices')
@RequirePermission(ItemAction.VIEW, AbilitySubject.Item)
@ApiOperation({ @ApiOperation({
summary: 'Retrieves the item associated invoices transactions.', summary: 'Retrieves the item associated invoices transactions.',
}) })
@@ -354,7 +314,6 @@ export class ItemsController extends TenantController {
} }
@Get(':id/bills') @Get(':id/bills')
@RequirePermission(ItemAction.VIEW, AbilitySubject.Item)
@ApiOperation({ @ApiOperation({
summary: 'Retrieves the item associated bills transactions.', summary: 'Retrieves the item associated bills transactions.',
}) })
@@ -380,7 +339,6 @@ export class ItemsController extends TenantController {
} }
@Get(':id/estimates') @Get(':id/estimates')
@RequirePermission(ItemAction.VIEW, AbilitySubject.Item)
@ApiOperation({ @ApiOperation({
summary: 'Retrieves the item associated estimates transactions.', summary: 'Retrieves the item associated estimates transactions.',
}) })
@@ -406,7 +364,6 @@ export class ItemsController extends TenantController {
} }
@Get(':id/receipts') @Get(':id/receipts')
@RequirePermission(ItemAction.VIEW, AbilitySubject.Item)
@ApiOperation({ @ApiOperation({
summary: 'Retrieves the item associated receipts transactions.', summary: 'Retrieves the item associated receipts transactions.',
}) })

View File

@@ -1,112 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
/**
* Item API Error Types
* These error types are returned when item operations fail validation
*/
export enum ItemErrorType {
/** Item name already exists in the system */
ItemNameExists = 'ITEM_NAME_EXISTS',
/** Item category was not found */
ItemCategoryNotFound = 'ITEM_CATEOGRY_NOT_FOUND',
/** Cost account is not a Cost of Goods Sold account */
CostAccountNotCogs = 'COST_ACCOUNT_NOT_COGS',
/** Cost account was not found */
CostAccountNotFound = 'COST_ACCOUNT_NOT_FOUMD',
/** Sell account was not found */
SellAccountNotFound = 'SELL_ACCOUNT_NOT_FOUND',
/** Sell account is not an income account */
SellAccountNotIncome = 'SELL_ACCOUNT_NOT_INCOME',
/** Inventory account was not found */
InventoryAccountNotFound = 'INVENTORY_ACCOUNT_NOT_FOUND',
/** Account is not an inventory type account */
InventoryAccountNotInventory = 'INVENTORY_ACCOUNT_NOT_INVENTORY',
/** Multiple items have associated transactions */
ItemsHaveAssociatedTransactions = 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS',
/** Item has associated transactions (singular) */
ItemHasAssociatedTransactions = 'ITEM_HAS_ASSOCIATED_TRANSACTINS',
/** Item has associated inventory adjustments */
ItemHasAssociatedInventoryAdjustment = 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
/** Cannot change item type to inventory */
ItemCannotChangeInventoryType = 'ITEM_CANNOT_CHANGE_INVENTORY_TYPE',
/** Cannot change type when item has transactions */
TypeCannotChangeWithItemHasTransactions = 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
/** Inventory account cannot be modified */
InventoryAccountCannotModified = 'INVENTORY_ACCOUNT_CANNOT_MODIFIED',
/** Purchase tax rate was not found */
PurchaseTaxRateNotFound = 'PURCHASE_TAX_RATE_NOT_FOUND',
/** Sell tax rate was not found */
SellTaxRateNotFound = 'SELL_TAX_RATE_NOT_FOUND',
/** Income account is required for sellable items */
IncomeAccountRequiredWithSellableItem = 'INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM',
/** Cost account is required for purchasable items */
CostAccountRequiredWithPurchasableItem = 'COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM',
/** Item not found */
NotFound = 'NOT_FOUND',
/** Items not found */
ItemsNotFound = 'ITEMS_NOT_FOUND',
}
/**
* Item API Error Response
* Returned when an item operation fails
*/
export class ItemErrorResponseDto {
@ApiProperty({
description: 'HTTP status code',
example: 400,
})
statusCode: number;
@ApiProperty({
description: 'Error type identifier',
enum: ItemErrorType,
example: ItemErrorType.ItemNameExists,
})
type: ItemErrorType;
@ApiProperty({
description: 'Human-readable error message',
example: 'The item name is already exist.',
required: false,
nullable: true,
})
message: string | null;
@ApiProperty({
description: 'Additional error payload data',
required: false,
nullable: true,
})
payload: any;
}
/**
* Item API Error Response Wrapper
*/
export class ItemApiErrorResponseDto {
@ApiProperty({
description: 'Array of error details',
type: [ItemErrorResponseDto],
})
errors: ItemErrorResponseDto[];
}

View File

@@ -70,16 +70,6 @@ export class Item extends TenantBaseModel {
}; };
} }
/**
* Model search roles.
*/
static get searchRoles() {
return [
{ condition: 'or', fieldKey: 'name', comparator: 'contains' },
{ condition: 'or', fieldKey: 'code', comparator: 'like' },
];
}
/** /**
* Relationship mapping. * Relationship mapping.
*/ */

View File

@@ -8,7 +8,6 @@ import {
Post, Post,
Put, Put,
Query, Query,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { ManualJournalsApplication } from './ManualJournalsApplication.service'; import { ManualJournalsApplication } from './ManualJournalsApplication.service';
import { import {
@@ -30,23 +29,16 @@ import {
BulkDeleteDto, BulkDeleteDto,
ValidateBulkDeleteResponseDto, ValidateBulkDeleteResponseDto,
} from '@/common/dtos/BulkDelete.dto'; } from '@/common/dtos/BulkDelete.dto';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { ManualJournalAction } from './types/ManualJournals.types';
@Controller('manual-journals') @Controller('manual-journals')
@ApiTags('Manual Journals') @ApiTags('Manual Journals')
@ApiExtraModels(ManualJournalResponseDto) @ApiExtraModels(ManualJournalResponseDto)
@ApiExtraModels(ValidateBulkDeleteResponseDto) @ApiExtraModels(ValidateBulkDeleteResponseDto)
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class ManualJournalsController { export class ManualJournalsController {
constructor(private manualJournalsApplication: ManualJournalsApplication) { } constructor(private manualJournalsApplication: ManualJournalsApplication) { }
@Post('validate-bulk-delete') @Post('validate-bulk-delete')
@RequirePermission(ManualJournalAction.Delete, AbilitySubject.ManualJournal)
@ApiOperation({ @ApiOperation({
summary: summary:
'Validate which manual journals can be deleted and return the results.', 'Validate which manual journals can be deleted and return the results.',
@@ -68,7 +60,6 @@ export class ManualJournalsController {
} }
@Post('bulk-delete') @Post('bulk-delete')
@RequirePermission(ManualJournalAction.Delete, AbilitySubject.ManualJournal)
@ApiOperation({ summary: 'Deletes multiple manual journals.' }) @ApiOperation({ summary: 'Deletes multiple manual journals.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -84,7 +75,6 @@ export class ManualJournalsController {
} }
@Post() @Post()
@RequirePermission(ManualJournalAction.Create, AbilitySubject.ManualJournal)
@ApiOperation({ summary: 'Create a new manual journal.' }) @ApiOperation({ summary: 'Create a new manual journal.' })
@ApiResponse({ @ApiResponse({
status: 201, status: 201,
@@ -96,7 +86,6 @@ export class ManualJournalsController {
} }
@Put(':id') @Put(':id')
@RequirePermission(ManualJournalAction.Edit, AbilitySubject.ManualJournal)
@ApiOperation({ summary: 'Edit the given manual journal.' }) @ApiOperation({ summary: 'Edit the given manual journal.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -121,7 +110,6 @@ export class ManualJournalsController {
} }
@Delete(':id') @Delete(':id')
@RequirePermission(ManualJournalAction.Delete, AbilitySubject.ManualJournal)
@ApiOperation({ summary: 'Delete the given manual journal.' }) @ApiOperation({ summary: 'Delete the given manual journal.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -139,7 +127,6 @@ export class ManualJournalsController {
} }
@Patch(':id/publish') @Patch(':id/publish')
@RequirePermission(ManualJournalAction.Edit, AbilitySubject.ManualJournal)
@ApiOperation({ summary: 'Publish the given manual journal.' }) @ApiOperation({ summary: 'Publish the given manual journal.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -160,7 +147,6 @@ export class ManualJournalsController {
} }
@Get(':id') @Get(':id')
@RequirePermission(ManualJournalAction.View, AbilitySubject.ManualJournal)
@ApiOperation({ summary: 'Retrieves the manual journal details.' }) @ApiOperation({ summary: 'Retrieves the manual journal details.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -181,7 +167,6 @@ export class ManualJournalsController {
} }
@Get() @Get()
@RequirePermission(ManualJournalAction.View, AbilitySubject.ManualJournal)
@ApiOperation({ summary: 'Retrieves the manual journals paginated list.' }) @ApiOperation({ summary: 'Retrieves the manual journals paginated list.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -17,7 +17,6 @@ import {
HttpCode, HttpCode,
Param, Param,
} from '@nestjs/common'; } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { BuildOrganizationService } from './commands/BuildOrganization.service'; import { BuildOrganizationService } from './commands/BuildOrganization.service';
import { import {
BuildOrganizationDto, BuildOrganizationDto,
@@ -78,7 +77,6 @@ export class OrganizationController {
} }
@Get('build/:buildJobId') @Get('build/:buildJobId')
@Throttle({ default: { limit: 300, ttl: 60000 } }) // 300 req/min
@ApiParam({ @ApiParam({
name: 'buildJobId', name: 'buildJobId',
required: true, required: true,

View File

@@ -2,8 +2,10 @@ import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Scope } from '@nestjs/common'; import { Scope } from '@nestjs/common';
import { Job } from 'bullmq'; import { Job } from 'bullmq';
import { ClsService, UseCls } from 'nestjs-cls'; import { ClsService, UseCls } from 'nestjs-cls';
import { Process } from '@nestjs/bull';
import { import {
OrganizationBuildQueue, OrganizationBuildQueue,
OrganizationBuildQueueJob,
OrganizationBuildQueueJobPayload, OrganizationBuildQueueJobPayload,
} from '../Organization.types'; } from '../Organization.types';
import { BuildOrganizationService } from '../commands/BuildOrganization.service'; import { BuildOrganizationService } from '../commands/BuildOrganization.service';
@@ -20,6 +22,7 @@ export class OrganizationBuildProcessor extends WorkerHost {
super(); super();
} }
@Process(OrganizationBuildQueueJob)
@UseCls() @UseCls()
async process(job: Job<OrganizationBuildQueueJobPayload>) { async process(job: Job<OrganizationBuildQueueJobPayload>) {
console.log('Processing organization build job:', job.id); console.log('Processing organization build job:', job.id);

View File

@@ -19,7 +19,6 @@ import {
Put, Put,
Query, Query,
Res, Res,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { PaymentReceivesApplication } from './PaymentReceived.application'; import { PaymentReceivesApplication } from './PaymentReceived.application';
import { import {
@@ -39,11 +38,6 @@ import {
BulkDeleteDto, BulkDeleteDto,
ValidateBulkDeleteResponseDto, ValidateBulkDeleteResponseDto,
} from '@/common/dtos/BulkDelete.dto'; } from '@/common/dtos/BulkDelete.dto';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { PaymentReceiveAction } from './types/PaymentReceived.types';
@Controller('payments-received') @Controller('payments-received')
@ApiTags('Payments Received') @ApiTags('Payments Received')
@@ -52,7 +46,6 @@ import { PaymentReceiveAction } from './types/PaymentReceived.types';
@ApiExtraModels(PaymentReceivedStateResponseDto) @ApiExtraModels(PaymentReceivedStateResponseDto)
@ApiExtraModels(ValidateBulkDeleteResponseDto) @ApiExtraModels(ValidateBulkDeleteResponseDto)
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class PaymentReceivesController { export class PaymentReceivesController {
constructor(private paymentReceivesApplication: PaymentReceivesApplication) { } constructor(private paymentReceivesApplication: PaymentReceivesApplication) { }
@@ -101,7 +94,6 @@ export class PaymentReceivesController {
} }
@Post() @Post()
@RequirePermission(PaymentReceiveAction.Create, AbilitySubject.PaymentReceive)
@ApiOperation({ summary: 'Create a new payment received.' }) @ApiOperation({ summary: 'Create a new payment received.' })
public createPaymentReceived( public createPaymentReceived(
@Body() paymentReceiveDTO: CreatePaymentReceivedDto, @Body() paymentReceiveDTO: CreatePaymentReceivedDto,
@@ -112,7 +104,6 @@ export class PaymentReceivesController {
} }
@Put(':id') @Put(':id')
@RequirePermission(PaymentReceiveAction.Edit, AbilitySubject.PaymentReceive)
@ApiOperation({ summary: 'Edit the given payment received.' }) @ApiOperation({ summary: 'Edit the given payment received.' })
public editPaymentReceive( public editPaymentReceive(
@Param('id', ParseIntPipe) paymentReceiveId: number, @Param('id', ParseIntPipe) paymentReceiveId: number,
@@ -125,7 +116,6 @@ export class PaymentReceivesController {
} }
@Delete(':id') @Delete(':id')
@RequirePermission(PaymentReceiveAction.Delete, AbilitySubject.PaymentReceive)
@ApiOperation({ summary: 'Delete the given payment received.' }) @ApiOperation({ summary: 'Delete the given payment received.' })
public deletePaymentReceive( public deletePaymentReceive(
@Param('id', ParseIntPipe) paymentReceiveId: number, @Param('id', ParseIntPipe) paymentReceiveId: number,
@@ -136,7 +126,6 @@ export class PaymentReceivesController {
} }
@Get() @Get()
@RequirePermission(PaymentReceiveAction.View, AbilitySubject.PaymentReceive)
@ApiOperation({ summary: 'Retrieves the payment received list.' }) @ApiOperation({ summary: 'Retrieves the payment received list.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -162,7 +151,6 @@ export class PaymentReceivesController {
} }
@Post('validate-bulk-delete') @Post('validate-bulk-delete')
@RequirePermission(PaymentReceiveAction.Delete, AbilitySubject.PaymentReceive)
@ApiOperation({ @ApiOperation({
summary: summary:
'Validates which payments received can be deleted and returns the results.', 'Validates which payments received can be deleted and returns the results.',
@@ -184,7 +172,6 @@ export class PaymentReceivesController {
} }
@Post('bulk-delete') @Post('bulk-delete')
@RequirePermission(PaymentReceiveAction.Delete, AbilitySubject.PaymentReceive)
@ApiOperation({ summary: 'Deletes multiple payments received.' }) @ApiOperation({ summary: 'Deletes multiple payments received.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -200,7 +187,6 @@ export class PaymentReceivesController {
} }
@Get('state') @Get('state')
@RequirePermission(PaymentReceiveAction.View, AbilitySubject.PaymentReceive)
@ApiOperation({ summary: 'Retrieves the payment received state.' }) @ApiOperation({ summary: 'Retrieves the payment received state.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -214,7 +200,6 @@ export class PaymentReceivesController {
} }
@Get(':id/invoices') @Get(':id/invoices')
@RequirePermission(PaymentReceiveAction.View, AbilitySubject.PaymentReceive)
@ApiOperation({ summary: 'Retrieves the payment received invoices.' }) @ApiOperation({ summary: 'Retrieves the payment received invoices.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -230,7 +215,6 @@ export class PaymentReceivesController {
} }
@Get(':id') @Get(':id')
@RequirePermission(PaymentReceiveAction.View, AbilitySubject.PaymentReceive)
@ApiOperation({ summary: 'Retrieves the payment received details.' }) @ApiOperation({ summary: 'Retrieves the payment received details.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { BullBoardModule } from '@bull-board/nestjs'; import { BullBoardModule } from '@bull-board/nestjs';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; import { BullAdapter } from '@bull-board/api/bullAdapter';
import { BullModule } from '@nestjs/bullmq'; import { BullModule } from '@nestjs/bull';
import { PaymentReceivesController } from './PaymentsReceived.controller'; import { PaymentReceivesController } from './PaymentsReceived.controller';
import { PaymentReceivesApplication } from './PaymentReceived.application'; import { PaymentReceivesApplication } from './PaymentReceived.application';
import { CreatePaymentReceivedService } from './commands/CreatePaymentReceived.serivce'; import { CreatePaymentReceivedService } from './commands/CreatePaymentReceived.serivce';
@@ -99,7 +99,7 @@ import { ValidateBulkDeletePaymentReceivedService } from './ValidateBulkDeletePa
BullModule.registerQueue({ name: SEND_PAYMENT_RECEIVED_MAIL_QUEUE }), BullModule.registerQueue({ name: SEND_PAYMENT_RECEIVED_MAIL_QUEUE }),
BullBoardModule.forFeature({ BullBoardModule.forFeature({
name: SEND_PAYMENT_RECEIVED_MAIL_QUEUE, name: SEND_PAYMENT_RECEIVED_MAIL_QUEUE,
adapter: BullMQAdapter, adapter: BullAdapter,
}), }),
], ],
}) })

View File

@@ -1,6 +1,6 @@
import { Processor, WorkerHost } from '@nestjs/bullmq'; import { JOB_REF, Process, Processor } from '@nestjs/bull';
import { Job } from 'bullmq'; import { Job } from 'bull';
import { Scope } from '@nestjs/common'; import { Inject, Scope } from '@nestjs/common';
import { ClsService, UseCls } from 'nestjs-cls'; import { ClsService, UseCls } from 'nestjs-cls';
import { import {
SEND_PAYMENT_RECEIVED_MAIL_JOB, SEND_PAYMENT_RECEIVED_MAIL_JOB,
@@ -13,18 +13,20 @@ import { SendPaymentReceivedMailPayload } from '../types/PaymentReceived.types';
name: SEND_PAYMENT_RECEIVED_MAIL_QUEUE, name: SEND_PAYMENT_RECEIVED_MAIL_QUEUE,
scope: Scope.REQUEST, scope: Scope.REQUEST,
}) })
export class SendPaymentReceivedMailProcessor extends WorkerHost { export class SendPaymentReceivedMailProcessor {
constructor( constructor(
private readonly sendPaymentReceivedMail: SendPaymentReceiveMailNotification, private readonly sendPaymentReceivedMail: SendPaymentReceiveMailNotification,
private readonly clsService: ClsService, private readonly clsService: ClsService,
) {
super();
}
@Inject(JOB_REF)
private readonly jobRef: Job<SendPaymentReceivedMailPayload>,
) { }
@Process(SEND_PAYMENT_RECEIVED_MAIL_JOB)
@UseCls() @UseCls()
async process(job: Job<SendPaymentReceivedMailPayload>) { async handleSendMail() {
const { messageOptions, paymentReceivedId, organizationId, userId } = const { messageOptions, paymentReceivedId, organizationId, userId } =
job.data; this.jobRef.data;
this.clsService.set('organizationId', organizationId); this.clsService.set('organizationId', organizationId);
this.clsService.set('userId', userId); this.clsService.set('userId', userId);

View File

@@ -31,10 +31,9 @@ export class AuthorizationGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> { async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request>(); const request = context.switchToHttp().getRequest<Request>();
const { user } = request as any; const { user } = request as any;
const userId = this.clsService.get('userId');
if (ABILITIES_CACHE.has(userId)) { if (ABILITIES_CACHE.has(user.id)) {
(request as any).ability = ABILITIES_CACHE.get(userId); (request as any).ability = ABILITIES_CACHE.get(user.id);
} else { } else {
const ability = await this.getAbilityForUser(); const ability = await this.getAbilityForUser();
(request as any).ability = ability; (request as any).ability = ability;

View File

@@ -1,67 +0,0 @@
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { REQUIRED_PERMISSION_KEY, RequiredPermission } from './RequirePermission.decorator';
import { AbilitySubject } from './Roles.types';
/**
* Guard that checks if the user has the required permission to access a route.
* Uses CASL ability instance attached to the request by AuthorizationGuard.
*/
@Injectable()
export class PermissionGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
/**
* Checks if the user has the required permission to access the route.
* @param context - The execution context
* @returns A boolean indicating if the user can access the route
* @throws ForbiddenException if the user doesn't have the required permission
*/
canActivate(context: ExecutionContext): boolean {
const requiredPermission = this.reflector.getAllAndOverride<RequiredPermission>(
REQUIRED_PERMISSION_KEY,
[context.getHandler(), context.getClass()],
);
// If no permission is required, allow access
if (!requiredPermission) {
return true;
}
const request = context.switchToHttp().getRequest<Request>();
const ability = (request as any).ability;
// If no ability instance is attached to the request, deny access
if (!ability) {
throw new ForbiddenException('Ability instance not found. Ensure AuthorizationGuard is applied.');
}
const { ability: action, subject } = requiredPermission;
// Check if the user has the required permission using CASL ability
const hasPermission = ability.can(action, subject);
if (!hasPermission) {
throw new ForbiddenException(
`You do not have permission to ${action} ${subject}`,
);
}
return true;
}
/**
* Helper method to check if a subject value is a valid AbilitySubject.
* @param subject - The subject value to check
* @returns True if the subject is a valid AbilitySubject enum value
*/
private isValidSubject(subject: string): subject is AbilitySubject {
return Object.values(AbilitySubject).includes(subject as AbilitySubject);
}
}

View File

@@ -1,29 +0,0 @@
import { SetMetadata } from '@nestjs/common';
import { AbilitySubject } from './Roles.types';
export const REQUIRED_PERMISSION_KEY = 'requiredPermission';
export interface RequiredPermission {
ability: string;
subject: AbilitySubject | string;
}
/**
* Decorator to specify required ability and subject for a route handler or controller.
* @param ability - The ability/action required (e.g., 'Create', 'View', 'Edit', 'Delete')
* @param subject - The subject/entity the ability applies to (e.g., AbilitySubject.Item, AbilitySubject.SaleInvoice)
* @example
* ```typescript
* @RequirePermission('Create', AbilitySubject.Item)
* @Post()
* async createItem(@Body() dto: CreateItemDto) { ... }
*
* @RequirePermission('View', AbilitySubject.SaleInvoice)
* @Get(':id')
* async getInvoice(@Param('id') id: number) { ... }
* ```
*/
export const RequirePermission = (
ability: string,
subject: AbilitySubject | string,
) => SetMetadata(REQUIRED_PERMISSION_KEY, { ability, subject });

View File

@@ -10,8 +10,6 @@ import { RolePermission } from './models/RolePermission.model';
import { RolesController } from './Roles.controller'; import { RolesController } from './Roles.controller';
import { RolesApplication } from './Roles.application'; import { RolesApplication } from './Roles.application';
import { RolePermissionsSchema } from './queries/RolePermissionsSchema'; import { RolePermissionsSchema } from './queries/RolePermissionsSchema';
import { AuthorizationGuard } from './Authorization.guard';
import { PermissionGuard } from './Permission.guard';
const models = [ const models = [
RegisterTenancyModel(Role), RegisterTenancyModel(Role),
@@ -27,11 +25,9 @@ const models = [
GetRoleService, GetRoleService,
GetRolesService, GetRolesService,
RolesApplication, RolesApplication,
RolePermissionsSchema, RolePermissionsSchema
AuthorizationGuard,
PermissionGuard,
], ],
controllers: [RolesController], controllers: [RolesController],
exports: [...models, AuthorizationGuard, PermissionGuard], exports: [...models],
}) })
export class RolesModule {} export class RolesModule {}

View File

@@ -1,4 +1,3 @@
import { IsOptional } from '@/common/decorators/Validators';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { import {
@@ -42,7 +41,7 @@ export class CommandRolePermissionDto {
export class CreateRolePermissionDto extends CommandRolePermissionDto { } export class CreateRolePermissionDto extends CommandRolePermissionDto { }
export class EditRolePermissionDto extends CommandRolePermissionDto { export class EditRolePermissionDto extends CommandRolePermissionDto {
@IsNumber() @IsNumber()
@IsOptional() @IsNotEmpty()
@ApiProperty({ @ApiProperty({
example: 1, example: 1,
description: 'The permission ID', description: 'The permission ID',
@@ -60,6 +59,7 @@ class CommandRoleDto {
roleName: string; roleName: string;
@IsString() @IsString()
@IsNotEmpty()
@ApiProperty({ @ApiProperty({
example: 'Administrator', example: 'Administrator',
description: 'The description of the role', description: 'The description of the role',
@@ -71,9 +71,9 @@ export class CreateRoleDto extends CommandRoleDto {
@IsArray() @IsArray()
@ArrayMinSize(1) @ArrayMinSize(1)
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => CreateRolePermissionDto) @Type(() => CommandRolePermissionDto)
@ApiProperty({ @ApiProperty({
type: [CreateRolePermissionDto], type: [CommandRolePermissionDto],
description: 'The permissions of the role', description: 'The permissions of the role',
}) })
permissions: Array<CreateRolePermissionDto>; permissions: Array<CreateRolePermissionDto>;

View File

@@ -19,7 +19,6 @@ import {
Put, Put,
Query, Query,
Res, Res,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { SaleEstimatesApplication } from './SaleEstimates.application'; import { SaleEstimatesApplication } from './SaleEstimates.application';
import { import {
@@ -41,11 +40,6 @@ import {
BulkDeleteDto, BulkDeleteDto,
ValidateBulkDeleteResponseDto, ValidateBulkDeleteResponseDto,
} from '@/common/dtos/BulkDelete.dto'; } from '@/common/dtos/BulkDelete.dto';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { SaleEstimateAction } from './types/SaleEstimates.types';
@Controller('sale-estimates') @Controller('sale-estimates')
@ApiTags('Sale Estimates') @ApiTags('Sale Estimates')
@@ -54,10 +48,8 @@ import { SaleEstimateAction } from './types/SaleEstimates.types';
@ApiExtraModels(SaleEstiamteStateResponseDto) @ApiExtraModels(SaleEstiamteStateResponseDto)
@ApiCommonHeaders() @ApiCommonHeaders()
@ApiExtraModels(ValidateBulkDeleteResponseDto) @ApiExtraModels(ValidateBulkDeleteResponseDto)
@UseGuards(AuthorizationGuard, PermissionGuard)
export class SaleEstimatesController { export class SaleEstimatesController {
@Post('validate-bulk-delete') @Post('validate-bulk-delete')
@RequirePermission(SaleEstimateAction.Delete, AbilitySubject.SaleEstimate)
@ApiOperation({ @ApiOperation({
summary: summary:
'Validates which sale estimates can be deleted and returns the results.', 'Validates which sale estimates can be deleted and returns the results.',
@@ -79,7 +71,6 @@ export class SaleEstimatesController {
} }
@Post('bulk-delete') @Post('bulk-delete')
@RequirePermission(SaleEstimateAction.Delete, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Deletes multiple sale estimates.' }) @ApiOperation({ summary: 'Deletes multiple sale estimates.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -102,7 +93,6 @@ export class SaleEstimatesController {
) { } ) { }
@Post() @Post()
@RequirePermission(SaleEstimateAction.Create, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Create a new sale estimate.' }) @ApiOperation({ summary: 'Create a new sale estimate.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -115,7 +105,6 @@ export class SaleEstimatesController {
} }
@Put(':id') @Put(':id')
@RequirePermission(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Edit the given sale estimate.' }) @ApiOperation({ summary: 'Edit the given sale estimate.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -142,7 +131,6 @@ export class SaleEstimatesController {
} }
@Delete(':id') @Delete(':id')
@RequirePermission(SaleEstimateAction.Delete, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Delete the given sale estimate.' }) @ApiOperation({ summary: 'Delete the given sale estimate.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -165,7 +153,6 @@ export class SaleEstimatesController {
} }
@Get('state') @Get('state')
@RequirePermission(SaleEstimateAction.View, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Retrieves the sale estimate state.' }) @ApiOperation({ summary: 'Retrieves the sale estimate state.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -179,7 +166,6 @@ export class SaleEstimatesController {
} }
@Get() @Get()
@RequirePermission(SaleEstimateAction.View, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Retrieves the sale estimates.' }) @ApiOperation({ summary: 'Retrieves the sale estimates.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -203,7 +189,6 @@ export class SaleEstimatesController {
} }
@Post(':id/deliver') @Post(':id/deliver')
@RequirePermission(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Deliver the given sale estimate.' }) @ApiOperation({ summary: 'Deliver the given sale estimate.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -222,7 +207,6 @@ export class SaleEstimatesController {
} }
@Put(':id/approve') @Put(':id/approve')
@RequirePermission(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Approve the given sale estimate.' }) @ApiOperation({ summary: 'Approve the given sale estimate.' })
@ApiParam({ @ApiParam({
name: 'id', name: 'id',
@@ -237,7 +221,6 @@ export class SaleEstimatesController {
} }
@Put(':id/reject') @Put(':id/reject')
@RequirePermission(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Reject the given sale estimate.' }) @ApiOperation({ summary: 'Reject the given sale estimate.' })
@ApiParam({ @ApiParam({
name: 'id', name: 'id',
@@ -252,7 +235,6 @@ export class SaleEstimatesController {
} }
@Post(':id/notify-sms') @Post(':id/notify-sms')
@RequirePermission(SaleEstimateAction.NotifyBySms, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Notify the given sale estimate by SMS.' }) @ApiOperation({ summary: 'Notify the given sale estimate by SMS.' })
@ApiParam({ @ApiParam({
name: 'id', name: 'id',
@@ -269,7 +251,6 @@ export class SaleEstimatesController {
} }
@Get(':id/sms-details') @Get(':id/sms-details')
@RequirePermission(SaleEstimateAction.View, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Retrieves the sale estimate SMS details.' }) @ApiOperation({ summary: 'Retrieves the sale estimate SMS details.' })
public getSaleEstimateSmsDetails( public getSaleEstimateSmsDetails(
@Param('id', ParseIntPipe) saleEstimateId: number, @Param('id', ParseIntPipe) saleEstimateId: number,
@@ -281,7 +262,6 @@ export class SaleEstimatesController {
@Post(':id/mail') @Post(':id/mail')
@HttpCode(200) @HttpCode(200)
@RequirePermission(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Send the given sale estimate by mail.' }) @ApiOperation({ summary: 'Send the given sale estimate by mail.' })
@ApiParam({ @ApiParam({
name: 'id', name: 'id',
@@ -300,7 +280,6 @@ export class SaleEstimatesController {
} }
@Get(':id/mail') @Get(':id/mail')
@RequirePermission(SaleEstimateAction.View, AbilitySubject.SaleEstimate)
@ApiOperation({ summary: 'Retrieves the sale estimate mail state.' }) @ApiOperation({ summary: 'Retrieves the sale estimate mail state.' })
@ApiParam({ @ApiParam({
name: 'id', name: 'id',
@@ -317,7 +296,6 @@ export class SaleEstimatesController {
} }
@Get(':id') @Get(':id')
@RequirePermission(SaleEstimateAction.View, AbilitySubject.SaleEstimate)
@ApiOperation({ @ApiOperation({
summary: 'Retrieves the sale estimate details.', summary: 'Retrieves the sale estimate details.',
}) })

View File

@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { BullBoardModule } from '@bull-board/nestjs'; import { BullBoardModule } from '@bull-board/nestjs';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; import { BullAdapter } from '@bull-board/api/bullAdapter';
import { BullModule } from '@nestjs/bullmq'; import { BullModule } from '@nestjs/bull';
import { TenancyContext } from '../Tenancy/TenancyContext.service'; import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module'; import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
@@ -58,7 +58,7 @@ import { SendSaleEstimateMailProcess } from './processes/SendSaleEstimateMail.pr
BullModule.registerQueue({ name: SendSaleEstimateMailQueue }), BullModule.registerQueue({ name: SendSaleEstimateMailQueue }),
BullBoardModule.forFeature({ BullBoardModule.forFeature({
name: SendSaleEstimateMailQueue, name: SendSaleEstimateMailQueue,
adapter: BullMQAdapter, adapter: BullAdapter,
}), }),
], ],
controllers: [SaleEstimatesController], controllers: [SaleEstimatesController],

View File

@@ -1,5 +1,5 @@
import { InjectQueue } from '@nestjs/bullmq'; import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq'; import { Queue } from 'bull';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { ContactMailNotification } from '@/modules/MailNotification/ContactMailNotification'; import { ContactMailNotification } from '@/modules/MailNotification/ContactMailNotification';

View File

@@ -1,6 +1,7 @@
import { Processor, WorkerHost } from '@nestjs/bullmq'; import { Process, Processor } from '@nestjs/bull';
import { Job } from 'bullmq'; import { Job } from 'bull';
import { Scope } from '@nestjs/common'; import { Inject, Scope } from '@nestjs/common';
import { JOB_REF } from '@nestjs/bull';
import { import {
SendSaleEstimateMailJob, SendSaleEstimateMailJob,
SendSaleEstimateMailQueue, SendSaleEstimateMailQueue,
@@ -12,17 +13,18 @@ import { ClsService, UseCls } from 'nestjs-cls';
name: SendSaleEstimateMailQueue, name: SendSaleEstimateMailQueue,
scope: Scope.REQUEST, scope: Scope.REQUEST,
}) })
export class SendSaleEstimateMailProcess extends WorkerHost { export class SendSaleEstimateMailProcess {
constructor( constructor(
private readonly sendEstimateMailService: SendSaleEstimateMail, private readonly sendEstimateMailService: SendSaleEstimateMail,
private readonly clsService: ClsService, private readonly clsService: ClsService,
) { @Inject(JOB_REF)
super(); private readonly jobRef: Job,
} ) { }
@Process(SendSaleEstimateMailJob)
@UseCls() @UseCls()
async process(job: Job) { async handleSendMail() {
const { saleEstimateId, messageOptions, organizationId, userId } = job.data; const { saleEstimateId, messageOptions, organizationId, userId } = this.jobRef.data;
this.clsService.set('organizationId', organizationId); this.clsService.set('organizationId', organizationId);
this.clsService.set('userId', userId); this.clsService.set('userId', userId);

View File

@@ -9,7 +9,7 @@ import { CommonMailOptionsDTO } from '@/modules/MailNotification/MailNotificatio
import { CommonMailOptions } from '@/modules/MailNotification/MailNotification.types'; import { CommonMailOptions } from '@/modules/MailNotification/MailNotification.types';
import { EditSaleEstimateDto } from '../dtos/SaleEstimate.dto'; import { EditSaleEstimateDto } from '../dtos/SaleEstimate.dto';
export const SendSaleEstimateMailQueue = 'SendSaleEstimateMailQueue'; export const SendSaleEstimateMailQueue = 'SendSaleEstimateMailProcessor';
export const SendSaleEstimateMailJob = 'SendSaleEstimateMailProcess'; export const SendSaleEstimateMailJob = 'SendSaleEstimateMailProcess';
export interface ISaleEstimateDTO { export interface ISaleEstimateDTO {

View File

@@ -12,7 +12,6 @@ import {
Put, Put,
Query, Query,
Res, Res,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
ISaleInvoiceWriteoffDTO, ISaleInvoiceWriteoffDTO,
@@ -44,11 +43,6 @@ import {
BulkDeleteDto, BulkDeleteDto,
ValidateBulkDeleteResponseDto, ValidateBulkDeleteResponseDto,
} from '@/common/dtos/BulkDelete.dto'; } from '@/common/dtos/BulkDelete.dto';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { SaleInvoiceAction } from './SaleInvoice.types';
@Controller('sale-invoices') @Controller('sale-invoices')
@ApiTags('Sale Invoices') @ApiTags('Sale Invoices')
@@ -58,12 +52,10 @@ import { SaleInvoiceAction } from './SaleInvoice.types';
@ApiExtraModels(GenerateSaleInvoiceSharableLinkResponseDto) @ApiExtraModels(GenerateSaleInvoiceSharableLinkResponseDto)
@ApiCommonHeaders() @ApiCommonHeaders()
@ApiExtraModels(ValidateBulkDeleteResponseDto) @ApiExtraModels(ValidateBulkDeleteResponseDto)
@UseGuards(AuthorizationGuard, PermissionGuard)
export class SaleInvoicesController { export class SaleInvoicesController {
constructor(private saleInvoiceApplication: SaleInvoiceApplication) { } constructor(private saleInvoiceApplication: SaleInvoiceApplication) { }
@Post('validate-bulk-delete') @Post('validate-bulk-delete')
@RequirePermission(SaleInvoiceAction.Delete, AbilitySubject.SaleInvoice)
@ApiOperation({ @ApiOperation({
summary: summary:
'Validates which sale invoices can be deleted and returns the results.', 'Validates which sale invoices can be deleted and returns the results.',
@@ -85,7 +77,6 @@ export class SaleInvoicesController {
} }
@Post('bulk-delete') @Post('bulk-delete')
@RequirePermission(SaleInvoiceAction.Delete, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Deletes multiple sale invoices.' }) @ApiOperation({ summary: 'Deletes multiple sale invoices.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -99,7 +90,6 @@ export class SaleInvoicesController {
} }
@Post() @Post()
@RequirePermission(SaleInvoiceAction.Create, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Create a new sale invoice.' }) @ApiOperation({ summary: 'Create a new sale invoice.' })
@ApiResponse({ @ApiResponse({
status: 201, status: 201,
@@ -131,7 +121,6 @@ export class SaleInvoicesController {
} }
@Put(':id') @Put(':id')
@RequirePermission(SaleInvoiceAction.Edit, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Edit the given sale invoice.' }) @ApiOperation({ summary: 'Edit the given sale invoice.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -152,7 +141,6 @@ export class SaleInvoicesController {
} }
@Delete(':id') @Delete(':id')
@RequirePermission(SaleInvoiceAction.Delete, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Delete the given sale invoice.' }) @ApiOperation({ summary: 'Delete the given sale invoice.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -170,7 +158,6 @@ export class SaleInvoicesController {
} }
@Get('receivable') @Get('receivable')
@RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Retrieves the receivable sale invoices.' }) @ApiOperation({ summary: 'Retrieves the receivable sale invoices.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -189,7 +176,6 @@ export class SaleInvoicesController {
} }
@Get('state') @Get('state')
@RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Retrieves the sale invoice state.' }) @ApiOperation({ summary: 'Retrieves the sale invoice state.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -204,7 +190,6 @@ export class SaleInvoicesController {
} }
@Get(':id') @Get(':id')
@RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Retrieves the sale invoice details.' }) @ApiOperation({ summary: 'Retrieves the sale invoice details.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -243,7 +228,6 @@ export class SaleInvoicesController {
} }
@Get() @Get()
@RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Retrieves the sale invoices.' }) @ApiOperation({ summary: 'Retrieves the sale invoices.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -267,7 +251,6 @@ export class SaleInvoicesController {
} }
@Put(':id/deliver') @Put(':id/deliver')
@RequirePermission(SaleInvoiceAction.Edit, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Deliver the given sale invoice.' }) @ApiOperation({ summary: 'Deliver the given sale invoice.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -286,7 +269,6 @@ export class SaleInvoicesController {
} }
@Post(':id/writeoff') @Post(':id/writeoff')
@RequirePermission(SaleInvoiceAction.Writeoff, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Write off the given sale invoice.' }) @ApiOperation({ summary: 'Write off the given sale invoice.' })
@HttpCode(200) @HttpCode(200)
@ApiResponse({ @ApiResponse({
@@ -308,7 +290,6 @@ export class SaleInvoicesController {
} }
@Post(':id/cancel-writeoff') @Post(':id/cancel-writeoff')
@RequirePermission(SaleInvoiceAction.Writeoff, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Cancel the written off sale invoice.' }) @ApiOperation({ summary: 'Cancel the written off sale invoice.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -328,7 +309,6 @@ export class SaleInvoicesController {
} }
@Get(':id/payments') @Get(':id/payments')
@RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Retrieves the sale invoice payments.' }) @ApiOperation({ summary: 'Retrieves the sale invoice payments.' })
@ApiResponse({ status: 404, description: 'The sale invoice not found.' }) @ApiResponse({ status: 404, description: 'The sale invoice not found.' })
@ApiParam({ @ApiParam({
@@ -342,7 +322,6 @@ export class SaleInvoicesController {
} }
@Get(':id/html') @Get(':id/html')
@RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Retrieves the sale invoice HTML.' }) @ApiOperation({ summary: 'Retrieves the sale invoice HTML.' })
@ApiResponse({ status: 404, description: 'The sale invoice not found.' }) @ApiResponse({ status: 404, description: 'The sale invoice not found.' })
@ApiParam({ @ApiParam({
@@ -356,7 +335,6 @@ export class SaleInvoicesController {
} }
@Get(':id/mail') @Get(':id/mail')
@RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice)
@ApiOperation({ summary: 'Retrieves the sale invoice mail state.' }) @ApiOperation({ summary: 'Retrieves the sale invoice mail state.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -376,7 +354,6 @@ export class SaleInvoicesController {
} }
@Post(':id/generate-link') @Post(':id/generate-link')
@RequirePermission(SaleInvoiceAction.Edit, AbilitySubject.SaleInvoice)
@ApiOperation({ @ApiOperation({
summary: 'Generate sharable sale invoice link (private or public)', summary: 'Generate sharable sale invoice link (private or public)',
}) })

View File

@@ -46,8 +46,8 @@ import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { MailNotificationModule } from '../MailNotification/MailNotification.module'; import { MailNotificationModule } from '../MailNotification/MailNotification.module';
import { SendSaleInvoiceMailProcessor } from './processors/SendSaleInvoiceMail.processor'; import { SendSaleInvoiceMailProcessor } from './processors/SendSaleInvoiceMail.processor';
import { BullBoardModule } from '@bull-board/nestjs'; import { BullBoardModule } from '@bull-board/nestjs';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; import { BullAdapter } from '@bull-board/api/bullAdapter';
import { BullModule } from '@nestjs/bullmq'; import { BullModule } from '@nestjs/bull';
import { SendSaleInvoiceQueue } from './constants'; import { SendSaleInvoiceQueue } from './constants';
import { InvoicePaymentIntegrationSubscriber } from './subscribers/InvoicePaymentIntegrationSubscriber'; import { InvoicePaymentIntegrationSubscriber } from './subscribers/InvoicePaymentIntegrationSubscriber';
import { InvoiceChangeStatusOnMailSentSubscriber } from './subscribers/InvoiceChangeStatusOnMailSentSubscriber'; import { InvoiceChangeStatusOnMailSentSubscriber } from './subscribers/InvoiceChangeStatusOnMailSentSubscriber';
@@ -85,7 +85,7 @@ import { ValidateBulkDeleteSaleInvoicesService } from './ValidateBulkDeleteSaleI
BullModule.registerQueue({ name: SendSaleInvoiceQueue }), BullModule.registerQueue({ name: SendSaleInvoiceQueue }),
BullBoardModule.forFeature({ BullBoardModule.forFeature({
name: SendSaleInvoiceQueue, name: SendSaleInvoiceQueue,
adapter: BullMQAdapter, adapter: BullAdapter,
}), }),
], ],
controllers: [SaleInvoicesController], controllers: [SaleInvoicesController],

View File

@@ -1,6 +1,9 @@
// import config from '@/config';
export const SendSaleInvoiceQueue = 'SendSaleInvoiceQueue'; export const SendSaleInvoiceQueue = 'SendSaleInvoiceQueue';
export const SendSaleInvoiceMailJob = 'SendSaleInvoiceMailJob'; export const SendSaleInvoiceMailJob = 'SendSaleInvoiceMailJob';
export const DEFAULT_INVOICE_MAIL_SUBJECT = export const DEFAULT_INVOICE_MAIL_SUBJECT =
'Invoice {Invoice Number} from {Company Name} for {Customer Name}'; 'Invoice {Invoice Number} from {Company Name} for {Customer Name}';
export const DEFAULT_INVOICE_MAIL_CONTENT = `Hi {Customer Name}, export const DEFAULT_INVOICE_MAIL_CONTENT = `Hi {Customer Name},

View File

@@ -1,8 +1,9 @@
import { Processor, WorkerHost } from '@nestjs/bullmq'; import { JOB_REF, Process, Processor } from '@nestjs/bull';
import { Job } from 'bullmq'; import { Job } from 'bull';
import { SendSaleInvoiceMailJob, SendSaleInvoiceQueue } from '../constants'; import { SendSaleInvoiceMailJob, SendSaleInvoiceQueue } from '../constants';
import { SendSaleInvoiceMail } from '../commands/SendSaleInvoiceMail'; import { SendSaleInvoiceMail } from '../commands/SendSaleInvoiceMail';
import { Scope } from '@nestjs/common'; import { Inject, Scope } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { ClsService, UseCls } from 'nestjs-cls'; import { ClsService, UseCls } from 'nestjs-cls';
import { SendSaleInvoiceMailJobPayload } from '../SaleInvoice.types'; import { SendSaleInvoiceMailJobPayload } from '../SaleInvoice.types';
@@ -10,18 +11,20 @@ import { SendSaleInvoiceMailJobPayload } from '../SaleInvoice.types';
name: SendSaleInvoiceQueue, name: SendSaleInvoiceQueue,
scope: Scope.REQUEST, scope: Scope.REQUEST,
}) })
export class SendSaleInvoiceMailProcessor extends WorkerHost { export class SendSaleInvoiceMailProcessor {
constructor( constructor(
private readonly sendSaleInvoiceMail: SendSaleInvoiceMail, private readonly sendSaleInvoiceMail: SendSaleInvoiceMail,
@Inject(REQUEST) private readonly request: Request,
@Inject(JOB_REF)
private readonly jobRef: Job<SendSaleInvoiceMailJobPayload>,
private readonly clsService: ClsService, private readonly clsService: ClsService,
) { ) { }
super();
}
@Process(SendSaleInvoiceMailJob)
@UseCls() @UseCls()
async process(job: Job<SendSaleInvoiceMailJobPayload>) { async handleSendInvoice() {
const { messageOptions, saleInvoiceId, organizationId, userId } = const { messageOptions, saleInvoiceId, organizationId, userId } =
job.data; this.jobRef.data;
this.clsService.set('organizationId', organizationId); this.clsService.set('organizationId', organizationId);
this.clsService.set('userId', userId); this.clsService.set('userId', userId);

View File

@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { BullBoardModule } from '@bull-board/nestjs'; import { BullBoardModule } from '@bull-board/nestjs';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; import { BullAdapter } from '@bull-board/api/bullAdapter';
import { BullModule } from '@nestjs/bullmq'; import { BullModule } from '@nestjs/bull';
import { SaleReceiptApplication } from './SaleReceiptApplication.service'; import { SaleReceiptApplication } from './SaleReceiptApplication.service';
import { CreateSaleReceipt } from './commands/CreateSaleReceipt.service'; import { CreateSaleReceipt } from './commands/CreateSaleReceipt.service';
import { EditSaleReceipt } from './commands/EditSaleReceipt.service'; import { EditSaleReceipt } from './commands/EditSaleReceipt.service';
@@ -66,7 +66,7 @@ import { ValidateBulkDeleteSaleReceiptsService } from './ValidateBulkDeleteSaleR
BullModule.registerQueue({ name: SendSaleReceiptMailQueue }), BullModule.registerQueue({ name: SendSaleReceiptMailQueue }),
BullBoardModule.forFeature({ BullBoardModule.forFeature({
name: SendSaleReceiptMailQueue, name: SendSaleReceiptMailQueue,
adapter: BullMQAdapter, adapter: BullAdapter,
}), }),
], ],
providers: [ providers: [

View File

@@ -1,4 +1,4 @@
import { InjectQueue } from '@nestjs/bullmq'; import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bullmq'; import { Queue } from 'bullmq';
import { import {
DEFAULT_RECEIPT_MAIL_CONTENT, DEFAULT_RECEIPT_MAIL_CONTENT,

View File

@@ -1,26 +1,30 @@
import { Processor, WorkerHost } from '@nestjs/bullmq'; import { Process, Processor } from '@nestjs/bull';
import { Job } from 'bullmq'; import { Job } from 'bull';
import { Scope } from '@nestjs/common'; import { Inject, Scope } from '@nestjs/common';
import { JOB_REF } from '@nestjs/bull';
import { SendSaleReceiptMailQueue, SendSaleReceiptMailJob } from '../constants'; import { SendSaleReceiptMailQueue, SendSaleReceiptMailJob } from '../constants';
import { SaleReceiptMailNotification } from '../commands/SaleReceiptMailNotification'; import { SaleReceiptMailNotification } from '../commands/SaleReceiptMailNotification';
import { SaleReceiptSendMailPayload } from '../types/SaleReceipts.types';
import { ClsService, UseCls } from 'nestjs-cls'; import { ClsService, UseCls } from 'nestjs-cls';
@Processor({ @Processor({
name: SendSaleReceiptMailQueue, name: SendSaleReceiptMailQueue,
scope: Scope.REQUEST, scope: Scope.REQUEST,
}) })
export class SendSaleReceiptMailProcess extends WorkerHost { export class SendSaleReceiptMailProcess {
constructor( constructor(
private readonly saleReceiptMailNotification: SaleReceiptMailNotification, private readonly saleReceiptMailNotification: SaleReceiptMailNotification,
private readonly clsService: ClsService, private readonly clsService: ClsService,
) {
super();
}
@Inject(JOB_REF)
private readonly jobRef: Job<SaleReceiptSendMailPayload>,
) { }
@Process(SendSaleReceiptMailJob)
@UseCls() @UseCls()
async process(job: Job) { async handleSendMailJob() {
const { messageOpts, saleReceiptId, organizationId, userId } = const { messageOpts, saleReceiptId, organizationId, userId } =
job.data; this.jobRef.data;
this.clsService.set('organizationId', organizationId); this.clsService.set('organizationId', organizationId);
this.clsService.set('userId', userId); this.clsService.set('userId', userId);

View File

@@ -1,22 +1,16 @@
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Body, Controller, Get, Put, UseGuards } from '@nestjs/common'; import { Body, Controller, Get, Put } from '@nestjs/common';
import { SettingsApplicationService } from './SettingsApplication.service'; import { SettingsApplicationService } from './SettingsApplication.service';
import { ISettingsDTO, PreferencesAction } from './Settings.types'; import { ISettingsDTO } from './Settings.types';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
@Controller('settings') @Controller('settings')
@ApiTags('Settings') @ApiTags('Settings')
@UseGuards(AuthorizationGuard, PermissionGuard)
export class SettingsController { export class SettingsController {
constructor( constructor(
private readonly settingsApplicationService: SettingsApplicationService, private readonly settingsApplicationService: SettingsApplicationService,
) {} ) {}
@Put() @Put()
@RequirePermission(PreferencesAction.Mutate, AbilitySubject.Preferences)
@ApiOperation({ summary: 'Save the given settings.' }) @ApiOperation({ summary: 'Save the given settings.' })
async saveSettings(@Body() settingsDTO: ISettingsDTO) { async saveSettings(@Body() settingsDTO: ISettingsDTO) {
return this.settingsApplicationService.saveSettings(settingsDTO); return this.settingsApplicationService.saveSettings(settingsDTO);

View File

@@ -1,14 +1,12 @@
import { Controller, Get, HttpCode } from '@nestjs/common'; import { Controller, Get, Post } from '@nestjs/common';
import { PublicRoute } from '@/modules/Auth/guards/jwt.guard';
@Controller('system_db') @Controller('/system_db')
@PublicRoute()
export class SystemDatabaseController { export class SystemDatabaseController {
constructor() {} constructor() {}
@Post()
@Get() @Get()
@HttpCode(200)
ping(){ ping(){
return { status: 'ok' };
} }
} }

View File

@@ -6,7 +6,6 @@ import {
SystemKnexConnectionConfigure, SystemKnexConnectionConfigure,
} from './SystemDB.constants'; } from './SystemDB.constants';
import { knexSnakeCaseMappers } from 'objection'; import { knexSnakeCaseMappers } from 'objection';
import { SystemDatabaseController } from './SystemDB.controller';
const providers = [ const providers = [
{ {
@@ -23,7 +22,6 @@ const providers = [
}, },
migrations: { migrations: {
directory: configService.get('systemDatabase.migrationDir'), directory: configService.get('systemDatabase.migrationDir'),
loadExtensions: ['.js'],
}, },
seeds: { seeds: {
directory: configService.get('systemDatabase.seedsDir'), directory: configService.get('systemDatabase.seedsDir'),
@@ -43,7 +41,6 @@ const providers = [
@Global() @Global()
@Module({ @Module({
controllers: [SystemDatabaseController],
providers: [...providers], providers: [...providers],
exports: [...providers], exports: [...providers],
}) })

View File

@@ -62,11 +62,10 @@ export class TaxRatesApplication {
/** /**
* Retrieves the tax rates list. * Retrieves the tax rates list.
* @returns {Promise<{ data: ITaxRate[] }>} * @returns {Promise<ITaxRate[]>}
*/ */
public async getTaxRates() { public getTaxRates() {
const taxRates = await this.getTaxRatesService.getTaxRates(); return this.getTaxRatesService.getTaxRates();
return { data: taxRates };
} }
/** /**

View File

@@ -6,7 +6,6 @@ import {
Param, Param,
Post, Post,
Put, Put,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { TaxRatesApplication } from './TaxRate.application'; import { TaxRatesApplication } from './TaxRate.application';
import { import {
@@ -19,22 +18,15 @@ import {
import { CreateTaxRateDto, EditTaxRateDto } from './dtos/TaxRate.dto'; import { CreateTaxRateDto, EditTaxRateDto } from './dtos/TaxRate.dto';
import { TaxRateResponseDto } from './dtos/TaxRateResponse.dto'; import { TaxRateResponseDto } from './dtos/TaxRateResponse.dto';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { TaxRateAction } from './TaxRates.types';
@Controller('tax-rates') @Controller('tax-rates')
@ApiTags('Tax Rates') @ApiTags('Tax Rates')
@ApiExtraModels(TaxRateResponseDto) @ApiExtraModels(TaxRateResponseDto)
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class TaxRatesController { export class TaxRatesController {
constructor(private readonly taxRatesApplication: TaxRatesApplication) { } constructor(private readonly taxRatesApplication: TaxRatesApplication) { }
@Post() @Post()
@RequirePermission(TaxRateAction.CREATE, AbilitySubject.TaxRate)
@ApiOperation({ summary: 'Create a new tax rate.' }) @ApiOperation({ summary: 'Create a new tax rate.' })
@ApiResponse({ @ApiResponse({
status: 201, status: 201,
@@ -46,7 +38,6 @@ export class TaxRatesController {
} }
@Put(':id') @Put(':id')
@RequirePermission(TaxRateAction.EDIT, AbilitySubject.TaxRate)
@ApiOperation({ summary: 'Edit the given tax rate.' }) @ApiOperation({ summary: 'Edit the given tax rate.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -63,7 +54,6 @@ export class TaxRatesController {
} }
@Delete(':id') @Delete(':id')
@RequirePermission(TaxRateAction.DELETE, AbilitySubject.TaxRate)
@ApiOperation({ summary: 'Delete the given tax rate.' }) @ApiOperation({ summary: 'Delete the given tax rate.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -77,7 +67,6 @@ export class TaxRatesController {
} }
@Get(':id') @Get(':id')
@RequirePermission(TaxRateAction.VIEW, AbilitySubject.TaxRate)
@ApiOperation({ summary: 'Retrieves the tax rate details.' }) @ApiOperation({ summary: 'Retrieves the tax rate details.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -91,29 +80,22 @@ export class TaxRatesController {
} }
@Get() @Get()
@RequirePermission(TaxRateAction.VIEW, AbilitySubject.TaxRate)
@ApiOperation({ summary: 'Retrieves the tax rates.' }) @ApiOperation({ summary: 'Retrieves the tax rates.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'The tax rates have been successfully retrieved.', description: 'The tax rates have been successfully retrieved.',
schema: { schema: {
type: 'object',
properties: {
data: {
type: 'array', type: 'array',
items: { items: {
$ref: getSchemaPath(TaxRateResponseDto), $ref: getSchemaPath(TaxRateResponseDto),
}, },
}, },
},
},
}) })
public getTaxRates() { public getTaxRates() {
return this.taxRatesApplication.getTaxRates(); return this.taxRatesApplication.getTaxRates();
} }
@Put(':id/activate') @Put(':id/activate')
@RequirePermission(TaxRateAction.EDIT, AbilitySubject.TaxRate)
@ApiOperation({ summary: 'Activate the given tax rate.' }) @ApiOperation({ summary: 'Activate the given tax rate.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -127,7 +109,6 @@ export class TaxRatesController {
} }
@Put(':id/inactivate') @Put(':id/inactivate')
@RequirePermission(TaxRateAction.EDIT, AbilitySubject.TaxRate)
@ApiOperation({ summary: 'Inactivate the given tax rate.' }) @ApiOperation({ summary: 'Inactivate the given tax rate.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -1,4 +1,3 @@
import { ToNumber } from '@/common/decorators/Validators';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer'; import { Transform } from 'class-transformer';
import { import {
@@ -31,7 +30,6 @@ export class CommandTaxRateDto {
*/ */
@IsNumber() @IsNumber()
@IsNotEmpty() @IsNotEmpty()
@ToNumber()
@ApiProperty({ @ApiProperty({
description: 'The rate of the tax rate.', description: 'The rate of the tax rate.',
example: 10, example: 10,

View File

@@ -33,7 +33,6 @@ export const TenancyDatabaseProxyProvider = ClsModule.forFeatureAsync({
}, },
migrations: { migrations: {
directory: configService.get('tenantDatabase.migrationsDir'), directory: configService.get('tenantDatabase.migrationsDir'),
loadExtensions: ['.js'],
}, },
seeds: { seeds: {
directory: configService.get('tenantDatabase.seedsDir'), directory: configService.get('tenantDatabase.seedsDir'),

View File

@@ -22,7 +22,7 @@ export class UsersController {
/** /**
* Edit details of the given user. * Edit details of the given user.
*/ */
@Put(':id') @Post(':id')
@ApiOperation({ summary: 'Edit details of the given user.' }) @ApiOperation({ summary: 'Edit details of the given user.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -1,7 +1,4 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bullmq';
import { BullBoardModule } from '@bull-board/nestjs';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
import { ActivateUserService } from './commands/ActivateUser.service'; import { ActivateUserService } from './commands/ActivateUser.service';
import { DeleteUserService } from './commands/DeleteUser.service'; import { DeleteUserService } from './commands/DeleteUser.service';
import { EditUserService } from './commands/EditUser.service'; import { EditUserService } from './commands/EditUser.service';
@@ -20,26 +17,12 @@ import { GetUsersService } from './queries/GetUsers.service';
import { AcceptInviteUserService } from './commands/AcceptInviteUser.service'; import { AcceptInviteUserService } from './commands/AcceptInviteUser.service';
import { InviteTenantUserService } from './commands/InviteUser.service'; import { InviteTenantUserService } from './commands/InviteUser.service';
import { UsersInviteController } from './UsersInvite.controller'; import { UsersInviteController } from './UsersInvite.controller';
import { UsersInvitePublicController } from './UsersInvitePublic.controller';
import { InjectSystemModel } from '../System/SystemModels/SystemModels.module'; import { InjectSystemModel } from '../System/SystemModels/SystemModels.module';
import { SendInviteUserMailQueue } from './Users.constants';
import InviteSendMainNotificationSubscribe from './subscribers/InviteSendMailNotification.subscriber';
import { SendInviteUserMailProcessor } from './processors/SendInviteUserMail.processor';
import { SendInviteUsersMailMessage } from './commands/SendInviteUsersMailMessage.service';
import { MailModule } from '../Mail/Mail.module';
const models = [InjectSystemModel(UserInvite)]; const models = [InjectSystemModel(UserInvite)];
@Module({ @Module({
imports: [ imports: [TenancyModule],
TenancyModule,
MailModule,
BullModule.registerQueue({ name: SendInviteUserMailQueue }),
BullBoardModule.forFeature({
name: SendInviteUserMailQueue,
adapter: BullMQAdapter,
}),
],
exports: [...models], exports: [...models],
providers: [ providers: [
...models, ...models,
@@ -56,11 +39,8 @@ const models = [InjectSystemModel(UserInvite)];
SyncTenantUserMutateSubscriber, SyncTenantUserMutateSubscriber,
SyncSystemSendInviteSubscriber, SyncSystemSendInviteSubscriber,
SyncTenantAcceptInviteSubscriber, SyncTenantAcceptInviteSubscriber,
InviteSendMainNotificationSubscribe,
SendInviteUserMailProcessor,
SendInviteUsersMailMessage,
UsersApplication UsersApplication
], ],
controllers: [UsersController, UsersInviteController, UsersInvitePublicController], controllers: [UsersController, UsersInviteController],
}) })
export class UsersModule {} export class UsersModule {}

View File

@@ -32,12 +32,10 @@ export interface ITenantUserDeletedPayload {
export interface IUserInvitedEventPayload { export interface IUserInvitedEventPayload {
inviteToken: string; inviteToken: string;
user: ModelObject<TenantUser>; user: ModelObject<TenantUser>;
invitingUser: ModelObject<TenantUser>;
} }
export interface IUserInviteTenantSyncedEventPayload { export interface IUserInviteTenantSyncedEventPayload {
invite: ModelObject<UserInvite>; invite: ModelObject<UserInvite>;
user: ModelObject<TenantUser>; user: ModelObject<TenantUser>;
invitingUser: ModelObject<TenantUser>;
} }
export interface IUserInviteResendEventPayload { export interface IUserInviteResendEventPayload {

View File

@@ -1,13 +1,40 @@
import { Body, Controller, Param, Patch, Post } from '@nestjs/common'; import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { UsersApplication } from './Users.application'; import { UsersApplication } from './Users.application';
import { SendInviteUserDto } from './dtos/InviteUser.dto'; import { InviteUserDto, SendInviteUserDto } from './dtos/InviteUser.dto';
@Controller('invite') @Controller('invite')
@ApiTags('Users') @ApiTags('Users')
export class UsersInviteController { export class UsersInviteController {
constructor(private readonly usersApplication: UsersApplication) {} constructor(private readonly usersApplication: UsersApplication) {}
/**
* Accept a user invitation.
*/
@Post('accept/:token')
@ApiOperation({ summary: 'Accept a user invitation.' })
async acceptInvite(
@Param('token') token: string,
@Body() inviteUserDTO: InviteUserDto,
) {
await this.usersApplication.acceptInvite(token, inviteUserDTO);
return {
message: 'The invitation has been accepted successfully.',
};
}
/**
* Check if an invitation token is valid.
*/
@Get('check/:token')
@ApiOperation({ summary: 'Check if an invitation token is valid.' })
async checkInvite(@Param('token') token: string) {
const inviteDetails = await this.usersApplication.checkInvite(token);
return inviteDetails;
}
/** /**
* Send an invitation to a new user. * Send an invitation to a new user.
*/ */

View File

@@ -1,39 +0,0 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { PublicRoute } from '@/modules/Auth/guards/jwt.guard';
import { UsersApplication } from './Users.application';
import { InviteUserDto } from './dtos/InviteUser.dto';
@Controller('invite')
@ApiTags('Users')
@PublicRoute()
export class UsersInvitePublicController {
constructor(private readonly usersApplication: UsersApplication) {}
/**
* Accept a user invitation.
*/
@Post('accept/:token')
@ApiOperation({ summary: 'Accept a user invitation.' })
async acceptInvite(
@Param('token') token: string,
@Body() inviteUserDTO: InviteUserDto,
) {
await this.usersApplication.acceptInvite(token, inviteUserDTO);
return {
message: 'The invitation has been accepted successfully.',
};
}
/**
* Check if an invitation token is valid.
*/
@Get('check/:token')
@ApiOperation({ summary: 'Check if an invitation token is valid.' })
async checkInvite(@Param('token') token: string) {
const inviteDetails = await this.usersApplication.checkInvite(token);
return inviteDetails;
}
}

View File

@@ -1,7 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import * as moment from 'moment'; import * as moment from 'moment';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { ClsService } from 'nestjs-cls';
import { import {
IAcceptInviteEventPayload, IAcceptInviteEventPayload,
ICheckInviteEventPayload, ICheckInviteEventPayload,
@@ -16,11 +15,6 @@ import { UserInvite } from '../models/InviteUser.model';
import { ModelObject } from 'objection'; import { ModelObject } from 'objection';
import { InviteUserDto } from '../dtos/InviteUser.dto'; import { InviteUserDto } from '../dtos/InviteUser.dto';
interface InviteAcceptResponseDto {
inviteToken: { email: string, token: string, createdAt: Date };
orgName: string
}
@Injectable() @Injectable()
export class AcceptInviteUserService { export class AcceptInviteUserService {
constructor( constructor(
@@ -33,7 +27,6 @@ export class AcceptInviteUserService {
@Inject(UserInvite.name) @Inject(UserInvite.name)
private readonly userInviteModel: typeof UserInvite, private readonly userInviteModel: typeof UserInvite,
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
private readonly cls: ClsService,
) {} ) {}
/** /**
@@ -69,16 +62,6 @@ export class AcceptInviteUserService {
// Clear invite token by the given user id. // Clear invite token by the given user id.
await this.clearInviteTokensByUserId(inviteToken.userId); await this.clearInviteTokensByUserId(inviteToken.userId);
// Retrieve the tenant to get the organizationId for CLS.
const tenant = await this.tenantModel
.query()
.findById(inviteToken.tenantId);
// Set CLS values for tenant context before triggering sync events.
this.cls.set('tenantId', inviteToken.tenantId);
this.cls.set('userId', systemUser.id);
this.cls.set('organizationId', tenant.organizationId);
// Triggers `onUserAcceptInvite` event. // Triggers `onUserAcceptInvite` event.
await this.eventEmitter.emitAsync(events.inviteUser.acceptInvite, { await this.eventEmitter.emitAsync(events.inviteUser.acceptInvite, {
inviteToken, inviteToken,
@@ -94,7 +77,7 @@ export class AcceptInviteUserService {
*/ */
public async checkInvite( public async checkInvite(
token: string, token: string,
): Promise<InviteAcceptResponseDto> { ): Promise<{ inviteToken: ModelObject<UserInvite>; orgName: string }> {
const inviteToken = await this.getInviteTokenOrThrowError(token); const inviteToken = await this.getInviteTokenOrThrowError(token);
// Find the tenant that associated to the given token. // Find the tenant that associated to the given token.
@@ -109,16 +92,7 @@ export class AcceptInviteUserService {
tenant, tenant,
} as ICheckInviteEventPayload); } as ICheckInviteEventPayload);
// Explicitly convert to plain object to ensure all fields are serialized return { inviteToken, orgName: tenant.metadata.name };
const result = {
inviteToken: {
email: inviteToken.email,
token: inviteToken.token,
createdAt: inviteToken.createdAt,
},
orgName: tenant.metadata.name,
};
return result;
} }
/** /**

View File

@@ -43,11 +43,11 @@ export class InactivateUserService {
// Throw serivce error if the user is already inactivated. // Throw serivce error if the user is already inactivated.
this.throwErrorIfUserInactive(tenantUser); this.throwErrorIfUserInactive(tenantUser);
// Marks the tenant user as inactive. // Marks the tenant user as active.
await this.tenantUserModel() await this.tenantUserModel()
.query() .query()
.findById(userId) .findById(userId)
.update({ active: false }); .update({ active: true });
// Triggers `onTenantUserActivated` event. // Triggers `onTenantUserActivated` event.
await this.eventEmitter.emitAsync(events.tenantUser.onInactivated, { await this.eventEmitter.emitAsync(events.tenantUser.onInactivated, {

View File

@@ -15,13 +15,11 @@ import { events } from '@/common/events/events';
import { Role } from '@/modules/Roles/models/Role.model'; import { Role } from '@/modules/Roles/models/Role.model';
import { ModelObject } from 'objection'; import { ModelObject } from 'objection';
import { SendInviteUserDto } from '../dtos/InviteUser.dto'; import { SendInviteUserDto } from '../dtos/InviteUser.dto';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
@Injectable() @Injectable()
export class InviteTenantUserService { export class InviteTenantUserService {
constructor( constructor(
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
private readonly tenancyContext: TenancyContext,
@Inject(TenantUser.name) @Inject(TenantUser.name)
private readonly tenantUserModel: TenantModelProxy<typeof TenantUser>, private readonly tenantUserModel: TenantModelProxy<typeof TenantUser>,
@@ -55,18 +53,10 @@ export class InviteTenantUserService {
active: true, active: true,
invitedAt: new Date(), invitedAt: new Date(),
}); });
// Retrieves the authorized user (inviting user).
const authorizedUser = await this.tenancyContext.getSystemUser();
const invitingUser = await this.tenantUserModel()
.query()
.findOne({ systemUserId: authorizedUser.id });
// Triggers `onUserSendInvite` event. // Triggers `onUserSendInvite` event.
await this.eventEmitter.emitAsync(events.inviteUser.sendInvite, { await this.eventEmitter.emitAsync(events.inviteUser.sendInvite, {
inviteToken, inviteToken,
user, user,
invitingUser,
} as IUserInvitedEventPayload); } as IUserInvitedEventPayload);
return { invitedUser: user }; return { invitedUser: user };

View File

@@ -27,8 +27,8 @@ export class SendInviteUsersMailMessage {
invite: ModelObject<UserInvite>, invite: ModelObject<UserInvite>,
) { ) {
const tenant = await this.tenancyContext.getTenant(true); const tenant = await this.tenancyContext.getTenant(true);
const root = path.join(global.__images_dirname, '/bigcapital.png'); const root = path.join(global.__views_dir, '/images/bigcapital.png');
const baseURL = this.configService.get('app.baseUrl'); const baseURL = this.configService.get('baseURL');
const mail = new Mail() const mail = new Mail()
.setSubject(`${fromUser.firstName} has invited you to join a Bigcapital`) .setSubject(`${fromUser.firstName} has invited you to join a Bigcapital`)

View File

@@ -6,7 +6,6 @@ export class UserInvite extends BaseModel {
userId!: number; userId!: number;
tenantId!: number; tenantId!: number;
email!: string; email!: string;
createdAt!: Date;
/** /**
* Table name. * Table name.
@@ -33,11 +32,4 @@ export class UserInvite extends BaseModel {
}, },
}; };
} }
/**
* Called before inserting a new record.
*/
$beforeInsert() {
this.createdAt = new Date();
}
} }

View File

@@ -1,6 +1,7 @@
import { Processor, WorkerHost } from '@nestjs/bullmq'; import { JOB_REF, Process, Processor } from '@nestjs/bull';
import { Job } from 'bullmq'; import { Job } from 'bull';
import { Scope } from '@nestjs/common'; import { Inject, Scope } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { ClsService, UseCls } from 'nestjs-cls'; import { ClsService, UseCls } from 'nestjs-cls';
import { import {
SendInviteUserMailJob, SendInviteUserMailJob,
@@ -13,17 +14,19 @@ import { SendInviteUsersMailMessage } from '../commands/SendInviteUsersMailMessa
name: SendInviteUserMailQueue, name: SendInviteUserMailQueue,
scope: Scope.REQUEST, scope: Scope.REQUEST,
}) })
export class SendInviteUserMailProcessor extends WorkerHost { export class SendInviteUserMailProcessor {
constructor( constructor(
private readonly sendInviteUsersMailService: SendInviteUsersMailMessage, private readonly sendInviteUsersMailService: SendInviteUsersMailMessage,
@Inject(REQUEST) private readonly request: Request,
@Inject(JOB_REF)
private readonly jobRef: Job<SendInviteUserMailJobPayload>,
private readonly clsService: ClsService, private readonly clsService: ClsService,
) { ) { }
super();
}
@Process(SendInviteUserMailJob)
@UseCls() @UseCls()
async process(job: Job<SendInviteUserMailJobPayload>) { async handleSendInviteMail() {
const { fromUser, invite, organizationId, userId } = job.data; const { fromUser, invite, organizationId, userId } = this.jobRef.data;
this.clsService.set('organizationId', organizationId); this.clsService.set('organizationId', organizationId);
this.clsService.set('userId', userId); this.clsService.set('userId', userId);

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bullmq'; import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bullmq'; import { Queue } from 'bull';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { OnEvent } from '@nestjs/event-emitter'; import { OnEvent } from '@nestjs/event-emitter';
import { import {
@@ -29,7 +29,6 @@ export default class InviteSendMainNotificationSubscribe {
async sendMailNotification({ async sendMailNotification({
invite, invite,
user, user,
invitingUser,
}: IUserInviteTenantSyncedEventPayload) { }: IUserInviteTenantSyncedEventPayload) {
const tenant = await this.tenancyContext.getTenant(); const tenant = await this.tenancyContext.getTenant();
const authedUser = await this.tenancyContext.getSystemUser(); const authedUser = await this.tenancyContext.getSystemUser();
@@ -37,8 +36,8 @@ export default class InviteSendMainNotificationSubscribe {
const organizationId = tenant.organizationId; const organizationId = tenant.organizationId;
const userId = authedUser.id; const userId = authedUser.id;
await this.sendInviteMailQueue.add(SendInviteUserMailJob, { this.sendInviteMailQueue.add(SendInviteUserMailJob, {
fromUser: invitingUser, fromUser: user,
invite, invite,
userId, userId,
organizationId, organizationId,

View File

@@ -33,7 +33,7 @@ export class SyncSystemSendInviteSubscriber {
* @param {IUserInvitedEventPayload} payload - * @param {IUserInvitedEventPayload} payload -
*/ */
@OnEvent(events.inviteUser.sendInvite) @OnEvent(events.inviteUser.sendInvite)
async syncSendInviteSystem({ inviteToken, user, invitingUser }: IUserInvitedEventPayload) { async syncSendInviteSystem({ inviteToken, user }: IUserInvitedEventPayload) {
const authorizedUser = await this.tenancyContext.getSystemUser(); const authorizedUser = await this.tenancyContext.getSystemUser();
const tenantId = authorizedUser.tenantId; const tenantId = authorizedUser.tenantId;
@@ -63,7 +63,6 @@ export class SyncSystemSendInviteSubscriber {
{ {
invite, invite,
user, user,
invitingUser,
} as IUserInviteTenantSyncedEventPayload, } as IUserInviteTenantSyncedEventPayload,
); );
} }

View File

@@ -1,4 +1,4 @@
import { pick } from 'lodash'; import { omit } from 'lodash';
import * as moment from 'moment'; import * as moment from 'moment';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter'; import { OnEvent } from '@nestjs/event-emitter';
@@ -22,12 +22,13 @@ export class SyncTenantAcceptInviteSubscriber {
async syncTenantAcceptInvite({ async syncTenantAcceptInvite({
inviteToken, inviteToken,
user, user,
inviteUserDTO,
}: IAcceptInviteEventPayload) { }: IAcceptInviteEventPayload) {
await this.tenantUserModel() await this.tenantUserModel()
.query() .query()
.where('systemUserId', inviteToken.userId) .where('systemUserId', inviteToken.userId)
.update({ .update({
...pick(user, ['firstName', 'lastName', 'email', 'active']), ...omit(inviteUserDTO, ['password']),
inviteAcceptedAt: moment().format('YYYY-MM-DD'), inviteAcceptedAt: moment().format('YYYY-MM-DD'),
}); });
} }

View File

@@ -7,7 +7,6 @@ import {
Post, Post,
Put, Put,
Query, Query,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { VendorCreditsApplicationService } from './VendorCreditsApplication.service'; import { VendorCreditsApplicationService } from './VendorCreditsApplication.service';
import { IVendorCreditsQueryDTO } from './types/VendorCredit.types'; import { IVendorCreditsQueryDTO } from './types/VendorCredit.types';
@@ -27,24 +26,17 @@ import {
BulkDeleteDto, BulkDeleteDto,
ValidateBulkDeleteResponseDto, ValidateBulkDeleteResponseDto,
} from '@/common/dtos/BulkDelete.dto'; } from '@/common/dtos/BulkDelete.dto';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { VendorCreditAction } from './types/VendorCredit.types';
@Controller('vendor-credits') @Controller('vendor-credits')
@ApiTags('Vendor Credits') @ApiTags('Vendor Credits')
@ApiCommonHeaders() @ApiCommonHeaders()
@ApiExtraModels(ValidateBulkDeleteResponseDto) @ApiExtraModels(ValidateBulkDeleteResponseDto)
@UseGuards(AuthorizationGuard, PermissionGuard)
export class VendorCreditsController { export class VendorCreditsController {
constructor( constructor(
private readonly vendorCreditsApplication: VendorCreditsApplicationService, private readonly vendorCreditsApplication: VendorCreditsApplicationService,
) { } ) { }
@Post('validate-bulk-delete') @Post('validate-bulk-delete')
@RequirePermission(VendorCreditAction.Delete, AbilitySubject.VendorCredit)
@ApiOperation({ @ApiOperation({
summary: summary:
'Validates which vendor credits can be deleted and returns the results.', 'Validates which vendor credits can be deleted and returns the results.',
@@ -66,7 +58,6 @@ export class VendorCreditsController {
} }
@Post('bulk-delete') @Post('bulk-delete')
@RequirePermission(VendorCreditAction.Delete, AbilitySubject.VendorCredit)
@ApiOperation({ summary: 'Deletes multiple vendor credits.' }) @ApiOperation({ summary: 'Deletes multiple vendor credits.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -82,28 +73,24 @@ export class VendorCreditsController {
} }
@Post() @Post()
@RequirePermission(VendorCreditAction.Create, AbilitySubject.VendorCredit)
@ApiOperation({ summary: 'Create a new vendor credit.' }) @ApiOperation({ summary: 'Create a new vendor credit.' })
async createVendorCredit(@Body() dto: CreateVendorCreditDto) { async createVendorCredit(@Body() dto: CreateVendorCreditDto) {
return this.vendorCreditsApplication.createVendorCredit(dto); return this.vendorCreditsApplication.createVendorCredit(dto);
} }
@Put(':id/open') @Put(':id/open')
@RequirePermission(VendorCreditAction.Edit, AbilitySubject.VendorCredit)
@ApiOperation({ summary: 'Open the given vendor credit.' }) @ApiOperation({ summary: 'Open the given vendor credit.' })
async openVendorCredit(@Param('id') vendorCreditId: number) { async openVendorCredit(@Param('id') vendorCreditId: number) {
return this.vendorCreditsApplication.openVendorCredit(vendorCreditId); return this.vendorCreditsApplication.openVendorCredit(vendorCreditId);
} }
@Get() @Get()
@RequirePermission(VendorCreditAction.View, AbilitySubject.VendorCredit)
@ApiOperation({ summary: 'Retrieves the vendor credits.' }) @ApiOperation({ summary: 'Retrieves the vendor credits.' })
async getVendorCredits(@Query() filterDTO: IVendorCreditsQueryDTO) { async getVendorCredits(@Query() filterDTO: IVendorCreditsQueryDTO) {
return this.vendorCreditsApplication.getVendorCredits(filterDTO); return this.vendorCreditsApplication.getVendorCredits(filterDTO);
} }
@Put(':id') @Put(':id')
@RequirePermission(VendorCreditAction.Edit, AbilitySubject.VendorCredit)
@ApiOperation({ summary: 'Edit the given vendor credit.' }) @ApiOperation({ summary: 'Edit the given vendor credit.' })
async editVendorCredit( async editVendorCredit(
@Param('id') vendorCreditId: number, @Param('id') vendorCreditId: number,
@@ -113,14 +100,12 @@ export class VendorCreditsController {
} }
@Delete(':id') @Delete(':id')
@RequirePermission(VendorCreditAction.Delete, AbilitySubject.VendorCredit)
@ApiOperation({ summary: 'Delete the given vendor credit.' }) @ApiOperation({ summary: 'Delete the given vendor credit.' })
async deleteVendorCredit(@Param('id') vendorCreditId: number) { async deleteVendorCredit(@Param('id') vendorCreditId: number) {
return this.vendorCreditsApplication.deleteVendorCredit(vendorCreditId); return this.vendorCreditsApplication.deleteVendorCredit(vendorCreditId);
} }
@Get(':id') @Get(':id')
@RequirePermission(VendorCreditAction.View, AbilitySubject.VendorCredit)
@ApiOperation({ summary: 'Retrieves the vendor credit details.' }) @ApiOperation({ summary: 'Retrieves the vendor credit details.' })
async getVendorCredit(@Param('id') vendorCreditId: number) { async getVendorCredit(@Param('id') vendorCreditId: number) {
return this.vendorCreditsApplication.getVendorCredit(vendorCreditId); return this.vendorCreditsApplication.getVendorCredit(vendorCreditId);

View File

@@ -1,33 +1,16 @@
import { import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
Body,
Controller,
Delete,
Get,
Param,
Post,
UseGuards,
} from '@nestjs/common';
import { VendorCreditApplyBillsApplicationService } from './VendorCreditApplyBillsApplication.service'; import { VendorCreditApplyBillsApplicationService } from './VendorCreditApplyBillsApplication.service';
import { IVendorCreditApplyToInvoicesDTO } from './types/VendorCreditApplyBills.types'; import { IVendorCreditApplyToInvoicesDTO } from './types/VendorCreditApplyBills.types';
import { ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { VendorCreditAction } from '../VendorCredit/types/VendorCredit.types';
@Controller('vendor-credits') @Controller('vendor-credits')
@ApiTags('Vendor Credits Apply Bills') @ApiTags('Vendor Credits Apply Bills')
@ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class VendorCreditApplyBillsController { export class VendorCreditApplyBillsController {
constructor( constructor(
private readonly vendorCreditApplyBillsApplication: VendorCreditApplyBillsApplicationService, private readonly vendorCreditApplyBillsApplication: VendorCreditApplyBillsApplicationService,
) {} ) {}
@Get(':vendorCreditId/bills-to-apply') @Get(':vendorCreditId/bills-to-apply')
@RequirePermission(VendorCreditAction.View, AbilitySubject.VendorCredit)
async getVendorCreditToApplyBills( async getVendorCreditToApplyBills(
@Param('vendorCreditId') vendorCreditId: number, @Param('vendorCreditId') vendorCreditId: number,
) { ) {
@@ -37,7 +20,6 @@ export class VendorCreditApplyBillsController {
} }
@Post(':vendorCreditId/apply-to-bills') @Post(':vendorCreditId/apply-to-bills')
@RequirePermission(VendorCreditAction.Edit, AbilitySubject.VendorCredit)
async applyVendorCreditToBills( async applyVendorCreditToBills(
@Param('vendorCreditId') vendorCreditId: number, @Param('vendorCreditId') vendorCreditId: number,
@Body() applyCreditToBillsDTO: IVendorCreditApplyToInvoicesDTO, @Body() applyCreditToBillsDTO: IVendorCreditApplyToInvoicesDTO,
@@ -49,7 +31,6 @@ export class VendorCreditApplyBillsController {
} }
@Delete('applied-bills/:vendorCreditAppliedBillId') @Delete('applied-bills/:vendorCreditAppliedBillId')
@RequirePermission(VendorCreditAction.Edit, AbilitySubject.VendorCredit)
async deleteAppliedBillToVendorCredit( async deleteAppliedBillToVendorCredit(
@Param('vendorCreditAppliedBillId') vendorCreditAppliedBillId: number, @Param('vendorCreditAppliedBillId') vendorCreditAppliedBillId: number,
) { ) {
@@ -59,7 +40,6 @@ export class VendorCreditApplyBillsController {
} }
@Get(':vendorCreditId/applied-bills') @Get(':vendorCreditId/applied-bills')
@RequirePermission(VendorCreditAction.View, AbilitySubject.VendorCredit)
async getAppliedBillsToVendorCredit( async getAppliedBillsToVendorCredit(
@Param('vendorCreditId') vendorCreditId: number, @Param('vendorCreditId') vendorCreditId: number,
) { ) {

View File

@@ -1,27 +1,11 @@
import { import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
Body,
Controller,
Delete,
Get,
Param,
Post,
UseGuards,
} from '@nestjs/common';
import { VendorCreditsRefundApplication } from './VendorCreditsRefund.application'; import { VendorCreditsRefundApplication } from './VendorCreditsRefund.application';
import { RefundVendorCredit } from './models/RefundVendorCredit'; import { RefundVendorCredit } from './models/RefundVendorCredit';
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { RefundVendorCreditDto } from './dtos/RefundVendorCredit.dto'; import { RefundVendorCreditDto } from './dtos/RefundVendorCredit.dto';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { VendorCreditAction } from '../VendorCredit/types/VendorCredit.types';
@Controller('vendor-credits') @Controller('vendor-credits')
@ApiTags('Vendor Credits Refunds') @ApiTags('Vendor Credits Refunds')
@ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class VendorCreditsRefundController { export class VendorCreditsRefundController {
constructor( constructor(
private readonly vendorCreditsRefundApplication: VendorCreditsRefundApplication, private readonly vendorCreditsRefundApplication: VendorCreditsRefundApplication,
@@ -33,7 +17,6 @@ export class VendorCreditsRefundController {
* @returns {Promise<IRefundVendorCreditPOJO[]>} * @returns {Promise<IRefundVendorCreditPOJO[]>}
*/ */
@Get(':vendorCreditId/refund') @Get(':vendorCreditId/refund')
@RequirePermission(VendorCreditAction.View, AbilitySubject.VendorCredit)
@ApiOperation({ summary: 'Retrieve the vendor credit refunds graph.' }) @ApiOperation({ summary: 'Retrieve the vendor credit refunds graph.' })
public getVendorCreditRefunds( public getVendorCreditRefunds(
@Param('vendorCreditId') vendorCreditId: string, @Param('vendorCreditId') vendorCreditId: string,
@@ -50,7 +33,6 @@ export class VendorCreditsRefundController {
* @returns {Promise<RefundVendorCredit>} * @returns {Promise<RefundVendorCredit>}
*/ */
@Post(':vendorCreditId/refund') @Post(':vendorCreditId/refund')
@RequirePermission(VendorCreditAction.Refund, AbilitySubject.VendorCredit)
@ApiOperation({ summary: 'Create a refund for the given vendor credit.' }) @ApiOperation({ summary: 'Create a refund for the given vendor credit.' })
public async createRefundVendorCredit( public async createRefundVendorCredit(
@Param('vendorCreditId') vendorCreditId: string, @Param('vendorCreditId') vendorCreditId: string,
@@ -68,7 +50,6 @@ export class VendorCreditsRefundController {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
@Delete('refunds/:refundCreditId') @Delete('refunds/:refundCreditId')
@RequirePermission(VendorCreditAction.Refund, AbilitySubject.VendorCredit)
@ApiOperation({ summary: 'Delete a refund for the given vendor credit.' }) @ApiOperation({ summary: 'Delete a refund for the given vendor credit.' })
public async deleteRefundVendorCredit( public async deleteRefundVendorCredit(
@Param('refundCreditId') refundCreditId: string, @Param('refundCreditId') refundCreditId: string,

View File

@@ -7,7 +7,6 @@ import {
Post, Post,
Put, Put,
Query, Query,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { VendorsApplication } from './VendorsApplication.service'; import { VendorsApplication } from './VendorsApplication.service';
import { VendorOpeningBalanceEditDto } from './dtos/VendorOpeningBalanceEdit.dto'; import { VendorOpeningBalanceEditDto } from './dtos/VendorOpeningBalanceEdit.dto';
@@ -25,56 +24,44 @@ import {
BulkDeleteVendorsDto, BulkDeleteVendorsDto,
ValidateBulkDeleteVendorsResponseDto, ValidateBulkDeleteVendorsResponseDto,
} from './dtos/BulkDeleteVendors.dto'; } from './dtos/BulkDeleteVendors.dto';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { VendorAction } from '../Customers/types/Customers.types';
@Controller('vendors') @Controller('vendors')
@ApiTags('Vendors') @ApiTags('Vendors')
@ApiCommonHeaders() @ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class VendorsController { export class VendorsController {
constructor(private vendorsApplication: VendorsApplication) {} constructor(private vendorsApplication: VendorsApplication) {}
@Get() @Get()
@RequirePermission(VendorAction.View, AbilitySubject.Vendor)
@ApiOperation({ summary: 'Retrieves the vendors.' }) @ApiOperation({ summary: 'Retrieves the vendors.' })
getVendors(@Query() filterDTO: GetVendorsQueryDto) { getVendors(@Query() filterDTO: GetVendorsQueryDto) {
return this.vendorsApplication.getVendors(filterDTO); return this.vendorsApplication.getVendors(filterDTO);
} }
@Get(':id') @Get(':id')
@RequirePermission(VendorAction.View, AbilitySubject.Vendor)
@ApiOperation({ summary: 'Retrieves the vendor details.' }) @ApiOperation({ summary: 'Retrieves the vendor details.' })
getVendor(@Param('id') vendorId: number) { getVendor(@Param('id') vendorId: number) {
return this.vendorsApplication.getVendor(vendorId); return this.vendorsApplication.getVendor(vendorId);
} }
@Post() @Post()
@RequirePermission(VendorAction.Create, AbilitySubject.Vendor)
@ApiOperation({ summary: 'Create a new vendor.' }) @ApiOperation({ summary: 'Create a new vendor.' })
createVendor(@Body() vendorDTO: CreateVendorDto) { createVendor(@Body() vendorDTO: CreateVendorDto) {
return this.vendorsApplication.createVendor(vendorDTO); return this.vendorsApplication.createVendor(vendorDTO);
} }
@Put(':id') @Put(':id')
@RequirePermission(VendorAction.Edit, AbilitySubject.Vendor)
@ApiOperation({ summary: 'Edit the given vendor.' }) @ApiOperation({ summary: 'Edit the given vendor.' })
editVendor(@Param('id') vendorId: number, @Body() vendorDTO: EditVendorDto) { editVendor(@Param('id') vendorId: number, @Body() vendorDTO: EditVendorDto) {
return this.vendorsApplication.editVendor(vendorId, vendorDTO); return this.vendorsApplication.editVendor(vendorId, vendorDTO);
} }
@Delete(':id') @Delete(':id')
@RequirePermission(VendorAction.Delete, AbilitySubject.Vendor)
@ApiOperation({ summary: 'Delete the given vendor.' }) @ApiOperation({ summary: 'Delete the given vendor.' })
deleteVendor(@Param('id') vendorId: number) { deleteVendor(@Param('id') vendorId: number) {
return this.vendorsApplication.deleteVendor(vendorId); return this.vendorsApplication.deleteVendor(vendorId);
} }
@Put(':id/opening-balance') @Put(':id/opening-balance')
@RequirePermission(VendorAction.Edit, AbilitySubject.Vendor)
@ApiOperation({ summary: 'Edit the given vendor opening balance.' }) @ApiOperation({ summary: 'Edit the given vendor opening balance.' })
editOpeningBalance( editOpeningBalance(
@Param('id') vendorId: number, @Param('id') vendorId: number,
@@ -87,7 +74,6 @@ export class VendorsController {
} }
@Post('validate-bulk-delete') @Post('validate-bulk-delete')
@RequirePermission(VendorAction.Delete, AbilitySubject.Vendor)
@ApiOperation({ @ApiOperation({
summary: summary:
'Validates which vendors can be deleted and returns counts of deletable and non-deletable vendors.', 'Validates which vendors can be deleted and returns counts of deletable and non-deletable vendors.',
@@ -107,7 +93,6 @@ export class VendorsController {
} }
@Post('bulk-delete') @Post('bulk-delete')
@RequirePermission(VendorAction.Delete, AbilitySubject.Vendor)
@ApiOperation({ summary: 'Deletes multiple vendors in bulk.' }) @ApiOperation({ summary: 'Deletes multiple vendors in bulk.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -4,12 +4,7 @@ import styled from 'styled-components';
import { DataTable } from '../Datatable'; import { DataTable } from '../Datatable';
export const ReportDataTable = styled(DataTable)` export const ReportDataTable = styled(DataTable)`
--x-table-no-results-border-color: #ddd;
.bp4-dark & {
--x-table-no-results-border-color: var(--color-dark-gray5);
}
.table .tbody .tr.no-results:last-of-type .td { .table .tbody .tr.no-results:last-of-type .td {
border-bottom: 1px solid var(--x-table-no-results-border-color); border-bottom: 1px solid #ddd;
} }
`; `;

View File

@@ -3,7 +3,7 @@ import React from 'react';
import { Menu, MenuItem, MenuDivider } from '@blueprintjs/core'; import { Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import { useHistory, useLocation } from 'react-router-dom'; import { useHistory, useLocation } from 'react-router-dom';
import { FormattedMessage as T } from '@/components'; import { FormattedMessage as T } from '@/components';
import { PreferencesMenu } from '@/constants/preferencesMenu'; import preferencesMenu from '@/constants/preferencesMenu';
import PreferencesSidebarContainer from './PreferencesSidebarContainer'; import PreferencesSidebarContainer from './PreferencesSidebarContainer';
import '@/style/pages/Preferences/Sidebar.scss'; import '@/style/pages/Preferences/Sidebar.scss';
@@ -15,7 +15,7 @@ export default function PreferencesSidebar() {
const history = useHistory(); const history = useHistory();
const location = useLocation(); const location = useLocation();
const items = PreferencesMenu.map((item) => const items = preferencesMenu.map((item) =>
item.divider ? ( item.divider ? (
<MenuDivider title={item.title} /> <MenuDivider title={item.title} />
) : ( ) : (

View File

@@ -10,17 +10,12 @@ const TextStatusRoot = styled.span`
${(props) => ${(props) =>
props.intent === 'warning' && props.intent === 'warning' &&
` `
color: #c87619;`} color: #ec5b0a;`}
${(props) =>
props.intent === 'danger' &&
`
color: #f17377;`}
${(props) => ${(props) =>
props.intent === 'success' && props.intent === 'success' &&
` `
color: #238551;`} color: #2ba01d;`}
${(props) => ${(props) =>
props.intent === 'none' && props.intent === 'none' &&

View File

@@ -1,5 +1,7 @@
import React, { KeyboardEvent, ReactNode } from 'react'; // @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import classNames from 'classnames';
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import { import {
Overlay, Overlay,
@@ -8,14 +10,11 @@ import {
MenuItem, MenuItem,
Spinner, Spinner,
Intent, Intent,
OverlayProps,
Button,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { QueryList, ItemRenderer } from '@blueprintjs/select'; import { QueryList } from '@blueprintjs/select';
import { x } from '@xstyled/emotion'; import { CLASSES } from '@/constants/classes';
import { css } from '@emotion/css';
import { Icon, If, FormattedMessage as T } from '@/components'; import { Icon, If, ListSelect, FormattedMessage as T } from '@/components';
import { Select } from '@blueprintjs-formik/select';
import { import {
UniversalSearchProvider, UniversalSearchProvider,
useUniversalSearchContext, useUniversalSearchContext,
@@ -23,297 +22,59 @@ import {
import { filterItemsByResourceType } from './utils'; import { filterItemsByResourceType } from './utils';
import { RESOURCES_TYPES } from '@/constants/resourcesTypes'; import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
// Resource type from RESOURCES_TYPES constant
type ResourceType = string;
// Search type option item
interface SearchTypeOption {
key: ResourceType;
label: string;
}
// Universal search item
interface UniversalSearchItem {
id: number | string;
_type: ResourceType;
text: string;
subText?: string;
label?: string;
[key: string]: any;
}
// CSS styles for complex selectors
const overlayStyles = css`
.bp4-overlay-appear,
.bp4-overlay-enter {
filter: blur(20px);
opacity: 0.2;
}
.bp4-overlay-appear-active,
.bp4-overlay-enter-active {
filter: blur(0);
opacity: 1;
transition:
filter 0.2s cubic-bezier(0.4, 1, 0.75, 0.9),
opacity 0.2s cubic-bezier(0.4, 1, 0.75, 0.9);
}
.bp4-overlay-exit {
filter: blur(0);
opacity: 1;
}
.bp4-overlay-exit-active {
filter: blur(20px);
opacity: 0.2;
transition:
filter 0.2s cubic-bezier(0.4, 1, 0.75, 0.9),
opacity 0.2s cubic-bezier(0.4, 1, 0.75, 0.9);
}
`;
const containerStyles = css`
position: fixed;
filter: blur(0);
opacity: 1;
background-color: var(--color-universal-search-background);
border-radius: 3px;
box-shadow:
0 0 0 1px rgba(16, 22, 26, 0.1),
0 4px 8px rgba(16, 22, 26, 0.2),
0 18px 46px 6px rgba(16, 22, 26, 0.2);
left: calc(50% - 250px);
top: 20vh;
width: 500px;
z-index: 20;
.bp4-input-group {
.bp4-icon {
margin: 16px;
color: var(--color-universal-search-icon);
svg {
stroke: currentColor;
fill: none;
fill-rule: evenodd;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2;
--text-opacity: 1;
}
}
}
.bp4-input-group .bp4-input {
border: 0;
box-shadow: 0 0 0 0;
height: 50px;
line-height: 50px;
font-size: 20px;
}
.bp4-input-group.bp4-large .bp4-input:not(:first-child) {
padding-left: 50px !important;
}
.bp4-input-group.bp4-large .bp4-input:not(:last-child) {
padding-right: 130px !important;
}
.bp4-menu {
border-top: 1px solid var(--color-universal-search-menu-border);
max-height: calc(60vh - 20px);
overflow: auto;
.bp4-menu-item {
.bp4-text-muted {
font-size: 12px;
.bp4-icon {
color: var(--bp4-gray-600);
}
}
&.bp4-intent-primary {
&.bp4-active {
background-color: var(--bp4-blue-100);
color: var(--bp4-dark-gray-800);
.bp4-menu-item-label {
color: var(--bp4-gray-600);
}
}
}
&-label {
flex-direction: row;
text-align: right;
}
}
}
.bp4-input-action {
height: 100%;
display: flex;
flex-direction: row;
align-items: center;
}
`;
const inputRightElementsStyles = css`
display: flex;
margin: 10px;
.bp4-spinner {
margin-right: 6px;
}
`;
const footerStyles = css`
padding: 12px 12px;
border-top: 1px solid var(--color-universal-search-footer-divider);
`;
const actionBaseStyles = css`
&:not(:first-of-type) {
margin-left: 14px;
}
.bp4-tag {
background: var(--color-universal-search-tag-background);
color: var(--color-universal-search-tag-text);
}
`;
const actionArrowsStyles = css`
&:not(:first-of-type) {
margin-left: 14px;
}
.bp4-tag {
background: var(--color-universal-search-tag-background);
color: var(--color-universal-search-tag-text);
padding: 0;
text-align: center;
line-height: 16px;
margin-left: 4px;
svg {
fill: var(--color-universal-search-tag-text);
height: 100%;
display: block;
width: 100%;
padding: 2px;
}
}
`;
// UniversalSearchInputRightElements props
interface UniversalSearchInputRightElementsProps {
/** Callback when search type changes */
onSearchTypeChange?: (option: SearchTypeOption) => void;
}
/** /**
* Universal search input action. * Universal search input action.
*/ */
function UniversalSearchInputRightElements({ function UniversalSearchInputRightElements({ onSearchTypeChange }) {
onSearchTypeChange, const { isLoading, searchType, defaultSearchResource, searchTypeOptions } =
}: UniversalSearchInputRightElementsProps) {
const { isLoading, searchType, searchTypeOptions } =
useUniversalSearchContext(); useUniversalSearchContext();
// Find the currently selected item object.
const selectedItem = searchTypeOptions.find(
(item) => item.key === searchType,
);
// Handle search type option change. // Handle search type option change.
const handleSearchTypeChange = (option: SearchTypeOption) => { const handleSearchTypeChange = (option) => {
onSearchTypeChange?.(option); onSearchTypeChange && onSearchTypeChange(option);
};
// Item renderer for the select dropdown.
const itemRenderer: ItemRenderer<SearchTypeOption> = (
item,
{ handleClick },
) => {
return <MenuItem text={item.label} key={item.key} onClick={handleClick} />;
}; };
return ( return (
<x.div display="flex" m="10px" className={inputRightElementsStyles}> <div className={CLASSES.UNIVERSAL_SEARCH_INPUT_RIGHT_ELEMENTS}>
<If condition={isLoading}> <If condition={isLoading}>
<Spinner tagName="div" intent={Intent.NONE} size={18} /> <Spinner tagName="div" intent={Intent.NONE} size={18} value={null} />
</If> </If>
<Select<SearchTypeOption> <ListSelect
items={searchTypeOptions} items={searchTypeOptions}
itemRenderer={itemRenderer}
onItemSelect={handleSearchTypeChange} onItemSelect={handleSearchTypeChange}
selectedValue={selectedItem?.key}
valueAccessor={'key'}
labelAccessor={'label'}
filterable={false} filterable={false}
initialSelectedItem={defaultSearchResource}
selectedItem={searchType}
selectedItemProp={'key'}
textProp={'label'}
// defaultText={intl.get('type')}
popoverProps={{ popoverProps={{
minimal: true, minimal: true,
captureDismiss: true, captureDismiss: true,
className: CLASSES.UNIVERSAL_SEARCH_TYPE_SELECT_OVERLAY,
}}
buttonProps={{
minimal: true,
className: CLASSES.UNIVERSAL_SEARCH_TYPE_SELECT_BTN,
}} }}
input={({ activeItem }) => (
<Button minimal={true} text={activeItem?.label} />
)}
/> />
</x.div> </div>
); );
} }
// QueryList renderer props
interface QueryListRendererProps {
/** Current query string */
query: string;
/** Callback when query changes */
handleQueryChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
/** Item list element */
itemList: ReactNode;
/** Class name */
className?: string;
/** Handle key down */
handleKeyDown?: (event: KeyboardEvent<HTMLDivElement>) => void;
/** Handle key up */
handleKeyUp?: (event: KeyboardEvent<HTMLDivElement>) => void;
}
// UniversalSearchQueryList props
interface UniversalSearchQueryListProps {
/** Whether the search is open */
isOpen: boolean;
/** Whether the search is loading */
isLoading: boolean;
/** Callback when search type changes */
onSearchTypeChange?: (option: SearchTypeOption) => void;
/** Current search type */
searchType: ResourceType;
/** Items to display */
items: UniversalSearchItem[];
/** Renderer for items */
itemRenderer?: ItemRenderer<UniversalSearchItem>;
/** Callback when an item is selected */
onItemSelect?: (item: UniversalSearchItem, event?: any) => void;
/** Current query string */
query: string;
/** Callback when query changes */
onQueryChange?: (query: string) => void;
}
/** /**
* Universal search query list. * Universal search query list.
*/ */
function UniversalSearchQueryList({ function UniversalSearchQueryList(props) {
isOpen, const { isOpen, isLoading, onSearchTypeChange, searchType, ...restProps } =
isLoading, props;
onSearchTypeChange,
...restProps
}: UniversalSearchQueryListProps) {
return ( return (
<QueryList<UniversalSearchItem> <QueryList
{...(restProps as any)} {...restProps}
initialContent={null} initialContent={null}
renderer={(listProps: QueryListRendererProps) => ( renderer={(listProps) => (
<UniversalSearchBar <UniversalSearchBar
isOpen={isOpen} isOpen={isOpen}
onSearchTypeChange={onSearchTypeChange} onSearchTypeChange={onSearchTypeChange}
@@ -339,53 +100,47 @@ function UniversalSearchQueryList({
*/ */
function UniversalQuerySearchActions() { function UniversalQuerySearchActions() {
return ( return (
<x.div display="flex"> <div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTIONS)}>
<x.div className={actionBaseStyles}> <div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_SELECT)}>
<Tag>ENTER</Tag> <Tag>ENTER</Tag>
<x.span ml="6px">{intl.get('universal_search.enter_text')}</x.span> <span class={'text'}>{intl.get('universal_search.enter_text')}</span>
</x.div> </div>
<x.div className={actionBaseStyles}> <div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_CLOSE)}>
<Tag>ESC</Tag>{' '} <Tag>ESC</Tag>{' '}
<x.span ml="6px">{intl.get('universal_search.close_text')}</x.span> <span class={'text'}>{intl.get('universal_search.close_text')}</span>
</x.div> </div>
<x.div className={actionArrowsStyles}> <div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_ARROWS)}>
<Tag> <Tag>
<Icon icon={'arrow-up-24'} iconSize={16} /> <Icon icon={'arrow-up-24'} iconSize={16} />
</Tag> </Tag>
<Tag> <Tag>
<Icon icon={'arrow-down-24'} iconSize={16} /> <Icon icon={'arrow-down-24'} iconSize={16} />
</Tag> </Tag>
<x.span ml="6px">{intl.get('universal_seach.navigate_text')}</x.span> <span class="text">{intl.get('universal_seach.navigate_text')}</span>
</x.div> </div>
</x.div> </div>
); );
} }
// UniversalSearchBar props
interface UniversalSearchBarProps extends QueryListRendererProps {
/** Whether the search is open */
isOpen: boolean;
/** Callback when search type changes */
onSearchTypeChange?: (option: SearchTypeOption) => void;
}
/** /**
* Universal search input bar with items list. * Universal search input bar with items list.
*/ */
function UniversalSearchBar({ function UniversalSearchBar({ isOpen, onSearchTypeChange, ...listProps }) {
isOpen,
onSearchTypeChange,
...listProps
}: UniversalSearchBarProps) {
const { handleKeyDown, handleKeyUp } = listProps; const { handleKeyDown, handleKeyUp } = listProps;
const handlers = isOpen const handlers = isOpen
? { onKeyDown: handleKeyDown, onKeyUp: handleKeyUp } ? { onKeyDown: handleKeyDown, onKeyUp: handleKeyUp }
: {}; : {};
return ( return (
<x.div {...handlers}> <div
className={classNames(
CLASSES.UNIVERSAL_SEARCH_OMNIBAR,
listProps.className,
)}
{...handlers}
>
<InputGroup <InputGroup
large={true} large={true}
leftIcon={<Icon icon={'universal-search'} iconSize={20} />} leftIcon={<Icon icon={'universal-search'} iconSize={20} />}
@@ -400,44 +155,17 @@ function UniversalSearchBar({
autoFocus={true} autoFocus={true}
/> />
{listProps.itemList} {listProps.itemList}
</x.div> </div>
); );
} }
// UniversalSearch props
export interface UniversalSearchProps {
/** Default search resource type */
defaultSearchResource?: ResourceType;
/** Controlled search resource type */
searchResource?: ResourceType;
/** Overlay props */
overlayProps?: OverlayProps;
/** Whether the search overlay is open */
isOpen: boolean;
/** Whether the search is loading */
isLoading: boolean;
/** Callback when search type changes */
onSearchTypeChange?: (resource: SearchTypeOption) => void;
/** Items to display */
items: UniversalSearchItem[];
/** Available search type options */
searchTypeOptions: SearchTypeOption[];
/** Renderer for items */
itemRenderer?: ItemRenderer<UniversalSearchItem>;
/** Callback when an item is selected */
onItemSelect?: (item: UniversalSearchItem, event?: any) => void;
/** Current query string */
query: string;
/** Callback when query changes */
onQueryChange?: (query: string) => void;
}
/** /**
* Universal search. * Universal search.
*/ */
export function UniversalSearch({ export function UniversalSearch({
defaultSearchResource, defaultSearchResource,
searchResource, searchResource,
overlayProps, overlayProps,
isOpen, isOpen,
isLoading, isLoading,
@@ -445,9 +173,9 @@ export function UniversalSearch({
items, items,
searchTypeOptions, searchTypeOptions,
...queryListProps ...queryListProps
}: UniversalSearchProps) { }) {
// Search type state. // Search type state.
const [searchType, setSearchType] = React.useState<ResourceType>( const [searchType, setSearchType] = React.useState(
defaultSearchResource || RESOURCES_TYPES.CUSTOMER, defaultSearchResource || RESOURCES_TYPES.CUSTOMER,
); );
// Handle search resource type controlled mode. // Handle search resource type controlled mode.
@@ -461,9 +189,9 @@ export function UniversalSearch({
}, [searchResource, defaultSearchResource]); }, [searchResource, defaultSearchResource]);
// Handle search type change. // Handle search type change.
const handleSearchTypeChange = (searchTypeResource: SearchTypeOption) => { const handleSearchTypeChange = (searchTypeResource) => {
setSearchType(searchTypeResource.key); setSearchType(searchTypeResource.key);
onSearchTypeChange?.(searchTypeResource); onSearchTypeChange && onSearchTypeChange(searchTypeResource);
}; };
// Filters query list items based on the given search type. // Filters query list items based on the given search type.
const filteredItems = filterItemsByResourceType(items, searchType); const filteredItems = filterItemsByResourceType(items, searchType);
@@ -472,7 +200,7 @@ export function UniversalSearch({
<Overlay <Overlay
hasBackdrop={true} hasBackdrop={true}
isOpen={isOpen} isOpen={isOpen}
className={overlayStyles} className={classNames(CLASSES.UNIVERSAL_SEARCH_OVERLAY)}
{...overlayProps} {...overlayProps}
> >
<UniversalSearchProvider <UniversalSearchProvider
@@ -481,7 +209,7 @@ export function UniversalSearch({
defaultSearchResource={defaultSearchResource} defaultSearchResource={defaultSearchResource}
searchTypeOptions={searchTypeOptions} searchTypeOptions={searchTypeOptions}
> >
<x.div className={containerStyles}> <div className={classNames(CLASSES.UNIVERSAL_SEARCH)}>
<UniversalSearchQueryList <UniversalSearchQueryList
isOpen={isOpen} isOpen={isOpen}
isLoading={isLoading} isLoading={isLoading}
@@ -490,10 +218,10 @@ export function UniversalSearch({
{...queryListProps} {...queryListProps}
items={filteredItems} items={filteredItems}
/> />
<x.div className={footerStyles}> <div className={classNames(CLASSES.UNIVERSAL_SEARCH_FOOTER)}>
<UniversalQuerySearchActions /> <UniversalQuerySearchActions />
</x.div> </div>
</x.div> </div>
</UniversalSearchProvider> </UniversalSearchProvider>
</Overlay> </Overlay>
); );

Some files were not shown because too many files have changed in this diff Show More