diff --git a/db/changes/231601/00-aclClientInforma.sql b/db/changes/231601/00-aclClientInforma.sql new file mode 100644 index 000000000..6222d2632 --- /dev/null +++ b/db/changes/231601/00-aclClientInforma.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) +VALUES ('ClientInforma', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('ClientInforma', '*', 'WRITE', 'ALLOW', 'ROLE', 'financial'); diff --git a/db/changes/231601/00-clientInforma.sql b/db/changes/231601/00-clientInforma.sql new file mode 100644 index 000000000..9bf757fc3 --- /dev/null +++ b/db/changes/231601/00-clientInforma.sql @@ -0,0 +1,16 @@ +ALTER TABLE `vn`.`client` ADD rating INT UNSIGNED DEFAULT NULL NULL COMMENT 'información proporcionada por Informa'; +ALTER TABLE `vn`.`client` ADD recommendedCredit INT UNSIGNED DEFAULT NULL NULL COMMENT 'información proporcionada por Informa'; + +CREATE TABLE `vn`.`clientInforma` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `clientFk` int(11) NOT NULL, + `rating` int(10) unsigned DEFAULT NULL, + `recommendedCredit` int(10) unsigned DEFAULT NULL, + `workerFk` int(10) unsigned NOT NULL, + `created` timestamp NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `informaWorkers_fk_idx` (`workerFk`), + KEY `informaClientFk` (`clientFk`), + CONSTRAINT `informa_ClienteFk` FOREIGN KEY (`clientFk`) REFERENCES `client` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `informa_workers_fk` FOREIGN KEY (`workerFk`) REFERENCES `worker` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE +) ENGINE=InnoDB CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='información proporcionada por Informa, se actualiza desde el hook de client (salix)'; diff --git a/db/changes/231601/00-client_setRatingAcl.sql b/db/changes/231601/00-client_setRatingAcl.sql new file mode 100644 index 000000000..b041b131a --- /dev/null +++ b/db/changes/231601/00-client_setRatingAcl.sql @@ -0,0 +1,64 @@ +DELETE FROM `salix`.`ACL` WHERE id=7; + +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('Client', 'setRating', 'WRITE', 'ALLOW', 'ROLE', 'financial'); + +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('Client', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'addressesPropagateRe', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'canBeInvoiced', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'canCreateTicket', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'consumption', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'createAddress', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'createWithUser', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'extendedListFilter', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'getAverageInvoiced', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'getCard', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'getDebt', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'getMana', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'transactions', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'hasCustomerRole', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'isValidClient', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'lastActiveTickets', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'sendSms', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'setPassword', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'summary', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'updateAddress', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'updateFiscalData', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'updateUser', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'uploadFile', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'campaignMetricsPdf', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'campaignMetricsEmail', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'clientWelcomeHtml', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'clientWelcomeEmail', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'printerSetupHtml', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'printerSetupEmail', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'sepaCoreEmail', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'letterDebtorPdf', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'letterDebtorStHtml', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'letterDebtorStEmail', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'letterDebtorNdHtml', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'letterDebtorNdEmail', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'clientDebtStatementPdf', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'clientDebtStatementHtml', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'clientDebtStatementEmail', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'creditRequestPdf', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'creditRequestHtml', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'creditRequestEmail', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'incotermsAuthorizationPdf', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'incotermsAuthorizationHtml', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'incotermsAuthorizationEmail', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'consumptionSendQueued', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'filter', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'getClientOrSupplierReference', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'upsert', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'create', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'replaceById', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'updateAttributes', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'updateAttributes', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'deleteById', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'replaceOrCreate', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'updateAll', '*', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'upsertWithWhere', '*', 'ALLOW', 'ROLE', 'employee'); diff --git a/e2e/helpers/extensions.js b/e2e/helpers/extensions.js index 7d80c69ee..1fcbfd616 100644 --- a/e2e/helpers/extensions.js +++ b/e2e/helpers/extensions.js @@ -156,14 +156,14 @@ let actions = { await this.waitForSpinnerLoad(); }, - accessToSection: async function(state) { + accessToSection: async function(state, name = 'Others') { await this.waitForSelector('vn-left-menu'); let nested = await this.evaluate(state => { return document.querySelector(`vn-left-menu li li > a[ui-sref="${state}"]`) != null; }, state); if (nested) { - let selector = 'vn-left-menu vn-item-section > vn-icon[icon=keyboard_arrow_down]'; + let selector = `vn-left-menu li[name="${name}"]`; await this.evaluate(selector => { document.querySelector(selector).scrollIntoViewIfNeeded(); }, selector); diff --git a/modules/client/back/methods/client/setRating.js b/modules/client/back/methods/client/setRating.js new file mode 100644 index 000000000..21ac0c914 --- /dev/null +++ b/modules/client/back/methods/client/setRating.js @@ -0,0 +1,55 @@ +module.exports = Self => { + Self.remoteMethodCtx('setRating', { + description: 'Change rating and recommendedCredit of a client', + accessType: 'WRITE', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'The user id', + http: {source: 'path'} + }, + { + arg: 'rating', + type: 'number' + }, + { + arg: 'recommendedCredit', + type: 'number' + } + ], + http: { + path: `/:id/setRating`, + verb: 'POST' + } + }); + + Self.setRating = async function(ctx, id, rating, recommendedCredit, options) { + let tx; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const client = await Self.findById(id, null, myOptions); + const clientUpdated = await client.updateAttributes({ + rating: rating, + recommendedCredit: recommendedCredit + }, myOptions); + + if (tx) await tx.commit(); + + return clientUpdated; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/client/back/methods/client/specs/setRating.spec.js b/modules/client/back/methods/client/specs/setRating.spec.js new file mode 100644 index 000000000..a7d0fb03a --- /dev/null +++ b/modules/client/back/methods/client/specs/setRating.spec.js @@ -0,0 +1,43 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('Client setRating()', () => { + const financialId = 73; + const activeCtx = { + accessToken: {userId: financialId}, + http: { + req: { + headers: {origin: 'http://localhost'} + } + } + }; + const ctx = {req: activeCtx}; + + beforeAll(async() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should change rating and recommendedCredit', async() => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const clientId = 1101; + const newRating = 10; + const newRecommendedCredit = 20; + + const updatedClient = await models.Client.setRating(ctx, clientId, newRating, newRecommendedCredit, options); + + expect(updatedClient.rating).toEqual(newRating); + expect(updatedClient.recommendedCredit).toEqual(newRecommendedCredit); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/client/back/model-config.json b/modules/client/back/model-config.json index b466aa5a1..1e06ea1c0 100644 --- a/modules/client/back/model-config.json +++ b/modules/client/back/model-config.json @@ -32,6 +32,9 @@ "ClientConsumptionQueue": { "dataSource": "vn" }, + "ClientInforma": { + "dataSource": "vn" + }, "ClientLog": { "dataSource": "vn" }, diff --git a/modules/client/back/models/client-informa.json b/modules/client/back/models/client-informa.json new file mode 100644 index 000000000..0c652484e --- /dev/null +++ b/modules/client/back/models/client-informa.json @@ -0,0 +1,42 @@ +{ + "name": "ClientInforma", + "base": "Loggable", + "log": { + "model":"ClientLog", + "relation": "client", + "showField": "clientFk" + }, + "options": { + "mysql": { + "table": "clientInforma" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "rating": { + "type": "number" + }, + "recommendedCredit": { + "type": "number" + }, + "created": { + "type": "date" + } + }, + "relations": { + "worker": { + "type": "belongsTo", + "model": "Worker", + "foreignKey": "workerFk" + }, + "client": { + "type": "belongsTo", + "model": "Client", + "foreignKey": "clientFk" + } + } +} diff --git a/modules/client/back/models/client-methods.js b/modules/client/back/models/client-methods.js index 3538dbeb8..3b1a588ac 100644 --- a/modules/client/back/models/client-methods.js +++ b/modules/client/back/models/client-methods.js @@ -47,4 +47,5 @@ module.exports = Self => { require('../methods/client/consumptionSendQueued')(Self); require('../methods/client/filter')(Self); require('../methods/client/getClientOrSupplierReference')(Self); + require('../methods/client/setRating')(Self); }; diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index 7933f2f42..a86a782e4 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -280,6 +280,10 @@ module.exports = Self => { if (changes.credit !== undefined) await Self.changeCredit(ctx, finalState, changes); + // Credit management changes + if (orgData?.rating != changes.rating || orgData?.recommendedCredit != changes.recommendedCredit) + await Self.changeCreditManagement(ctx, finalState, changes); + const oldInstance = {}; if (!ctx.isNewInstance) { const newProps = Object.keys(changes); @@ -441,6 +445,19 @@ module.exports = Self => { }, ctx.options); }; + Self.changeCreditManagement = async function changeCreditManagement(ctx, finalState, changes) { + const models = Self.app.models; + const loopBackContext = LoopBackContext.getCurrentContext(); + const userId = loopBackContext.active.accessToken.userId; + + await models.ClientInforma.create({ + clientFk: finalState.id, + rating: changes.rating, + recommendedCredit: changes.recommendedCredit, + workerFk: userId + }, ctx.options); + }; + const app = require('vn-loopback/server/server'); app.on('started', function() { const VnUser = app.models.VnUser; diff --git a/modules/client/back/models/client.json b/modules/client/back/models/client.json index e8ce12453..5f56a1ed2 100644 --- a/modules/client/back/models/client.json +++ b/modules/client/back/models/client.json @@ -141,6 +141,12 @@ }, "hasElectronicInvoice": { "type": "boolean" + }, + "rating": { + "type": "number" + }, + "recommendedCredit": { + "type": "number" } }, diff --git a/modules/client/front/credit-management/index.html b/modules/client/front/credit-management/index.html new file mode 100644 index 000000000..d7456fd06 --- /dev/null +++ b/modules/client/front/credit-management/index.html @@ -0,0 +1,79 @@ + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + Since + Employee + Rating + Recommended credit + + + + + {{::clientInforma.created | date:'dd/MM/yyyy HH:mm'}} + + + {{::clientInforma.worker.user.nickname}} + + + {{::clientInforma.rating}} + {{::clientInforma.recommendedCredit}} + + + + + + + diff --git a/modules/client/front/credit-management/index.js b/modules/client/front/credit-management/index.js new file mode 100644 index 000000000..856acd27b --- /dev/null +++ b/modules/client/front/credit-management/index.js @@ -0,0 +1,32 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + + this.filter = { + include: [{ + relation: 'worker', + scope: { + fields: ['userFk'], + include: { + relation: 'user', + scope: { + fields: ['nickname'] + } + } + } + }], + }; + } + onSubmit() { + this.$.watcher.submit() + .then(() => this.$state.reload()); + } +} + +ngModule.vnComponent('vnClientCreditManagement', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/client/front/credit-management/locale/es.yml b/modules/client/front/credit-management/locale/es.yml new file mode 100644 index 000000000..8743a1fb9 --- /dev/null +++ b/modules/client/front/credit-management/locale/es.yml @@ -0,0 +1,2 @@ +Recommended credit: Crédito recomendado +Rating: Clasificación diff --git a/modules/client/front/index.js b/modules/client/front/index.js index ff767bc9e..c7e39ea5d 100644 --- a/modules/client/front/index.js +++ b/modules/client/front/index.js @@ -47,3 +47,5 @@ import './defaulter'; import './notification'; import './unpaid'; import './extended-list'; +import './credit-management'; + diff --git a/modules/client/front/locale/es.yml b/modules/client/front/locale/es.yml index 45ae33914..0c44a17bc 100644 --- a/modules/client/front/locale/es.yml +++ b/modules/client/front/locale/es.yml @@ -64,4 +64,6 @@ Compensation Account: Cuenta para compensar Amount to return: Cantidad a devolver Delivered amount: Cantidad entregada Unpaid: Impagado -There is no zona: No hay zona \ No newline at end of file +Credit management: Gestión de crédito +Credit opinion: Opinión de crédito +There is no zona: No hay zona diff --git a/modules/client/front/routes.json b/modules/client/front/routes.json index 406ca07d7..01d1b53bf 100644 --- a/modules/client/front/routes.json +++ b/modules/client/front/routes.json @@ -23,6 +23,14 @@ {"state": "client.card.recovery.index", "icon": "icon-recovery"}, {"state": "client.card.webAccess", "icon": "cloud"}, {"state": "client.card.log", "icon": "history"}, + { + "description": "Credit management", + "icon": "monetization_on", + "childs": [ + {"state": "client.card.creditInsurance.index", "icon": "icon-solunion"}, + {"state": "client.card.creditManagement", "icon": "contact_support"} + ] + }, { "description": "Others", "icon": "more", @@ -30,7 +38,6 @@ {"state": "client.card.sample.index", "icon": "mail"}, {"state": "client.card.consumption", "icon": "show_chart"}, {"state": "client.card.mandate", "icon": "pan_tool"}, - {"state": "client.card.creditInsurance.index", "icon": "icon-solunion"}, {"state": "client.card.contact", "icon": "contact_phone"}, {"state": "client.card.webPayment", "icon": "icon-onlinepayment"}, {"state": "client.card.dms.index", "icon": "cloud_upload"}, @@ -416,7 +423,8 @@ "state": "client.notification", "component": "vn-client-notification", "description": "Notifications" - }, { + }, + { "url": "/unpaid", "state": "client.card.unpaid", "component": "vn-client-unpaid", @@ -428,6 +436,13 @@ "state": "client.extendedList", "component": "vn-client-extended-list", "description": "Extended list" + }, + { + "url": "/credit-management", + "state": "client.card.creditManagement", + "component": "vn-client-credit-management", + "acl": ["financial"], + "description": "Credit opinion" } ] } diff --git a/modules/client/front/summary/index.html b/modules/client/front/summary/index.html index ed4b89ee4..1b58d42cc 100644 --- a/modules/client/front/summary/index.html +++ b/modules/client/front/summary/index.html @@ -1,3 +1,6 @@ + + +

- Basic data

-

Basic data

- - - - - - {{$ctrl.summary.salesPersonUser.name}} -

- Fiscal address

-

Fiscal address

- - - - - -

- Fiscal data

-

Fiscal data

- Billing data

-

Billing data

- - -

- Address @@ -201,10 +204,10 @@ ng-show="!$ctrl.isEmployee"> Address

- -

- Web access

-

Web access

- @@ -236,52 +239,60 @@

Business data

- - - - -
-

Financial information

- + + Billing data + + + + info="Invoices minus payments plus orders not yet invoiced"> - - - - - + +
@@ -341,7 +352,7 @@ class="link"> {{::ticket.refFk}} - {{::ticket.ticketState.state.name}} @@ -355,8 +366,8 @@ - - @@ -397,4 +408,4 @@ ticket="$ctrl.selectedTicket" model="model"> - \ No newline at end of file + diff --git a/modules/client/front/summary/locale/es.yml b/modules/client/front/summary/locale/es.yml index b6233d4b3..ca6e96fef 100644 --- a/modules/client/front/summary/locale/es.yml +++ b/modules/client/front/summary/locale/es.yml @@ -20,3 +20,6 @@ Invoices minus payments: Facturas menos recibos Deviated invoices minus payments: Facturas fuera de plazo menos recibos Go to the client: Ir al cliente Latest tickets: Últimos tickets +Rating: Clasificación +Value from 1 to 20. The higher the better value: Valor del 1 al 20. Cuanto más alto mejor valoración +Go to grafana: Ir a grafana diff --git a/modules/client/front/summary/style.scss b/modules/client/front/summary/style.scss index 79708b361..77fc020ef 100644 --- a/modules/client/front/summary/style.scss +++ b/modules/client/front/summary/style.scss @@ -2,8 +2,13 @@ vn-client-summary .summary { max-width: $width-lg; - + .alert span { color: $color-alert !important } -} \ No newline at end of file + + vn-horizontal h4 .grafana:after { + content: 'contact_support'; + font-size: 17px; + } +}