diff --git a/client/core/src/components/crud-model/crud-model.js b/client/core/src/components/crud-model/crud-model.js index 9682416b5..a18ff4ad4 100644 --- a/client/core/src/components/crud-model/crud-model.js +++ b/client/core/src/components/crud-model/crud-model.js @@ -135,14 +135,14 @@ export default class CrudModel extends ModelProxy { if (!this.isChanged) return null; - let create = []; - let update = []; - let remove = []; + let deletes = []; + let updates = []; + let creates = []; let pk = this.primaryKey; for (let row of this.removed) - remove.push(row.$orgRow[pk]); + deletes.push(row.$orgRow[pk]); for (let row of this._data) if (row.$isNew) { @@ -150,22 +150,22 @@ export default class CrudModel extends ModelProxy { for (let prop in row) if (prop.charAt(0) !== '$') data[prop] = row[prop]; - create.push(data); + creates.push(data); } else if (row.$oldData) { let data = {}; for (let prop in row.$oldData) data[prop] = row[prop]; - update.push({ + updates.push({ data, where: {[pk]: row.$orgRow[pk]} }); } - let changes = { - create: create, - update: update, - delete: remove - }; + let changes = {deletes, updates, creates}; + + for (let prop in changes) + if (changes[prop].length === 0) + changes[prop] = undefined; return changes; } diff --git a/client/ticket/src/weekly/index.html b/client/ticket/src/weekly/index.html index 2af8f9ec4..e1f92bd82 100644 --- a/client/ticket/src/weekly/index.html +++ b/client/ticket/src/weekly/index.html @@ -8,11 +8,6 @@ auto-save="true" on-save="$ctrl.onSave()"> - -
diff --git a/client/ticket/src/weekly/index.js b/client/ticket/src/weekly/index.js index 07f5dd66b..a81d100e3 100644 --- a/client/ticket/src/weekly/index.js +++ b/client/ticket/src/weekly/index.js @@ -2,30 +2,36 @@ import ngModule from '../module'; import './style.scss'; export default class Controller { - constructor($scope) { + constructor($scope, vnApp, $translate) { this.$scope = $scope; + this.vnApp = vnApp; + this._ = $translate; + this.ticketSelected = null; this.filter = { - include: [ - {relation: 'ticket', - scope: { - fields: ['id', 'clientFk', 'companyFk', 'warehouseFk'], - include: [ - {relation: 'client', - scope: { - fields: ['salesPersonFk', 'name'], - include: { - relation: 'salesPerson', - fields: ['firstName', 'name'] + include: { + relation: 'ticket', + scope: { + fields: ['id', 'clientFk', 'companyFk', 'warehouseFk'], + include: [ + { + relation: 'client', + scope: { + fields: ['salesPersonFk', 'name'], + include: { + relation: 'salesPerson', + scope: { + fields: ['id', 'firstName', 'name'] } } - }, - {relation: 'warehouse'} - ] - } + } + }, + {relation: 'warehouse'} + ] } - ] + } }; + this.weekdays = [ {id: 0, name: 'Monday'}, {id: 1, name: 'Tuesday'}, @@ -38,7 +44,7 @@ export default class Controller { } onSave() { - this.$scope.watcher.notifySaved(); + this.vnApp.showSuccess(this._.instant('Data saved!')); } showClientDescriptor(event, clientFk) { @@ -65,17 +71,9 @@ export default class Controller { this.expeditionId = expedition.id; this.$scope.deleteWeekly.show(); } - - onSubmit() { - this.$scope.watcher.check(); - this.$scope.model.save().then(() => { - this.$scope.watcher.notifySaved(); - this.$scope.model.refresh(); - }); - } } -Controller.$inject = ['$scope']; +Controller.$inject = ['$scope', 'vnApp', '$translate']; ngModule.component('vnTicketWeekly', { template: require('./index.html'), diff --git a/services/loopback/common/methods/state/list.js b/services/loopback/common/methods/state/list.js deleted file mode 100644 index 566b576de..000000000 --- a/services/loopback/common/methods/state/list.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = State => { - var serverFilter = {where: {order: {gt: 0}}, order: 'order, name'}; - State.defineScope(serverFilter); -}; diff --git a/services/loopback/common/methods/vn-model/rewriteDbError.js b/services/loopback/common/methods/vn-model/rewriteDbError.js deleted file mode 100644 index 1a4045662..000000000 --- a/services/loopback/common/methods/vn-model/rewriteDbError.js +++ /dev/null @@ -1,58 +0,0 @@ -module.exports = Self => { - /** - * Catches database errors overriding - * model.create() and model.upsert() methods - * @param {Object} replaceErrFunc - Callback - */ - Self.rewriteDbError = function(replaceErrFunc) { - this.once('attached', () => { - let realUpsert = this.upsert; - this.upsert = async(data, options, cb) => { - if (options instanceof Function) { - cb = options; - options = null; - } - - try { - await realUpsert.call(this, data, options); - if (cb) cb(); - } catch (err) { - let myErr = replaceErr(err, replaceErrFunc); - if (cb) - cb(myErr); - else - throw myErr; - } - }; - - let realCreate = this.create; - this.create = async(data, options, cb) => { - if (options instanceof Function) { - cb = options; - options = null; - } - - try { - await realCreate.call(this, data, options); - if (cb) cb(); - } catch (err) { - let myErr = replaceErr(err, replaceErrFunc); - if (cb) - cb(myErr); - else - throw myErr; - } - }; - }); - }; - - function replaceErr(err, replaceErrFunc) { - if (Array.isArray(err)) { - let errs = []; - for (let e of err) - errs.push(replaceErrFunc(e)); - return errs; - } - return replaceErrFunc(err); - } -}; diff --git a/services/loopback/common/methods/vn-model/specs/crud.spec.js b/services/loopback/common/methods/vn-model/specs/crud.spec.js index 3e7ce0525..c114bd0b8 100644 --- a/services/loopback/common/methods/vn-model/specs/crud.spec.js +++ b/services/loopback/common/methods/vn-model/specs/crud.spec.js @@ -8,13 +8,11 @@ describe('Model crud()', () => { expect(ItemBarcode.crud).toBeDefined(); }); - it('should create a new instance', async() => { + it('should create a new instance', async () => { let data = {code: '500', itemFk: '1'}; + let creates = [data]; - crudObject = { - create: [data] - }; - await ItemBarcode.crud(crudObject); + await ItemBarcode.crud(null, null, creates); let instance = await ItemBarcode.findOne({where: data}); insertId = instance.id; @@ -22,24 +20,21 @@ describe('Model crud()', () => { expect(instance.code).toEqual('500'); }); - it('should update the instance', async() => { - crudObject = { - update: [{ - where: {id: insertId}, - data: {code: '501', itemFk: 1} - }] - }; - await ItemBarcode.crud(crudObject); + it('should update the instance', async () => { + let updates = [{ + where: {id: insertId}, + data: {code: '501', itemFk: 1} + }]; + + await ItemBarcode.crud(null, updates); let instance = await ItemBarcode.findById(insertId); expect(instance.code).toEqual('501'); }); - it('should delete the created instance', async() => { - crudObject = { - delete: [insertId] - }; - await ItemBarcode.crud(crudObject); + it('should delete the created instance', async () => { + let deletes = [insertId]; + await ItemBarcode.crud(deletes); let instance = await ItemBarcode.findById(insertId); expect(instance).toEqual(null); diff --git a/services/loopback/common/methods/vn-model/validateBinded.js b/services/loopback/common/methods/vn-model/validateBinded.js deleted file mode 100644 index 3e5e3f156..000000000 --- a/services/loopback/common/methods/vn-model/validateBinded.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = function(Self) { - Self.validateBinded = function(propertyName, validatorFn, options) { - var customValidator = function(err) { - if (!validatorFn(this[propertyName])) err(); - }; - options.isExportable = true; - options.bindedFunction = validatorFn; - this.validate(propertyName, customValidator, options); - }; -}; diff --git a/services/loopback/common/models/agency.js b/services/loopback/common/models/agency.js index 9994624df..b649e9065 100644 --- a/services/loopback/common/models/agency.js +++ b/services/loopback/common/models/agency.js @@ -1,5 +1,4 @@ module.exports = Self => { - Self.defineScope({where: {isManaged: {neq: 0}}}); require('../methods/agency/landsThatDay')(Self); require('../methods/agency/getFirstShipped')(Self); }; diff --git a/services/loopback/common/models/state.js b/services/loopback/common/models/state.js index 06e1ac92d..d2b0aa30a 100644 --- a/services/loopback/common/models/state.js +++ b/services/loopback/common/models/state.js @@ -1,6 +1,4 @@ module.exports = Self => { - require('../methods/state/list')(Self); - /** * Checks if the alertLevel of a state is 0. * diff --git a/services/loopback/common/models/vn-model.js b/services/loopback/common/models/vn-model.js index e5c15be8a..3fb16a57f 100644 --- a/services/loopback/common/models/vn-model.js +++ b/services/loopback/common/models/vn-model.js @@ -5,261 +5,267 @@ const UserError = require('../helpers').UserError; module.exports = function(Self) { Self.ParameterizedSQL = ParameterizedSQL; - require('../methods/vn-model/validateBinded')(Self); - require('../methods/vn-model/rewriteDbError')(Self); require('../methods/vn-model/getSetValues')(Self); - Self.setup = function() { - Self.super_.setup.call(this); + Object.assign(Self, { + setup() { + Self.super_.setup.call(this); - // Register field ACL validation - this.beforeRemote('prototype.patchAttributes', ctx => this.checkUpdateAcls(ctx)); - this.beforeRemote('updateAll', ctx => this.checkUpdateAcls(ctx)); - this.beforeRemote('patchOrCreate', ctx => this.checkInsertAcls(ctx)); - this.beforeRemote('create', ctx => this.checkInsertAcls(ctx)); - this.beforeRemote('replaceById', ctx => this.checkInsertAcls(ctx)); - this.beforeRemote('replaceOrCreate', ctx => this.checkInsertAcls(ctx)); + // Register field ACL validation + this.beforeRemote('prototype.patchAttributes', ctx => this.checkUpdateAcls(ctx)); + this.beforeRemote('updateAll', ctx => this.checkUpdateAcls(ctx)); + this.beforeRemote('patchOrCreate', ctx => this.checkInsertAcls(ctx)); + this.beforeRemote('create', ctx => this.checkInsertAcls(ctx)); + this.beforeRemote('replaceById', ctx => this.checkInsertAcls(ctx)); + this.beforeRemote('replaceOrCreate', ctx => this.checkInsertAcls(ctx)); - this.remoteMethod('crud', { - description: 'Create, update or/and delete instances from model with a single request', - accessType: 'WRITE', - accepts: [ - { - arg: 'actions', - type: 'Object', - require: true, - description: 'Instances to update, example: {create: [instances], update: [instances], delete: [ids]}', - http: {source: 'body'} - } - ], - http: { - path: `/crud`, - verb: 'POST' - } - }); - }; - - Self.defineScope = function(serverFilter) { - this.remoteMethodCtx('list', { - accepts: [ - { - arg: 'filter', - type: 'object', - description: 'Filter defining where' - } - ], - returns: { - type: [this.modelName], - root: true - }, - http: { - verb: 'get', - path: '/list' - } - }); - - this.list = function(ctx, clientFilter, cb) { - let clientFields = (clientFilter && clientFilter.fields) ? clientFilter.fields : []; - let serverFields = (serverFilter && serverFilter.fields) ? serverFilter.fields : []; - let fields = clientFields.filter(itemC => { - return serverFields.some(itemS => itemS === itemC); + this.remoteMethod('crud', { + description: `Create, update or/and delete instances from model with a single request`, + accessType: 'WRITE', + accepts: [ + { + arg: 'deletes', + description: `Identifiers of instances to delete`, + type: ['Integer'] + }, { + arg: 'updates', + description: `Instances to update with it's identifier {where, data}`, + type: ['Object'] + }, { + arg: 'creates', + description: `Instances to create`, + type: ['Object'] + } + ] }); - let and = []; - let order; - let limit; - let filter = {order: order, limit: limit}; + }, - if (clientFilter && clientFilter.where) - and.push(clientFilter.where); - if (serverFilter && serverFilter.where) - and.push(serverFilter.where); + async crud(deletes, updates, creates) { + let transaction = await this.beginTransaction({}); + let options = {transaction}; - if (clientFilter && clientFilter.order) - order = clientFilter.order; - else if (serverFilter && serverFilter.order) - order = serverFilter.order; - - if (serverFilter && serverFilter.limit) - limit = serverFilter.limit; - else if (clientFilter && clientFilter.limit) - limit = clientFilter.limit; - - filter.where = (and.length > 0) && {and: and}; - filter.fields = fields; - - this.find(filter, function(err, states) { - if (err) - cb(err, null); - else - cb(null, states); - }); - }; - }; - - Self.remoteMethodCtx = function(methodName, args) { - let ctx = { - arg: 'context', - type: 'object', - http: function(ctx) { - return ctx; - } - }; - if (args.accepts === undefined) - args.accepts = []; - else if (!Array.isArray(args.accepts)) - args.accepts = [args.accepts]; - args.accepts.unshift(ctx); - this.remoteMethod(methodName, args); - }; - - Self.getConnection = function(cb) { - this.dataSource.connector.client.getConnection(cb); - }; - - Self.connectToService = function(ctx, dataSource) { - this.app.dataSources[dataSource].connector.remotes.auth = { - bearer: new Buffer(ctx.req.accessToken.id).toString('base64'), - sendImmediately: true - }; - }; - - Self.disconnectFromService = function(dataSource) { - this.app.dataSources[dataSource].connector.remotes.auth = { - bearer: new Buffer('').toString('base64'), - sendImmediately: true - }; - }; - - Self.crud = async function(actions) { - let transaction = await this.beginTransaction({}); - let options = {transaction: transaction}; - - try { - if (actions.delete && actions.delete.length) - await this.destroyAll({id: {inq: actions.delete}}, options); - - if (actions.update) { - try { + try { + if (deletes) { let promises = []; - actions.update.forEach(toUpdate => { - promises.push(this.upsertWithWhere(toUpdate.where, toUpdate.data, options)); - }); + for (let id of deletes) + promises.push(this.destroyById(id, options)); await Promise.all(promises); - } catch (error) { - throw error; } - } - if (actions.create && actions.create.length) { - try { - await this.create(actions.create, options); - } catch (error) { - throw error[error.length - 1]; + if (updates) { + let promises = []; + for (let update of updates) + promises.push(this.upsertWithWhere(update.where, update.data, options)); + await Promise.all(promises); } + if (creates && creates.length) + try { + await this.create(creates, options); + } catch (error) { + throw error[error.length - 1]; + } + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; } - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; + }, + + /** + * Wrapper for remoteMethod() but adding the context as + * extra argument at the beginning of arguments list. + * + * @param {String} methodName The method name + * @param {Object} options The method options + */ + remoteMethodCtx(methodName, options) { + if (options.accepts === undefined) + options.accepts = []; + else if (!Array.isArray(options.accepts)) + options.accepts = [options.accepts]; + + options.accepts.unshift({ + arg: 'ctx', + type: 'Object', + http: {source: 'context'} + }); + this.remoteMethod(methodName, options); + }, + + /** + * Adds a validation, marking it as exportable to the browser. + * Exportable validation functions should be synchronous and totally + * independent from other code because they are parsed in the browser + * using eval(). + * + * @param {String} propertyName The property name + * @param {Function} validatorFn The validation function + * @param {Object} options The validation options + */ + validateBinded(propertyName, validatorFn, options) { + let customValidator = function(err) { + if (!validatorFn(this[propertyName])) err(); + }; + options.isExportable = true; + options.bindedFunction = validatorFn; + this.validate(propertyName, customValidator, options); + }, + + /** + * Catches database errors overriding create() and upsert() methods. + * + * @param {Function} replaceErrFunc - Callback + */ + rewriteDbError(replaceErrFunc) { + function replaceErr(err, replaceErrFunc) { + if (Array.isArray(err)) { + let errs = []; + for (let e of err) + errs.push(replaceErrFunc(e)); + return errs; + } + return replaceErrFunc(err); + } + + this.once('attached', () => { + let realUpsert = this.upsert; + this.upsert = async (data, options, cb) => { + if (options instanceof Function) { + cb = options; + options = null; + } + + try { + await realUpsert.call(this, data, options); + if (cb) cb(); + } catch (err) { + let myErr = replaceErr(err, replaceErrFunc); + if (cb) + cb(myErr); + else + throw myErr; + } + }; + + let realCreate = this.create; + this.create = async (data, options, cb) => { + if (options instanceof Function) { + cb = options; + options = null; + } + + try { + await realCreate.call(this, data, options); + if (cb) cb(); + } catch (err) { + let myErr = replaceErr(err, replaceErrFunc); + if (cb) + cb(myErr); + else + throw myErr; + } + }; + }); + }, + + /* + * Shortcut to VnMySQL.executeP() + */ + rawSql(query, params, options, cb) { + return this.dataSource.connector.executeP(query, params, options, cb); + }, + + /* + * Shortcut to VnMySQL.executeStmt() + */ + rawStmt(stmt, options) { + return this.dataSource.connector.executeStmt(stmt, options); + }, + + /* + * Shortcut to VnMySQL.makeLimit() + */ + makeLimit(filter) { + return this.dataSource.connector.makeLimit(filter); + }, + + /* + * Shortcut to VnMySQL.makeSuffix() + */ + makeSuffix(filter) { + return this.dataSource.connector.makeSuffix(filter); + }, + + /* + * Shortcut to VnMySQL.buildModelSuffix() + */ + buildSuffix(filter, tableAlias) { + return this.dataSource.connector.buildModelSuffix(this.modelName, filter, tableAlias); + }, + + async checkAcls(ctx, actionType) { + let userId = ctx.req.accessToken.userId; + let models = this.app.models; + let userRoles = await models.Account.getRoles(userId); + let data = ctx.args.data; + let modelAcls; + + function modifiedProperties(data) { + let properties = []; + + for (property in data) + properties.push(property); + + return properties; + } + + modelAcls = await models.FieldAcl.find({ + where: { + and: [ + {model: this.modelName}, + {role: {inq: userRoles}}, + {property: '*'}, + {or: [{actionType: '*'}, {actionType: actionType}]} + ] + } + }); + + let allowedAll = modelAcls.find(acl => { + return acl.property == '*'; + }); + + if (allowedAll) + return; + + modelAcls = await models.FieldAcl.find({ + where: { + and: [ + {model: this.modelName}, + {role: {inq: userRoles}}, + {property: {inq: modifiedProperties(data)}}, + {or: [{actionType: '*'}, {actionType: actionType}]} + ] + } + }); + + let propsHash = {}; + for (let acl of modelAcls) + propsHash[acl.property] = true; + + let allowedProperties = Object.keys(data).every(property => { + return propsHash[property]; + }); + + if (!allowedProperties) + throw new UserError(`You don't have enough privileges`); + }, + + checkUpdateAcls(ctx) { + return this.checkAcls(ctx, 'update'); + }, + + checkInsertAcls(ctx) { + return this.checkAcls(ctx, 'insert'); } - }; - - Self.checkAcls = async function(ctx, actionType) { - let userId = ctx.req.accessToken.userId; - let models = this.app.models; - let userRoles = await models.Account.getRoles(userId); - let data = ctx.args.data; - let modelAcls; - - function modifiedProperties(data) { - let properties = []; - - for (property in data) - properties.push(property); - - return properties; - } - - modelAcls = await models.FieldAcl.find({ - where: { - and: [ - {model: this.modelName}, - {role: {inq: userRoles}}, - {property: '*'}, - {or: [{actionType: '*'}, {actionType: actionType}]} - ] - } - }); - - let allowedAll = modelAcls.find(acl => { - return acl.property == '*'; - }); - - if (allowedAll) - return; - - modelAcls = await models.FieldAcl.find({ - where: { - and: [ - {model: this.modelName}, - {role: {inq: userRoles}}, - {property: {inq: modifiedProperties(data)}}, - {or: [{actionType: '*'}, {actionType: actionType}]} - ] - } - }); - - let propsHash = {}; - for (let acl of modelAcls) - propsHash[acl.property] = true; - - let allowedProperties = Object.keys(data).every(property => { - return propsHash[property]; - }); - - if (!allowedProperties) - throw new UserError(`You don't have enough privileges`); - }; - - Self.checkUpdateAcls = function(ctx) { - return this.checkAcls(ctx, 'update'); - }; - - Self.checkInsertAcls = function(ctx) { - return this.checkAcls(ctx, 'insert'); - }; - - /* - * Shortcut to VnMySQL.executeP() - */ - Self.rawSql = function(query, params, options, cb) { - return this.dataSource.connector.executeP(query, params, options, cb); - }; - - /* - * Shortcut to VnMySQL.executeStmt() - */ - Self.rawStmt = function(stmt, options) { - return this.dataSource.connector.executeStmt(stmt, options); - }; - - /* - * Shortcut to VnMySQL.makeLimit() - */ - Self.makeLimit = function(filter) { - return this.dataSource.connector.makeLimit(filter); - }; - - /* - * Shortcut to VnMySQL.makeSuffix() - */ - Self.makeSuffix = function(filter) { - return this.dataSource.connector.makeSuffix(filter); - }; - - /* - * Shortcut to VnMySQL.buildModelSuffix() - */ - Self.buildSuffix = function(filter, tableAlias) { - return this.dataSource.connector.buildModelSuffix(this.modelName, filter, tableAlias); - }; + }); }; diff --git a/services/loopback/server/connectors/vn-mysql.js b/services/loopback/server/connectors/vn-mysql.js index 046866ede..dee523be7 100644 --- a/services/loopback/server/connectors/vn-mysql.js +++ b/services/loopback/server/connectors/vn-mysql.js @@ -1,18 +1,11 @@ const mysql = require('mysql'); -const loopbackConnector = require('loopback-connector'); -const SqlConnector = loopbackConnector.SqlConnector; -const ParameterizedSQL = loopbackConnector.ParameterizedSQL; +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const MySQL = require('loopback-connector-mysql').MySQL; const EnumFactory = require('loopback-connector-mysql').EnumFactory; const fs = require('fs'); class VnMySQL extends MySQL { - constructor(settings) { - super(); - SqlConnector.call(this, 'mysql', settings); - } - toColumnValue(prop, val) { if (val == null || !prop || prop.type !== Date) return MySQL.prototype.toColumnValue.call(this, prop, val); @@ -237,12 +230,11 @@ exports.initialize = function initialize(dataSource, callback) { dataSource.EnumFactory = EnumFactory; - if (callback) { + if (callback) if (dataSource.settings.lazyConnect) { process.nextTick(function() { callback(); }); } else dataSource.connector.connect(callback); - } };