mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 12:20:31 +00:00
411 lines
9.0 KiB
TypeScript
411 lines
9.0 KiB
TypeScript
import bcrypt from 'bcryptjs';
|
|
import moment from 'moment';
|
|
import _ from 'lodash';
|
|
import accounting from 'accounting';
|
|
import Currencies from 'js-money/lib/currency';
|
|
import definedOptions from 'data/options';
|
|
|
|
export * from './table';
|
|
|
|
const hashPassword = (password) =>
|
|
new Promise((resolve) => {
|
|
bcrypt.genSalt(10, (error, salt) => {
|
|
bcrypt.hash(password, salt, (err, hash) => {
|
|
resolve(hash);
|
|
});
|
|
});
|
|
});
|
|
|
|
const origin = (request) => `${request.protocol}://${request.hostname}`;
|
|
|
|
const dateRangeCollection = (
|
|
fromDate,
|
|
toDate,
|
|
addType = 'day',
|
|
increment = 1
|
|
) => {
|
|
const collection = [];
|
|
const momentFromDate = moment(fromDate);
|
|
let dateFormat = '';
|
|
|
|
switch (addType) {
|
|
case 'day':
|
|
default:
|
|
dateFormat = 'YYYY-MM-DD';
|
|
break;
|
|
case 'month':
|
|
case 'quarter':
|
|
dateFormat = 'YYYY-MM';
|
|
break;
|
|
case 'year':
|
|
dateFormat = 'YYYY';
|
|
break;
|
|
}
|
|
for (
|
|
let i = momentFromDate;
|
|
i.isBefore(toDate, addType) || i.isSame(toDate, addType);
|
|
i.add(increment, `${addType}s`)
|
|
) {
|
|
collection.push(i.endOf(addType).format(dateFormat));
|
|
}
|
|
return collection;
|
|
};
|
|
|
|
const dateRangeFromToCollection = (
|
|
fromDate,
|
|
toDate,
|
|
addType = 'day',
|
|
increment = 1
|
|
) => {
|
|
const collection = [];
|
|
const momentFromDate = moment(fromDate);
|
|
const dateFormat = 'YYYY-MM-DD';
|
|
|
|
for (
|
|
let i = momentFromDate;
|
|
i.isBefore(toDate, addType) || i.isSame(toDate, addType);
|
|
i.add(increment, `${addType}s`)
|
|
) {
|
|
collection.push({
|
|
fromDate: i.startOf(addType).format(dateFormat),
|
|
toDate: i.endOf(addType).format(dateFormat),
|
|
});
|
|
}
|
|
return collection;
|
|
};
|
|
|
|
|
|
const dateRangeFormat = (rangeType) => {
|
|
switch (rangeType) {
|
|
case 'year':
|
|
return 'YYYY';
|
|
case 'month':
|
|
case 'quarter':
|
|
default:
|
|
return 'YYYY-MM';
|
|
}
|
|
};
|
|
|
|
function mapKeysDeep(obj, cb, isRecursive) {
|
|
if (!obj && !isRecursive) {
|
|
return {};
|
|
}
|
|
if (!isRecursive) {
|
|
if (
|
|
typeof obj === 'string' ||
|
|
typeof obj === 'number' ||
|
|
typeof obj === 'boolean'
|
|
) {
|
|
return {};
|
|
}
|
|
}
|
|
if (Array.isArray(obj)) {
|
|
return obj.map((item) => mapKeysDeep(item, cb, true));
|
|
}
|
|
if (!_.isPlainObject(obj)) {
|
|
return obj;
|
|
}
|
|
const result = _.mapKeys(obj, cb);
|
|
return _.mapValues(result, (value) => mapKeysDeep(value, cb, true));
|
|
}
|
|
|
|
const mapValuesDeep = (v, callback) =>
|
|
_.isObject(v)
|
|
? _.mapValues(v, (v) => mapValuesDeep(v, callback))
|
|
: callback(v);
|
|
|
|
const promiseSerial = (funcs) => {
|
|
return funcs.reduce(
|
|
(promise, func) =>
|
|
promise.then((result) =>
|
|
func().then(Array.prototype.concat.bind(result))
|
|
),
|
|
Promise.resolve([])
|
|
);
|
|
};
|
|
|
|
const flatToNestedArray = (
|
|
data,
|
|
config = { id: 'id', parentId: 'parent_id' }
|
|
) => {
|
|
const map = {};
|
|
const nestedArray = [];
|
|
|
|
data.forEach((item) => {
|
|
map[item[config.id]] = item;
|
|
map[item[config.id]].children = [];
|
|
});
|
|
|
|
data.forEach((item) => {
|
|
const parentItemId = item[config.parentId];
|
|
|
|
if (!item[config.parentId]) {
|
|
nestedArray.push(item);
|
|
}
|
|
if (parentItemId) {
|
|
map[parentItemId].children.push(item);
|
|
}
|
|
});
|
|
return nestedArray;
|
|
};
|
|
|
|
const itemsStartWith = (items, char) => {
|
|
return items.filter((item) => item.indexOf(char) === 0);
|
|
};
|
|
|
|
const getTotalDeep = (items, deepProp, totalProp) =>
|
|
items.reduce((acc, item) => {
|
|
const total = Array.isArray(item[deepProp])
|
|
? getTotalDeep(item[deepProp], deepProp, totalProp)
|
|
: 0;
|
|
return _.sumBy(item, totalProp) + total + acc;
|
|
}, 0);
|
|
|
|
function applyMixins(derivedCtor, baseCtors) {
|
|
baseCtors.forEach((baseCtor) => {
|
|
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
|
|
Object.defineProperty(
|
|
derivedCtor.prototype,
|
|
name,
|
|
Object.getOwnPropertyDescriptor(baseCtor.prototype, name)
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
const formatDateFields = (inputDTO, fields, format = 'YYYY-MM-DD') => {
|
|
const _inputDTO = { ...inputDTO };
|
|
|
|
fields.forEach((field) => {
|
|
if (_inputDTO[field]) {
|
|
_inputDTO[field] = moment(_inputDTO[field]).format(format);
|
|
}
|
|
});
|
|
return _inputDTO;
|
|
};
|
|
|
|
const getDefinedOptions = () => {
|
|
const options = [];
|
|
|
|
Object.keys(definedOptions).forEach((groupKey) => {
|
|
const groupOptions = definedOptions[groupKey];
|
|
groupOptions.forEach((option) => {
|
|
options.push({ ...option, group: groupKey });
|
|
});
|
|
});
|
|
return options;
|
|
};
|
|
|
|
const getDefinedOption = (key, group) => {
|
|
return definedOptions?.[group]?.find((option) => option.key == key);
|
|
};
|
|
|
|
const isDefinedOptionConfigurable = (key, group) => {
|
|
const definedOption = getDefinedOption(key, group);
|
|
return definedOption?.config || false;
|
|
};
|
|
|
|
const entriesAmountDiff = (
|
|
newEntries,
|
|
oldEntries,
|
|
amountAttribute,
|
|
idAttribute
|
|
) => {
|
|
const oldEntriesTable = _.chain(oldEntries)
|
|
.groupBy(idAttribute)
|
|
.mapValues((group) => _.sumBy(group, amountAttribute) || 0)
|
|
.value();
|
|
|
|
const newEntriesTable = _.chain(newEntries)
|
|
.groupBy(idAttribute)
|
|
.mapValues((group) => _.sumBy(group, amountAttribute) || 0)
|
|
.mergeWith(oldEntriesTable, (objValue, srcValue) => {
|
|
return _.isNumber(objValue) ? objValue - srcValue : srcValue * -1;
|
|
})
|
|
.value();
|
|
|
|
return _.chain(newEntriesTable)
|
|
.mapValues((value, key) => ({
|
|
[idAttribute]: key,
|
|
[amountAttribute]: value,
|
|
}))
|
|
.filter((entry) => entry[amountAttribute] != 0)
|
|
.values()
|
|
.value();
|
|
};
|
|
|
|
const convertEmptyStringToNull = (value) => {
|
|
return typeof value === 'string'
|
|
? value.trim() === ''
|
|
? null
|
|
: value
|
|
: value;
|
|
};
|
|
|
|
const getNegativeFormat = (formatName) => {
|
|
switch (formatName) {
|
|
case 'parentheses':
|
|
return '(%s%v)';
|
|
case 'mines':
|
|
return '-%s%v';
|
|
}
|
|
};
|
|
|
|
const getCurrencySign = (currencyCode) => {
|
|
return _.get(Currencies, `${currencyCode}.symbol`);
|
|
};
|
|
|
|
const formatNumber = (
|
|
balance,
|
|
{
|
|
precision = 2,
|
|
divideOn1000 = false,
|
|
excerptZero = false,
|
|
negativeFormat = 'mines',
|
|
thousand = ',',
|
|
decimal = '.',
|
|
zeroSign = '',
|
|
money = true,
|
|
currencyCode,
|
|
symbol
|
|
}
|
|
) => {
|
|
const formattedSymbol = getCurrencySign(currencyCode);
|
|
const negForamt = getNegativeFormat(negativeFormat);
|
|
const format = '%s%v';
|
|
|
|
let formattedBalance = parseFloat(balance);
|
|
|
|
if (divideOn1000) {
|
|
formattedBalance /= 1000;
|
|
}
|
|
return accounting.formatMoney(
|
|
formattedBalance,
|
|
money ? formattedSymbol : symbol ? symbol : '',
|
|
precision,
|
|
thousand,
|
|
decimal,
|
|
{
|
|
pos: format,
|
|
neg: negForamt,
|
|
zero: excerptZero ? zeroSign : format,
|
|
}
|
|
);
|
|
};
|
|
|
|
const isBlank = (value) => {
|
|
return (_.isEmpty(value) && !_.isNumber(value)) || _.isNaN(value);
|
|
};
|
|
|
|
function defaultToTransform(value, defaultOrTransformedValue, defaultValue) {
|
|
const _defaultValue =
|
|
typeof defaultValue === 'undefined'
|
|
? defaultOrTransformedValue
|
|
: defaultValue;
|
|
|
|
const _transfromedValue =
|
|
typeof defaultValue === 'undefined' ? value : defaultOrTransformedValue;
|
|
|
|
return value == null || value !== value || value === ''
|
|
? _defaultValue
|
|
: _transfromedValue;
|
|
}
|
|
|
|
const transformToMap = (objects, key) => {
|
|
const map = new Map();
|
|
|
|
objects.forEach((object) => {
|
|
map.set(object[key], object);
|
|
});
|
|
return map;
|
|
};
|
|
|
|
const transactionIncrement = (s) => s.replace(/([0-8]|\d?9+)?$/, (e) => ++e);
|
|
|
|
const booleanValuesRepresentingTrue: string[] = [
|
|
'true',
|
|
'1',
|
|
];
|
|
const booleanValuesRepresentingFalse: string[] = [
|
|
'false',
|
|
'0',
|
|
];
|
|
|
|
const normalizeValue = (value: any): string => value.toString().trim().toLowerCase();
|
|
|
|
const booleanValues: string[] = [
|
|
...booleanValuesRepresentingTrue,
|
|
...booleanValuesRepresentingFalse,
|
|
].map((value) => normalizeValue(value));
|
|
|
|
export const parseBoolean = <T>(value: any, defaultValue: T): T | boolean => {
|
|
const normalizedValue = normalizeValue(value);
|
|
if (booleanValues.indexOf(normalizedValue) === -1) {
|
|
return defaultValue;
|
|
}
|
|
return booleanValuesRepresentingTrue.indexOf(normalizedValue) !== -1;
|
|
};
|
|
|
|
var increment = (n) => {
|
|
return () => {
|
|
n += 1;
|
|
return n;
|
|
};
|
|
};
|
|
|
|
const transformToMapBy = (collection, key) => {
|
|
return new Map(
|
|
Object.entries(_.groupBy(collection, key)),
|
|
);
|
|
}
|
|
|
|
const transformToMapKeyValue = (collection, key) => {
|
|
return new Map(
|
|
collection.map((item) => [item[key], item]),
|
|
);
|
|
};
|
|
|
|
|
|
const accumSum = (data, callback) => {
|
|
return data.reduce((acc, _data) => {
|
|
const amount = callback(_data);
|
|
return acc + amount;
|
|
}, 0)
|
|
}
|
|
|
|
const mergeObjectsBykey = (object1, object2, key) => {
|
|
var merged = _.merge(_.keyBy(object1, key), _.keyBy(object2, key));
|
|
return _.values(merged);
|
|
}
|
|
|
|
export {
|
|
accumSum,
|
|
increment,
|
|
hashPassword,
|
|
origin,
|
|
dateRangeCollection,
|
|
dateRangeFormat,
|
|
mapValuesDeep,
|
|
mapKeysDeep,
|
|
promiseSerial,
|
|
flatToNestedArray,
|
|
itemsStartWith,
|
|
getTotalDeep,
|
|
applyMixins,
|
|
formatDateFields,
|
|
isDefinedOptionConfigurable,
|
|
getDefinedOption,
|
|
getDefinedOptions,
|
|
entriesAmountDiff,
|
|
convertEmptyStringToNull,
|
|
formatNumber,
|
|
isBlank,
|
|
defaultToTransform,
|
|
transformToMap,
|
|
transactionIncrement,
|
|
transformToMapBy,
|
|
dateRangeFromToCollection,
|
|
transformToMapKeyValue,
|
|
mergeObjectsBykey
|
|
};
|