Compare commits

..

1 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
985e1dbc01 refactor(nestjs): users module 2025-05-19 19:21:06 +02:00
162 changed files with 1064 additions and 1817 deletions

View File

@@ -1,7 +0,0 @@
import { registerAs } from '@nestjs/config';
export default registerAs('bankfeed', () => ({
enabled:
process.env.BANK_FEED_ENABLED === 'true' ||
process.env.BANK_FEED_ENABLED === 'yes',
}));

View File

@@ -13,7 +13,6 @@ import signupRestrictions from './signup-restrictions';
import jwt from './jwt'; import jwt from './jwt';
import mail from './mail'; import mail from './mail';
import loops from './loops'; import loops from './loops';
import bankfeed from './bankfeed';
export const config = [ export const config = [
systemDatabase, systemDatabase,
@@ -30,6 +29,5 @@ export const config = [
signupRestrictions, signupRestrictions,
jwt, jwt,
mail, mail,
loops, loops
bankfeed,
]; ];

View File

@@ -1,57 +0,0 @@
/**
* Map to store all models that have been marked to prevent base currency mutation.
* Key is the model name, value is the model class.
*/
export const preventMutateBaseCurrencyModels = new Map<string, any>();
/**
* Decorator that marks an ORM model to prevent base currency mutation.
* When applied to a model class, it adds a static property `preventMutateBaseCurrency` set to true
* and registers the model in the preventMutateBaseCurrencyModels map.
*
* @returns {ClassDecorator} A decorator function that can be applied to a class.
*/
export function PreventMutateBaseCurrency(): ClassDecorator {
return (target: any) => {
// Set the static property on the model class
target.preventMutateBaseCurrency = true;
// Register the model in the map
const modelName = target.name;
preventMutateBaseCurrencyModels.set(modelName, target);
// Return the modified class
return target;
};
}
/**
* Get all registered models that prevent base currency mutation.
*
* @returns {Map<string, any>} Map of model names to model classes
*/
export function getPreventMutateBaseCurrencyModels(): Map<string, any> {
return preventMutateBaseCurrencyModels;
}
/**
* Check if a model is registered to prevent base currency mutation.
*
* @param {string} modelName - The name of the model to check
* @returns {boolean} True if the model is registered, false otherwise
*/
export function isModelPreventMutateBaseCurrency(modelName: string): boolean {
return preventMutateBaseCurrencyModels.has(modelName);
}
/**
* Get a specific model by name that prevents base currency mutation.
*
* @param {string} modelName - The name of the model to retrieve
* @returns {any | undefined} The model class if found, undefined otherwise
*/
export function getPreventMutateBaseCurrencyModel(
modelName: string,
): any | undefined {
return preventMutateBaseCurrencyModels.get(modelName);
}

View File

@@ -1,32 +0,0 @@
import { Transform } from 'class-transformer';
import { ValidateIf, ValidationOptions } from 'class-validator';
/**
* Decorator that converts the property value to a number.
* @returns PropertyDecorator
*/
export function ToNumber() {
return Transform(({ value, key }) => {
const defaultValue = null;
if (typeof value === 'number') {
return value;
}
// If value is an empty string or undefined/null, return it as-is (wont pass validation)
if (value === '' || value === null || value === undefined) {
return defaultValue;
}
const parsed = Number(value);
return !isNaN(parsed) ? parsed : value;
});
}
/**
* Validates if the property is not empty.
* @returns PropertyDecorator
*/
export function IsOptional(validationOptions?: ValidationOptions) {
return ValidateIf((_obj, value) => {
return value !== null && value !== undefined && value !== '';
}, validationOptions);
}

View File

@@ -70,10 +70,7 @@ export class SerializeInterceptor implements NestInterceptor<any, any> {
next: CallHandler<any>, next: CallHandler<any>,
): Observable<any> { ): Observable<any> {
const request = context.switchToHttp().getRequest(); const request = context.switchToHttp().getRequest();
// Transform both body and query parameters
request.body = this.strategy.in(request.body); request.body = this.strategy.in(request.body);
request.query = this.strategy.in(request.query);
// handle returns stream.. // handle returns stream..
return next.handle().pipe(map(this.strategy.out)); return next.handle().pipe(map(this.strategy.out));

View File

@@ -14,15 +14,12 @@ export class ValidationPipe implements PipeTransform<any> {
return value; return value;
} }
const object = plainToInstance(metatype, value); const object = plainToInstance(metatype, value);
const errors = await validate(object, { const errors = await validate(object);
// Strip validated object of any properties that do not have any decorators.
whitelist: true,
});
if (errors.length > 0) { if (errors.length > 0) {
throw new BadRequestException(errors); throw new BadRequestException(errors);
} }
return object; return value;
} }
private toValidate(metatype: Function): boolean { private toValidate(metatype: Function): boolean {

View File

@@ -1,4 +0,0 @@
{
"primary_warehouse": "Primary Warehouse"
}

View File

@@ -1,4 +1,4 @@
import * as FormData from 'form-data'; import FormData from 'form-data';
import { GotenbergUtils } from './GotenbergUtils'; import { GotenbergUtils } from './GotenbergUtils';
import { PageProperties } from './_types'; import { PageProperties } from './_types';

View File

@@ -1,5 +1,5 @@
import * as FormData from 'form-data'; import FormData from 'form-data';
import { Axios } from 'axios'; import Axios from 'axios';
export class GotenbergUtils { export class GotenbergUtils {
public static assert(condition: boolean, message: string): asserts condition { public static assert(condition: boolean, message: string): asserts condition {
@@ -10,12 +10,12 @@ export class GotenbergUtils {
public static async fetch(endpoint: string, data: FormData): Promise<Buffer> { public static async fetch(endpoint: string, data: FormData): Promise<Buffer> {
try { try {
const response = await new Axios({ const response = await Axios.post(endpoint, data, {
headers: { headers: {
...data.getHeaders(), ...data.getHeaders(),
}, },
responseType: 'arraybuffer', // This ensures you get a Buffer bac responseType: 'arraybuffer', // This ensures you get a Buffer bac
}).post(endpoint, data); });
return response.data; return response.data;
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View File

@@ -1,5 +1,5 @@
import { constants, createReadStream, PathLike, promises } from 'fs'; import { constants, createReadStream, PathLike, promises } from 'fs';
import * as FormData from 'form-data'; import FormData from 'form-data';
import { GotenbergUtils } from './GotenbergUtils'; import { GotenbergUtils } from './GotenbergUtils';
import { IConverter, PageProperties } from './_types'; import { IConverter, PageProperties } from './_types';
import { PdfFormat, ChromiumRoute } from './_types'; import { PdfFormat, ChromiumRoute } from './_types';

View File

@@ -1,4 +1,4 @@
import * as FormData from 'form-data'; import FormData from 'form-data';
import { IConverter, PageProperties, PdfFormat, ChromiumRoute } from './_types'; import { IConverter, PageProperties, PdfFormat, ChromiumRoute } from './_types';
import { ConverterUtils } from './ConvertUtils'; import { ConverterUtils } from './ConvertUtils';
import { Converter } from './Converter'; import { Converter } from './Converter';

View File

@@ -13,7 +13,9 @@ export class MutateBaseCurrencyAccounts {
* Mutates the all accounts or the organziation. * Mutates the all accounts or the organziation.
* @param {string} currencyCode * @param {string} currencyCode
*/ */
async mutateAllAccountsCurrency(currencyCode: string) { mutateAllAccountsCurrency = async (
await this.accountModel().query().update({ currencyCode }); currencyCode: string,
} ) => {
await Account.query().update({ currencyCode });
};
} }

View File

@@ -88,8 +88,6 @@ import { ViewsModule } from '../Views/Views.module';
import { CurrenciesModule } from '../Currencies/Currencies.module'; import { CurrenciesModule } from '../Currencies/Currencies.module';
import { MiscellaneousModule } from '../Miscellaneous/Miscellaneous.module'; import { MiscellaneousModule } from '../Miscellaneous/Miscellaneous.module';
import { UsersModule } from '../UsersModule/Users.module'; import { UsersModule } from '../UsersModule/Users.module';
import { ContactsModule } from '../Contacts/Contacts.module';
import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module';
@Module({ @Module({
imports: [ imports: [
@@ -187,7 +185,6 @@ import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module';
BankingTransactionsExcludeModule, BankingTransactionsExcludeModule,
BankingTransactionsRegonizeModule, BankingTransactionsRegonizeModule,
BankingMatchingModule, BankingMatchingModule,
BankingPlaidModule,
TransactionsLockingModule, TransactionsLockingModule,
SettingsModule, SettingsModule,
FeaturesModule, FeaturesModule,
@@ -213,8 +210,7 @@ import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module';
ViewsModule, ViewsModule,
CurrenciesModule, CurrenciesModule,
MiscellaneousModule, MiscellaneousModule,
UsersModule, UsersModule
ContactsModule
], ],
controllers: [AppController], controllers: [AppController],
providers: [ providers: [

View File

@@ -33,7 +33,6 @@ const models = [
@Module({ @Module({
imports: [S3Module, ...models], imports: [S3Module, ...models],
exports: [...models],
controllers: [AttachmentsController], controllers: [AttachmentsController],
providers: [ providers: [
DeleteAttachment, DeleteAttachment,

View File

@@ -1,4 +1,4 @@
import * as path from 'path'; import path from 'path';
// import config from '@/config'; // import config from '@/config';
export const getUploadedObjectUri = (objectKey: string) => { export const getUploadedObjectUri = (objectKey: string) => {

View File

@@ -12,7 +12,6 @@ import {
} from 'class-validator'; } from 'class-validator';
import { BankRuleComparator } from '../types'; import { BankRuleComparator } from '../types';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { ToNumber } from '@/common/decorators/Validators';
class BankRuleConditionDto { class BankRuleConditionDto {
@IsNotEmpty() @IsNotEmpty()
@@ -45,8 +44,6 @@ export class CommandBankRuleDto {
}) })
name: string; name: string;
@IsNotEmpty()
@ToNumber()
@IsInt() @IsInt()
@Min(0) @Min(0)
@ApiProperty({ @ApiProperty({
@@ -56,7 +53,6 @@ export class CommandBankRuleDto {
order: number; order: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
@Min(0) @Min(0)
@ApiProperty({ @ApiProperty({
@@ -65,7 +61,6 @@ export class CommandBankRuleDto {
}) })
applyIfAccountId?: number; applyIfAccountId?: number;
@IsNotEmpty()
@IsIn(['deposit', 'withdrawal']) @IsIn(['deposit', 'withdrawal'])
@ApiProperty({ @ApiProperty({
description: 'The transaction type to apply the rule if', description: 'The transaction type to apply the rule if',
@@ -87,14 +82,11 @@ export class CommandBankRuleDto {
@Type(() => BankRuleConditionDto) @Type(() => BankRuleConditionDto)
@ApiProperty({ @ApiProperty({
description: 'The conditions to apply the rule if', description: 'The conditions to apply the rule if',
example: [ example: [{ field: 'description', comparator: 'contains', value: 'Salary' }],
{ field: 'description', comparator: 'contains', value: 'Salary' },
],
}) })
conditions: BankRuleConditionDto[]; conditions: BankRuleConditionDto[];
@IsString() @IsString()
@IsNotEmpty()
@ApiProperty({ @ApiProperty({
description: 'The category to assign the rule if', description: 'The category to assign the rule if',
example: 'Income:Salary', example: 'Income:Salary',
@@ -103,8 +95,6 @@ export class CommandBankRuleDto {
@IsInt() @IsInt()
@Min(0) @Min(0)
@ToNumber()
@IsNotEmpty()
@ApiProperty({ @ApiProperty({
description: 'The account ID to assign the rule if', description: 'The account ID to assign the rule if',
example: 1, example: 1,

View File

@@ -1,13 +1,8 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Account } from '@/modules/Accounts/models/Account.model'; import { Account } from '@/modules/Accounts/models/Account.model';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction'; import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { BaseModel } from '@/models/Model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { Knex } from 'knex';
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
import { initialize } from 'objection';
import { MatchedBankTransaction } from '@/modules/BankingMatching/models/MatchedBankTransaction';
import { TenantModel } from '@/modules/System/models/TenantModel';
import { RecognizedBankTransaction } from '@/modules/BankingTranasctionsRegonize/models/RecognizedBankTransaction';
@Injectable() @Injectable()
export class GetBankAccountSummary { export class GetBankAccountSummary {
@@ -19,19 +14,6 @@ export class GetBankAccountSummary {
private readonly uncategorizedBankTransactionModel: TenantModelProxy< private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction typeof UncategorizedBankTransaction
>, >,
@Inject(MatchedBankTransaction.name)
private readonly matchedBankTransactionModel: TenantModelProxy<
typeof MatchedBankTransaction
>,
@Inject(RecognizedBankTransaction.name)
private readonly recognizedBankTransaction: TenantModelProxy<
typeof RecognizedBankTransaction
>,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantDb: () => Knex,
) {} ) {}
/** /**
@@ -45,11 +27,6 @@ export class GetBankAccountSummary {
.findById(bankAccountId) .findById(bankAccountId)
.throwIfNotFound(); .throwIfNotFound();
await initialize(this.tenantDb(), [
this.uncategorizedBankTransactionModel(),
this.matchedBankTransactionModel(),
this.recognizedBankTransaction(),
]);
const commonQuery = (q) => { const commonQuery = (q) => {
// Include just the given account. // Include just the given account.
q.where('accountId', bankAccountId); q.where('accountId', bankAccountId);
@@ -60,6 +37,11 @@ export class GetBankAccountSummary {
// Only the not categorized. // Only the not categorized.
q.modify('notCategorized'); q.modify('notCategorized');
}; };
interface UncategorizedTransactionsCount {
total: number;
}
// Retrieves the uncategorized transactions count of the given bank account. // Retrieves the uncategorized transactions count of the given bank account.
const uncategorizedTranasctionsCount = const uncategorizedTranasctionsCount =
await this.uncategorizedBankTransactionModel() await this.uncategorizedBankTransactionModel()

View File

@@ -1,22 +0,0 @@
import { Body, Controller, Post } from '@nestjs/common';
import { PlaidApplication } from './PlaidApplication';
import { PlaidItemDto } from './dtos/PlaidItem.dto';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
@Controller('banking/plaid')
@ApiTags('banking-plaid')
export class BankingPlaidController {
constructor(private readonly plaidApplication: PlaidApplication) {}
@Post('link-token')
@ApiOperation({ summary: 'Get Plaid link token' })
getLinkToken() {
return this.plaidApplication.getLinkToken();
}
@Post('exchange-token')
@ApiOperation({ summary: 'Exchange Plaid access token' })
exchangeToken(@Body() itemDTO: PlaidItemDto) {
return this.plaidApplication.exchangeToken(itemDTO);
}
}

View File

@@ -15,7 +15,6 @@ import { PlaidItemService } from './command/PlaidItem';
import { TenancyContext } from '../Tenancy/TenancyContext.service'; import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { InjectSystemModel } from '../System/SystemModels/SystemModels.module'; import { InjectSystemModel } from '../System/SystemModels/SystemModels.module';
import { SystemPlaidItem } from './models/SystemPlaidItem'; import { SystemPlaidItem } from './models/SystemPlaidItem';
import { BankingPlaidController } from './BankingPlaid.controller';
const models = [RegisterTenancyModel(PlaidItem)]; const models = [RegisterTenancyModel(PlaidItem)];
@@ -39,6 +38,5 @@ const models = [RegisterTenancyModel(PlaidItem)];
TenancyContext, TenancyContext,
], ],
exports: [...models], exports: [...models],
controllers: [BankingPlaidController]
}) })
export class BankingPlaidModule {} export class BankingPlaidModule {}

View File

@@ -3,7 +3,6 @@ import { PlaidItemService } from './command/PlaidItem';
import { PlaidWebooks } from './command/PlaidWebhooks'; import { PlaidWebooks } from './command/PlaidWebhooks';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { PlaidItemDTO } from './types/BankingPlaid.types'; import { PlaidItemDTO } from './types/BankingPlaid.types';
import { PlaidItemDto } from './dtos/PlaidItem.dto';
@Injectable() @Injectable()
export class PlaidApplication { export class PlaidApplication {
@@ -26,7 +25,7 @@ export class PlaidApplication {
* @param {PlaidItemDTO} itemDTO * @param {PlaidItemDTO} itemDTO
* @returns * @returns
*/ */
public exchangeToken(itemDTO: PlaidItemDto): Promise<void> { public exchangeToken(itemDTO: PlaidItemDTO): Promise<void> {
return this.plaidItemService.item(itemDTO); return this.plaidItemService.item(itemDTO);
} }

View File

@@ -6,9 +6,11 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { SystemPlaidItem } from '../models/SystemPlaidItem'; import { SystemPlaidItem } from '../models/SystemPlaidItem';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { IPlaidItemCreatedEventPayload } from '../types/BankingPlaid.types'; import {
IPlaidItemCreatedEventPayload,
PlaidItemDTO,
} from '../types/BankingPlaid.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { PlaidItemDto } from '../dtos/PlaidItem.dto';
@Injectable() @Injectable()
export class PlaidItemService { export class PlaidItemService {
@@ -31,10 +33,10 @@ export class PlaidItemService {
/** /**
* Exchanges the public token to get access token and item id and then creates * Exchanges the public token to get access token and item id and then creates
* a new Plaid item. * a new Plaid item.
* @param {PlaidItemDto} itemDTO - Plaid item data transfer object. * @param {PlaidItemDTO} itemDTO - Plaid item data transfer object.
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async item(itemDTO: PlaidItemDto): Promise<void> { public async item(itemDTO: PlaidItemDTO): Promise<void> {
const { publicToken, institutionId } = itemDTO; const { publicToken, institutionId } = itemDTO;
const tenant = await this.tenancyContext.getTenant(); const tenant = await this.tenancyContext.getTenant();

View File

@@ -15,13 +15,6 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() @Injectable()
export class PlaidUpdateTransactions { export class PlaidUpdateTransactions {
/**
* Constructor method.
* @param {PlaidSyncDb} plaidSync - Plaid sync service.
* @param {UnitOfWork} uow - Unit of work.
* @param {TenantModelProxy<typeof PlaidItem>} plaidItemModel - Plaid item model.
* @param {PlaidApi} plaidClient - Plaid client.
*/
constructor( constructor(
private readonly plaidSync: PlaidSyncDb, private readonly plaidSync: PlaidSyncDb,
private readonly uow: UnitOfWork, private readonly uow: UnitOfWork,
@@ -35,7 +28,8 @@ export class PlaidUpdateTransactions {
/** /**
* Handles sync the Plaid item to Bigcaptial under UOW. * Handles sync the Plaid item to Bigcaptial under UOW.
* @param {string} plaidItemId - Plaid item id. * @param {number} tenantId - Tenant id.
* @param {number} plaidItemId - Plaid item id.
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>} * @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
*/ */
public async updateTransactions(plaidItemId: string) { public async updateTransactions(plaidItemId: string) {
@@ -50,9 +44,9 @@ export class PlaidUpdateTransactions {
* - New bank accounts. * - New bank accounts.
* - Last accounts feeds updated at. * - Last accounts feeds updated at.
* - Turn on the accounts feed flag. * - Turn on the accounts feed flag.
* @param {number} tenantId - Tenant ID.
* @param {string} plaidItemId - The Plaid ID for the item. * @param {string} plaidItemId - The Plaid ID for the item.
* @param {Knex.Transaction} trx - Knex transaction. * @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
*/ */
public async updateTransactionsWork( public async updateTransactionsWork(
plaidItemId: string, plaidItemId: string,

View File

@@ -1,11 +0,0 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class PlaidItemDto {
@IsString()
@IsNotEmpty()
publicToken: string;
@IsString()
@IsNotEmpty()
institutionId: string;
}

View File

@@ -24,7 +24,7 @@ export class TriggerRecognizedTransactionsSubscriber {
* @param {IBankRuleEventEditedPayload} payload - * @param {IBankRuleEventEditedPayload} payload -
*/ */
@OnEvent(events.bankRules.onEdited) @OnEvent(events.bankRules.onEdited)
async recognizedTransactionsOnRuleEdited({ private async recognizedTransactionsOnRuleEdited({
editRuleDTO, editRuleDTO,
oldBankRule, oldBankRule,
bankRule, bankRule,

View File

@@ -9,8 +9,11 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
import { BankingTransactionsApplication } from './BankingTransactionsApplication.service'; import { BankingTransactionsApplication } from './BankingTransactionsApplication.service';
import {
IBankAccountsFilter,
ICashflowAccountTransactionsQuery,
} from './types/BankingTransactions.types';
import { CreateBankTransactionDto } from './dtos/CreateBankTransaction.dto'; import { CreateBankTransactionDto } from './dtos/CreateBankTransaction.dto';
import { GetBankTransactionsQueryDto } from './dtos/GetBankTranasctionsQuery.dto';
@Controller('banking/transactions') @Controller('banking/transactions')
@ApiTags('banking-transactions') @ApiTags('banking-transactions')
@@ -21,7 +24,7 @@ export class BankingTransactionsController {
@Get() @Get()
async getBankAccountTransactions( async getBankAccountTransactions(
@Query() query: GetBankTransactionsQueryDto, @Query() query: ICashflowAccountTransactionsQuery,
) { ) {
return this.bankingTransactionsApplication.getBankAccountTransactions( return this.bankingTransactionsApplication.getBankAccountTransactions(
query, query,

View File

@@ -1,28 +0,0 @@
import {
IsNotEmpty,
IsNumber,
IsNumberString,
IsOptional,
} from 'class-validator';
import { NumberFormatQueryDto } from './NumberFormatQuery.dto';
import { Type } from 'class-transformer';
export class GetBankTransactionsQueryDto {
@IsOptional()
@Type(() => Number)
@IsNumber()
page: number;
@IsOptional()
@Type(() => Number)
@IsNumber()
pageSize: number;
@IsNotEmpty()
@Type(() => Number)
@IsNumber()
accountId: number;
@IsOptional()
numberFormat: NumberFormatQueryDto;
}

View File

@@ -1,26 +0,0 @@
import { Type } from "class-transformer";
import { IsBoolean, IsEnum, IsNumber, IsOptional, IsPositive } from "class-validator";
export class NumberFormatQueryDto {
@Type(() => Number)
@IsNumber()
@IsPositive()
@IsOptional()
readonly precision: number;
@IsBoolean()
@IsOptional()
readonly divideOn1000: boolean;
@IsBoolean()
@IsOptional()
readonly showZero: boolean;
@IsEnum(['total', 'always', 'none'])
@IsOptional()
readonly formatMoney: 'total' | 'always' | 'none';
@IsEnum(['parentheses', 'mines'])
@IsOptional()
readonly negativeFormat: 'parentheses' | 'mines';
}

View File

@@ -1,9 +1,9 @@
/* eslint-disable global-require */ /* eslint-disable global-require */
import * as moment from 'moment'; import * as moment from 'moment';
import { Model } from 'objection'; import { Model } from 'objection';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { BaseModel } from '@/models/Model';
export class UncategorizedBankTransaction extends TenantBaseModel { export class UncategorizedBankTransaction extends BaseModel {
readonly amount!: number; readonly amount!: number;
readonly date!: Date | string; readonly date!: Date | string;
readonly categorized!: boolean; readonly categorized!: boolean;

View File

@@ -1,6 +1,6 @@
// @ts-nocheck // @ts-nocheck
import * as R from 'ramda'; import R from 'ramda';
import * as moment from 'moment'; import moment from 'moment';
import { first, isEmpty } from 'lodash'; import { first, isEmpty } from 'lodash';
import { import {
ICashflowAccountTransaction, ICashflowAccountTransaction,

View File

@@ -1,16 +1,13 @@
import { Inject, Injectable, Scope } from '@nestjs/common'; import { Injectable, Scope } from '@nestjs/common';
import { ICashflowAccountTransactionsQuery } from '../../types/BankingTransactions.types'; import { ICashflowAccountTransactionsQuery } from '../../types/BankingTransactions.types';
import { import {
groupMatchedBankTransactions, groupMatchedBankTransactions,
groupUncategorizedTransactions, groupUncategorizedTransactions,
} from './_utils'; } from './_utils';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
import { UncategorizedBankTransaction } from '../../models/UncategorizedBankTransaction';
import { MatchedBankTransaction } from '@/modules/BankingMatching/models/MatchedBankTransaction';
@Injectable({ scope: Scope.REQUEST }) @Injectable({ scope: Scope.REQUEST })
export class GetBankAccountTransactionsRepository { export class GetBankAccountTransactionsRepository {
private models: any;
public query: ICashflowAccountTransactionsQuery; public query: ICashflowAccountTransactionsQuery;
public transactions: any; public transactions: any;
public uncategorizedTransactions: any; public uncategorizedTransactions: any;
@@ -20,28 +17,6 @@ export class GetBankAccountTransactionsRepository {
public pagination: any; public pagination: any;
public openingBalance: any; public openingBalance: any;
/**
* @param {TenantModelProxy<typeof AccountTransaction>} accountTransactionModel - Account transaction model.
* @param {TenantModelProxy<typeof UncategorizedBankTransaction>} uncategorizedBankTransactionModel - Uncategorized transaction model
* @param {TenantModelProxy<typeof MatchedBankTransaction>} matchedBankTransactionModel - Matched bank transaction model.
*/
constructor(
@Inject(AccountTransaction.name)
private readonly accountTransactionModel: TenantModelProxy<
typeof AccountTransaction
>,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
@Inject(MatchedBankTransaction.name)
private readonly matchedBankTransactionModel: TenantModelProxy<
typeof MatchedBankTransaction
>,
) {}
setQuery(query: ICashflowAccountTransactionsQuery) { setQuery(query: ICashflowAccountTransactionsQuery) {
this.query = query; this.query = query;
} }
@@ -62,8 +37,9 @@ export class GetBankAccountTransactionsRepository {
* @param {ICashflowAccountTransactionsQuery} query - * @param {ICashflowAccountTransactionsQuery} query -
*/ */
async initCashflowAccountTransactions() { async initCashflowAccountTransactions() {
const { results, pagination } = await this.accountTransactionModel() const { AccountTransaction } = this.models;
.query()
const { results, pagination } = await AccountTransaction.query()
.where('account_id', this.query.accountId) .where('account_id', this.query.accountId)
.orderBy([ .orderBy([
{ column: 'date', order: 'desc' }, { column: 'date', order: 'desc' },
@@ -83,9 +59,10 @@ export class GetBankAccountTransactionsRepository {
* @return {Promise<number>} * @return {Promise<number>}
*/ */
async initCashflowAccountOpeningBalance(): Promise<void> { async initCashflowAccountOpeningBalance(): Promise<void> {
const { AccountTransaction } = this.models;
// Retrieve the opening balance of credit and debit balances. // Retrieve the opening balance of credit and debit balances.
const openingBalancesSubquery = this.accountTransactionModel() const openingBalancesSubquery = AccountTransaction.query()
.query()
.where('account_id', this.query.accountId) .where('account_id', this.query.accountId)
.orderBy([ .orderBy([
{ column: 'date', order: 'desc' }, { column: 'date', order: 'desc' },
@@ -95,8 +72,7 @@ export class GetBankAccountTransactionsRepository {
.offset(this.pagination.pageSize * (this.pagination.page - 1)); .offset(this.pagination.pageSize * (this.pagination.page - 1));
// Sumation of credit and debit balance. // Sumation of credit and debit balance.
const openingBalances = await this.accountTransactionModel() const openingBalances = await AccountTransaction.query()
.query()
.sum('credit as credit') .sum('credit as credit')
.sum('debit as debit') .sum('debit as debit')
.from(openingBalancesSubquery.as('T')) .from(openingBalancesSubquery.as('T'))
@@ -111,11 +87,14 @@ export class GetBankAccountTransactionsRepository {
* Initialize the uncategorized transactions of the bank account. * Initialize the uncategorized transactions of the bank account.
*/ */
async initCategorizedTransactions() { async initCategorizedTransactions() {
const { UncategorizedCashflowTransaction } = this.models;
const refs = this.transactions.map((t) => [t.referenceType, t.referenceId]); const refs = this.transactions.map((t) => [t.referenceType, t.referenceId]);
const uncategorizedTransactions = const uncategorizedTransactions =
await this.uncategorizedBankTransactionModel() await UncategorizedCashflowTransaction.query().whereIn(
.query() ['categorizeRefType', 'categorizeRefId'],
.whereIn(['categorizeRefType', 'categorizeRefId'], refs); refs,
);
this.uncategorizedTransactions = uncategorizedTransactions; this.uncategorizedTransactions = uncategorizedTransactions;
this.uncategorizedTransactionsMapByRef = groupUncategorizedTransactions( this.uncategorizedTransactionsMapByRef = groupUncategorizedTransactions(
@@ -127,11 +106,14 @@ export class GetBankAccountTransactionsRepository {
* Initialize the matched bank transactions of the bank account. * Initialize the matched bank transactions of the bank account.
*/ */
async initMatchedTransactions(): Promise<void> { async initMatchedTransactions(): Promise<void> {
const { MatchedBankTransaction } = this.models;
const refs = this.transactions.map((t) => [t.referenceType, t.referenceId]); const refs = this.transactions.map((t) => [t.referenceType, t.referenceId]);
const matchedBankTransactions = await this.matchedBankTransactionModel() const matchedBankTransactions =
.query() await MatchedBankTransaction.query().whereIn(
.whereIn(['referenceType', 'referenceId'], refs); ['referenceType', 'referenceId'],
refs,
);
this.matchedBankTransactions = matchedBankTransactions; this.matchedBankTransactions = matchedBankTransactions;
this.matchedBankTransactionsMapByRef = groupMatchedBankTransactions( this.matchedBankTransactionsMapByRef = groupMatchedBankTransactions(
matchedBankTransactions, matchedBankTransactions,

View File

@@ -6,7 +6,6 @@ import {
Param, Param,
Post, Post,
Put, Put,
Query,
} from '@nestjs/common'; } from '@nestjs/common';
import { BillPaymentsApplication } from './BillPaymentsApplication.service'; import { BillPaymentsApplication } from './BillPaymentsApplication.service';
import { ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger';
@@ -14,16 +13,11 @@ import {
CreateBillPaymentDto, CreateBillPaymentDto,
EditBillPaymentDto, EditBillPaymentDto,
} from './dtos/BillPayment.dto'; } from './dtos/BillPayment.dto';
import { GetBillPaymentsFilterDto } from './dtos/GetBillPaymentsFilter.dto';
import { BillPaymentsPages } from './commands/BillPaymentsPages.service';
@Controller('bill-payments') @Controller('bill-payments')
@ApiTags('bill-payments') @ApiTags('bill-payments')
export class BillPaymentsController { export class BillPaymentsController {
constructor( constructor(private billPaymentsApplication: BillPaymentsApplication) {}
private billPaymentsApplication: BillPaymentsApplication,
private billPaymentsPagesService: BillPaymentsPages,
) {}
@Post() @Post()
@ApiOperation({ summary: 'Create a new bill payment.' }) @ApiOperation({ summary: 'Create a new bill payment.' })
@@ -63,22 +57,16 @@ export class BillPaymentsController {
); );
} }
@Get('/new-page/entries') @Get(':billPaymentId')
@ApiOperation({ @ApiOperation({ summary: 'Retrieves the bill payment details.' })
summary:
'Retrieves the payable entries of the new page once vendor be selected.',
})
@ApiParam({ @ApiParam({
name: 'vendorId', name: 'billPaymentId',
required: true, required: true,
type: Number, type: Number,
description: 'The vendor id', description: 'The bill payment id',
}) })
async getBillPaymentNewPageEntries(@Query('vendorId') vendorId: number) { public getBillPayment(@Param('billPaymentId') billPaymentId: string) {
const entries = return this.billPaymentsApplication.getBillPayment(Number(billPaymentId));
await this.billPaymentsPagesService.getNewPageEntries(vendorId);
return entries;
} }
@Get(':billPaymentId/bills') @Get(':billPaymentId/bills')
@@ -92,47 +80,4 @@ export class BillPaymentsController {
public getPaymentBills(@Param('billPaymentId') billPaymentId: string) { public getPaymentBills(@Param('billPaymentId') billPaymentId: string) {
return this.billPaymentsApplication.getPaymentBills(Number(billPaymentId)); return this.billPaymentsApplication.getPaymentBills(Number(billPaymentId));
} }
@Get('/:billPaymentId/edit-page')
@ApiOperation({
summary: 'Retrieves the edit page of the given bill payment.',
})
@ApiParam({
name: 'billPaymentId',
required: true,
type: Number,
description: 'The bill payment id',
})
public async getBillPaymentEditPage(
@Param('billPaymentId') billPaymentId: number,
) {
const billPaymentsWithEditEntries =
await this.billPaymentsPagesService.getBillPaymentEditPage(billPaymentId);
return billPaymentsWithEditEntries;
}
@Get(':billPaymentId')
@ApiOperation({ summary: 'Retrieves the bill payment details.' })
@ApiParam({
name: 'billPaymentId',
required: true,
type: Number,
description: 'The bill payment id',
})
public getBillPayment(@Param('billPaymentId') billPaymentId: string) {
return this.billPaymentsApplication.getBillPayment(Number(billPaymentId));
}
@Get()
@ApiOperation({ summary: 'Retrieves the bill payments list.' })
@ApiParam({
name: 'filterDTO',
required: true,
type: GetBillPaymentsFilterDto,
description: 'The bill payments filter dto',
})
public getBillPayments(@Query() filterDTO: GetBillPaymentsFilterDto) {
return this.billPaymentsApplication.getBillPayments(filterDTO);
}
} }

View File

@@ -17,13 +17,11 @@ import { BillPaymentGLEntriesSubscriber } from './subscribers/BillPaymentGLEntri
import { LedgerModule } from '../Ledger/Ledger.module'; import { LedgerModule } from '../Ledger/Ledger.module';
import { AccountsModule } from '../Accounts/Accounts.module'; import { AccountsModule } from '../Accounts/Accounts.module';
import { BillPaymentsExportable } from './queries/BillPaymentsExportable'; import { BillPaymentsExportable } from './queries/BillPaymentsExportable';
import { GetBillPayments } from '../Bills/queries/GetBillPayments';
import { BillPaymentsImportable } from './commands/BillPaymentsImportable'; import { BillPaymentsImportable } from './commands/BillPaymentsImportable';
import { GetBillPaymentsService } from './queries/GetBillPayments.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { BillPaymentsPages } from './commands/BillPaymentsPages.service';
@Module({ @Module({
imports: [LedgerModule, AccountsModule, DynamicListModule], imports: [LedgerModule, AccountsModule],
providers: [ providers: [
BillPaymentsApplication, BillPaymentsApplication,
CreateBillPaymentService, CreateBillPaymentService,
@@ -39,10 +37,9 @@ import { BillPaymentsPages } from './commands/BillPaymentsPages.service';
TenancyContext, TenancyContext,
BillPaymentGLEntries, BillPaymentGLEntries,
BillPaymentGLEntriesSubscriber, BillPaymentGLEntriesSubscriber,
GetBillPayments,
BillPaymentsExportable, BillPaymentsExportable,
BillPaymentsImportable, BillPaymentsImportable,
GetBillPaymentsService,
BillPaymentsPages,
], ],
exports: [ exports: [
BillPaymentValidators, BillPaymentValidators,

View File

@@ -2,11 +2,11 @@ import { Injectable } from '@nestjs/common';
import { CreateBillPaymentService } from './commands/CreateBillPayment.service'; import { CreateBillPaymentService } from './commands/CreateBillPayment.service';
import { DeleteBillPayment } from './commands/DeleteBillPayment.service'; import { DeleteBillPayment } from './commands/DeleteBillPayment.service';
import { EditBillPayment } from './commands/EditBillPayment.service'; import { EditBillPayment } from './commands/EditBillPayment.service';
// import { GetBillPayments } from './GetBillPayments';
import { GetBillPayment } from './queries/GetBillPayment.service'; import { GetBillPayment } from './queries/GetBillPayment.service';
import { GetPaymentBills } from './queries/GetPaymentBills.service'; import { GetPaymentBills } from './queries/GetPaymentBills.service';
import { GetBillPayments } from '../Bills/queries/GetBillPayments';
import { CreateBillPaymentDto, EditBillPaymentDto } from './dtos/BillPayment.dto'; import { CreateBillPaymentDto, EditBillPaymentDto } from './dtos/BillPayment.dto';
import { GetBillPaymentsService } from './queries/GetBillPayments.service';
import { GetBillPaymentsFilterDto } from './dtos/GetBillPaymentsFilter.dto';
/** /**
* Bill payments application. * Bill payments application.
@@ -20,7 +20,7 @@ export class BillPaymentsApplication {
private deleteBillPaymentService: DeleteBillPayment, private deleteBillPaymentService: DeleteBillPayment,
private getBillPaymentService: GetBillPayment, private getBillPaymentService: GetBillPayment,
private getPaymentBillsService: GetPaymentBills, private getPaymentBillsService: GetPaymentBills,
private getBillPaymentsService: GetBillPaymentsService, private getBillPaymentsService: GetBillPayments,
) {} ) {}
/** /**
@@ -58,10 +58,9 @@ export class BillPaymentsApplication {
/** /**
* Retrieves bill payments list. * Retrieves bill payments list.
* @param {GetBillPaymentsFilterDto} filterDTO - The given bill payments filter dto.
*/ */
public getBillPayments(filterDTO: GetBillPaymentsFilterDto) { public getBillPayments() {
return this.getBillPaymentsService.getBillPayments(filterDTO); // return this.getBillPaymentsService.getBillPayments(filterDTO);
} }
/** /**

View File

@@ -0,0 +1,75 @@
// import { Inject, Service } from 'typedi';
// import * as R from 'ramda';
// import {
// IBillPayment,
// IBillPaymentsFilter,
// IPaginationMeta,
// IFilterMeta,
// } from '@/interfaces';
// import { BillPaymentTransformer } from './queries/BillPaymentTransformer';
// import DynamicListingService from '@/services/DynamicListing/DynamicListService';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
// @Service()
// export class GetBillPayments {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private dynamicListService: DynamicListingService;
// @Inject()
// private transformer: TransformerInjectable;
// /**
// * Retrieve bill payment paginted and filterable list.
// * @param {number} tenantId
// * @param {IBillPaymentsFilter} billPaymentsFilter
// */
// public async getBillPayments(
// tenantId: number,
// filterDTO: IBillPaymentsFilter
// ): Promise<{
// billPayments: IBillPayment[];
// pagination: IPaginationMeta;
// filterMeta: IFilterMeta;
// }> {
// const { BillPayment } = this.tenancy.models(tenantId);
// // Parses filter DTO.
// const filter = this.parseListFilterDTO(filterDTO);
// // Dynamic list service.
// const dynamicList = await this.dynamicListService.dynamicList(
// tenantId,
// BillPayment,
// filter
// );
// const { results, pagination } = await BillPayment.query()
// .onBuild((builder) => {
// builder.withGraphFetched('vendor');
// builder.withGraphFetched('paymentAccount');
// dynamicList.buildQuery()(builder);
// filter?.filterQuery && filter?.filterQuery(builder);
// })
// .pagination(filter.page - 1, filter.pageSize);
// // Transformes the bill payments models to POJO.
// const billPayments = await this.transformer.transform(
// tenantId,
// results,
// new BillPaymentTransformer()
// );
// return {
// billPayments,
// pagination,
// filterMeta: dynamicList.getResponseMeta(),
// };
// }
// private parseListFilterDTO(filterDTO) {
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
// }
// }

View File

@@ -8,7 +8,7 @@ import { ServiceError } from '../../Items/ServiceError';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() @Injectable()
export class BillPaymentsPages { export default class BillPaymentsPages {
/** /**
* @param {TenantModelProxy<typeof Bill>} billModel - Bill model. * @param {TenantModelProxy<typeof Bill>} billModel - Bill model.
* @param {TenantModelProxy<typeof BillPayment>} billPaymentModel - Bill payment model. * @param {TenantModelProxy<typeof BillPayment>} billPaymentModel - Bill payment model.

View File

@@ -1,8 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { import {
IsArray, IsArray,
IsDateString, IsDate,
IsNotEmpty, IsNotEmpty,
IsNumber, IsNumber,
IsOptional, IsOptional,
@@ -10,33 +9,25 @@ import {
ValidateNested, ValidateNested,
} from 'class-validator'; } from 'class-validator';
import { AttachmentLinkDto } from '@/modules/Attachments/dtos/Attachment.dto'; import { AttachmentLinkDto } from '@/modules/Attachments/dtos/Attachment.dto';
import { ToNumber } from '@/common/decorators/Validators'; import { ApiProperty } from '@nestjs/swagger';
export class BillPaymentEntryDto { export class BillPaymentEntryDto {
@ToNumber()
@IsNumber() @IsNumber()
@IsNotEmpty()
@ApiProperty({ description: 'The id of the bill', example: 1 })
billId: number; billId: number;
@ToNumber()
@IsNumber() @IsNumber()
@IsNotEmpty()
@ApiProperty({
description: 'The payment amount of the bill payment',
example: 100,
})
paymentAmount: number; paymentAmount: number;
} }
export class CommandBillPaymentDTO { export class CommandBillPaymentDTO {
@ToNumber()
@IsNumber() @IsNumber()
@IsNotEmpty() @IsNotEmpty()
@ApiProperty({ description: 'The id of the vendor', example: 1 }) @ApiProperty({
description: 'The id of the vendor',
example: 1,
})
vendorId: number; vendorId: number;
@ToNumber()
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
@ApiProperty({ @ApiProperty({
@@ -45,10 +36,12 @@ export class CommandBillPaymentDTO {
}) })
amount?: number; amount?: number;
@ToNumber()
@IsNumber() @IsNumber()
@IsNotEmpty() @IsNotEmpty()
@ApiProperty({ description: 'The id of the payment account', example: 1 }) @ApiProperty({
description: 'The id of the payment account',
example: 1,
})
paymentAccountId: number; paymentAccountId: number;
@IsString() @IsString()
@@ -59,8 +52,8 @@ export class CommandBillPaymentDTO {
}) })
paymentNumber?: string; paymentNumber?: string;
@IsDateString() @IsDate()
@IsNotEmpty() @Type(() => Date)
@ApiProperty({ @ApiProperty({
description: 'The payment date of the bill payment', description: 'The payment date of the bill payment',
example: '2021-01-01', example: '2021-01-01',
@@ -83,6 +76,7 @@ export class CommandBillPaymentDTO {
}) })
statement?: string; statement?: string;
@IsString()
@IsOptional() @IsOptional()
@IsArray() @IsArray()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@@ -98,10 +92,12 @@ export class CommandBillPaymentDTO {
}) })
entries: BillPaymentEntryDto[]; entries: BillPaymentEntryDto[];
@ToNumber()
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
@ApiProperty({ description: 'The id of the branch', example: 1 }) @ApiProperty({
description: 'The id of the branch',
example: 1,
})
branchId?: number; branchId?: number;
@IsArray() @IsArray()

View File

@@ -1,34 +0,0 @@
import { Type } from 'class-transformer';
import { IsIn, IsJSON, IsNumber, IsOptional, IsString } from 'class-validator';
export class GetBillPaymentsFilterDto {
@IsOptional()
@IsNumber()
@Type(() => Number)
readonly customViewId?: number;
@IsOptional()
@IsJSON()
readonly stringifiedFilterRoles?: string;
@IsOptional()
readonly columnSortBy?: string;
@IsOptional()
@IsIn(['desc', 'asc'])
readonly sortOrder?: string;
@IsOptional()
@IsNumber()
@Type(() => Number)
readonly page?: number;
@IsOptional()
@IsNumber()
@Type(() => Number)
readonly pageSize?: number;
@IsOptional()
@IsString()
readonly searchKeyword?: string;
}

View File

@@ -6,7 +6,6 @@ import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.dec
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator'; import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
import { BillPaymentMeta } from './BillPayment.meta'; import { BillPaymentMeta } from './BillPayment.meta';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { Model } from 'objection';
@ImportableModel() @ImportableModel()
@ExportableModel() @ExportableModel()
@@ -62,97 +61,102 @@ export class BillPayment extends TenantBaseModel {
return this.amount * this.exchangeRate; return this.amount * this.exchangeRate;
} }
/**
* Model settings.
*/
// static get meta() {
// return BillPaymentSettings;
// }
/** /**
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { // static get relationMappings() {
const { BillPaymentEntry } = require('./BillPaymentEntry'); // const BillPaymentEntry = require('models/BillPaymentEntry');
const { // const AccountTransaction = require('models/AccountTransaction');
AccountTransaction, // const Vendor = require('models/Vendor');
} = require('../../Accounts/models/AccountTransaction.model'); // const Account = require('models/Account');
const { Vendor } = require('../../Vendors/models/Vendor'); // const Branch = require('models/Branch');
const { Account } = require('../../Accounts/models/Account.model'); // const Document = require('models/Document');
const { Branch } = require('../../Branches/models/Branch.model');
const { Document } = require('../../ChromiumlyTenancy/models/Document');
return { // return {
entries: { // entries: {
relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
modelClass: BillPaymentEntry, // modelClass: BillPaymentEntry.default,
join: { // join: {
from: 'bills_payments.id', // from: 'bills_payments.id',
to: 'bills_payments_entries.billPaymentId', // to: 'bills_payments_entries.billPaymentId',
}, // },
filter: (query) => { // filter: (query) => {
query.orderBy('index', 'ASC'); // query.orderBy('index', 'ASC');
}, // },
}, // },
vendor: { // vendor: {
relation: Model.BelongsToOneRelation, // relation: Model.BelongsToOneRelation,
modelClass: Vendor, // modelClass: Vendor.default,
join: { // join: {
from: 'bills_payments.vendorId', // from: 'bills_payments.vendorId',
to: 'contacts.id', // to: 'contacts.id',
}, // },
filter(query) { // filter(query) {
query.where('contact_service', 'vendor'); // query.where('contact_service', 'vendor');
}, // },
}, // },
paymentAccount: { // paymentAccount: {
relation: Model.BelongsToOneRelation, // relation: Model.BelongsToOneRelation,
modelClass: Account, // modelClass: Account.default,
join: { // join: {
from: 'bills_payments.paymentAccountId', // from: 'bills_payments.paymentAccountId',
to: 'accounts.id', // to: 'accounts.id',
}, // },
}, // },
transactions: { // transactions: {
relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
modelClass: AccountTransaction, // modelClass: AccountTransaction.default,
join: { // join: {
from: 'bills_payments.id', // from: 'bills_payments.id',
to: 'accounts_transactions.referenceId', // to: 'accounts_transactions.referenceId',
}, // },
filter(builder) { // filter(builder) {
builder.where('reference_type', 'BillPayment'); // builder.where('reference_type', 'BillPayment');
}, // },
}, // },
/** // /**
* Bill payment may belongs to branch. // * Bill payment may belongs to branch.
*/ // */
branch: { // branch: {
relation: Model.BelongsToOneRelation, // relation: Model.BelongsToOneRelation,
modelClass: Branch, // modelClass: Branch.default,
join: { // join: {
from: 'bills_payments.branchId', // from: 'bills_payments.branchId',
to: 'branches.id', // to: 'branches.id',
}, // },
}, // },
/** // /**
* Bill payment may has many attached attachments. // * Bill payment may has many attached attachments.
*/ // */
attachments: { // attachments: {
relation: Model.ManyToManyRelation, // relation: Model.ManyToManyRelation,
modelClass: Document, // modelClass: Document.default,
join: { // join: {
from: 'bills_payments.id', // from: 'bills_payments.id',
through: { // through: {
from: 'document_links.modelId', // from: 'document_links.modelId',
to: 'document_links.documentId', // to: 'document_links.documentId',
}, // },
to: 'documents.id', // to: 'documents.id',
}, // },
filter(query) { // filter(query) {
query.where('model_ref', 'BillPayment'); // query.where('model_ref', 'BillPayment');
}, // },
}, // },
}; // };
} // }
/** /**
* Retrieve the default custom views, roles and columns. * Retrieve the default custom views, roles and columns.

View File

@@ -1,15 +1,15 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from "@nestjs/common";
import { BillPaymentsApplication } from '../BillPaymentsApplication.service'; import { BillPaymentsApplication } from "../BillPaymentsApplication.service";
import { Exportable } from '@/modules/Export/Exportable'; import { Exportable } from "@/modules/Export/Exportable";
import { EXPORT_SIZE_LIMIT } from '@/modules/Export/constants'; import { EXPORT_SIZE_LIMIT } from "@/modules/Export/constants";
import { ExportableService } from '@/modules/Export/decorators/ExportableModel.decorator'; import { ExportableService } from "@/modules/Export/decorators/ExportableModel.decorator";
import { BillPayment } from '../models/BillPayment'; import { BillPayment } from "../models/BillPayment";
@Injectable() @Injectable()
@ExportableService({ name: BillPayment.name }) @ExportableService({ name: BillPayment.name })
export class BillPaymentsExportable extends Exportable { export class BillPaymentsExportable extends Exportable {
constructor( constructor(
private readonly billPaymentsApplication: BillPaymentsApplication, private readonly billPaymentsApplication: BillPaymentsApplication
) { ) {
super(); super();
} }
@@ -30,11 +30,13 @@ export class BillPaymentsExportable extends Exportable {
...query, ...query,
page: 1, page: 1,
pageSize: EXPORT_SIZE_LIMIT, pageSize: EXPORT_SIZE_LIMIT,
filterQuery, filterQuery
} as any; } as any;
return this.billPaymentsApplication return [];
.getBillPayments(parsedQuery)
.then((output) => output.billPayments); // return this.billPaymentsApplication
// .billPayments(tenantId, parsedQuery)
// .then((output) => output.billPayments);
} }
} }

View File

@@ -1,67 +0,0 @@
import * as R from 'ramda';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { BillPayment } from '../models/BillPayment';
import { Inject, Injectable } from '@nestjs/common';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
import { BillPaymentTransformer } from './BillPaymentTransformer';
import { GetBillPaymentsFilterDto } from '../dtos/GetBillPaymentsFilter.dto';
@Injectable()
export class GetBillPaymentsService {
constructor(
private readonly dynamicListService: DynamicListService,
private readonly transformer: TransformerInjectable,
@Inject(BillPayment.name)
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
) {}
/**
* Retrieve bill payment paginted and filterable list.
* @param {GetBillPaymentsFilterDto} billPaymentsFilter
*/
public async getBillPayments(filterDTO: GetBillPaymentsFilterDto) {
const _filterDto = {
page: 1,
pageSize: 12,
filterRoles: [],
sortOrder: 'desc',
columnSortBy: 'created_at',
...filterDTO,
};
// Parses filter DTO.
const filter = this.parseListFilterDTO(_filterDto);
// Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList(
BillPayment,
filter,
);
const { results, pagination } = await this.billPaymentModel()
.query()
.onBuild((builder) => {
builder.withGraphFetched('vendor');
builder.withGraphFetched('paymentAccount');
dynamicList.buildQuery()(builder);
filter?.filterQuery && filter?.filterQuery(builder);
})
.pagination(filter.page - 1, filter.pageSize);
// Transformes the bill payments models to POJO.
const billPayments = await this.transformer.transform(
results,
new BillPaymentTransformer(),
);
return {
billPayments,
pagination,
filterMeta: dynamicList.getResponseMeta(),
};
}
private parseListFilterDTO(filterDTO) {
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
}
}

View File

@@ -18,7 +18,7 @@ export class BillPaymentGLEntriesSubscriber {
* Handle bill payment writing journal entries once created. * Handle bill payment writing journal entries once created.
*/ */
@OnEvent(events.billPayment.onCreated) @OnEvent(events.billPayment.onCreated)
async handleWriteJournalEntries({ private async handleWriteJournalEntries({
billPayment, billPayment,
trx, trx,
}: IBillPaymentEventCreatedPayload) { }: IBillPaymentEventCreatedPayload) {
@@ -34,7 +34,7 @@ export class BillPaymentGLEntriesSubscriber {
* Handle bill payment re-writing journal entries once the payment transaction be edited. * Handle bill payment re-writing journal entries once the payment transaction be edited.
*/ */
@OnEvent(events.billPayment.onEdited) @OnEvent(events.billPayment.onEdited)
async handleRewriteJournalEntriesOncePaymentEdited({ private async handleRewriteJournalEntriesOncePaymentEdited({
billPayment, billPayment,
trx, trx,
}: IBillPaymentEventEditedPayload) { }: IBillPaymentEventEditedPayload) {
@@ -48,7 +48,7 @@ export class BillPaymentGLEntriesSubscriber {
* Reverts journal entries once bill payment deleted. * Reverts journal entries once bill payment deleted.
*/ */
@OnEvent(events.billPayment.onDeleted) @OnEvent(events.billPayment.onDeleted)
async handleRevertJournalEntries({ private async handleRevertJournalEntries({
billPaymentId, billPaymentId,
trx, trx,
}: IBillPaymentEventDeletedPayload) { }: IBillPaymentEventDeletedPayload) {

View File

@@ -1,4 +1,3 @@
import { ToNumber } from '@/common/decorators/Validators';
import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto'; import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { import {
@@ -6,14 +5,14 @@ import {
IsArray, IsArray,
IsBoolean, IsBoolean,
IsDate, IsDate,
IsDateString,
IsEnum, IsEnum,
IsInt, IsInt,
IsNotEmpty,
IsNumber, IsNumber,
IsOptional, IsOptional,
IsPositive, IsPositive,
IsString, IsString,
Min,
MinLength,
ValidateNested, ValidateNested,
} from 'class-validator'; } from 'class-validator';
@@ -30,12 +29,10 @@ export class BillEntryDto extends ItemEntryDto {
class AttachmentDto { class AttachmentDto {
@IsString() @IsString()
@IsNotEmpty()
key: string; key: string;
} }
export class CommandBillDto { export class CommandBillDto {
@IsOptional()
@IsString() @IsString()
billNumber: string; billNumber: string;
@@ -43,36 +40,32 @@ export class CommandBillDto {
@IsString() @IsString()
referenceNo?: string; referenceNo?: string;
@IsNotEmpty() @IsDate()
@IsDateString() @Type(() => Date)
billDate: Date; billDate: Date;
@IsOptional() @IsOptional()
@IsDateString() @IsDate()
@Type(() => Date)
dueDate?: Date; dueDate?: Date;
@IsInt() @IsInt()
@IsNotEmpty()
vendorId: number; vendorId: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsNumber() @IsNumber()
@IsPositive() @IsPositive()
exchangeRate?: number; exchangeRate?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
warehouseId?: number; warehouseId?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
branchId?: number; branchId?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
projectId?: number; projectId?: number;
@@ -81,11 +74,9 @@ export class CommandBillDto {
note?: string; note?: string;
@IsBoolean() @IsBoolean()
@IsOptional()
open: boolean = false; open: boolean = false;
@IsBoolean() @IsBoolean()
@IsOptional()
isInclusiveTax: boolean = false; isInclusiveTax: boolean = false;
@IsArray() @IsArray()
@@ -101,17 +92,13 @@ export class CommandBillDto {
attachments?: AttachmentDto[]; attachments?: AttachmentDto[];
@IsEnum(DiscountType) @IsEnum(DiscountType)
@IsOptional()
discountType: DiscountType = DiscountType.Amount; discountType: DiscountType = DiscountType.Amount;
@IsOptional() @IsOptional()
@ToNumber()
@IsNumber() @IsNumber()
@IsPositive()
discount?: number; discount?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsNumber() @IsNumber()
adjustment?: number; adjustment?: number;
} }

View File

@@ -76,13 +76,8 @@ export class BranchesController {
status: 200, status: 200,
description: 'The branches feature has been successfully activated.', description: 'The branches feature has been successfully activated.',
}) })
async activateBranches() { activateBranches() {
await this.branchesApplication.activateBranches(); return this.branchesApplication.activateBranches();
return {
code: 200,
message: 'The branches activated successfully.',
};
} }
@Put(':id/mark-as-primary') @Put(':id/mark-as-primary')

View File

@@ -17,8 +17,6 @@ export class BranchesSettingsService {
const settingsStore = await this.settingsStore(); const settingsStore = await this.settingsStore();
settingsStore.set({ group: 'features', key: Features.BRANCHES, value: 1 }); settingsStore.set({ group: 'features', key: Features.BRANCHES, value: 1 });
await settingsStore.save();
}; };
/** /**

View File

@@ -1,9 +1,9 @@
import { IsOptional } from '@/common/decorators/Validators';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { import {
IsBoolean, IsBoolean,
IsEmail, IsEmail,
IsNotEmpty, IsNotEmpty,
IsOptional,
IsString, IsString,
IsUrl, IsUrl,
} from 'class-validator'; } from 'class-validator';

View File

@@ -1,5 +1,5 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import * as path from 'path'; import path from 'path';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { PageProperties, PdfFormat } from '@/libs/Chromiumly/_types'; import { PageProperties, PdfFormat } from '@/libs/Chromiumly/_types';
import { UrlConverter } from '@/libs/Chromiumly/UrlConvert'; import { UrlConverter } from '@/libs/Chromiumly/UrlConvert';
@@ -40,7 +40,7 @@ export class ChromiumlyHtmlConvert {
const cleanup = async () => { const cleanup = async () => {
await fs.unlink(filePath); await fs.unlink(filePath);
await this.documentModel().query().where('key', filename).delete(); await Document.query().where('key', filename).delete();
}; };
return [filename, cleanup]; return [filename, cleanup];
} }

View File

@@ -1,4 +1,4 @@
import * as path from 'path'; import path from 'path';
export const PDF_FILE_SUB_DIR = '/pdf'; export const PDF_FILE_SUB_DIR = '/pdf';
export const PDF_FILE_EXPIRE_IN = 40; // ms export const PDF_FILE_EXPIRE_IN = 40; // ms
@@ -9,5 +9,6 @@ export const getPdfFilesStorageDir = (filename: string) => {
export const getPdfFilePath = (filename: string) => { export const getPdfFilePath = (filename: string) => {
const storageDir = getPdfFilesStorageDir(filename); const storageDir = getPdfFilesStorageDir(filename);
return path.join(global.__static_dirname, storageDir);
return path.join(global.__storage_dir, storageDir);
}; };

View File

@@ -1,6 +0,0 @@
export const ERRORS = {
CONTACT_ALREADY_ACTIVE: 'CONTACT_ALREADY_ACTIVE',
CONTACT_ALREADY_INACTIVE: 'CONTACT_ALREADY_INACTIVE'
}

View File

@@ -1,45 +0,0 @@
import {
Controller,
Get,
Query,
Param,
Post,
ParseIntPipe,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiParam } from '@nestjs/swagger';
import { GetContactsAutoCompleteQuery } from './dtos/GetContactsAutoCompleteQuery.dto';
import { GetAutoCompleteContactsService } from './queries/GetAutoCompleteContacts.service';
import { ActivateContactService } from './commands/ActivateContact.service';
import { InactivateContactService } from './commands/InactivateContact.service';
@Controller('contacts')
@ApiTags('contacts')
export class ContactsController {
constructor(
private readonly getAutoCompleteService: GetAutoCompleteContactsService,
private readonly activateContactService: ActivateContactService,
private readonly inactivateContactService: InactivateContactService,
) {}
@Get('auto-complete')
@ApiOperation({ summary: 'Get the auto-complete contacts' })
getAutoComplete(@Query() query: GetContactsAutoCompleteQuery) {
return this.getAutoCompleteService.autocompleteContacts(query);
}
@Post(':id/activate')
@ApiOperation({ summary: 'Activate a contact' })
@ApiParam({ name: 'id', type: 'number', description: 'Contact ID' })
async activateContact(@Param('id', ParseIntPipe) contactId: number) {
await this.activateContactService.activateContact(contactId);
return { id: contactId, activated: true };
}
@Post(':id/inactivate')
@ApiOperation({ summary: 'Inactivate a contact' })
@ApiParam({ name: 'id', type: 'number', description: 'Contact ID' })
async inactivateContact(@Param('id', ParseIntPipe) contactId: number) {
await this.inactivateContactService.inactivateContact(contactId);
return { id: contactId, inactivated: true };
}
}

View File

@@ -1,15 +0,0 @@
import { Module } from '@nestjs/common';
import { GetAutoCompleteContactsService } from './queries/GetAutoCompleteContacts.service';
import { ContactsController } from './Contacts.controller';
import { ActivateContactService } from './commands/ActivateContact.service';
import { InactivateContactService } from './commands/InactivateContact.service';
@Module({
providers: [
GetAutoCompleteContactsService,
ActivateContactService,
InactivateContactService,
],
controllers: [ContactsController],
})
export class ContactsModule {}

View File

@@ -1,14 +0,0 @@
import { IFilterRole } from '../DynamicListing/DynamicFilter/DynamicFilter.types';
export interface IContactsAutoCompleteFilter {
limit: number;
keyword: string;
filterRoles?: IFilterRole[];
columnSortBy: string;
sortOrder: string;
}
export interface IContactAutoCompleteItem {
displayName: string;
contactService: string;
}

View File

@@ -1,28 +0,0 @@
import { ServiceError } from '@/modules/Items/ServiceError';
import { Contact } from '../models/Contact';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { Inject, Injectable } from '@nestjs/common';
import { ERRORS } from '../Contacts.constants';
@Injectable()
export class ActivateContactService {
constructor(
@Inject(Contact.name)
private readonly contactModel: TenantModelProxy<typeof Contact>,
) {}
async activateContact(contactId: number) {
const contact = await this.contactModel()
.query()
.findById(contactId)
.throwIfNotFound();
if (contact.active) {
throw new ServiceError(ERRORS.CONTACT_ALREADY_ACTIVE);
}
await this.contactModel()
.query()
.findById(contactId)
.update({ active: true });
}
}

View File

@@ -1,28 +0,0 @@
import { Inject, Injectable } from '@nestjs/common';
import { ServiceError } from '@/modules/Items/ServiceError';
import { Contact } from '../models/Contact';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { ERRORS } from '../Contacts.constants';
@Injectable()
export class InactivateContactService {
constructor(
@Inject(Contact.name)
private readonly contactModel: TenantModelProxy<typeof Contact>,
) {}
async inactivateContact(contactId: number) {
const contact = await this.contactModel()
.query()
.findById(contactId)
.throwIfNotFound();
if (!contact.active) {
throw new ServiceError(ERRORS.CONTACT_ALREADY_INACTIVE);
}
await this.contactModel()
.query()
.findById(contactId)
.update({ active: false });
}
}

View File

@@ -1,11 +0,0 @@
import { IsNumber, IsOptional, IsString } from 'class-validator';
export class GetContactsAutoCompleteQuery {
@IsNumber()
@IsOptional()
limit: number;
@IsString()
@IsOptional()
keyword: string;
}

View File

@@ -1,39 +0,0 @@
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { Contact } from '../models/Contact';
import { Inject, Injectable } from '@nestjs/common';
import { IContactsAutoCompleteFilter } from '../Contacts.types';
import { GetContactsAutoCompleteQuery } from '../dtos/GetContactsAutoCompleteQuery.dto';
@Injectable()
export class GetAutoCompleteContactsService {
constructor(
@Inject(Contact.name)
private readonly contactModel: TenantModelProxy<typeof Contact>,
) {}
/**
* Retrieve auto-complete contacts list.
* @param {number} tenantId -
* @param {IContactsAutoCompleteFilter} contactsFilter -
* @return {IContactAutoCompleteItem}
*/
async autocompleteContacts(queryDto: GetContactsAutoCompleteQuery) {
const _queryDto = {
filterRoles: [],
sortOrder: 'asc',
columnSortBy: 'display_name',
limit: 10,
...queryDto,
};
// Retrieve contacts list by the given query.
const contacts = await this.contactModel()
.query()
.onBuild((builder) => {
if (_queryDto.keyword) {
builder.where('display_name', 'LIKE', `%${_queryDto.keyword}%`);
}
builder.limit(_queryDto.limit);
});
return contacts;
}
}

View File

@@ -1,5 +1,5 @@
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; import { Body, Controller, Delete, Param, Post } 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';
@@ -12,14 +12,6 @@ export class CreditNoteRefundsController {
private readonly creditNotesRefundsApplication: CreditNotesRefundsApplication, private readonly creditNotesRefundsApplication: CreditNotesRefundsApplication,
) {} ) {}
@Get(':creditNoteId/refunds')
@ApiOperation({ summary: 'Retrieve the credit note graph.' })
getCreditNoteRefunds(@Param('creditNoteId') creditNoteId: number) {
return this.creditNotesRefundsApplication.getCreditNoteRefunds(
creditNoteId,
);
}
/** /**
* Create a refund credit note. * Create a refund credit note.
* @param {number} creditNoteId - The credit note ID. * @param {number} creditNoteId - The credit note ID.

View File

@@ -6,7 +6,6 @@ import { RefundSyncCreditNoteBalanceService } from './commands/RefundSyncCreditN
import { CreditNotesRefundsApplication } from './CreditNotesRefundsApplication.service'; import { CreditNotesRefundsApplication } from './CreditNotesRefundsApplication.service';
import { CreditNoteRefundsController } from './CreditNoteRefunds.controller'; import { CreditNoteRefundsController } from './CreditNoteRefunds.controller';
import { CreditNotesModule } from '../CreditNotes/CreditNotes.module'; import { CreditNotesModule } from '../CreditNotes/CreditNotes.module';
import { GetCreditNoteRefundsService } from './queries/GetCreditNoteRefunds.service';
@Module({ @Module({
imports: [forwardRef(() => CreditNotesModule)], imports: [forwardRef(() => CreditNotesModule)],
@@ -16,9 +15,10 @@ import { GetCreditNoteRefundsService } from './queries/GetCreditNoteRefunds.serv
RefundCreditNoteService, RefundCreditNoteService,
RefundSyncCreditNoteBalanceService, RefundSyncCreditNoteBalanceService,
CreditNotesRefundsApplication, CreditNotesRefundsApplication,
GetCreditNoteRefundsService,
], ],
exports: [RefundSyncCreditNoteBalanceService], exports: [
RefundSyncCreditNoteBalanceService
],
controllers: [CreditNoteRefundsController], controllers: [CreditNoteRefundsController],
}) })
export class CreditNoteRefundsModule {} export class CreditNoteRefundsModule {}

View File

@@ -5,27 +5,16 @@ import { DeleteRefundCreditNoteService } from './commands/DeleteRefundCreditNote
import { RefundCreditNoteService } from './commands/RefundCreditNote.service'; import { RefundCreditNoteService } from './commands/RefundCreditNote.service';
import { RefundSyncCreditNoteBalanceService } from './commands/RefundSyncCreditNoteBalance'; import { RefundSyncCreditNoteBalanceService } from './commands/RefundSyncCreditNoteBalance';
import { CreditNoteRefundDto } from './dto/CreditNoteRefund.dto'; import { CreditNoteRefundDto } from './dto/CreditNoteRefund.dto';
import { GetCreditNoteRefundsService } from './queries/GetCreditNoteRefunds.service';
@Injectable() @Injectable()
export class CreditNotesRefundsApplication { export class CreditNotesRefundsApplication {
constructor( constructor(
private readonly createRefundCreditNoteService: CreateRefundCreditNoteService, private readonly createRefundCreditNoteService: CreateRefundCreditNoteService,
private readonly deleteRefundCreditNoteService: DeleteRefundCreditNoteService, private readonly deleteRefundCreditNoteService: DeleteRefundCreditNoteService,
private readonly getCreditNoteRefundsService: GetCreditNoteRefundsService,
private readonly refundCreditNoteService: RefundCreditNoteService, private readonly refundCreditNoteService: RefundCreditNoteService,
private readonly refundSyncCreditNoteBalanceService: RefundSyncCreditNoteBalanceService, private readonly refundSyncCreditNoteBalanceService: RefundSyncCreditNoteBalanceService,
) {} ) {}
/**
* Retrieve the credit note graph.
* @param {number} creditNoteId - Credit note id.
* @returns {Promise<IRefundCreditNotePOJO[]>}
*/
public getCreditNoteRefunds(creditNoteId: number) {
return this.getCreditNoteRefundsService.getCreditNoteRefunds(creditNoteId);
}
/** /**
* Create a refund credit note. * Create a refund credit note.
* @param {number} creditNoteId - The credit note ID. * @param {number} creditNoteId - The credit note ID.

View File

@@ -6,7 +6,7 @@ import { IRefundCreditNotePOJO } from '../types/CreditNoteRefunds.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() @Injectable()
export class GetCreditNoteRefundsService { export class ListCreditNoteRefunds {
constructor( constructor(
private readonly transformer: TransformerInjectable, private readonly transformer: TransformerInjectable,
@@ -18,7 +18,7 @@ export class GetCreditNoteRefundsService {
/** /**
* Retrieve the credit note graph. * Retrieve the credit note graph.
* @param {number} creditNoteId - Credit note id. * @param {number} creditNoteId
* @returns {Promise<IRefundCreditNotePOJO[]>} * @returns {Promise<IRefundCreditNotePOJO[]>}
*/ */
public async getCreditNoteRefunds( public async getCreditNoteRefunds(

View File

@@ -7,8 +7,6 @@ import { GetCreditNotePdf } from './queries/GetCreditNotePdf.serivce';
import { ICreditNotesQueryDTO } from './types/CreditNotes.types'; import { ICreditNotesQueryDTO } from './types/CreditNotes.types';
import { GetCreditNotesService } from './queries/GetCreditNotes.service'; import { GetCreditNotesService } from './queries/GetCreditNotes.service';
import { CreateCreditNoteDto, EditCreditNoteDto } from './dtos/CreditNote.dto'; import { CreateCreditNoteDto, EditCreditNoteDto } from './dtos/CreditNote.dto';
import { GetCreditNoteState } from './queries/GetCreditNoteState.service';
import { GetCreditNoteService } from './queries/GetCreditNote.service';
@Injectable() @Injectable()
export class CreditNoteApplication { export class CreditNoteApplication {
@@ -19,8 +17,6 @@ export class CreditNoteApplication {
private readonly deleteCreditNoteService: DeleteCreditNoteService, private readonly deleteCreditNoteService: DeleteCreditNoteService,
private readonly getCreditNotePdfService: GetCreditNotePdf, private readonly getCreditNotePdfService: GetCreditNotePdf,
private readonly getCreditNotesService: GetCreditNotesService, private readonly getCreditNotesService: GetCreditNotesService,
private readonly getCreditNoteStateService: GetCreditNoteState,
private readonly getCreditNoteService: GetCreditNoteService
) {} ) {}
/** /**
@@ -80,21 +76,4 @@ export class CreditNoteApplication {
getCreditNotes(creditNotesQuery: ICreditNotesQueryDTO) { getCreditNotes(creditNotesQuery: ICreditNotesQueryDTO) {
return this.getCreditNotesService.getCreditNotesList(creditNotesQuery); return this.getCreditNotesService.getCreditNotesList(creditNotesQuery);
} }
/**
* Retrieves the create/edit initial state of the credit note.
* @returns {Promise<ICreditNoteState>}
*/
getCreditNoteState() {
return this.getCreditNoteStateService.getCreditNoteState();
}
/**
* Retrieves the credit note.
* @param {number} creditNoteId
* @returns {Promise<CreditNote>}
*/
getCreditNote(creditNoteId: number) {
return this.getCreditNoteService.getCreditNote(creditNoteId);
}
} }

View File

@@ -1,4 +1,3 @@
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import { import {
Body, Body,
Controller, Controller,
@@ -11,6 +10,7 @@ import {
} 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';
import { ApiTags } from '@nestjs/swagger';
import { CreateCreditNoteDto, EditCreditNoteDto } from './dtos/CreditNote.dto'; import { CreateCreditNoteDto, EditCreditNoteDto } from './dtos/CreditNote.dto';
@Controller('credit-notes') @Controller('credit-notes')
@@ -22,42 +22,16 @@ export class CreditNotesController {
constructor(private creditNoteApplication: CreditNoteApplication) {} constructor(private creditNoteApplication: CreditNoteApplication) {}
@Post() @Post()
@ApiOperation({ summary: 'Create a new credit note' })
@ApiResponse({ status: 201, description: 'Credit note successfully created' })
@ApiResponse({ status: 400, description: 'Invalid input data' })
createCreditNote(@Body() creditNoteDTO: CreateCreditNoteDto) { createCreditNote(@Body() creditNoteDTO: CreateCreditNoteDto) {
return this.creditNoteApplication.createCreditNote(creditNoteDTO); return this.creditNoteApplication.createCreditNote(creditNoteDTO);
} }
@Get('state')
@ApiOperation({ summary: 'Get credit note state' })
@ApiResponse({ status: 200, description: 'Returns the credit note state' })
getCreditNoteState() {
return this.creditNoteApplication.getCreditNoteState();
}
@Get(':id')
@ApiOperation({ summary: 'Get a specific credit note by ID' })
@ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' })
@ApiResponse({ status: 200, description: 'Returns the credit note' })
@ApiResponse({ status: 404, description: 'Credit note not found' })
getCreditNote(@Param('id') creditNoteId: number) {
return this.creditNoteApplication.getCreditNote(creditNoteId);
}
@Get() @Get()
@ApiOperation({ summary: 'Get all credit notes' })
@ApiResponse({ status: 200, description: 'Returns a list of credit notes' })
getCreditNotes(@Query() creditNotesQuery: ICreditNotesQueryDTO) { getCreditNotes(@Query() creditNotesQuery: ICreditNotesQueryDTO) {
return this.creditNoteApplication.getCreditNotes(creditNotesQuery); return this.creditNoteApplication.getCreditNotes(creditNotesQuery);
} }
@Put(':id') @Put(':id')
@ApiOperation({ summary: 'Update a credit note' })
@ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' })
@ApiResponse({ status: 200, description: 'Credit note successfully updated' })
@ApiResponse({ status: 404, description: 'Credit note not found' })
@ApiResponse({ status: 400, description: 'Invalid input data' })
editCreditNote( editCreditNote(
@Param('id') creditNoteId: number, @Param('id') creditNoteId: number,
@Body() creditNoteDTO: EditCreditNoteDto, @Body() creditNoteDTO: EditCreditNoteDto,
@@ -69,19 +43,11 @@ export class CreditNotesController {
} }
@Put(':id/open') @Put(':id/open')
@ApiOperation({ summary: 'Open a credit note' })
@ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' })
@ApiResponse({ status: 200, description: 'Credit note successfully opened' })
@ApiResponse({ status: 404, description: 'Credit note not found' })
openCreditNote(@Param('id') creditNoteId: number) { openCreditNote(@Param('id') creditNoteId: number) {
return this.creditNoteApplication.openCreditNote(creditNoteId); return this.creditNoteApplication.openCreditNote(creditNoteId);
} }
@Delete(':id') @Delete(':id')
@ApiOperation({ summary: 'Delete a credit note' })
@ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' })
@ApiResponse({ status: 200, description: 'Credit note successfully deleted' })
@ApiResponse({ status: 404, description: 'Credit note not found' })
deleteCreditNote(@Param('id') creditNoteId: number) { deleteCreditNote(@Param('id') creditNoteId: number) {
return this.creditNoteApplication.deleteCreditNote(creditNoteId); return this.creditNoteApplication.deleteCreditNote(creditNoteId);
} }

View File

@@ -15,7 +15,7 @@ import { WarehousesModule } from '../Warehouses/Warehouses.module';
import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module'; import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module'; import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module';
import { TemplateInjectableModule } from '../TemplateInjectable/TemplateInjectable.module'; import { TemplateInjectableModule } from '../TemplateInjectable/TemplateInjectable.module';
import { GetCreditNoteService } from './queries/GetCreditNote.service'; import { GetCreditNote } from './queries/GetCreditNote.service';
import { CreditNoteBrandingTemplate } from './queries/CreditNoteBrandingTemplate.service'; import { CreditNoteBrandingTemplate } from './queries/CreditNoteBrandingTemplate.service';
import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module'; import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module';
import { CreditNoteGLEntries } from './commands/CreditNoteGLEntries'; import { CreditNoteGLEntries } from './commands/CreditNoteGLEntries';
@@ -52,7 +52,7 @@ import { CreditNotesApplyInvoiceModule } from '../CreditNotesApplyInvoice/Credit
], ],
providers: [ providers: [
CreateCreditNoteService, CreateCreditNoteService,
GetCreditNoteService, GetCreditNote,
CommandCreditNoteDTOTransform, CommandCreditNoteDTOTransform,
EditCreditNoteService, EditCreditNoteService,
OpenCreditNoteService, OpenCreditNoteService,
@@ -74,7 +74,7 @@ import { CreditNotesApplyInvoiceModule } from '../CreditNotesApplyInvoice/Credit
], ],
exports: [ exports: [
CreateCreditNoteService, CreateCreditNoteService,
GetCreditNoteService, GetCreditNote,
CommandCreditNoteDTOTransform, CommandCreditNoteDTOTransform,
EditCreditNoteService, EditCreditNoteService,
OpenCreditNoteService, OpenCreditNoteService,

View File

@@ -1,4 +1,3 @@
import { ToNumber } from '@/common/decorators/Validators';
import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto'; import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
@@ -6,10 +5,9 @@ import {
ArrayMinSize, ArrayMinSize,
IsArray, IsArray,
IsBoolean, IsBoolean,
IsDateString, IsDate,
IsEnum, IsEnum,
IsInt, IsInt,
IsNotEmpty,
IsNumber, IsNumber,
IsOptional, IsOptional,
IsPositive, IsPositive,
@@ -27,25 +25,21 @@ export class CreditNoteEntryDto extends ItemEntryDto {}
class AttachmentDto { class AttachmentDto {
@IsString() @IsString()
@IsNotEmpty()
key: string; key: string;
} }
export class CommandCreditNoteDto { export class CommandCreditNoteDto {
@ToNumber()
@IsInt() @IsInt()
@IsNotEmpty()
@ApiProperty({ example: 1, description: 'The customer ID' }) @ApiProperty({ example: 1, description: 'The customer ID' })
customerId: number; customerId: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsPositive() @IsPositive()
@ApiProperty({ example: 3.43, description: 'The exchange rate' }) @ApiProperty({ example: 3.43, description: 'The exchange rate' })
exchangeRate?: number; exchangeRate?: number;
@IsNotEmpty() @IsDate()
@IsDateString() @Type(() => Date)
@ApiProperty({ example: '2021-09-01', description: 'The credit note date' }) @ApiProperty({ example: '2021-09-01', description: 'The credit note date' })
creditNoteDate: Date; creditNoteDate: Date;
@@ -70,19 +64,26 @@ export class CommandCreditNoteDto {
termsConditions?: string; termsConditions?: string;
@IsBoolean() @IsBoolean()
@ApiProperty({ example: false, description: 'The credit note is open' }) @ApiProperty({
example: false,
description: 'The credit note is open',
})
open: boolean = false; open: boolean = false;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
@ApiProperty({ example: 1, description: 'The warehouse ID' }) @ApiProperty({
example: 1,
description: 'The warehouse ID',
})
warehouseId?: number; warehouseId?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
@ApiProperty({ example: 1, description: 'The branch ID' }) @ApiProperty({
example: 1,
description: 'The branch ID',
})
branchId?: number; branchId?: number;
@IsArray() @IsArray()
@@ -90,7 +91,14 @@ export class CommandCreditNoteDto {
@Type(() => CreditNoteEntryDto) @Type(() => CreditNoteEntryDto)
@ArrayMinSize(1) @ArrayMinSize(1)
@ApiProperty({ @ApiProperty({
example: [{ itemId: 1, quantity: 1, rate: 10, taxRateId: 1 }], example: [
{
itemId: 1,
quantity: 1,
rate: 10,
taxRateId: 1,
},
],
description: 'The credit note entries', description: 'The credit note entries',
}) })
entries: CreditNoteEntryDto[]; entries: CreditNoteEntryDto[];
@@ -102,15 +110,19 @@ export class CommandCreditNoteDto {
attachments?: AttachmentDto[]; attachments?: AttachmentDto[];
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
@ApiProperty({ example: 1, description: 'The pdf template ID' }) @ApiProperty({
example: 1,
description: 'The pdf template ID',
})
pdfTemplateId?: number; pdfTemplateId?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsNumber() @IsNumber()
@ApiProperty({ example: 10, description: 'The discount amount' }) @ApiProperty({
example: 10,
description: 'The discount amount',
})
discount?: number; discount?: number;
@IsOptional() @IsOptional()
@@ -123,7 +135,6 @@ export class CommandCreditNoteDto {
discountType?: DiscountType; discountType?: DiscountType;
@IsOptional() @IsOptional()
@ToNumber()
@IsNumber() @IsNumber()
adjustment?: number; adjustment?: number;
} }

View File

@@ -7,7 +7,7 @@ import { ServiceError } from '@/modules/Items/ServiceError';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() @Injectable()
export class GetCreditNoteService { export class GetCreditNote {
constructor( constructor(
private readonly transformer: TransformerInjectable, private readonly transformer: TransformerInjectable,

View File

@@ -1,5 +1,5 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { GetCreditNoteService } from './GetCreditNote.service'; import { GetCreditNote } from './GetCreditNote.service';
import { CreditNoteBrandingTemplate } from './CreditNoteBrandingTemplate.service'; import { CreditNoteBrandingTemplate } from './CreditNoteBrandingTemplate.service';
import { transformCreditNoteToPdfTemplate } from '../utils'; import { transformCreditNoteToPdfTemplate } from '../utils';
import { CreditNote } from '../models/CreditNote'; import { CreditNote } from '../models/CreditNote';
@@ -25,7 +25,7 @@ export class GetCreditNotePdf {
constructor( constructor(
private readonly chromiumlyTenancy: ChromiumlyTenancy, private readonly chromiumlyTenancy: ChromiumlyTenancy,
private readonly templateInjectable: TemplateInjectable, private readonly templateInjectable: TemplateInjectable,
private readonly getCreditNoteService: GetCreditNoteService, private readonly getCreditNoteService: GetCreditNote,
private readonly creditNoteBrandingTemplate: CreditNoteBrandingTemplate, private readonly creditNoteBrandingTemplate: CreditNoteBrandingTemplate,
private readonly eventPublisher: EventEmitter2, private readonly eventPublisher: EventEmitter2,

View File

@@ -1,39 +0,0 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { GetCreditNoteAssociatedAppliedInvoices } from './queries/GetCreditNoteAssociatedAppliedInvoices.service';
@Controller('credit-notes')
@ApiTags('credit-notes-apply-invoice')
export class CreditNotesApplyInvoiceController {
constructor(
private readonly getCreditNoteAssociatedAppliedInvoicesService: GetCreditNoteAssociatedAppliedInvoices,
) {}
@Get(':creditNoteId/applied-invoices')
@ApiOperation({ summary: 'Applied credit note to invoices' })
@ApiResponse({
status: 200,
description: 'Credit note successfully applied to invoices',
})
@ApiResponse({ status: 404, description: 'Credit note not found' })
@ApiResponse({ status: 400, description: 'Invalid input data' })
appliedCreditNoteToInvoices(@Param('creditNoteId') creditNoteId: number) {
return this.getCreditNoteAssociatedAppliedInvoicesService.getCreditAssociatedAppliedInvoices(
creditNoteId,
);
}
@Post(':creditNoteId/apply-invoices')
@ApiOperation({ summary: 'Apply credit note to invoices' })
@ApiResponse({
status: 200,
description: 'Credit note successfully applied to invoices',
})
@ApiResponse({ status: 404, description: 'Credit note not found' })
@ApiResponse({ status: 400, description: 'Invalid input data' })
applyCreditNoteToInvoices(@Param('creditNoteId') creditNoteId: number) {
return this.getCreditNoteAssociatedAppliedInvoicesService.getCreditAssociatedAppliedInvoices(
creditNoteId,
);
}
}

View File

@@ -8,7 +8,6 @@ import { PaymentsReceivedModule } from '../PaymentReceived/PaymentsReceived.modu
import { CreditNotesModule } from '../CreditNotes/CreditNotes.module'; import { CreditNotesModule } from '../CreditNotes/CreditNotes.module';
import { GetCreditNoteAssociatedAppliedInvoices } from './queries/GetCreditNoteAssociatedAppliedInvoices.service'; import { GetCreditNoteAssociatedAppliedInvoices } from './queries/GetCreditNoteAssociatedAppliedInvoices.service';
import { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteAssociatedInvoicesToApply.service'; import { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteAssociatedInvoicesToApply.service';
import { CreditNotesApplyInvoiceController } from './CreditNotesApplyInvoice.controller';
@Module({ @Module({
providers: [ providers: [
@@ -17,11 +16,12 @@ import { CreditNotesApplyInvoiceController } from './CreditNotesApplyInvoice.con
CreditNoteApplyToInvoices, CreditNoteApplyToInvoices,
CreditNoteApplySyncInvoicesCreditedAmount, CreditNoteApplySyncInvoicesCreditedAmount,
CreditNoteApplySyncCredit, CreditNoteApplySyncCredit,
GetCreditNoteAssociatedAppliedInvoices, // GetCreditNoteAssociatedAppliedInvoices,
GetCreditNoteAssociatedInvoicesToApply, // GetCreditNoteAssociatedInvoicesToApply
],
exports: [
DeleteCustomerLinkedCreditNoteService,
], ],
exports: [DeleteCustomerLinkedCreditNoteService],
imports: [PaymentsReceivedModule, forwardRef(() => CreditNotesModule)], imports: [PaymentsReceivedModule, forwardRef(() => CreditNotesModule)],
controllers: [CreditNotesApplyInvoiceController],
}) })
export class CreditNotesApplyInvoiceModule {} export class CreditNotesApplyInvoiceModule {}

View File

@@ -7,16 +7,9 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() @Injectable()
export class GetCreditNoteAssociatedAppliedInvoices { export class GetCreditNoteAssociatedAppliedInvoices {
/**
* @param {TransformerInjectable} transformer - The transformer service.
* @param {TenantModelProxy<typeof CreditNoteAppliedInvoice>} creditNoteAppliedInvoiceModel - The credit note applied invoice model.
* @param {TenantModelProxy<typeof CreditNote>} creditNoteModel - The credit note model.
*/
constructor( constructor(
private readonly transformer: TransformerInjectable, private readonly transformer: TransformerInjectable,
private readonly creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice,
@Inject(CreditNoteAppliedInvoice.name)
private readonly creditNoteAppliedInvoiceModel: TenantModelProxy<typeof CreditNoteAppliedInvoice>,
@Inject(CreditNote.name) @Inject(CreditNote.name)
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>, private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
@@ -36,7 +29,7 @@ export class GetCreditNoteAssociatedAppliedInvoices {
.findById(creditNoteId) .findById(creditNoteId)
.throwIfNotFound(); .throwIfNotFound();
const appliedToInvoices = await this.creditNoteAppliedInvoiceModel() const appliedToInvoices = await this.creditNoteAppliedInvoiceModel
.query() .query()
.where('credit_note_id', creditNoteId) .where('credit_note_id', creditNoteId)
.withGraphFetched('saleInvoice') .withGraphFetched('saleInvoice')

View File

@@ -1,7 +1,7 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { GetCreditNoteService } from '../../CreditNotes/queries/GetCreditNote.service'; import { GetCreditNote } from '../../CreditNotes/queries/GetCreditNote.service';
import { CreditNoteWithInvoicesToApplyTransformer } from './CreditNoteWithInvoicesToApplyTransformer'; import { CreditNoteWithInvoicesToApplyTransformer } from './CreditNoteWithInvoicesToApplyTransformer';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@@ -10,11 +10,11 @@ export class GetCreditNoteAssociatedInvoicesToApply {
/** /**
* @param {TransformerInjectable} transformer - Transformer service. * @param {TransformerInjectable} transformer - Transformer service.
* @param {GetCreditNote} getCreditNote - Get credit note service. * @param {GetCreditNote} getCreditNote - Get credit note service.
* @param {TenantModelProxy<typeof SaleInvoice>} saleInvoiceModel - Sale invoice model. * @param {typeof SaleInvoice} saleInvoiceModel - Sale invoice model.
*/ */
constructor( constructor(
private transformer: TransformerInjectable, private transformer: TransformerInjectable,
private getCreditNote: GetCreditNoteService, private getCreditNote: GetCreditNote,
@Inject(SaleInvoice.name) @Inject(SaleInvoice.name)
private saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>, private saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,

View File

@@ -37,15 +37,15 @@ export class CurrenciesController {
return this.currenciesApp.createCurrency(dto); return this.currenciesApp.createCurrency(dto);
} }
@Put(':id') @Put(':code')
@ApiOperation({ summary: 'Edit an existing currency' }) @ApiOperation({ summary: 'Edit an existing currency' })
@ApiParam({ name: 'id', type: Number, description: 'Currency ID' }) @ApiParam({ name: 'id', type: Number, description: 'Currency ID' })
@ApiBody({ type: EditCurrencyDto }) @ApiBody({ type: EditCurrencyDto })
@ApiOkResponse({ description: 'The currency has been successfully updated.' }) @ApiOkResponse({ description: 'The currency has been successfully updated.' })
@ApiNotFoundResponse({ description: 'Currency not found.' }) @ApiNotFoundResponse({ description: 'Currency not found.' })
@ApiBadRequestResponse({ description: 'Invalid input data.' }) @ApiBadRequestResponse({ description: 'Invalid input data.' })
edit(@Param('id') id: number, @Body() dto: EditCurrencyDto) { edit(@Param('code') code: string, @Body() dto: EditCurrencyDto) {
return this.currenciesApp.editCurrency(id, dto); return this.currenciesApp.editCurrency(code, dto);
} }
@Delete(':code') @Delete(':code')

View File

@@ -27,8 +27,8 @@ export class CurrenciesApplication {
/** /**
* Edits an existing currency. * Edits an existing currency.
*/ */
public editCurrency(currencyId: number, currencyDTO: EditCurrencyDto) { public editCurrency(currencyCode: string, currencyDTO: EditCurrencyDto) {
return this.editCurrencyService.editCurrency(currencyId, currencyDTO); return this.editCurrencyService.editCurrency(currencyCode, currencyDTO);
} }
/** /**

View File

@@ -12,22 +12,21 @@ export class EditCurrencyService {
/** /**
* Edit details of the given currency. * Edit details of the given currency.
* @param {number} currencyId - Currency ID. * @param {number} currencyCode - Currency code.
* @param {ICurrencyDTO} currencyDTO - Edit currency dto. * @param {ICurrencyDTO} currencyDTO - Edit currency dto.
*/ */
public async editCurrency( public async editCurrency(
currencyId: number, currencyCode: string,
currencyDTO: EditCurrencyDto, currencyDTO: EditCurrencyDto,
): Promise<Currency> { ): Promise<Currency> {
const foundCurrency = this.currencyModel() const foundCurrency = await this.currencyModel()
.query() .query()
.findById(currencyId) .findOne('currencyCode', currencyCode)
.throwIfNotFound(); .throwIfNotFound();
// Directly use the provided ID to update the currency
const currency = await this.currencyModel() const currency = await this.currencyModel()
.query() .query()
.patchAndFetchById(currencyId, { .patchAndFetchById(foundCurrency.id, {
...currencyDTO, ...currencyDTO,
}); });
return currency; return currency;

View File

@@ -1,16 +1,12 @@
import { IsNotEmpty } from "class-validator"; import { IsString } from 'class-validator';
import { IsString } from "class-validator";
export class CreateCurrencyDto { export class CreateCurrencyDto {
@IsString() @IsString()
@IsNotEmpty()
currencyName: string; currencyName: string;
@IsString() @IsString()
@IsNotEmpty()
currencyCode: string; currencyCode: string;
@IsString() @IsString()
@IsNotEmpty()
currencySign: string; currencySign: string;
} }

View File

@@ -1,12 +1,9 @@
import { IsNotEmpty } from "class-validator"; import { IsString } from 'class-validator';
import { IsString } from "class-validator";
export class EditCurrencyDto { export class EditCurrencyDto {
@IsString() @IsString()
@IsNotEmpty()
currencyName: string; currencyName: string;
@IsString() @IsString()
@IsNotEmpty()
currencySign: string; currencySign: string;
} }

View File

@@ -1,6 +1,6 @@
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { TenantModel } from "@/modules/System/models/TenantModel";
export class Currency extends TenantBaseModel { export class Currency extends TenantModel {
public readonly currencySign: string; public readonly currencySign: string;
public readonly currencyName: string; public readonly currencyName: string;
public readonly currencyCode: string; public readonly currencyCode: string;

View File

@@ -394,5 +394,7 @@ export abstract class DynamicFilterRoleAbstractor implements IDynamicFilter {
/** /**
* Retrieves the response meta. * Retrieves the response meta.
*/ */
getResponseMeta() {} getResponseMeta() {
throw new Error('Method not implemented.');
}
} }

View File

@@ -1,12 +1,10 @@
import { ToNumber } 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 {
IsArray, IsArray,
IsBoolean, IsBoolean,
IsDateString, IsDate,
IsInt, IsInt,
IsISO4217CurrencyCode,
IsNotEmpty, IsNotEmpty,
IsNumber, IsNumber,
IsOptional, IsOptional,
@@ -25,12 +23,10 @@ export class ExpenseCategoryDto {
@IsNotEmpty() @IsNotEmpty()
index: number; index: number;
@IsNotEmpty()
@ToNumber()
@IsInt() @IsInt()
@IsNotEmpty()
expenseAccountId: number; expenseAccountId: number;
@ToNumber()
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
amount?: number; amount?: number;
@@ -44,7 +40,6 @@ export class ExpenseCategoryDto {
@IsOptional() @IsOptional()
landedCost?: boolean; landedCost?: boolean;
@ToNumber()
@IsInt() @IsInt()
@IsOptional() @IsOptional()
projectId?: number; projectId?: number;
@@ -60,7 +55,7 @@ export class CommandExpenseDto {
}) })
referenceNo?: string; referenceNo?: string;
@IsDateString() @IsDate()
@IsNotEmpty() @IsNotEmpty()
@ApiProperty({ @ApiProperty({
description: 'The payment date of the expense', description: 'The payment date of the expense',
@@ -68,9 +63,8 @@ export class CommandExpenseDto {
}) })
paymentDate: Date; paymentDate: Date;
@IsNotEmpty()
@ToNumber()
@IsInt() @IsInt()
@IsNotEmpty()
@ApiProperty({ @ApiProperty({
description: 'The payment account id of the expense', description: 'The payment account id of the expense',
example: 1, example: 1,
@@ -86,22 +80,31 @@ export class CommandExpenseDto {
}) })
description?: string; description?: string;
@ToNumber()
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
@ApiProperty({ description: 'The exchange rate of the expense', example: 1 }) @ApiProperty({
description: 'The exchange rate of the expense',
example: 1,
})
exchangeRate?: number; exchangeRate?: number;
@IsString() @IsString()
@MaxLength(3) @MaxLength(3)
@IsOptional() @IsOptional()
@IsISO4217CurrencyCode()
@ApiProperty({ @ApiProperty({
description: 'The currency code of the expense', description: 'The currency code of the expense',
example: 'USD', example: 'USD',
}) })
currencyCode?: string; currencyCode?: string;
@IsNumber()
@IsOptional()
@ApiProperty({
description: 'The exchange rate of the expense',
example: 1,
})
exchange_rate?: number;
@IsBoolean() @IsBoolean()
@IsOptional() @IsOptional()
@ApiProperty({ @ApiProperty({
@@ -110,16 +113,14 @@ export class CommandExpenseDto {
}) })
publish?: boolean; publish?: boolean;
@IsOptional()
@ToNumber()
@IsInt() @IsInt()
@IsOptional()
@ApiProperty({ @ApiProperty({
description: 'The payee id of the expense', description: 'The payee id of the expense',
example: 1, example: 1,
}) })
payeeId?: number; payeeId?: number;
@ToNumber()
@IsInt() @IsInt()
@IsOptional() @IsOptional()
@ApiProperty({ @ApiProperty({

View File

@@ -24,7 +24,7 @@ export class FeaturesConfigure {
}, },
{ {
name: Features.BankSyncing, name: Features.BankSyncing,
defaultValue: this.configService.get('bankfeed.enabled') ?? false, defaultValue: this.configService.get('bankSync.enabled') ?? false,
}, },
]; ];
} }

View File

@@ -6,28 +6,23 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Injectable() @Injectable()
export class ImportFileMeta { export class ImportFileMeta {
/**
* @param {TransformerInjectable} transformer - Transformer injectable service.
* @param {TenancyContext} tenancyContext - Tenancy context service.
* @param {typeof ImportModel} importModel - Import model.
*/
constructor( constructor(
private readonly transformer: TransformerInjectable, private readonly transformer: TransformerInjectable,
private readonly tenancyContext: TenancyContext, private readonly tenancyContext: TenancyContext,
@Inject(ImportModel.name) @Inject(ImportModel.name)
private readonly importModel: typeof ImportModel, private readonly importModel: () => typeof ImportModel,
) {} ) {}
/** /**
* Retrieves the import meta of the given import model id. * Retrieves the import meta of the given import model id.
* @param {string} importId - Import id. * @param {number} importId
*/ */
async getImportMeta(importId: string) { async getImportMeta(importId: string) {
const tenant = await this.tenancyContext.getTenant(); const tenant = await this.tenancyContext.getTenant();
const tenantId = tenant.id; const tenantId = tenant.id;
const importFile = await this.importModel const importFile = await this.importModel()
.query() .query()
.where('tenantId', tenantId) .where('tenantId', tenantId)
.findOne('importId', importId); .findOne('importId', importId);

View File

@@ -74,10 +74,11 @@ export class EditItemCategoryService {
); );
// Creates item category under unit-of-work evnirement. // Creates item category under unit-of-work evnirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => { return this.uow.withTransaction(async (trx: Knex.Transaction) => {
const itemCategory = await this.itemCategoryModel() //
.query(trx) const itemCategory = await ItemCategory.query().patchAndFetchById(
.patchAndFetchById(itemCategoryId, { ...itemCategoryObj }); itemCategoryId,
{ ...itemCategoryObj },
);
// Triggers `onItemCategoryEdited` event. // Triggers `onItemCategoryEdited` event.
await this.eventEmitter.emitAsync(events.itemCategory.onEdited, { await this.eventEmitter.emitAsync(events.itemCategory.onEdited, {
oldItemCategory, oldItemCategory,

View File

@@ -1,7 +1,6 @@
import { IsOptional, ToNumber } from '@/common/decorators/Validators';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNumber } from 'class-validator'; import { IsNumber } from 'class-validator';
import { IsString } from 'class-validator'; import { IsOptional, IsString } from 'class-validator';
import { IsNotEmpty } from 'class-validator'; import { IsNotEmpty } from 'class-validator';
class CommandItemCategoryDto { class CommandItemCategoryDto {
@@ -18,19 +17,16 @@ class CommandItemCategoryDto {
}) })
description?: string; description?: string;
@ToNumber()
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
@ApiProperty({ example: 1, description: 'The cost account ID' }) @ApiProperty({ example: 1, description: 'The cost account ID' })
costAccountId?: number; costAccountId?: number;
@ToNumber()
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
@ApiProperty({ example: 1, description: 'The sell account ID' }) @ApiProperty({ example: 1, description: 'The sell account ID' })
sellAccountId?: number; sellAccountId?: number;
@ToNumber()
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
@ApiProperty({ example: 1, description: 'The inventory account ID' }) @ApiProperty({ example: 1, description: 'The inventory account ID' })

View File

@@ -1,6 +1,7 @@
import { import {
IsString, IsString,
IsIn, IsIn,
IsOptional,
IsBoolean, IsBoolean,
IsNumber, IsNumber,
IsInt, IsInt,
@@ -8,10 +9,11 @@ import {
ValidateIf, ValidateIf,
MaxLength, MaxLength,
Min, Min,
Max,
IsNotEmpty, IsNotEmpty,
} from 'class-validator'; } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, ToNumber } from '@/common/decorators/Validators';
export class CommandItemDto { export class CommandItemDto {
@IsString() @IsString()
@@ -21,7 +23,6 @@ export class CommandItemDto {
name: string; name: string;
@IsString() @IsString()
@IsNotEmpty()
@IsIn(['service', 'non-inventory', 'inventory']) @IsIn(['service', 'non-inventory', 'inventory'])
@ApiProperty({ @ApiProperty({
description: 'Item type', description: 'Item type',
@@ -51,7 +52,6 @@ export class CommandItemDto {
purchasable?: boolean; purchasable?: boolean;
@IsOptional() @IsOptional()
@ToNumber()
@IsNumber({ maxDecimalPlaces: 3 }) @IsNumber({ maxDecimalPlaces: 3 })
@Min(0) @Min(0)
@ValidateIf((o) => o.purchasable === true) @ValidateIf((o) => o.purchasable === true)
@@ -64,7 +64,6 @@ export class CommandItemDto {
costPrice?: number; costPrice?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
@Min(0) @Min(0)
@ValidateIf((o) => o.purchasable === true) @ValidateIf((o) => o.purchasable === true)
@@ -87,7 +86,6 @@ export class CommandItemDto {
sellable?: boolean; sellable?: boolean;
@IsOptional() @IsOptional()
@ToNumber()
@IsNumber({ maxDecimalPlaces: 3 }) @IsNumber({ maxDecimalPlaces: 3 })
@Min(0) @Min(0)
@ValidateIf((o) => o.sellable === true) @ValidateIf((o) => o.sellable === true)
@@ -100,7 +98,6 @@ export class CommandItemDto {
sellPrice?: number; sellPrice?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
@Min(0) @Min(0)
@ValidateIf((o) => o.sellable === true) @ValidateIf((o) => o.sellable === true)
@@ -113,7 +110,6 @@ export class CommandItemDto {
sellAccountId?: number; sellAccountId?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
@Min(0) @Min(0)
@ValidateIf((o) => o.type === 'inventory') @ValidateIf((o) => o.type === 'inventory')
@@ -144,7 +140,6 @@ export class CommandItemDto {
purchaseDescription?: string; purchaseDescription?: string;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
@ApiProperty({ @ApiProperty({
description: 'ID of the tax rate applied to sales', description: 'ID of the tax rate applied to sales',
@@ -154,7 +149,6 @@ export class CommandItemDto {
sellTaxRateId?: number; sellTaxRateId?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
@ApiProperty({ @ApiProperty({
description: 'ID of the tax rate applied to purchases', description: 'ID of the tax rate applied to purchases',
@@ -164,7 +158,6 @@ export class CommandItemDto {
purchaseTaxRateId?: number; purchaseTaxRateId?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
@Min(0) @Min(0)
@ApiProperty({ @ApiProperty({
@@ -196,6 +189,7 @@ export class CommandItemDto {
@IsOptional() @IsOptional()
@IsArray() @IsArray()
@Type(() => Number)
@IsInt({ each: true }) @IsInt({ each: true })
@ApiProperty({ @ApiProperty({
description: 'IDs of media files associated with the item', description: 'IDs of media files associated with the item',

View File

@@ -5,12 +5,10 @@ import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.dec
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator'; import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
import { ItemMeta } from './Item.meta'; import { ItemMeta } from './Item.meta';
import { ImportableModel } from '@/modules/Import/decorators/Import.decorator'; import { ImportableModel } from '@/modules/Import/decorators/Import.decorator';
import { PreventMutateBaseCurrency } from '@/common/decorators/LockMutateBaseCurrency.decorator';
@ExportableModel() @ExportableModel()
@ImportableModel() @ImportableModel()
@InjectModelMeta(ItemMeta) @InjectModelMeta(ItemMeta)
@PreventMutateBaseCurrency()
export class Item extends TenantBaseModel { export class Item extends TenantBaseModel {
public readonly quantityOnHand: number; public readonly quantityOnHand: number;
public readonly name: string; public readonly name: string;

View File

@@ -108,7 +108,11 @@ export class ManualJournalsController {
description: 'The manual journal details have been successfully retrieved.', description: 'The manual journal details have been successfully retrieved.',
}) })
@ApiResponse({ status: 404, description: 'The manual journal not found.' }) @ApiResponse({ status: 404, description: 'The manual journal not found.' })
public getManualJournals(@Query() filterDto: Partial<IManualJournalsFilter>) { public getManualJournals(
@Query() filterDto: Partial<IManualJournalsFilter>
) {
return this.manualJournalsApplication.getManualJournals(filterDto); return this.manualJournalsApplication.getManualJournals(filterDto);
} }
} }

View File

@@ -1,13 +1,10 @@
import { ToNumber } from '@/common/decorators/Validators';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { import {
IsArray, IsArray,
IsBoolean, IsBoolean,
IsDate, IsDate,
IsDateString,
IsInt, IsInt,
IsNotEmpty,
IsNumber, IsNumber,
IsOptional, IsOptional,
IsPositive, IsPositive,
@@ -23,21 +20,18 @@ export class ManualJournalEntryDto {
index: number; index: number;
@ApiPropertyOptional({ description: 'Credit amount' }) @ApiPropertyOptional({ description: 'Credit amount' })
@ToNumber()
@IsOptional() @IsOptional()
@IsNumber() @IsNumber()
@Min(0) @Min(0)
credit?: number; credit?: number;
@ApiPropertyOptional({ description: 'Debit amount' }) @ApiPropertyOptional({ description: 'Debit amount' })
@ToNumber()
@IsOptional() @IsOptional()
@IsNumber() @IsNumber()
@Min(0) @Min(0)
debit?: number; debit?: number;
@ApiProperty({ description: 'Account ID' }) @ApiProperty({ description: 'Account ID' })
@IsNotEmpty()
@IsInt() @IsInt()
accountId: number; accountId: number;
@@ -47,19 +41,16 @@ export class ManualJournalEntryDto {
note?: string; note?: string;
@ApiPropertyOptional({ description: 'Contact ID' }) @ApiPropertyOptional({ description: 'Contact ID' })
@ToNumber()
@IsInt()
@IsOptional() @IsOptional()
@IsInt()
contactId?: number; contactId?: number;
@ApiPropertyOptional({ description: 'Branch ID' }) @ApiPropertyOptional({ description: 'Branch ID' })
@ToNumber()
@IsOptional() @IsOptional()
@IsInt() @IsInt()
branchId?: number; branchId?: number;
@ApiPropertyOptional({ description: 'Project ID' }) @ApiPropertyOptional({ description: 'Project ID' })
@ToNumber()
@IsOptional() @IsOptional()
@IsInt() @IsInt()
projectId?: number; projectId?: number;
@@ -73,7 +64,8 @@ class AttachmentDto {
export class CommandManualJournalDto { export class CommandManualJournalDto {
@ApiProperty({ description: 'Journal date' }) @ApiProperty({ description: 'Journal date' })
@IsDateString() @IsDate()
@Type(() => Date)
date: Date; date: Date;
@ApiPropertyOptional({ description: 'Currency code' }) @ApiPropertyOptional({ description: 'Currency code' })
@@ -82,7 +74,6 @@ export class CommandManualJournalDto {
currencyCode?: string; currencyCode?: string;
@ApiPropertyOptional({ description: 'Exchange rate' }) @ApiPropertyOptional({ description: 'Exchange rate' })
@ToNumber()
@IsOptional() @IsOptional()
@IsNumber() @IsNumber()
@IsPositive() @IsPositive()
@@ -112,7 +103,6 @@ export class CommandManualJournalDto {
description?: string; description?: string;
@ApiPropertyOptional({ description: 'Branch ID' }) @ApiPropertyOptional({ description: 'Branch ID' })
@ToNumber()
@IsOptional() @IsOptional()
@IsInt() @IsInt()
branchId?: number; branchId?: number;

View File

@@ -11,9 +11,13 @@ import {
Put, Put,
Get, Get,
Body, Body,
Req,
Res,
Next,
HttpCode, HttpCode,
Param, Param,
} from '@nestjs/common'; } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { BuildOrganizationService } from './commands/BuildOrganization.service'; import { BuildOrganizationService } from './commands/BuildOrganization.service';
import { import {
BuildOrganizationDto, BuildOrganizationDto,
@@ -84,7 +88,7 @@ export class OrganizationController {
const abilities = const abilities =
await this.orgBaseCurrencyLockingService.baseCurrencyMutateLocks(); await this.orgBaseCurrencyLockingService.baseCurrencyMutateLocks();
return { abilities }; return res.status(200).send({ abilities });
} }
@Put() @Put()

View File

@@ -1,7 +1,6 @@
import { isEmpty } from 'lodash'; // @ts-nocheck
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { getPreventMutateBaseCurrencyModels } from '@/common/decorators/LockMutateBaseCurrency.decorator'; import { isEmpty } from 'lodash';
import { ModuleRef } from '@nestjs/core';
interface MutateBaseCurrencyLockMeta { interface MutateBaseCurrencyLockMeta {
modelName: string; modelName: string;
@@ -10,27 +9,26 @@ interface MutateBaseCurrencyLockMeta {
@Injectable() @Injectable()
export class OrganizationBaseCurrencyLocking { export class OrganizationBaseCurrencyLocking {
constructor(private readonly moduleRef: ModuleRef) {}
/** /**
* Retrieves the tenant models that have prevented mutation base currency. * Retrieves the tenant models that have prevented mutation base currency.
*/ */
private getModelsPreventsMutate() { private getModelsPreventsMutate = (tenantId: number) => {
const lockedModels = getPreventMutateBaseCurrencyModels(); const Models = this.tenancy.models(tenantId);
const filteredEntries = Array.from(lockedModels).filter( const filteredEntries = Object.entries(Models).filter(
([key, Model]) => !!Model.preventMutateBaseCurrency, ([key, Model]) => !!Model.preventMutateBaseCurrency,
); );
return Object.fromEntries(filteredEntries); return Object.fromEntries(filteredEntries);
} };
/** /**
* Detarmines the mutation base currency model is locked. * Detarmines the mutation base currency model is locked.
* @param {Model} Model
* @returns {Promise<MutateBaseCurrencyLockMeta | false>} * @returns {Promise<MutateBaseCurrencyLockMeta | false>}
*/ */
private async isModelMutateLocked( private isModelMutateLocked = async (
Model, Model,
): Promise<MutateBaseCurrencyLockMeta | false> { ): Promise<MutateBaseCurrencyLockMeta | false> => {
const validateQuery = Model.query(); const validateQuery = Model.query();
if (typeof Model?.modifiers?.preventMutateBaseCurrency !== 'undefined') { if (typeof Model?.modifiers?.preventMutateBaseCurrency !== 'undefined') {
@@ -47,24 +45,20 @@ export class OrganizationBaseCurrencyLocking {
pluralName: Model.pluralName, pluralName: Model.pluralName,
} }
: false; : false;
} };
/** /**
* Retrieves the base currency mutation locks of the tenant models. * Retrieves the base currency mutation locks of the tenant models.
* @param {number} tenantId
* @returns {Promise<MutateBaseCurrencyLockMeta[]>} * @returns {Promise<MutateBaseCurrencyLockMeta[]>}
*/ */
public async baseCurrencyMutateLocks(): Promise< public async baseCurrencyMutateLocks(
MutateBaseCurrencyLockMeta[] ): Promise<MutateBaseCurrencyLockMeta[]> {
> { const PreventedModels = this.getModelsPreventsMutate(tenantId);
const PreventedModels = this.getModelsPreventsMutate();
const opers = Object.entries(PreventedModels).map(([ModelName, Model]) => {
const InjectedModelProxy = this.moduleRef.get(ModelName, {
strict: false,
});
const InjectedModel = InjectedModelProxy();
return this.isModelMutateLocked(InjectedModel); const opers = Object.entries(PreventedModels).map(([ModelName, Model]) =>
}); this.isModelMutateLocked(Model),
);
const results = await Promise.all(opers); const results = await Promise.all(opers);
return results.filter( return results.filter(
@@ -74,11 +68,12 @@ export class OrganizationBaseCurrencyLocking {
/** /**
* Detarmines the base currency mutation locked. * Detarmines the base currency mutation locked.
* @param {number} tenantId
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
public async isBaseCurrencyMutateLocked() { public isBaseCurrencyMutateLocked = async (tenantId: number) => {
const locks = await this.baseCurrencyMutateLocks(); const locks = await this.baseCurrencyMutateLocks(tenantId);
return !isEmpty(locks); return !isEmpty(locks);
} };
} }

View File

@@ -0,0 +1,102 @@
import { Inject, Service } from 'typedi';
import { ObjectId } from 'mongodb';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { SeedMigration } from '@/lib/Seeder/SeedMigration';
import { Tenant } from '@/system/models';
import { ServiceError } from '@/exceptions';
import TenantDBManager from '@/services/Tenancy/TenantDBManager';
import config from '../../config';
import { ERRORS } from './constants';
import OrganizationService from './OrganizationService';
import TenantsManagerService from '@/services/Tenancy/TenantsManager';
@Service()
export default class OrganizationUpgrade {
@Inject()
private organizationService: OrganizationService;
@Inject()
private tenantsManager: TenantsManagerService;
@Inject('agenda')
private agenda: any;
/**
* Upgrades the given organization database.
* @param {number} tenantId - Tenant id.
* @returns {Promise<void>}
*/
public upgradeJob = async (tenantId: number): Promise<void> => {
const tenant = await Tenant.query()
.findById(tenantId)
.withGraphFetched('metadata');
// Validate tenant version.
this.validateTenantVersion(tenant);
// Initialize the tenant.
const seedContext = this.tenantsManager.getSeedMigrationContext(tenant);
// Database manager.
const dbManager = new TenantDBManager();
// Migrate the organization database schema.
await dbManager.migrate(tenant);
// Seeds the organization database data.
await new SeedMigration(seedContext.knex, seedContext).latest();
// Update the organization database version.
await this.organizationService.flagTenantDBBatch(tenantId);
// Remove the tenant job id.
await Tenant.markAsUpgraded(tenantId);
};
/**
* Running organization upgrade job.
* @param {number} tenantId - Tenant id.
* @return {Promise<void>}
*/
public upgrade = async (tenantId: number): Promise<{ jobId: string }> => {
const tenant = await Tenant.query().findById(tenantId);
// Validate tenant version.
this.validateTenantVersion(tenant);
// Validate tenant upgrade is not running.
this.validateTenantUpgradeNotRunning(tenant);
// Send welcome mail to the user.
const jobMeta = await this.agenda.now('organization-upgrade', {
tenantId,
});
// Transformes the mangodb id to string.
const jobId = new ObjectId(jobMeta.attrs._id).toString();
// Markes the tenant as currently building.
await Tenant.markAsUpgrading(tenantId, jobId);
return { jobId };
};
/**
* Validates the given tenant version.
* @param {ITenant} tenant
*/
private validateTenantVersion(tenant) {
if (tenant.databaseBatch >= config.databaseBatch) {
throw new ServiceError(ERRORS.TENANT_DATABASE_UPGRADED);
}
}
/**
* Validates the given tenant upgrade is not running.
* @param tenant
*/
private validateTenantUpgradeNotRunning(tenant) {
if (tenant.isUpgradeRunning) {
throw new ServiceError(ERRORS.TENANT_UPGRADE_IS_RUNNING);
}
}
}

View File

@@ -23,7 +23,9 @@ export class CommandOrganizationValidators {
) { ) {
if (tenant.isReady && newBaseCurrency !== oldBaseCurrency) { if (tenant.isReady && newBaseCurrency !== oldBaseCurrency) {
const isLocked = const isLocked =
await this.baseCurrencyMutateLocking.isBaseCurrencyMutateLocked(); await this.baseCurrencyMutateLocking.isBaseCurrencyMutateLocked(
tenant.id,
);
if (isLocked) { if (isLocked) {
throw new ServiceError(ERRORS.BASE_CURRENCY_MUTATE_LOCKED); throw new ServiceError(ERRORS.BASE_CURRENCY_MUTATE_LOCKED);

View File

@@ -18,7 +18,6 @@ import {
CreatePaymentReceivedDto, CreatePaymentReceivedDto,
EditPaymentReceivedDto, EditPaymentReceivedDto,
} from './dtos/PaymentReceived.dto'; } from './dtos/PaymentReceived.dto';
import { PaymentsReceivedPagesService } from './queries/PaymentsReceivedPages.service';
@Injectable() @Injectable()
export class PaymentReceivesApplication { export class PaymentReceivesApplication {
@@ -32,7 +31,6 @@ export class PaymentReceivesApplication {
private sendPaymentReceiveMailNotification: SendPaymentReceiveMailNotification, private sendPaymentReceiveMailNotification: SendPaymentReceiveMailNotification,
private getPaymentReceivePdfService: GetPaymentReceivedPdfService, private getPaymentReceivePdfService: GetPaymentReceivedPdfService,
private getPaymentReceivedStateService: GetPaymentReceivedStateService, private getPaymentReceivedStateService: GetPaymentReceivedStateService,
private paymentsReceivedPagesService: PaymentsReceivedPagesService,
) {} ) {}
/** /**
@@ -149,14 +147,4 @@ export class PaymentReceivesApplication {
public getPaymentReceivedState() { public getPaymentReceivedState() {
return this.getPaymentReceivedStateService.getPaymentReceivedState(); return this.getPaymentReceivedStateService.getPaymentReceivedState();
} }
/**
* Retrieve the payment received edit page.
* @param {number} paymentReceiveId - Payment receive id.
*/
public getPaymentReceivedEditPage(paymentReceiveId: number) {
return this.paymentsReceivedPagesService.getPaymentReceiveEditPage(
paymentReceiveId,
);
}
} }

View File

@@ -4,7 +4,6 @@ import {
Controller, Controller,
Delete, Delete,
Get, Get,
Headers,
HttpCode, HttpCode,
Param, Param,
ParseIntPipe, ParseIntPipe,
@@ -14,14 +13,11 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { PaymentReceivesApplication } from './PaymentReceived.application'; import { PaymentReceivesApplication } from './PaymentReceived.application';
import { import {
IPaymentReceivedCreateDTO,
IPaymentReceivedEditDTO,
IPaymentsReceivedFilter, IPaymentsReceivedFilter,
PaymentReceiveMailOptsDTO, PaymentReceiveMailOptsDTO,
} from './types/PaymentReceived.types'; } from './types/PaymentReceived.types';
import {
CreatePaymentReceivedDto,
EditPaymentReceivedDto,
} from './dtos/PaymentReceived.dto';
import { AcceptType } from '@/constants/accept-type';
@Controller('payments-received') @Controller('payments-received')
@ApiTags('payments-received') @ApiTags('payments-received')
@@ -44,20 +40,6 @@ export class PaymentReceivesController {
); );
} }
@Get(':id/edit-page')
@ApiResponse({
status: 200,
description:
'The payment received edit page has been successfully retrieved.',
})
public getPaymentReceiveEditPage(
@Param('id', ParseIntPipe) paymentReceiveId: number,
) {
return this.paymentReceivesApplication.getPaymentReceivedEditPage(
paymentReceiveId,
);
}
@Get(':id/mail') @Get(':id/mail')
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@@ -75,7 +57,7 @@ export class PaymentReceivesController {
@Post() @Post()
@ApiOperation({ summary: 'Create a new payment received.' }) @ApiOperation({ summary: 'Create a new payment received.' })
public createPaymentReceived( public createPaymentReceived(
@Body() paymentReceiveDTO: CreatePaymentReceivedDto, @Body() paymentReceiveDTO: IPaymentReceivedCreateDTO,
) { ) {
return this.paymentReceivesApplication.createPaymentReceived( return this.paymentReceivesApplication.createPaymentReceived(
paymentReceiveDTO, paymentReceiveDTO,
@@ -86,7 +68,7 @@ export class PaymentReceivesController {
@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,
@Body() paymentReceiveDTO: EditPaymentReceivedDto, @Body() paymentReceiveDTO: IPaymentReceivedEditDTO,
) { ) {
return this.paymentReceivesApplication.editPaymentReceive( return this.paymentReceivesApplication.editPaymentReceive(
paymentReceiveId, paymentReceiveId,
@@ -106,9 +88,7 @@ export class PaymentReceivesController {
@Get() @Get()
@ApiOperation({ summary: 'Retrieves the payment received list.' }) @ApiOperation({ summary: 'Retrieves the payment received list.' })
public getPaymentsReceived( public getPaymentsReceived(@Query() filterDTO: Partial<IPaymentsReceivedFilter>) {
@Query() filterDTO: Partial<IPaymentsReceivedFilter>,
) {
return this.paymentReceivesApplication.getPaymentsReceived(filterDTO); return this.paymentReceivesApplication.getPaymentsReceived(filterDTO);
} }
@@ -146,16 +126,21 @@ export class PaymentReceivesController {
}) })
public getPaymentReceive( public getPaymentReceive(
@Param('id', ParseIntPipe) paymentReceiveId: number, @Param('id', ParseIntPipe) paymentReceiveId: number,
@Headers('accept') acceptHeader: string,
) { ) {
if (acceptHeader.includes(AcceptType.ApplicationPdf)) { return this.paymentReceivesApplication.getPaymentReceive(paymentReceiveId);
return this.paymentReceivesApplication.getPaymentReceivePdf( }
paymentReceiveId,
); @Get(':id/pdf')
} else { @ApiOperation({ summary: 'Retrieves the payment received pdf.' })
return this.paymentReceivesApplication.getPaymentReceive( @ApiResponse({
paymentReceiveId, status: 200,
); description: 'The payment received pdf has been successfully retrieved.',
} })
public getPaymentReceivePdf(
@Param('id', ParseIntPipe) paymentReceivedId: number,
) {
return this.paymentReceivesApplication.getPaymentReceivePdf(
paymentReceivedId,
);
} }
} }

View File

@@ -36,7 +36,6 @@ import { SendPaymentReceivedMailProcessor } from './processors/PaymentReceivedMa
import { SEND_PAYMENT_RECEIVED_MAIL_QUEUE } from './constants'; import { SEND_PAYMENT_RECEIVED_MAIL_QUEUE } from './constants';
import { PaymentsReceivedExportable } from './commands/PaymentsReceivedExportable'; import { PaymentsReceivedExportable } from './commands/PaymentsReceivedExportable';
import { PaymentsReceivedImportable } from './commands/PaymentsReceivedImportable'; import { PaymentsReceivedImportable } from './commands/PaymentsReceivedImportable';
import { PaymentsReceivedPagesService } from './queries/PaymentsReceivedPages.service';
@Module({ @Module({
controllers: [PaymentReceivesController], controllers: [PaymentReceivesController],
@@ -64,7 +63,6 @@ import { PaymentsReceivedPagesService } from './queries/PaymentsReceivedPages.se
SendPaymentReceivedMailProcessor, SendPaymentReceivedMailProcessor,
PaymentsReceivedExportable, PaymentsReceivedExportable,
PaymentsReceivedImportable, PaymentsReceivedImportable,
PaymentsReceivedPagesService
], ],
exports: [ exports: [
PaymentReceivesApplication, PaymentReceivesApplication,

View File

@@ -1,47 +1,32 @@
import { AttachmentLinkDto } from '@/modules/Attachments/dtos/Attachment.dto';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { import { IsArray, IsNotEmpty, ValidateNested } from 'class-validator';
IsString, import { IsString } from 'class-validator';
IsDateString, import { IsDateString, IsNumber, IsOptional } from 'class-validator';
IsNumber, import { IsInt } from 'class-validator';
IsOptional,
IsArray,
IsNotEmpty,
IsInt,
ValidateNested,
} from 'class-validator';
import { ToNumber } from '@/common/decorators/Validators';
import { AttachmentLinkDto } from '@/modules/Attachments/dtos/Attachment.dto';
export class PaymentReceivedEntryDto { export class PaymentReceivedEntryDto {
@ToNumber()
@IsOptional() @IsOptional()
@IsInt() @IsInt()
id?: number; id?: number;
@ToNumber()
@IsInt()
@IsOptional() @IsOptional()
@IsInt()
index?: number; index?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
paymentReceiveId?: number; paymentReceiveId?: number;
@ToNumber()
@IsInt() @IsInt()
@IsNotEmpty()
invoiceId: number; invoiceId: number;
@ToNumber() @IsNumber()
@IsInt()
@IsNotEmpty()
paymentAmount: number; paymentAmount: number;
} }
export class CommandPaymentReceivedDto { export class CommandPaymentReceivedDto {
@ToNumber()
@IsInt() @IsInt()
@IsNotEmpty() @IsNotEmpty()
@ApiProperty({ description: 'The id of the customer', example: 1 }) @ApiProperty({ description: 'The id of the customer', example: 1 })
@@ -55,7 +40,6 @@ export class CommandPaymentReceivedDto {
paymentDate: Date | string; paymentDate: Date | string;
@IsOptional() @IsOptional()
@ToNumber()
@IsNumber() @IsNumber()
@ApiProperty({ @ApiProperty({
description: 'The amount of the payment received', description: 'The amount of the payment received',
@@ -64,7 +48,6 @@ export class CommandPaymentReceivedDto {
amount?: number; amount?: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsNumber() @IsNumber()
@ApiProperty({ @ApiProperty({
description: 'The exchange rate of the payment received', description: 'The exchange rate of the payment received',
@@ -80,7 +63,6 @@ export class CommandPaymentReceivedDto {
}) })
referenceNo?: string; referenceNo?: string;
@ToNumber()
@IsInt() @IsInt()
@IsNotEmpty() @IsNotEmpty()
@ApiProperty({ @ApiProperty({
@@ -90,7 +72,6 @@ export class CommandPaymentReceivedDto {
depositAccountId: number; depositAccountId: number;
@IsOptional() @IsOptional()
@ToNumber()
@IsString() @IsString()
@ApiProperty({ @ApiProperty({
description: 'The payment receive number of the payment received', description: 'The payment receive number of the payment received',
@@ -116,7 +97,6 @@ export class CommandPaymentReceivedDto {
entries: PaymentReceivedEntryDto[]; entries: PaymentReceivedEntryDto[];
@IsOptional() @IsOptional()
@ToNumber()
@IsInt() @IsInt()
@ApiProperty({ @ApiProperty({
description: 'The id of the branch', description: 'The id of the branch',

View File

@@ -41,10 +41,11 @@ export class PaymentsReceivedPagesService {
/** /**
* Retrieve payment receive new page receivable entries. * Retrieve payment receive new page receivable entries.
* @param {number} tenantId - Tenant id.
* @param {number} vendorId - Vendor id. * @param {number} vendorId - Vendor id.
* @return {IPaymentReceivePageEntry[]} * @return {IPaymentReceivePageEntry[]}
*/ */
public async getNewPageEntries(customerId: number) { public async getNewPageEntries(tenantId: number, customerId: number) {
// Retrieve due invoices. // Retrieve due invoices.
const entries = await this.saleInvoice() const entries = await this.saleInvoice()
.query() .query()
@@ -61,7 +62,10 @@ export class PaymentsReceivedPagesService {
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @param {Integer} paymentReceiveId - Payment receive id. * @param {Integer} paymentReceiveId - Payment receive id.
*/ */
public async getPaymentReceiveEditPage(paymentReceiveId: number): Promise<{ public async getPaymentReceiveEditPage(
tenantId: number,
paymentReceiveId: number,
): Promise<{
paymentReceive: Omit<PaymentReceived, 'entries'>; paymentReceive: Omit<PaymentReceived, 'entries'>;
entries: IPaymentReceivePageEntry[]; entries: IPaymentReceivePageEntry[];
}> { }> {

View File

@@ -29,7 +29,7 @@ export class PdfTemplateApplication {
* @param {ICreateInvoicePdfTemplateDTO} invoiceTemplateDTO - The data transfer object containing the details for the new PDF template. * @param {ICreateInvoicePdfTemplateDTO} invoiceTemplateDTO - The data transfer object containing the details for the new PDF template.
* @returns {Promise<any>} * @returns {Promise<any>}
*/ */
public createPdfTemplate( public async createPdfTemplate(
templateName: string, templateName: string,
resource: string, resource: string,
invoiceTemplateDTO: ICreateInvoicePdfTemplateDTO, invoiceTemplateDTO: ICreateInvoicePdfTemplateDTO,
@@ -45,7 +45,7 @@ export class PdfTemplateApplication {
* Deletes a PDF template. * Deletes a PDF template.
* @param {number} templateId - The ID of the template to delete. * @param {number} templateId - The ID of the template to delete.
*/ */
public deletePdfTemplate(templateId: number) { public async deletePdfTemplate(templateId: number) {
return this.deletePdfTemplateService.deletePdfTemplate(templateId); return this.deletePdfTemplateService.deletePdfTemplate(templateId);
} }
@@ -53,7 +53,7 @@ export class PdfTemplateApplication {
* Retrieves a specific PDF template. * Retrieves a specific PDF template.
* @param {number} templateId - The ID of the template to retrieve. * @param {number} templateId - The ID of the template to retrieve.
*/ */
public getPdfTemplate(templateId: number) { public async getPdfTemplate(templateId: number) {
return this.getPdfTemplateService.getPdfTemplate(templateId); return this.getPdfTemplateService.getPdfTemplate(templateId);
} }
@@ -61,7 +61,7 @@ export class PdfTemplateApplication {
* Retrieves all PDF templates. * Retrieves all PDF templates.
* @param {string} resource - The resource type to filter templates. * @param {string} resource - The resource type to filter templates.
*/ */
public getPdfTemplates(query?: { resource?: string }) { public async getPdfTemplates(query?: { resource?: string }) {
return this.getPdfTemplatesService.getPdfTemplates(query); return this.getPdfTemplatesService.getPdfTemplates(query);
} }
@@ -70,7 +70,7 @@ export class PdfTemplateApplication {
* @param {number} templateId - The ID of the template to edit. * @param {number} templateId - The ID of the template to edit.
* @param {IEditPdfTemplateDTO} editDTO - The data transfer object containing the updates. * @param {IEditPdfTemplateDTO} editDTO - The data transfer object containing the updates.
*/ */
public editPdfTemplate( public async editPdfTemplate(
templateId: number, templateId: number,
editDTO: IEditPdfTemplateDTO, editDTO: IEditPdfTemplateDTO,
) { ) {
@@ -80,7 +80,7 @@ export class PdfTemplateApplication {
/** /**
* Gets the PDF template branding state. * Gets the PDF template branding state.
*/ */
public getPdfTemplateBrandingState() { public async getPdfTemplateBrandingState() {
return this.getPdfTemplateBrandingStateService.execute(); return this.getPdfTemplateBrandingStateService.execute();
} }
@@ -89,7 +89,7 @@ export class PdfTemplateApplication {
* @param {number} templateId - The ID of the PDF template to assign as default. * @param {number} templateId - The ID of the PDF template to assign as default.
* @returns {Promise<any>} * @returns {Promise<any>}
*/ */
public assignPdfTemplateAsDefault(templateId: number) { public async assignPdfTemplateAsDefault(templateId: number) {
return this.assignPdfTemplateDefaultService.assignDefaultTemplate( return this.assignPdfTemplateDefaultService.assignDefaultTemplate(
templateId, templateId,
); );
@@ -99,7 +99,7 @@ export class PdfTemplateApplication {
* Retrieves the organization branding attributes. * Retrieves the organization branding attributes.
* @returns {Promise<CommonOrganizationBrandingAttributes>} The organization branding attributes. * @returns {Promise<CommonOrganizationBrandingAttributes>} The organization branding attributes.
*/ */
public getOrganizationBrandingAttributes() { getOrganizationBrandingAttributes() {
return this.getOrganizationBrandingAttributesService.execute(); return this.getOrganizationBrandingAttributesService.execute();
} }
} }

View File

@@ -47,17 +47,6 @@ export class PdfTemplatesController {
return this.pdfTemplateApplication.deletePdfTemplate(templateId); return this.pdfTemplateApplication.deletePdfTemplate(templateId);
} }
@Get('/state')
@ApiOperation({ summary: 'Retrieves the PDF template branding state.' })
@ApiResponse({
status: 200,
description:
'The PDF template branding state has been successfully retrieved.',
})
async getPdfTemplateBrandingState() {
return this.pdfTemplateApplication.getPdfTemplateBrandingState();
}
@Get(':id') @Get(':id')
@ApiOperation({ summary: 'Retrieves the PDF template details.' }) @ApiOperation({ summary: 'Retrieves the PDF template details.' })
@ApiResponse({ @ApiResponse({

View File

@@ -4,7 +4,6 @@ import { DeleteRoleService } from './commands/DeleteRole.service';
import { EditRoleService } from './commands/EditRole.service'; import { EditRoleService } from './commands/EditRole.service';
import { GetRoleService } from './queries/GetRole.service'; import { GetRoleService } from './queries/GetRole.service';
import { GetRolesService } from './queries/GetRoles.service'; import { GetRolesService } from './queries/GetRoles.service';
import { RolePermissionsSchema } from './queries/RolePermissionsSchema';
@Injectable() @Injectable()
export class RolesApplication { export class RolesApplication {
@@ -14,7 +13,6 @@ export class RolesApplication {
private readonly deleteRoleService: DeleteRoleService, private readonly deleteRoleService: DeleteRoleService,
private readonly getRoleService: GetRoleService, private readonly getRoleService: GetRoleService,
private readonly getRolesService: GetRolesService, private readonly getRolesService: GetRolesService,
private readonly getRolePermissionsSchemaService: RolePermissionsSchema,
) {} ) {}
/** /**
@@ -61,12 +59,4 @@ export class RolesApplication {
async getRoles() { async getRoles() {
return this.getRolesService.getRoles(); return this.getRolesService.getRoles();
} }
/**
* Gets the role permissions schema.
* @returns The role permissions schema.
*/
async getRolePermissionsSchema() {
return this.getRolePermissionsSchemaService.getRolePermissionsSchema();
}
} }

View File

@@ -72,7 +72,9 @@ export class RolesController {
status: HttpStatus.OK, status: HttpStatus.OK,
description: 'Role deleted successfully', description: 'Role deleted successfully',
}) })
async deleteRole(@Param('id', ParseIntPipe) roleId: number) { async deleteRole(
@Param('id', ParseIntPipe) roleId: number,
) {
await this.rolesApp.deleteRole(roleId); await this.rolesApp.deleteRole(roleId);
return { return {
@@ -81,34 +83,24 @@ export class RolesController {
}; };
} }
@Get('permissions/schema')
@ApiOperation({ summary: 'Get role permissions schema' })
@ApiResponse({
status: HttpStatus.OK,
description: 'Role permissions schema',
})
async getRolePermissionsSchema() {
const schema = await this.rolesApp.getRolePermissionsSchema();
return schema;
}
@Get() @Get()
@ApiOperation({ summary: 'Get all roles' }) @ApiOperation({ summary: 'Get all roles' })
@ApiResponse({ status: HttpStatus.OK, description: 'List of all roles' }) @ApiResponse({ status: HttpStatus.OK, description: 'List of all roles' })
async getRoles() { async getRoles() {
const roles = await this.rolesApp.getRoles(); const roles = await this.rolesApp.getRoles();
return roles; return { roles };
} }
@Get(':id') @Get(':id')
@ApiOperation({ summary: 'Get a specific role by ID' }) @ApiOperation({ summary: 'Get a specific role by ID' })
@ApiParam({ name: 'id', description: 'Role ID' }) @ApiParam({ name: 'id', description: 'Role ID' })
@ApiResponse({ status: HttpStatus.OK, description: 'Role details' }) @ApiResponse({ status: HttpStatus.OK, description: 'Role details' })
async getRole(@Param('id', ParseIntPipe) roleId: number) { async getRole(
@Param('id', ParseIntPipe) roleId: number,
) {
const role = await this.rolesApp.getRole(roleId); const role = await this.rolesApp.getRole(roleId);
return role; return { role };
} }
} }

View File

@@ -9,7 +9,6 @@ import { Role } from './models/Role.model';
import { RolePermission } from './models/RolePermission.model'; 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';
const models = [ const models = [
RegisterTenancyModel(Role), RegisterTenancyModel(Role),
@@ -25,7 +24,6 @@ const models = [
GetRoleService, GetRoleService,
GetRolesService, GetRolesService,
RolesApplication, RolesApplication,
RolePermissionsSchema
], ],
controllers: [RolesController], controllers: [RolesController],
exports: [...models], exports: [...models],

View File

@@ -1,5 +1,5 @@
import { Ability } from '@casl/ability'; import { Ability } from '@casl/ability';
import * as LruCache from 'lru-cache'; import LruCache from 'lru-cache';
import { Role } from './models/Role.model'; import { Role } from './models/Role.model';
import { RolePermission } from './models/RolePermission.model'; import { RolePermission } from './models/RolePermission.model';

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