Compare commits

..

1 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
f395cce710 fix(script): setup script 2026-01-29 20:49:54 +02:00
178 changed files with 742 additions and 1903 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,10 +9,5 @@
"net_cash_financing": "Net cash provided by financing activities", "net_cash_financing": "Net cash provided by financing activities",
"cash_beginning_period": "Cash at beginning of period", "cash_beginning_period": "Cash at beginning of period",
"net_cash_increase": "NET CASH INCREASE FOR PERIOD", "net_cash_increase": "NET CASH INCREASE FOR PERIOD",
"cash_end_period": "CASH AT END OF PERIOD", "cash_end_period": "CASH AT END OF PERIOD"
"account_name": "Account name",
"total": "Total",
"sheet_name": "Statement of Cash Flow",
"from_date": "From",
"to_date": "To"
} }

View File

@@ -1,5 +0,0 @@
{
"account_name": "Account name",
"total": "Total",
"percentage_column": "% of Column"
}

View File

@@ -1,14 +0,0 @@
{
"opening_balance": "Opening balance",
"closing_balance": "Closing balance",
"date": "Date",
"transaction_type": "Transaction type",
"transaction_number": "Transaction #",
"quantity": "Quantity",
"rate": "Rate",
"total": "Total",
"value": "Value",
"profit_margin": "Profit Margin",
"running_quantity": "Running quantity",
"running_value": "Running Value"
}

View File

@@ -1,4 +0,0 @@
{
"opening_balance": "Opening balance",
"closing_balance": "Closing balance"
}

View File

@@ -1,6 +0,0 @@
{
"account": "Account",
"debit": "Debit",
"credit": "Credit",
"total": "Total"
}

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

