Merge pull request '5128-client.credit-management' (!1459) from 5128-client.credit-management into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #1459
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
This commit is contained in:
Vicent Llopis 2023-04-21 11:49:32 +00:00
commit 75432b8823
20 changed files with 476 additions and 75 deletions

View File

@ -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');

View File

@ -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)';

View File

@ -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');

View File

@ -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);

View File

@ -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;
}
};
};

View File

@ -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;
}
});
});

View File

@ -32,6 +32,9 @@
"ClientConsumptionQueue": {
"dataSource": "vn"
},
"ClientInforma": {
"dataSource": "vn"
},
"ClientLog": {
"dataSource": "vn"
},

View File

@ -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"
}
}
}

View File

@ -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);
};

View File

@ -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;

View File

@ -141,6 +141,12 @@
},
"hasElectronicInvoice": {
"type": "boolean"
},
"rating": {
"type": "number"
},
"recommendedCredit": {
"type": "number"
}
},

View File

@ -0,0 +1,79 @@
<mg-ajax path="Clients/{{post.params.id}}/setRating" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
url="Clients"
data="$ctrl.client"
id-value="$ctrl.$params.id"
insert-mode="true"
form="form"
save="post">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-input-number
vn-one
label="Rating"
ng-model="$ctrl.client.rating"
vn-focus
rule>
</vn-input-number>
<vn-input-number
vn-one
label="Recommended credit"
ng-model="$ctrl.client.recommendedCredit"
rule>
</vn-input-number>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
</vn-button-bar>
</form>
<vn-crud-model
vn-id="model"
url="ClientInformas"
filter="$ctrl.filter"
link="{clientFk: $ctrl.$params.id}"
limit="20"
data="clientInformas"
order="created DESC"
auto-load="true">
</vn-crud-model>
<vn-data-viewer
model="model"
class="vn-w-md">
<vn-card>
<vn-table model="model" class="vn-mt-lg">
<vn-thead>
<vn-tr>
<vn-th shrink-date field="created">Since</vn-th>
<vn-th field="workerFk">Employee</vn-th>
<vn-th field="rating" number>Rating</vn-th>
<vn-th field="recommendedCredit" number>Recommended credit</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="clientInforma in clientInformas">
<vn-td shrink-datetime>{{::clientInforma.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
<vn-td shrink>
<span
ng-click="workerDescriptor.show($event, clientInforma.workerFk)"
class="link">
{{::clientInforma.worker.user.nickname}}
</span>
</vn-td>
<vn-td number>{{::clientInforma.rating}}</vn-td>
<vn-td number>{{::clientInforma.recommendedCredit}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>

View File

@ -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
});

View File

@ -0,0 +1,2 @@
Recommended credit: Crédito recomendado
Rating: Clasificación

View File

@ -47,3 +47,5 @@ import './defaulter';
import './notification';
import './unpaid';
import './extended-list';
import './credit-management';

View File

@ -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
Credit management: Gestión de crédito
Credit opinion: Opinión de crédito
There is no zona: No hay zona

View File

@ -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"
}
]
}

View File

