refactor: settings module to Nestjs

This commit is contained in:
Ahmed Bouhuolia
2025-01-07 20:43:31 +02:00
parent 385d84d654
commit abf92ac83f
27 changed files with 2647 additions and 3 deletions

View File

@@ -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: [

View File

@@ -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');
}
}

View File

@@ -0,0 +1,9 @@
export class Metable {
static get modifiers() {
return {
whereKey(builder, key) {
builder.where('key', key);
},
};
}
}

View File

@@ -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 = [];
}
}

View File

@@ -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<string>;
/**
* 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<Promise<void>> = [];
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<Promise<void>> = [];
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.',
);
}
}
}

View File

@@ -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<void>;
}

View File

@@ -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();
}
}

View File

@@ -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 {}

View File

@@ -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);
}
}

View File

@@ -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());
// }
// }
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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 {}

View File

@@ -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,
});
}
}

View File

@@ -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<ITransactionsLockingAllDTO>} allLockingDTO
* @returns {Promise<ITransactionMeta>}
*/
public commandTransactionsLocking = async (
module: TransactionsLockingGroup = TransactionsLockingGroup.All,
transactionLockingDTO: Partial<ITransactionsLockingAllDTO>,
): Promise<ITransactionMeta> => {
// 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<ITransactionMeta>}
*/
public cancelTransactionLocking = async (
module: TransactionsLockingGroup = TransactionsLockingGroup.All,
cancelLockingDTO: ICancelTransactionsLockingDTO,
): Promise<ITransactionMeta> => {
// 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<ITransactionMeta>}
*/
public unlockTransactionsLockingPartially = async (
moduleGroup: TransactionsLockingGroup = TransactionsLockingGroup.All,
partialTransactionLockingDTO: ITransactionLockingPartiallyDTO,
): Promise<ITransactionMeta> => {
// 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);
}
};
}

View File

@@ -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);
}

View File

@@ -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,
);
};
}

View File

@@ -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
);
};
}

View File

@@ -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
);
};
}

View File

@@ -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
);
};
}

View File

@@ -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<ITransactionLockingMetaPOJO> => {
return this.getTransactionsLockingModuleMeta(
TransactionsLockingGroup.All,
);
};
/**
* Retrieve the transactions locking module meta.
* @param {number} tenantId -
* @param {TransactionsLockingGroup} module -
* @returns {ITransactionLockingMetaPOJO}
*/
public getTransactionsLockingModuleMeta = (
module: TransactionsLockingGroup,
): Promise<ITransactionLockingMetaPOJO> => {
const meta = this.transactionsLockingRepo.getTransactionsLocking(module);
return this.transformer.transform(
meta,
new TransactionsLockingMetaTransformer(),
{ module },
);
};
/**
* Retrieve transactions locking list.
* @returns {Promise<ITransactionsLockingListPOJO>}
*/
public getTransactionsLockingList =
async (): Promise<ITransactionsLockingListPOJO> => {
// 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,
};
};
}

View File

@@ -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) : '';
};
}

View File

@@ -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
// );
// }
}

View File

@@ -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,
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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;
}