Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2665-route_index_multi_clone
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Carlos Jimenez Ruiz 2020-12-24 11:31:27 +01:00
commit e6106b1aa9
24 changed files with 231 additions and 22 deletions

View File

@ -1 +1,2 @@
INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId) VALUES ('Image', '*', 'WRITE', 'ALLOW', 'ROLE', 'employee') INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId) VALUES ('Image', '*', 'WRITE', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('PayDem', '*', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,14 @@
CREATE TABLE `vn`.`entryObservation` (
id int NOT NULL AUTO_INCREMENT,
entryFk int NOT NULL,
observationTypeFk TINYINT(3) UNSIGNED,
description TEXT,
PRIMARY KEY (id),
CONSTRAINT entry_id_entryFk
FOREIGN KEY (entryFk) REFERENCES entry(id),
CONSTRAINT observationType_id_observationTypeFk
FOREIGN KEY (observationTypeFk) REFERENCES observationType(id)
);
ALTER TABLE `vn`.`entryObservation`
ADD UNIQUE INDEX `entryFk_observationTypeFk_UNIQUE` (`entryFk` ASC,`observationTypeFk` ASC);

View File

@ -1,2 +0,0 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES ('PayDem', '*', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -1638,7 +1638,7 @@ INSERT INTO `vn`.`orderTicket`(`orderFk`, `ticketFk`)
INSERT INTO `vn`.`userConfig` (`userFk`, `warehouseFk`, `companyFk`) INSERT INTO `vn`.`userConfig` (`userFk`, `warehouseFk`, `companyFk`)
VALUES VALUES
(1, 2, 69), (1, 1, 69),
(5, 1, 442), (5, 1, 442),
(9, 1, 442), (9, 1, 442),
(18, 3, 567); (18, 3, 567);

View File

@ -71,7 +71,7 @@ describe('User config', () => {
expect(expectedLocalWarehouse).toBeTruthy(); expect(expectedLocalWarehouse).toBeTruthy();
expect(expectedLocalBank).toBeTruthy(); expect(expectedLocalBank).toBeTruthy();
expect(expectedLocalCompany).toBeTruthy(); expect(expectedLocalCompany).toBeTruthy();
expect(userWarehouse).toEqual('Warehouse Two'); expect(userWarehouse).toEqual('Warehouse One');
expect(userCompany).toEqual('CCs'); expect(userCompany).toEqual('CCs');
}); });

View File

@ -4,6 +4,8 @@ import './style.scss';
export default class DescriptorPopover extends Popover { export default class DescriptorPopover extends Popover {
show(parent, id) { show(parent, id) {
if (!id) return;
super.show(parent); super.show(parent);
this.id = id; this.id = id;

View File

@ -7,5 +7,8 @@
}, },
"EntryLog": { "EntryLog": {
"dataSource": "vn" "dataSource": "vn"
},
"EntryObservation": {
"dataSource": "vn"
} }
} }

View File

@ -0,0 +1,9 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY')
return new UserError(`The observation type can't be repeated`);
return err;
});
};

View File

@ -0,0 +1,37 @@
{
"name": "EntryObservation",
"base": "Loggable",
"log": {
"model": "EntryLog",
"relation": "entry"
},
"options": {
"mysql": {
"table": "entryObservation"
}
},
"properties": {
"id": {
"id": true,
"type": "Number",
"description": "Identifier"
},
"description": {
"type": "String",
"required": true
}
},
"relations": {
"entry": {
"type": "belongsTo",
"model": "Entry",
"foreignKey": "entryFk"
},
"observationType": {
"type": "belongsTo",
"model": "ObservationType",
"foreignKey": "observationTypeFk",
"required": true
}
}
}

View File

@ -10,6 +10,7 @@ import './latest-buys-search-panel';
import './descriptor'; import './descriptor';
import './descriptor-popover'; import './descriptor-popover';
import './card'; import './card';
import './note';
import './summary'; import './summary';
import './log'; import './log';
import './buy'; import './buy';

View File

@ -0,0 +1,72 @@
<vn-crud-model
vn-id="model"
url="EntryObservations"
fields="['id', 'entryFk', 'observationTypeFk', 'description']"
link="{entryFk: $ctrl.$params.id}"
data="observations"
auto-load="true">
</vn-crud-model>
<vn-crud-model
url="ObservationTypes"
data="observationTypes"
auto-load="true">
</vn-crud-model>
<vn-watcher
vn-id="watcher"
data="observations"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-one class="vn-mt-md">
<vn-horizontal ng-repeat="observation in observations track by $index">
<vn-autocomplete
ng-attr-disabled="observation.id ? true : false"
ng-model="observation.observationTypeFk"
data="observationTypes"
show-field="description"
label="Observation type"
vn-one
vn-focus>
</vn-autocomplete>
<vn-textfield
vn-two
class="vn-mr-lg"
label="Description"
ng-model="observation.description"
rule="EntryObservation">
</vn-textfield>
<vn-auto class="vn-pt-md">
<vn-icon-button
pointer
vn-tooltip="Remove note"
icon="delete"
ng-click="model.remove($index)">
</vn-icon-button>
</vn-auto>
</vn-horizontal>
</vn-one>
<vn-one>
<vn-icon-button
vn-tooltip="Add note"
icon="add_circle"
vn-bind="+"
ng-if="observationTypes.length > observations.length"
ng-click="model.insert()">
</vn-icon-button>
</vn-one>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
<!-- # #2680 Undo changes button bugs -->
<!-- <vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button> -->
</vn-button-bar>
</form>