@ -1,3 +1,6 @@
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<vn-crud-model
vn-id="ticketsModel"
url="Tickets"
@ -22,75 +25,75 @@
<vn-horizontal>
<vn-one>
<h4 ng-show="$ctrl.isEmployee">
<a
<a
ui-sref="client.card.basicData({id:$ctrl.client.id})"
target="_self">
<span translate vn-tooltip="Go to">Basic data</span>
</a>
</h4>
<h4
<h4
translate
ng-show="!$ctrl.isEmployee">
Basic data
</h4>
<vn-label-value label="Id"
<vn-label-value label="Id"
value="{{$ctrl.summary.id}}">
</vn-label-value>
<vn-label-value label="Comercial Name"
<vn-label-value label="Comercial Name"
value="{{$ctrl.summary.name}}">
</vn-label-value>
<vn-label-value label="Contact"
<vn-label-value label="Contact"
value="{{$ctrl.summary.contact}}">
</vn-label-value>
<vn-label-value label="Phone"
<vn-label-value label="Phone"
value="{{$ctrl.summary.phone}}">
</vn-label-value>
<vn-label-value label="Mobile"
<vn-label-value label="Mobile"
value="{{$ctrl.summary.mobile}}">
</vn-label-value>
<vn-label-value label="Email" no-ellipsize
value="{{$ctrl.listEmails($ctrl.summary.email)}}">
</vn-label-value>
<vn-label-value label="Sales person">
<span
<span
ng-click="workerDescriptor.show($event, $ctrl.summary.salesPersonFk)"
class="link">
{{$ctrl.summary.salesPersonUser.name}}
</span>
</vn-label-value>
<vn-label-value label="Channel"
<vn-label-value label="Channel"
value="{{$ctrl.summary.contactChannel.name}}">
</vn-label-value>
</vn-one>
<vn-one>
<h4 ng-show="$ctrl.isEmployee">
<a
<a
ui-sref="client.card.fiscalData({id:$ctrl.client.id})"
target="_self">
<span translate vn-tooltip="Go to">Fiscal address</span>
</a>
</h4>
<h4
<h4
translate
ng-show="!$ctrl.isEmployee">
Fiscal address
</h4>
<vn-label-value label="Social name"
<vn-label-value label="Social name"
value="{{$ctrl.summary.socialName}}">
</vn-label-value>
<vn-label-value label="NIF / CIF"
<vn-label-value label="NIF / CIF"
value="{{$ctrl.summary.fi}}">
</vn-label-value>
<vn-label-value label="City"
<vn-label-value label="City"
value="{{$ctrl.summary.city}}">
</vn-label-value>
<vn-label-value label="Postcode"
<vn-label-value label="Postcode"
value="{{$ctrl.summary.postcode}}">
</vn-label-value>
<vn-label-value label="Province"
<vn-label-value label="Province"
value="{{$ctrl.summary.province.name}}">
</vn-label-value>
<vn-label-value label="Country"
<vn-label-value label="Country"
value="{{$ctrl.summary.country.country}}">
</vn-label-value>
<vn-label-value label="Street" no-ellipsize
@ -99,98 +102,98 @@
</vn-one>
<vn-one>
<h4 ng-show="$ctrl.isEmployee">
<a
<a
ui-sref="client.card.fiscalData({id:$ctrl.client.id})"
target="_self">
<span translate vn-tooltip="Go to">Fiscal data</span>
</a>
</h4>
<h4
<h4
translate
ng-show="!$ctrl.isEmployee">
Fiscal data
</h4>
<vn-vertical>
<vn-check
label="Is equalizated"
ng-model="$ctrl.summary.isEqualizated"
label="Is equalizated"
ng-model="$ctrl.summary.isEqualizated"
disabled="true">
</vn-check>
<vn-check
label="Active"
label="Active"
ng-model="$ctrl.summary.isActive"
disabled="true">
</vn-check>
<vn-check
label="Invoice by address"
ng-model="$ctrl.summary.hasToInvoiceByAddress"
label="Invoice by address"
ng-model="$ctrl.summary.hasToInvoiceByAddress"
disabled="true">
</vn-check>
<vn-check
label="Verified data"
ng-model="$ctrl.summary.isTaxDataChecked"
label="Verified data"
ng-model="$ctrl.summary.isTaxDataChecked"
disabled="true">
</vn-check>
<vn-check
label="Has to invoice"
ng-model="$ctrl.summary.hasToInvoice"
label="Has to invoice"
ng-model="$ctrl.summary.hasToInvoice"
disabled="true">
</vn-check>
<vn-check
label="Notify by email"
ng-model="$ctrl.summary.isToBeMailed"
label="Notify by email"
ng-model="$ctrl.summary.isToBeMailed"
disabled="true">
</vn-check>
<vn-check
label="Vies"
ng-model="$ctrl.summary.isVies"
label="Vies"
ng-model="$ctrl.summary.isVies"
disabled="true">
</vn-check>
</vn-vertical>
</vn-one>
<vn-one>
<h4 ng-show="$ctrl.isEmployee">
<a
<a
ui-sref="client.card.billingData({id:$ctrl.client.id})"
target="_self">
<span translate vn-tooltip="Go to">Billing data</span>
</a>
</h4>
<h4
<h4
translate
ng-show="!$ctrl.isEmployee">
Billing data
</h4>
<vn-label-value label="Pay method"
<vn-label-value label="Pay method"
value="{{$ctrl.summary.payMethod.name}}">
</vn-label-value>
<vn-label-value label="IBAN"
<vn-label-value label="IBAN"
value="{{$ctrl.summary.iban}}">
</vn-label-value>
<vn-label-value label="Due day"
<vn-label-value label="Due day"
value="{{$ctrl.summary.dueDay}}">
</vn-label-value>
<vn-vertical>
<vn-check
label="Received LCR"
ng-model="$ctrl.summary.hasLcr"
label="Received LCR"
ng-model="$ctrl.summary.hasLcr"
disabled="true">
</vn-check>
<vn-check
label="Received core VNL"
ng-model="$ctrl.summary.hasCoreVnl"
label="Received core VNL"
ng-model="$ctrl.summary.hasCoreVnl"
disabled="true">
</vn-check>
<vn-check
label="Received B2B VNL"
ng-model="$ctrl.summary.hasSepaVnl"
label="Received B2B VNL"
ng-model="$ctrl.summary.hasSepaVnl"
disabled="true">
</vn-check>
</vn-vertical>
</vn-one>
<vn-one>
<h4 ng-show="$ctrl.isEmployee">
<a
<a
ui-sref="client.card.address.index({id:$ctrl.client.id})"
target="_self">
<span translate vn-tooltip="Go to">Address</span>
@ -201,10 +204,10 @@
ng-show="!$ctrl.isEmployee">
Address
</h4>
<vn-label-value label="Name"
<vn-label-value label="Name"
value="{{$ctrl.summary.defaultAddress.nickname}}">
</vn-label-value>
<vn-label-value label="City"
<vn-label-value label="City"
value="{{$ctrl.summary.defaultAddress.city}}">
</vn-label-value>
<vn-label-value label="Street" no-ellipsize
@ -213,17 +216,17 @@
</vn-one>
<vn-one>
<h4 ng-show="$ctrl.isEmployee">
<a
<a
ui-sref="client.card.webAccess({id:$ctrl.client.id})"
target="_self">
<span translate vn-tooltip="Go to">Web access</span>
</a>
</h4>
<h4
<h4
translate
ng-show="!$ctrl.isEmployee">Web access
</h4>
<vn-label-value label="User"
<vn-label-value label="User"
value="{{$ctrl.summary.account.name}}">
</vn-label-value>
<vn-vertical>
@ -236,52 +239,60 @@
</vn-one>
<vn-one>
<h4 translate>Business data</h4>
<vn-label-value label="Total greuge"
<vn-label-value label="Total greuge"
value="{{$ctrl.summary.totalGreuge | currency: 'EUR':2}}">
</vn-label-value>
<vn-label-value label="Mana"
<vn-label-value label="Mana"
value="{{$ctrl.summary.mana.mana | currency: 'EUR':2}}">
</vn-label-value>
<vn-label-value label="Rate"
<vn-label-value label="Rate"
value="{{$ctrl.claimRate($ctrl.summary.claimsRatio.priceIncreasing / 100) | percentage}}">
</vn-label-value>
<vn-label-value label="Average invoiced"
<vn-label-value label="Average invoiced"
value="{{$ctrl.summary.averageInvoiced.invoiced | currency: 'EUR':2}}">
</vn-label-value>
<vn-label-value label="Claims"
<vn-label-value label="Claims"
value="{{$ctrl.claimingRate($ctrl.summary.claimsRatio.claimingRate) | percentage}}">
</vn-label-value>
</vn-one>
<vn-one>
<h4 translate>Financial information</h4>
<vn-label-value label="Risk"
<h4 ng-show="$ctrl.isEmployee">
<a href="https://grafana.verdnatura.es/d/40buzE4Vk/comportamiento-pagos-clientes?orgId=1&var-clientFk={{::$ctrl.client.id}}">
<span class="grafana" translate vn-tooltip="Go to grafana">Billing data</span>
</a>
</h4>
<vn-label-value label="Risk"
value="{{$ctrl.summary.debt.debt | currency: 'EUR':2}}"
ng-class="{alert: $ctrl.summary.debt.debt > $ctrl.summary.credit}"
info="Invoices minus payments plus orders not yet invoiced">
info="Invoices minus payments plus orders not yet invoiced">
</vn-label-value>
<vn-label-value label="Credit"
<vn-label-value label="Credit"
value="{{$ctrl.summary.credit | currency: 'EUR':2 }} "
ng-class="{alert: $ctrl.summary.credit > $ctrl.summary.creditInsurance ||
($ctrl.summary.credit && $ctrl.summary.creditInsurance == null)}"
info="Verdnatura's maximum risk">
</vn-label-value>
<vn-label-value label="Secured credit"
<vn-label-value label="Secured credit"
value="{{$ctrl.summary.creditInsurance | currency: 'EUR':2}} ({{$ctrl.summary.classifications[0].insurances[0].grade}})"
info="Solunion's maximum risk">
</vn-label-value>
<vn-label-value label="Balance"
<vn-label-value label="Balance"
value="{{$ctrl.summary.sumRisk | currency: 'EUR':2}}"
info="Invoices minus payments">
</vn-label-value>
<vn-label-value label="Balance due"
<vn-label-value label="Balance due"
value="{{($ctrl.summary.defaulters[0].amount >= 0 ? $ctrl.summary.defaulters[0].amount : '-') | currency: 'EUR':2}}"
ng-class="{alert: $ctrl.summary.defaulters[0].amount}"
info="Deviated invoices minus payments">
</vn-label-value>
<vn-label-value label="Recovery since"
<vn-label-value label="Recovery since"
ng-if="$ctrl.summary.recovery.started"
value="{{$ctrl.summary.recovery.started | date:'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Rating"
value="{{$ctrl.summary.rating}}"
info="Value from 1 to 20. The higher the better value">
</vn-label-value>
</vn-one>
</vn-horizontal>
<vn-horizontal>
@ -341,7 +352,7 @@
class="link">
{{::ticket.refFk}}
</span>
<span
<span
ng-show="::!ticket.refFk"
class="chip {{::$ctrl.stateColor(ticket)}}">
{{::ticket.ticketState.state.name}}
@ -355,8 +366,8 @@
<vn-td actions>
<vn-icon-button
vn-anchor="::{
state: 'ticket.card.sale',
params: {id: ticket.id},
state: 'ticket.card.sale',
params: {id: ticket.id},
target: '_blank'
}"
vn-tooltip="Go to lines"
@ -386,10 +397,10 @@
<vn-route-descriptor-popover
vn-id="routeDescriptor">
</vn-route-descriptor-popover>
<vn-worker-descriptor-popover
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-invoice-out-descriptor-popover
<vn-invoice-out-descriptor-popover
vn-id="invoiceOutDescriptor">
</vn-invoice-out-descriptor-popover>
<vn-popup vn-id="summary">
@ -397,4 +408,4 @@
ticket="$ctrl.selectedTicket"
model="model">
</vn-ticket-summary>
</vn-popup>
</vn-popup>

View File

@ -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

View File

@ -2,8 +2,13 @@
vn-client-summary .summary {
max-width: $width-lg;
.alert span {
color: $color-alert !important
}
}
vn-horizontal h4 .grafana:after {
content: 'contact_support';
font-size: 17px;
}
}