refactor: tenant models to nestjs

This commit is contained in:
Ahmed Bouhuolia
2025-03-30 21:22:54 +02:00
parent 682be715ae
commit caff6ce47c
13 changed files with 92 additions and 70 deletions

View File

@@ -11,6 +11,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { BankRule } from '../models/BankRule'; import { BankRule } from '../models/BankRule';
import { CreateBankRuleDto } from '../dtos/BankRule.dto'; import { CreateBankRuleDto } from '../dtos/BankRule.dto';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() @Injectable()
export class CreateBankRuleService { export class CreateBankRuleService {
@@ -18,7 +19,8 @@ export class CreateBankRuleService {
private readonly uow: UnitOfWork, private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2, private readonly eventPublisher: EventEmitter2,
@Inject(BankRule.name) private readonly bankRuleModel: typeof BankRule, @Inject(BankRule.name)
private readonly bankRuleModel: TenantModelProxy<typeof BankRule>,
) {} ) {}
/** /**
@@ -48,9 +50,11 @@ export class CreateBankRuleService {
trx, trx,
} as IBankRuleEventCreatingPayload); } as IBankRuleEventCreatingPayload);
const bankRule = await this.bankRuleModel.query(trx).upsertGraphAndFetch({ const bankRule = await this.bankRuleModel()
...transformDTO, .query(trx)
}); .upsertGraphAndFetch({
...transformDTO,
});
// Triggers `onBankRuleCreated` event. // Triggers `onBankRuleCreated` event.
await this.eventPublisher.emitAsync(events.bankRules.onCreated, { await this.eventPublisher.emitAsync(events.bankRules.onCreated, {
createRuleDTO, createRuleDTO,

View File

@@ -5,6 +5,7 @@ import { RevertRecognizedTransactionsCriteria } from '../_types';
import { RecognizedBankTransaction } from '../models/RecognizedBankTransaction'; import { RecognizedBankTransaction } from '../models/RecognizedBankTransaction';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction'; import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() @Injectable()
export class RevertRecognizedTransactionsService { export class RevertRecognizedTransactionsService {
@@ -12,10 +13,14 @@ export class RevertRecognizedTransactionsService {
private readonly uow: UnitOfWork, private readonly uow: UnitOfWork,
@Inject(RecognizedBankTransaction.name) @Inject(RecognizedBankTransaction.name)
private readonly recognizedBankTransactionModel: typeof RecognizedBankTransaction, private readonly recognizedBankTransactionModel: TenantModelProxy<
typeof RecognizedBankTransaction
>,
@Inject(UncategorizedBankTransaction.name) @Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction, private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {} ) {}
/** /**
@@ -36,32 +41,34 @@ export class RevertRecognizedTransactionsService {
return this.uow.withTransaction(async (trx: Knex.Transaction) => { return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Retrieves all the recognized transactions of the banbk rule. // Retrieves all the recognized transactions of the banbk rule.
const uncategorizedTransactions = const uncategorizedTransactions =
await this.uncategorizedBankTransactionModel.query(trx).onBuild((q) => { await this.uncategorizedBankTransactionModel()
q.withGraphJoined('recognizedTransaction'); .query(trx)
q.whereNotNull('recognizedTransaction.id'); .onBuild((q) => {
q.withGraphJoined('recognizedTransaction');
q.whereNotNull('recognizedTransaction.id');
if (rulesIds.length > 0) { if (rulesIds.length > 0) {
q.whereIn('recognizedTransaction.bankRuleId', rulesIds); q.whereIn('recognizedTransaction.bankRuleId', rulesIds);
} }
if (transactionsCriteria?.accountId) { if (transactionsCriteria?.accountId) {
q.where('accountId', transactionsCriteria.accountId); q.where('accountId', transactionsCriteria.accountId);
} }
if (transactionsCriteria?.batch) { if (transactionsCriteria?.batch) {
q.where('batch', transactionsCriteria.batch); q.where('batch', transactionsCriteria.batch);
} }
}); });
const uncategorizedTransactionIds = uncategorizedTransactions.map( const uncategorizedTransactionIds = uncategorizedTransactions.map(
(r) => r.id, (r) => r.id,
); );
// Unlink the recongized transactions out of uncategorized transactions. // Unlink the recognized transactions out of un-categorized transactions.
await this.uncategorizedBankTransactionModel await this.uncategorizedBankTransactionModel()
.query(trx) .query(trx)
.whereIn('id', uncategorizedTransactionIds) .whereIn('id', uncategorizedTransactionIds)
.patch({ .patch({
recognizedTransactionId: null, recognizedTransactionId: null,
}); });
// Delete the recognized bank transactions that assocaited to bank rule. // Delete the recognized bank transactions that associated to bank rule.
await this.recognizedBankTransactionModel await this.recognizedBankTransactionModel()
.query(trx) .query(trx)
.whereIn('uncategorizedTransactionId', uncategorizedTransactionIds) .whereIn('uncategorizedTransactionId', uncategorizedTransactionIds)
.delete(); .delete();

View File

@@ -21,7 +21,9 @@ export class FeaturesSettingsDriver {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async turnOn(feature: string) { async turnOn(feature: string) {
this.settings().set({ group: 'features', key: feature, value: true }); const settingsStore = await this.settings();
settingsStore.set({ group: 'features', key: feature, value: true });
} }
/** /**
@@ -30,24 +32,24 @@ export class FeaturesSettingsDriver {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async turnOff(feature: string) { async turnOff(feature: string) {
this.settings().set({ group: 'features', key: feature, value: false }); const settingsStore = await this.settings();
settingsStore.set({ group: 'features', key: feature, value: false });
} }
/** /**
* Detarmines the given feature name is accessible. * Determines the given feature name is accessible.
* @param {string} feature - The feature name. * @param {string} feature - The feature name.
* @returns {Promise<boolean|null|undefined>} * @returns {Promise<boolean|null|undefined>}
*/ */
async accessible(feature: string) { async accessible(feature: string) {
const settingsStore = await this.settings();
const defaultValue = this.configure.getFeatureConfigure( const defaultValue = this.configure.getFeatureConfigure(
feature, feature,
'defaultValue', 'defaultValue',
); );
const settingValue = this.settings().get( return settingsStore.get({ group: 'features', key: feature }, defaultValue);
{ group: 'features', key: feature },
defaultValue,
);
return settingValue;
} }
/** /**
@@ -55,11 +57,13 @@ export class FeaturesSettingsDriver {
* @returns {Promise<IFeatureAllItem>} * @returns {Promise<IFeatureAllItem>}
*/ */
async all(): Promise<IFeatureAllItem[]> { async all(): Promise<IFeatureAllItem[]> {
const mappedOpers = this.featuresConfigure.getConfigure().map(async (featureConfigure) => { const mappedOpers = this.featuresConfigure
const { name, defaultValue } = featureConfigure; .getConfigure()
const isAccessible = await this.accessible(featureConfigure.name); .map(async (featureConfigure) => {
return { name, isAccessible, defaultAccessible: defaultValue }; const { name, defaultValue } = featureConfigure;
}); const isAccessible = await this.accessible(featureConfigure.name);
return { name, isAccessible, defaultAccessible: defaultValue };
});
return Promise.all(mappedOpers); return Promise.all(mappedOpers);
} }
} }

