diff --git a/packages/server-nest/src/constants/metable-options.ts b/packages/server-nest/src/constants/metable-options.ts new file mode 100644 index 000000000..31c3bcf1e --- /dev/null +++ b/packages/server-nest/src/constants/metable-options.ts @@ -0,0 +1,241 @@ +// import { getTransactionsLockingSettingsSchema } from '@/api/controllers/TransactionsLocking/utils'; + +export const SettingsOptions = { + organization: { + name: { + type: 'string', + }, + base_currency: { + type: 'string', + }, + industry: { + type: 'string', + }, + location: { + type: 'string', + }, + fiscal_year: { + type: 'string', + }, + financial_date_start: { + type: 'string', + }, + language: { + type: 'string', + }, + time_zone: { + type: 'string', + }, + date_format: { + type: 'string', + }, + accounting_basis: { + type: 'string', + }, + }, + manual_journals: { + next_number: { + type: 'string', + }, + number_prefix: { + type: 'string', + }, + auto_increment: { + type: 'boolean', + }, + }, + bill_payments: { + withdrawal_account: { + type: 'number', + }, + }, + sales_estimates: { + next_number: { + type: 'string', + }, + number_prefix: { + type: 'string', + }, + auto_increment: { + type: 'boolean', + }, + customer_notes: { + type: 'string', + }, + terms_conditions: { + type: 'string', + }, + }, + sales_receipts: { + next_number: { + type: 'string', + }, + number_prefix: { + type: 'string', + }, + auto_increment: { + type: 'boolean', + }, + preferred_deposit_account: { + type: 'number', + }, + receipt_message: { + type: 'string', + }, + terms_conditions: { + type: 'string', + }, + }, + sales_invoices: { + next_number: { + type: 'string', + }, + number_prefix: { + type: 'string', + }, + auto_increment: { + type: 'boolean', + }, + customer_notes: { + type: 'string', + }, + terms_conditions: { + type: 'string', + }, + }, + payment_receives: { + next_number: { + type: 'string', + }, + number_prefix: { + type: 'string', + }, + auto_increment: { + type: 'boolean', + }, + preferred_deposit_account: { + type: 'number', + }, + preferred_advance_deposit: { + type: 'number', + }, + }, + items: { + preferred_sell_account: { + type: 'number', + }, + preferred_cost_account: { + type: 'number', + }, + preferred_inventory_account: { + type: 'number', + }, + }, + expenses: { + preferred_payment_account: { + type: 'number', + }, + }, + inventory: { + cost_compute_running: { + type: 'boolean', + }, + }, + accounts: { + account_code_required: { + type: 'boolean', + }, + account_code_unique: { + type: 'boolean', + }, + }, + cashflow: { + next_number: { + type: 'string', + }, + number_prefix: { + type: 'string', + }, + auto_increment: { + type: 'boolean', + }, + }, + credit_note: { + next_number: { + type: 'string', + }, + number_prefix: { + type: 'string', + }, + auto_increment: { + type: 'boolean', + }, + customer_notes: { + type: 'string', + }, + terms_conditions: { + type: 'string', + }, + }, + vendor_credit: { + next_number: { + type: 'string', + }, + number_prefix: { + type: 'string', + }, + auto_increment: { + type: 'boolean', + }, + }, + warehouse_transfers: { + next_number: { + type: 'string', + }, + number_prefix: { + type: 'string', + }, + auto_increment: { + type: 'boolean', + }, + }, + 'sms-notification': { + 'sms-notification-enable.sale-invoice-details': { + type: 'boolean', + }, + 'sms-notification-enable.sale-invoice-reminder': { + type: 'boolean', + }, + 'sms-notification-enable.sale-estimate-details': { + type: 'boolean', + }, + 'sms-notification-enable.sale-receipt-details': { + type: 'boolean', + }, + 'sms-notification-enable.payment-receive-details': { + type: 'boolean', + }, + 'sms-notification-enable.customer-balance': { + type: 'boolean', + }, + }, + 'transactions-locking': { + 'locking-type': { + type: 'string', + }, + // ...getTransactionsLockingSettingsSchema([ + // 'all', + // 'sales', + // 'purchases', + // 'financial', + // ]), + }, + features: { + 'multi-warehouses': { + type: 'boolean', + }, + 'multi-branches': { + type: 'boolean', + }, + }, +}; diff --git a/packages/server-nest/src/modules/App/App.module.ts b/packages/server-nest/src/modules/App/App.module.ts index 6dfb72f4f..a04389476 100644 --- a/packages/server-nest/src/modules/App/App.module.ts +++ b/packages/server-nest/src/modules/App/App.module.ts @@ -60,6 +60,8 @@ import { BankingTransactionsExcludeModule } from '../BankingTransactionsExclude/ import { BankingTransactionsRegonizeModule } from '../BankingTranasctionsRegonize/BankingTransactionsRegonize.module'; import { BankingMatchingModule } from '../BankingMatching/BankingMatching.module'; import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module'; +import { TransactionsLockingModule } from '../TransactionsLocking/TransactionsLocking.module'; +import { SettingsModule } from '../Settings/Settings.module'; @Module({ imports: [ @@ -141,9 +143,11 @@ import { BankingTransactionsModule } from '../BankingTransactions/BankingTransac BankAccountsModule, BankRulesModule, BankingTransactionsModule, - // BankingTransactionsExcludeModule, - // BankingTransactionsRegonizeModule, - // BankingMatchingModule, + BankingTransactionsExcludeModule, + BankingTransactionsRegonizeModule, + BankingMatchingModule, + TransactionsLockingModule, + SettingsModule ], controllers: [AppController], providers: [ diff --git a/packages/server-nest/src/modules/Metable/MetableConfig.ts b/packages/server-nest/src/modules/Metable/MetableConfig.ts new file mode 100644 index 000000000..f05591a85 --- /dev/null +++ b/packages/server-nest/src/modules/Metable/MetableConfig.ts @@ -0,0 +1,40 @@ +import { get } from 'lodash'; + +export class MetableConfig { + public config: any; + + constructor(config: any) { + this.setConfig(config); + } + + /** + * Sets the config. + */ + setConfig(config: any) { + this.config = config; + } + + /** + * Gets the meta config. + * @param {string} key + * @param {string} group + * @param {string} accessor + * @returns {object|string} + */ + getMetaConfig(key: string, group?: string, accessor?: string) { + const configGroup = get(this.config, group); + const config = get(configGroup, key); + + return accessor ? get(config, accessor) : config; + } + + /** + * Gets the meta type. + * @param {string} key + * @param {string} group + * @returns {string} + */ + getMetaType(key: string, group?: string) { + return this.getMetaConfig(key, group, 'type'); + } +} diff --git a/packages/server-nest/src/modules/Metable/MetableModel.ts b/packages/server-nest/src/modules/Metable/MetableModel.ts new file mode 100644 index 000000000..53facca69 --- /dev/null +++ b/packages/server-nest/src/modules/Metable/MetableModel.ts @@ -0,0 +1,9 @@ +export class Metable { + static get modifiers() { + return { + whereKey(builder, key) { + builder.where('key', key); + }, + }; + } +} diff --git a/packages/server-nest/src/modules/Metable/MetableStore.ts b/packages/server-nest/src/modules/Metable/MetableStore.ts new file mode 100644 index 000000000..6b5aee04c --- /dev/null +++ b/packages/server-nest/src/modules/Metable/MetableStore.ts @@ -0,0 +1,214 @@ +import { Model } from 'objection'; +import { omit, isEmpty } from 'lodash'; +import { IMetadata, IMetaQuery, IMetableStore } from './types'; +import { itemsStartWith } from 'utils'; + +export class MetableStore implements IMetableStore { + metadata: IMetadata[]; + model: Model; + extraColumns: string[]; + + /** + * Constructor method. + */ + constructor() { + this.metadata = []; + this.model = null; + this.extraColumns = []; + } + + /** + * Sets a extra columns. + * @param {Array} columns - + */ + setExtraColumns(columns: string[]): void { + this.extraColumns = columns; + } + + /** + * Find the given metadata key. + * @param {string|IMetaQuery} query - + * @returns {IMetadata} - Metadata object. + */ + find(query: string | IMetaQuery): IMetadata { + const { key, value, ...extraColumns } = this.parseQuery(query); + + return this.metadata.find((meta: IMetadata) => { + const isSameKey = meta.key === key; + const sameExtraColumns = this.extraColumns.some( + (extraColumn: string) => extraColumns[extraColumn] === meta[extraColumn] + ); + const isSameExtraColumns = sameExtraColumns || isEmpty(extraColumns); + + return isSameKey && isSameExtraColumns; + }); + } + + /** + * Retrieve all metadata. + * @returns {IMetadata[]} + */ + all(): IMetadata[] { + return this.metadata + .filter((meta: IMetadata) => !meta._markAsDeleted) + .map((meta: IMetadata) => + omit(meta, itemsStartWith(Object.keys(meta), '_')) + ); + } + + /** + * Retrieve metadata of the given key. + * @param {String} key - + * @param {Mixied} defaultValue - + */ + get(query: string | IMetaQuery, defaultValue?: any): any | null { + const metadata = this.find(query); + return metadata + ? metadata.value + : typeof defaultValue !== 'undefined' + ? defaultValue + : null; + } + + /** + * Markes the metadata to should be deleted. + * @param {String} key - + */ + remove(query: string | IMetaQuery): void { + const metadata: IMetadata = this.find(query); + + if (metadata) { + metadata._markAsDeleted = true; + } + } + + /** + * Remove all meta data of the given group. + * @param {string} group + */ + removeAll(group: string = 'default'): void { + this.metadata = this.metadata.map((meta) => ({ + ...meta, + _markAsDeleted: true, + })); + } + + /** + * Set the meta data to the stack. + * @param {String} key - + * @param {String} value - + */ + set(query: IMetaQuery | IMetadata[] | string, metaValue?: any): void { + if (Array.isArray(query)) { + const metadata = query; + + metadata.forEach((meta: IMetadata) => { + this.set(meta); + }); + return; + } + const { key, value, ...extraColumns } = this.parseQuery(query); + const metadata = this.find(query); + const newValue = metaValue || value; + + if (metadata) { + metadata.value = newValue; + metadata._markAsUpdated = true; + } else { + this.metadata.push({ + value: newValue, + key, + ...extraColumns, + _markAsInserted: true, + }); + } + } + + /** + * Parses query query. + * @param query + * @param value + */ + parseQuery(query: string | IMetaQuery): IMetaQuery { + return typeof query !== 'object' ? { key: query } : { ...query }; + } + + /** + * Format the metadata before saving to the database. + * @param {string|number|boolean} value - + * @param {string} valueType - + * @return {string|number|boolean} - + */ + static formatMetaValue( + value: string | boolean | number, + valueType: string + ): string | number | boolean { + let parsedValue; + + switch (valueType) { + case 'number': + parsedValue = `${value}`; + break; + case 'boolean': + parsedValue = value ? '1' : '0'; + break; + case 'json': + parsedValue = JSON.stringify(parsedValue); + break; + default: + parsedValue = value; + break; + } + return parsedValue; + } + + /** + * Parse the metadata to the collection. + * @param {Array} collection - + */ + mapMetadataToCollection(metadata: IMetadata[], parseType: string = 'parse') { + return metadata.map((model) => + this.mapMetadataToCollection(model, parseType) + ); + } + + /** + * Load metadata to the metable collection. + * @param {Array} meta - + */ + from(meta: []) { + if (Array.isArray(meta)) { + meta.forEach((m) => { + this.from(m); + }); + return; + } + this.metadata.push(meta); + } + + /** + * + * @returns {array} + */ + toArray(): IMetadata[] { + return this.metadata; + } + + /** + * Static method to load metadata to the collection. + * @param {Array} meta + */ + static from(meta) { + const collection = new MetableCollection(); + collection.from(meta); + + return collection; + } + + /** + * Reset the momerized metadata. + */ + resetMetadata() { + this.metadata = []; + } +} diff --git a/packages/server-nest/src/modules/Metable/MetableStoreDB.ts b/packages/server-nest/src/modules/Metable/MetableStoreDB.ts new file mode 100644 index 000000000..c2dbff9dc --- /dev/null +++ b/packages/server-nest/src/modules/Metable/MetableStoreDB.ts @@ -0,0 +1,246 @@ +import { IMetadata, IMetableStoreStorage } from './types'; +import { MetableStore } from './MetableStore'; +import { isBlank, parseBoolean } from 'utils'; +import { MetableConfig } from './MetableConfig'; +import { EntityRepository } from '@/common/repository/EntityRepository'; + +export class MetableDBStore + extends MetableStore + implements IMetableStoreStorage +{ + repository: any; + KEY_COLUMN: string; + VALUE_COLUMN: string; + TYPE_COLUMN: string; + extraQuery: Function; + loaded: Boolean; + config: MetableConfig; + extraColumns: Array; + + /** + * Constructor method. + */ + constructor(config: any) { + super(); + + this.loaded = false; + this.KEY_COLUMN = 'key'; + this.VALUE_COLUMN = 'value'; + this.TYPE_COLUMN = 'type'; + this.repository = null; + + this.extraQuery = (meta) => { + return { + key: meta[this.KEY_COLUMN], + ...this.transfromMetaExtraColumns(meta), + }; + }; + this.config = new MetableConfig(config); + } + + /** + * Transformes meta query. + * @param {IMetadata} meta + */ + private transfromMetaExtraColumns(meta: IMetadata) { + return this.extraColumns.reduce((obj, column) => { + const metaValue = meta[column]; + + if (!isBlank(metaValue)) { + obj[column] = metaValue; + } + return obj; + }, {}); + } + + /** + * Set repository entity of this metadata collection. + * @param {Object} repository - + */ + setRepository(repository: EntityRepository) { + this.repository = repository; + } + + /** + * Sets a extra query callback. + * @param callback + */ + setExtraQuery(callback) { + this.extraQuery = callback; + } + + /** + * Saves the modified, deleted and insert metadata. + */ + save() { + this.validateStoreIsLoaded(); + + return Promise.all([ + this.saveUpdated(this.metadata), + this.saveDeleted(this.metadata), + this.saveInserted(this.metadata), + ]); + } + + /** + * Saves the updated metadata. + * @param {IMetadata[]} metadata - + * @returns {Promise} + */ + saveUpdated(metadata: IMetadata[]) { + const updated = metadata.filter((m) => m._markAsUpdated === true); + const opers = []; + + updated.forEach((meta) => { + const updateOper = this.repository + .update( + { [this.VALUE_COLUMN]: meta.value }, + { ...this.extraQuery(meta) }, + ) + .then(() => { + meta._markAsUpdated = false; + }); + opers.push(updateOper); + }); + return Promise.all(opers); + } + + /** + * Saves the deleted metadata. + * @param {IMetadata[]} metadata - + * @returns {Promise} + */ + saveDeleted(metadata: IMetadata[]) { + const deleted = metadata.filter( + (m: IMetadata) => m._markAsDeleted === true, + ); + const opers: Array> = []; + + if (deleted.length > 0) { + deleted.forEach((meta) => { + const deleteOper = this.repository + .deleteBy({ + ...this.extraQuery(meta), + }) + .then(() => { + meta._markAsDeleted = false; + }); + opers.push(deleteOper); + }); + } + return Promise.all(opers); + } + + /** + * Saves the inserted metadata. + * @param {IMetadata[]} metadata - + * @returns {Promise} + */ + saveInserted(metadata: IMetadata[]) { + const inserted = metadata.filter( + (m: IMetadata) => m._markAsInserted === true, + ); + const opers: Array> = []; + + inserted.forEach((meta) => { + const insertData = { + [this.KEY_COLUMN]: meta.key, + [this.VALUE_COLUMN]: meta.value, + ...this.transfromMetaExtraColumns(meta), + }; + const insertOper = this.repository.create(insertData).then(() => { + meta._markAsInserted = false; + }); + opers.push(insertOper); + }); + return Promise.all(opers); + } + + /** + * Loads the metadata from the storage. + * @param {String|Array} key - + * @param {Boolean} force - + */ + async load() { + const metadata = await this.repository.all(); + const mappedMetadata = this.mapMetadataCollection(metadata); + + this.resetMetadata(); + + mappedMetadata.forEach((meta: IMetadata) => { + this.metadata.push(meta); + }); + this.loaded = true; + } + + /** + * Parse the metadata values after fetching it from the storage. + * @param {String|Number|Boolean} value - + * @param {String} valueType - + * @return {String|Number|Boolean} - + */ + static parseMetaValue( + value: string, + valueType: string | false, + ): string | boolean | number { + let parsedValue: string | number | boolean; + + switch (valueType) { + case 'number': + parsedValue = parseFloat(value); + break; + case 'boolean': + parsedValue = parseBoolean(value, false); + break; + case 'json': + parsedValue = JSON.stringify(parsedValue); + break; + default: + parsedValue = value; + break; + } + return parsedValue; + } + + /** + * Mapping and parse metadata to collection entries. + * @param {Meta} attr - + * @param {String} parseType - + */ + mapMetadata(metadata: IMetadata) { + const metaType = this.config.getMetaType( + metadata[this.KEY_COLUMN], + metadata['group'], + ); + return { + key: metadata[this.KEY_COLUMN], + value: MetableDBStore.parseMetaValue( + metadata[this.VALUE_COLUMN], + metaType, + ), + ...this.extraColumns.reduce((obj, extraCol: string) => { + obj[extraCol] = metadata[extraCol] || null; + return obj; + }, {}), + }; + } + + /** + * Parse the metadata to the collection. + * @param {Array} collection - + */ + mapMetadataCollection(metadata: IMetadata[]) { + return metadata.map((model) => this.mapMetadata(model)); + } + + /** + * Throw error in case the store is not loaded yet. + */ + private validateStoreIsLoaded() { + if (!this.loaded) { + throw new Error( + 'You could not save the store before loaded from the storage.', + ); + } + } +} diff --git a/packages/server-nest/src/modules/Metable/types.ts b/packages/server-nest/src/modules/Metable/types.ts new file mode 100644 index 000000000..ae4e3e62b --- /dev/null +++ b/packages/server-nest/src/modules/Metable/types.ts @@ -0,0 +1,27 @@ +export interface IMetadata { + key: string; + value: string | boolean | number; + group: string; + _markAsDeleted?: boolean; + _markAsInserted?: boolean; + _markAsUpdated?: boolean; +} + +export interface IMetaQuery { + key: string; + group?: string; + value?: any; +} + +export interface IMetableStore { + find(query: string | IMetaQuery): IMetadata; + all(): IMetadata[]; + get(query: string | IMetaQuery, defaultValue: any): string | number | boolean; + remove(query: string | IMetaQuery): void; + removeAll(): void; + toArray(): IMetadata[]; +} + +export interface IMetableStoreStorage { + save(): Promise; +} diff --git a/packages/server-nest/src/modules/Settings/Settings.controller.ts b/packages/server-nest/src/modules/Settings/Settings.controller.ts new file mode 100644 index 000000000..51c0cf955 --- /dev/null +++ b/packages/server-nest/src/modules/Settings/Settings.controller.ts @@ -0,0 +1,16 @@ +import { Controller, Get } from '@nestjs/common'; +import { Inject } from '@nestjs/common'; +import { SETTINGS } from './Settings.module'; +import { SettingsStore } from './SettingsStore'; + +@Controller('settings') +export class SettingsController { + constructor( + @Inject(SETTINGS) private readonly settingsStore: SettingsStore, + ) {} + + @Get('') + async getSettings() { + return this.settingsStore.all(); + } +} diff --git a/packages/server-nest/src/modules/Settings/Settings.module.ts b/packages/server-nest/src/modules/Settings/Settings.module.ts new file mode 100644 index 000000000..14b059ac9 --- /dev/null +++ b/packages/server-nest/src/modules/Settings/Settings.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { SettingRepository } from './repositories/Setting.repository'; +import { SettingsStore } from './SettingsStore'; + +export const SETTINGS = 'SETTINGS'; + +@Module({ + providers: [ + SettingRepository, + { + provide: SETTINGS, + useFactory: (settingRepository: SettingRepository) => { + return new SettingsStore(settingRepository); + }, + inject: [SettingRepository], + }, + ], +}) +export class SettingsModule {} diff --git a/packages/server-nest/src/modules/Settings/SettingsStore.ts b/packages/server-nest/src/modules/Settings/SettingsStore.ts new file mode 100644 index 000000000..3ff11f85e --- /dev/null +++ b/packages/server-nest/src/modules/Settings/SettingsStore.ts @@ -0,0 +1,16 @@ +import { EntityRepository } from '@/common/repository/EntityRepository'; +import { MetableDBStore } from '../Metable/MetableStoreDB'; +import { SettingsOptions } from '@/constants/metable-options'; + +export class SettingsStore extends MetableDBStore { + /** + * Constructor method. + * @param {number} tenantId + */ + constructor(repository: EntityRepository, config: any = SettingsOptions) { + super(config); + + // this.setExtraColumns(['group']); + this.setRepository(repository); + } +} diff --git a/packages/server-nest/src/modules/Settings/models/Setting.ts b/packages/server-nest/src/modules/Settings/models/Setting.ts new file mode 100644 index 000000000..ace76f31f --- /dev/null +++ b/packages/server-nest/src/modules/Settings/models/Setting.ts @@ -0,0 +1,22 @@ +import { BaseModel } from '@/models/Model'; +// import TenantModel from 'models/TenantModel'; +// import Auth from './Auth'; + +export class Setting extends BaseModel { + /** + * Table name + */ + static get tableName() { + return 'settings'; + } + + /** + * Extra metadata query to query with the current authenticate user. + * @param {Object} query + */ + // static extraMetadataQuery(query) { + // if (Auth.isLogged()) { + // query.where('user_id', Auth.userId()); + // } + // } +} diff --git a/packages/server-nest/src/modules/Settings/repositories/Setting.repository.ts b/packages/server-nest/src/modules/Settings/repositories/Setting.repository.ts new file mode 100644 index 000000000..cc1a8e465 --- /dev/null +++ b/packages/server-nest/src/modules/Settings/repositories/Setting.repository.ts @@ -0,0 +1,21 @@ +import { Knex } from 'knex'; +import { Inject } from '@nestjs/common'; +import { TenantRepository } from '@/common/repository/TenantRepository'; +import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants'; +import { Setting } from '../models/Setting'; + +export class SettingRepository extends TenantRepository { + constructor( + @Inject(TENANCY_DB_CONNECTION) + private readonly tenantKnex: Knex, + ) { + super(); + } + + /** + * Gets the repository's model. + */ + get model(): typeof Setting { + return Setting.bindKnex(this.tenantKnex); + } +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/TransactionsLocking.controller.ts b/packages/server-nest/src/modules/TransactionsLocking/TransactionsLocking.controller.ts new file mode 100644 index 000000000..698768cbb --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/TransactionsLocking.controller.ts @@ -0,0 +1,86 @@ +import { Controller, Put, Get, Body, Param, UseGuards } from '@nestjs/common'; +import { TransactionsLockingService } from './commands/CommandTransactionsLockingService'; +import { TransactionsLockingGroup } from './types/TransactionsLocking.types'; +import { ITransactionsLockingAllDTO } from './types/TransactionsLocking.types'; +import { ICancelTransactionsLockingDTO } from './types/TransactionsLocking.types'; +import { ITransactionLockingPartiallyDTO } from './types/TransactionsLocking.types'; +import { QueryTransactionsLocking } from './queries/QueryTransactionsLocking'; + +@Controller('transactions-locking') +export class TransactionsLockingController { + constructor( + private readonly transactionsLockingService: TransactionsLockingService, + private readonly queryTransactionsLocking: QueryTransactionsLocking, + ) {} + + @Put('lock') + async commandTransactionsLocking( + @Body('module') module: TransactionsLockingGroup, + @Body() transactionLockingDTO: ITransactionsLockingAllDTO, + ) { + const transactionMeta = + await this.transactionsLockingService.commandTransactionsLocking( + module, + transactionLockingDTO, + ); + return { + message: 'All transactions locking has been submit successfully.', + data: transactionMeta, + }; + } + + @Put('cancel-lock') + async cancelTransactionLocking( + @Body('module') module: TransactionsLockingGroup, + @Body() cancelLockingDTO: ICancelTransactionsLockingDTO, + ) { + const data = await this.transactionsLockingService.cancelTransactionLocking( + module, + cancelLockingDTO, + ); + return { + message: 'Transactions locking has been canceled successfully.', + data, + }; + } + + @Put('unlock-partial') + async unlockTransactionsLockingBetweenPeriod( + @Body('module') module: TransactionsLockingGroup, + @Body() unlockDTO: ITransactionLockingPartiallyDTO, + ) { + const transactionMeta = + await this.transactionsLockingService.unlockTransactionsLockingPartially( + module, + unlockDTO, + ); + return { + message: 'Transactions locking has been unlocked partially successfully.', + data: transactionMeta, + }; + } + + @Put('cancel-unlock-partial') + async cancelPartialUnlocking( + @Body('module') module: TransactionsLockingGroup, + ) { + const transactionMeta = + await this.transactionsLockingService.cancelPartialTransactionsUnlock( + module, + ); + return { + message: 'Partial transaction unlocking has been canceled successfully.', + data: transactionMeta, + }; + } + + // @Get(':module') + // async getTransactionLockingMeta(@Param('module') module: string) { + // return await this.queryTransactionsLocking.getTransactionsLocking(module); + // } + + @Get() + async getTransactionLockingMetaList() { + return await this.queryTransactionsLocking.getTransactionsLockingAll(); + } +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/TransactionsLocking.module.ts b/packages/server-nest/src/modules/TransactionsLocking/TransactionsLocking.module.ts new file mode 100644 index 000000000..41c755406 --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/TransactionsLocking.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { TransactionsLockingService } from './commands/CommandTransactionsLockingService'; +import { FinancialTransactionLocking } from './guards/FinancialTransactionLockingGuard'; +import { PurchasesTransactionLockingGuard } from './guards/PurchasesTransactionLockingGuard'; +import { SalesTransactionLockingGuard } from './guards/SalesTransactionLockingGuard'; +import { TransactionsLockingGuard } from './guards/TransactionsLockingGuard'; +import { TransactionsLockingRepository } from './TransactionsLockingRepository'; +import { FinancialTransactionLockingGuardSubscriber } from './subscribers/FinancialsTransactionLockingGuardSubscriber'; +import { PurchasesTransactionLockingGuardSubscriber } from './subscribers/PurchasesTransactionLockingGuardSubscriber'; +import { SalesTransactionLockingGuardSubscriber } from './subscribers/SalesTransactionLockingGuardSubscriber'; +import { QueryTransactionsLocking } from './queries/QueryTransactionsLocking'; +import { TransactionsLockingController } from './TransactionsLocking.controller'; + +@Module({ + providers: [ + TransactionsLockingService, + FinancialTransactionLocking, + PurchasesTransactionLockingGuard, + SalesTransactionLockingGuard, + TransactionsLockingGuard, + TransactionsLockingRepository, + FinancialTransactionLockingGuardSubscriber, + PurchasesTransactionLockingGuardSubscriber, + SalesTransactionLockingGuardSubscriber, + QueryTransactionsLocking, + ], + controllers: [TransactionsLockingController], +}) +export class TransactionsLockingModule {} diff --git a/packages/server-nest/src/modules/TransactionsLocking/TransactionsLockingRepository.ts b/packages/server-nest/src/modules/TransactionsLocking/TransactionsLockingRepository.ts new file mode 100644 index 000000000..8ff1b212e --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/TransactionsLockingRepository.ts @@ -0,0 +1,161 @@ +import { isUndefined } from 'lodash'; +import { + ITransactionMeta, + TransactionsLockingGroup, + TransactionsLockingType, +} from './types/TransactionsLocking.types'; +import { parseDate } from 'utils'; +import { Inject, Injectable } from '@nestjs/common'; +import { SettingsStore } from '../Settings/SettingsStore'; +import { SETTINGS } from '../Settings/Settings.module'; + +@Injectable() +export class TransactionsLockingRepository { + constructor( + @Inject(SETTINGS) private readonly settingsStore: SettingsStore, + ) {} + + /** + * Save transactions locking settings + * @param {string} lockingGroup - The group of the transactions locking + * @param {ITransactionMeta} transactionlocking - The transactions locking settings + */ + public async saveTransactionsLocking( + lockingGroup: string = TransactionsLockingGroup.All, + transactionlocking, + ) { + const group = `transactions-locking`; + + if (!isUndefined(transactionlocking.active)) { + this.settingsStore.set({ + group, + key: `${lockingGroup}.active`, + value: transactionlocking.active, + }); + } + if (!isUndefined(transactionlocking.lockToDate)) { + this.settingsStore.set({ + group, + key: `${lockingGroup}.lock_to_date`, + value: parseDate(transactionlocking.lockToDate), + }); + } + if (!isUndefined(transactionlocking.unlockFromDate)) { + this.settingsStore.set({ + group, + key: `${lockingGroup}.unlock_from_date`, + value: parseDate(transactionlocking.unlockFromDate), + }); + } + if (!isUndefined(transactionlocking.unlockToDate)) { + this.settingsStore.set({ + group, + key: `${lockingGroup}.unlock_to_date`, + value: parseDate(transactionlocking.unlockToDate), + }); + } + if (!isUndefined(transactionlocking.lockReason)) { + this.settingsStore.set({ + group, + key: `${lockingGroup}.lock_reason`, + value: transactionlocking.lockReason, + }); + } + if (!isUndefined(transactionlocking.unlockReason)) { + this.settingsStore.set({ + group, + key: `${lockingGroup}.unlock_reason`, + value: transactionlocking.unlockReason, + }); + } + if (!isUndefined(transactionlocking.partialUnlockReason)) { + this.settingsStore.set({ + group, + key: `${lockingGroup}.partial_unlock_reason`, + value: transactionlocking.partialUnlockReason, + }); + } + await this.settingsStore.save(); + } + + /** + * Get transactions locking settings + * @param {string} lockingGroup - The group of the transactions locking + * @returns {ITransactionMeta} - The transactions locking settings + */ + public getTransactionsLocking( + lockingGroup: string = TransactionsLockingGroup.All, + ): ITransactionMeta { + const group = `transactions-locking`; + + const isEnabled = this.settingsStore.get({ + group, + key: `${lockingGroup}.active`, + }); + const lockFromDate = this.settingsStore.get({ + group, + key: `${lockingGroup}.lock_from_date`, + }); + const lockToDate = this.settingsStore.get({ + group, + key: `${lockingGroup}.lock_to_date`, + }); + const unlockFromDate = this.settingsStore.get({ + group, + key: `${lockingGroup}.unlock_from_date`, + }); + const unlockToDate = this.settingsStore.get({ + group, + key: `${lockingGroup}.unlock_to_date`, + }); + const lockReason = this.settingsStore.get({ + group, + key: `${lockingGroup}.lock_reason`, + }); + const unlockReason = this.settingsStore.get({ + group, + key: `${lockingGroup}.unlock_reason`, + }); + const partialUnlockReason = this.settingsStore.get({ + group, + key: `${lockingGroup}.partial_unlock_reason`, + }); + + return { + isEnabled, + lockToDate: lockToDate || null, + unlockFromDate: unlockFromDate || null, + unlockToDate: unlockToDate || null, + isPartialUnlock: Boolean(unlockToDate && unlockFromDate), + lockReason: lockReason || '', + unlockReason: unlockReason || '', + partialUnlockReason: partialUnlockReason || '', + }; + } + + /** + * Get transactions locking type + * @returns {string} - The transactions locking type + */ + public getTransactionsLockingType() { + const lockingType = this.settingsStore.get({ + group: 'transactions-locking', + key: 'locking-type', + }); + return lockingType || 'partial'; + } + + /** + * Flag transactions locking type + * @param {TransactionsLockingType} transactionsType - The transactions locking type + */ + public flagTransactionsLockingType( + transactionsType: TransactionsLockingType, + ) { + this.settingsStore.set({ + group: 'transactions-locking', + key: 'locking-type', + value: transactionsType, + }); + } +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/commands/CommandTransactionsLockingService.ts b/packages/server-nest/src/modules/TransactionsLocking/commands/CommandTransactionsLockingService.ts new file mode 100644 index 000000000..c0879811d --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/commands/CommandTransactionsLockingService.ts @@ -0,0 +1,175 @@ +import { omit } from 'lodash'; +import { + ICancelTransactionsLockingDTO, + ITransactionLockingPartiallyDTO, + ITransactionMeta, + ITransactionsLockingAllDTO, + ITransactionsLockingCanceled, + ITransactionsLockingPartialUnlocked, + TransactionsLockingGroup, + TransactionsLockingType, +} from '../types/TransactionsLocking.types'; +import { TransactionsLockingRepository } from '../TransactionsLockingRepository'; +import { ERRORS } from '../constants'; +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; +import { ServiceError } from '@/modules/Items/ServiceError'; + +const Modules = ['all', 'sales', 'purchases', 'financial']; + +@Injectable() +export class TransactionsLockingService { + constructor( + private readonly transactionsLockingRepo: TransactionsLockingRepository, + private readonly eventPublisher: EventEmitter2, + ) {} + + /** + * Enable/disable all transacations locking. + * @param {TransactionsLockingGroup} module - The transaction locking module. + * @param {Partial} allLockingDTO + * @returns {Promise} + */ + public commandTransactionsLocking = async ( + module: TransactionsLockingGroup = TransactionsLockingGroup.All, + transactionLockingDTO: Partial, + ): Promise => { + // Validate the transaction locking module. + this.validateTransactionsLockingModule(module); + + // Saves all transactions locking settings. + await this.transactionsLockingRepo.saveTransactionsLocking(module, { + active: true, + lockToDate: transactionLockingDTO.lockToDate, + lockReason: transactionLockingDTO.reason, + }); + // Flag transactions locking type. + await this.transactionsLockingRepo.flagTransactionsLockingType( + module === TransactionsLockingGroup.All + ? TransactionsLockingType.All + : TransactionsLockingType.Partial, + ); + // Triggers `onTransactionLockingPartialUnlocked` event. + await this.eventPublisher.emitAsync( + events.transactionsLocking.partialUnlocked, + { + module, + transactionLockingDTO, + } as ITransactionsLockingPartialUnlocked, + ); + // Retrieve the transaction locking meta of the given + return this.transactionsLockingRepo.getTransactionsLocking(module); + }; + + /** + * Cancels the full transactions locking. + * @param {TransactionsLockingGroup} module - The transaction locking module. + * @param {ICancelTransactionsLockingDTO} cancelLockingDTO + * @returns {Promise} + */ + public cancelTransactionLocking = async ( + module: TransactionsLockingGroup = TransactionsLockingGroup.All, + cancelLockingDTO: ICancelTransactionsLockingDTO, + ): Promise => { + // Validate the transaction locking module. + this.validateTransactionsLockingModule(module); + + // Saves transactions locking. + await this.transactionsLockingRepo.saveTransactionsLocking(module, { + active: false, + unlockFromDate: '', + unlockToDate: '', + unlockReason: cancelLockingDTO.reason, + }); + // Reset flag transactions locking type to partial. + await this.transactionsLockingRepo.flagTransactionsLockingType( + TransactionsLockingType.Partial, + ); + // Triggers `onTransactionLockingPartialUnlocked` event. + await this.eventPublisher.emitAsync( + events.transactionsLocking.partialUnlocked, + { + module, + cancelLockingDTO, + } as ITransactionsLockingCanceled, + ); + return this.transactionsLockingRepo.getTransactionsLocking(module); + }; + + /** + * Unlock tranactions locking partially. + * @param {TransactionsLockingGroup} module - The transaction locking module. + * @param {ITransactionLockingPartiallyDTO} partialTransactionLockingDTO + * @returns {Promise} + */ + public unlockTransactionsLockingPartially = async ( + moduleGroup: TransactionsLockingGroup = TransactionsLockingGroup.All, + partialTransactionLockingDTO: ITransactionLockingPartiallyDTO, + ): Promise => { + // Validate the transaction locking module. + this.validateTransactionsLockingModule(moduleGroup); + + // Retrieve the current transactions locking type. + const lockingType = + this.transactionsLockingRepo.getTransactionsLockingType(); + + if (moduleGroup !== TransactionsLockingGroup.All) { + this.validateLockingTypeNotAll(lockingType); + } + // Saves transactions locking settings. + await this.transactionsLockingRepo.saveTransactionsLocking(moduleGroup, { + ...omit(partialTransactionLockingDTO, ['reason']), + partialUnlockReason: partialTransactionLockingDTO.reason, + }); + // Retrieve transaction locking meta of the given module. + return this.transactionsLockingRepo.getTransactionsLocking(moduleGroup); + }; + + /** + * Cancel partial transactions unlocking. + * @param {TransactionsLockingGroup} module - The transaction locking module. + */ + public cancelPartialTransactionsUnlock = async ( + module: TransactionsLockingGroup = TransactionsLockingGroup.All, + ) => { + // Validate the transaction locking module. + this.validateTransactionsLockingModule(module); + + // Saves transactions locking settings. + await this.transactionsLockingRepo.saveTransactionsLocking( + module, + { unlockFromDate: '', unlockToDate: '', partialUnlockReason: '' }, + ); + }; + + /** + * Validates the transaction locking type not partial. + * @param {string} lockingType - The transaction locking type. + */ + public validateLockingTypeNotPartial = (lockingType: string) => { + if (lockingType === TransactionsLockingType.Partial) { + throw new ServiceError(ERRORS.TRANSACTION_LOCKING_PARTIAL); + } + }; + + /** + * Validates the transaction locking type not all. + * @param {string} lockingType - The transaction locking type. + */ + public validateLockingTypeNotAll = (lockingType: string) => { + if (lockingType === TransactionsLockingType.All) { + throw new ServiceError(ERRORS.TRANSACTION_LOCKING_ALL); + } + }; + + /** + * Validate transactions locking module. + * @param {string} module - The transaction locking module. + */ + public validateTransactionsLockingModule = (module: string) => { + if (Modules.indexOf(module) === -1) { + throw new ServiceError(ERRORS.TRANSACTIONS_LOCKING_MODULE_NOT_FOUND); + } + }; +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/constants.ts b/packages/server-nest/src/modules/TransactionsLocking/constants.ts new file mode 100644 index 000000000..01a2bd35e --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/constants.ts @@ -0,0 +1,36 @@ +import { + ITransactionsLockingSchema, + TransactionsLockingGroup, +} from '@/interfaces'; + +export const ERRORS = { + TRANSACTIONS_DATE_LOCKED: 'TRANSACTIONS_DATE_LOCKED', + TRANSACTION_LOCKING_PARTIAL: 'TRANSACTION_LOCKING_PARTIAL', + TRANSACTION_LOCKING_ALL: 'TRANSACTION_LOCKING_ALL', + TRANSACTIONS_LOCKING_MODULE_NOT_FOUND: + 'TRANSACTIONS_LOCKING_MODULE_NOT_FOUND', +}; + +export const TRANSACTIONS_LOCKING_SCHEMA = [ + { + module: 'sales', + formattedModule: 'transactions_locking.module.sales.label', + description: 'transactions_locking.module.sales.desc', + }, + { + module: 'purchases', + formattedModule: 'transactions_locking.module.purchases.label', + description: 'transactions_locking.module.purchases.desc', + }, + { + module: 'financial', + formattedModule: 'transactions_locking.module.financial.label', + description: 'transactions_locking.module.financial.desc', + }, +] as ITransactionsLockingSchema[]; + +export function getTransactionsLockingSchemaMeta( + module: TransactionsLockingGroup +): ITransactionsLockingSchema { + return TRANSACTIONS_LOCKING_SCHEMA.find((schema) => schema.module === module); +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/guards/FinancialTransactionLockingGuard.ts b/packages/server-nest/src/modules/TransactionsLocking/guards/FinancialTransactionLockingGuard.ts new file mode 100644 index 000000000..b86ca9c02 --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/guards/FinancialTransactionLockingGuard.ts @@ -0,0 +1,22 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { TransactionsLockingGroup } from '../types/TransactionsLocking.types'; +import { TransactionsLockingGuard } from './TransactionsLockingGuard'; + +@Injectable() +export class FinancialTransactionLocking { + constructor( + public readonly transactionLockingGuardService: TransactionsLockingGuard, + ) {} + + /** + * Validates the transaction locking of cashflow command action. + * @param {Date} transactionDate - The transaction date. + * @throws {ServiceError(TRANSACTIONS_DATE_LOCKED)} + */ + public transactionLockingGuard = (transactionDate: Date) => { + this.transactionLockingGuardService.transactionsLockingGuard( + transactionDate, + TransactionsLockingGroup.Financial, + ); + }; +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/guards/PurchasesTransactionLockingGuard.ts b/packages/server-nest/src/modules/TransactionsLocking/guards/PurchasesTransactionLockingGuard.ts new file mode 100644 index 000000000..efecaf04c --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/guards/PurchasesTransactionLockingGuard.ts @@ -0,0 +1,23 @@ +import { TransactionsLockingGroup } from '../types/TransactionsLocking.types'; +import { Injectable } from '@nestjs/common'; +import { TransactionsLockingGuard } from './TransactionsLockingGuard'; + +@Injectable() +export class PurchasesTransactionLockingGuard { + constructor( + private readonly transactionLockingGuardService: TransactionsLockingGuard, + ) {} + + /** + * Validates the transaction locking of purchases services commands. + * @param {Date} transactionDate - The transaction date. + */ + public transactionLockingGuard = async ( + transactionDate: Date + ) => { + this.transactionLockingGuardService.transactionsLockingGuard( + transactionDate, + TransactionsLockingGroup.Purchases + ); + }; +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/guards/SalesTransactionLockingGuard.ts b/packages/server-nest/src/modules/TransactionsLocking/guards/SalesTransactionLockingGuard.ts new file mode 100644 index 000000000..318f7f777 --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/guards/SalesTransactionLockingGuard.ts @@ -0,0 +1,23 @@ +import { TransactionsLockingGroup } from '../types/TransactionsLocking.types'; +import { Injectable } from '@nestjs/common'; +import { TransactionsLockingGuard } from './TransactionsLockingGuard'; + +@Injectable() +export class SalesTransactionLockingGuard { + constructor( + private readonly transactionLockingGuardService: TransactionsLockingGuard, + ) {} + + /** + * Validates the transaction locking of sales services commands. + * @param {Date} transactionDate - The transaction date. + */ + public transactionLockingGuard = async ( + transactionDate: Date + ) => { + await this.transactionLockingGuardService.transactionsLockingGuard( + transactionDate, + TransactionsLockingGroup.Sales + ); + }; +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/guards/TransactionsLockingGuard.ts b/packages/server-nest/src/modules/TransactionsLocking/guards/TransactionsLockingGuard.ts new file mode 100644 index 000000000..b7fd8fe42 --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/guards/TransactionsLockingGuard.ts @@ -0,0 +1,105 @@ +import moment from 'moment'; +import { Injectable } from '@nestjs/common'; +import { TransactionsLockingGroup } from '../types/TransactionsLocking.types'; +import { TransactionsLockingRepository } from '../TransactionsLockingRepository'; +import { ERRORS } from '../constants'; +import { ServiceError } from '@/modules/Items/ServiceError'; + +@Injectable() +export class TransactionsLockingGuard { + constructor( + private readonly transactionsLockingRepo: TransactionsLockingRepository, + ) {} + + /** + * Detarmines whether the transaction date between the locking date period. + * @param {Date} transactionDate - The transaction date. + * @param {TransactionsLockingGroup} lockingGroup - The transaction group. + * @returns {boolean} + */ + public isTransactionsLocking = ( + transactionDate: Date, + lockingGroup: string = TransactionsLockingGroup.All + ): boolean => { + const { isEnabled, unlockFromDate, unlockToDate, lockToDate } = + this.transactionsLockingRepo.getTransactionsLocking( + lockingGroup + ); + // Returns false anyway in case if the transaction locking is disabled. + if (!isEnabled) return false; + + const inLockingDate = moment(transactionDate).isSameOrBefore(lockToDate); + const inUnlockDate = + unlockFromDate && unlockToDate + ? moment(transactionDate).isSameOrAfter(unlockFromDate) && + moment(transactionDate).isSameOrBefore(unlockFromDate) + : false; + + // Retruns true in case the transaction date between locking date + // and not between unlocking date. + return !!(isEnabled && inLockingDate && !inUnlockDate); + }; + + /** + * Validates the transaction date between the locking date period + * or throw service error. + * @param {Date} transactionDate - The transaction date. + * @param {TransactionsLockingGroup} lockingGroup - The transaction group. + * + * @throws {ServiceError} + */ + public validateTransactionsLocking = ( + transactionDate: Date, + lockingGroup: TransactionsLockingGroup + ) => { + const isLocked = this.isTransactionsLocking( + transactionDate, + lockingGroup + ); + if (isLocked) { + this.throwTransactionsLockError(lockingGroup); + } + }; + + /** + * Throws transactions locking error. + * @param {TransactionsLockingGroup} lockingGroup - The transaction group. + */ + public throwTransactionsLockError = ( + lockingGroup: TransactionsLockingGroup + ) => { + const { lockToDate } = this.transactionsLockingRepo.getTransactionsLocking( + lockingGroup + ); + throw new ServiceError(ERRORS.TRANSACTIONS_DATE_LOCKED, null, { + lockedToDate: lockToDate, + formattedLockedToDate: moment(lockToDate).format('YYYY/MM/DD'), + }); + }; + + /** + * Validate the transaction locking of the given locking group and transaction date. + * @param {TransactionsLockingGroup} lockingGroup - The transaction group. + * @param {Date} transactionDate - The transaction date. + */ + public transactionsLockingGuard = ( + transactionDate: Date, + moduleType: TransactionsLockingGroup + ) => { + const lockingType = + this.transactionsLockingRepo.getTransactionsLockingType(); + + // + if (lockingType === TransactionsLockingGroup.All) { + return this.validateTransactionsLocking( + transactionDate, + TransactionsLockingGroup.All + ); + } + // + return this.validateTransactionsLocking( + transactionDate, + moduleType + ); + }; +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/queries/QueryTransactionsLocking.ts b/packages/server-nest/src/modules/TransactionsLocking/queries/QueryTransactionsLocking.ts new file mode 100644 index 000000000..166c06634 --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/queries/QueryTransactionsLocking.ts @@ -0,0 +1,81 @@ +import { Injectable } from '@nestjs/common'; +import { + ITransactionLockingMetaPOJO, + ITransactionsLockingListPOJO, + ITransactionsLockingSchema, + TransactionsLockingGroup, +} from '../types/TransactionsLocking.types'; +import { TRANSACTIONS_LOCKING_SCHEMA } from '../constants'; +import { TransactionsLockingRepository } from '../TransactionsLockingRepository'; +import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; +import { TransactionsLockingMetaTransformer } from './TransactionsLockingMetaTransformer'; + +@Injectable() +export class QueryTransactionsLocking { + constructor( + private readonly transactionsLockingRepo: TransactionsLockingRepository, + private readonly transformer: TransformerInjectable, + ) {} + + /** + * Retrieve transactions locking modules. + * @returns {ITransactionLockingMetaPOJO[]} + */ + public getTransactionsLockingModules = (): Promise< + ITransactionLockingMetaPOJO[] + > => { + const modules = TRANSACTIONS_LOCKING_SCHEMA.map( + (schema: ITransactionsLockingSchema) => + this.getTransactionsLockingModuleMeta(schema.module), + ); + return Promise.all(modules); + }; + + /** + * Retireve the transactions locking all module. + * @returns {ITransactionLockingMetaPOJO} + */ + public getTransactionsLockingAll = + (): Promise => { + return this.getTransactionsLockingModuleMeta( + TransactionsLockingGroup.All, + ); + }; + + /** + * Retrieve the transactions locking module meta. + * @param {number} tenantId - + * @param {TransactionsLockingGroup} module - + * @returns {ITransactionLockingMetaPOJO} + */ + public getTransactionsLockingModuleMeta = ( + module: TransactionsLockingGroup, + ): Promise => { + const meta = this.transactionsLockingRepo.getTransactionsLocking(module); + return this.transformer.transform( + meta, + new TransactionsLockingMetaTransformer(), + { module }, + ); + }; + + /** + * Retrieve transactions locking list. + * @returns {Promise} + */ + public getTransactionsLockingList = + async (): Promise => { + // Retrieve the current transactions locking type. + const lockingType = + this.transactionsLockingRepo.getTransactionsLockingType(); + + const all = await this.getTransactionsLockingAll(); + const modules = await this.getTransactionsLockingModules(); + + return { + lockingType, + all, + modules, + }; + }; +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/queries/TransactionsLockingMetaTransformer.ts b/packages/server-nest/src/modules/TransactionsLocking/queries/TransactionsLockingMetaTransformer.ts new file mode 100644 index 000000000..42c911246 --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/queries/TransactionsLockingMetaTransformer.ts @@ -0,0 +1,83 @@ +import { get } from 'lodash'; +import { getTransactionsLockingSchemaMeta } from '../constants'; +import { Transformer } from '@/modules/Transformer/Transformer'; +import { TransactionsLockingGroup } from '../types/TransactionsLocking.types'; + +export class TransactionsLockingMetaTransformer extends Transformer { + /** + * Include these attributes to sale credit note object. + * @returns {Array} + */ + public includeAttributes = (): string[] => { + return [ + 'module', + 'formattedModule', + 'description', + 'formattedLockToDate', + 'formattedUnlockFromDate', + 'formattedUnlockToDate', + ]; + }; + + /** + * Module slug. + * @returns {string} + */ + protected module = () => { + return this.options.module; + }; + + /** + * Formatted module name. + * @returns {string} + */ + protected formattedModule = () => { + return this.options.module === TransactionsLockingGroup.All + ? this.context.i18n.t('transactions_locking.module.all_transactions') + : this.context.i18n.t( + get( + getTransactionsLockingSchemaMeta(this.options.module), + 'formattedModule', + ), + ); + }; + + /** + * Module description. + * @returns {string} + */ + protected description = () => { + return this.options.module === TransactionsLockingGroup.All + ? '' + : this.context.i18n.t( + get( + getTransactionsLockingSchemaMeta(this.options.module), + 'description', + ), + ); + }; + + /** + * Formatted unlock to date. + * @returns {string} + */ + protected formattedUnlockToDate = (item) => { + return item.unlockToDate ? this.formatDate(item.unlockToDate) : ''; + }; + + /** + * Formatted unlock from date. + * @returns {string} + */ + protected formattedUnlockFromDate = (item) => { + return item.unlockFromDate ? this.formatDate(item.unlockFromDate) : ''; + }; + + /** + * Formatted lock to date. + * @returns {string} + */ + protected formattedLockToDate = (item) => { + return item.lockToDate ? this.formatDate(item.lockToDate) : ''; + }; +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/subscribers/FinancialsTransactionLockingGuardSubscriber.ts b/packages/server-nest/src/modules/TransactionsLocking/subscribers/FinancialsTransactionLockingGuardSubscriber.ts new file mode 100644 index 000000000..a05a3a6a2 --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/subscribers/FinancialsTransactionLockingGuardSubscriber.ts @@ -0,0 +1,261 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { FinancialTransactionLocking } from '../guards/FinancialTransactionLockingGuard'; +import { + IManualJournalCreatingPayload, + IManualJournalEditingPayload, + IManualJournalPublishingPayload, +} from '@/modules/ManualJournals/types/ManualJournals.types'; +import { + IExpenseCreatingPayload, + IExpenseDeletingPayload, + IExpenseEventEditingPayload, + IExpensePublishingPayload, +} from '@/modules/Expenses/Expenses.types'; +import { + ICommandCashflowCreatingPayload, + ICommandCashflowDeletingPayload, +} from '@/modules/BankingTransactions/types/BankingTransactions.types'; +import { events } from '@/common/events/events'; + +@Injectable() +export class FinancialTransactionLockingGuardSubscriber { + constructor( + public readonly financialTransactionsLocking: FinancialTransactionLocking, + ) {} + + /** + * --------------------------------------------- + * - MANUAL JOURNALS SERVICE. + * --------------------------------------------- + */ + /** + * Transaction locking guard on manual journal creating. + * @param {IManualJournalCreatingPayload} payload + */ + @OnEvent(events.manualJournals.onCreating) + public async transactionsLockingGuardOnManualJournalCreating({ + manualJournalDTO, + }: IManualJournalCreatingPayload) { + // Can't continue if the new journal is not published yet. + if (!manualJournalDTO.publish) return; + + await this.financialTransactionsLocking.transactionLockingGuard( + manualJournalDTO.date, + ); + } + + /** + * Transactions locking guard on manual journal deleting. + * @param {IManualJournalEditingPayload} payload + */ + @OnEvent(events.manualJournals.onDeleting) + public async transactionsLockingGuardOnManualJournalDeleting({ + oldManualJournal, + }: IManualJournalEditingPayload) { + // Can't continue if the old journal is not published. + if (!oldManualJournal.isPublished) return; + + await this.financialTransactionsLocking.transactionLockingGuard( + oldManualJournal.date, + ); + } + + /** + * Transactions locking guard on manual journal editing. + * @param {IManualJournalDeletingPayload} payload + */ + @OnEvent(events.manualJournals.onEditing) + public async transactionsLockingGuardOnManualJournalEditing({ + oldManualJournal, + manualJournalDTO, + }: IManualJournalEditingPayload) { + // Can't continue if the old and new journal are not published. + if (!oldManualJournal.isPublished && !manualJournalDTO.publish) return; + + // Validate the old journal date. + await this.financialTransactionsLocking.transactionLockingGuard( + oldManualJournal.date, + ); + // Validate the new journal date. + await this.financialTransactionsLocking.transactionLockingGuard( + manualJournalDTO.date, + ); + } + + /** + * Transactions locking guard on manual journal publishing. + * @param {IManualJournalPublishingPayload} + */ + @OnEvent(events.manualJournals.onPublishing) + public async transactionsLockingGuardOnManualJournalPublishing({ + oldManualJournal, + }: IManualJournalPublishingPayload) { + await this.financialTransactionsLocking.transactionLockingGuard( + oldManualJournal.date, + ); + } + + /** + * --------------------------------------------- + * - EXPENSES SERVICE. + * --------------------------------------------- + */ + + /** + * Transactions locking guard on expense creating. + * @param {IExpenseCreatingPayload} payload + */ + @OnEvent(events.expenses.onCreating) + public async transactionsLockingGuardOnExpenseCreating({ + expenseDTO, + }: IExpenseCreatingPayload) { + // Can't continue if the new expense is not published yet. + if (!expenseDTO.publish) return; + + await this.financialTransactionsLocking.transactionLockingGuard( + expenseDTO.paymentDate, + ); + } + + /** + * Transactions locking guard on expense deleting. + * @param {IExpenseDeletingPayload} payload + */ + @OnEvent(events.expenses.onDeleting) + public async transactionsLockingGuardOnExpenseDeleting({ + oldExpense, + }: IExpenseDeletingPayload) { + // Can't continue if expense transaction is not published. + if (!oldExpense.isPublished) return; + + await this.financialTransactionsLocking.transactionLockingGuard( + oldExpense.paymentDate, + ); + } + + /** + * Transactions locking guard on expense editing. + * @param {IExpenseEventEditingPayload} + */ + @OnEvent(events.expenses.onEditing) + public async transactionsLockingGuardOnExpenseEditing({ + oldExpense, + expenseDTO, + }: IExpenseEventEditingPayload) { + // Can't continue if the old and new expense is not published. + if (!oldExpense.isPublished && !expenseDTO.publish) return; + + // Validate the old expense date. + await this.financialTransactionsLocking.transactionLockingGuard( + oldExpense.paymentDate, + ); + // Validate the new expense date. + await this.financialTransactionsLocking.transactionLockingGuard( + expenseDTO.paymentDate, + ); + } + + /** + * Transactions locking guard on expense publishing. + * @param {IExpensePublishingPayload} payload - + */ + @OnEvent(events.expenses.onPublishing) + public async transactionsLockingGuardOnExpensePublishing({ + oldExpense, + }: IExpensePublishingPayload) { + await this.financialTransactionsLocking.transactionLockingGuard( + oldExpense.paymentDate, + ); + } + + /** + * --------------------------------------------- + * - CASHFLOW SERVICE. + * --------------------------------------------- + */ + + /** + * Transactions locking guard on cashflow transaction creating. + * @param {ICommandCashflowCreatingPayload} + */ + @OnEvent(events.cashflow.onTransactionCreating) + public async transactionsLockingGuardOnCashflowTransactionCreating({ + newTransactionDTO, + }: ICommandCashflowCreatingPayload) { + if (!newTransactionDTO.publish) return; + + await this.financialTransactionsLocking.transactionLockingGuard( + newTransactionDTO.date, + ); + } + + /** + * Transactions locking guard on cashflow transaction deleting. + * @param {ICommandCashflowDeletingPayload} + */ + @OnEvent(events.cashflow.onTransactionDeleting) + public async transactionsLockingGuardOnCashflowTransactionDeleting({ + oldCashflowTransaction, + }: ICommandCashflowDeletingPayload) { + // Can't continue if the cashflow transaction is not published. + if (!oldCashflowTransaction.isPublished) return; + + await this.financialTransactionsLocking.transactionLockingGuard( + oldCashflowTransaction.date, + ); + } + + /** + * --------------------------------------------- + * - INVENTORY ADJUSTMENT SERVICE. + * --------------------------------------------- + */ + + /** + * Transactions locking guard on inventory adjustment creating. + * @param {IInventoryAdjustmentCreatingPayload} payload - + */ + // @OnEvent(events.inventoryAdjustment.onQuickCreating) + // public async transactionsLockingGuardOnInventoryAdjCreating({ + // tenantId, + // quickAdjustmentDTO, + // }: IInventoryAdjustmentCreatingPayload) { + // // Can't locking if the new adjustment is not published yet. + // if (!quickAdjustmentDTO.publish) return; + + // await this.financialTransactionsLocking.transactionLockingGuard( + // tenantId, + // quickAdjustmentDTO.date + // ); + // } + + // /** + // * Transaction locking guard on inventory adjustment deleting. + // * @param {IInventoryAdjustmentDeletingPayload} payload + // */ + // @OnEvent(events.inventoryAdjustment.onDeleting) + // public async transactionsLockingGuardOnInventoryAdjDeleting({ + // oldInventoryAdjustment, + // }: IInventoryAdjustmentDeletingPayload) { + // // Can't locking if the adjustment is published yet. + // if (!oldInventoryAdjustment.isPublished) return; + + // await this.financialTransactionsLocking.transactionLockingGuard( + // oldInventoryAdjustment.date + // ); + // } + + // /** + // * Transaction locking guard on inventory adjustment publishing. + // * @param {IInventoryAdjustmentPublishingPayload} payload + // */ + // @OnEvent(events.inventoryAdjustment.onPublishing) + // public async transactionsLockingGuardOnInventoryAdjPublishing({ + // oldInventoryAdjustment, + // }: IInventoryAdjustmentPublishingPayload) { + // await this.financialTransactionsLocking.transactionLockingGuard( + // oldInventoryAdjustment.date + // ); + // } +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/subscribers/PurchasesTransactionLockingGuardSubscriber.ts b/packages/server-nest/src/modules/TransactionsLocking/subscribers/PurchasesTransactionLockingGuardSubscriber.ts new file mode 100644 index 000000000..87a26ce7f --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/subscribers/PurchasesTransactionLockingGuardSubscriber.ts @@ -0,0 +1,226 @@ +import { Injectable } from '@nestjs/common'; +import { events } from '@/common/events/events'; +import { + IRefundVendorCreditCreatingPayload, + IRefundVendorCreditDeletingPayload, +} from '@/modules/VendorCreditsRefund/types/VendorCreditRefund.types'; +import { + IVendorCreditCreatingPayload, + IVendorCreditDeletingPayload, + IVendorCreditEditingPayload, +} from '@/modules/VendorCredit/types/VendorCredit.types'; +import { + IBillCreatingPayload, + IBillEditingPayload, + IBillEventDeletingPayload, +} from '@/modules/Bills/Bills.types'; +import { + IBillPaymentCreatingPayload, + IBillPaymentEditingPayload, +} from '@/modules/BillPayments/types/BillPayments.types'; +import { IBillPaymentDeletingPayload } from '@/modules/BillPayments/types/BillPayments.types'; +import { PurchasesTransactionLockingGuard } from '../guards/PurchasesTransactionLockingGuard'; +import { OnEvent } from '@nestjs/event-emitter'; + +@Injectable() +export class PurchasesTransactionLockingGuardSubscriber { + constructor( + public readonly purchasesTransactionsLocking: PurchasesTransactionLockingGuard, + ) {} + + /** + * --------------------------------------------- + * PAYMENT MADES. + * --------------------------------------------- + */ + /** + * Transaction locking guard on payment editing. + * @param {IBillPaymentEditingPayload} + */ + @OnEvent(events.billPayment.onEditing) + public async transactionLockingGuardOnPaymentEditing({ + oldBillPayment, + billPaymentDTO, + }: IBillPaymentEditingPayload) { + // Validate old payment date. + await this.purchasesTransactionsLocking.transactionLockingGuard( + oldBillPayment.paymentDate, + ); + // Validate the new payment date. + await this.purchasesTransactionsLocking.transactionLockingGuard( + billPaymentDTO.paymentDate, + ); + } + + /** + * Transaction locking guard on payment creating. + * @param {IBillPaymentCreatingPayload} + */ + @OnEvent(events.billPayment.onCreating) + public async transactionLockingGuardOnPaymentCreating({ + billPaymentDTO, + }: IBillPaymentCreatingPayload) { + await this.purchasesTransactionsLocking.transactionLockingGuard( + billPaymentDTO.paymentDate, + ); + } + + /** + * Transaction locking guard on payment deleting. + * @param {IBillPaymentDeletingPayload} payload - + */ + @OnEvent(events.billPayment.onDeleting) + public async transactionLockingGuardOnPaymentDeleting({ + oldBillPayment, + }: IBillPaymentDeletingPayload) { + await this.purchasesTransactionsLocking.transactionLockingGuard( + oldBillPayment.paymentDate, + ); + } + + /** + * --------------------------------------------- + * BILLS. + * --------------------------------------------- + */ + + /** + * Transaction locking guard on bill creating. + * @param {IBillCreatingPayload} payload + */ + @OnEvent(events.bill.onCreating) + public async transactionLockingGuardOnBillCreating({ + billDTO, + }: IBillCreatingPayload) { + // Can't continue if the new bill is not published. + if (!billDTO.open) return; + + await this.purchasesTransactionsLocking.transactionLockingGuard( + billDTO.billDate, + ); + } + + /** + * Transaction locking guard on bill editing. + * @param {IBillEditingPayload} payload + */ + @OnEvent(events.bill.onEditing) + public async transactionLockingGuardOnBillEditing({ + oldBill, + billDTO, + }: IBillEditingPayload) { + // Can't continue if the old and new bill are not published. + if (!oldBill.isOpen && !billDTO.open) return; + + // Validate the old bill date. + await this.purchasesTransactionsLocking.transactionLockingGuard( + oldBill.billDate, + ); + // Validate the new bill date. + await this.purchasesTransactionsLocking.transactionLockingGuard( + billDTO.billDate, + ); + } + + /** + * Transaction locking guard on bill deleting. + * @param {IBillEventDeletingPayload} payload + */ + @OnEvent(events.bill.onDeleting) + public async transactionLockingGuardOnBillDeleting({ + oldBill, + }: IBillEventDeletingPayload) { + // Can't continue if the old bill is not published. + if (!oldBill.isOpen) return; + + await this.purchasesTransactionsLocking.transactionLockingGuard( + oldBill.billDate, + ); + } + + /** + * --------------------------------------------- + * VENDOR CREDITS. + * --------------------------------------------- + */ + + /** + * Transaction locking guard on vendor credit creating. + * @param {IVendorCreditCreatingPayload} payload + */ + @OnEvent(events.vendorCredit.onCreating) + public async transactionLockingGuardOnVendorCreditCreating({ + vendorCreditCreateDTO, + }: IVendorCreditCreatingPayload) { + // Can't continue if the new vendor credit is not published. + if (!vendorCreditCreateDTO.open) return; + + await this.purchasesTransactionsLocking.transactionLockingGuard( + vendorCreditCreateDTO.vendorCreditDate, + ); + } + + /** + * Transaction locking guard on vendor credit deleting. + * @param {IVendorCreditDeletingPayload} payload + */ + @OnEvent(events.vendorCredit.onDeleting) + public async transactionLockingGuardOnVendorCreditDeleting({ + oldVendorCredit, + }: IVendorCreditDeletingPayload) { + // Can't continue if the old vendor credit is not open. + if (!oldVendorCredit.isOpen) return; + + await this.purchasesTransactionsLocking.transactionLockingGuard( + oldVendorCredit.vendorCreditDate, + ); + } + + /** + * Transaction locking guard on vendor credit editing. + * @param {IVendorCreditEditingPayload} payload + */ + @OnEvent(events.vendorCredit.onEditing) + public async transactionLockingGuardOnVendorCreditEditing({ + oldVendorCredit, + vendorCreditDTO, + }: IVendorCreditEditingPayload) { + // Can't continue if the old and new vendor credit are not published. + if (!oldVendorCredit.isPublished && !vendorCreditDTO.open) return; + + // Validate the old credit date. + await this.purchasesTransactionsLocking.transactionLockingGuard( + oldVendorCredit.vendorCreditDate, + ); + // Validate the new credit date. + await this.purchasesTransactionsLocking.transactionLockingGuard( + vendorCreditDTO.vendorCreditDate, + ); + } + + /** + * Transaction locking guard on refund vendor credit creating. + * @param {IRefundVendorCreditCreatingPayload} payload - + */ + @OnEvent(events.vendorCredit.onRefundCreating) + public async transactionLockingGuardOnRefundVendorCredit({ + refundVendorCreditDTO, + }: IRefundVendorCreditCreatingPayload) { + await this.purchasesTransactionsLocking.transactionLockingGuard( + refundVendorCreditDTO.date, + ); + } + + /** + * Transaction locking guard on refund vendor credit deleting. + * @param {IRefundVendorCreditDeletingPayload} payload + */ + @OnEvent(events.vendorCredit.onRefundDeleting) + public async transactionLockingGuardOnRefundCreditDeleting({ + oldRefundCredit, + }: IRefundVendorCreditDeletingPayload) { + await this.purchasesTransactionsLocking.transactionLockingGuard( + oldRefundCredit.date, + ); + } +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/subscribers/SalesTransactionLockingGuardSubscriber.ts b/packages/server-nest/src/modules/TransactionsLocking/subscribers/SalesTransactionLockingGuardSubscriber.ts new file mode 100644 index 000000000..de980278f --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/subscribers/SalesTransactionLockingGuardSubscriber.ts @@ -0,0 +1,387 @@ +import { events } from '@/common/events/events'; +import { SalesTransactionLockingGuard } from '../guards/SalesTransactionLockingGuard'; +import { OnEvent } from '@nestjs/event-emitter'; +import { Injectable } from '@nestjs/common'; +import { + ISaleInvoiceCreatingPaylaod, + ISaleInvoiceDeletePayload, + ISaleInvoiceEditingPayload, + ISaleInvoiceWriteoffCreatePayload, + ISaleInvoiceWrittenOffCancelPayload, +} from '@/modules/SaleInvoices/SaleInvoice.types'; +import { + IPaymentReceivedCreatingPayload, + IPaymentReceivedEditingPayload, + IPaymentReceivedDeletingPayload, +} from '@/modules/PaymentReceived/types/PaymentReceived.types'; +import { + ISaleEstimateCreatingPayload, + ISaleEstimateDeletingPayload, +} from '@/modules/SaleEstimates/types/SaleEstimates.types'; +import { ISaleEstimateEditingPayload } from '@/modules/SaleEstimates/types/SaleEstimates.types'; +import { IRefundCreditNoteDeletingPayload } from '@/modules/CreditNoteRefunds/types/CreditNoteRefunds.types'; +import { IRefundCreditNoteCreatingPayload } from '@/modules/CreditNoteRefunds/types/CreditNoteRefunds.types'; +import { + ICreditNoteCreatingPayload, + ICreditNoteDeletingPayload, +} from '@/modules/CreditNotes/types/CreditNotes.types'; +import { ICreditNoteEditingPayload } from '@/modules/CreditNotes/types/CreditNotes.types'; +import { + ISaleReceiptCreatingPayload, + ISaleReceiptDeletingPayload, + ISaleReceiptEditingPayload, +} from '@/modules/SaleReceipts/types/SaleReceipts.types'; +import { ISaleReceiptEventClosingPayload } from '@/modules/SaleReceipts/types/SaleReceipts.types'; + +@Injectable() +export class SalesTransactionLockingGuardSubscriber { + constructor( + public readonly salesLockingGuard: SalesTransactionLockingGuard, + ) {} + + /** + * --------------------------------------------- + * SALES INVOICES. + * --------------------------------------------- + */ + /** + * Transaction locking guard on invoice creating. + * @param {ISaleInvoiceCreatingPaylaod} payload + */ + @OnEvent(events.saleInvoice.onCreating) + public async transactionLockingGuardOnInvoiceCreating({ + saleInvoiceDTO, + }: ISaleInvoiceCreatingPaylaod) { + // Can't continue if the new invoice is not published yet. + if (!saleInvoiceDTO.delivered) return; + + await this.salesLockingGuard.transactionLockingGuard( + saleInvoiceDTO.invoiceDate, + ); + } + + /** + * Transaction locking guard on invoice editing. + * @param {ISaleInvoiceEditingPayload} payload + */ + @OnEvent(events.saleInvoice.onEditing) + public async transactionLockingGuardOnInvoiceEditing({ + oldSaleInvoice, + saleInvoiceDTO, + }: ISaleInvoiceEditingPayload) { + // Can't continue if the old and new invoice are not published yet. + if (!oldSaleInvoice.isDelivered && !saleInvoiceDTO.delivered) return; + + // Validate the old invoice date. + await this.salesLockingGuard.transactionLockingGuard( + oldSaleInvoice.invoiceDate, + ); + // Validate the new invoice date. + await this.salesLockingGuard.transactionLockingGuard( + saleInvoiceDTO.invoiceDate, + ); + } + + /** + * Transaction locking guard on invoice deleting. + * @param {ISaleInvoiceDeletePayload} payload + */ + @OnEvent(events.saleInvoice.onDelete) + public async transactionLockingGuardOnInvoiceDeleting({ + oldSaleInvoice, + }: ISaleInvoiceDeletePayload) { + // Can't continue if the old invoice not published. + if (!oldSaleInvoice.isDelivered) return; + + await this.salesLockingGuard.transactionLockingGuard( + oldSaleInvoice.invoiceDate, + ); + } + + /** + * Transaction locking guard on invoice writingoff. + * @param {ISaleInvoiceWriteoffCreatePayload} payload + */ + @OnEvent(events.saleInvoice.onWriteoff) + public async transactionLockinGuardOnInvoiceWritingoff({ + saleInvoice, + }: ISaleInvoiceWriteoffCreatePayload) { + await this.salesLockingGuard.transactionLockingGuard( + saleInvoice.invoiceDate, + ); + } + + /** + * Transaciton locking guard on canceling written-off invoice. + * @param {ISaleInvoiceWrittenOffCancelPayload} payload + */ + @OnEvent(events.saleInvoice.onWrittenoffCancel) + public async transactionLockinGuardOnInvoiceWritingoffCanceling({ + saleInvoice, + }: ISaleInvoiceWrittenOffCancelPayload) { + await this.salesLockingGuard.transactionLockingGuard( + saleInvoice.invoiceDate, + ); + } + + /** + * --------------------------------------------- + * SALES RECEIPTS. + * --------------------------------------------- + */ + + /** + * Transaction locking guard on receipt creating. + * @param {ISaleReceiptCreatingPayload} + */ + @OnEvent(events.saleReceipt.onCreating) + public async transactionLockingGuardOnReceiptCreating({ + saleReceiptDTO, + }: ISaleReceiptCreatingPayload) { + // Can't continue if the sale receipt is not published. + if (!saleReceiptDTO.closed) return; + + await this.salesLockingGuard.transactionLockingGuard( + saleReceiptDTO.receiptDate, + ); + } + + /** + * Transaction locking guard on receipt creating. + * @param {ISaleReceiptDeletingPayload} + */ + @OnEvent(events.saleReceipt.onDeleting) + public async transactionLockingGuardOnReceiptDeleting({ + oldSaleReceipt, + }: ISaleReceiptDeletingPayload) { + if (!oldSaleReceipt.isClosed) return; + + await this.salesLockingGuard.transactionLockingGuard( + oldSaleReceipt.receiptDate, + ); + } + + /** + * Transaction locking guard on sale receipt editing. + * @param {ISaleReceiptEditingPayload} payload + */ + @OnEvent(events.saleReceipt.onEditing) + public async transactionLockingGuardOnReceiptEditing({ + oldSaleReceipt, + saleReceiptDTO, + }: ISaleReceiptEditingPayload) { + // Validate the old receipt date. + await this.salesLockingGuard.transactionLockingGuard( + oldSaleReceipt.receiptDate, + ); + // Validate the new receipt date. + await this.salesLockingGuard.transactionLockingGuard( + saleReceiptDTO.receiptDate, + ); + } + + /** + * Transaction locking guard on sale receipt closing. + * @param {ISaleReceiptEventClosingPayload} payload + */ + @OnEvent(events.saleReceipt.onClosing) + public async transactionLockingGuardOnReceiptClosing({ + oldSaleReceipt, + }: ISaleReceiptEventClosingPayload) { + await this.salesLockingGuard.transactionLockingGuard( + oldSaleReceipt.receiptDate, + ); + } + + /** + * --------------------------------------------- + * CREDIT NOTES. + * --------------------------------------------- + */ + + /** + * Transaction locking guard on credit note deleting. + * @param {ICreditNoteDeletingPayload} payload - + */ + @OnEvent(events.creditNote.onDeleting) + public async transactionLockingGuardOnCreditDeleting({ + oldCreditNote, + }: ICreditNoteDeletingPayload) { + // Can't continue if the old credit is not published. + if (!oldCreditNote.isPublished) return; + + await this.salesLockingGuard.transactionLockingGuard( + oldCreditNote.creditNoteDate, + ); + } + + /** + * Transaction locking guard on credit note creating. + * @param {ICreditNoteCreatingPayload} payload + */ + @OnEvent(events.creditNote.onCreating) + public async transactionLockingGuardOnCreditCreating({ + creditNoteDTO, + }: ICreditNoteCreatingPayload) { + // Can't continue if the new credit is still draft. + if (!creditNoteDTO.open) return; + + await this.salesLockingGuard.transactionLockingGuard( + creditNoteDTO.creditNoteDate, + ); + } + + /** + * Transaction locking guard on credit note editing. + * @param {ICreditNoteEditingPayload} payload - + */ + @OnEvent(events.creditNote.onEditing) + public async transactionLockingGuardOnCreditEditing({ + creditNoteEditDTO, + oldCreditNote, + }: ICreditNoteEditingPayload) { + // Can't continue if the new and old credit note are not published yet. + if (!creditNoteEditDTO.open && !oldCreditNote.isPublished) return; + + // Validate the old credit date. + await this.salesLockingGuard.transactionLockingGuard( + oldCreditNote.creditNoteDate, + ); + // Validate the new credit date. + await this.salesLockingGuard.transactionLockingGuard( + creditNoteEditDTO.creditNoteDate, + ); + } + + /** + * Transaction locking guard on payment deleting. + * @param {IRefundCreditNoteDeletingPayload} paylaod - + */ + @OnEvent(events.creditNote.onRefundDeleting) + public async transactionLockingGuardOnCreditRefundDeleteing({ + oldRefundCredit, + }: IRefundCreditNoteDeletingPayload) { + await this.salesLockingGuard.transactionLockingGuard(oldRefundCredit.date); + } + + /** + * Transaction locking guard on refund credit note creating. + * @param {IRefundCreditNoteCreatingPayload} payload - + */ + @OnEvent(events.creditNote.onRefundCreating) + public async transactionLockingGuardOnCreditRefundCreating({ + newCreditNoteDTO, + }: IRefundCreditNoteCreatingPayload) { + await this.salesLockingGuard.transactionLockingGuard(newCreditNoteDTO.date); + } + + /** + * --------------------------------------------- + * SALES ESTIMATES. + * --------------------------------------------- + */ + /** + * Transaction locking guard on estimate creating. + * @param {ISaleEstimateCreatingPayload} payload - + */ + @OnEvent(events.saleEstimate.onCreating) + public async transactionLockingGuardOnEstimateCreating({ + estimateDTO, + }: ISaleEstimateCreatingPayload) { + // Can't continue if the new estimate is not published yet. + if (!estimateDTO.delivered) return; + + await this.salesLockingGuard.transactionLockingGuard( + estimateDTO.estimateDate, + ); + } + + /** + * Transaction locking guard on estimate deleting. + * @param {ISaleEstimateDeletingPayload} payload + */ + @OnEvent(events.saleEstimate.onDeleting) + public async transactionLockingGuardOnEstimateDeleting({ + oldSaleEstimate, + }: ISaleEstimateDeletingPayload) { + // Can't continue if the old estimate is not published. + if (!oldSaleEstimate.isDelivered) return; + + await this.salesLockingGuard.transactionLockingGuard( + oldSaleEstimate.estimateDate, + ); + } + + /** + * Transaction locking guard on estimate editing. + * @param {ISaleEstimateEditingPayload} payload + */ + @OnEvent(events.saleEstimate.onEditing) + public async transactionLockingGuardOnEstimateEditing({ + oldSaleEstimate, + estimateDTO, + }: ISaleEstimateEditingPayload) { + // Can't continue if the new and old estimate transactions are not published yet. + if (!estimateDTO.delivered && !oldSaleEstimate.isDelivered) return; + + // Validate the old estimate date. + await this.salesLockingGuard.transactionLockingGuard( + oldSaleEstimate.estimateDate, + ); + // Validate the new estimate date. + await this.salesLockingGuard.transactionLockingGuard( + estimateDTO.estimateDate, + ); + } + + /** + * --------------------------------------------- + * PAYMENT RECEIVES. + * --------------------------------------------- + */ + + /** + * Transaction locking guard on payment receive editing. + * @param {IPaymentReceivedEditingPayload} + */ + @OnEvent(events.paymentReceive.onEditing) + public async transactionLockingGuardOnPaymentEditing({ + oldPaymentReceive, + paymentReceiveDTO, + }: IPaymentReceivedEditingPayload) { + // Validate the old payment date. + await this.salesLockingGuard.transactionLockingGuard( + oldPaymentReceive.paymentDate, + ); + // Validate the new payment date. + await this.salesLockingGuard.transactionLockingGuard( + paymentReceiveDTO.paymentDate, + ); + } + + /** + * Transaction locking guard on payment creating. + * @param {IPaymentReceivedCreatingPayload} + */ + @OnEvent(events.paymentReceive.onCreating) + public async transactionLockingGuardOnPaymentCreating({ + paymentReceiveDTO, + }: IPaymentReceivedCreatingPayload) { + await this.salesLockingGuard.transactionLockingGuard( + paymentReceiveDTO.paymentDate, + ); + } + + /** + * Transaction locking guard on payment deleting. + * @param {IPaymentReceivedDeletingPayload} payload - + */ + @OnEvent(events.paymentReceive.onDeleting) + public async transactionLockingGuardPaymentDeleting({ + oldPaymentReceive, + }: IPaymentReceivedDeletingPayload) { + await this.salesLockingGuard.transactionLockingGuard( + oldPaymentReceive.paymentDate, + ); + } +} diff --git a/packages/server-nest/src/modules/TransactionsLocking/types/TransactionsLocking.types.ts b/packages/server-nest/src/modules/TransactionsLocking/types/TransactionsLocking.types.ts new file mode 100644 index 000000000..9b57d9b23 --- /dev/null +++ b/packages/server-nest/src/modules/TransactionsLocking/types/TransactionsLocking.types.ts @@ -0,0 +1,71 @@ +export interface ITransactionsLockingAllDTO { + lockToDate: Date; + reason: string; +} +export interface ITransactionsLockingCashflowDTO {} +export interface ITransactionsLockingSalesDTO {} +export interface ITransactionsLockingPurchasesDTO {} + +export enum TransactionsLockingGroup { + All = 'all', + Sales = 'sales', + Purchases = 'purchases', + Financial = 'financial', +} + +export enum TransactionsLockingType { + Partial = 'partial', + All = 'all', +} + +export interface ITransactionsLockingPartialUnlocked { + tenantId: number; + module: TransactionsLockingGroup; + transactionLockingDTO: ITransactionsLockingAllDTO; +} + +export interface ITransactionsLockingCanceled { + tenantId: number; + module: TransactionsLockingGroup; + cancelLockingDTO: ICancelTransactionsLockingDTO; +} + +export interface ITransactionLockingPartiallyDTO { + unlockFromDate: Date; + unlockToDate: Date; + reason: string; +} +export interface ICancelTransactionsLockingDTO { + reason: string; +} +export interface ITransactionMeta { + isEnabled: boolean; + isPartialUnlock: boolean; + lockToDate: Date; + unlockFromDate: string; + unlockToDate: string; + lockReason: string; + unlockReason: string; + partialUnlockReason: string; +} + +export interface ITransactionLockingMetaPOJO { + module: string; + formattedModule: string; + description: string; + + formattedLockToDate: Date; + formattedUnlockFromDate: string; + formattedunlockToDate: string; +} +export interface ITransactionsLockingListPOJO { + lockingType: string; + all: ITransactionLockingMetaPOJO; + modules: ITransactionLockingMetaPOJO[]; +} + +export interface ITransactionsLockingSchema { + module: TransactionsLockingGroup; + formattedModule: string; + description: string; +}