View File

@ -0,0 +1,20 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
onSubmit() {
this.$.watcher.check();
this.$.model.save().then(() => {
this.$.watcher.notifySaved();
this.$.watcher.updateOriginalData();
});
}
}
ngModule.vnComponent('vnEntryObservation', {
template: require('./index.html'),
controller: Controller,
bindings: {
entry: '<'
}
});

View File

@ -12,6 +12,7 @@
"card": [ "card": [
{"state": "entry.card.basicData", "icon": "settings"}, {"state": "entry.card.basicData", "icon": "settings"},
{"state": "entry.card.buy", "icon": "icon-lines"}, {"state": "entry.card.buy", "icon": "icon-lines"},
{"state": "entry.card.observation", "icon": "insert_drive_file"},
{"state": "entry.card.log", "icon": "history"} {"state": "entry.card.log", "icon": "history"}
] ]
}, },
@ -61,7 +62,15 @@
"params": { "params": {
"entry": "$ctrl.entry" "entry": "$ctrl.entry"
} }
}, { },{
"url": "/observation",
"state": "entry.card.observation",
"component": "vn-entry-observation",
"description": "Notes",
"params": {
"entry": "$ctrl.entry"
}
},{
"url" : "/log", "url" : "/log",
"state": "entry.card.log", "state": "entry.card.log",
"component": "vn-entry-log", "component": "vn-entry-log",

View File

@ -2,15 +2,14 @@
vn-id="model" vn-id="model"
url="Routes/filter" url="Routes/filter"
limit="20" limit="20"
order="created DESC" order="created DESC">
auto-load="true">
</vn-crud-model> </vn-crud-model>
<vn-portal slot="topbar"> <vn-portal slot="topbar">
<vn-searchbar <vn-searchbar
vn-focus vn-focus
panel="vn-route-search-panel" panel="vn-route-search-panel"
info="Search routes by id" info="Search routes by id"
filter="$ctrl.filter" filter="$ctrl.filterParams"
model="model"> model="model">
</vn-searchbar> </vn-searchbar>
</vn-portal> </vn-portal>

View File

@ -10,7 +10,8 @@ export default class Route extends ModuleMain {
let from = new Date(); let from = new Date();
from.setHours(0, 0, 0, 0); from.setHours(0, 0, 0, 0);
this.filter = {from, to, warehouseFk: this.vnConfig.warehouseFk}; this.filterParams = {from, to, warehouseFk: this.vnConfig.warehouseFk};
this.$.model.applyFilter(null, this.filterParams);
} }
} }

View File

