mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
add server to monorepo.
This commit is contained in:
76
packages/server/src/collection/BudgetEntriesSet.ts
Normal file
76
packages/server/src/collection/BudgetEntriesSet.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
|
||||
export default class BudgetEntriesSet {
|
||||
|
||||
constructor() {
|
||||
this.accounts = {};
|
||||
this.totalSummary = {}
|
||||
this.orderSize = null;
|
||||
}
|
||||
|
||||
setZeroPlaceholder() {
|
||||
if (!this.orderSize) { return; }
|
||||
|
||||
Object.values(this.accounts).forEach((account) => {
|
||||
|
||||
for (let i = 0; i <= this.orderSize.length; i++) {
|
||||
if (typeof account[i] === 'undefined') {
|
||||
account[i] = { amount: 0 };
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static from(accounts, configs) {
|
||||
const collection = new this(configs);
|
||||
|
||||
accounts.forEach((entry) => {
|
||||
if (typeof this.accounts[entry.accountId] === 'undefined') {
|
||||
collection.accounts[entry.accountId] = {};
|
||||
}
|
||||
if (entry.order) {
|
||||
collection.accounts[entry.accountId][entry.order] = entry;
|
||||
}
|
||||
});
|
||||
return collection;
|
||||
}
|
||||
|
||||
toArray() {
|
||||
const output = [];
|
||||
|
||||
Object.key(this.accounts).forEach((accountId) => {
|
||||
const entries = this.accounts[accountId];
|
||||
output.push({
|
||||
account_id: accountId,
|
||||
entries: [
|
||||
...Object.key(entries).map((order) => {
|
||||
const entry = entries[order];
|
||||
return {
|
||||
order,
|
||||
amount: entry.amount,
|
||||
};
|
||||
}),
|
||||
],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
calcTotalSummary() {
|
||||
const totalSummary = {};
|
||||
|
||||
for (let i = 0; i < this.orderSize.length; i++) {
|
||||
Object.value(this.accounts).forEach((account) => {
|
||||
if (typeof totalSummary[i] !== 'undefined') {
|
||||
totalSummary[i] = { amount: 0, order: i };
|
||||
}
|
||||
totalSummary[i].amount += account[i].amount;
|
||||
});
|
||||
}
|
||||
this.totalSummary = totalSummary;
|
||||
}
|
||||
|
||||
toArrayTotalSummary() {
|
||||
return Object.values(this.totalSummary);
|
||||
}
|
||||
|
||||
}
|
||||
0
packages/server/src/collection/Cachable.ts
Normal file
0
packages/server/src/collection/Cachable.ts
Normal file
279
packages/server/src/collection/Metable.ts
Normal file
279
packages/server/src/collection/Metable.ts
Normal file
@@ -0,0 +1,279 @@
|
||||
|
||||
|
||||
export default {
|
||||
METADATA_GROUP: 'default',
|
||||
KEY_COLUMN: 'key',
|
||||
VALUE_COLUMN: 'value',
|
||||
TYPE_COLUMN: 'type',
|
||||
|
||||
extraColumns: [],
|
||||
metadata: [],
|
||||
shouldReload: true,
|
||||
extraMetadataQuery: () => {},
|
||||
|
||||
/**
|
||||
* Set the value column key to query from.
|
||||
* @param {String} name -
|
||||
*/
|
||||
setKeyColumnName(name) {
|
||||
this.KEY_COLUMN = name;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the key column name to query from.
|
||||
* @param {String} name -
|
||||
*/
|
||||
setValueColumnName(name) {
|
||||
this.VALUE_COLUMN = name;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set extra columns to be added to the rows.
|
||||
* @param {Array} columns -
|
||||
*/
|
||||
setExtraColumns(columns) {
|
||||
this.extraColumns = columns;
|
||||
},
|
||||
|
||||
/**
|
||||
* Metadata database query.
|
||||
* @param {Object} query -
|
||||
* @param {String} groupName -
|
||||
*/
|
||||
whereQuery(query, key) {
|
||||
const groupName = this.METADATA_GROUP;
|
||||
|
||||
if (groupName) {
|
||||
query.where('group', groupName);
|
||||
}
|
||||
if (key) {
|
||||
if (Array.isArray(key)) {
|
||||
query.whereIn('key', key);
|
||||
} else {
|
||||
query.where('key', key);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the metadata from the storage.
|
||||
* @param {String|Array} key -
|
||||
* @param {Boolean} force -
|
||||
*/
|
||||
async load(force = false) {
|
||||
if (this.shouldReload || force) {
|
||||
const metadataCollection = await this.query((query) => {
|
||||
this.whereQuery(query);
|
||||
this.extraMetadataQuery(query);
|
||||
}).fetchAll();
|
||||
|
||||
this.shouldReload = false;
|
||||
this.metadata = [];
|
||||
|
||||
const metadataArray = this.mapMetadataCollection(metadataCollection);
|
||||
metadataArray.forEach((metadata) => { this.metadata.push(metadata); });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches all the metadata that associate with the current group.
|
||||
*/
|
||||
async allMeta(force = false) {
|
||||
await this.load(force);
|
||||
return this.metadata;
|
||||
},
|
||||
|
||||
/**
|
||||
* Find the given metadata key.
|
||||
* @param {String} key -
|
||||
* @return {object} - Metadata object.
|
||||
*/
|
||||
findMeta(key) {
|
||||
return this.metadata.find((meta) => meta.key === key);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the metadata of the current group.
|
||||
* @param {*} key -
|
||||
*/
|
||||
async getMeta(key, defaultValue, force = false) {
|
||||
await this.load(force);
|
||||
|
||||
const metadata = this.findMeta(key);
|
||||
return metadata ? metadata.value : defaultValue || false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Markes the metadata to should be deleted.
|
||||
* @param {String} key -
|
||||
*/
|
||||
async removeMeta(key) {
|
||||
await this.load();
|
||||
const metadata = this.findMeta(key);
|
||||
|
||||
if (metadata) {
|
||||
metadata.markAsDeleted = true;
|
||||
}
|
||||
this.shouldReload = true;
|
||||
|
||||
|
||||
/**
|
||||
* Remove all meta data of the given group.
|
||||
* @param {*} group
|
||||
*/
|
||||
removeAllMeta(group = 'default') {
|
||||
this.metdata.map((meta) => ({
|
||||
...(meta.group !== group) ? { markAsDeleted: true } : {},
|
||||
...meta,
|
||||
}));
|
||||
this.shouldReload = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the meta data to the stack.
|
||||
* @param {String} key -
|
||||
* @param {String} value -
|
||||
*/
|
||||
async setMeta(key, value, payload) {
|
||||
if (Array.isArray(key)) {
|
||||
const metadata = key;
|
||||
metadata.forEach((meta) => {
|
||||
this.setMeta(meta.key, meta.value);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.load();
|
||||
const metadata = this.findMeta(key);
|
||||
|
||||
if (metadata) {
|
||||
metadata.value = value;
|
||||
metadata.markAsUpdated = true;
|
||||
} else {
|
||||
this.metadata.push({
|
||||
value, key, ...payload, markAsInserted: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Saved the modified metadata.
|
||||
*/
|
||||
async saveMeta() {
|
||||
const inserted = this.metadata.filter((m) => (m.markAsInserted === true));
|
||||
const updated = this.metadata.filter((m) => (m.markAsUpdated === true));
|
||||
const deleted = this.metadata.filter((m) => (m.markAsDeleted === true));
|
||||
|
||||
const metadataDeletedKeys = deleted.map((m) => m.key);
|
||||
const metadataInserted = inserted.map((m) => this.mapMetadata(m, 'format'));
|
||||
const metadataUpdated = updated.map((m) => this.mapMetadata(m, 'format'));
|
||||
|
||||
const batchUpdate = (collection) => knex.transaction((trx) => {
|
||||
const queries = collection.map((tuple) => {
|
||||
const query = knex(this.tableName);
|
||||
this.whereQuery(query, tuple.key);
|
||||
this.extraMetadataQuery(query);
|
||||
return query.update(tuple).transacting(trx);
|
||||
});
|
||||
return Promise.all(queries).then(trx.commit).catch(trx.rollback);
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
knex.insert(metadataInserted).into(this.tableName),
|
||||
batchUpdate(metadataUpdated),
|
||||
metadataDeletedKeys.length > 0
|
||||
? this.query('whereIn', this.KEY_COLUMN, metadataDeletedKeys).destroy({
|
||||
require: true,
|
||||
}) : null,
|
||||
]);
|
||||
this.shouldReload = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Purge all the cached metadata in the memory.
|
||||
*/
|
||||
purgeMetadata() {
|
||||
this.metadata = [];
|
||||
this.shouldReload = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parses the metadata value.
|
||||
* @param {String} value -
|
||||
* @param {String} valueType -
|
||||
*/
|
||||
parseMetaValue(value, valueType) {
|
||||
let parsedValue;
|
||||
|
||||
switch (valueType) {
|
||||
case 'integer':
|
||||
parsedValue = parseInt(value, 10);
|
||||
break;
|
||||
case 'float':
|
||||
parsedValue = parseFloat(value);
|
||||
break;
|
||||
case 'boolean':
|
||||
parsedValue = Boolean(value);
|
||||
break;
|
||||
case 'json':
|
||||
parsedValue = JSON.parse(parsedValue);
|
||||
break;
|
||||
default:
|
||||
parsedValue = value;
|
||||
break;
|
||||
}
|
||||
return parsedValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Format the metadata before saving to the database.
|
||||
* @param {String|Number|Boolean} value -
|
||||
* @param {String} valueType -
|
||||
* @return {String|Number|Boolean} -
|
||||
*/
|
||||
formatMetaValue(value, valueType) {
|
||||
let parsedValue;
|
||||
|
||||
switch (valueType) {
|
||||
case 'number':
|
||||
parsedValue = `${value}`;
|
||||
break;
|
||||
case 'boolean':
|
||||
parsedValue = value ? '1' : '0';
|
||||
break;
|
||||
case 'json':
|
||||
parsedValue = JSON.stringify(parsedValue);
|
||||
break;
|
||||
default:
|
||||
parsedValue = value;
|
||||
break;
|
||||
}
|
||||
return parsedValue;
|
||||
},
|
||||
|
||||
mapMetadata(attr, parseType = 'parse') {
|
||||
return {
|
||||
key: attr[this.KEY_COLUMN],
|
||||
value: (parseType === 'parse')
|
||||
? this.parseMetaValue(
|
||||
attr[this.VALUE_COLUMN],
|
||||
this.TYPE_COLUMN ? attr[this.TYPE_COLUMN] : false,
|
||||
)
|
||||
: this.formatMetaValue(
|
||||
attr[this.VALUE_COLUMN],
|
||||
this.TYPE_COLUMN ? attr[this.TYPE_COLUMN] : false,
|
||||
),
|
||||
...this.extraColumns.map((extraCol) => ({
|
||||
[extraCol]: attr[extraCol] || null,
|
||||
})),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse the metadata collection.
|
||||
* @param {Array} collection -
|
||||
*/
|
||||
mapMetadataCollection(collection, parseType = 'parse') {
|
||||
return collection.map((model) => this.mapMetadata(model.attributes, parseType));
|
||||
},
|
||||
};
|
||||
116
packages/server/src/collection/NestedSet/index.ts
Normal file
116
packages/server/src/collection/NestedSet/index.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
|
||||
export default class NestedSet {
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {Object} options -
|
||||
*/
|
||||
constructor(items, options) {
|
||||
this.options = {
|
||||
parentId: 'parent_id',
|
||||
id: 'id',
|
||||
...options,
|
||||
};
|
||||
this.items = items || [];
|
||||
this.tree = this.linkChildren();
|
||||
}
|
||||
|
||||
setItems(items) {
|
||||
this.items = items;
|
||||
this.tree = this.linkChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* Link nodes children.
|
||||
*/
|
||||
linkChildren() {
|
||||
if (this.items.length <= 0) return false;
|
||||
|
||||
const map = {};
|
||||
this.items.forEach((item) => {
|
||||
map[item.id] = item;
|
||||
map[item.id].children = {};
|
||||
});
|
||||
|
||||
this.items.forEach((item) => {
|
||||
const parentNodeId = item[this.options.parentId];
|
||||
if (parentNodeId) {
|
||||
map[parentNodeId].children[item.id] = item;
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
toArray() {
|
||||
const stack = [];
|
||||
const treeNodes = this.items.map((i) => ({ ...i }));
|
||||
|
||||
const walk = (nodes) => {
|
||||
nodes.forEach((node) => {
|
||||
if (!node[this.options.parentId]) {
|
||||
stack.push(node);
|
||||
}
|
||||
if (node.children) {
|
||||
const childrenNodes = Object.values(node.children)
|
||||
.map((i) => ({ ...i }));
|
||||
|
||||
node.children = childrenNodes;
|
||||
walk(childrenNodes);
|
||||
}
|
||||
});
|
||||
};
|
||||
walk(treeNodes);
|
||||
return stack;
|
||||
}
|
||||
|
||||
getTree() {
|
||||
return this.tree;
|
||||
}
|
||||
|
||||
getElementById(id) {
|
||||
return this.tree[id] || null
|
||||
}
|
||||
|
||||
getParents(id) {
|
||||
const item = this.getElementById(id);
|
||||
const parents = [];
|
||||
let index = 0;
|
||||
|
||||
const walk = (_item) => {
|
||||
if (!item) return;
|
||||
|
||||
if (index) {
|
||||
parents.push(_item);
|
||||
}
|
||||
if (_item[this.options.parentId]) {
|
||||
const parentItem = this.getElementById(_item[this.options.parentId]);
|
||||
|
||||
index++;
|
||||
walk(parentItem);
|
||||
}
|
||||
};
|
||||
walk(item);
|
||||
return parents;
|
||||
}
|
||||
|
||||
toFlattenArray(nodeMapper) {
|
||||
const flattenTree = [];
|
||||
|
||||
const traversal = (nodes, parentNode) => {
|
||||
nodes.forEach((node) => {
|
||||
let nodeMapped = node;
|
||||
|
||||
if (typeof nodeMapper === 'function') {
|
||||
nodeMapped = nodeMapper(nodeMapped, parentNode);
|
||||
}
|
||||
flattenTree.push(nodeMapped);
|
||||
|
||||
if (node.children && node.children.length > 0) {
|
||||
traversal(node.children, node);
|
||||
}
|
||||
});
|
||||
};
|
||||
traversal(this.collection);
|
||||
|
||||
return flattenTree;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import MetableCollection from '@/lib/Metable/MetableCollection';
|
||||
import ResourceFieldMetadata from 'models/ResourceFieldMetadata';
|
||||
|
||||
export default class ResourceFieldMetadataCollection extends MetableCollection {
|
||||
/**
|
||||
* Constructor method.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.setModel(ResourceFieldMetadata);
|
||||
this.extraColumns = ['resource_id', 'resource_item_id'];
|
||||
}
|
||||
}
|
||||
73
packages/server/src/collection/SoftDeleteQueryBuilder.ts
Normal file
73
packages/server/src/collection/SoftDeleteQueryBuilder.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import moment from 'moment';
|
||||
import { Model } from 'objection';
|
||||
|
||||
const options = {
|
||||
columnName: 'deleted_at',
|
||||
deletedValue: moment().format('YYYY-MM-DD HH:mm:ss'),
|
||||
notDeletedValue: null,
|
||||
};
|
||||
|
||||
export default class SoftDeleteQueryBuilder extends Model.QueryBuilder {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.onBuild((builder) => {
|
||||
if (builder.isFind() || builder.isDelete() || builder.isUpdate()) {
|
||||
builder.whereNotDeleted();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* override the normal delete function with one that patches the row's "deleted" column
|
||||
*/
|
||||
delete() {
|
||||
this.context({
|
||||
softDelete: true,
|
||||
});
|
||||
const patch = {};
|
||||
patch[options.columnName] = options.deletedValue;
|
||||
return this.patch(patch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a way to actually delete the row if necessary
|
||||
*/
|
||||
hardDelete() {
|
||||
return super.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a way to undo the delete
|
||||
*/
|
||||
undelete() {
|
||||
this.context({
|
||||
undelete: true,
|
||||
});
|
||||
const patch = {};
|
||||
patch[options.columnName] = options.notDeletedValue;
|
||||
return this.patch(patch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a way to filter to ONLY deleted records without having to remember the column name
|
||||
*/
|
||||
whereDeleted() {
|
||||
const prefix = this.modelClass().tableName;
|
||||
|
||||
// this if is for backwards compatibility, to protect those that used a nullable `deleted` field
|
||||
if (options.deletedValue === true) {
|
||||
return this.where(`${prefix}.${options.columnName}`, options.deletedValue);
|
||||
}
|
||||
// qualify the column name
|
||||
return this.whereNot(`${prefix}.${options.columnName}`, options.notDeletedValue);
|
||||
}
|
||||
|
||||
// provide a way to filter out deleted records without having to remember the column name
|
||||
whereNotDeleted() {
|
||||
const prefix = this.modelClass().tableName;
|
||||
|
||||
// qualify the column name
|
||||
return this.where(`${prefix}.${options.columnName}`, options.notDeletedValue);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user