Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5488-use_checkAccessAcl

This commit is contained in:
Alex Moreno 2023-04-21 13:53:07 +02:00
commit faee5ccd7d
23 changed files with 582 additions and 78 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;
}
}

View File

@ -24,9 +24,11 @@ sections:
dicho stock puede variar en función de la fecha seleccionada al configurar el
pedido. Es importante CONFIRMAR los pedidos para que la mercancía quede reservada.
delivery: El reparto se realiza de lunes a sábado según la zona en la que te encuentres.
Por regla general, los pedidos que se entregan por agencia, deben estar confirmados
y pagados antes de las 17h del día en que se preparan (el día anterior a recibirlos),
aunque esto puede variar si el pedido se envía a través de nuestro reparto y
Los pedidos que se entregan por agencia o por reparto Verdnatura deben estar confirmados y pagados
antes del cierre de la correspondiente ruta el mismo día de preparación del pedido. Este horario
puede variar mucho en función de la ruta y del volumen de pedidos para ese día, por lo que es
recomendable no apurar el tiempo y dejar todo listo a primera hora del día de preparación del pedido.
Aunque esto puede variar si el pedido se envía a través de nuestro reparto y
según la zona.
howToPay:
title: Cómo pagar

View File

@ -0,0 +1,51 @@
subject: Bienvenue chez Verdnatura
title: "Nous vous souhaitons la bienvenue!"
dearClient: Cher client
clientData: 'Vos données pour pouvoir acheter sur le site de Verdnatura (<a href="https://shop.verdnatura.es"
title="Visiter Verdnatura" target="_blank" style="color: #8dba25">https://shop.verdnatura.es</a>)
ou sur nos applications pour <a href="https://goo.gl/3hC2mG" title="App Store"
target="_blank" style="color: #8dba25">iOS</a> et <a href="https://goo.gl/8obvLc"
title="Google Play" target="_blank" style="color: #8dba25">Android</a>, sont'
clientId: Identifiant du client
user: Utilisateur
password: Mot de passe
passwordResetText: Cliquez sur "Vous avez oublié votre mot de passe?"
sections:
howToBuy:
title: Comment passer une commande
description: 'Pour passer une commande sur notre site, vous devez configurer celle-ci en indiquant :'
requeriments:
- Si vous souhaitez recevoir la commande (par agence ou par notre propre livraison)
ou si vous préférez la récupérer dans l'un de nos entrepôts.
- La date à laquelle vous souhaitez recevoir la commande (elle sera préparée la veille).
- L'adresse de livraison ou l'entrepôt où vous souhaitez récupérer la commande.
stock: Sur notre site et nos applications, vous pouvez visualiser le stock disponible de
fleurs coupées, feuillages, plantes, accessoires et artificiels. Veuillez noter que ce
stock peut varier en fonction de la date sélectionnée lors de la configuration de la
commande. Il est important de CONFIRMER les commandes pour que la marchandise soit réservée.
delivery: La livraison est effectuée du lundi au samedi selon la zone dans laquelle vous
vous trouvez. Les commandes livrées par agence ou par notre propre livraison doivent
être confirmées et payées avant la fermeture de la route correspondante le jour même
de la préparation de la commande. Cet horaire peut varier considérablement en fonction
de la route et du volume de commandes pour cette journée, il est donc recommandé de ne
pas attendre la dernière minute et de tout préparer tôt le matin le jour de la
préparation de la commande. Cela peut toutefois varier si la commande est envoyée par
notre propre livraison et en fonction de la zone.
howToPay:
title: Comment payer
description: 'Les moyens de paiement acceptés chez Verdnatura sont les suivants :'
options:
- Par <strong>carte de crédit</strong> via notre plateforme web (lors de la confirmation de la commande).
- Par <strong>virement bancaire mensuel</strong>, modalité à demander et à gérer.
toConsider:
title: Choses à prendre en compte
description: Verdnatura vend EXCLUSIVEMENT aux professionnels, vous devez donc nous envoyer
le modèle 036 ou 037 pour vérifier que vous êtes bien inscrit dans la catégorie du commerce de fleurs.
claimsPolicy:
title: POLITIQUE DE RÉCLAMATION
description: Verdnatura acceptera les réclamations effectuées dans les deux jours civils suivant
la réception de la commande (y compris le jour même de la réception). Passé ce délai, aucune réclamation ne sera acceptée.
help: Si vous avez des questions, n'hésitez pas à nous contacter, <strong>nous sommes là pour vous aider !</strong>
salesPersonName: Je suis votre commercial et mon nom est
salesPersonPhone: Téléphone et Whatsapp
salesPersonEmail: Adresse e-mail

