diff --git a/backends/database.js b/backends/database.js index fe339284..00d58d0a 100644 --- a/backends/database.js +++ b/backends/database.js @@ -13,9 +13,10 @@ module.exports = class Database extends Observable { connect() { this.knex = Knex(this.connectionParams); - this.knex.on('query-error', error => { + this.knex.on('query-error', (error) => { error.type = this.getError(error); }); + this.executePostDbConnect(); } close() { @@ -41,15 +42,25 @@ module.exports = class Database extends Observable { async initializeSingles() { let singleDoctypes = frappe - .getModels(model => model.isSingle) - .map(model => model.name); + .getModels((model) => model.isSingle) + .map((model) => model.name); for (let doctype of singleDoctypes) { if (await this.singleExists(doctype)) { + const singleValues = await this.getSingleFieldsToInsert(doctype); + singleValues.forEach(({ fieldname, value }) => { + let singleValue = frappe.newDoc({ + doctype: 'SingleValue', + parent: doctype, + fieldname, + value, + }); + singleValue.insert(); + }); continue; } let meta = frappe.getMeta(doctype); - if (meta.fields.every(df => df.default == null)) { + if (meta.fields.every((df) => df.default == null)) { continue; } let defaultValues = meta.fields.reduce((doc, df) => { @@ -70,6 +81,26 @@ module.exports = class Database extends Observable { return res.count > 0; } + async getSingleFieldsToInsert(doctype) { + const existingFields = ( + await frappe.db + .knex('SingleValue') + .where({ parent: doctype }) + .select('fieldname') + ).map(({ fieldname }) => fieldname); + + return frappe + .getMeta(doctype) + .fields.map(({ fieldname, default: value }) => ({ + fieldname, + value, + })) + .filter( + ({ fieldname, value }) => + !existingFields.includes(fieldname) && value !== undefined + ); + } + tableExists(table) { return this.knex.schema.hasTable(table); } @@ -80,7 +111,7 @@ module.exports = class Database extends Observable { } runCreateTableQuery(doctype, fields) { - return this.knex.schema.createTable(doctype, table => { + return this.knex.schema.createTable(doctype, (table) => { for (let field of fields) { this.buildColumnForTable(table, field); } @@ -93,7 +124,7 @@ module.exports = class Database extends Observable { let newForeignKeys = await this.getNewForeignKeys(doctype); return this.knex.schema - .table(doctype, table => { + .table(doctype, (table) => { if (diff.added.length) { for (let field of diff.added) { this.buildColumnForTable(table, field); @@ -162,7 +193,7 @@ module.exports = class Database extends Observable { } } - const validFieldNames = validFields.map(field => field.fieldname); + const validFieldNames = validFields.map((field) => field.fieldname); for (let column of tableColumns) { if (!validFieldNames.includes(column)) { diff.removed.push(column); @@ -235,7 +266,7 @@ module.exports = class Database extends Observable { fields: ['*'], filters: { parent: doc.name }, orderBy: 'idx', - order: 'asc' + order: 'asc', }); } } @@ -246,7 +277,7 @@ module.exports = class Database extends Observable { fields: ['fieldname', 'value'], filters: { parent: doctype }, orderBy: 'fieldname', - order: 'asc' + order: 'asc', }); let doc = {}; for (let row of values) { @@ -255,6 +286,38 @@ module.exports = class Database extends Observable { return doc; } + /** + * Get list of values from the singles table. + * @param {...string | Object} fieldnames list of fieldnames to get the values of + * @returns {Array} array of {parent, value, fieldname}. + * @example + * Database.getSingleValues('internalPrecision'); + * // returns [{ fieldname: 'internalPrecision', parent: 'SystemSettings', value: '12' }] + * @example + * Database.getSingleValues({fieldname:'internalPrecision', parent: 'SystemSettings'}); + * // returns [{ fieldname: 'internalPrecision', parent: 'SystemSettings', value: '12' }] + */ + async getSingleValues(...fieldnames) { + fieldnames = fieldnames.map((fieldname) => { + if (typeof fieldname === 'string') { + return { fieldname }; + } + return fieldname; + }); + + let builder = frappe.db.knex('SingleValue'); + builder = builder.where(fieldnames[0]); + + fieldnames.slice(1).forEach(({ fieldname, parent }) => { + if (typeof parent === 'undefined') { + builder = builder.orWhere({ fieldname }); + } else { + builder = builder.orWhere({ fieldname, parent }); + } + }); + return await builder.select('fieldname', 'value', 'parent'); + } + getOne(doctype, name, fields = '*') { let meta = frappe.getMeta(doctype); let baseDoctype = meta.getBaseDocType(); @@ -358,8 +421,8 @@ module.exports = class Database extends Observable { updateOne(doctype, doc) { let validFields = this.getValidFields(doctype); - let fieldsToUpdate = Object.keys(doc).filter(f => f !== 'name'); - let fields = validFields.filter(df => + let fieldsToUpdate = Object.keys(doc).filter((f) => f !== 'name'); + let fields = validFields.filter((df) => fieldsToUpdate.includes(df.fieldname) ); let formattedDoc = this.getFormattedDoc(fields, doc); @@ -396,7 +459,7 @@ module.exports = class Database extends Observable { doctype: 'SingleValue', parent: doctype, fieldname: field.fieldname, - value: value + value: value, }); await singleValue.insert(); } @@ -404,9 +467,7 @@ module.exports = class Database extends Observable { } deleteSingleValues(name) { - return this.knex('SingleValue') - .where('parent', name) - .delete(); + return this.knex('SingleValue').where('parent', name).delete(); } async rename(doctype, oldName, newName) { @@ -439,7 +500,7 @@ module.exports = class Database extends Observable { getFormattedDoc(fields, doc) { let formattedDoc = {}; - fields.map(field => { + fields.map((field) => { let value = doc[field.fieldname]; formattedDoc[field.fieldname] = this.getFormattedValue(field, value); }); @@ -507,9 +568,7 @@ module.exports = class Database extends Observable { } deleteChildren(parenttype, parent) { - return this.knex(parenttype) - .where('parent', parent) - .delete(); + return this.knex(parenttype).where('parent', parent).delete(); } async exists(doctype, name) { @@ -533,14 +592,14 @@ module.exports = class Database extends Observable { start: 0, limit: 1, orderBy: 'name', - order: 'asc' + order: 'asc', }); return row.length ? row[0][fieldname] : null; } async setValue(doctype, name, fieldname, value) { return await this.setValues(doctype, name, { - [fieldname]: value + [fieldname]: value, }); } @@ -565,7 +624,7 @@ module.exports = class Database extends Observable { limit, groupBy, orderBy = 'creation', - order = 'desc' + order = 'desc', } = {}) { let meta = frappe.getMeta(doctype); let baseDoctype = meta.getBaseDocType(); @@ -643,7 +702,7 @@ module.exports = class Database extends Observable { } } - filtersArray.map(filter => { + filtersArray.map((filter) => { const [field, operator, comparisonValue] = filter; if (operator === '=') { builder.where(field, comparisonValue); @@ -689,4 +748,8 @@ module.exports = class Database extends Observable { initTypeMap() { this.typeMap = {}; } + + executePostDbConnect() { + frappe.initializeMoneyMaker(); + } }; diff --git a/index.js b/index.js index ba076f5d..e51c7755 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ const Observable = require('./utils/observable'); const utils = require('./utils'); +const { getMoneyMaker } = require('pesa'); module.exports = { initializeAndRegister(customModels = {}, force = false) { @@ -11,6 +12,37 @@ module.exports = { this.registerModels(customModels); }, + async initializeMoneyMaker() { + // to be called after db initialization + const { currency, internalPrecision: precision } = ( + await frappe.db.getSingleValues( + { fieldname: 'currency', parent: 'AccountingSettings' }, + { fieldname: 'internalPrecision', parent: 'SystemSettings' } + ) + ).reduce((acc, { fieldname, value }) => { + acc[fieldname] = value; + return acc; + }, {}); + + if (typeof precision === 'undefined') { + precision = this.getMeta('SystemSettings').fields.find( + (f) => f.fieldname === 'internalPrecision' + )?.default; + } + + if (typeof precision === 'undefined') { + throw new frappe.errors.NotFoundError( + 'SystemSettings internalPrecision value is undefined' + ); + } + + if (typeof precision.value === 'string') { + precision = parseInt(precision); + } + + this.pesa = getMoneyMaker({ currency, precision }); + }, + init(force) { if (this._initialized && !force) return; this.initConfig(); diff --git a/package.json b/package.json index 3ccd243c..a1c5855b 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "multer": "^1.4.3", "node-fetch": "^3.0.0", "nunjucks": "^3.2.3", - "pesa": "latest", + "pesa": "^1.0.3", "postcss": "^8.3.11", "postcss-loader": "^6.2.0", "sass-loader": "^12.3.0", diff --git a/yarn.lock b/yarn.lock index 3973cecd..3caaa78e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3417,10 +3417,10 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -pesa@latest: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pesa/-/pesa-1.0.2.tgz#78410dbbebb92382cb5c4285aa0a781de2dfc769" - integrity sha512-ITR9V8bRe1GDbBQq8/SyMBtzXJlDs8ludO2ZBMtCZen6D7oqlJXVBeZQSJQs4FfoHBLKJtdsk/JvsleE5Qqu4g== +pesa@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pesa/-/pesa-1.0.3.tgz#e0eab7a13a6a8d0cfd1cbc0214aece6befd63a74" + integrity sha512-UGw3TPnQKAcM0EhPzQO17cXtibBcJCCqlB/hjfYtVPFAgDOIJ2kv6az9Fwq5Gflp2R7hODZlJy1cG+eL70od/g== pg-connection-string@2.5.0: version "2.5.0"