mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
refactor: accounts module to Nestjs
This commit is contained in:
@@ -36,6 +36,7 @@
|
|||||||
"@types/ramda": "^0.30.2",
|
"@types/ramda": "^0.30.2",
|
||||||
"js-money": "^0.6.3",
|
"js-money": "^0.6.3",
|
||||||
"accounting": "^0.4.1",
|
"accounting": "^0.4.1",
|
||||||
|
"object-hash": "^2.0.3",
|
||||||
"bull": "^4.16.3",
|
"bull": "^4.16.3",
|
||||||
"bullmq": "^5.21.1",
|
"bullmq": "^5.21.1",
|
||||||
"cache-manager": "^6.1.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 */
|
/* eslint-disable global-require */
|
||||||
import { mixin, Model } from 'objection';
|
// import { mixin, Model } from 'objection';
|
||||||
import { castArray } from 'lodash';
|
import { castArray } from 'lodash';
|
||||||
import DependencyGraph from '@/libs/dependency-graph';
|
import DependencyGraph from '@/libs/dependency-graph';
|
||||||
import {
|
import {
|
||||||
@@ -7,9 +7,9 @@ import {
|
|||||||
getAccountsSupportsMultiCurrency,
|
getAccountsSupportsMultiCurrency,
|
||||||
} from '@/constants/accounts';
|
} from '@/constants/accounts';
|
||||||
import { TenantModel } from '@/modules/System/models/TenantModel';
|
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||||
import { SearchableModel } from '@/modules/Search/SearchableMdel';
|
// import { SearchableModel } from '@/modules/Search/SearchableMdel';
|
||||||
import { CustomViewBaseModel } from '@/modules/CustomViews/CustomViewBaseModel';
|
// import { CustomViewBaseModel } from '@/modules/CustomViews/CustomViewBaseModel';
|
||||||
import { ModelSettings } from '@/modules/Settings/ModelSettings';
|
// import { ModelSettings } from '@/modules/Settings/ModelSettings';
|
||||||
import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
|
import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
|
||||||
// import AccountSettings from './Account.Settings';
|
// import AccountSettings from './Account.Settings';
|
||||||
// import { DEFAULT_VIEWS } from '@/modules/Accounts/constants';
|
// import { DEFAULT_VIEWS } from '@/modules/Accounts/constants';
|
||||||
@@ -17,12 +17,24 @@ import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
|
|||||||
// import { flatToNestedArray } from 'utils';
|
// import { flatToNestedArray } from 'utils';
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
export class Account extends mixin(TenantModel, [
|
// export class AccountModel extends mixin(TenantModel, [
|
||||||
ModelSettings,
|
// ModelSettings,
|
||||||
CustomViewBaseModel,
|
// CustomViewBaseModel,
|
||||||
SearchableModel,
|
// SearchableModel,
|
||||||
]) {
|
// ]) {
|
||||||
|
|
||||||
|
export class AccountModel extends TenantModel {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
code: string;
|
||||||
|
index: number;
|
||||||
accountType: string;
|
accountType: string;
|
||||||
|
predefined: boolean;
|
||||||
|
currencyCode: string;
|
||||||
|
active: boolean;
|
||||||
|
bankBalance: number;
|
||||||
|
lastFeedsUpdatedAt: string | null;
|
||||||
|
amount: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
@@ -113,7 +125,7 @@ export class Account extends mixin(TenantModel, [
|
|||||||
* Model modifiers.
|
* Model modifiers.
|
||||||
*/
|
*/
|
||||||
static get modifiers() {
|
static get modifiers() {
|
||||||
const TABLE_NAME = Account.tableName;
|
const TABLE_NAME = AccountModel.tableName;
|
||||||
|
|
||||||
return {
|
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 { TenancyGlobalMiddleware } from '../Tenancy/TenancyGlobal.middleware';
|
||||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||||
import { TransformerModule } from '../Transformer/Transformer.module';
|
import { TransformerModule } from '../Transformer/Transformer.module';
|
||||||
|
import { AccountsModule } from '../Accounts/Accounts.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -85,6 +86,7 @@ import { TransformerModule } from '../Transformer/Transformer.module';
|
|||||||
TenancyDatabaseModule,
|
TenancyDatabaseModule,
|
||||||
TenancyModelsModule,
|
TenancyModelsModule,
|
||||||
ItemsModule,
|
ItemsModule,
|
||||||
|
AccountsModule
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Transformer } from '../Transformer/Transformer';
|
import { Transformer } from '../Transformer/Transformer';
|
||||||
|
import { Item } from './models/Item';
|
||||||
// import { GetItemWarehouseTransformer } from '@/services/Warehouses/Items/GettItemWarehouseTransformer';
|
// import { GetItemWarehouseTransformer } from '@/services/Warehouses/Items/GettItemWarehouseTransformer';
|
||||||
|
|
||||||
export class ItemTransformer extends Transformer {
|
export class ItemTransformer extends Transformer {
|
||||||
@@ -20,7 +21,7 @@ export class ItemTransformer extends Transformer {
|
|||||||
* @param {IItem} item
|
* @param {IItem} item
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
public typeFormatted(item): string {
|
public typeFormatted(item: Item): string {
|
||||||
return this.context.i18n.t(`item.field.type.${item.type}`);
|
return this.context.i18n.t(`item.field.type.${item.type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ export class ItemTransformer extends Transformer {
|
|||||||
* @param item
|
* @param item
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
public sellPriceFormatted(item): string {
|
public sellPriceFormatted(item: Item): string {
|
||||||
return this.formatNumber(item.sellPrice, {
|
return this.formatNumber(item.sellPrice, {
|
||||||
currencyCode: this.context.organization.baseCurrency,
|
currencyCode: this.context.organization.baseCurrency,
|
||||||
});
|
});
|
||||||
@@ -40,7 +41,7 @@ export class ItemTransformer extends Transformer {
|
|||||||
* @param item
|
* @param item
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
public costPriceFormatted(item): string {
|
public costPriceFormatted(item: Item): string {
|
||||||
return this.formatNumber(item.costPrice, {
|
return this.formatNumber(item.costPrice, {
|
||||||
currencyCode: this.context.organization.baseCurrency,
|
currencyCode: this.context.organization.baseCurrency,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ import { ServiceError } from './ServiceError';
|
|||||||
import { IItem, IItemDTO } from '@/interfaces/Item';
|
import { IItem, IItemDTO } from '@/interfaces/Item';
|
||||||
import { ERRORS } from './Items.constants';
|
import { ERRORS } from './Items.constants';
|
||||||
import { Item } from './models/Item';
|
import { Item } from './models/Item';
|
||||||
import { Account } from '../Accounts/models/Account';
|
import { AccountModel } from '../Accounts/models/Account.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ItemsValidators {
|
export class ItemsValidators {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(Item.name) private itemModel: typeof Item,
|
@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 taxRateModel: typeof Item,
|
||||||
@Inject(Item.name) private itemEntryModel: typeof Item,
|
@Inject(Item.name) private itemEntryModel: typeof Item,
|
||||||
@Inject(Item.name) private itemCategoryModel: 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 { TENANCY_DB_CONNECTION } from '../TenancyDB/TenancyDB.constants';
|
||||||
|
|
||||||
import { Item } from '../../../modules/Items/models/Item';
|
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 { 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) => {
|
const modelProviders = models.map((model) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user