@ -25,8 +25,7 @@
"ticket": { "ticket": {
"type": "belongsTo", "type": "belongsTo",
"model": "Ticket", "model": "Ticket",
"foreignKey": "ticketFk", "foreignKey": "ticketFk"
"required": true
}, },
"observationType": { "observationType": {
"type": "belongsTo", "type": "belongsTo",

View File

@ -11,7 +11,7 @@
info="Search ticket by id or alias" info="Search ticket by id or alias"
model="model" model="model"
fetch-params="$ctrl.fetchParams($params)" fetch-params="$ctrl.fetchParams($params)"
suggested-filter="$ctrl.defaultFilter"> suggested-filter="$ctrl.filterParams">
</vn-searchbar> </vn-searchbar>
</vn-portal> </vn-portal>
<vn-portal slot="menu"> <vn-portal slot="menu">

View File

@ -5,7 +5,7 @@ export default class Ticket extends ModuleMain {
constructor() { constructor() {
super(); super();
this.defaultFilter = { this.filterParams = {
scopeDays: 1 scopeDays: 1
}; };
} }

View File

@ -23,9 +23,9 @@
<vn-td>{{::tracking.state.name}}</vn-td> <vn-td>{{::tracking.state.name}}</vn-td>
<vn-td expand> <vn-td expand>
<span <span
class="link" ng-class="{'link': tracking.worker.id}"
ng-click="workerDescriptor.show($event, tracking.worker.user.id)"> ng-click="workerDescriptor.show($event, tracking.worker.user.id)">
{{::tracking.worker.user.name | dashIfEmpty}} {{::tracking.worker.user.name || 'System' | translate}}
</span> </span>
</vn-td> </vn-td>
<vn-td>{{::tracking.created | date:'dd/MM/yyyy HH:mm'}}</vn-td> <vn-td>{{::tracking.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>

View File

@ -10,7 +10,8 @@
panel="vn-travel-search-panel" panel="vn-travel-search-panel"
info="Search travels by id" info="Search travels by id"
model="model" model="model"
fetch-params="$ctrl.fetchParams($params)"> fetch-params="$ctrl.fetchParams($params)"
suggested-filter="$ctrl.filterParams">
</vn-searchbar> </vn-searchbar>
</vn-portal> </vn-portal>
<vn-portal slot="menu"> <vn-portal slot="menu">

View File

@ -2,6 +2,14 @@ import ngModule from '../module';
import ModuleMain from 'salix/components/module-main'; import ModuleMain from 'salix/components/module-main';
export default class Travel extends ModuleMain { export default class Travel extends ModuleMain {
constructor() {
super();
this.filterParams = {
scopeDays: 1
};
}
fetchParams($params) { fetchParams($params) {
if (!Object.entries($params).length) if (!Object.entries($params).length)
$params.scopeDays = 1; $params.scopeDays = 1;

View File

@ -33,7 +33,7 @@
<span <span
ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}" ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}"
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)" ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)"
translate>{{::log.user.name | dashIfEmpty}} translate>{{::log.user.name || 'System' | translate}}
</span> </span>
</div> </div>
<div> <div>
@ -54,7 +54,7 @@
<span <span
ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}" ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}"
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)" ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)"
translate>{{::log.user.name | dashIfEmpty}} translate>{{::log.user.name || 'System' | translate}}
</span> </span>
</vn-td> </vn-td>
<vn-td class="expendable"> <vn-td class="expendable">
@ -70,7 +70,7 @@
<vn-one ng-repeat="old in log.oldProperties"> <vn-one ng-repeat="old in log.oldProperties">
<div> <div>
<span translate class="label">{{::old.key}}</span><span class="label">: </span> <span translate class="label">{{::old.key}}</span><span class="label">: </span>
<span translate class="value">{{::old.value}}</span> <span translate class="value">{{::old.value | dashIfEmpty}}</span>
</div> </div>
</vn-one> </vn-one>
</vn-td> </vn-td>
@ -81,7 +81,7 @@
id="newInstance"> id="newInstance">
<div> <div>
<span translate class="label">{{::new.key}}</span><span class="label">: </span> <span translate class="label">{{::new.key}}</span><span class="label">: </span>
<span translate class="value">{{::new.value}}</span> <span translate class="value">{{::new.value | dashIfEmpty}}</span>
</div> </div>
</vn-one> </vn-one>
<vn-one <vn-one

View File

@ -9,4 +9,5 @@ Name: Nombre
Creates: Crea Creates: Crea
Updates: Actualiza Updates: Actualiza
Deletes: Elimina Deletes: Elimina
Views: Visualiza Views: Visualiza
System: Sistema

View File

@ -151,6 +151,7 @@ module.exports = app => {
SELECT SELECT
t.id, t.id,
t.clientFk, t.clientFk,
c.name clientName,
c.email recipient, c.email recipient,
c.salesPersonFk, c.salesPersonFk,
c.isToBeMailed, c.isToBeMailed,
@ -196,6 +197,10 @@ module.exports = app => {
const email = new Email('delivery-note-link', args); const email = new Email('delivery-note-link', args);
await email.send(); await email.send();
} catch (error) { } catch (error) {
// Domain not found
if (error.responseCode == 450)
return invalidEmail(ticket);
// Save tickets on a list of failed ids // Save tickets on a list of failed ids
failedtickets.push({ failedtickets.push({
id: ticket.id, id: ticket.id,
@ -220,4 +225,33 @@ module.exports = app => {
}); });
} }
} }
async function invalidEmail(ticket) {
await db.rawSql(`UPDATE client SET email = NULL WHERE id = :clientId`, {
clientId: ticket.clientFk
});
const oldInstance = `{"email": "${ticket.recipient}"}`;
const newInstance = `{"email": ""}`;
await db.rawSql(`
INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance)
VALUES (:clientId, :userId, 'UPDATE', 'Client', :oldInstance, :newInstance)`, {
clientId: ticket.clientFk,
userId: null,
oldInstance: oldInstance,
newInstance: newInstance
});
const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong>
al cliente <strong>${ticket.clientFk} - ${ticket.clientName}</strong>
porque la dirección de email <strong>"${ticket.recipient}"</strong> no es correcta o no está disponible.<br/><br/>
Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente.
Actualiza la dirección de email con una correcta.`;
smtp.send({
to: ticket.salesPersonEmail,
subject: 'No se ha podido enviar el albarán',
html: body
});
}
}; };