mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
WIP server side.
This commit is contained in:
76
server/src/collection/BudgetEntriesSet.js
Normal file
76
server/src/collection/BudgetEntriesSet.js
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
server/src/collection/Cachable.js
Normal file
0
server/src/collection/Cachable.js
Normal file
279
server/src/collection/Metable.js
Normal file
279
server/src/collection/Metable.js
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));
|
||||
},
|
||||
};
|
||||
73
server/src/collection/NestedSet/index.js
Normal file
73
server/src/collection/NestedSet/index.js
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
export default class NestedSet {
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {Object} options -
|
||||
*/
|
||||
constructor(items, options) {
|
||||
this.options = {
|
||||
parentId: 'parent_id',
|
||||
id: 'id',
|
||||
...options,
|
||||
};
|
||||
this.items = items;
|
||||
this.collection = {};
|
||||
this.toTree();
|
||||
|
||||
return this.collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.push(item);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
toTree() {
|
||||
const map = this.linkChildren();
|
||||
const tree = {};
|
||||
|
||||
this.items.forEach((item) => {
|
||||
const parentNodeId = item[this.options.parentId];
|
||||
if (!parentNodeId) {
|
||||
tree[item.id] = map[item.id];
|
||||
}
|
||||
});
|
||||
this.collection = Object.values(tree);
|
||||
}
|
||||
|
||||
walk() {
|
||||
|
||||
}
|
||||
|
||||
getParents() {
|
||||
|
||||
}
|
||||
|
||||
getChildren() {
|
||||
|
||||
}
|
||||
|
||||
toFlattenArray() {
|
||||
|
||||
}
|
||||
|
||||
toArray() {
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user