View File

@ -0,0 +1,50 @@
subject: Bem-Vindo à Verdnatura
title: "Damos-te as boas-vindas!"
dearClient: Estimado cliente
clientData: 'seus dados para poder comprar na loja online da Verdnatura (<a href="https://shop.verdnatura.es"
title="Visitar Verdnatura" target="_blank" style="color: #8dba25">https://shop.verdnatura.es</a>)
ou em nossos aplicativos para <a href="https://goo.gl/3hC2mG" title="App Store" target="_blank" style="color: #8dba25">iOS</a>
e <a href="https://goo.gl/8obvLc" title="Google Play" target="_blank" style="color: #8dba25">Android</a>, são'
clientId: Identificador de cliente
user: Utilizador
password: Palavra-passe
passwordResetText: Clique em 'Esqueceu a sua palavra-passe?'
sections:
howToBuy:
title: Como fazer uma encomenda
description: 'Para realizar uma encomenda no nosso site, deves configurá-la indicando:'
requeriments:
- Se queres receber a encomenda (por agência ou o nosso próprio transporte) ou se preferes levantá-lo em algum dos nossos armazéns.
- A data que queres receber a encomenda (se preparará no dia anterior).
- A morada de entrega ou armazém aonde queres levantar a encomenda.
stock: No nosso site e apps podes visualizar o estoque disponível de
flor-de-corte, verduras, plantas, acessórios e artificial. Tenha presente que
dito estoque pode variar em função da data escolhida ao configurar a
encomenda. É importante confirmar as encomendas para que a mercadoria fique reservada.
delivery: O transporte se realiza de terça a sabado. As encomendas que se entreguem por agências ou transporte Verdnatura, devem estar confirmadas e pagas até
antes do horário de encerre da correspondente rota do dia de preparação da mesma. Este horario
pode variar muito em função da rota e o volume de encomendas deste dia, pelo qual é
recomendável não esperar à última hora e deixar tudo pronto à primeira hora do dia de preparação. Ainda que isto possa variar se a encomenda se envia através do nosso transporte
dependendo da zona.
howToPay:
title: Como pagar
description: 'As formas de pagamentos admitidas na Verdnatura são:'
options:
- Com <strong>cartão</strong> através da plataforma de pagamentos (ao confirmar a encomenda ou entrando em Encomendas > Confirmadas).
- Mediante <strong>débito automatico mensual</strong>, modalidade que deve-se solicitar e tramitar.
toConsider:
title: Coisas a ter em conta
description: A Verdnatura vende EXCLUSIVAMENTE a profissionais, pelo qual deves
remetir-nos o documento de Inicio de Actividade, para comprobar-mos que o vosso CAE
esteja relacionado com o mundo das flores.
claimsPolicy:
title: POLÍTICA DE RECLAMAÇÕES
description: A Verdnatura aceitará as reclamações que se realizem dentro dos
dois dias naturais seguintes à recepção da encomenda (incluindo o mesmo
dia da receção). Passado este prazo não se aceitará nenhuma reclamação.
help: Qualquer dúvida que lhe surja, não hesite em consultá-la <strong>estamos
para atender-te!</strong>
salesPersonName: Sou o seu asesor comercial e o meu nome é
salesPersonPhone: Telemovel e whatsapp
salesPersonEmail: Correio eletrônico