const got = require('got'); const UserError = require('vn-loopback/util/user-error'); const getFinalState = require('vn-loopback/util/hook').getFinalState; const isMultiple = require('vn-loopback/util/hook').isMultiple; const validateTin = require('vn-loopback/util/validateTin'); const validateIban = require('vn-loopback/util/validateIban'); const LoopBackContext = require('loopback-context'); module.exports = Self => { // Methods require('../methods/client/activeWorkersWithRole')(Self); require('../methods/client/getCard')(Self); require('../methods/client/createWithUser')(Self); require('../methods/client/listWorkers')(Self); require('../methods/client/hasCustomerRole')(Self); require('../methods/client/canCreateTicket')(Self); require('../methods/client/isValidClient')(Self); require('../methods/client/addressesPropagateRe')(Self); require('../methods/client/getDebt')(Self); require('../methods/client/getMana')(Self); require('../methods/client/getAverageInvoiced')(Self); require('../methods/client/summary')(Self); require('../methods/client/updateFiscalData')(Self); require('../methods/client/getTransactions')(Self); require('../methods/client/confirmTransaction')(Self); require('../methods/client/canBeInvoiced')(Self); require('../methods/client/uploadFile')(Self); require('../methods/client/lastActiveTickets')(Self); require('../methods/client/sendSms')(Self); require('../methods/client/createAddress')(Self); require('../methods/client/updateAddress')(Self); require('../methods/client/consumption')(Self); require('../methods/client/createReceipt')(Self); // Validations Self.validatesPresenceOf('street', { message: 'Street cannot be empty' }); Self.validatesPresenceOf('city', { message: 'City cannot be empty' }); Self.validatesUniquenessOf('fi', { message: 'TIN must be unique' }); Self.validatesUniquenessOf('socialName', { message: 'The company name must be unique' }); Self.validatesFormatOf('email', { message: 'Invalid email', allowNull: true, allowBlank: true, with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/ }); Self.validatesLengthOf('postcode', { allowNull: true, allowBlank: true, min: 3, max: 10 }); Self.validateAsync('iban', ibanNeedsValidation, { message: 'The IBAN does not have the correct format' }); async function ibanNeedsValidation(err, done) { let filter = { fields: ['code'], where: {id: this.countryFk} }; let country = await Self.app.models.Country.findOne(filter); let code = country ? country.code.toLowerCase() : null; if (code != 'es') return done(); if (!validateIban(this.iban)) err(); done(); } Self.validateAsync('fi', tinIsValid, { message: 'Invalid TIN' }); async function tinIsValid(err, done) { if (!this.isTaxDataChecked) return done(); let filter = { fields: ['code'], where: {id: this.countryFk} }; let country = await Self.app.models.Country.findOne(filter); let code = country ? country.code.toLowerCase() : null; if (!this.fi || !validateTin(this.fi, code)) err(); done(); } Self.validate('payMethod', hasSalesMan, { message: 'Cannot change the payment method if no salesperson' }); function hasSalesMan(err) { if (this.payMethod && !this.salesPersonUser) err(); } Self.validate('isEqualizated', cannotHaveET, { message: 'Cannot check Equalization Tax in this NIF/CIF' }); function cannotHaveET(err) { if (!this.fi) return; let tin = this.fi.toUpperCase(); let cannotHaveET = /^[A-B]/.test(tin); if (cannotHaveET && this.isEqualizated) err(); } Self.validateAsync('payMethodFk', hasIban, { message: 'That payment method requires an IBAN' }); function hasIban(err, done) { Self.app.models.PayMethod.findById(this.payMethodFk, (_, instance) => { if (instance && instance.ibanRequired && !this.iban) err(); done(); }); } Self.validateAsync('bankEntityFk', hasBic, { message: 'That payment method requires a BIC' }); function hasBic(err, done) { if (this.iban && !this.bankEntityFk) err(); done(); } Self.validateAsync('defaultAddressFk', isActive, {message: 'Unable to default a disabled consignee'} ); async function isActive(err, done) { if (!this.defaultAddressFk) return done(); const address = await Self.app.models.Address.findById(this.defaultAddressFk); if (address && !address.isActive) err(); done(); } Self.validateAsync('postCode', hasValidPostcode, { message: `The postcode doesn't exist. Please enter a correct one` }); async function hasValidPostcode(err, done) { if (!this.postcode) return done(); const models = Self.app.models; const postcode = await models.Postcode.findById(this.postcode); if (!postcode) err(); done(); } function isAlpha(value) { const regexp = new RegExp(/^[ñça-zA-Z0-9\s]*$/i); return regexp.test(value); } Self.observe('before save', async function(ctx) { let changes = ctx.data || ctx.instance; let orgData = ctx.currentInstance; let finalState = getFinalState(ctx); let payMethodWithIban = 4; // Validate socialName format const hasChanges = orgData && changes; const socialName = changes && changes.socialName || orgData && orgData.socialName; const isTaxDataChecked = hasChanges && (changes.isTaxDataChecked || orgData.isTaxDataChecked); const socialNameChanged = hasChanges && orgData.socialName != socialName; const dataCheckedChanged = hasChanges && orgData.isTaxDataChecked != isTaxDataChecked; if ((socialNameChanged || dataCheckedChanged) && !isAlpha(socialName)) throw new UserError('The socialName has an invalid format'); if (changes.salesPerson === null) { changes.credit = 0; changes.discount = 0; changes.payMethodFk = 5; // Credit card } let payMethodFk = changes.payMethodFk || (orgData && orgData.payMethodFk); let dueDay = changes.dueDay || (orgData && orgData.dueDay); if (payMethodFk == payMethodWithIban && dueDay == 0) changes.dueDay = 5; if (isMultiple(ctx)) return; if (changes.credit !== undefined) { await validateCreditChange(ctx, finalState); let filter = {fields: ['id'], where: {userFk: ctx.options.accessToken.userId}}; let worker = await Self.app.models.Worker.findOne(filter); let newCredit = { amount: changes.credit, clientFk: finalState.id, workerFk: worker ? worker.id : null }; await Self.app.models.ClientCredit.create(newCredit); } }); Self.observe('after save', async ctx => { if (ctx.isNewInstance) return; const hookState = ctx.hookState; const newInstance = hookState.newInstance; const oldInstance = hookState.oldInstance; const instance = ctx.instance; const models = Self.app.models; const payMethodChanged = oldInstance.payMethodFk != newInstance.payMethodFk; const ibanChanged = oldInstance.iban != newInstance.iban; const dueDayChanged = oldInstance.dueDay != newInstance.dueDay; if (payMethodChanged || ibanChanged || dueDayChanged) { const loopBackContext = LoopBackContext.getCurrentContext(); const httpCtx = {req: loopBackContext.active}; const httpRequest = httpCtx.req.http.req; const $t = httpRequest.__; const headers = httpRequest.headers; const origin = headers.origin; const authorization = headers.authorization; const salesPersonId = instance.salesPersonFk; if (salesPersonId) { const fullUrl = `${origin}/#!/client/${instance.id}/billing-data`; const message = $t('MESSAGE_CHANGED_PAYMETHOD', { clientId: instance.id, clientName: instance.name, url: fullUrl }); await models.Chat.sendCheckingPresence(httpCtx, salesPersonId, message); } // Send email to client if (!instance.email) return; const params = { authorization: authorization, recipientId: instance.id, recipient: instance.email }; await got.get(`${origin}/api/email/payment-update`, { query: params }); } const workerIdBefore = oldInstance.salesPersonFk; const workerIdAfter = newInstance.salesPersonFk; const assignmentChanged = workerIdBefore != workerIdAfter; if (assignmentChanged) await Self.notifyAssignment(instance, workerIdBefore, workerIdAfter); }); // Send notification on client worker assignment Self.notifyAssignment = async function notifyAssignment(client, previousWorkerId, currentWorkerId) { const loopBackContext = LoopBackContext.getCurrentContext(); const httpCtx = {req: loopBackContext.active}; const httpRequest = httpCtx.req.http.req; const $t = httpRequest.__; const headers = httpRequest.headers; const origin = headers.origin; const models = Self.app.models; let previousWorker = {name: $t('None')}; let currentWorker = {name: $t('None')}; if (previousWorkerId) { const worker = await models.Worker.findById(previousWorkerId, { include: {relation: 'user'} }); previousWorker.user = worker && worker.user().name; previousWorker.name = worker && worker.user().nickname; } if (currentWorkerId) { const worker = await models.Worker.findById(currentWorkerId, { include: {relation: 'user'} }); currentWorker.user = worker && worker.user().name; currentWorker.name = worker && worker.user().nickname; } const fullUrl = `${origin}/#!/client/${client.id}/basic-data`; const message = $t('Client assignment has changed', { clientId: client.id, clientName: client.name, url: fullUrl, previousWorkerName: previousWorker.name, currentWorkerName: currentWorker.name }); if (previousWorkerId) await models.Chat.send(httpCtx, `@${previousWorker.user}`, message); if (currentWorkerId) await models.Chat.send(httpCtx, `@${currentWorker.user}`, message); }; async function validateCreditChange(ctx, finalState) { let models = Self.app.models; let userId = ctx.options.accessToken.userId; let currentUserIsManager = await models.Account.hasRole(userId, 'manager'); if (currentUserIsManager) return; let filter = { fields: ['roleFk'], where: { maxAmount: {gt: ctx.data.credit} } }; let limits = await models.ClientCreditLimit.find(filter); if (limits.length == 0) throw new UserError('Credit limits not found'); // Si el usuario no tiene alguno de los roles no continua let requiredRoles = []; for (limit of limits) requiredRoles.push(limit.roleFk); let where = { roleId: {inq: requiredRoles}, principalType: 'USER', principalId: userId }; let count = await models.RoleMapping.count(where); if (count <= 0) throw new UserError('The role cannot set this credit amount'); } const app = require('vn-loopback/server/server'); app.on('started', function() { let account = app.models.Account; account.observe('before save', async ctx => { if (ctx.isNewInstance) return; ctx.hookState.oldInstance = JSON.parse(JSON.stringify(ctx.currentInstance)); }); account.observe('after save', async ctx => { let changes = ctx.data || ctx.instance; if (!ctx.isNewInstance && changes) { let oldData = ctx.hookState.oldInstance; let hasChanges = oldData.name != changes.name || oldData.active != changes.active; if (!hasChanges) return; let userId = ctx.options.accessToken.userId; let logRecord = { originFk: oldData.id, userFk: userId, action: 'update', changedModel: 'Account', oldInstance: {name: oldData.name, active: oldData.active}, newInstance: {name: changes.name, active: changes.active} }; await Self.app.models.ClientLog.create(logRecord); } }); }); };