@@ -65,7 +65,7 @@ export class AuthController {
return this.authApp.signUp(signupDto); return this.authApp.signUp(signupDto);
} }
@Post('/signup/verify') @Post('/signup/confirm')
@ApiOperation({ summary: 'Confirm user signup' }) @ApiOperation({ summary: 'Confirm user signup' })
@ApiBody({ @ApiBody({
schema: { schema: {

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

@@ -7,13 +7,17 @@ import {
import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service'; import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service';
import { Controller, Get, Post } from '@nestjs/common'; import { Controller, Get, Post } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler'; import { Throttle } from '@nestjs/throttler';
import { TenantAgnosticRoute } from '../Tenancy/TenancyGlobal.guard'; import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards';
import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard';
import { AuthenticationApplication } from './AuthApplication.sevice'; import { AuthenticationApplication } from './AuthApplication.sevice';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { IgnoreUserVerifiedRoute } from './guards/EnsureUserVerified.guard'; import { IgnoreUserVerifiedRoute } from './guards/EnsureUserVerified.guard';
@Controller('/auth') @Controller('/auth')
@ApiTags('Auth') @ApiTags('Auth')
@TenantAgnosticRoute() @ApiExcludeController()
@IgnoreTenantSeededRoute()
@IgnoreTenantInitializedRoute()
@IgnoreUserVerifiedRoute() @IgnoreUserVerifiedRoute()
@Throttle({ auth: {} }) @Throttle({ auth: {} })
export class AuthedController { export class AuthedController {

View File

@@ -13,6 +13,7 @@ import {
IAuthSignedUpEventPayload, IAuthSignedUpEventPayload,
IAuthSigningUpEventPayload, IAuthSigningUpEventPayload,
} from '../Auth.interfaces'; } from '../Auth.interfaces';
import { defaultTo } from 'ramda';
import { ERRORS } from '../Auth.constants'; import { ERRORS } from '../Auth.constants';
import { hashPassword } from '../Auth.utils'; import { hashPassword } from '../Auth.utils';
import { ClsService } from 'nestjs-cls'; import { ClsService } from 'nestjs-cls';
@@ -50,7 +51,7 @@ export class AuthSignupService {
const signupConfirmation = this.configService.get('signupConfirmation'); const signupConfirmation = this.configService.get('signupConfirmation');
const verifyTokenCrypto = crypto.randomBytes(64).toString('hex'); const verifyTokenCrypto = crypto.randomBytes(64).toString('hex');
const verifiedEnabed = signupConfirmation.enabled ?? false; const verifiedEnabed = defaultTo(signupConfirmation.enabled, false);
const verifyToken = verifiedEnabed ? verifyTokenCrypto : ''; const verifyToken = verifiedEnabed ? verifyTokenCrypto : '';
const verified = !verifiedEnabed; const verified = !verifiedEnabed;

View File

@@ -4,6 +4,7 @@ import { SystemUser } from '@/modules/System/models/SystemUser';
import { ServiceError } from '@/modules/Items/ServiceError'; import { ServiceError } from '@/modules/Items/ServiceError';
import { ERRORS } from '../Auth.constants'; import { ERRORS } from '../Auth.constants';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { ModelObject } from 'objection';
import { ISignUpConfigmResendedEventPayload } from '../Auth.interfaces'; import { ISignUpConfigmResendedEventPayload } from '../Auth.interfaces';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';

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

@@ -239,7 +239,7 @@ export class CashFlowTable {
section: ICashFlowStatementSection, section: ICashFlowStatementSection,
): ICashFlowStatementSection => { ): ICashFlowStatementSection => {
const label = section.footerLabel const label = section.footerLabel
? this.i18n.t(section.footerLabel) ? section.footerLabel
: this.i18n.t('financial_sheet.total_row', { : this.i18n.t('financial_sheet.total_row', {
args: { value: section.label }, args: { value: section.label },
}); });
@@ -302,7 +302,7 @@ export class CashFlowTable {
* @returns {ITableColumn} * @returns {ITableColumn}
*/ */
private totalColumns = (): ITableColumn[] => { private totalColumns = (): ITableColumn[] => {
return [{ key: 'total', label: this.i18n.t('cash_flow_statement.total') }]; return [{ key: 'total', label: this.i18n.t('Total') }];
}; };
/** /**
@@ -366,7 +366,7 @@ export class CashFlowTable {
*/ */
public tableColumns = (): ITableColumn[] => { public tableColumns = (): ITableColumn[] => {
return R.compose( return R.compose(
R.concat([{ key: 'name', label: this.i18n.t('cash_flow_statement.account_name') }]), R.concat([{ key: 'name', label: this.i18n.t('Account name') }]),
R.when( R.when(
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
R.concat(this.datePeriodsColumns()), R.concat(this.datePeriodsColumns()),

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

@@ -1,6 +1,5 @@
import * as moment from 'moment'; import * as moment from 'moment';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { I18nService } from 'nestjs-i18n';
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta'; import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
import { import {
ICashFlowStatementMeta, ICashFlowStatementMeta,
@@ -9,10 +8,7 @@ import {
@Injectable() @Injectable()
export class CashflowSheetMeta { export class CashflowSheetMeta {
constructor( constructor(private readonly financialSheetMeta: FinancialSheetMeta) {}
private readonly financialSheetMeta: FinancialSheetMeta,
private readonly i18n: I18nService,
) {}
/** /**
* Cashflow sheet meta. * Cashflow sheet meta.
@@ -25,11 +21,9 @@ export class CashflowSheetMeta {
const meta = await this.financialSheetMeta.meta(); const meta = await this.financialSheetMeta.meta();
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedToDate = moment(query.toDate).format('YYYY/MM/DD');
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD');
const fromLabel = this.i18n.t('cash_flow_statement.from_date'); const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
const toLabel = this.i18n.t('cash_flow_statement.to_date');
const formattedDateRange = `${fromLabel} ${formattedFromDate} | ${toLabel} ${formattedToDate}`;
const sheetName = this.i18n.t('cash_flow_statement.sheet_name'); const sheetName = 'Statement of Cash Flow';
return { return {
...meta, ...meta,

View File

@@ -91,7 +91,7 @@ export class CustomerBalanceSummaryTable {
*/ */
private getTotalColumnsAccessor = (): IColumnMapperMeta[] => { private getTotalColumnsAccessor = (): IColumnMapperMeta[] => {
const columns = [ const columns = [
{ key: 'name', value: this.i18n.t('contact_summary_balance.total') }, { key: 'name', value: this.i18n.t('Total') },
{ key: 'total', accessor: 'total.formattedAmount' }, { key: 'total', accessor: 'total.formattedAmount' },
]; ];
// @ts-ignore // @ts-ignore

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

@@ -93,7 +93,7 @@ export class InventoryItemDetailsTable {
): ITableRow => { ): ITableRow => {
const columns: Array<IColumnMapperMeta> = [ const columns: Array<IColumnMapperMeta> = [
{ key: 'date', accessor: 'date.formattedDate' }, { key: 'date', accessor: 'date.formattedDate' },
{ key: 'closing', value: this.i18n.t('inventory_item_details.opening_balance') }, { key: 'closing', value: this.i18n.t('Opening balance') },
{ key: 'empty', value: '' }, { key: 'empty', value: '' },
{ key: 'quantity', accessor: 'quantity.formattedNumber' }, { key: 'quantity', accessor: 'quantity.formattedNumber' },
{ key: 'empty', value: '' }, { key: 'empty', value: '' },
@@ -115,7 +115,7 @@ export class InventoryItemDetailsTable {
): ITableRow => { ): ITableRow => {
const columns: Array<IColumnMapperMeta> = [ const columns: Array<IColumnMapperMeta> = [
{ key: 'date', accessor: 'date.formattedDate' }, { key: 'date', accessor: 'date.formattedDate' },
{ key: 'closing', value: this.i18n.t('inventory_item_details.closing_balance') }, { key: 'closing', value: this.i18n.t('Closing balance') },
{ key: 'empty', value: '' }, { key: 'empty', value: '' },
{ key: 'quantity', accessor: 'quantity.formattedNumber' }, { key: 'quantity', accessor: 'quantity.formattedNumber' },
{ key: 'empty', value: '' }, { key: 'empty', value: '' },
@@ -193,16 +193,16 @@ export class InventoryItemDetailsTable {
*/ */
public tableColumns = (): ITableColumn[] => { public tableColumns = (): ITableColumn[] => {
return [ return [
{ key: 'date', label: this.i18n.t('inventory_item_details.date') }, { key: 'date', label: this.i18n.t('Date') },
{ key: 'transaction_type', label: this.i18n.t('inventory_item_details.transaction_type') }, { key: 'transaction_type', label: this.i18n.t('Transaction type') },
{ key: 'transaction_id', label: this.i18n.t('inventory_item_details.transaction_number') }, { key: 'transaction_id', label: this.i18n.t('Transaction #') },
{ key: 'quantity', label: this.i18n.t('inventory_item_details.quantity') }, { key: 'quantity', label: this.i18n.t('Quantity') },
{ key: 'rate', label: this.i18n.t('inventory_item_details.rate') }, { key: 'rate', label: this.i18n.t('Rate') },
{ key: 'total', label: this.i18n.t('inventory_item_details.total') }, { key: 'total', label: this.i18n.t('Total') },
{ key: 'value', label: this.i18n.t('inventory_item_details.value') }, { key: 'value', label: this.i18n.t('Value') },
{ key: 'profit_margin', label: this.i18n.t('inventory_item_details.profit_margin') }, { key: 'profit_margin', label: this.i18n.t('Profit Margin') },
{ key: 'running_quantity', label: this.i18n.t('inventory_item_details.running_quantity') }, { key: 'running_quantity', label: this.i18n.t('Running quantity') },
{ key: 'running_value', label: this.i18n.t('inventory_item_details.running_value') }, { key: 'running_value', label: this.i18n.t('Running Value') },
]; ];
}; };
} }

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

@@ -52,7 +52,7 @@ export class TransactionsByContactsTableRows {
const columns = [ const columns = [
{ {
key: 'openingBalanceLabel', key: 'openingBalanceLabel',
value: this.i18n.t('transactions_by_contact.opening_balance') as string, value: this.i18n.t('Opening balance') as string,
}, },
...R.repeat({ key: 'empty', value: '' }, 5), ...R.repeat({ key: 'empty', value: '' }, 5),
{ {
@@ -76,7 +76,7 @@ export class TransactionsByContactsTableRows {
const columns = [ const columns = [
{ {
key: 'closingBalanceLabel', key: 'closingBalanceLabel',
value: this.i18n.t('transactions_by_contact.closing_balance') as string, value: this.i18n.t('Closing balance') as string,
}, },
...R.repeat({ key: 'empty', value: '' }, 5), ...R.repeat({ key: 'empty', value: '' }, 5),
{ {

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

@@ -141,10 +141,10 @@ export class TrialBalanceSheetTable extends R.compose(
return R.compose( return R.compose(
this.tableColumnsCellIndexing, this.tableColumnsCellIndexing,
R.concat([ R.concat([
{ key: 'account', label: this.i18n.t('trial_balance_sheet.account') }, { key: 'account', label: 'Account' },
{ key: 'debit', label: this.i18n.t('trial_balance_sheet.debit') }, { key: 'debit', label: 'Debit' },
{ key: 'credit', label: this.i18n.t('trial_balance_sheet.credit') }, { key: 'credit', label: 'Credit' },
{ key: 'total', label: this.i18n.t('trial_balance_sheet.total') }, { key: 'total', label: 'Total' },
]), ]),
)([]); )([]);
}; };

View File

@@ -91,7 +91,7 @@ export class VendorBalanceSummaryTable {
*/ */
private getTotalColumnsAccessor = (): IColumnMapperMeta[] => { private getTotalColumnsAccessor = (): IColumnMapperMeta[] => {
const columns = [ const columns = [
{ key: 'name', value: this.i18n.t('contact_summary_balance.total') }, { key: 'name', value: this.i18n.t('Total') },
{ key: 'total', accessor: 'total.formattedAmount' }, { key: 'total', accessor: 'total.formattedAmount' },
]; ];
return R.compose( return R.compose(

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,
@@ -28,7 +27,6 @@ import { UpdateOrganizationService } from './commands/UpdateOrganization.service
import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard'; import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard';
import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards'; import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards';
import { IgnoreTenantModelsInitialize } from '../Tenancy/TenancyInitializeModels.guard'; import { IgnoreTenantModelsInitialize } from '../Tenancy/TenancyInitializeModels.guard';
import { IgnoreUserVerifiedRoute } from '../Auth/guards/EnsureUserVerified.guard';
import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service'; import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service';
import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBaseCurrencyLocking.service'; import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBaseCurrencyLocking.service';
import { import {
@@ -79,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,
@@ -94,7 +91,6 @@ export class OrganizationController {
@Get('current') @Get('current')
@HttpCode(200) @HttpCode(200)
@IgnoreUserVerifiedRoute()
@ApiOperation({ summary: 'Get current organization' }) @ApiOperation({ summary: 'Get current organization' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

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

@@ -8,7 +8,6 @@ import {
import { TenancyContext } from './TenancyContext.service'; import { TenancyContext } from './TenancyContext.service';
import { Reflector } from '@nestjs/core'; import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants'; import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants';
import { IS_TENANT_AGNOSTIC } from './TenancyGlobal.guard';
export const IS_IGNORE_TENANT_INITIALIZED = 'IS_IGNORE_TENANT_INITIALIZED'; export const IS_IGNORE_TENANT_INITIALIZED = 'IS_IGNORE_TENANT_INITIALIZED';
export const IgnoreTenantInitializedRoute = () => export const IgnoreTenantInitializedRoute = () =>
@@ -36,12 +35,8 @@ export class EnsureTenantIsInitializedGuard implements CanActivate {
IS_PUBLIC_ROUTE, IS_PUBLIC_ROUTE,
[context.getHandler(), context.getClass()], [context.getHandler(), context.getClass()],
); );
const isTenantAgnostic = this.reflector.getAllAndOverride<boolean>(
IS_TENANT_AGNOSTIC,
[context.getHandler(), context.getClass()],
);
// Skip the guard early if the route marked as public or ignored. // Skip the guard early if the route marked as public or ignored.
if (isPublic || isIgnoreEnsureTenantInitialized || isTenantAgnostic) { if (isPublic || isIgnoreEnsureTenantInitialized) {
return true; return true;
} }
const tenant = await this.tenancyContext.getTenant(); const tenant = await this.tenancyContext.getTenant();

View File

@@ -9,7 +9,6 @@ import {
import { TenancyContext } from './TenancyContext.service'; import { TenancyContext } from './TenancyContext.service';
import { Reflector } from '@nestjs/core'; import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants'; import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants';
import { IS_TENANT_AGNOSTIC } from './TenancyGlobal.guard';
export const IS_IGNORE_TENANT_SEEDED = 'IS_IGNORE_TENANT_SEEDED'; export const IS_IGNORE_TENANT_SEEDED = 'IS_IGNORE_TENANT_SEEDED';
export const IgnoreTenantSeededRoute = () => export const IgnoreTenantSeededRoute = () =>
@@ -37,12 +36,7 @@ export class EnsureTenantIsSeededGuard implements CanActivate {
context.getHandler(), context.getHandler(),
context.getClass(), context.getClass(),
]); ]);
const isTenantAgnostic = this.reflector.getAllAndOverride<boolean>( if (isPublic || isIgnoreEnsureTenantSeeded) {
IS_TENANT_AGNOSTIC,
[context.getHandler(), context.getClass()],
);
// Skip the guard early if the route marked as public, tenant agnostic or ignored.
if (isPublic || isIgnoreEnsureTenantSeeded || isTenantAgnostic) {
return true; return true;
} }
const tenant = await this.tenancyContext.getTenant(); const tenant = await this.tenancyContext.getTenant();

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.
*/ */

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