View File

@@ -1,6 +1,6 @@
// @ts-nocheck // @ts-nocheck
import { Knex } from 'knex'; import { Knex } from 'knex';
import { Inject } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { import {
IInventoryTransactionsDeletedPayload, IInventoryTransactionsDeletedPayload,
@@ -14,6 +14,7 @@ import { transformItemEntriesToInventory } from '../utils';
import { IItemEntryTransactionType } from '../../TransactionItemEntry/ItemEntry.types'; import { IItemEntryTransactionType } from '../../TransactionItemEntry/ItemEntry.types';
import { ItemEntry } from '../../TransactionItemEntry/models/ItemEntry'; import { ItemEntry } from '../../TransactionItemEntry/models/ItemEntry';
@Injectable()
export class InventoryTransactionsService { export class InventoryTransactionsService {
/** /**
* @param {EventEmitter2} eventEmitter - Event emitter. * @param {EventEmitter2} eventEmitter - Event emitter.
@@ -143,7 +144,7 @@ export class InventoryTransactionsService {
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<{ oldInventoryTransactions: InventoryTransaction[] }> { ): Promise<{ oldInventoryTransactions: InventoryTransaction[] }> {
// Retrieve the inventory transactions of the given sale invoice. // Retrieve the inventory transactions of the given sale invoice.
const oldInventoryTransactions = await this.inventoryTransactionModel const oldInventoryTransactions = await this.inventoryTransactionModel()
.query(trx) .query(trx)
.where({ transactionId, transactionType }); .where({ transactionId, transactionType });

View File

@@ -5,6 +5,7 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { Customer } from '../Customers/models/Customer'; import { Customer } from '../Customers/models/Customer';
import { CommonMailOptions } from './MailNotification.types'; import { CommonMailOptions } from './MailNotification.types';
import { formatMessage } from '@/utils/format-message'; import { formatMessage } from '@/utils/format-message';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable() @Injectable()
export class ContactMailNotification { export class ContactMailNotification {
@@ -13,7 +14,7 @@ export class ContactMailNotification {
private readonly tenantContext: TenancyContext, private readonly tenantContext: TenancyContext,
@Inject(Customer.name) @Inject(Customer.name)
private readonly customerModel: typeof Customer, private readonly customerModel: TenantModelProxy<typeof Customer>,
) {} ) {}
/** /**
@@ -26,7 +27,7 @@ export class ContactMailNotification {
): Promise< ): Promise<
Pick<CommonMailOptions, 'to' | 'from' | 'toOptions' | 'fromOptions'> Pick<CommonMailOptions, 'to' | 'from' | 'toOptions' | 'fromOptions'>
> { > {
const customer = await this.customerModel const customer = await this.customerModel()
.query() .query()
.findById(customerId) .findById(customerId)
.throwIfNotFound(); .throwIfNotFound();

View File

@@ -8,6 +8,7 @@ import { Injectable } from '@nestjs/common';
import { Inject } from '@nestjs/common'; import { Inject } from '@nestjs/common';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { Account } from '@/modules/Accounts/models/Account.model'; import { Account } from '@/modules/Accounts/models/Account.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() @Injectable()
export class PaymentReceivedGLEntries { export class PaymentReceivedGLEntries {
@@ -17,7 +18,9 @@ export class PaymentReceivedGLEntries {
private readonly tenancyContext: TenancyContext, private readonly tenancyContext: TenancyContext,
@Inject(PaymentReceived.name) @Inject(PaymentReceived.name)
private readonly paymentReceivedModel: typeof PaymentReceived, private readonly paymentReceivedModel: TenantModelProxy<
typeof PaymentReceived
>,
) {} ) {}
/** /**
@@ -28,13 +31,13 @@ export class PaymentReceivedGLEntries {
*/ */
public writePaymentGLEntries = async ( public writePaymentGLEntries = async (
paymentReceiveId: number, paymentReceiveId: number,
trx?: Knex.Transaction trx?: Knex.Transaction,
): Promise<void> => { ): Promise<void> => {
// Retrieves the given tenant metadata. // Retrieves the given tenant metadata.
const tenantMeta = await this.tenancyContext.getTenantMetadata(); const tenantMeta = await this.tenancyContext.getTenantMetadata();
// Retrieves the payment receive with associated entries. // Retrieves the payment receive with associated entries.
const paymentReceive = await this.paymentReceivedModel const paymentReceive = await this.paymentReceivedModel()
.query(trx) .query(trx)
.findById(paymentReceiveId) .findById(paymentReceiveId)
.withGraphFetched('entries.invoice'); .withGraphFetched('entries.invoice');
@@ -55,12 +58,12 @@ export class PaymentReceivedGLEntries {
*/ */
public revertPaymentGLEntries = async ( public revertPaymentGLEntries = async (
paymentReceiveId: number, paymentReceiveId: number,
trx?: Knex.Transaction trx?: Knex.Transaction,
) => { ) => {
await this.ledgerStorage.deleteByReference( await this.ledgerStorage.deleteByReference(
paymentReceiveId, paymentReceiveId,
'PaymentReceive', 'PaymentReceive',
trx trx,
); );
}; };
@@ -71,7 +74,7 @@ export class PaymentReceivedGLEntries {
*/ */
public rewritePaymentGLEntries = async ( public rewritePaymentGLEntries = async (
paymentReceiveId: number, paymentReceiveId: number,
trx?: Knex.Transaction trx?: Knex.Transaction,
) => { ) => {
// Reverts the payment GL entries. // Reverts the payment GL entries.
await this.revertPaymentGLEntries(paymentReceiveId, trx); await this.revertPaymentGLEntries(paymentReceiveId, trx);
@@ -94,12 +97,12 @@ export class PaymentReceivedGLEntries {
// Retrieve the A/R account of the given currency. // Retrieve the A/R account of the given currency.
const receivableAccount = const receivableAccount =
await this.accountRepository.findOrCreateAccountReceivable( await this.accountRepository.findOrCreateAccountReceivable(
paymentReceive.currencyCode paymentReceive.currencyCode,
); );
// Exchange gain/loss account. // Exchange gain/loss account.
const exGainLossAccount = await this.accountRepository.findBySlug( const exGainLossAccount = (await this.accountRepository.findBySlug(
'exchange-grain-loss' 'exchange-grain-loss',
) as Account; )) as Account;
const paymentReceivedGL = new PaymentReceivedGL(paymentReceive) const paymentReceivedGL = new PaymentReceivedGL(paymentReceive)
.setARAccountId(receivableAccount.id) .setARAccountId(receivableAccount.id)
@@ -108,5 +111,4 @@ export class PaymentReceivedGLEntries {
return paymentReceivedGL.getLedger(); return paymentReceivedGL.getLedger();
}; };
} }

View File

@@ -1,8 +1,6 @@
// import { getUploadedObjectUri } from '@/services/Attachments/utils'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { BaseModel } from '@/models/Model';
// import TenantModel from 'models/TenantModel';
export class PdfTemplateModel extends BaseModel { export class PdfTemplateModel extends TenantBaseModel {
public resource!: string; public resource!: string;
public templateName!: string; public templateName!: string;
public predefined!: boolean; public predefined!: boolean;

View File

@@ -151,7 +151,7 @@ export class SaleInvoicesController {
return this.saleInvoiceApplication.getSaleInvoiceState(); return this.saleInvoiceApplication.getSaleInvoiceState();
} }
@Post(':id/deliver') @Put(':id/deliver')
@ApiOperation({ summary: 'Deliver the given sale invoice.' }) @ApiOperation({ summary: 'Deliver the given sale invoice.' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -2,6 +2,7 @@ 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';
import { import {
ArrayMinSize,
IsArray, IsArray,
IsBoolean, IsBoolean,
IsDate, IsDate,
@@ -12,7 +13,6 @@ import {
IsOptional, IsOptional,
IsString, IsString,
Min, Min,
MinLength,
ValidateNested, ValidateNested,
} from 'class-validator'; } from 'class-validator';
@@ -130,7 +130,7 @@ class CommandSaleInvoiceDto {
@IsArray() @IsArray()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => ItemEntryDto) @Type(() => ItemEntryDto)
@MinLength(1) @ArrayMinSize(1)
@ApiProperty({ @ApiProperty({
description: 'Invoice line items', description: 'Invoice line items',
type: [ItemEntryDto], type: [ItemEntryDto],

View File

@@ -16,7 +16,7 @@ export class SaveSettingsService {
*/ */
public async saveSettings(settingsDTO: ISettingsDTO) { public async saveSettings(settingsDTO: ISettingsDTO) {
const settingsStore = await this.settingsStore(); const settingsStore = await this.settingsStore();
const notDefinedOptions = this.validateNotDefinedSettings( const notDefinedOptions = await this.validateNotDefinedSettings(
settingsDTO.options, settingsDTO.options,
); );
const errorReasons: { type: string; code: number; keys: any[] }[] = []; const errorReasons: { type: string; code: number; keys: any[] }[] = [];
@@ -41,11 +41,12 @@ export class SaveSettingsService {
* @param {Array} options * @param {Array} options
* @return {Boolean} * @return {Boolean}
*/ */
private validateNotDefinedSettings(options) { private async validateNotDefinedSettings(options) {
const notDefined = []; const notDefined = [];
const settingStore = await this.settingsStore();
options.forEach((option) => { options.forEach((option) => {
const setting = this.settingsStore().config.getMetaConfig( const setting = settingStore.config.getMetaConfig(
option.key, option.key,
option.group, option.group,
); );

View File

@@ -19,9 +19,13 @@ import { SaleInvoiceTaxRateValidateSubscriber } from './subscribers/SaleInvoiceT
import { SyncItemTaxRateOnEditTaxSubscriber } from './subscribers/SyncItemTaxRateOnEditTaxSubscriber'; import { SyncItemTaxRateOnEditTaxSubscriber } from './subscribers/SyncItemTaxRateOnEditTaxSubscriber';
import { WriteTaxTransactionsItemEntries } from './WriteTaxTransactionsItemEntries'; import { WriteTaxTransactionsItemEntries } from './WriteTaxTransactionsItemEntries';
import { SyncItemTaxRateOnEditTaxRate } from './SyncItemTaxRateOnEditTaxRate'; import { SyncItemTaxRateOnEditTaxRate } from './SyncItemTaxRateOnEditTaxRate';
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
import { TaxRateTransaction } from './models/TaxRateTransaction.model';
const models = [RegisterTenancyModel(TaxRateTransaction)];
@Module({ @Module({
imports: [], imports: [...models],
controllers: [TaxRatesController], controllers: [TaxRatesController],
providers: [ providers: [
CreateTaxRate, CreateTaxRate,
@@ -42,8 +46,8 @@ import { SyncItemTaxRateOnEditTaxRate } from './SyncItemTaxRateOnEditTaxRate';
SaleInvoiceTaxRateValidateSubscriber, SaleInvoiceTaxRateValidateSubscriber,
SyncItemTaxRateOnEditTaxSubscriber, SyncItemTaxRateOnEditTaxSubscriber,
WriteTaxTransactionsItemEntries, WriteTaxTransactionsItemEntries,
SyncItemTaxRateOnEditTaxRate SyncItemTaxRateOnEditTaxRate,
], ],
exports: [ItemEntriesTaxTransactions], exports: [ItemEntriesTaxTransactions, ...models],
}) })
export class TaxRatesModule {} export class TaxRatesModule {}

View File

@@ -1,19 +1,18 @@
import { sumBy, chain, keyBy } from 'lodash'; import { sumBy, chain, keyBy } from 'lodash';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { SaleInvoice } from '../SaleInvoices/models/SaleInvoice'; import { ModelObject } from 'objection';
import { TenantModelProxy } from '../System/models/TenantBaseModel'; import { TenantModelProxy } from '../System/models/TenantBaseModel';
import { TaxRateModel } from './models/TaxRate.model'; import { TaxRateModel } from './models/TaxRate.model';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { ModelObject } from 'objection';
import { ItemEntry } from '../TransactionItemEntry/models/ItemEntry'; import { ItemEntry } from '../TransactionItemEntry/models/ItemEntry';
import { TaxRateTransaction } from './models/TaxRateTransaction.model'; import { TaxRateTransaction } from './models/TaxRateTransaction.model';
@Injectable() @Injectable()
export class WriteTaxTransactionsItemEntries { export class WriteTaxTransactionsItemEntries {
constructor( constructor(
@Inject(SaleInvoice.name) @Inject(TaxRateTransaction.name)
private readonly taxRateTransactionModel: TenantModelProxy< private readonly taxRateTransactionModel: TenantModelProxy<
typeof SaleInvoice typeof TaxRateTransaction
>, >,
@Inject(TaxRateModel.name) @Inject(TaxRateModel.name)

View File

@@ -4,8 +4,9 @@ import { AppModule } from '../src/modules/App/App.module';
let app: INestApplication; let app: INestApplication;
let orgainzationId = 'fxdo7u419m5ryy4tb'; let orgainzationId = 'hpgpqhanm8s4921m';
let authenticationToken = ''; let authenticationToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZXhwIjoxNzQ4MzExOTc5LjIzOCwiaWF0IjoxNzQzMTI3OTc5fQ.h3xvmuNjeyFeshEZRVRLCsARgTpx4xeZQHQuZzESm2U';
beforeAll(async () => { beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({ const moduleFixture: TestingModule = await Test.createTestingModule({