mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
refactor: accounts module to Nestjs
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
"@types/ramda": "^0.30.2",
|
||||
"js-money": "^0.6.3",
|
||||
"accounting": "^0.4.1",
|
||||
"object-hash": "^2.0.3",
|
||||
"bull": "^4.16.3",
|
||||
"bullmq": "^5.21.1",
|
||||
"cache-manager": "^6.1.1",
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export class ModelEntityNotFound extends Error {
|
||||
constructor(entityId, message?) {
|
||||
message = message || `Entity with id ${entityId} does not exist`;
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
260
packages/server-nest/src/common/repository/CachableRepository.ts
Normal file
260
packages/server-nest/src/common/repository/CachableRepository.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
// import hashObject from 'object-hash';
|
||||
// import { EntityRepository } from './EntityRepository';
|
||||
|
||||
// export class CachableRepository extends EntityRepository {
|
||||
// repositoryName: string;
|
||||
// cache: any;
|
||||
// i18n: any;
|
||||
|
||||
// /**
|
||||
// * Constructor method.
|
||||
// * @param {Knex} knex
|
||||
// * @param {Cache} cache
|
||||
// */
|
||||
// constructor(knex, cache, i18n) {
|
||||
// super(knex);
|
||||
|
||||
// this.cache = cache;
|
||||
// this.i18n = i18n;
|
||||
// this.repositoryName = this.constructor.name;
|
||||
// }
|
||||
|
||||
// getByCache(key, callback) {
|
||||
// return callback();
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieve the cache key of the method name and arguments.
|
||||
// * @param {string} method
|
||||
// * @param {...any} args
|
||||
// * @return {string}
|
||||
// */
|
||||
// getCacheKey(method, ...args) {
|
||||
// const hashArgs = hashObject({ ...args });
|
||||
// const repositoryName = this.repositoryName;
|
||||
|
||||
// return `${repositoryName}-${method}-${hashArgs}`;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieve all entries with specified relations.
|
||||
// * @param withRelations
|
||||
// */
|
||||
// all(withRelations?, trx?) {
|
||||
// const cacheKey = this.getCacheKey('all', withRelations);
|
||||
|
||||
// return this.getByCache(cacheKey, () => {
|
||||
// return super.all(withRelations, trx);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Finds list of entities with specified attributes
|
||||
// * @param {Object} attributeValues - values to filter retrieved entities by
|
||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve.
|
||||
// * @returns {Promise<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
// */
|
||||
// find(attributeValues = {}, withRelations?) {
|
||||
// const cacheKey = this.getCacheKey('find', attributeValues, withRelations);
|
||||
|
||||
// return this.getByCache(cacheKey, () => {
|
||||
// return super.find(attributeValues, withRelations);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Finds list of entities with attribute values that are different from specified ones
|
||||
// * @param {Object} attributeValues - values to filter retrieved entities by
|
||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
// * @returns {Promise<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
// */
|
||||
// findWhereNot(attributeValues = {}, withRelations?) {
|
||||
// const cacheKey = this.getCacheKey(
|
||||
// 'findWhereNot',
|
||||
// attributeValues,
|
||||
// withRelations
|
||||
// );
|
||||
|
||||
// return this.getByCache(cacheKey, () => {
|
||||
// return super.findWhereNot(attributeValues, withRelations);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Finds list of entities with specified attributes (any of multiple specified values)
|
||||
// * Supports both ('attrName', ['value1', 'value2]) and ({attrName: ['value1', 'value2']} formats)
|
||||
// *
|
||||
// * @param {string|Object} searchParam - attribute name or search criteria object
|
||||
// * @param {*[]} [attributeValues] - attribute values to filter retrieved entities by
|
||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
// * @returns {PromiseLike<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
// */
|
||||
// findWhereIn(searchParam, attributeValues, withRelations?) {
|
||||
// const cacheKey = this.getCacheKey(
|
||||
// 'findWhereIn',
|
||||
// attributeValues,
|
||||
// withRelations
|
||||
// );
|
||||
|
||||
// return this.getByCache(cacheKey, () => {
|
||||
// return super.findWhereIn(searchParam, attributeValues, withRelations);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Finds first entity by given parameters
|
||||
// *
|
||||
// * @param {Object} attributeValues - values to filter retrieved entities by
|
||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
// * @returns {Promise<Object>}
|
||||
// */
|
||||
// findOne(attributeValues = {}, withRelations?) {
|
||||
// const cacheKey = this.getCacheKey(
|
||||
// 'findOne',
|
||||
// attributeValues,
|
||||
// withRelations
|
||||
// );
|
||||
// return this.getByCache(cacheKey, () => {
|
||||
// return super.findOne(attributeValues, withRelations);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Finds first entity by given parameters
|
||||
// *
|
||||
// * @param {string || number} id - value of id column of the entity
|
||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
// * @returns {Promise<Object>}
|
||||
// */
|
||||
// findOneById(id, withRelations?) {
|
||||
// const cacheKey = this.getCacheKey('findOneById', id, withRelations);
|
||||
|
||||
// return this.getByCache(cacheKey, () => {
|
||||
// return super.findOneById(id, withRelations);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Persists new entity or an array of entities.
|
||||
// * This method does not recursively persist related entities, use createRecursively (to be implemented) for that.
|
||||
// * Batch insert only works on PostgreSQL
|
||||
// * @param {Object} entity - model instance or parameters for a new entity
|
||||
// * @returns {Promise<Object>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
// */
|
||||
// async create(entity, trx?) {
|
||||
// const result = await super.create(entity, trx);
|
||||
|
||||
// // Flushes the repository cache after insert operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Persists updated entity. If previously set fields are not present, performs an incremental update (does not remove fields unless explicitly set to null)
|
||||
// *
|
||||
// * @param {Object} entity - single entity instance
|
||||
// * @param {Object} [trx] - knex transaction instance. If not specified, new implicit transaction will be used.
|
||||
// * @returns {Promise<integer>} number of affected rows
|
||||
// */
|
||||
// async update(entity, whereAttributes?, trx?) {
|
||||
// const result = await super.update(entity, whereAttributes, trx);
|
||||
|
||||
// // Flushes the repository cache after update operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * @param {Object} attributeValues - values to filter deleted entities by
|
||||
// * @param {Object} [trx]
|
||||
// * @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
||||
// */
|
||||
// async deleteBy(attributeValues, trx?) {
|
||||
// const result = await super.deleteBy(attributeValues, trx);
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * @param {string || number} id - value of id column of the entity
|
||||
// * @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
||||
// */
|
||||
// deleteById(id: number | string, trx?) {
|
||||
// const result = super.deleteById(id, trx);
|
||||
|
||||
// // Flushes the repository cache after insert operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// *
|
||||
// * @param {string|number[]} values -
|
||||
// */
|
||||
// async deleteWhereIn(field: string, values: string | number[]) {
|
||||
// const result = await super.deleteWhereIn(field, values);
|
||||
|
||||
// // Flushes the repository cache after delete operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// *
|
||||
// * @param {string|number[]} values
|
||||
// */
|
||||
// async deleteWhereIdIn(values: string | number[], trx?) {
|
||||
// const result = await super.deleteWhereIdIn(values, trx);
|
||||
|
||||
// // Flushes the repository cache after delete operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// *
|
||||
// * @param graph
|
||||
// * @param options
|
||||
// */
|
||||
// async upsertGraph(graph, options) {
|
||||
// const result = await super.upsertGraph(graph, options);
|
||||
|
||||
// // Flushes the repository cache after insert operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// *
|
||||
// * @param {} whereAttributes
|
||||
// * @param {string} field
|
||||
// * @param {number} amount
|
||||
// */
|
||||
// async changeNumber(whereAttributes, field: string, amount: number, trx?) {
|
||||
// const result = await super.changeNumber(
|
||||
// whereAttributes,
|
||||
// field,
|
||||
// amount,
|
||||
// trx
|
||||
// );
|
||||
|
||||
// // Flushes the repository cache after update operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Flush repository cache.
|
||||
// */
|
||||
// flushCache(): void {
|
||||
// this.cache.delStartWith(this.repositoryName);
|
||||
// }
|
||||
// }
|
||||
232
packages/server-nest/src/common/repository/EntityRepository.ts
Normal file
232
packages/server-nest/src/common/repository/EntityRepository.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
import { cloneDeep, forOwn, isString } from 'lodash';
|
||||
import { ModelEntityNotFound } from '../exceptions/ModelEntityNotFound';
|
||||
import { Model } from 'objection';
|
||||
|
||||
function applyGraphFetched(withRelations, builder) {
|
||||
const relations = Array.isArray(withRelations)
|
||||
? withRelations
|
||||
: typeof withRelations === 'string'
|
||||
? withRelations.split(',').map((relation) => relation.trim())
|
||||
: [];
|
||||
|
||||
relations.forEach((relation) => {
|
||||
builder.withGraphFetched(relation);
|
||||
});
|
||||
}
|
||||
|
||||
export class EntityRepository {
|
||||
idColumn: string = 'id';
|
||||
knex: any;
|
||||
|
||||
/**
|
||||
* Retrieve the repository model binded it to knex instance.
|
||||
*/
|
||||
get model(): typeof Model {
|
||||
throw new Error("The repository's model is not defined.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all entries with specified relations.
|
||||
* @param withRelations
|
||||
*/
|
||||
all(withRelations?, trx?) {
|
||||
const builder = this.model.query(trx);
|
||||
applyGraphFetched(withRelations, builder);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds list of entities with specified attributes
|
||||
*
|
||||
* @param {Object} attributeValues - values to filter retrieved entities by
|
||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve.
|
||||
* @returns {Promise<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
*/
|
||||
find(attributeValues = {}, withRelations?) {
|
||||
const builder = this.model.query().where(attributeValues);
|
||||
|
||||
applyGraphFetched(withRelations, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds list of entities with attribute values that are different from specified ones
|
||||
*
|
||||
* @param {Object} attributeValues - values to filter retrieved entities by
|
||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
* @returns {PromiseLike<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
*/
|
||||
findWhereNot(attributeValues = {}, withRelations?) {
|
||||
const builder = this.model.query().whereNot(attributeValues);
|
||||
|
||||
applyGraphFetched(withRelations, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds list of entities with specified attributes (any of multiple specified values)
|
||||
* Supports both ('attrName', ['value1', 'value2]) and ({attrName: ['value1', 'value2']} formats)
|
||||
*
|
||||
* @param {string|Object} searchParam - attribute name or search criteria object
|
||||
* @param {*[]} [attributeValues] - attribute values to filter retrieved entities by
|
||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
* @returns {PromiseLike<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
*/
|
||||
findWhereIn(searchParam, attributeValues, withRelations?) {
|
||||
const commonBuilder = (builder) => {
|
||||
applyGraphFetched(withRelations, builder);
|
||||
};
|
||||
if (isString(searchParam)) {
|
||||
return this.model
|
||||
.query()
|
||||
.whereIn(searchParam, attributeValues)
|
||||
.onBuild(commonBuilder);
|
||||
} else {
|
||||
const builder = this.model.query(this.knex).onBuild(commonBuilder);
|
||||
|
||||
forOwn(searchParam, (value, key) => {
|
||||
if (Array.isArray(value)) {
|
||||
builder.whereIn(key, value);
|
||||
} else {
|
||||
builder.where(key, value);
|
||||
}
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds first entity by given parameters
|
||||
*
|
||||
* @param {Object} attributeValues - values to filter retrieved entities by
|
||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async findOne(attributeValues = {}, withRelations?) {
|
||||
const results = await this.find(attributeValues, withRelations);
|
||||
return results[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds first entity by given parameters
|
||||
*
|
||||
* @param {string || number} id - value of id column of the entity
|
||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
findOneById(id, withRelations?) {
|
||||
return this.findOne({ [this.idColumn]: id }, withRelations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists new entity or an array of entities.
|
||||
* This method does not recursively persist related entities, use createRecursively (to be implemented) for that.
|
||||
* Batch insert only works on PostgreSQL
|
||||
*
|
||||
* @param {Object} entity - model instance or parameters for a new entity
|
||||
* @returns {Promise<Object>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
*/
|
||||
create(entity, trx?) {
|
||||
// Keep the input parameter immutable
|
||||
const instanceDTO = cloneDeep(entity);
|
||||
|
||||
return this.model.query(trx).insert(instanceDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists updated entity. If previously set fields are not present, performs an incremental update (does not remove fields unless explicitly set to null)
|
||||
*
|
||||
* @param {Object} entity - single entity instance
|
||||
* @returns {Promise<integer>} number of affected rows
|
||||
*/
|
||||
async update(entity, whereAttributes?, trx?) {
|
||||
const entityDto = cloneDeep(entity);
|
||||
const identityClause = {};
|
||||
|
||||
if (Array.isArray(this.idColumn)) {
|
||||
this.idColumn.forEach(
|
||||
(idColumn) => (identityClause[idColumn] = entityDto[idColumn]),
|
||||
);
|
||||
} else {
|
||||
identityClause[this.idColumn] = entityDto[this.idColumn];
|
||||
}
|
||||
const whereConditions = whereAttributes || identityClause;
|
||||
const modifiedEntitiesCount = await this.model
|
||||
.query(trx)
|
||||
.where(whereConditions)
|
||||
.update(entityDto);
|
||||
|
||||
if (modifiedEntitiesCount === 0) {
|
||||
throw new ModelEntityNotFound(entityDto[this.idColumn]);
|
||||
}
|
||||
return modifiedEntitiesCount;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} attributeValues - values to filter deleted entities by
|
||||
* @param {Object} [trx]
|
||||
* @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
||||
*/
|
||||
deleteBy(attributeValues, trx?) {
|
||||
return this.model.query(trx).delete().where(attributeValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string || number} id - value of id column of the entity
|
||||
* @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
||||
*/
|
||||
deleteById(id: number | string, trx?) {
|
||||
return this.deleteBy(
|
||||
{
|
||||
[this.idColumn]: id,
|
||||
},
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given entries in the array on the specific field.
|
||||
* @param {string} field -
|
||||
* @param {number|string} values -
|
||||
*/
|
||||
deleteWhereIn(field: string, values: (string | number)[], trx) {
|
||||
return this.model.query(trx).whereIn(field, values).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string|number[]} values
|
||||
*/
|
||||
deleteWhereIdIn(values: (string | number)[], trx?) {
|
||||
return this.deleteWhereIn(this.idColumn, values, trx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Arbitrary relation graphs can be upserted (insert + update + delete)
|
||||
* using the upsertGraph method.
|
||||
* @param graph
|
||||
* @param options
|
||||
*/
|
||||
upsertGraph(graph, options) {
|
||||
// Keep the input grpah immutable
|
||||
const graphCloned = cloneDeep(graph);
|
||||
return this.model.query().upsertGraph(graphCloned, options);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} whereAttributes
|
||||
* @param {string} field
|
||||
* @param amount
|
||||
*/
|
||||
changeNumber(whereAttributes, field: string, amount: number, trx) {
|
||||
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
||||
|
||||
return this.model
|
||||
.query(trx)
|
||||
.where(whereAttributes)
|
||||
[changeMethod](field, Math.abs(amount));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// import { CachableRepository } from './CachableRepository';
|
||||
import { EntityRepository } from './EntityRepository';
|
||||
|
||||
export class TenantRepository extends EntityRepository {
|
||||
|
||||
}
|
||||
132
packages/server-nest/src/modules/Accounts/Account.transformer.ts
Normal file
132
packages/server-nest/src/modules/Accounts/Account.transformer.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
// import { IAccountsStructureType } from './Accounts.types';
|
||||
// import {
|
||||
// assocDepthLevelToObjectTree,
|
||||
// flatToNestedArray,
|
||||
// nestedArrayToFlatten,
|
||||
// } from 'utils';
|
||||
import { Transformer } from '../Transformer/Transformer';
|
||||
import { AccountModel } from './models/Account.model';
|
||||
|
||||
export class AccountTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'accountTypeLabel',
|
||||
'accountNormalFormatted',
|
||||
'formattedAmount',
|
||||
'flattenName',
|
||||
'bankBalanceFormatted',
|
||||
'lastFeedsUpdatedAtFormatted',
|
||||
'isFeedsPaused',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['plaidItem'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the flatten name with all dependants accounts names.
|
||||
* @param {IAccount} account -
|
||||
* @returns {string}
|
||||
*/
|
||||
public flattenName = (account: AccountModel): string => {
|
||||
const parentDependantsIds = this.options.accountsGraph.dependantsOf(
|
||||
account.id,
|
||||
);
|
||||
const prefixAccounts = parentDependantsIds.map((dependId) => {
|
||||
const node = this.options.accountsGraph.getNodeData(dependId);
|
||||
return `${node.name}: `;
|
||||
});
|
||||
return `${prefixAccounts}${account.name}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted account amount.
|
||||
* @param {IAccount} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedAmount = (account: AccountModel): string => {
|
||||
return this.formatNumber(account.amount, {
|
||||
currencyCode: account.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted bank balance.
|
||||
* @param {AccountModel} account
|
||||
* @returns {string}
|
||||
*/
|
||||
protected bankBalanceFormatted = (account: AccountModel): string => {
|
||||
return this.formatNumber(account.bankBalance, {
|
||||
currencyCode: account.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted last feeds update at.
|
||||
* @param {IAccount} account
|
||||
* @returns {string}
|
||||
*/
|
||||
protected lastFeedsUpdatedAtFormatted = (account: AccountModel): string => {
|
||||
return account.lastFeedsUpdatedAt
|
||||
? this.formatDate(account.lastFeedsUpdatedAt)
|
||||
: '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the bank account connection is paused.
|
||||
* @param account
|
||||
* @returns {boolean}
|
||||
*/
|
||||
protected isFeedsPaused = (account: AccountModel): boolean => {
|
||||
// return account.plaidItem?.isPaused || false;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves formatted account type label.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected accountTypeLabel = (account: AccountModel): string => {
|
||||
return this.context.i18n.t(account.accountTypeLabel);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves formatted account normal.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected accountNormalFormatted = (account: AccountModel): string => {
|
||||
return this.context.i18n.t(account.accountNormalFormatted);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the accounts collection to flat or nested array.
|
||||
* @param {IAccount[]}
|
||||
* @returns {IAccount[]}
|
||||
*/
|
||||
// protected postCollectionTransform = (accounts: AccountModel[]) => {
|
||||
// // Transfom the flatten to accounts tree.
|
||||
// const transformed = flatToNestedArray(accounts, {
|
||||
// id: 'id',
|
||||
// parentId: 'parentAccountId',
|
||||
// });
|
||||
// // Associate `accountLevel` attr to indicate object depth.
|
||||
// const transformed2 = assocDepthLevelToObjectTree(
|
||||
// transformed,
|
||||
// 1,
|
||||
// 'accountLevel',
|
||||
// );
|
||||
// return this.options.structure === IAccountsStructureType.Flat
|
||||
// ? nestedArrayToFlatten(transformed2)
|
||||
// : transformed2;
|
||||
// };
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { Transformer } from '../Transformer/Transformer';
|
||||
import { AccountTransaction } from './models/AccountTransaction.model';
|
||||
|
||||
export class AccountTransactionTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'date',
|
||||
'formattedDate',
|
||||
'transactionType',
|
||||
'transactionId',
|
||||
'transactionTypeFormatted',
|
||||
'credit',
|
||||
'debit',
|
||||
'formattedCredit',
|
||||
'formattedDebit',
|
||||
'fcCredit',
|
||||
'fcDebit',
|
||||
'formattedFcCredit',
|
||||
'formattedFcDebit',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude all attributes of the model.
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['*'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted date.
|
||||
* @returns {string}
|
||||
*/
|
||||
public formattedDate(transaction: AccountTransaction) {
|
||||
return this.formatDate(transaction.date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the formatted transaction type.
|
||||
* @returns {string}
|
||||
*/
|
||||
public transactionTypeFormatted(transaction: AccountTransaction) {
|
||||
return this.context.i18n.t(transaction.referenceTypeFormatted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the tranasction type.
|
||||
* @returns {string}
|
||||
*/
|
||||
public transactionType(transaction: AccountTransaction) {
|
||||
return transaction.referenceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the transaction id.
|
||||
* @returns {number}
|
||||
*/
|
||||
public transactionId(transaction: AccountTransaction) {
|
||||
return transaction.referenceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the credit amount.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedCredit(transaction: AccountTransaction) {
|
||||
return this.formatMoney(transaction.credit, {
|
||||
excerptZero: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the credit amount.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedDebit(transaction: AccountTransaction) {
|
||||
return this.formatMoney(transaction.debit, {
|
||||
excerptZero: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the foreign credit amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
protected fcCredit(transaction: AccountTransaction) {
|
||||
return transaction.credit * transaction.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the foreign debit amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
protected fcDebit(transaction: AccountTransaction) {
|
||||
return transaction.debit * transaction.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the formatted foreign credit amount.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedFcCredit(transaction: AccountTransaction) {
|
||||
return this.formatMoney(this.fcCredit(transaction), {
|
||||
currencyCode: transaction.currencyCode,
|
||||
excerptZero: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the formatted foreign debit amount.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedFcDebit(transaction: AccountTransaction) {
|
||||
return this.formatMoney(this.fcDebit(transaction), {
|
||||
currencyCode: transaction.currencyCode,
|
||||
excerptZero: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
592
packages/server-nest/src/modules/Accounts/Accounts.constants.ts
Normal file
592
packages/server-nest/src/modules/Accounts/Accounts.constants.ts
Normal file
@@ -0,0 +1,592 @@
|
||||
export const TaxPayableAccount = {
|
||||
name: 'Tax Payable',
|
||||
slug: 'tax-payable',
|
||||
account_type: 'tax-payable',
|
||||
code: '20006',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
};
|
||||
|
||||
export const UnearnedRevenueAccount = {
|
||||
name: 'Unearned Revenue',
|
||||
slug: 'unearned-revenue',
|
||||
account_type: 'other-current-liability',
|
||||
parent_account_id: null,
|
||||
code: '50005',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export const PrepardExpenses = {
|
||||
name: 'Prepaid Expenses',
|
||||
slug: 'prepaid-expenses',
|
||||
account_type: 'other-current-asset',
|
||||
parent_account_id: null,
|
||||
code: '100010',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export const StripeClearingAccount = {
|
||||
name: 'Stripe Clearing',
|
||||
slug: 'stripe-clearing',
|
||||
account_type: 'other-current-asset',
|
||||
parent_account_id: null,
|
||||
code: '100020',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export const SeedAccounts = [
|
||||
{
|
||||
name: 'Bank Account',
|
||||
slug: 'bank-account',
|
||||
account_type: 'bank',
|
||||
code: '10001',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Saving Bank Account',
|
||||
slug: 'saving-bank-account',
|
||||
account_type: 'bank',
|
||||
code: '10002',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Undeposited Funds',
|
||||
slug: 'undeposited-funds',
|
||||
account_type: 'cash',
|
||||
code: '10003',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Petty Cash',
|
||||
slug: 'petty-cash',
|
||||
account_type: 'cash',
|
||||
code: '10004',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Computer Equipment',
|
||||
slug: 'computer-equipment',
|
||||
code: '10005',
|
||||
account_type: 'fixed-asset',
|
||||
predefined: 0,
|
||||
parent_account_id: null,
|
||||
index: 1,
|
||||
active: 1,
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
name: 'Office Equipment',
|
||||
slug: 'office-equipment',
|
||||
code: '10006',
|
||||
account_type: 'fixed-asset',
|
||||
predefined: 0,
|
||||
parent_account_id: null,
|
||||
index: 1,
|
||||
active: 1,
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
name: 'Accounts Receivable (A/R)',
|
||||
slug: 'accounts-receivable',
|
||||
account_type: 'accounts-receivable',
|
||||
code: '10007',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Inventory Asset',
|
||||
slug: 'inventory-asset',
|
||||
code: '10008',
|
||||
account_type: 'inventory',
|
||||
predefined: 1,
|
||||
parent_account_id: null,
|
||||
index: 1,
|
||||
active: 1,
|
||||
description:
|
||||
'An account that holds valuation of products or goods that available for sale.',
|
||||
},
|
||||
|
||||
// Libilities
|
||||
{
|
||||
name: 'Accounts Payable (A/P)',
|
||||
slug: 'accounts-payable',
|
||||
account_type: 'accounts-payable',
|
||||
parent_account_id: null,
|
||||
code: '20001',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Owner A Drawings',
|
||||
slug: 'owner-drawings',
|
||||
account_type: 'other-current-liability',
|
||||
parent_account_id: null,
|
||||
code: '20002',
|
||||
description: 'Withdrawals by the owners.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Loan',
|
||||
slug: 'owner-drawings',
|
||||
account_type: 'other-current-liability',
|
||||
code: '20003',
|
||||
description: 'Money that has been borrowed from a creditor.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Opening Balance Liabilities',
|
||||
slug: 'opening-balance-liabilities',
|
||||
account_type: 'other-current-liability',
|
||||
code: '20004',
|
||||
description:
|
||||
'This account will hold the difference in the debits and credits entered during the opening balance..',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Revenue Received in Advance',
|
||||
slug: 'revenue-received-in-advance',
|
||||
account_type: 'other-current-liability',
|
||||
parent_account_id: null,
|
||||
code: '20005',
|
||||
description: 'When customers pay in advance for products/services.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
TaxPayableAccount,
|
||||
|
||||
// Equity
|
||||
{
|
||||
name: 'Retained Earnings',
|
||||
slug: 'retained-earnings',
|
||||
account_type: 'equity',
|
||||
code: '30001',
|
||||
description:
|
||||
'Retained earnings tracks net income from previous fiscal years.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Opening Balance Equity',
|
||||
slug: 'opening-balance-equity',
|
||||
account_type: 'equity',
|
||||
code: '30002',
|
||||
description:
|
||||
'When you enter opening balances to the accounts, the amounts enter in Opening balance equity. This ensures that you have a correct trial balance sheet for your company, without even specific the second credit or debit entry.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: "Owner's Equity",
|
||||
slug: 'owner-equity',
|
||||
account_type: 'equity',
|
||||
code: '30003',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: `Drawings`,
|
||||
slug: 'drawings',
|
||||
account_type: 'equity',
|
||||
code: '30003',
|
||||
description:
|
||||
'Goods purchased with the intention of selling these to customers',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
|
||||
// Expenses
|
||||
{
|
||||
name: 'Other Expenses',
|
||||
slug: 'other-expenses',
|
||||
account_type: 'other-expense',
|
||||
parent_account_id: null,
|
||||
code: '40001',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Cost of Goods Sold',
|
||||
slug: 'cost-of-goods-sold',
|
||||
account_type: 'cost-of-goods-sold',
|
||||
parent_account_id: null,
|
||||
code: '40002',
|
||||
description: 'Tracks the direct cost of the goods sold.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Office expenses',
|
||||
slug: 'office-expenses',
|
||||
account_type: 'expense',
|
||||
parent_account_id: null,
|
||||
code: '40003',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Rent',
|
||||
slug: 'rent',
|
||||
account_type: 'expense',
|
||||
parent_account_id: null,
|
||||
code: '40004',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Exchange Gain or Loss',
|
||||
slug: 'exchange-grain-loss',
|
||||
account_type: 'other-expense',
|
||||
parent_account_id: null,
|
||||
code: '40005',
|
||||
description: 'Tracks the gain and losses of the exchange differences.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Bank Fees and Charges',
|
||||
slug: 'bank-fees-and-charges',
|
||||
account_type: 'expense',
|
||||
parent_account_id: null,
|
||||
code: '40006',
|
||||
description:
|
||||
'Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Depreciation Expense',
|
||||
slug: 'depreciation-expense',
|
||||
account_type: 'expense',
|
||||
parent_account_id: null,
|
||||
code: '40007',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
|
||||
// Income
|
||||
{
|
||||
name: 'Sales of Product Income',
|
||||
slug: 'sales-of-product-income',
|
||||
account_type: 'income',
|
||||
predefined: 1,
|
||||
parent_account_id: null,
|
||||
code: '50001',
|
||||
index: 1,
|
||||
active: 1,
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
name: 'Sales of Service Income',
|
||||
slug: 'sales-of-service-income',
|
||||
account_type: 'income',
|
||||
predefined: 0,
|
||||
parent_account_id: null,
|
||||
code: '50002',
|
||||
index: 1,
|
||||
active: 1,
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
name: 'Uncategorized Income',
|
||||
slug: 'uncategorized-income',
|
||||
account_type: 'income',
|
||||
parent_account_id: null,
|
||||
code: '50003',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Other Income',
|
||||
slug: 'other-income',
|
||||
account_type: 'other-income',
|
||||
parent_account_id: null,
|
||||
code: '50004',
|
||||
description:
|
||||
'The income activities are not associated to the core business.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
UnearnedRevenueAccount,
|
||||
PrepardExpenses,
|
||||
];
|
||||
|
||||
export const ACCOUNT_TYPE = {
|
||||
CASH: 'cash',
|
||||
BANK: 'bank',
|
||||
ACCOUNTS_RECEIVABLE: 'accounts-receivable',
|
||||
INVENTORY: 'inventory',
|
||||
OTHER_CURRENT_ASSET: 'other-current-asset',
|
||||
FIXED_ASSET: 'fixed-asset',
|
||||
NON_CURRENT_ASSET: 'none-current-asset',
|
||||
|
||||
ACCOUNTS_PAYABLE: 'accounts-payable',
|
||||
CREDIT_CARD: 'credit-card',
|
||||
TAX_PAYABLE: 'tax-payable',
|
||||
OTHER_CURRENT_LIABILITY: 'other-current-liability',
|
||||
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||
|
||||
EQUITY: 'equity',
|
||||
INCOME: 'income',
|
||||
OTHER_INCOME: 'other-income',
|
||||
COST_OF_GOODS_SOLD: 'cost-of-goods-sold',
|
||||
EXPENSE: 'expense',
|
||||
OTHER_EXPENSE: 'other-expense',
|
||||
};
|
||||
|
||||
export const ACCOUNT_PARENT_TYPE = {
|
||||
CURRENT_ASSET: 'current-asset',
|
||||
FIXED_ASSET: 'fixed-asset',
|
||||
NON_CURRENT_ASSET: 'non-current-asset',
|
||||
|
||||
CURRENT_LIABILITY: 'current-liability',
|
||||
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||
|
||||
EQUITY: 'equity',
|
||||
EXPENSE: 'expense',
|
||||
INCOME: 'income',
|
||||
};
|
||||
|
||||
export const ACCOUNT_ROOT_TYPE = {
|
||||
ASSET: 'asset',
|
||||
LIABILITY: 'liability',
|
||||
EQUITY: 'equity',
|
||||
EXPENSE: 'expense',
|
||||
INCOME: 'income',
|
||||
};
|
||||
|
||||
export const ACCOUNT_NORMAL = {
|
||||
CREDIT: 'credit',
|
||||
DEBIT: 'debit',
|
||||
};
|
||||
export const ACCOUNT_TYPES = [
|
||||
{
|
||||
label: 'Cash',
|
||||
key: ACCOUNT_TYPE.CASH,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
multiCurrency: true,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Bank',
|
||||
key: ACCOUNT_TYPE.BANK,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
multiCurrency: true,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Accounts Receivable',
|
||||
key: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Inventory',
|
||||
key: ACCOUNT_TYPE.INVENTORY,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Other Current Asset',
|
||||
key: ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Fixed Asset',
|
||||
key: ACCOUNT_TYPE.FIXED_ASSET,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Non-Current Asset',
|
||||
key: ACCOUNT_TYPE.NON_CURRENT_ASSET,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Accounts Payable',
|
||||
key: ACCOUNT_TYPE.ACCOUNTS_PAYABLE,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Credit Card',
|
||||
key: ACCOUNT_TYPE.CREDIT_CARD,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
multiCurrency: true,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Tax Payable',
|
||||
key: ACCOUNT_TYPE.TAX_PAYABLE,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Other Current Liability',
|
||||
key: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Long Term Liability',
|
||||
key: ACCOUNT_TYPE.LOGN_TERM_LIABILITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.LOGN_TERM_LIABILITY,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Non-Current Liability',
|
||||
key: ACCOUNT_TYPE.NON_CURRENT_LIABILITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.NON_CURRENT_LIABILITY,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Equity',
|
||||
key: ACCOUNT_TYPE.EQUITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EQUITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EQUITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Income',
|
||||
key: ACCOUNT_TYPE.INCOME,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Other Income',
|
||||
key: ACCOUNT_TYPE.OTHER_INCOME,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Cost of Goods Sold',
|
||||
key: ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Expense',
|
||||
key: ACCOUNT_TYPE.EXPENSE,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Other Expense',
|
||||
key: ACCOUNT_TYPE.OTHER_EXPENSE,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const getAccountsSupportsMultiCurrency = () => {
|
||||
return ACCOUNT_TYPES.filter((account) => account.multiCurrency);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
Body,
|
||||
Param,
|
||||
Delete,
|
||||
Get,
|
||||
Query,
|
||||
ParseIntPipe,
|
||||
} from '@nestjs/common';
|
||||
import { AccountsApplication } from './AccountsApplication.service';
|
||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||
import { EditAccountDTO } from './EditAccount.dto';
|
||||
import { PublicRoute } from '../Auth/Jwt.guard';
|
||||
import { IAccountsTransactionsFilter } from './Accounts.types';
|
||||
// import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types';
|
||||
// import { ZodValidationPipe } from '@/common/pipes/ZodValidation.pipe';
|
||||
|
||||
@Controller('accounts')
|
||||
@PublicRoute()
|
||||
export class AccountsController {
|
||||
constructor(private readonly accountsApplication: AccountsApplication) {}
|
||||
|
||||
@Post()
|
||||
async createAccount(@Body() accountDTO: CreateAccountDTO) {
|
||||
return this.accountsApplication.createAccount(accountDTO);
|
||||
}
|
||||
|
||||
@Post(':id')
|
||||
async editAccount(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() accountDTO: EditAccountDTO,
|
||||
) {
|
||||
return this.accountsApplication.editAccount(id, accountDTO);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async deleteAccount(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.accountsApplication.deleteAccount(id);
|
||||
}
|
||||
|
||||
@Post(':id/activate')
|
||||
async activateAccount(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.accountsApplication.activateAccount(id);
|
||||
}
|
||||
|
||||
@Post(':id/inactivate')
|
||||
async inactivateAccount(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.accountsApplication.inactivateAccount(id);
|
||||
}
|
||||
|
||||
@Get('types')
|
||||
async getAccountTypes() {
|
||||
return this.accountsApplication.getAccountTypes();
|
||||
}
|
||||
|
||||
@Get('transactions')
|
||||
async getAccountTransactions(@Query() filter: IAccountsTransactionsFilter) {
|
||||
return this.accountsApplication.getAccountsTransactions(filter);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async getAccount(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.accountsApplication.getAccount(id);
|
||||
}
|
||||
|
||||
// @Get()
|
||||
// async getAccounts(@Query() filter: IAccountsFilter) {
|
||||
// return this.accountsApplication.getAccounts(filter);
|
||||
// }
|
||||
|
||||
}
|
||||
38
packages/server-nest/src/modules/Accounts/Accounts.module.ts
Normal file
38
packages/server-nest/src/modules/Accounts/Accounts.module.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
|
||||
import { AccountsController } from './Accounts.controller';
|
||||
import { AccountsApplication } from './AccountsApplication.service';
|
||||
import { CreateAccountService } from './CreateAccount.service';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
||||
import { AccountRepository } from './repositories/Account.repository';
|
||||
import { EditAccount } from './EditAccount.service';
|
||||
import { DeleteAccount } from './DeleteAccount.service';
|
||||
import { GetAccount } from './GetAccount.service';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
import { ActivateAccount } from './ActivateAccount.service';
|
||||
import { GetAccountTypesService } from './GetAccountTypes.service';
|
||||
import { GetAccountTransactionsService } from './GetAccountTransactions.service';
|
||||
// import { EditAccount } from './EditAccount.service';
|
||||
// import { GetAccountsService } from './GetAccounts.service';
|
||||
|
||||
@Module({
|
||||
imports: [TenancyDatabaseModule],
|
||||
controllers: [AccountsController],
|
||||
providers: [
|
||||
AccountsApplication,
|
||||
CreateAccountService,
|
||||
TenancyContext,
|
||||
CommandAccountValidators,
|
||||
AccountRepository,
|
||||
EditAccount,
|
||||
DeleteAccount,
|
||||
GetAccount,
|
||||
TransformerInjectable,
|
||||
ActivateAccount,
|
||||
GetAccountTypesService,
|
||||
GetAccountTransactionsService,
|
||||
// GetAccountsService,
|
||||
],
|
||||
})
|
||||
export class AccountsModule {}
|
||||
94
packages/server-nest/src/modules/Accounts/Accounts.types.ts
Normal file
94
packages/server-nest/src/modules/Accounts/Accounts.types.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Knex } from 'knex';
|
||||
import { AccountModel } from './models/Account.model';
|
||||
// import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter';
|
||||
|
||||
export enum AccountNormal {
|
||||
DEBIT = 'debit',
|
||||
CREDIT = 'credit',
|
||||
}
|
||||
|
||||
export interface IAccountsTransactionsFilter {
|
||||
accountId?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export enum IAccountsStructureType {
|
||||
Tree = 'tree',
|
||||
Flat = 'flat',
|
||||
}
|
||||
|
||||
// export interface IAccountsFilter extends IDynamicListFilterDTO {
|
||||
// stringifiedFilterRoles?: string;
|
||||
// onlyInactive: boolean;
|
||||
// structure?: IAccountsStructureType;
|
||||
// }
|
||||
export interface IAccountsFilter {}
|
||||
export interface IAccountType {
|
||||
label: string;
|
||||
key: string;
|
||||
normal: string;
|
||||
rootType: string;
|
||||
childType: string;
|
||||
balanceSheet: boolean;
|
||||
incomeSheet: boolean;
|
||||
}
|
||||
|
||||
export interface IAccountsTypesService {
|
||||
getAccountsTypes(): Promise<IAccountType>;
|
||||
}
|
||||
|
||||
export interface IAccountEventCreatingPayload {
|
||||
accountDTO: any;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
export interface IAccountEventCreatedPayload {
|
||||
account: AccountModel;
|
||||
accountId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IAccountEventEditedPayload {
|
||||
account: AccountModel;
|
||||
oldAccount: AccountModel;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IAccountEventDeletedPayload {
|
||||
accountId: number;
|
||||
oldAccount: AccountModel;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IAccountEventDeletePayload {
|
||||
trx: Knex.Transaction;
|
||||
oldAccount: AccountModel;
|
||||
}
|
||||
|
||||
export interface IAccountEventActivatedPayload {
|
||||
accountId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export enum AccountAction {
|
||||
CREATE = 'Create',
|
||||
EDIT = 'Edit',
|
||||
DELETE = 'Delete',
|
||||
VIEW = 'View',
|
||||
TransactionsLocking = 'TransactionsLocking',
|
||||
}
|
||||
|
||||
export enum TaxRateAction {
|
||||
CREATE = 'Create',
|
||||
EDIT = 'Edit',
|
||||
DELETE = 'Delete',
|
||||
VIEW = 'View',
|
||||
}
|
||||
|
||||
export interface CreateAccountParams {
|
||||
ignoreUniqueName: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface IGetAccountTransactionPOJO {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
// import {
|
||||
// IAccount,
|
||||
// IAccountCreateDTO,
|
||||
// IAccountEditDTO,
|
||||
// IAccountResponse,
|
||||
// IAccountsFilter,
|
||||
// IAccountsTransactionsFilter,
|
||||
// IFilterMeta,
|
||||
// IGetAccountTransactionPOJO,
|
||||
// } from '@/interfaces';
|
||||
import { CreateAccountService } from './CreateAccount.service';
|
||||
import { DeleteAccount } from './DeleteAccount.service';
|
||||
import { EditAccount } from './EditAccount.service';
|
||||
// import { GetAccounts } from './GetAccounts.service';
|
||||
// import { GetAccountTransactions } from './GetAccountTransactions.service';
|
||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||
import { AccountModel } from './models/Account.model';
|
||||
import { EditAccountDTO } from './EditAccount.dto';
|
||||
import { GetAccount } from './GetAccount.service';
|
||||
import { ActivateAccount } from './ActivateAccount.service';
|
||||
import { GetAccountTypesService } from './GetAccountTypes.service';
|
||||
import { GetAccountTransactionsService } from './GetAccountTransactions.service';
|
||||
import {
|
||||
IAccountsTransactionsFilter,
|
||||
IGetAccountTransactionPOJO,
|
||||
} from './Accounts.types';
|
||||
|
||||
@Injectable()
|
||||
export class AccountsApplication {
|
||||
constructor(
|
||||
private readonly createAccountService: CreateAccountService,
|
||||
private readonly deleteAccountService: DeleteAccount,
|
||||
private readonly editAccountService: EditAccount,
|
||||
private readonly activateAccountService: ActivateAccount,
|
||||
private readonly getAccountTypesService: GetAccountTypesService,
|
||||
private readonly getAccountService: GetAccount,
|
||||
// private readonly getAccountsService: GetAccounts,
|
||||
private readonly getAccountTransactionsService: GetAccountTransactionsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new account.
|
||||
* @param {number} tenantId
|
||||
* @param {IAccountCreateDTO} accountDTO
|
||||
* @returns {Promise<IAccount>}
|
||||
*/
|
||||
public createAccount = (
|
||||
accountDTO: CreateAccountDTO,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<AccountModel> => {
|
||||
return this.createAccountService.createAccount(accountDTO, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the given account.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteAccount = (accountId: number) => {
|
||||
return this.deleteAccountService.deleteAccount(accountId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits the given account.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {IAccountEditDTO} accountDTO
|
||||
* @returns
|
||||
*/
|
||||
public editAccount = (accountId: number, accountDTO: EditAccountDTO) => {
|
||||
return this.editAccountService.editAccount(accountId, accountDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Activate the given account.
|
||||
* @param {number} accountId - Account id.
|
||||
*/
|
||||
public activateAccount = (accountId: number) => {
|
||||
return this.activateAccountService.activateAccount(accountId, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inactivate the given account.
|
||||
* @param {number} accountId - Account id.
|
||||
*/
|
||||
public inactivateAccount = (accountId: number) => {
|
||||
return this.activateAccountService.activateAccount(accountId, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the account details.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} accountId - Account id.
|
||||
* @returns {Promise<IAccount>}
|
||||
*/
|
||||
public getAccount = (accountId: number) => {
|
||||
return this.getAccountService.getAccount(accountId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves all account types.
|
||||
* @returns {Promise<IAccountType[]>}
|
||||
*/
|
||||
public getAccountTypes = () => {
|
||||
return this.getAccountTypesService.getAccountsTypes();
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Retrieves the accounts list.
|
||||
// * @param {number} tenantId
|
||||
// * @param {IAccountsFilter} filterDTO
|
||||
// * @returns {Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }>}
|
||||
// */
|
||||
// public getAccounts = (
|
||||
// filterDTO: IAccountsFilter,
|
||||
// ): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> => {
|
||||
// return this.getAccountsService.getAccountsList(filterDTO);
|
||||
// };
|
||||
|
||||
/**
|
||||
* Retrieves the given account transactions.
|
||||
* @param {IAccountsTransactionsFilter} filter
|
||||
* @returns {Promise<IGetAccountTransactionPOJO[]>}
|
||||
*/
|
||||
public getAccountsTransactions = (
|
||||
filter: IAccountsTransactionsFilter,
|
||||
): Promise<IGetAccountTransactionPOJO[]> => {
|
||||
return this.getAccountTransactionsService.getAccountsTransactions(filter);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import { AccountsApplication } from './AccountsApplication.service';
|
||||
// import { Exportable } from '../Export/Exportable';
|
||||
// import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
||||
// import { EXPORT_SIZE_LIMIT } from '../Export/constants';
|
||||
|
||||
// @Service()
|
||||
// export class AccountsExportable extends Exportable {
|
||||
// @Inject()
|
||||
// private accountsApplication: AccountsApplication;
|
||||
|
||||
// /**
|
||||
// * Retrieves the accounts data to exportable sheet.
|
||||
// * @param {number} tenantId
|
||||
// * @returns
|
||||
// */
|
||||
// public exportable(tenantId: number, query: IAccountsFilter) {
|
||||
// const parsedQuery = {
|
||||
// sortOrder: 'desc',
|
||||
// columnSortBy: 'created_at',
|
||||
// inactiveMode: false,
|
||||
// ...query,
|
||||
// structure: IAccountsStructureType.Flat,
|
||||
// pageSize: EXPORT_SIZE_LIMIT,
|
||||
// page: 1,
|
||||
// } as IAccountsFilter;
|
||||
|
||||
// return this.accountsApplication
|
||||
// .getAccounts(tenantId, parsedQuery)
|
||||
// .then((output) => output.accounts);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,50 @@
|
||||
export const AccountsSampleData = [
|
||||
{
|
||||
'Account Name': 'Utilities Expense',
|
||||
'Account Code': 9000,
|
||||
Type: 'Expense',
|
||||
Description: 'Omnis voluptatum consequatur.',
|
||||
Active: 'T',
|
||||
'Currency Code': '',
|
||||
},
|
||||
{
|
||||
'Account Name': 'Unearned Revenue',
|
||||
'Account Code': 9010,
|
||||
Type: 'Long Term Liability',
|
||||
Description: 'Autem odit voluptas nihil unde.',
|
||||
Active: 'T',
|
||||
'Currency Code': '',
|
||||
},
|
||||
{
|
||||
'Account Name': 'Long-Term Debt',
|
||||
'Account Code': 9020,
|
||||
Type: 'Long Term Liability',
|
||||
Description: 'In voluptas cumque exercitationem.',
|
||||
Active: 'T',
|
||||
'Currency Code': '',
|
||||
},
|
||||
{
|
||||
'Account Name': 'Salaries and Wages Expense',
|
||||
'Account Code': 9030,
|
||||
Type: 'Expense',
|
||||
Description: 'Assumenda aspernatur soluta aliquid perspiciatis quasi.',
|
||||
Active: 'T',
|
||||
'Currency Code': '',
|
||||
},
|
||||
{
|
||||
'Account Name': 'Rental Income',
|
||||
'Account Code': 9040,
|
||||
Type: 'Income',
|
||||
Description: 'Omnis possimus amet occaecati inventore.',
|
||||
Active: 'T',
|
||||
'Currency Code': '',
|
||||
},
|
||||
{
|
||||
'Account Name': 'Paypal',
|
||||
'Account Code': 9050,
|
||||
Type: 'Bank',
|
||||
Description: 'In voluptas cumque exercitationem.',
|
||||
Active: 'T',
|
||||
'Currency Code': '',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,45 @@
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import { Knex } from 'knex';
|
||||
// import { IAccountCreateDTO } from '@/interfaces';
|
||||
// import { CreateAccount } from './CreateAccount.service';
|
||||
// import { Importable } from '../Import/Importable';
|
||||
// import { AccountsSampleData } from './AccountsImportable.SampleData';
|
||||
|
||||
// @Service()
|
||||
// export class AccountsImportable extends Importable {
|
||||
// @Inject()
|
||||
// private createAccountService: CreateAccount;
|
||||
|
||||
// /**
|
||||
// * Importing to account service.
|
||||
// * @param {number} tenantId
|
||||
// * @param {IAccountCreateDTO} createAccountDTO
|
||||
// * @returns
|
||||
// */
|
||||
// public importable(
|
||||
// tenantId: number,
|
||||
// createAccountDTO: IAccountCreateDTO,
|
||||
// trx?: Knex.Transaction
|
||||
// ) {
|
||||
// return this.createAccountService.createAccount(
|
||||
// tenantId,
|
||||
// createAccountDTO,
|
||||
// trx
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Concurrrency controlling of the importing process.
|
||||
// * @returns {number}
|
||||
// */
|
||||
// public get concurrency() {
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieves the sample data that used to download accounts sample sheet.
|
||||
// */
|
||||
// public sampleData(): any[] {
|
||||
// return AccountsSampleData;
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { IAccountEventActivatedPayload } from './Accounts.types';
|
||||
import { AccountModel } from './models/Account.model';
|
||||
import { AccountRepository } from './repositories/Account.repository';
|
||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
@Injectable()
|
||||
export class ActivateAccount {
|
||||
constructor(
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
|
||||
@Inject(AccountModel.name)
|
||||
private readonly accountModel: typeof AccountModel,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Activates/Inactivates the given account.
|
||||
* @param {number} accountId
|
||||
* @param {boolean} activate
|
||||
*/
|
||||
public activateAccount = async (accountId: number, activate?: boolean) => {
|
||||
// Retrieve the given account or throw not found error.
|
||||
const oldAccount = await this.accountModel
|
||||
.query()
|
||||
.findById(accountId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Get all children accounts.
|
||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||
const dependenciesAccounts = accountsGraph.dependenciesOf(accountId);
|
||||
|
||||
const patchAccountsIds = [...dependenciesAccounts, accountId];
|
||||
|
||||
// Activate account and associated transactions under unit-of-work environment.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Activate and inactivate the given accounts ids.
|
||||
activate
|
||||
? await this.accountRepository.activateByIds(patchAccountsIds, trx)
|
||||
: await this.accountRepository.inactivateByIds(patchAccountsIds, trx);
|
||||
|
||||
// Triggers `onAccountActivated` event.
|
||||
this.eventEmitter.emitAsync(events.accounts.onActivated, {
|
||||
accountId,
|
||||
trx,
|
||||
} as IAccountEventActivatedPayload);
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// @ts-nocheck
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
// import { IAccountDTO, IAccount, IAccountCreateDTO } from './Accounts.types';
|
||||
// import AccountTypesUtils from '@/lib/AccountTypes';
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
import { ERRORS, MAX_ACCOUNTS_CHART_DEPTH } from './constants';
|
||||
import { AccountModel } from './models/Account.model';
|
||||
import { AccountRepository } from './repositories/Account.repository';
|
||||
import { AccountTypesUtils } from './utils/AccountType.utils';
|
||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||
import { EditAccountDTO } from './EditAccount.dto';
|
||||
|
||||
@Injectable({ scope: Scope.REQUEST })
|
||||
export class CommandAccountValidators {
|
||||
constructor(
|
||||
@Inject(AccountModel.name)
|
||||
private readonly accountModel: typeof AccountModel,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Throws error if the account was prefined.
|
||||
* @param {AccountModel} account
|
||||
*/
|
||||
public throwErrorIfAccountPredefined(account: AccountModel) {
|
||||
if (account.predefined) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_PREDEFINED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff account type between new and old account, throw service error
|
||||
* if they have different account type.
|
||||
* @param {AccountModel|CreateAccountDTO|EditAccountDTO} oldAccount
|
||||
* @param {AccountModel|CreateAccountDTO|EditAccountDTO} newAccount
|
||||
*/
|
||||
public async isAccountTypeChangedOrThrowError(
|
||||
oldAccount: AccountModel | CreateAccountDTO | EditAccountDTO,
|
||||
newAccount: AccountModel | CreateAccountDTO | EditAccountDTO,
|
||||
) {
|
||||
if (oldAccount.accountType !== newAccount.accountType) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_TYPE_NOT_ALLOWED_TO_CHANGE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account type or throws service error.
|
||||
* @param {string} accountTypeKey -
|
||||
* @return {IAccountType}
|
||||
*/
|
||||
public getAccountTypeOrThrowError(accountTypeKey: string) {
|
||||
const accountType = AccountTypesUtils.getType(accountTypeKey);
|
||||
|
||||
if (!accountType) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_TYPE_NOT_FOUND);
|
||||
}
|
||||
return accountType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve parent account or throw service error.
|
||||
* @param {number} accountId - Account id.
|
||||
* @param {number} notAccountId - Ignore the account id.
|
||||
*/
|
||||
public async getParentAccountOrThrowError(
|
||||
accountId: number,
|
||||
notAccountId?: number,
|
||||
) {
|
||||
const parentAccount = await this.accountModel
|
||||
.query()
|
||||
.findById(accountId)
|
||||
.onBuild((query) => {
|
||||
if (notAccountId) {
|
||||
query.whereNot('id', notAccountId);
|
||||
}
|
||||
});
|
||||
if (!parentAccount) {
|
||||
throw new ServiceError(ERRORS.PARENT_ACCOUNT_NOT_FOUND);
|
||||
}
|
||||
return parentAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws error if the account type was not unique on the storage.
|
||||
* @param {string} accountCode - Account code.
|
||||
* @param {number} notAccountId - Ignore the account id.
|
||||
*/
|
||||
public async isAccountCodeUniqueOrThrowError(
|
||||
accountCode: string,
|
||||
notAccountId?: number,
|
||||
) {
|
||||
const account = await this.accountModel
|
||||
.query()
|
||||
.where('code', accountCode)
|
||||
.onBuild((query) => {
|
||||
if (notAccountId) {
|
||||
query.whereNot('id', notAccountId);
|
||||
}
|
||||
});
|
||||
if (account.length > 0) {
|
||||
throw new ServiceError(
|
||||
ERRORS.ACCOUNT_CODE_NOT_UNIQUE,
|
||||
'Account code is not unique.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the account name uniquiness.
|
||||
* @param {string} accountName - Account name.
|
||||
* @param {number} notAccountId - Ignore the account id.
|
||||
*/
|
||||
public async validateAccountNameUniquiness(
|
||||
accountName: string,
|
||||
notAccountId?: number,
|
||||
) {
|
||||
const foundAccount = await this.accountModel
|
||||
.query()
|
||||
.findOne('name', accountName)
|
||||
.onBuild((query) => {
|
||||
if (notAccountId) {
|
||||
query.whereNot('id', notAccountId);
|
||||
}
|
||||
});
|
||||
if (foundAccount) {
|
||||
throw new ServiceError(
|
||||
ERRORS.ACCOUNT_NAME_NOT_UNIQUE,
|
||||
'Account name is not unique.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given account type supports multi-currency.
|
||||
* @param {CreateAccountDTO | EditAccountDTO} accountDTO -
|
||||
*/
|
||||
public validateAccountTypeSupportCurrency = (
|
||||
accountDTO: CreateAccountDTO | EditAccountDTO,
|
||||
baseCurrency: string,
|
||||
) => {
|
||||
// Can't continue to validate the type has multi-currency feature
|
||||
// if the given currency equals the base currency or not assigned.
|
||||
if (accountDTO.currencyCode === baseCurrency || !accountDTO.currencyCode) {
|
||||
return;
|
||||
}
|
||||
const meta = AccountTypesUtils.getType(accountDTO.accountType);
|
||||
|
||||
// Throw error if the account type does not support multi-currency.
|
||||
if (!meta?.multiCurrency) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the account DTO currency code whether equals the currency code of
|
||||
* parent account.
|
||||
* @param {CreateAccountDTO | EditAccountDTO} accountDTO
|
||||
* @param {AccountModel} parentAccount
|
||||
* @param {string} baseCurrency -
|
||||
* @throws {ServiceError(ERRORS.ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT)}
|
||||
*/
|
||||
public validateCurrentSameParentAccount = (
|
||||
accountDTO: CreateAccountDTO | EditAccountDTO,
|
||||
parentAccount: AccountModel,
|
||||
baseCurrency: string,
|
||||
) => {
|
||||
// If the account DTO currency not assigned and the parent account has no base currency.
|
||||
if (
|
||||
!accountDTO.currencyCode &&
|
||||
parentAccount.currencyCode !== baseCurrency
|
||||
) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT);
|
||||
}
|
||||
// If the account DTO is assigned and not equals the currency code of parent account.
|
||||
if (
|
||||
accountDTO.currencyCode &&
|
||||
parentAccount.currencyCode !== accountDTO.currencyCode
|
||||
) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Throws service error if parent account has different type.
|
||||
* @param {IAccountDTO} accountDTO
|
||||
* @param {IAccount} parentAccount
|
||||
*/
|
||||
public throwErrorIfParentHasDiffType(
|
||||
accountDTO: CreateAccountDTO | EditAccountDTO,
|
||||
parentAccount: AccountModel,
|
||||
) {
|
||||
if (accountDTO.accountType !== parentAccount.accountType) {
|
||||
throw new ServiceError(ERRORS.PARENT_ACCOUNT_HAS_DIFFERENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account of throw service error in case account not found.
|
||||
* @param {number} accountId
|
||||
* @return {IAccount}
|
||||
*/
|
||||
public async getAccountOrThrowError(accountId: number) {
|
||||
const account = await this.accountRepository.findOneById(accountId);
|
||||
|
||||
if (!account) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_NOT_FOUND);
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the max depth level of accounts chart.
|
||||
* @param {number} parentAccountId - Parent account id.
|
||||
*/
|
||||
public async validateMaxParentAccountDepthLevels(parentAccountId: number) {
|
||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||
const parentDependantsIds = accountsGraph.dependantsOf(parentAccountId);
|
||||
|
||||
if (parentDependantsIds.length >= MAX_ACCOUNTS_CHART_DEPTH) {
|
||||
throw new ServiceError(ERRORS.PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsInt,
|
||||
MinLength,
|
||||
MaxLength,
|
||||
IsBoolean,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateAccountDTO {
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(6)
|
||||
code?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
currencyCode?: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
||||
accountType: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(65535) // Assuming DATATYPES_LENGTH.TEXT is 65535
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
parentAccountId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
active?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
plaidAccountId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
plaidItemId?: string;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { kebabCase } from 'lodash';
|
||||
import { Knex } from 'knex';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import {
|
||||
// IAccount,
|
||||
// IAccountEventCreatedPayload,
|
||||
// IAccountCreateDTO,
|
||||
IAccountEventCreatingPayload,
|
||||
CreateAccountParams,
|
||||
} from './Accounts.types';
|
||||
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
||||
import { AccountModel } from './models/Account.model';
|
||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||
|
||||
@Injectable()
|
||||
export class CreateAccountService {
|
||||
constructor(
|
||||
@Inject(AccountModel.name)
|
||||
private readonly accountModel: typeof AccountModel,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly validator: CommandAccountValidators,
|
||||
private readonly tenancyContext: TenancyContext,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Authorize the account creation.
|
||||
* @param {CreateAccountDTO} accountDTO
|
||||
*/
|
||||
private authorize = async (
|
||||
accountDTO: CreateAccountDTO,
|
||||
baseCurrency: string,
|
||||
params?: CreateAccountParams,
|
||||
) => {
|
||||
// Validate account name uniquiness.
|
||||
if (!params.ignoreUniqueName) {
|
||||
await this.validator.validateAccountNameUniquiness(accountDTO.name);
|
||||
}
|
||||
// Validate the account code uniquiness.
|
||||
if (accountDTO.code) {
|
||||
await this.validator.isAccountCodeUniqueOrThrowError(accountDTO.code);
|
||||
}
|
||||
// Retrieve the account type meta or throw service error if not found.
|
||||
this.validator.getAccountTypeOrThrowError(accountDTO.accountType);
|
||||
|
||||
// Ingore the parent account validation if not presented.
|
||||
if (accountDTO.parentAccountId) {
|
||||
const parentAccount = await this.validator.getParentAccountOrThrowError(
|
||||
accountDTO.parentAccountId,
|
||||
);
|
||||
this.validator.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
||||
|
||||
// Inherit active status from parent account.
|
||||
accountDTO.active = parentAccount.active;
|
||||
|
||||
// Validate should currency code be the same currency of parent account.
|
||||
this.validator.validateCurrentSameParentAccount(
|
||||
accountDTO,
|
||||
parentAccount,
|
||||
baseCurrency,
|
||||
);
|
||||
// Validates the max depth level of accounts chart.
|
||||
await this.validator.validateMaxParentAccountDepthLevels(
|
||||
accountDTO.parentAccountId,
|
||||
);
|
||||
}
|
||||
// Validates the given account type supports the multi-currency.
|
||||
this.validator.validateAccountTypeSupportCurrency(accountDTO, baseCurrency);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the create account DTO to input model.
|
||||
* @param {IAccountCreateDTO} createAccountDTO
|
||||
*/
|
||||
private transformDTOToModel = (
|
||||
createAccountDTO: CreateAccountDTO,
|
||||
baseCurrency: string,
|
||||
) => {
|
||||
return {
|
||||
...createAccountDTO,
|
||||
slug: kebabCase(createAccountDTO.name),
|
||||
currencyCode: createAccountDTO.currencyCode || baseCurrency,
|
||||
|
||||
// Mark the account is Plaid owner since Plaid item/account is defined on creating.
|
||||
isSyncingOwner: Boolean(
|
||||
createAccountDTO.plaidAccountId || createAccountDTO.plaidItemId,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new account on the storage.
|
||||
* @param {IAccountCreateDTO} accountDTO
|
||||
* @returns {Promise<IAccount>}
|
||||
*/
|
||||
public createAccount = async (
|
||||
accountDTO: CreateAccountDTO,
|
||||
trx?: Knex.Transaction,
|
||||
params: CreateAccountParams = { ignoreUniqueName: false },
|
||||
): Promise<AccountModel> => {
|
||||
// Retrieves the given tenant metadata.
|
||||
const tenant = await this.tenancyContext.getTenant(true);
|
||||
|
||||
// Authorize the account creation.
|
||||
await this.authorize(accountDTO, tenant.metadata.baseCurrency, params);
|
||||
|
||||
// Transformes the DTO to model.
|
||||
const accountInputModel = this.transformDTOToModel(
|
||||
accountDTO,
|
||||
tenant.metadata.baseCurrency,
|
||||
);
|
||||
// Creates a new account with associated transactions under unit-of-work envirement.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onAccountCreating` event.
|
||||
await this.eventEmitter.emitAsync(events.accounts.onCreating, {
|
||||
accountDTO,
|
||||
trx,
|
||||
} as IAccountEventCreatingPayload);
|
||||
|
||||
// Inserts account to the storage.
|
||||
const account = await this.accountModel.query().insert({
|
||||
...accountInputModel,
|
||||
});
|
||||
// Triggers `onAccountCreated` event.
|
||||
// await this.eventEmitter.emitAsync(events.accounts.onCreated, {
|
||||
// account,
|
||||
// accountId: account.id,
|
||||
// trx,
|
||||
// } as IAccountEventCreatedPayload);
|
||||
|
||||
return account;
|
||||
}, trx);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
// import { IAccountEventDeletedPayload } from '@/interfaces';
|
||||
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
||||
import { AccountModel } from './models/Account.model';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import { IAccountEventDeletedPayload } from './Accounts.types';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteAccount {
|
||||
constructor(
|
||||
@Inject(AccountModel.name) private accountModel: typeof AccountModel,
|
||||
private eventEmitter: EventEmitter2,
|
||||
private uow: UnitOfWork,
|
||||
private validator: CommandAccountValidators,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Authorize account delete.
|
||||
* @param {number} accountId - Account id.
|
||||
*/
|
||||
private authorize = async (accountId: number, oldAccount: AccountModel) => {
|
||||
// Throw error if the account was predefined.
|
||||
this.validator.throwErrorIfAccountPredefined(oldAccount);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unlink the given parent account with children accounts.
|
||||
* @param {number|number[]} parentAccountId -
|
||||
*/
|
||||
private async unassociateChildrenAccountsFromParent(
|
||||
parentAccountId: number | number[],
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const accountsIds = Array.isArray(parentAccountId)
|
||||
? parentAccountId
|
||||
: [parentAccountId];
|
||||
|
||||
await this.accountModel
|
||||
.query(trx)
|
||||
.whereIn('parent_account_id', accountsIds)
|
||||
.patch({ parent_account_id: null });
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the account from the storage.
|
||||
* @param {number} accountId
|
||||
*/
|
||||
public deleteAccount = async (accountId: number): Promise<void> => {
|
||||
// Retrieve account or not found service error.
|
||||
const oldAccount = await this.accountModel.query().findById(accountId);
|
||||
|
||||
// Authorize before delete account.
|
||||
await this.authorize(accountId, oldAccount);
|
||||
|
||||
// Deletes the account and associated transactions under UOW environment.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onAccountDelete` event.
|
||||
await this.eventEmitter.emitAsync(events.accounts.onDelete, {
|
||||
trx,
|
||||
oldAccount,
|
||||
} as IAccountEventDeletedPayload);
|
||||
|
||||
// Unlink the parent account from children accounts.
|
||||
await this.unassociateChildrenAccountsFromParent(accountId, trx);
|
||||
|
||||
// Deletes account by the given id.
|
||||
await this.accountModel.query(trx).deleteById(accountId);
|
||||
|
||||
// Triggers `onAccountDeleted` event.
|
||||
await this.eventEmitter.emitAsync(events.accounts.onDeleted, {
|
||||
accountId,
|
||||
oldAccount,
|
||||
trx,
|
||||
} as IAccountEventDeletedPayload);
|
||||
});
|
||||
};
|
||||
}
|
||||
34
packages/server-nest/src/modules/Accounts/EditAccount.dto.ts
Normal file
34
packages/server-nest/src/modules/Accounts/EditAccount.dto.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsInt,
|
||||
MinLength,
|
||||
MaxLength,
|
||||
} from 'class-validator';
|
||||
|
||||
export class EditAccountDTO {
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(6)
|
||||
code?: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
||||
accountType: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(65535) // Assuming DATATYPES_LENGTH.TEXT is 65535
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
parentAccountId?: number;
|
||||
}
|
||||
100
packages/server-nest/src/modules/Accounts/EditAccount.service.ts
Normal file
100
packages/server-nest/src/modules/Accounts/EditAccount.service.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
||||
import { AccountModel } from './models/Account.model';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import { EditAccountDTO } from './EditAccount.dto';
|
||||
|
||||
@Injectable()
|
||||
export class EditAccount {
|
||||
constructor(
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly validator: CommandAccountValidators,
|
||||
|
||||
@Inject(AccountModel.name)
|
||||
private readonly accountModel: typeof AccountModel,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Authorize the account editing.
|
||||
* @param {number} accountId
|
||||
* @param {IAccountEditDTO} accountDTO
|
||||
* @param {IAccount} oldAccount -
|
||||
*/
|
||||
private authorize = async (
|
||||
accountId: number,
|
||||
accountDTO: EditAccountDTO,
|
||||
oldAccount: AccountModel,
|
||||
) => {
|
||||
// Validate account name uniquiness.
|
||||
await this.validator.validateAccountNameUniquiness(
|
||||
accountDTO.name,
|
||||
accountId,
|
||||
);
|
||||
// Validate the account type should be not mutated.
|
||||
await this.validator.isAccountTypeChangedOrThrowError(
|
||||
oldAccount,
|
||||
accountDTO,
|
||||
);
|
||||
// Validate the account code not exists on the storage.
|
||||
if (accountDTO.code && accountDTO.code !== oldAccount.code) {
|
||||
await this.validator.isAccountCodeUniqueOrThrowError(
|
||||
accountDTO.code,
|
||||
oldAccount.id,
|
||||
);
|
||||
}
|
||||
// Retrieve the parent account of throw not found service error.
|
||||
if (accountDTO.parentAccountId) {
|
||||
const parentAccount = await this.validator.getParentAccountOrThrowError(
|
||||
accountDTO.parentAccountId,
|
||||
oldAccount.id,
|
||||
);
|
||||
this.validator.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits details of the given account.
|
||||
* @param {number} accountId
|
||||
* @param {IAccountDTO} accountDTO
|
||||
*/
|
||||
public async editAccount(
|
||||
accountId: number,
|
||||
accountDTO: EditAccountDTO,
|
||||
): Promise<AccountModel> {
|
||||
// Retrieve the old account or throw not found service error.
|
||||
const oldAccount = await this.accountModel
|
||||
.query()
|
||||
.findById(accountId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Authorize the account editing.
|
||||
await this.authorize(accountId, accountDTO, oldAccount);
|
||||
|
||||
// Edits account and associated transactions under unit-of-work environment.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onAccountEditing` event.
|
||||
await this.eventEmitter.emitAsync(events.accounts.onEditing, {
|
||||
oldAccount,
|
||||
accountDTO,
|
||||
});
|
||||
// Update the account on the storage.
|
||||
const account = await this.accountModel
|
||||
.query(trx)
|
||||
.findById(accountId)
|
||||
.updateAndFetch({ ...accountDTO });
|
||||
|
||||
// Triggers `onAccountEdited` event.
|
||||
// await this.eventEmitter.emitAsync(events.accounts.onEdited, {
|
||||
// account,
|
||||
// oldAccount,
|
||||
// trx,
|
||||
// } as IAccountEventEditedPayload);
|
||||
|
||||
return account;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { AccountTransformer } from './Account.transformer';
|
||||
import { AccountModel } from './models/Account.model';
|
||||
import { AccountRepository } from './repositories/Account.repository';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
@Injectable()
|
||||
export class GetAccount {
|
||||
constructor(
|
||||
@Inject(AccountModel.name)
|
||||
private readonly accountModel: typeof AccountModel,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
private readonly transformer: TransformerInjectable,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the given account details.
|
||||
* @param {number} accountId
|
||||
*/
|
||||
public getAccount = async (accountId: number) => {
|
||||
// Find the given account or throw not found error.
|
||||
const account = await this.accountModel
|
||||
.query()
|
||||
.findById(accountId)
|
||||
.withGraphFetched('plaidItem')
|
||||
.throwIfNotFound();
|
||||
|
||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||
|
||||
// Transforms the account model to POJO.
|
||||
const transformed = await this.transformer.transform(
|
||||
account,
|
||||
new AccountTransformer(),
|
||||
{ accountsGraph },
|
||||
);
|
||||
const eventPayload = { accountId };
|
||||
|
||||
// Triggers `onAccountViewed` event.
|
||||
await this.eventEmitter.emitAsync(events.accounts.onViewed, eventPayload);
|
||||
|
||||
return transformed;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
IAccountsTransactionsFilter,
|
||||
IGetAccountTransactionPOJO,
|
||||
} from './Accounts.types';
|
||||
import { AccountTransactionTransformer } from './AccountTransaction.transformer';
|
||||
import { AccountTransaction } from './models/AccountTransaction.model';
|
||||
import { AccountModel } from './models/Account.model';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
|
||||
@Injectable()
|
||||
export class GetAccountTransactionsService {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(AccountTransaction.name)
|
||||
private readonly accountTransaction: typeof AccountTransaction,
|
||||
|
||||
@Inject(AccountModel.name)
|
||||
private readonly account: typeof AccountModel,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the accounts transactions.
|
||||
* @param {IAccountsTransactionsFilter} filter -
|
||||
*/
|
||||
public getAccountsTransactions = async (
|
||||
filter: IAccountsTransactionsFilter,
|
||||
): Promise<IGetAccountTransactionPOJO[]> => {
|
||||
// Retrieve the given account or throw not found error.
|
||||
if (filter.accountId) {
|
||||
await this.account.query().findById(filter.accountId).throwIfNotFound();
|
||||
}
|
||||
const transactions = await this.accountTransaction
|
||||
.query()
|
||||
.onBuild((query) => {
|
||||
query.orderBy('date', 'DESC');
|
||||
|
||||
if (filter.accountId) {
|
||||
query.where('account_id', filter.accountId);
|
||||
}
|
||||
query.withGraphFetched('account');
|
||||
query.withGraphFetched('contact');
|
||||
query.limit(filter.limit || 50);
|
||||
});
|
||||
// Transform the account transaction.
|
||||
return this.transformer.transform(
|
||||
transactions,
|
||||
new AccountTransactionTransformer(),
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// import { IAccountType } from './Accounts.types';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AccountTypesUtils } from './utils/AccountType.utils';
|
||||
|
||||
@Injectable()
|
||||
export class GetAccountTypesService {
|
||||
/**
|
||||
* Retrieve all accounts types.
|
||||
* @param {number} tenantId -
|
||||
* @return {IAccountType}
|
||||
*/
|
||||
public getAccountsTypes() {
|
||||
const accountTypes = AccountTypesUtils.getList();
|
||||
|
||||
return accountTypes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// import { Injectable } from '@nestjs/common';
|
||||
// import * as R from 'ramda';
|
||||
// import {
|
||||
// IAccountsFilter,
|
||||
// IAccountResponse,
|
||||
// IFilterMeta,
|
||||
// } from '@/interfaces';
|
||||
// import { DynamicListingService } from '@/services/DynamicListing/DynamicListService';
|
||||
// import { AccountTransformer } from './Account.transformer';
|
||||
// import { TransformerService } from '@/lib/Transformer/TransformerService';
|
||||
// import { flatToNestedArray } from '@/utils';
|
||||
// import { Account } from './Account.model';
|
||||
// import { AccountRepository } from './repositories/Account.repository';
|
||||
|
||||
// @Injectable()
|
||||
// export class GetAccountsService {
|
||||
// constructor(
|
||||
// private readonly dynamicListService: DynamicListingService,
|
||||
// private readonly transformerService: TransformerService,
|
||||
// private readonly accountModel: typeof Account,
|
||||
// private readonly accountRepository: AccountRepository,
|
||||
// ) {}
|
||||
|
||||
// /**
|
||||
// * Retrieve accounts datatable list.
|
||||
// * @param {IAccountsFilter} accountsFilter
|
||||
// * @returns {Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }>}
|
||||
// */
|
||||
// public async getAccountsList(
|
||||
// filterDTO: IAccountsFilter,
|
||||
// ): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> {
|
||||
// // Parses the stringified filter roles.
|
||||
// const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// // Dynamic list service.
|
||||
// const dynamicList = await this.dynamicListService.dynamicList(
|
||||
// this.accountModel,
|
||||
// filter,
|
||||
// );
|
||||
// // Retrieve accounts model based on the given query.
|
||||
// const accounts = await this.accountModel.query().onBuild((builder) => {
|
||||
// dynamicList.buildQuery()(builder);
|
||||
// builder.modify('inactiveMode', filter.inactiveMode);
|
||||
// });
|
||||
// const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||
|
||||
// // Retrieves the transformed accounts collection.
|
||||
// const transformedAccounts = await this.transformerService.transform(
|
||||
// accounts,
|
||||
// new AccountTransformer(),
|
||||
// { accountsGraph, structure: filterDTO.structure },
|
||||
// );
|
||||
|
||||
// return {
|
||||
// accounts: transformedAccounts,
|
||||
// filterMeta: dynamicList.getResponseMeta(),
|
||||
// };
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Parsees accounts list filter DTO.
|
||||
// * @param filterDTO
|
||||
// * @returns
|
||||
// */
|
||||
// private parseListFilterDTO(filterDTO) {
|
||||
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,22 @@
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
// @Service()
|
||||
// export class MutateBaseCurrencyAccounts {
|
||||
// @Inject()
|
||||
// tenancy: HasTenancyService;
|
||||
|
||||
// /**
|
||||
// * Mutates the all accounts or the organziation.
|
||||
// * @param {number} tenantId
|
||||
// * @param {string} currencyCode
|
||||
// */
|
||||
// public mutateAllAccountsCurrency = async (
|
||||
// tenantId: number,
|
||||
// currencyCode: string
|
||||
// ) => {
|
||||
// const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
// await Account.query().update({ currencyCode });
|
||||
// };
|
||||
// }
|
||||
103
packages/server-nest/src/modules/Accounts/constants.ts
Normal file
103
packages/server-nest/src/modules/Accounts/constants.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
export const ERRORS = {
|
||||
ACCOUNT_NOT_FOUND: 'account_not_found',
|
||||
ACCOUNT_TYPE_NOT_FOUND: 'account_type_not_found',
|
||||
PARENT_ACCOUNT_NOT_FOUND: 'parent_account_not_found',
|
||||
ACCOUNT_CODE_NOT_UNIQUE: 'account_code_not_unique',
|
||||
ACCOUNT_NAME_NOT_UNIQUE: 'account_name_not_unqiue',
|
||||
PARENT_ACCOUNT_HAS_DIFFERENT_TYPE: 'parent_has_different_type',
|
||||
ACCOUNT_TYPE_NOT_ALLOWED_TO_CHANGE: 'account_type_not_allowed_to_changed',
|
||||
ACCOUNT_PREDEFINED: 'account_predefined',
|
||||
ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS: 'account_has_associated_transactions',
|
||||
PREDEFINED_ACCOUNTS: 'predefined_accounts',
|
||||
ACCOUNTS_HAVE_TRANSACTIONS: 'accounts_have_transactions',
|
||||
CLOSE_ACCOUNT_AND_TO_ACCOUNT_NOT_SAME_TYPE:
|
||||
'close_account_and_to_account_not_same_type',
|
||||
ACCOUNTS_NOT_FOUND: 'accounts_not_found',
|
||||
ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY:
|
||||
'ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY',
|
||||
ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT:
|
||||
'ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT',
|
||||
PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL:
|
||||
'PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL',
|
||||
};
|
||||
|
||||
// Default views columns.
|
||||
export const DEFAULT_VIEW_COLUMNS = [
|
||||
{ key: 'name', label: 'Account name' },
|
||||
{ key: 'code', label: 'Account code' },
|
||||
{ key: 'account_type_label', label: 'Account type' },
|
||||
{ key: 'account_normal', label: 'Account normal' },
|
||||
{ key: 'amount', label: 'Balance' },
|
||||
{ key: 'currencyCode', label: 'Currency' },
|
||||
];
|
||||
|
||||
export const MAX_ACCOUNTS_CHART_DEPTH = 5;
|
||||
|
||||
// Accounts default views.
|
||||
export const DEFAULT_VIEWS = [
|
||||
{
|
||||
name: 'Assets',
|
||||
slug: 'assets',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{ index: 1, fieldKey: 'root_type', comparator: 'equals', value: 'asset' },
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
{
|
||||
name: 'Liabilities',
|
||||
slug: 'liabilities',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{
|
||||
fieldKey: 'root_type',
|
||||
index: 1,
|
||||
comparator: 'equals',
|
||||
value: 'liability',
|
||||
},
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
{
|
||||
name: 'Equity',
|
||||
slug: 'equity',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{
|
||||
fieldKey: 'root_type',
|
||||
index: 1,
|
||||
comparator: 'equals',
|
||||
value: 'equity',
|
||||
},
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
{
|
||||
name: 'Income',
|
||||
slug: 'income',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{
|
||||
fieldKey: 'root_type',
|
||||
index: 1,
|
||||
comparator: 'equals',
|
||||
value: 'income',
|
||||
},
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
{
|
||||
name: 'Expenses',
|
||||
slug: 'expenses',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{
|
||||
fieldKey: 'root_type',
|
||||
index: 1,
|
||||
comparator: 'equals',
|
||||
value: 'expense',
|
||||
},
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
];
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable global-require */
|
||||
import { mixin, Model } from 'objection';
|
||||
// import { mixin, Model } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import DependencyGraph from '@/libs/dependency-graph';
|
||||
import {
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
getAccountsSupportsMultiCurrency,
|
||||
} from '@/constants/accounts';
|
||||
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||
import { SearchableModel } from '@/modules/Search/SearchableMdel';
|
||||
import { CustomViewBaseModel } from '@/modules/CustomViews/CustomViewBaseModel';
|
||||
import { ModelSettings } from '@/modules/Settings/ModelSettings';
|
||||
// import { SearchableModel } from '@/modules/Search/SearchableMdel';
|
||||
// import { CustomViewBaseModel } from '@/modules/CustomViews/CustomViewBaseModel';
|
||||
// import { ModelSettings } from '@/modules/Settings/ModelSettings';
|
||||
import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
|
||||
// import AccountSettings from './Account.Settings';
|
||||
// import { DEFAULT_VIEWS } from '@/modules/Accounts/constants';
|
||||
@@ -17,12 +17,24 @@ import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
|
||||
// import { flatToNestedArray } from 'utils';
|
||||
|
||||
// @ts-expect-error
|
||||
export class Account extends mixin(TenantModel, [
|
||||
ModelSettings,
|
||||
CustomViewBaseModel,
|
||||
SearchableModel,
|
||||
]) {
|
||||
// export class AccountModel extends mixin(TenantModel, [
|
||||
// ModelSettings,
|
||||
// CustomViewBaseModel,
|
||||
// SearchableModel,
|
||||
// ]) {
|
||||
|
||||
export class AccountModel extends TenantModel {
|
||||
name: string;
|
||||
slug: string;
|
||||
code: string;
|
||||
index: number;
|
||||
accountType: string;
|
||||
predefined: boolean;
|
||||
currencyCode: string;
|
||||
active: boolean;
|
||||
bankBalance: number;
|
||||
lastFeedsUpdatedAt: string | null;
|
||||
amount: number;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
@@ -113,7 +125,7 @@ export class Account extends mixin(TenantModel, [
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
const TABLE_NAME = Account.tableName;
|
||||
const TABLE_NAME = AccountModel.tableName;
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -0,0 +1,218 @@
|
||||
import { Model, raw } from 'objection';
|
||||
import moment from 'moment';
|
||||
import { isEmpty, castArray } from 'lodash';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
// import { getTransactionTypeLabel } from '@/utils/transactions-types';
|
||||
|
||||
export class AccountTransaction extends BaseModel {
|
||||
referenceType: string;
|
||||
referenceId: number;
|
||||
credit: number;
|
||||
debit: number;
|
||||
exchangeRate: number;
|
||||
taxRate: number;
|
||||
date: string;
|
||||
transactionType: string;
|
||||
currencyCode: string;
|
||||
referenceTypeFormatted: string;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'accounts_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['referenceTypeFormatted', 'creditLocal', 'debitLocal'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the credit amount in base currency.
|
||||
* @return {number}
|
||||
*/
|
||||
get creditLocal() {
|
||||
return this.credit * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the debit amount in base currency.
|
||||
* @return {number}
|
||||
*/
|
||||
get debitLocal() {
|
||||
return this.debit * this.exchangeRate;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Retrieve formatted reference type.
|
||||
// * @return {string}
|
||||
// */
|
||||
// get referenceTypeFormatted() {
|
||||
// return getTransactionTypeLabel(this.referenceType, this.transactionType);
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Model modifiers.
|
||||
// */
|
||||
// static get modifiers() {
|
||||
// return {
|
||||
// /**
|
||||
// * Filters accounts by the given ids.
|
||||
// * @param {Query} query
|
||||
// * @param {number[]} accountsIds
|
||||
// */
|
||||
// filterAccounts(query, accountsIds) {
|
||||
// if (Array.isArray(accountsIds) && accountsIds.length > 0) {
|
||||
// query.whereIn('account_id', accountsIds);
|
||||
// }
|
||||
// },
|
||||
// filterTransactionTypes(query, types) {
|
||||
// if (Array.isArray(types) && types.length > 0) {
|
||||
// query.whereIn('reference_type', types);
|
||||
// } else if (typeof types === 'string') {
|
||||
// query.where('reference_type', types);
|
||||
// }
|
||||
// },
|
||||
// filterDateRange(query, startDate, endDate, type = 'day') {
|
||||
// const dateFormat = 'YYYY-MM-DD';
|
||||
// const fromDate = moment(startDate).startOf(type).format(dateFormat);
|
||||
// const toDate = moment(endDate).endOf(type).format(dateFormat);
|
||||
|
||||
// if (startDate) {
|
||||
// query.where('date', '>=', fromDate);
|
||||
// }
|
||||
// if (endDate) {
|
||||
// query.where('date', '<=', toDate);
|
||||
// }
|
||||
// },
|
||||
// filterAmountRange(query, fromAmount, toAmount) {
|
||||
// if (fromAmount) {
|
||||
// query.andWhere((q) => {
|
||||
// q.where('credit', '>=', fromAmount);
|
||||
// q.orWhere('debit', '>=', fromAmount);
|
||||
// });
|
||||
// }
|
||||
// if (toAmount) {
|
||||
// query.andWhere((q) => {
|
||||
// q.where('credit', '<=', toAmount);
|
||||
// q.orWhere('debit', '<=', toAmount);
|
||||
// });
|
||||
// }
|
||||
// },
|
||||
// sumationCreditDebit(query) {
|
||||
// query.select(['accountId']);
|
||||
|
||||
// query.sum('credit as credit');
|
||||
// query.sum('debit as debit');
|
||||
// query.groupBy('account_id');
|
||||
// },
|
||||
// filterContactType(query, contactType) {
|
||||
// query.where('contact_type', contactType);
|
||||
// },
|
||||
// filterContactIds(query, contactIds) {
|
||||
// query.whereIn('contact_id', contactIds);
|
||||
// },
|
||||
// openingBalance(query, fromDate) {
|
||||
// query.modify('filterDateRange', null, fromDate);
|
||||
// query.modify('sumationCreditDebit');
|
||||
// },
|
||||
// closingBalance(query, toDate) {
|
||||
// query.modify('filterDateRange', null, toDate);
|
||||
// query.modify('sumationCreditDebit');
|
||||
// },
|
||||
// contactsOpeningBalance(
|
||||
// query,
|
||||
// openingDate,
|
||||
// receivableAccounts,
|
||||
// customersIds
|
||||
// ) {
|
||||
// // Filter by date.
|
||||
// query.modify('filterDateRange', null, openingDate);
|
||||
|
||||
// // Filter by customers.
|
||||
// query.whereNot('contactId', null);
|
||||
// query.whereIn('accountId', castArray(receivableAccounts));
|
||||
|
||||
// if (!isEmpty(customersIds)) {
|
||||
// query.whereIn('contactId', castArray(customersIds));
|
||||
// }
|
||||
// // Group by the contact transactions.
|
||||
// query.groupBy('contactId');
|
||||
// query.sum('credit as credit');
|
||||
// query.sum('debit as debit');
|
||||
// query.select('contactId');
|
||||
// },
|
||||
// creditDebitSummation(query) {
|
||||
// query.sum('credit as credit');
|
||||
// query.sum('debit as debit');
|
||||
// },
|
||||
// groupByDateFormat(query, groupType = 'month') {
|
||||
// const groupBy = {
|
||||
// day: '%Y-%m-%d',
|
||||
// month: '%Y-%m',
|
||||
// year: '%Y',
|
||||
// };
|
||||
// const dateFormat = groupBy[groupType];
|
||||
|
||||
// query.select(raw(`DATE_FORMAT(DATE, '${dateFormat}')`).as('date'));
|
||||
// query.groupByRaw(`DATE_FORMAT(DATE, '${dateFormat}')`);
|
||||
// },
|
||||
|
||||
// filterByBranches(query, branchesIds) {
|
||||
// const formattedBranchesIds = castArray(branchesIds);
|
||||
|
||||
// query.whereIn('branchId', formattedBranchesIds);
|
||||
// },
|
||||
|
||||
// filterByProjects(query, projectsIds) {
|
||||
// const formattedProjectsIds = castArray(projectsIds);
|
||||
|
||||
// query.whereIn('projectId', formattedProjectsIds);
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Relationship mapping.
|
||||
// */
|
||||
// static get relationMappings() {
|
||||
// const Account = require('models/Account');
|
||||
// const Contact = require('models/Contact');
|
||||
|
||||
// return {
|
||||
// account: {
|
||||
// relation: Model.BelongsToOneRelation,
|
||||
// modelClass: Account.default,
|
||||
// join: {
|
||||
// from: 'accounts_transactions.accountId',
|
||||
// to: 'accounts.id',
|
||||
// },
|
||||
// },
|
||||
// contact: {
|
||||
// relation: Model.BelongsToOneRelation,
|
||||
// modelClass: Contact.default,
|
||||
// join: {
|
||||
// from: 'accounts_transactions.contactId',
|
||||
// to: 'contacts.id',
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import { TenantRepository } from '@/common/repository/TenantRepository';
|
||||
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
|
||||
import { AccountModel } from '../models/Account.model';
|
||||
// import { TenantMetadata } from '@/modules/System/models/TenantMetadataModel';
|
||||
// import { IAccount } from '../Accounts.types';
|
||||
// import {
|
||||
// PrepardExpenses,
|
||||
// StripeClearingAccount,
|
||||
// TaxPayableAccount,
|
||||
// UnearnedRevenueAccount,
|
||||
// } from '../Accounts.constants';
|
||||
|
||||
@Injectable({ scope: Scope.REQUEST })
|
||||
export class AccountRepository extends TenantRepository {
|
||||
@Inject(TENANCY_DB_CONNECTION)
|
||||
private readonly tenantDBKnex: Knex;
|
||||
|
||||
/**
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
get model(): typeof AccountModel {
|
||||
return AccountModel.bindKnex(this.tenantDBKnex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts dependency graph.
|
||||
* @param {string} withRelation
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns {}
|
||||
*/
|
||||
public async getDependencyGraph(
|
||||
withRelation?: string,
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const accounts = await this.all(withRelation, trx);
|
||||
|
||||
return this.model.toDependencyGraph(accounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account by slug.
|
||||
* @param {string} slug
|
||||
* @return {Promise<IAccount>}
|
||||
*/
|
||||
public findBySlug(slug: string) {
|
||||
return this.findOne({ slug });
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Changes account balance.
|
||||
// * @param {number} accountId
|
||||
// * @param {number} amount
|
||||
// * @return {Promise<void>}
|
||||
// */
|
||||
// async balanceChange(accountId: number, amount: number): Promise<void> {
|
||||
// const method: string = amount < 0 ? 'decrement' : 'increment';
|
||||
|
||||
// await this.model.query().where('id', accountId)[method]('amount', amount);
|
||||
// this.flushCache();
|
||||
// }
|
||||
|
||||
/**
|
||||
* Activate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
activateById(userId: number): Promise<number> {
|
||||
return super.update({ active: 1 }, { id: userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
inactivateById(userId: number): Promise<number> {
|
||||
return super.update({ active: 0 }, { id: userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async activateByIds(userIds: number[], trx): Promise<number> {
|
||||
const results = await this.model
|
||||
.query(trx)
|
||||
.whereIn('id', userIds)
|
||||
.patch({ active: true });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async inactivateByIds(userIds: number[], trx): Promise<number> {
|
||||
const results = await this.model
|
||||
.query(trx)
|
||||
.whereIn('id', userIds)
|
||||
.patch({ active: false });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// /**
|
||||
// *
|
||||
// * @param {string} currencyCode
|
||||
// * @param extraAttrs
|
||||
// * @param trx
|
||||
// * @returns
|
||||
// */
|
||||
// findOrCreateAccountReceivable = async (
|
||||
// currencyCode: string = '',
|
||||
// extraAttrs = {},
|
||||
// trx?: Knex.Transaction,
|
||||
// ) => {
|
||||
// let result = await this.model
|
||||
// .query(trx)
|
||||
// .onBuild((query) => {
|
||||
// if (currencyCode) {
|
||||
// query.where('currencyCode', currencyCode);
|
||||
// }
|
||||
// query.where('accountType', 'accounts-receivable');
|
||||
// })
|
||||
// .first();
|
||||
|
||||
// if (!result) {
|
||||
// result = await this.model.query(trx).insertAndFetch({
|
||||
// name: this.i18n.__('account.accounts_receivable.currency', {
|
||||
// currency: currencyCode,
|
||||
// }),
|
||||
// accountType: 'accounts-receivable',
|
||||
// currencyCode,
|
||||
// active: 1,
|
||||
// ...extraAttrs,
|
||||
// });
|
||||
// }
|
||||
// return result;
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Find or create tax payable account.
|
||||
// * @param {Record<string, string>}extraAttrs
|
||||
// * @param {Knex.Transaction} trx
|
||||
// * @returns
|
||||
// */
|
||||
// async findOrCreateTaxPayable(
|
||||
// extraAttrs: Record<string, string> = {},
|
||||
// trx?: Knex.Transaction,
|
||||
// ) {
|
||||
// let result = await this.model
|
||||
// .query(trx)
|
||||
// .findOne({ slug: TaxPayableAccount.slug, ...extraAttrs });
|
||||
|
||||
// if (!result) {
|
||||
// result = await this.model.query(trx).insertAndFetch({
|
||||
// ...TaxPayableAccount,
|
||||
// ...extraAttrs,
|
||||
// });
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// findOrCreateAccountsPayable = async (
|
||||
// currencyCode: string = '',
|
||||
// extraAttrs = {},
|
||||
// trx?: Knex.Transaction,
|
||||
// ) => {
|
||||
// let result = await this.model
|
||||
// .query(trx)
|
||||
// .onBuild((query) => {
|
||||
// if (currencyCode) {
|
||||
// query.where('currencyCode', currencyCode);
|
||||
// }
|
||||
// query.where('accountType', 'accounts-payable');
|
||||
// })
|
||||
// .first();
|
||||
|
||||
// if (!result) {
|
||||
// result = await this.model.query(trx).insertAndFetch({
|
||||
// name: this.i18n.__('account.accounts_payable.currency', {
|
||||
// currency: currencyCode,
|
||||
// }),
|
||||
// accountType: 'accounts-payable',
|
||||
// currencyCode,
|
||||
// active: 1,
|
||||
// ...extraAttrs,
|
||||
// });
|
||||
// }
|
||||
// return result;
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Finds or creates the unearned revenue.
|
||||
// * @param {Record<string, string>} extraAttrs
|
||||
// * @param {Knex.Transaction} trx
|
||||
// * @returns
|
||||
// */
|
||||
// public async findOrCreateUnearnedRevenue(
|
||||
// extraAttrs: Record<string, string> = {},
|
||||
// trx?: Knex.Transaction,
|
||||
// ) {
|
||||
// // Retrieves the given tenant metadata.
|
||||
// const tenantMeta = await TenantMetadata.query().findOne({
|
||||
// tenantId: this.tenantId,
|
||||
// });
|
||||
// const _extraAttrs = {
|
||||
// currencyCode: tenantMeta.baseCurrency,
|
||||
// ...extraAttrs,
|
||||
// };
|
||||
// let result = await this.model
|
||||
// .query(trx)
|
||||
// .findOne({ slug: UnearnedRevenueAccount.slug, ..._extraAttrs });
|
||||
|
||||
// if (!result) {
|
||||
// result = await this.model.query(trx).insertAndFetch({
|
||||
// ...UnearnedRevenueAccount,
|
||||
// ..._extraAttrs,
|
||||
// });
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Finds or creates the prepard expenses account.
|
||||
// * @param {Record<string, string>} extraAttrs
|
||||
// * @param {Knex.Transaction} trx
|
||||
// * @returns
|
||||
// */
|
||||
// public async findOrCreatePrepardExpenses(
|
||||
// extraAttrs: Record<string, string> = {},
|
||||
// trx?: Knex.Transaction,
|
||||
// ) {
|
||||
// // Retrieves the given tenant metadata.
|
||||
// const tenantMeta = await TenantMetadata.query().findOne({
|
||||
// tenantId: this.tenantId,
|
||||
// });
|
||||
// const _extraAttrs = {
|
||||
// currencyCode: tenantMeta.baseCurrency,
|
||||
// ...extraAttrs,
|
||||
// };
|
||||
|
||||
// let result = await this.model
|
||||
// .query(trx)
|
||||
// .findOne({ slug: PrepardExpenses.slug, ..._extraAttrs });
|
||||
|
||||
// if (!result) {
|
||||
// result = await this.model.query(trx).insertAndFetch({
|
||||
// ...PrepardExpenses,
|
||||
// ..._extraAttrs,
|
||||
// });
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Finds or creates the stripe clearing account.
|
||||
// * @param {Record<string, string>} extraAttrs
|
||||
// * @param {Knex.Transaction} trx
|
||||
// * @returns
|
||||
// */
|
||||
// public async findOrCreateStripeClearing(
|
||||
// extraAttrs: Record<string, string> = {},
|
||||
// trx?: Knex.Transaction,
|
||||
// ) {
|
||||
// // Retrieves the given tenant metadata.
|
||||
// const tenantMeta = await TenantMetadata.query().findOne({
|
||||
// tenantId: this.tenantId,
|
||||
// });
|
||||
// const _extraAttrs = {
|
||||
// currencyCode: tenantMeta.baseCurrency,
|
||||
// ...extraAttrs,
|
||||
// };
|
||||
// let result = await this.model
|
||||
// .query(trx)
|
||||
// .findOne({ slug: StripeClearingAccount.slug, ..._extraAttrs });
|
||||
|
||||
// if (!result) {
|
||||
// result = await this.model.query(trx).insertAndFetch({
|
||||
// ...StripeClearingAccount,
|
||||
// ..._extraAttrs,
|
||||
// });
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import events from '@/subscribers/events';
|
||||
// import { MutateBaseCurrencyAccounts } from '../MutateBaseCurrencyAccounts';
|
||||
|
||||
// @Service()
|
||||
// export class MutateBaseCurrencyAccountsSubscriber {
|
||||
// @Inject()
|
||||
// public mutateBaseCurrencyAccounts: MutateBaseCurrencyAccounts;
|
||||
|
||||
// /**
|
||||
// * Attaches the events with handles.
|
||||
// * @param bus
|
||||
// */
|
||||
// attach(bus) {
|
||||
// bus.subscribe(
|
||||
// events.organization.baseCurrencyUpdated,
|
||||
// this.updateAccountsCurrencyOnBaseCurrencyMutated
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Updates the all accounts currency once the base currency
|
||||
// * of the organization is mutated.
|
||||
// */
|
||||
// private updateAccountsCurrencyOnBaseCurrencyMutated = async ({
|
||||
// tenantId,
|
||||
// organizationDTO,
|
||||
// }) => {
|
||||
// await this.mutateBaseCurrencyAccounts.mutateAllAccountsCurrency(
|
||||
// tenantId,
|
||||
// organizationDTO.baseCurrency
|
||||
// );
|
||||
// };
|
||||
// }
|
||||
@@ -0,0 +1,101 @@
|
||||
import { get } from 'lodash';
|
||||
import { ACCOUNT_TYPES } from '../Accounts.constants';
|
||||
|
||||
export class AccountTypesUtils {
|
||||
/**
|
||||
* Retrieve account types list.
|
||||
*/
|
||||
static getList() {
|
||||
return ACCOUNT_TYPES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts types by the given root type.
|
||||
* @param {string} rootType -
|
||||
* @return {string}
|
||||
*/
|
||||
static getTypesByRootType(rootType: string) {
|
||||
return ACCOUNT_TYPES.filter((type) => type.rootType === rootType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account type by the given account type key.
|
||||
* @param {string} key
|
||||
* @param {string} accessor
|
||||
*/
|
||||
static getType(key: string, accessor?: string) {
|
||||
const type = ACCOUNT_TYPES.find((type) => type.key === key);
|
||||
|
||||
if (accessor) {
|
||||
return get(type, accessor);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts types by the parent account type.
|
||||
* @param {string} parentType
|
||||
*/
|
||||
static getTypesByParentType(parentType: string) {
|
||||
return ACCOUNT_TYPES.filter((type) => type.parentType === parentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts types by the given account normal.
|
||||
* @param {string} normal
|
||||
*/
|
||||
static getTypesByNormal(normal: string) {
|
||||
return ACCOUNT_TYPES.filter((type) => type.normal === normal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the root type equals the account type.
|
||||
* @param {string} key
|
||||
* @param {string} rootType
|
||||
*/
|
||||
static isRootTypeEqualsKey(key: string, rootType: string): boolean {
|
||||
return ACCOUNT_TYPES.some((type) => {
|
||||
const isType = type.key === key;
|
||||
const isRootType = type.rootType === rootType;
|
||||
|
||||
return isType && isRootType;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the parent account type equals the account type key.
|
||||
* @param {string} key - Account type key.
|
||||
* @param {string} parentType - Account parent type.
|
||||
*/
|
||||
static isParentTypeEqualsKey(key: string, parentType: string): boolean {
|
||||
return ACCOUNT_TYPES.some((type) => {
|
||||
const isType = type.key === key;
|
||||
const isParentType = type.parentType === parentType;
|
||||
|
||||
return isType && isParentType;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether account type has balance sheet.
|
||||
* @param {string} key - Account type key.
|
||||
*
|
||||
*/
|
||||
static isTypeBalanceSheet(key: string): boolean {
|
||||
return ACCOUNT_TYPES.some((type) => {
|
||||
const isType = type.key === key;
|
||||
return isType && type.balanceSheet;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether account type has profit/loss sheet.
|
||||
* @param {string} key - Account type key.
|
||||
*/
|
||||
static isTypePLSheet(key: string): boolean {
|
||||
return ACCOUNT_TYPES.some((type) => {
|
||||
const isType = type.key === key;
|
||||
return isType && type.incomeSheet;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import { UserIpInterceptor } from '@/interceptors/user-ip.interceptor';
|
||||
import { TenancyGlobalMiddleware } from '../Tenancy/TenancyGlobal.middleware';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
import { TransformerModule } from '../Transformer/Transformer.module';
|
||||
import { AccountsModule } from '../Accounts/Accounts.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -85,6 +86,7 @@ import { TransformerModule } from '../Transformer/Transformer.module';
|
||||
TenancyDatabaseModule,
|
||||
TenancyModelsModule,
|
||||
ItemsModule,
|
||||
AccountsModule
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Transformer } from '../Transformer/Transformer';
|
||||
import { Item } from './models/Item';
|
||||
// import { GetItemWarehouseTransformer } from '@/services/Warehouses/Items/GettItemWarehouseTransformer';
|
||||
|
||||
export class ItemTransformer extends Transformer {
|
||||
@@ -20,7 +21,7 @@ export class ItemTransformer extends Transformer {
|
||||
* @param {IItem} item
|
||||
* @returns {string}
|
||||
*/
|
||||
public typeFormatted(item): string {
|
||||
public typeFormatted(item: Item): string {
|
||||
return this.context.i18n.t(`item.field.type.${item.type}`);
|
||||
}
|
||||
|
||||
@@ -29,7 +30,7 @@ export class ItemTransformer extends Transformer {
|
||||
* @param item
|
||||
* @returns {string}
|
||||
*/
|
||||
public sellPriceFormatted(item): string {
|
||||
public sellPriceFormatted(item: Item): string {
|
||||
return this.formatNumber(item.sellPrice, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
@@ -40,7 +41,7 @@ export class ItemTransformer extends Transformer {
|
||||
* @param item
|
||||
* @returns {string}
|
||||
*/
|
||||
public costPriceFormatted(item): string {
|
||||
public costPriceFormatted(item: Item): string {
|
||||
return this.formatNumber(item.costPrice, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
|
||||
@@ -8,13 +8,13 @@ import { ServiceError } from './ServiceError';
|
||||
import { IItem, IItemDTO } from '@/interfaces/Item';
|
||||
import { ERRORS } from './Items.constants';
|
||||
import { Item } from './models/Item';
|
||||
import { Account } from '../Accounts/models/Account';
|
||||
import { AccountModel } from '../Accounts/models/Account.model';
|
||||
|
||||
@Injectable()
|
||||
export class ItemsValidators {
|
||||
constructor(
|
||||
@Inject(Item.name) private itemModel: typeof Item,
|
||||
@Inject(Account.name) private accountModel: typeof Account,
|
||||
@Inject(AccountModel.name) private accountModel: typeof AccountModel,
|
||||
@Inject(Item.name) private taxRateModel: typeof Item,
|
||||
@Inject(Item.name) private itemEntryModel: typeof Item,
|
||||
@Inject(Item.name) private itemCategoryModel: typeof Item,
|
||||
|
||||
@@ -3,10 +3,11 @@ import { Global, Module, Scope } from '@nestjs/common';
|
||||
import { TENANCY_DB_CONNECTION } from '../TenancyDB/TenancyDB.constants';
|
||||
|
||||
import { Item } from '../../../modules/Items/models/Item';
|
||||
import { Account } from '@/modules/Accounts/models/Account';
|
||||
import { AccountModel } from '@/modules/Accounts/models/Account.model';
|
||||
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
|
||||
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||
|
||||
const models = [Item, Account, ItemEntry];
|
||||
const models = [Item, AccountModel, ItemEntry, AccountTransaction];
|
||||
|
||||
const modelProviders = models.map((model) => {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user