Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5395-travel.extra-community
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Vicent Llopis 2023-03-16 14:01:19 +01:00
commit 6d04933fe0
106 changed files with 1696 additions and 1462 deletions

View File

@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2310.01] - 2023-03-23 ## [2312.01] - 2023-04-06
### Added ### Added
- -
@ -13,6 +13,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- -
### Fixed
-
## [2310.01] - 2023-03-23
### Added
- (Trabajadores -> Control de horario) Ahora se puede confirmar/no confirmar el registro horario de cada semana desde esta sección
### Fixed ### Fixed
- (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo" - (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo"
- (General) Al pasar el ratón por encima del icono de "Borrar" en un campo, se hacía más grande afectando a la interfaz - (General) Al pasar el ratón por encima del icono de "Borrar" en un campo, se hacía más grande afectando a la interfaz

View File

@ -10,6 +10,7 @@ RUN apt-get update \
curl \ curl \
ca-certificates \ ca-certificates \
gnupg2 \ gnupg2 \
graphicsmagick \
&& curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \ && curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \ && apt-get install -y --no-install-recommends nodejs \
&& npm install -g npm@8.19.2 && npm install -g npm@8.19.2

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('previousLabel', { Self.remoteMethodCtx('previousLabel', {
description: 'Returns the previa label pdf', description: 'Returns the previa label pdf',
@ -33,17 +31,5 @@ module.exports = Self => {
} }
}); });
Self.previousLabel = async(ctx, id) => { Self.previousLabel = (ctx, id) => Self.printReport(ctx, id, 'previa-label');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('previa-label', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="previa-${id}.pdf"`];
};
}; };

View File

@ -1,215 +0,0 @@
const md5 = require('md5');
const fs = require('fs-extra');
module.exports = Self => {
Self.remoteMethodCtx('saveSign', {
description: 'Save sign',
accessType: 'WRITE',
accepts:
[
{
arg: 'signContent',
type: 'string',
required: true,
description: 'The sign content'
}, {
arg: 'tickets',
type: ['number'],
required: true,
description: 'The tickets'
}, {
arg: 'signedTime',
type: 'date',
description: 'The signed time'
}, {
arg: 'addressFk',
type: 'number',
required: true,
description: 'The address fk'
}
],
returns: {
type: 'Object',
root: true
},
http: {
path: `/saveSign`,
verb: 'POST'
}
});
async function createGestDoc(ticketId, userFk) {
const models = Self.app.models;
if (!await gestDocExists(ticketId)) {
const result = await models.Ticket.findOne({
where: {
id: ticketId
},
include: [
{
relation: 'warehouse',
scope: {
fields: ['id']
}
}, {
relation: 'client',
scope: {
fields: ['name']
}
}, {
relation: 'route',
scope: {
fields: ['id']
}
}
]
});
const warehouseFk = result.warehouseFk;
const companyFk = result.companyFk;
const client = result.client.name;
const route = result.route.id;
const resultDmsType = await models.DmsType.findOne({
where: {
code: 'Ticket'
}
});
const resultDms = await models.Dms.create({
dmsTypeFk: resultDmsType.id,
reference: ticketId,
description: `Ticket ${ticketId} Cliente ${client} Ruta ${route}`,
companyFk: companyFk,
warehouseFk: warehouseFk,
workerFk: userFk
});
return resultDms.insertId;
}
}
async function gestDocExists(ticket) {
const models = Self.app.models;
const result = await models.TicketDms.findOne({
where: {
ticketFk: ticket
},
fields: ['dmsFk']
});
if (result == null)
return false;
const isSigned = await models.Ticket.findOne({
where: {
id: ticket
},
fields: ['isSigned']
});
if (isSigned)
return true;
else
await models.Dms.destroyById(ticket);
}
async function dmsRecover(ticket, signContent) {
const models = Self.app.models;
await models.DmsRecover.create({
ticketFk: ticket,
sign: signContent
});
}
async function ticketGestdoc(ticket, dmsFk) {
const models = Self.app.models;
models.TicketDms.replaceOrCreate({
ticketFk: ticket,
dmsFk: dmsFk
});
const queryVnTicketSetState = `CALL vn.ticket_setState(?, ?)`;
await Self.rawSql(queryVnTicketSetState, [ticket, 'DELIVERED']);
}
async function updateGestdoc(file, ticket) {
const models = Self.app.models;
models.Dms.updateOne({
where: {
id: ticket
},
file: file,
contentType: 'image/png'
});
}
Self.saveSign = async(ctx, signContent, tickets, signedTime) => {
const models = Self.app.models;
let tx = await Self.beginTransaction({});
try {
const userId = ctx.req.accessToken.userId;
const dmsDir = `storage/dms`;
let image = null;
for (let i = 0; i < tickets.length; i++) {
const alertLevel = await models.TicketState.findOne({
where: {
ticketFk: tickets[i]
},
fields: ['alertLevel']
});
signedTime ? signedTime != undefined : signedTime = Date.vnNew();
if (alertLevel >= 2) {
let dir;
let id = null;
let fileName = null;
if (!await gestDocExists(tickets[i])) {
id = await createGestDoc(tickets[i], userId);
const hashDir = md5(id).substring(0, 3);
dir = `${dmsDir}/${hashDir}`;
if (!fs.existsSync(dir))
fs.mkdirSync(dir);
fileName = `${id}.png`;
image = `${dir}/${fileName}`;
} else
if (image != null) {
if (!fs.existsSync(dir))
dmsRecover(tickets[i], signContent);
else {
fs.writeFile(image, signContent, 'base64', async function(err) {
if (err) {
await tx.rollback();
return err.message;
}
});
}
} else
dmsRecover(tickets[i], signContent);
if (id != null && fileName.length > 0) {
ticketGestdoc(tickets[i], id);
updateGestdoc(id, fileName);
}
}
}
if (tx) await tx.commit();
return 'OK';
} catch (err) {
await tx.rollback();
throw err.message;
}
};
};

View File

@ -9,17 +9,29 @@
"properties": { "properties": {
"id": { "id": {
"id": true, "id": true,
"type": "number", "type": "number"
"forceId": false
}, },
"date": { "created": {
"type": "date" "type": "date"
}, },
"m3":{ "longitude":{
"type": "number" "type": "number"
}, },
"warehouseFk":{ "latitude":{
"type": "number"
},
"dated":{
"type": "date"
},
"ticketFk":{
"type": "number" "type": "number"
} }
} },
"relations": {
"ticket": {
"type": "belongsTo",
"model": "Ticket",
"foreignKey": "ticketFk"
}
}
} }

View File

@ -6,7 +6,6 @@ module.exports = Self => {
require('../methods/dms/removeFile')(Self); require('../methods/dms/removeFile')(Self);
require('../methods/dms/updateFile')(Self); require('../methods/dms/updateFile')(Self);
require('../methods/dms/deleteTrashFiles')(Self); require('../methods/dms/deleteTrashFiles')(Self);
require('../methods/dms/saveSign')(Self);
Self.checkRole = async function(ctx, id) { Self.checkRole = async function(ctx, id) {
const models = Self.app.models; const models = Self.app.models;

View File

@ -0,0 +1,74 @@
DROP TABLE `vn`.`dmsRecover`;
ALTER TABLE `vn`.`delivery` DROP FOREIGN KEY delivery_FK;
ALTER TABLE `vn`.`delivery` DROP COLUMN addressFk;
ALTER TABLE `vn`.`delivery` ADD ticketFk INT NOT NULL;
ALTER TABLE `vn`.`delivery` ADD CONSTRAINT delivery_ticketFk_FK FOREIGN KEY (`ticketFk`) REFERENCES `vn`.`ticket`(`id`);
DELETE FROM `salix`.`ACL` WHERE `property` = 'saveSign';
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
VALUES
('Ticket','saveSign','WRITE','ALLOW','employee');
DROP PROCEDURE IF EXISTS vn.route_getTickets;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`route_getTickets`(vRouteFk INT)
BEGIN
/**
* Pasado un RouteFk devuelve la información
* de sus tickets.
*
* @param vRouteFk
*
* @select Información de los tickets
*/
SELECT
t.id Id,
t.clientFk Client,
a.id Address,
t.packages Packages,
a.street AddressName,
a.postalCode PostalCode,
a.city City,
sub2.itemPackingTypeFk PackingType,
c.phone ClientPhone,
c.mobile ClientMobile,
a.phone AddressPhone,
a.mobile AddressMobile,
d.longitude Longitude,
d.latitude Latitude,
wm.mediaValue SalePersonPhone,
tob.Note Note,
t.isSigned Signed
FROM ticket t
JOIN client c ON t.clientFk = c.id
JOIN address a ON t.addressFk = a.id
LEFT JOIN delivery d ON t.id = d.ticketFk
LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk
LEFT JOIN
(SELECT tob.description Note, t.id
FROM ticketObservation tob
JOIN ticket t ON tob.ticketFk = t.id
JOIN observationType ot ON ot.id = tob.observationTypeFk
WHERE t.routeFk = vRouteFk
AND ot.code = 'delivery'
)tob ON tob.id = t.id
LEFT JOIN
(SELECT sub.ticketFk,
CONCAT('(', GROUP_CONCAT(DISTINCT sub.itemPackingTypeFk ORDER BY sub.items DESC SEPARATOR ','), ') ') itemPackingTypeFk
FROM (SELECT s.ticketFk , i.itemPackingTypeFk, COUNT(*) items
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
WHERE t.routeFk = vRouteFk
GROUP BY t.id,i.itemPackingTypeFk)sub
GROUP BY sub.ticketFk
) sub2 ON sub2.ticketFk = t.id
WHERE t.routeFk = vRouteFk
GROUP BY t.id
ORDER BY t.priority;
END$$
DELIMITER ;

View File

@ -2825,4 +2825,11 @@ INSERT INTO `vn`.`deviceProductionUser` (`deviceProductionFk`, `userFk`, `create
(1, 1, util.VN_NOW()), (1, 1, util.VN_NOW()),
(3, 3, util.VN_NOW()); (3, 3, util.VN_NOW());
INSERT INTO `vn`.`workerTimeControlMail` (`id`, `workerFk`, `year`, `week`, `state`, `updated`, `sendedCounter`, `reason`)
VALUES
(1, 9, 2000, 49, 'REVISE', util.VN_NOW(), 1, 'test2'),
(2, 9, 2000, 50, 'SENDED', util.VN_NOW(), 1, NULL),
(3, 9, 2000, 51, 'CONFIRMED', util.VN_NOW(), 1, NULL),
(4, 9, 2001, 1, 'SENDED', util.VN_NOW(), 1, NULL);

View File

@ -81220,3 +81220,4 @@ USE `vn`;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2023-02-21 8:14:30 -- Dump completed on 2023-02-21 8:14:30

View File

@ -524,7 +524,7 @@ export default {
}, },
itemLog: { itemLog: {
anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr', anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr',
fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(2) td.after', fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(4) td.after',
}, },
ticketSummary: { ticketSummary: {
header: 'vn-ticket-summary > vn-card > h5', header: 'vn-ticket-summary > vn-card > h5',

View File

@ -59,6 +59,6 @@ describe('Item log path', () => {
const fifthLineCreatedProperty = await page const fifthLineCreatedProperty = await page
.waitToGetProperty(selectors.itemLog.fifthLineCreatedProperty, 'innerText'); .waitToGetProperty(selectors.itemLog.fifthLineCreatedProperty, 'innerText');
expect(fifthLineCreatedProperty).toEqual('Coral y materiales similares'); expect(fifthLineCreatedProperty).toEqual('05080000');
}); });
}); });

View File

@ -37,6 +37,6 @@ describe('Zone descriptor path', () => {
await page.accessToSection('ticket.card.log'); await page.accessToSection('ticket.card.log');
const lastChanges = await page.waitToGetProperty(selectors.ticketLog.changes, 'innerText'); const lastChanges = await page.waitToGetProperty(selectors.ticketLog.changes, 'innerText');
expect(lastChanges).toContain('Arreglar'); expect(lastChanges).toContain('1');
}); });
}); });

View File

@ -15,9 +15,9 @@ export default class Calendar extends FormInput {
constructor($element, $scope, vnWeekDays, moment) { constructor($element, $scope, vnWeekDays, moment) {
super($element, $scope); super($element, $scope);
this.weekDays = vnWeekDays.locales; this.weekDays = vnWeekDays.locales;
this.defaultDate = Date.vnNew();
this.displayControls = true; this.displayControls = true;
this.moment = moment; this.moment = moment;
this.defaultDate = Date.vnNew();
} }
/** /**
@ -207,14 +207,23 @@ export default class Calendar extends FormInput {
} }
repeatLast() { repeatLast() {
if (!this.formatDay) return; if (this.formatDay) {
const days = this.element.querySelectorAll('.days > .day');
for (let i = 0; i < days.length; i++) {
this.formatDay({
$day: this.days[i],
$element: days[i]
});
}
}
let days = this.element.querySelectorAll('.days > .day'); if (this.formatWeek) {
for (let i = 0; i < days.length; i++) { const weeks = this.element.querySelectorAll('.weeks > .day');
this.formatDay({ for (const week of weeks) {
$day: this.days[i], this.formatWeek({
$element: days[i] $element: week
}); });
}
} }
} }
} }
@ -228,6 +237,7 @@ ngModule.vnComponent('vnCalendar', {
hasEvents: '&?', hasEvents: '&?',
getClass: '&?', getClass: '&?',
formatDay: '&?', formatDay: '&?',
formatWeek: '&?',
displayControls: '<?', displayControls: '<?',
hideYear: '<?', hideYear: '<?',
hideContiguous: '<?', hideContiguous: '<?',

View File

@ -0,0 +1,57 @@
const {Report, Email} = require('vn-print');
module.exports = Self => {
Self.printReport = async function(ctx, id, reportName) {
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report(reportName, params);
const stream = await report.toPdfStream();
let fileName = `${reportName}`;
if (id) fileName += `-${id}`;
return [stream, 'application/pdf', `filename="${fileName}.pdf"`];
};
Self.printEmail = async function(ctx, id, templateName) {
const {accessToken} = ctx.req;
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
params.access_token = accessToken.id;
const report = new Email(templateName, params);
const html = await report.render();
let fileName = `${templateName}`;
if (id) fileName += `-${id}`;
return [html, 'text/html', `filename=${fileName}.pdf"`];
};
Self.sendTemplate = async function(ctx, templateName) {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email(templateName, params);
return email.send();
};
};

View File

@ -1,4 +1,3 @@
const pick = require('object.pick');
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
module.exports = function(Self) { module.exports = function(Self) {
@ -6,344 +5,11 @@ module.exports = function(Self) {
Self.super_.setup.call(this); Self.super_.setup.call(this);
}; };
Self.observe('after save', async function(ctx) {
const loopBackContext = LoopBackContext.getCurrentContext();
await logInModel(ctx, loopBackContext);
});
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
const appModels = ctx.Model.app.models; ctx.options.httpCtx = LoopBackContext.getCurrentContext();
const definition = ctx.Model.definition;
const options = {};
// Check for transactions
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
let oldInstance;
let newInstance;
if (ctx.data) {
const changes = pick(ctx.currentInstance, Object.keys(ctx.data));
newInstance = ctx.data;
oldInstance = changes;
if (ctx.where && !ctx.currentInstance) {
const fields = Object.keys(ctx.data);
const modelName = definition.name;
ctx.oldInstances = await appModels[modelName].find({
where: ctx.where,
fields: fields
}, options);
}
}
// Get changes from created instance
if (ctx.isNewInstance)
newInstance = ctx.instance.__data;
ctx.hookState.oldInstance = oldInstance;
ctx.hookState.newInstance = newInstance;
}); });
Self.observe('before delete', async function(ctx) { Self.observe('before delete', async function(ctx) {
const appModels = ctx.Model.app.models; ctx.options.httpCtx = LoopBackContext.getCurrentContext();
const definition = ctx.Model.definition;
const relations = ctx.Model.relations;
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
if (ctx.where) {
let affectedModel = definition.name;
let deletedInstances = await appModels[affectedModel].find({
where: ctx.where
}, options);
let relation = definition.settings.log.relation;
if (relation) {
let primaryKey = relations[relation].keyFrom;
let arrangedDeletedInstances = [];
for (let i = 0; i < deletedInstances.length; i++) {
if (primaryKey)
deletedInstances[i].originFk = deletedInstances[i][primaryKey];
let arrangedInstance = await fkToValue(deletedInstances[i], ctx);
arrangedDeletedInstances[i] = arrangedInstance;
}
ctx.hookState.oldInstance = arrangedDeletedInstances;
}
}
}); });
Self.observe('after delete', async function(ctx) {
const loopBackContext = LoopBackContext.getCurrentContext();
if (ctx.hookState.oldInstance)
logDeletedInstances(ctx, loopBackContext);
});
async function logDeletedInstances(ctx, loopBackContext) {
const appModels = ctx.Model.app.models;
const definition = ctx.Model.definition;
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
ctx.hookState.oldInstance.forEach(async instance => {
let userFk;
if (loopBackContext)
userFk = loopBackContext.active.accessToken.userId;
let changedModelValue = definition.settings.log.changedModelValue;
let logRecord = {
originFk: instance.originFk,
userFk: userFk,
action: 'delete',
changedModel: definition.name,
changedModelId: instance.id,
changedModelValue: instance[changedModelValue],
oldInstance: instance,
newInstance: {}
};
delete instance.originFk;
let logModel = definition.settings.log.model;
await appModels[logModel].create(logRecord, options);
});
}
// Get log values from a foreign key
async function fkToValue(instance, ctx) {
const appModels = ctx.Model.app.models;
const relations = ctx.Model.relations;
let options = {};
// Check for transactions
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
const instanceCopy = JSON.parse(JSON.stringify(instance));
const result = {};
for (const key in instanceCopy) {
let value = instanceCopy[key];
if (value instanceof Object)
continue;
if (value === undefined) continue;
if (value) {
for (let relationName in relations) {
const relation = relations[relationName];
if (relation.keyFrom == key && key != 'id') {
const model = relation.modelTo;
const modelName = relation.modelTo.modelName;
const properties = model && model.definition.properties;
const settings = model && model.definition.settings;
const recordSet = await appModels[modelName].findById(value, null, options);
const hasShowField = settings.log && settings.log.showField;
let showField = hasShowField && recordSet
&& recordSet[settings.log.showField];
if (!showField) {
const showFieldNames = [
'name',
'description',
'code',
'nickname'
];
for (field of showFieldNames) {
const propField = properties && properties[field];
const recordField = recordSet && recordSet[field];
if (propField && recordField) {
showField = field;
break;
}
}
}
if (showField && recordSet && recordSet[showField]) {
value = recordSet[showField];
break;
}
value = recordSet && recordSet.id || value;
break;
}
}
}
result[key] = value;
}
return result;
}
async function logInModel(ctx, loopBackContext) {
const appModels = ctx.Model.app.models;
const definition = ctx.Model.definition;
const defSettings = ctx.Model.definition.settings;
const relations = ctx.Model.relations;
const options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
let primaryKey;
for (let property in definition.properties) {
if (definition.properties[property].id) {
primaryKey = property;
break;
}
}
if (!primaryKey) throw new Error('Primary key not found');
let originId;
// RELATIONS LOG
let changedModelId;
if (ctx.instance && !defSettings.log.relation) {
originId = ctx.instance.id;
changedModelId = ctx.instance.id;
} else if (defSettings.log.relation) {
primaryKey = relations[defSettings.log.relation].keyFrom;
if (ctx.where && ctx.where[primaryKey])
originId = ctx.where[primaryKey];
else if (ctx.instance) {
originId = ctx.instance[primaryKey];
changedModelId = ctx.instance.id;
}
} else {
originId = ctx.currentInstance.id;
changedModelId = ctx.currentInstance.id;
}
// Sets the changedModelValue to save and the instances changed in case its an updateAll
let showField = defSettings.log.showField;
let where;
if (showField && (!ctx.instance || !ctx.instance[showField]) && ctx.where) {
changedModelId = [];
where = [];
let changedInstances = await appModels[definition.name].find({
where: ctx.where,
fields: ['id', showField, primaryKey]
}, options);
changedInstances.forEach(element => {
where.push(element[showField]);
changedModelId.push(element.id);
originId = element[primaryKey];
});
} else if (ctx.hookState.oldInstance)
where = ctx.instance[showField];
// Set oldInstance, newInstance, userFk and action
let oldInstance = {};
if (ctx.hookState.oldInstance)
Object.assign(oldInstance, ctx.hookState.oldInstance);
let newInstance = {};
if (ctx.hookState.newInstance)
Object.assign(newInstance, ctx.hookState.newInstance);
let userFk;
if (loopBackContext)
userFk = loopBackContext.active.accessToken.userId;
let action = setActionType(ctx);
removeUnloggable(definition, oldInstance);
removeUnloggable(definition, newInstance);
oldInstance = await fkToValue(oldInstance, ctx);
newInstance = await fkToValue(newInstance, ctx);
// Prevent log with no new changes
const hasNewChanges = Object.keys(newInstance).length;
if (!hasNewChanges) return;
let logRecord = {
originFk: originId,
userFk: userFk,
action: action,
changedModel: definition.name,
changedModelId: changedModelId, // Model property with an different data type will throw a NaN error
changedModelValue: where,
oldInstance: oldInstance,
newInstance: newInstance
};
let logsToSave = setLogsToSave(where, changedModelId, logRecord, ctx);
let logModel = defSettings.log.model;
await appModels[logModel].create(logsToSave, options);
}
/**
* Removes unwanted properties
* @param {*} definition Model definition
* @param {*} properties Modified object properties
*/
function removeUnloggable(definition, properties) {
const objectCopy = Object.assign({}, properties);
const propList = Object.keys(objectCopy);
const propDefs = new Map();
for (let property in definition.properties) {
const propertyDef = definition.properties[property];
propDefs.set(property, propertyDef);
}
for (let property of propList) {
const propertyDef = propDefs.get(property);
const firstChar = property.substring(0, 1);
const isPrivate = firstChar == '$';
if (isPrivate || !propertyDef)
delete properties[property];
if (!propertyDef) continue;
if (propertyDef.log === false || isPrivate)
delete properties[property];
else if (propertyDef.logValue === false)
properties[property] = null;
}
}
// this function retuns all the instances changed in case this is an updateAll
function setLogsToSave(changedInstances, changedInstancesIds, logRecord, ctx) {
let promises = [];
if (changedInstances && typeof changedInstances == 'object') {
for (let i = 0; i < changedInstances.length; i++) {
logRecord.changedModelId = changedInstancesIds[i];
logRecord.changedModelValue = changedInstances[i];
if (ctx.oldInstances)
logRecord.oldInstance = ctx.oldInstances[i];
promises.push(JSON.parse(JSON.stringify(logRecord)));
}
} else
return logRecord;
return promises;
}
function setActionType(ctx) {
let oldInstance = ctx.hookState.oldInstance;
let newInstance = ctx.hookState.newInstance;
if (oldInstance && newInstance)
return 'update';
else if (!oldInstance && newInstance)
return 'insert';
return 'delete';
}
}; };

View File

@ -7,6 +7,7 @@ module.exports = function(Self) {
require('../methods/vn-model/getSetValues')(Self); require('../methods/vn-model/getSetValues')(Self);
require('../methods/vn-model/getEnumValues')(Self); require('../methods/vn-model/getEnumValues')(Self);
require('../methods/vn-model/printService')(Self);
Object.assign(Self, { Object.assign(Self, {
setup() { setup() {

View File

@ -270,5 +270,6 @@
"Warehouse inventory not set": "El almacén inventario no está establecido", "Warehouse inventory not set": "El almacén inventario no está establecido",
"This locker has already been assigned": "Esta taquilla ya ha sido asignada", "This locker has already been assigned": "Esta taquilla ya ha sido asignada",
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}", "Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}",
"Not exist this branch": "La rama no existe" "Not exist this branch": "La rama no existe",
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado"
} }

View File

@ -2,8 +2,41 @@ const mysql = require('mysql');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const MySQL = require('loopback-connector-mysql').MySQL; const MySQL = require('loopback-connector-mysql').MySQL;
const EnumFactory = require('loopback-connector-mysql').EnumFactory; const EnumFactory = require('loopback-connector-mysql').EnumFactory;
const Transaction = require('loopback-connector').Transaction;
const fs = require('fs'); const fs = require('fs');
const limitSet = new Set([
'save',
'updateOrCreate',
'replaceOrCreate',
'replaceById',
'update'
]);
const opOpts = {
update: [
'update',
'replaceById',
// |insert
'save',
'updateOrCreate',
'replaceOrCreate'
],
delete: [
'destroy',
'destroyAll'
],
insert: [
'create'
]
};
const opMap = new Map();
for (const op in opOpts) {
for (const met of opOpts[op])
opMap.set(met, op);
}
class VnMySQL extends MySQL { class VnMySQL extends MySQL {
/** /**
* Promisified version of execute(). * Promisified version of execute().
@ -219,6 +252,277 @@ class VnMySQL extends MySQL {
this.makePagination(filter) this.makePagination(filter)
]); ]);
} }
create(model, data, opts, cb) {
const ctx = {data};
this.invokeMethod('create',
arguments, model, ctx, opts, cb);
}
createAll(model, data, opts, cb) {
const ctx = {data};
this.invokeMethod('createAll',
arguments, model, ctx, opts, cb);
}
save(model, data, opts, cb) {
const ctx = {data};
this.invokeMethod('save',
arguments, model, ctx, opts, cb);
}
updateOrCreate(model, data, opts, cb) {
const ctx = {data};
this.invokeMethod('updateOrCreate',
arguments, model, ctx, opts, cb);
}
replaceOrCreate(model, data, opts, cb) {
const ctx = {data};
this.invokeMethod('replaceOrCreate',
arguments, model, ctx, opts, cb);
}
destroyAll(model, where, opts, cb) {
const ctx = {where};
this.invokeMethod('destroyAll',
arguments, model, ctx, opts, cb);
}
update(model, where, data, opts, cb) {
const ctx = {where, data};
this.invokeMethod('update',
arguments, model, ctx, opts, cb);
}
replaceById(model, id, data, opts, cb) {
const ctx = {id, data};
this.invokeMethod('replaceById',
arguments, model, ctx, opts, cb);
}
isLoggable(model) {
const Model = this.getModelDefinition(model).model;
const settings = Model.definition.settings;
return settings.base && settings.base === 'Loggable';
}
invokeMethod(method, args, model, ctx, opts, cb) {
if (!this.isLoggable(model))
return super[method].apply(this, args);
this.invokeMethodP(method, [...args], model, ctx, opts)
.then(res => cb(...res), cb);
}
async invokeMethodP(method, args, model, ctx, opts) {
const Model = this.getModelDefinition(model).model;
const settings = Model.definition.settings;
let tx;
if (!opts.transaction) {
tx = await Transaction.begin(this, {});
opts = Object.assign({transaction: tx, httpCtx: opts.httpCtx}, opts);
}
try {
// Fetch old values (update|delete) or login
let where, id, data, idName, limit, op, oldInstances, newInstances;
const hasGrabUser = settings.log && settings.log.grabUser;
if(hasGrabUser){
const userId = opts.httpCtx && opts.httpCtx.active.accessToken.userId;
const user = await Model.app.models.Account.findById(userId, {fields: ['name']}, opts);
await this.executeP(`CALL account.myUser_loginWithName(?)`, [user.name], opts);
}
else {
where = ctx.where;
id = ctx.id;
data = ctx.data;
idName = this.idName(model);
limit = limitSet.has(method);
op = opMap.get(method);
if (!where) {
if (id) where = {[idName]: id};
else where = {[idName]: data[idName]};
}
// Fetch old values
switch (op) {
case 'update':
case 'delete':
// Single entity operation
const stmt = this.buildSelectStmt(op, data, idName, model, where, limit);
stmt.merge(`FOR UPDATE`);
oldInstances = await this.executeStmt(stmt, opts);
}
}
const res = await new Promise(resolve => {
const fnArgs = args.slice(0, -2);
fnArgs.push(opts, (...args) => resolve(args));
super[method].apply(this, fnArgs);
});
if(hasGrabUser)
await this.executeP(`CALL account.myUser_logout()`, null, opts);
else {
// Fetch new values
const ids = [];
switch (op) {
case 'insert':
case 'update': {
switch (method) {
case 'createAll':
for (const row of res[1])
ids.push(row[idName]);
break;
case 'create':
ids.push(res[1]);
break;
case 'update':
if (data[idName] != null)
ids.push(data[idName]);
break;
}
const newWhere = ids.length ? {[idName]: ids} : where;
const stmt = this.buildSelectStmt(op, data, idName, model, newWhere, limit);
newInstances = await this.executeStmt(stmt, opts);
}
}
await this.createLogRecord(oldInstances, newInstances, model, opts);
}
if (tx) await tx.commit();
return res;
} catch (err) {
if (tx) tx.rollback();
throw err;
}
}
buildSelectStmt(op, data, idName, model, where, limit) {
const Model = this.getModelDefinition(model).model;
const properties = Object.keys(Model.definition.properties);
const fields = data ? Object.keys(data) : [];
if (op == 'delete')
properties.forEach(property => fields.push(property));
else {
const log = Model.definition.settings.log;
fields.push(idName);
if (log.relation) fields.push(Model.relations[log.relation].keyFrom);
if (log.showField) fields.push(log.showField);
else {
const showFieldNames = ['name', 'description', 'code', 'nickname'];
for (const field of showFieldNames) {
if (properties.includes(field)) {
log.showField = field;
fields.push(field);
break;
}
}
}
}
const stmt = new ParameterizedSQL(
'SELECT ' +
this.buildColumnNames(model, {fields}) +
' FROM ' +
this.tableEscaped(model)
);
stmt.merge(this.buildWhere(model, where));
if (limit) stmt.merge(`LIMIT 1`);
return stmt;
}
async createLogRecord(oldInstances, newInstances, model, opts) {
function setActionType() {
if (oldInstances && newInstances)
return 'update';
else if (!oldInstances && newInstances)
return 'insert';
return 'delete';
}
const action = setActionType();
if (!newInstances && action != 'delete') return;
const Model = this.getModelDefinition(model).model;
const models = Model.app.models;
const definition = Model.definition;
const log = definition.settings.log;
const primaryKey = this.idName(model);
const originRelation = log.relation;
const originFkField = originRelation
? Model.relations[originRelation].keyFrom
: primaryKey;
// Prevent adding logs when deleting a principal entity (Client, Zone...)
if (action == 'delete' && !originRelation) return;
function map(instances) {
const map = new Map();
if (!instances) return;
for (const instance of instances)
map.set(instance[primaryKey], instance);
return map;
}
const changedModel = definition.name;
const userFk = opts.httpCtx && opts.httpCtx.active.accessToken.userId;
const oldMap = map(oldInstances);
const newMap = map(newInstances);
const ids = (oldMap || newMap).keys();
const logEntries = [];
function insertValuesLogEntry(logEntry, instance) {
logEntry.originFk = instance[originFkField];
logEntry.changedModelId = instance[primaryKey];
if (log.showField) logEntry.changedModelValue = instance[log.showField];
}
for (const id of ids) {
const oldI = oldMap && oldMap.get(id);
const newI = newMap && newMap.get(id);
const logEntry = {
action,
userFk,
changedModel,
};
if (newI) {
insertValuesLogEntry(logEntry, newI);
// Delete unchanged properties
if (oldI) {
Object.keys(oldI).forEach(prop => {
const hasChanges = oldI[prop] instanceof Date ?
oldI[prop]?.getTime() != newI[prop]?.getTime() :
oldI[prop] != newI[prop];
if (!hasChanges) {
delete oldI[prop];
delete newI[prop];
}
});
}
} else
insertValuesLogEntry(logEntry, oldI);
logEntry.oldInstance = oldI;
logEntry.newInstance = newI;
logEntries.push(logEntry);
}
await models[log.model].create(logEntries, opts);
}
} }
exports.VnMySQL = VnMySQL; exports.VnMySQL = VnMySQL;

View File

@ -91,7 +91,11 @@ exports.getChanges = (original, changes) => {
const isPrivate = firstChar == '$'; const isPrivate = firstChar == '$';
if (isPrivate) return; if (isPrivate) return;
if (changes[property] != original[property]) { const hasChanges = original[property] instanceof Date ?
changes[property]?.getTime() != original[property]?.getTime() :
changes[property] != original[property];
if (hasChanges) {
newChanges[property] = changes[property]; newChanges[property] = changes[property];
if (original[property] != undefined) if (original[property] != undefined)

View File

@ -1,5 +1,3 @@
const { Report } = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('claimPickupPdf', { Self.remoteMethodCtx('claimPickupPdf', {
description: 'Returns the claim pickup order pdf', description: 'Returns the claim pickup order pdf',
@ -39,17 +37,5 @@ module.exports = Self => {
} }
}); });
Self.claimPickupPdf = async(ctx, id) => { Self.claimPickupPdf = (ctx, id) => Self.printReport(ctx, id, 'claim-pickup-order');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('claim-pickup-order', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -86,7 +86,20 @@
icon="icon-ticket"> icon="icon-ticket">
</vn-quick-link> </vn-quick-link>
</div> </div>
<div ng-transclude="btnThree"></div> <div ng-transclude="btnThree">
<vn-quick-link
tooltip="Sale tracking"
state="['ticket.card.saleTracking', {id: $ctrl.claim.ticketFk}]"
icon="assignment">
</vn-quick-link>
</div>
<div ng-transclude="btnFour">
<vn-quick-link
tooltip="Ticket tracking"
state="['ticket.card.tracking.index', {id: $ctrl.claim.ticketFk}]"
icon="icon-eye">
</vn-quick-link>
</div>
</div> </div>
</slot-body> </slot-body>
</vn-descriptor-content> </vn-descriptor-content>

View File

@ -18,3 +18,5 @@ Claim deleted!: Reclamación eliminada!
claim: reclamación claim: reclamación
Photos: Fotos Photos: Fotos
Go to the claim: Ir a la reclamación Go to the claim: Ir a la reclamación
Sale tracking: Líneas preparadas
Ticket tracking: Estados del ticket

View File

@ -51,19 +51,5 @@ module.exports = Self => {
} }
}); });
Self.campaignMetricsEmail = async ctx => { Self.campaignMetricsEmail = ctx => Self.sendTemplate(ctx, 'campaign-metrics');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('campaign-metrics', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('campaignMetricsPdf', { Self.remoteMethodCtx('campaignMetricsPdf', {
description: 'Returns the campaign metrics pdf', description: 'Returns the campaign metrics pdf',
@ -50,17 +48,5 @@ module.exports = Self => {
} }
}); });
Self.campaignMetricsPdf = async(ctx, id) => { Self.campaignMetricsPdf = (ctx, id) => Self.printReport(ctx, id, 'campaign-metrics');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('campaign-metrics', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -46,19 +46,5 @@ module.exports = Self => {
} }
}); });
Self.clientDebtStatementEmail = async ctx => { Self.clientDebtStatementEmail = ctx => Self.sendTemplate(ctx, 'client-debt-statement');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('client-debt-statement', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('clientDebtStatementHtml', { Self.remoteMethodCtx('clientDebtStatementHtml', {
description: 'Returns the client debt statement email preview', description: 'Returns the client debt statement email preview',
@ -45,21 +43,5 @@ module.exports = Self => {
} }
}); });
Self.clientDebtStatementHtml = async(ctx, id) => { Self.clientDebtStatementHtml = (ctx, id) => Self.printEmail(ctx, id, 'client-debt-statement');
const {accessToken} = ctx.req;
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
params.access_token = accessToken.id;
const report = new Email('client-debt-statement', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('clientDebtStatementPdf', { Self.remoteMethodCtx('clientDebtStatementPdf', {
description: 'Returns the client debt statement pdf', description: 'Returns the client debt statement pdf',
@ -45,17 +43,5 @@ module.exports = Self => {
} }
}); });
Self.clientDebtStatementPdf = async(ctx, id) => { Self.clientDebtStatementPdf = (ctx, id) => Self.printReport(ctx, id, 'client-debt-statement');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('client-debt-statement', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('clientWelcomeEmail', { Self.remoteMethodCtx('clientWelcomeEmail', {
description: 'Sends the client welcome email with an attached PDF', description: 'Sends the client welcome email with an attached PDF',
@ -41,19 +39,5 @@ module.exports = Self => {
} }
}); });
Self.clientWelcomeEmail = async ctx => { Self.clientWelcomeEmail = ctx => Self.sendTemplate(ctx, 'client-welcome');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('client-welcome', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('clientWelcomeHtml', { Self.remoteMethodCtx('clientWelcomeHtml', {
description: 'Returns the client welcome email preview', description: 'Returns the client welcome email preview',
@ -40,19 +38,5 @@ module.exports = Self => {
} }
}); });
Self.clientWelcomeHtml = async(ctx, id) => { Self.clientWelcomeHtml = (ctx, id) => Self.printEmail(ctx, id, 'client-welcome');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
const report = new Email('client-welcome', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('clientCreditEmail', { Self.remoteMethodCtx('clientCreditEmail', {
description: 'Sends the credit request email with an attached PDF', description: 'Sends the credit request email with an attached PDF',
@ -10,7 +8,7 @@ module.exports = Self => {
type: 'number', type: 'number',
required: true, required: true,
description: 'The client id', description: 'The client id',
http: {source: 'path'} http: {source: 'path'},
}, },
{ {
arg: 'recipient', arg: 'recipient',
@ -22,38 +20,25 @@ module.exports = Self => {
arg: 'replyTo', arg: 'replyTo',
type: 'string', type: 'string',
description: 'The sender email to reply to', description: 'The sender email to reply to',
required: false required: false,
}, },
{ {
arg: 'recipientId', arg: 'recipientId',
type: 'number', type: 'number',
description: 'The recipient id to send to the recipient preferred language', description:
required: false 'The recipient id to send to the recipient preferred language',
} required: false,
},
], ],
returns: { returns: {
type: ['object'], type: ['object'],
root: true root: true,
}, },
http: { http: {
path: '/:id/credit-request-email', path: '/:id/credit-request-email',
verb: 'POST' verb: 'POST',
} },
}); });
Self.clientCreditEmail = async ctx => { Self.clientCreditEmail = ctx => Self.sendTemplate(ctx, 'credit-request');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('credit-request', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('creditRequestHtml', { Self.remoteMethodCtx('creditRequestHtml', {
description: 'Returns the credit request email preview', description: 'Returns the credit request email preview',
@ -40,21 +38,5 @@ module.exports = Self => {
} }
}); });
Self.creditRequestHtml = async(ctx, id) => { Self.creditRequestHtml = (ctx, id) => Self.printEmail(ctx, id, 'credit-request');
const {accessToken} = ctx.req;
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
params.access_token = accessToken.id;
const report = new Email('credit-request', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('creditRequestPdf', { Self.remoteMethodCtx('creditRequestPdf', {
description: 'Returns the credit request pdf', description: 'Returns the credit request pdf',
@ -40,17 +38,5 @@ module.exports = Self => {
} }
}); });
Self.creditRequestPdf = async(ctx, id) => { Self.creditRequestPdf = (ctx, id) => Self.printReport(ctx, id, 'credit-request');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('credit-request', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -80,7 +80,7 @@ module.exports = function(Self) {
const data = await Self.rawSql(query, [id, date], myOptions); const data = await Self.rawSql(query, [id, date], myOptions);
client.debt = data[0].debt; client.debt = data[0].debt;
client.unpaid = await Self.app.models.ClientUnpaid.findOne({id}, myOptions); client.unpaid = await Self.app.models.ClientUnpaid.findById(id, null, myOptions);
return client; return client;
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('incotermsAuthorizationEmail', { Self.remoteMethodCtx('incotermsAuthorizationEmail', {
description: 'Sends the incoterms authorization email with an attached PDF', description: 'Sends the incoterms authorization email with an attached PDF',
@ -47,19 +45,5 @@ module.exports = Self => {
} }
}); });
Self.incotermsAuthorizationEmail = async ctx => { Self.incotermsAuthorizationEmail = ctx => Self.sendTemplate(ctx, 'incoterms-authorization');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('incoterms-authorization', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('incotermsAuthorizationHtml', { Self.remoteMethodCtx('incotermsAuthorizationHtml', {
description: 'Returns the incoterms authorization email preview', description: 'Returns the incoterms authorization email preview',
@ -46,21 +44,5 @@ module.exports = Self => {
} }
}); });
Self.incotermsAuthorizationHtml = async(ctx, id) => { Self.incotermsAuthorizationHtml = (ctx, id) => Self.printEmail(ctx, id, 'incoterms-authorization');
const {accessToken} = ctx.req;
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
params.access_token = accessToken.id;
const report = new Email('incoterms-authorization', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('incotermsAuthorizationPdf', { Self.remoteMethodCtx('incotermsAuthorizationPdf', {
description: 'Returns the incoterms authorization pdf', description: 'Returns the incoterms authorization pdf',
@ -46,17 +44,5 @@ module.exports = Self => {
} }
}); });
Self.incotermsAuthorizationPdf = async(ctx, id) => { Self.incotermsAuthorizationPdf = (ctx, id) => Self.printReport(ctx, id, 'incoterms-authorization');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('incoterms-authorization', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('letterDebtorNdEmail', { Self.remoteMethodCtx('letterDebtorNdEmail', {
description: 'Sends the second debtor letter email with an attached PDF', description: 'Sends the second debtor letter email with an attached PDF',
@ -47,19 +45,5 @@ module.exports = Self => {
} }
}); });
Self.letterDebtorNdEmail = async ctx => { Self.letterDebtorNdEmail = ctx => Self.sendTemplate(ctx, 'letter-debtor-nd');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('letter-debtor-nd', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('letterDebtorNdHtml', { Self.remoteMethodCtx('letterDebtorNdHtml', {
description: 'Returns the second letter debtor email preview', description: 'Returns the second letter debtor email preview',
@ -46,21 +44,5 @@ module.exports = Self => {
} }
}); });
Self.letterDebtorNdHtml = async(ctx, id) => { Self.letterDebtorNdHtml = (ctx, id) => Self.printEmail(ctx, id, 'letter-debtor-nd');
const {accessToken} = ctx.req;
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
params.access_token = accessToken.id;
const report = new Email('letter-debtor-nd', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('letterDebtorPdf', { Self.remoteMethodCtx('letterDebtorPdf', {
description: 'Returns the letter debtor pdf', description: 'Returns the letter debtor pdf',
@ -46,17 +44,5 @@ module.exports = Self => {
} }
}); });
Self.letterDebtorPdf = async(ctx, id) => { Self.letterDebtorPdf = (ctx, id) => Self.printReport(ctx, id, 'letter-debtor');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('letter-debtor', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('letterDebtorStEmail', { Self.remoteMethodCtx('letterDebtorStEmail', {
description: 'Sends the printer setup email with an attached PDF', description: 'Sends the printer setup email with an attached PDF',
@ -47,19 +45,5 @@ module.exports = Self => {
} }
}); });
Self.letterDebtorStEmail = async ctx => { Self.letterDebtorStEmail = ctx => Self.sendTemplate(ctx, 'letter-debtor-st');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('letter-debtor-st', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('letterDebtorStHtml', { Self.remoteMethodCtx('letterDebtorStHtml', {
description: 'Returns the letter debtor email preview', description: 'Returns the letter debtor email preview',
@ -46,21 +44,5 @@ module.exports = Self => {
} }
}); });
Self.letterDebtorStHtml = async(ctx, id) => { Self.letterDebtorStHtml = (ctx, id) => Self.printEmail(ctx, id, 'letter-debtor-st');
const {accessToken} = ctx.req;
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
params.access_token = accessToken.id;
const report = new Email('letter-debtor-st', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('printerSetupEmail', { Self.remoteMethodCtx('printerSetupEmail', {
description: 'Sends the printer setup email with an attached PDF', description: 'Sends the printer setup email with an attached PDF',
@ -41,19 +39,5 @@ module.exports = Self => {
} }
}); });
Self.printerSetupEmail = async ctx => { Self.printerSetupEmail = ctx => Self.sendTemplate(ctx, 'printer-setup');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('printer-setup', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('printerSetupHtml', { Self.remoteMethodCtx('printerSetupHtml', {
description: 'Returns the printer setup email preview', description: 'Returns the printer setup email preview',
@ -40,19 +38,5 @@ module.exports = Self => {
} }
}); });
Self.printerSetupHtml = async(ctx, id) => { Self.printerSetupHtml = (ctx, id) => Self.printEmail(ctx, id, 'printer-setup');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
const report = new Email('printer-setup', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('sepaCoreEmail', { Self.remoteMethodCtx('sepaCoreEmail', {
description: 'Sends the campaign metrics email with an attached PDF', description: 'Sends the campaign metrics email with an attached PDF',
@ -47,19 +45,5 @@ module.exports = Self => {
} }
}); });
Self.sepaCoreEmail = async ctx => { Self.sepaCoreEmail = ctx => Self.sendTemplate(ctx, 'sepa-core');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('sepa-core', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const { Report } = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('balanceCompensationPdf', { Self.remoteMethodCtx('balanceCompensationPdf', {
description: 'Returns the the debit balances compensation pdf', description: 'Returns the the debit balances compensation pdf',
@ -10,7 +8,7 @@ module.exports = Self => {
type: 'number', type: 'number',
required: true, required: true,
description: 'The receipt id', description: 'The receipt id',
http: { source: 'path' } http: {source: 'path'}
} }
], ],
returns: [ returns: [
@ -34,17 +32,5 @@ module.exports = Self => {
} }
}); });
Self.balanceCompensationPdf = async(ctx, id) => { Self.balanceCompensationPdf = (ctx, id) => Self.printReport(ctx, id, 'balance-compensation');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('balance-compensation', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('receiptPdf', { Self.remoteMethodCtx('receiptPdf', {
description: 'Returns the receipt pdf', description: 'Returns the receipt pdf',
@ -39,17 +37,5 @@ module.exports = Self => {
} }
}); });
Self.receiptPdf = async(ctx, id) => { Self.receiptPdf = (ctx, id) => Self.printReport(ctx, id, 'receipt');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('receipt', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -279,6 +279,18 @@ module.exports = Self => {
// Credit changes // Credit changes
if (changes.credit !== undefined) if (changes.credit !== undefined)
await Self.changeCredit(ctx, finalState, changes); await Self.changeCredit(ctx, finalState, changes);
const oldInstance = {};
if (!ctx.isNewInstance) {
const newProps = Object.keys(changes);
Object.keys(orgData.__data).forEach(prop => {
if (newProps.includes(prop))
oldInstance[prop] = orgData[prop];
});
}
ctx.hookState.oldInstance = oldInstance;
ctx.hookState.newInstance = changes;
}); });
Self.observe('after save', async ctx => { Self.observe('after save', async ctx => {

View File

@ -62,6 +62,11 @@
"type": "belongsTo", "type": "belongsTo",
"model": "Bank", "model": "Bank",
"foreignKey": "bankFk" "foreignKey": "bankFk"
},
"supplier": {
"type": "belongsTo",
"model": "Supplier",
"foreignKey": "companyFk"
} }
} }
} }

View File

@ -70,11 +70,12 @@
icon="icon-no036" icon="icon-no036"
ng-if="$ctrl.client.isTaxDataChecked == false"> ng-if="$ctrl.client.isTaxDataChecked == false">
</vn-icon> </vn-icon>
<vn-icon <vn-icon-button
vn-tooltip="{{$ctrl.clientUnpaid()}}" vn-tooltip="{{$ctrl.clientUnpaid()}}"
icon="icon-clientUnpaid" icon="icon-clientUnpaid"
ui-sref="client.card.unpaid"
ng-if="$ctrl.client.unpaid"> ng-if="$ctrl.client.unpaid">
</vn-icon> </vn-icon-button>
</div> </div>
<div class="quicklinks"> <div class="quicklinks">
<div ng-transclude="btnOne"> <div ng-transclude="btnOne">

View File

@ -46,8 +46,9 @@ class Controller extends Descriptor {
} }
clientUnpaid() { clientUnpaid() {
return this.$t(`Unpaid Dated`, {dated: this.client.unpaid.dated}) + return this.$t(`Unpaid`) + '<br/>'
'<br/>' + this.$t(`Unpaid Amount`, {amount: this.client.unpaid.amount}); + this.$t(`Unpaid Dated`, {dated: this.client.unpaid.dated}) + '<br/>'
+ this.$t(`Unpaid Amount`, {amount: this.client.unpaid.amount});
} }
} }

View File

@ -9,7 +9,7 @@
</vn-watcher> </vn-watcher>
<form <form
name="form" name="form"
ng-submit="watcher.submit()" ng-submit="$ctrl.onSubmit()"
class="vn-w-md"> class="vn-w-md">
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-vertical> <vn-vertical>

View File

@ -6,9 +6,17 @@ export default class Controller extends Section {
if (hasData && !this.clientUnpaid.dated) if (hasData && !this.clientUnpaid.dated)
this.clientUnpaid.dated = Date.vnNew(); this.clientUnpaid.dated = Date.vnNew();
} }
onSubmit() {
this.$.watcher.submit()
.then(() => this.card.reload());
}
} }
ngModule.vnComponent('vnClientUnpaid', { ngModule.vnComponent('vnClientUnpaid', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller controller: Controller,
require: {
card: '^vnClientCard'
}
}); });

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('entryOrderPdf', { Self.remoteMethodCtx('entryOrderPdf', {
description: 'Returns the entry order pdf', description: 'Returns the entry order pdf',
@ -38,17 +36,5 @@ module.exports = Self => {
} }
}); });
Self.entryOrderPdf = async(ctx, id) => { Self.entryOrderPdf = (ctx, id) => Self.printReport(ctx, id, 'entry-order');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('entry-order', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('invoiceInEmail', { Self.remoteMethodCtx('invoiceInEmail', {
description: 'Sends the invoice in email with an attached PDF', description: 'Sends the invoice in email with an attached PDF',
@ -35,19 +33,5 @@ module.exports = Self => {
} }
}); });
Self.invoiceInEmail = async ctx => { Self.invoiceInEmail = ctx => Self.sendTemplate(ctx, 'invoiceIn');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('invoiceIn', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('invoiceInPdf', { Self.remoteMethodCtx('invoiceInPdf', {
description: 'Returns the invoiceIn pdf', description: 'Returns the invoiceIn pdf',
@ -34,17 +32,5 @@ module.exports = Self => {
} }
}); });
Self.invoiceInPdf = async(ctx, id) => { Self.invoiceInPdf = (ctx, id) => Self.printReport(ctx, id, 'invoiceIn');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('invoiceIn', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('exportationPdf', { Self.remoteMethodCtx('exportationPdf', {
description: 'Returns the exportation pdf', description: 'Returns the exportation pdf',
@ -39,17 +37,5 @@ module.exports = Self => {
} }
}); });
Self.exportationPdf = async(ctx, reference) => { Self.exportationPdf = (ctx, reference) => Self.printReport(ctx, reference, 'exportation');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('exportation', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${reference}.pdf"`];
};
}; };

View File

@ -0,0 +1,191 @@
const axios = require('axios');
const uuid = require('uuid');
const fs = require('fs/promises');
const { createWriteStream } = require('fs');
const path = require('path');
const gm = require('gm');
module.exports = Self => {
Self.remoteMethod('download', {
description: 'Processes the image download queue',
accessType: 'WRITE',
http: {
path: `/download`,
verb: 'POST',
},
});
Self.download = async () => {
const models = Self.app.models;
const tempContainer = await models.TempContainer.container(
'salix-image'
);
const tempPath = path.join(
tempContainer.client.root,
tempContainer.name
);
const maxAttempts = 3;
const collectionName = 'catalog';
const tx = await Self.beginTransaction({});
let tempFilePath;
let queueRow;
try {
const myOptions = { transaction: tx };
queueRow = await Self.findOne(
{
fields: ['id', 'itemFk', 'url', 'attempts'],
where: {
url: { neq: null },
attempts: {
lt: maxAttempts,
},
},
order: 'priority, attempts, updated',
},
myOptions
);
if (!queueRow) return;
const collection = await models.ImageCollection.findOne(
{
fields: [
'id',
'maxWidth',
'maxHeight',
'model',
'property',
],
where: { name: collectionName },
include: {
relation: 'sizes',
scope: {
fields: ['width', 'height', 'crop'],
},
},
},
myOptions
);
const fileName = `${uuid.v4()}.png`;
tempFilePath = path.join(tempPath, fileName);
// Insert image row
await models.Image.create(
{
name: fileName,
collectionFk: collectionName,
updated: Date.vnNow(),
},
myOptions
);
// Update item
const model = models[collection.model];
if (!model) throw new Error('No matching model found');
const item = await model.findById(queueRow.itemFk, null, myOptions);
if (item) {
await item.updateAttribute(
collection.property,
fileName,
myOptions
);
}
// Download remote image
const response = await axios.get(queueRow.url, {
responseType: 'stream',
});
const writeStream = createWriteStream(tempFilePath);
await new Promise((resolve, reject) => {
writeStream.on('open', () => response.data.pipe(writeStream));
writeStream.on('finish', () => resolve());
writeStream.on('error', error => reject(error));
});
// Resize
const container = await models.ImageContainer.container(
collectionName
);
const rootPath = container.client.root;
const collectionDir = path.join(rootPath, collectionName);
// To max size
const { maxWidth, maxHeight } = collection;
const fullSizePath = path.join(collectionDir, 'full');
const toFullSizePath = `${fullSizePath}/${fileName}`;
await fs.mkdir(fullSizePath, { recursive: true });
await new Promise((resolve, reject) => {
gm(tempFilePath)
.resize(maxWidth, maxHeight, '>')
.setFormat('png')
.write(toFullSizePath, function (err) {
if (err) reject(err);
if (!err) resolve();
});
});
// To collection sizes
for (const size of collection.sizes()) {
const { width, height } = size;
const sizePath = path.join(collectionDir, `${width}x${height}`);
const toSizePath = `${sizePath}/${fileName}`;
await fs.mkdir(sizePath, { recursive: true });
await new Promise((resolve, reject) => {
const gmInstance = gm(tempFilePath);
if (size.crop) {
gmInstance
.resize(width, height, '^')
.gravity('Center')
.crop(width, height);
}
if (!size.crop) gmInstance.resize(width, height, '>');
gmInstance
.setFormat('png')
.write(toSizePath, function (err) {
if (err) reject(err);
if (!err) resolve();
});
});
}
try {
await fs.unlink(tempFilePath);
} catch (error) { }
await queueRow.destroy(myOptions);
// Restart queue
Self.download();
await tx.commit();
} catch (error) {
await tx.rollback();
if (queueRow.attempts < maxAttempts) {
await queueRow.updateAttributes({
error: error,
attempts: queueRow.attempts + 1,
updated: Date.vnNew(),
});
}
try {
await fs.unlink(tempFilePath);
} catch (error) { }
Self.download();
}
};
};

View File

@ -62,7 +62,7 @@ module.exports = Self => {
writeStream.on('open', () => response.pipe(writeStream)); writeStream.on('open', () => response.pipe(writeStream));
writeStream.on('error', async error => writeStream.on('error', async error =>
await errorHandler(image.itemFk, error, filePath)); await errorHandler(image.itemFk, error, filePath));
writeStream.on('finish', writeStream.end()); writeStream.on('finish', () => writeStream.end());
writeStream.on('close', async function() { writeStream.on('close', async function() {
try { try {

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('labelPdf', { Self.remoteMethodCtx('labelPdf', {
description: 'Returns the item label pdf', description: 'Returns the item label pdf',
@ -56,17 +54,5 @@ module.exports = Self => {
} }
}); });
Self.labelPdf = async(ctx, id) => { Self.labelPdf = (ctx, id) => Self.printReport(ctx, id, 'item-label');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('item-label', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="item-${id}.pdf"`];
};
}; };

View File

@ -1,3 +1,3 @@
module.exports = Self => { module.exports = Self => {
require('../methods/item-image-queue/downloadImages')(Self); require('../methods/item-image-queue/download')(Self);
}; };

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('driverRouteEmail', { Self.remoteMethodCtx('driverRouteEmail', {
description: 'Sends the driver route email with an attached PDF', description: 'Sends the driver route email with an attached PDF',
@ -41,19 +39,5 @@ module.exports = Self => {
} }
}); });
Self.driverRouteEmail = async ctx => { Self.driverRouteEmail = ctx => Self.sendTemplate(ctx, 'driver-route');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('driver-route', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('driverRoutePdf', { Self.remoteMethodCtx('driverRoutePdf', {
description: 'Returns the driver route pdf', description: 'Returns the driver route pdf',
@ -39,17 +37,5 @@ module.exports = Self => {
} }
}); });
Self.driverRoutePdf = async(ctx, id) => { Self.driverRoutePdf = (ctx, id) => Self.printReport(ctx, id, 'driver-route');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('driver-route', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('campaignMetricsPdf', { Self.remoteMethodCtx('campaignMetricsPdf', {
description: 'Returns the campaign metrics pdf', description: 'Returns the campaign metrics pdf',
@ -49,17 +47,5 @@ module.exports = Self => {
} }
}); });
Self.campaignMetricsPdf = async(ctx, id) => { Self.campaignMetricsPdf = (ctx, id) => Self.printReport(ctx, id, 'supplier-campaign-metrics');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('supplier-campaign-metrics', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('collectionLabel', { Self.remoteMethodCtx('collectionLabel', {
description: 'Returns the collection label', description: 'Returns the collection label',
@ -39,17 +37,5 @@ module.exports = Self => {
} }
}); });
Self.collectionLabel = async(ctx, id) => { Self.collectionLabel = (ctx, id) => Self.printReport(ctx, id, 'collection-label');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('collection-label', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -165,18 +165,29 @@ module.exports = Self => {
'shipped', 'shipped',
'landed', 'landed',
'isDeleted', 'isDeleted',
'routeFk' 'routeFk',
'nickname'
], ],
include: [ include: [
{ {
relation: 'client', relation: 'client',
scope: { scope: {
fields: 'salesPersonFk' fields: 'salesPersonFk'
} },
}] include: [
{
relation: 'address',
scope: {
fields: 'nickname'
}
}
]
},
]
}, myOptions); }, myOptions);
args.routeFk = null; args.routeFk = null;
if (args.isWithoutNegatives === false) delete args.isWithoutNegatives;
const updatedTicket = Object.assign({}, args); const updatedTicket = Object.assign({}, args);
delete updatedTicket.ctx; delete updatedTicket.ctx;
delete updatedTicket.option; delete updatedTicket.option;
@ -224,37 +235,41 @@ module.exports = Self => {
} }
const changes = loggable.getChanges(originalTicket, updatedTicket); const changes = loggable.getChanges(originalTicket, updatedTicket);
const oldProperties = await loggable.translateValues(Self, changes.old); const hasChanges = Object.keys(changes.old).length > 0 || Object.keys(changes.new).length > 0;
const newProperties = await loggable.translateValues(Self, changes.new);
await models.TicketLog.create({ if (hasChanges) {
originFk: args.id, const oldProperties = await loggable.translateValues(Self, changes.old);
userFk: userId, const newProperties = await loggable.translateValues(Self, changes.new);
action: 'update',
changedModel: 'Ticket',
changedModelId: args.id,
oldInstance: oldProperties,
newInstance: newProperties
}, myOptions);
const salesPersonId = originalTicket.client().salesPersonFk; await models.TicketLog.create({
if (salesPersonId) { originFk: args.id,
const origin = ctx.req.headers.origin; userFk: userId,
action: 'update',
changedModel: 'Ticket',
changedModelId: args.id,
oldInstance: oldProperties,
newInstance: newProperties
}, myOptions);
let changesMade = ''; const salesPersonId = originalTicket.client().salesPersonFk;
for (let change in newProperties) { if (salesPersonId) {
let value = newProperties[change]; const origin = ctx.req.headers.origin;
let oldValue = oldProperties[change];
changesMade += `\r\n~${$t(change)}: ${oldValue}~ ➔ *${$t(change)}: ${value}*`; let changesMade = '';
for (let change in newProperties) {
let value = newProperties[change];
let oldValue = oldProperties[change];
changesMade += `\r\n~${$t(change)}: ${oldValue}~ ➔ *${$t(change)}: ${value}*`;
}
const message = $t('Changed this data from the ticket', {
ticketId: args.id,
ticketUrl: `${origin}/#!/ticket/${args.id}/sale`,
changes: changesMade
});
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
} }
const message = $t('Changed this data from the ticket', {
ticketId: args.id,
ticketUrl: `${origin}/#!/ticket/${args.id}/sale`,
changes: changesMade
});
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
} }
res.id = args.id; res.id = args.id;

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('deliveryNoteEmail', { Self.remoteMethodCtx('deliveryNoteEmail', {
description: 'Sends the delivery note email with an attached PDF', description: 'Sends the delivery note email with an attached PDF',
@ -47,19 +45,5 @@ module.exports = Self => {
} }
}); });
Self.deliveryNoteEmail = async ctx => { Self.deliveryNoteEmail = ctx => Self.sendTemplate(ctx, 'delivery-note');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('delivery-note', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('deliveryNotePdf', { Self.remoteMethodCtx('deliveryNotePdf', {
description: 'Returns the delivery note pdf', description: 'Returns the delivery note pdf',
@ -46,17 +44,5 @@ module.exports = Self => {
} }
}); });
Self.deliveryNotePdf = async(ctx, id) => { Self.deliveryNotePdf = (ctx, id) => Self.printReport(ctx, id, 'delivery-note');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('delivery-note', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('expeditionPalletLabel', { Self.remoteMethodCtx('expeditionPalletLabel', {
description: 'Returns the expedition pallet label', description: 'Returns the expedition pallet label',
@ -39,17 +37,5 @@ module.exports = Self => {
} }
}); });
Self.expeditionPalletLabel = async(ctx, id) => { Self.expeditionPalletLabel = (ctx, id) => Self.printReport(ctx, id, 'expedition-pallet-label');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('expedition-pallet-label', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
}; };

View File

@ -0,0 +1,132 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('saveSign', {
description: 'Save sign',
accessType: 'WRITE',
accepts:
[
{
arg: 'tickets',
type: ['number'],
required: true,
description: 'The tickets'
},
{
arg: 'location',
type: 'object',
description: 'The employee location the moment the sign is saved'
},
{
arg: 'signedTime',
type: 'date',
description: 'The signed time'
}
],
http: {
path: `/saveSign`,
verb: 'POST'
}
});
Self.saveSign = async(ctx, options) => {
const args = Object.assign({}, ctx.args);
const models = Self.app.models;
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
async function setLocation(ticketId) {
await models.Delivery.create({
ticketFk: ticketId,
longitude: args.location.Longitude,
latitude: args.location.Latitude,
dated: args.signedTime || new Date()
}, myOptions);
}
async function gestDocExists(ticketId) {
const ticketDms = await models.TicketDms.findOne({
where: {ticketFk: ticketId},
fields: ['dmsFk']
}, myOptions);
if (!ticketDms) return false;
const ticket = await models.Ticket.findById(ticketId, {fields: ['isSigned']}, myOptions);
if (ticket.isSigned == true)
return true;
else
await models.Dms.destroyAll({where: {reference: ticketId}}, myOptions);
return false;
}
async function createGestDoc(id) {
const ticket = await models.Ticket.findById(id,
{include: [
{
relation: 'warehouse',
scope: {
fields: ['id']
}
}, {
relation: 'client',
scope: {
fields: ['name']
}
}, {
relation: 'route',
scope: {
fields: ['id']
}
}
]
}, myOptions);
const dmsType = await models.DmsType.findOne({where: {code: 'Ticket'}, fields: ['id']}, myOptions);
const ctxUploadFile = Object.assign({}, ctx);
ctxUploadFile.args = {
warehouseId: ticket.warehouseFk,
companyId: ticket.companyFk,
dmsTypeId: dmsType.id,
reference: id,
description: `Ticket ${id} Cliente ${ticket.client().name} Ruta ${ticket.route().id}`,
hasFile: true
};
await models.Ticket.uploadFile(ctxUploadFile, id, myOptions);
}
try {
for (let i = 0; i < args.tickets.length; i++) {
const ticketState = await models.TicketState.findOne(
{where: {ticketFk: args.tickets[i]},
fields: ['alertLevel']
}, myOptions);
const packedAlertLevel = await models.AlertLevel.findOne({where: {code: 'PACKED'},
fields: ['id']
}, myOptions);
if (ticketState.alertLevel < packedAlertLevel.id)
throw new UserError('This ticket cannot be signed because it has not been boxed');
else if (!await gestDocExists(args.tickets[i])) {
if (args.location) setLocation(args.tickets[i]);
await createGestDoc(args.tickets[i]);
await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [args.tickets[i], 'DELIVERED'], myOptions);
}
}
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -2,13 +2,13 @@ const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('ticket merge()', () => { describe('ticket merge()', () => {
const tickets = [{ const tickets = {
originId: 13, originId: 13,
destinationId: 12, destinationId: 12,
originShipped: Date.vnNew(), originShipped: Date.vnNew(),
destinationShipped: Date.vnNew(), destinationShipped: Date.vnNew(),
workerFk: 1 workerFk: 1
}]; };
const activeCtx = { const activeCtx = {
accessToken: {userId: 9}, accessToken: {userId: 9},
@ -37,14 +37,14 @@ describe('ticket merge()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const chatNotificationBeforeMerge = await models.Chat.find(); const chatNotificationBeforeMerge = await models.Chat.find();
await models.Ticket.merge(ctx, tickets, options); await models.Ticket.merge(ctx, [tickets], options);
const createdTicketLog = await models.TicketLog.find({where: {originFk: tickets[0].originId}}, options); const createdTicketLog = await models.TicketLog.find({where: {originFk: tickets.originId}}, options);
const deletedTicket = await models.Ticket.findOne({where: {id: tickets[0].originId}}, options); const deletedTicket = await models.Ticket.findOne({where: {id: tickets.originId}}, options);
const salesTicketFuture = await models.Sale.find({where: {ticketFk: tickets[0].destinationId}}, options); const salesTicketFuture = await models.Sale.find({where: {ticketFk: tickets.destinationId}}, options);
const chatNotificationAfterMerge = await models.Chat.find(); const chatNotificationAfterMerge = await models.Chat.find();
expect(createdTicketLog.length).toEqual(1); expect(createdTicketLog.length).toEqual(2);
expect(deletedTicket.isDeleted).toEqual(true); expect(deletedTicket.isDeleted).toEqual(true);
expect(salesTicketFuture.length).toEqual(2); expect(salesTicketFuture.length).toEqual(2);
expect(chatNotificationBeforeMerge.length).toEqual(chatNotificationAfterMerge.length - 2); expect(chatNotificationBeforeMerge.length).toEqual(chatNotificationAfterMerge.length - 2);

View File

@ -0,0 +1,32 @@
const models = require('vn-loopback/server/server').models;
describe('Ticket saveSign()', () => {
const FormData = require('form-data');
const data = new FormData();
let ctx = {req: {
accessToken: {userId: 9},
headers: {
...data.getHeaders()
}
}};
it(`should throw error if the ticket's alert level is lower than 2`, async() => {
const tx = await models.TicketDms.beginTransaction({});
const ticketWithOkState = 12;
let error;
try {
const options = {transaction: tx};
ctx.args = {tickets: [ticketWithOkState]};
await models.Ticket.saveSign(ctx, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error).toBeDefined();
});
});

View File

@ -105,8 +105,8 @@ module.exports = Self => {
originFk: id, originFk: id,
userFk: userId, userFk: userId,
action: 'update', action: 'update',
changedModel: 'Ticket', changedModel: 'Sale',
changedModelId: id, changedModelId: sale.id,
oldInstance: { oldInstance: {
item: originalSaleData.itemFk, item: originalSaleData.itemFk,
quantity: originalSaleData.quantity, quantity: originalSaleData.quantity,
@ -126,8 +126,8 @@ module.exports = Self => {
originFk: ticketId, originFk: ticketId,
userFk: userId, userFk: userId,
action: 'update', action: 'update',
changedModel: 'Ticket', changedModel: 'Sale',
changedModelId: ticketId, changedModelId: sale.id,
oldInstance: { oldInstance: {
item: originalSaleData.itemFk, item: originalSaleData.itemFk,
quantity: originalSaleData.quantity, quantity: originalSaleData.quantity,

View File

@ -39,4 +39,5 @@ module.exports = function(Self) {
require('../methods/ticket/isRoleAdvanced')(Self); require('../methods/ticket/isRoleAdvanced')(Self);
require('../methods/ticket/collectionLabel')(Self); require('../methods/ticket/collectionLabel')(Self);
require('../methods/ticket/expeditionPalletLabel')(Self); require('../methods/ticket/expeditionPalletLabel')(Self);
require('../methods/ticket/saveSign')(Self);
}; };

View File

@ -44,6 +44,9 @@
"isDeleted": { "isDeleted": {
"type": "boolean" "type": "boolean"
}, },
"isSigned": {
"type": "boolean"
},
"priority": { "priority": {
"type": "number" "type": "number"
}, },

View File

@ -1,5 +1,3 @@
const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('extraCommunityEmail', { Self.remoteMethodCtx('extraCommunityEmail', {
description: 'Sends the extra community email with an attached PDF', description: 'Sends the extra community email with an attached PDF',
@ -74,19 +72,5 @@ module.exports = Self => {
} }
}); });
Self.extraCommunityEmail = async ctx => { Self.extraCommunityEmail = ctx => Self.sendTemplate(ctx, 'extra-community');
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('extra-community', params);
return email.send();
};
}; };

View File

@ -1,5 +1,3 @@
const {Report} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('extraCommunityPdf', { Self.remoteMethodCtx('extraCommunityPdf', {
description: 'Returns the extra community pdf', description: 'Returns the extra community pdf',
@ -11,6 +9,16 @@ module.exports = Self => {
description: 'The recipient id', description: 'The recipient id',
required: false required: false
}, },
{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string'
},
{
arg: 'search',
type: 'string',
description: 'Searchs the travel by id'
},
{ {
arg: 'landedTo', arg: 'landedTo',
type: 'date' type: 'date'
@ -73,17 +81,5 @@ module.exports = Self => {
} }
}); });
Self.extraCommunityPdf = async ctx => { Self.extraCommunityPdf = ctx => Self.printReport(ctx, null, 'extra-community');
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('extra-community', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="extra-community.pdf"`];
};
}; };

View File

@ -1,7 +1,7 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="Travels/extraCommunityFilter" url="Travels/extraCommunityFilter"
filter="::$ctrl.filter" user-params="::$ctrl.defaultFilter"
data="travels" data="travels"
order="landed ASC, shipped ASC, travelFk, loadPriority, agencyModeFk, supplierName, evaNotes" order="landed ASC, shipped ASC, travelFk, loadPriority, agencyModeFk, supplierName, evaNotes"
limit="20" limit="20"

View File

@ -141,8 +141,11 @@ class Controller extends Section {
get reportParams() { get reportParams() {
const userParams = this.$.model.userParams; const userParams = this.$.model.userParams;
const currentFilter = this.$.model.currentFilter;
return Object.assign({ return Object.assign({
authorization: this.vnToken.token authorization: this.vnToken.token,
filter: currentFilter
}, userParams); }, userParams);
} }

View File

@ -0,0 +1,61 @@
module.exports = Self => {
Self.remoteMethodCtx('getMailStates', {
description: 'Get the states of a month about time control mail',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
description: 'The worker id',
http: {source: 'path'}
},
{
arg: 'month',
type: 'number',
description: 'The number of the month'
},
{
arg: 'year',
type: 'number',
description: 'The number of the year'
}],
returns: [{
type: ['object'],
root: true
}],
http: {
path: `/:id/getMailStates`,
verb: 'GET'
}
});
Self.getMailStates = async(ctx, workerId, options) => {
const models = Self.app.models;
const args = ctx.args;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const times = await models.Time.find({
fields: ['week'],
where: {
month: args.month,
year: args.year
}
}, myOptions);
const weeks = times.map(time => time.week);
const weekNumbersSet = new Set(weeks);
const weekNumbers = Array.from(weekNumbersSet);
const workerTimeControlMails = await models.WorkerTimeControlMail.find({
where: {
workerFk: workerId,
year: args.year,
week: {inq: weekNumbers}
}
}, myOptions);
return workerTimeControlMails;
};
};

View File

@ -0,0 +1,29 @@
const models = require('vn-loopback/server/server').models;
describe('workerTimeControl getMailStates()', () => {
const workerId = 9;
const ctx = {args: {
month: 12,
year: 2000
}};
it('should get the states of a month about time control mail', async() => {
const tx = await models.WorkerTimeControl.beginTransaction({});
try {
const options = {transaction: tx};
const response = await models.WorkerTimeControl.getMailStates(ctx, workerId, options);
expect(response[0].state).toEqual('REVISE');
expect(response[1].state).toEqual('SENDED');
expect(response[2].state).toEqual('CONFIRMED');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -47,6 +47,10 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const isHimself = userId == args.workerId;
if (!isHimself)
throw new UserError(`You don't have enough privileges`);
const workerTimeControlMail = await models.WorkerTimeControlMail.findOne({ const workerTimeControlMail = await models.WorkerTimeControlMail.findOne({
where: { where: {
workerFk: args.workerId, workerFk: args.workerId,
@ -60,8 +64,6 @@ module.exports = Self => {
const oldState = workerTimeControlMail.state; const oldState = workerTimeControlMail.state;
const oldReason = workerTimeControlMail.reason; const oldReason = workerTimeControlMail.reason;
if (oldState == args.state) throw new UserError('Already has this status');
await workerTimeControlMail.updateAttributes({ await workerTimeControlMail.updateAttributes({
state: args.state, state: args.state,
reason: args.reason || null reason: args.reason || null

View File

@ -14,6 +14,9 @@
"year": { "year": {
"type": "number" "type": "number"
}, },
"month": {
"type": "number"
},
"week": { "week": {
"type": "number" "type": "number"
} }

View File

@ -8,6 +8,7 @@ module.exports = Self => {
require('../methods/worker-time-control/sendMail')(Self); require('../methods/worker-time-control/sendMail')(Self);
require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self); require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self);
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self); require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
require('../methods/worker-time-control/getMailStates')(Self);
Self.rewriteDbError(function(err) { Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY') if (err.code === 'ER_DUP_ENTRY')

View File

@ -13,14 +13,20 @@ export default class Controller extends Section {
}); });
} }
get ibanCountry() {
if (!this.worker || !this.worker.iban) return false;
let countryCode = this.worker.iban.substr(0, 2);
return countryCode;
}
autofillBic() { autofillBic() {
if (!this.worker || !this.worker.iban) return; if (!this.worker || !this.worker.iban) return;
let bankEntityId = parseInt(this.worker.iban.substr(4, 4)); let bankEntityId = parseInt(this.worker.iban.substr(4, 4));
let filter = {where: {id: bankEntityId}}; let filter = {where: {id: bankEntityId}};
if (this.ibanCountry != 'ES') return;
this.$http.get(`BankEntities`, {filter}).then(response => { this.$http.get(`BankEntities`, {filter}).then(response => {
const hasData = response.data && response.data[0]; const hasData = response.data && response.data[0];

View File

@ -78,15 +78,31 @@
</vn-table> </vn-table>
</vn-card> </vn-card>
<vn-button-bar class="vn-pa-xs vn-w-lg"> <vn-button-bar ng-show="$ctrl.state" class="vn-w-lg">
<vn-button <vn-button
label="Satisfied" label="Satisfied"
disabled="$ctrl.state == 'CONFIRMED'"
ng-if="$ctrl.isHimSelf"
ng-click="$ctrl.isSatisfied()"> ng-click="$ctrl.isSatisfied()">
</vn-button> </vn-button>
<vn-button <vn-button
label="Not satisfied" label="Not satisfied"
disabled="$ctrl.state == 'REVISE'"
ng-if="$ctrl.isHimSelf"
ng-click="reason.show()"> ng-click="reason.show()">
</vn-button> </vn-button>
<vn-button
label="Reason"
ng-if="$ctrl.reason && ($ctrl.isHimSelf || $ctrl.isHr)"
ng-click="reason.show()">
</vn-button>
<vn-button
label="Resend"
ng-click="sendEmailConfirmation.show()"
class="right"
vn-tooltip="Resend email of this week to the user"
ng-show="::$ctrl.isHr">
</vn-button>
</vn-button-bar> </vn-button-bar>
<vn-side-menu side="right"> <vn-side-menu side="right">
@ -106,6 +122,8 @@
vn-id="calendar" vn-id="calendar"
class="vn-pt-md" class="vn-pt-md"
ng-model="$ctrl.date" ng-model="$ctrl.date"
format-week="$ctrl.formatWeek($element)"
on-move="$ctrl.getMailStates($date)"
has-events="$ctrl.hasEvents($day)"> has-events="$ctrl.hasEvents($day)">
</vn-calendar> </vn-calendar>
</div> </div>
@ -166,15 +184,31 @@
vn-id="reason" vn-id="reason"
on-accept="$ctrl.isUnsatisfied()"> on-accept="$ctrl.isUnsatisfied()">
<tpl-body> <tpl-body>
<vn-textarea <div class="reasonDialog">
label="Reason" <vn-textarea
ng-model="$ctrl.reason" label="Reason"
required="true" ng-model="$ctrl.reason"
rows="3"> disabled="!$ctrl.isHimSelf"
</vn-textarea> rows="5"
required="true">
</vn-textarea>
</div>
</tpl-body> </tpl-body>
<tpl-buttons> <tpl-buttons ng-if="$ctrl.isHimSelf">
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/> <input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Save</button> <button response="accept" translate>Save</button>
</tpl-buttons> </tpl-buttons>
</vn-dialog> </vn-dialog>
<vn-dialog
vn-id="sendEmailConfirmation"
on-accept="$ctrl.resendEmail()"
message="Send time control email">
<tpl-body style="min-width: 500px;">
<span translate>Are you sure you want to send it?</span>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>

View File

@ -1,6 +1,7 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
import './style.scss'; import './style.scss';
import UserError from 'core/lib/user-error';
class Controller extends Section { class Controller extends Section {
constructor($element, $, vnWeekDays) { constructor($element, $, vnWeekDays) {
@ -24,12 +25,31 @@ class Controller extends Section {
} }
this.date = initialDate; this.date = initialDate;
this.getMailStates(this.date);
}
get isHr() {
return this.aclService.hasAny(['hr']);
}
get isHimSelf() {
const userId = window.localStorage.currentUserWorkerId;
return userId == this.$params.id;
} }
get worker() { get worker() {
return this._worker; return this._worker;
} }
get weekNumber() {
return this.getWeekNumber(this.date);
}
set weekNumber(value) {
this._weekNumber = value;
}
set worker(value) { set worker(value) {
this._worker = value; this._worker = value;
} }
@ -68,6 +88,27 @@ class Controller extends Section {
} }
this.fetchHours(); this.fetchHours();
this.getWeekData();
}
getWeekData() {
const filter = {
where: {
workerFk: this.$params.id,
year: this._date.getFullYear(),
week: this.getWeekNumber(this._date)
}
};
this.$http.get('WorkerTimeControlMails', {filter})
.then(res => {
const workerTimeControlMail = res.data;
if (!workerTimeControlMail.length) {
this.state = null;
return;
}
this.state = workerTimeControlMail[0].state;
this.reason = workerTimeControlMail[0].reason;
});
} }
/** /**
@ -294,42 +335,56 @@ class Controller extends Section {
this.$.editEntry.show($event); this.$.editEntry.show($event);
} }
getWeekNumber(currentDate) { getWeekNumber(date) {
const startDate = new Date(currentDate.getFullYear(), 0, 1); const tempDate = new Date(date);
let days = Math.floor((currentDate - startDate) / let dayOfWeek = tempDate.getDay();
(24 * 60 * 60 * 1000)); dayOfWeek = (dayOfWeek === 0) ? 7 : dayOfWeek;
return Math.ceil(days / 7); const firstDayOfWeek = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() - (dayOfWeek - 1));
const firstDayOfYear = new Date(tempDate.getFullYear(), 0, 1);
const differenceInMilliseconds = firstDayOfWeek.getTime() - firstDayOfYear.getTime();
const weekNumber = Math.floor(differenceInMilliseconds / (1000 * 60 * 60 * 24 * 7)) + 1;
return weekNumber;
} }
isSatisfied() { isSatisfied() {
const weekNumber = this.getWeekNumber(this.date);
const params = { const params = {
workerId: this.worker.id, workerId: this.worker.id,
year: this.date.getFullYear(), year: this.date.getFullYear(),
week: weekNumber, week: this.weekNumber,
state: 'CONFIRMED' state: 'CONFIRMED'
}; };
const query = `WorkerTimeControls/updateWorkerTimeControlMail`; const query = `WorkerTimeControls/updateWorkerTimeControlMail`;
this.$http.post(query, params).then(() => { this.$http.post(query, params).then(() => {
this.getMailStates(this.date);
this.getWeekData();
this.vnApp.showSuccess(this.$t('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
}); });
} }
isUnsatisfied() { isUnsatisfied() {
const weekNumber = this.getWeekNumber(this.date); if (!this.reason) throw new UserError(`You must indicate a reason`);
const params = { const params = {
workerId: this.worker.id, workerId: this.worker.id,
year: this.date.getFullYear(), year: this.date.getFullYear(),
week: weekNumber, week: this.weekNumber,
state: 'REVISE', state: 'REVISE',
reason: this.reason reason: this.reason
}; };
const query = `WorkerTimeControls/updateWorkerTimeControlMail`; const query = `WorkerTimeControls/updateWorkerTimeControlMail`;
this.$http.post(query, params).then(() => { this.$http.post(query, params).then(() => {
this.getMailStates(this.date);
this.getWeekData();
this.vnApp.showSuccess(this.$t('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
}); });
} }
changeState(state, reason) {
this.state = state;
this.reason = reason;
this.repaint();
}
save() { save() {
try { try {
const entry = this.selectedRow; const entry = this.selectedRow;
@ -345,6 +400,77 @@ class Controller extends Section {
this.vnApp.showError(this.$t(e.message)); this.vnApp.showError(this.$t(e.message));
} }
} }
resendEmail() {
const timestamp = this.date.getTime() / 1000;
const url = `${window.location.origin}/#!/worker/${this.worker.id}/time-control?timestamp=${timestamp}`;
const params = {
recipient: this.worker.user.emailUser.email,
week: this.weekNumber,
year: this.date.getFullYear(),
url: url,
};
this.$http.post(`WorkerTimeControls/weekly-hour-hecord-email`, params)
.then(() => {
this.vnApp.showSuccess(this.$t('Email sended'));
});
}
getTime(timeString) {
const [hours, minutes, seconds] = timeString.split(':');
return [parseInt(hours), parseInt(minutes), parseInt(seconds)];
}
getMailStates(date) {
const params = {
month: date.getMonth() + 1,
year: date.getFullYear()
};
const query = `WorkerTimeControls/${this.$params.id}/getMailStates`;
this.$http.get(query, {params})
.then(res => {
this.workerTimeControlMails = res.data;
this.repaint();
});
}
formatWeek($element) {
const weekNumberHTML = $element.firstElementChild;
const weekNumberValue = weekNumberHTML.innerHTML;
if (!this.workerTimeControlMails) return;
const workerTimeControlMail = this.workerTimeControlMails.find(
workerTimeControlMail => workerTimeControlMail.week == weekNumberValue
);
if (!workerTimeControlMail) return;
const state = workerTimeControlMail.state;
if (state == 'CONFIRMED') {
weekNumberHTML.classList.remove('revise');
weekNumberHTML.classList.remove('sended');
weekNumberHTML.classList.add('confirmed');
weekNumberHTML.setAttribute('title', 'Conforme');
}
if (state == 'REVISE') {
weekNumberHTML.classList.remove('confirmed');
weekNumberHTML.classList.remove('sended');
weekNumberHTML.classList.add('revise');
weekNumberHTML.setAttribute('title', 'No conforme');
}
if (state == 'SENDED') {
weekNumberHTML.classList.add('sended');
weekNumberHTML.setAttribute('title', 'Pendiente');
}
}
repaint() {
let calendars = this.element.querySelectorAll('vn-calendar');
for (let calendar of calendars)
calendar.$ctrl.repaint();
}
} }
Controller.$inject = ['$element', '$scope', 'vnWeekDays']; Controller.$inject = ['$element', '$scope', 'vnWeekDays'];

View File

@ -5,12 +5,14 @@ describe('Component vnWorkerTimeControl', () => {
let $scope; let $scope;
let $element; let $element;
let controller; let controller;
let $httpParamSerializer;
beforeEach(ngModule('worker')); beforeEach(ngModule('worker'));
beforeEach(inject(($componentController, $rootScope, $stateParams, _$httpBackend_) => { beforeEach(inject(($componentController, $rootScope, $stateParams, _$httpBackend_, _$httpParamSerializer_) => {
$stateParams.id = 1; $stateParams.id = 1;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$element = angular.element('<vn-worker-time-control></vn-worker-time-control>'); $element = angular.element('<vn-worker-time-control></vn-worker-time-control>');
controller = $componentController('vnWorkerTimeControl', {$element, $scope}); controller = $componentController('vnWorkerTimeControl', {$element, $scope});
@ -82,6 +84,9 @@ describe('Component vnWorkerTimeControl', () => {
$httpBackend.whenRoute('GET', 'Workers/:id/getWorkedHours') $httpBackend.whenRoute('GET', 'Workers/:id/getWorkedHours')
.respond(response); .respond(response);
$httpBackend.whenRoute('GET', 'WorkerTimeControlMails')
.respond([]);
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
let weekOffset = today.getDay() - 1; let weekOffset = today.getDay() - 1;
@ -97,7 +102,6 @@ describe('Component vnWorkerTimeControl', () => {
controller.ended = ended; controller.ended = ended;
controller.getWorkedHours(controller.started, controller.ended); controller.getWorkedHours(controller.started, controller.ended);
$httpBackend.flush(); $httpBackend.flush();
expect(controller.weekDays.length).toEqual(7); expect(controller.weekDays.length).toEqual(7);
@ -152,5 +156,120 @@ describe('Component vnWorkerTimeControl', () => {
expect(controller.date.toDateString()).toEqual(date.toDateString()); expect(controller.date.toDateString()).toEqual(date.toDateString());
}); });
}); });
describe('getWeekData() ', () => {
it(`should make a query an then update the state and reason`, () => {
const today = Date.vnNew();
const response = [
{
state: 'SENDED',
reason: null
}
];
controller._date = today;
$httpBackend.whenRoute('GET', 'WorkerTimeControlMails')
.respond(response);
controller.getWeekData();
$httpBackend.flush();
expect(controller.state).toBe('SENDED');
expect(controller.reason).toBe(null);
});
});
describe('isSatisfied() ', () => {
it(`should make a query an then call three methods`, () => {
const today = Date.vnNew();
jest.spyOn(controller, 'getWeekData').mockReturnThis();
jest.spyOn(controller, 'getMailStates').mockReturnThis();
jest.spyOn(controller.vnApp, 'showSuccess');
controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())};
controller.worker = {id: 1};
controller.date = today;
controller.weekNumber = 1;
$httpBackend.expect('POST', 'WorkerTimeControls/updateWorkerTimeControlMail').respond();
controller.isSatisfied();
$httpBackend.flush();
expect(controller.getMailStates).toHaveBeenCalledWith(controller.date);
expect(controller.getWeekData).toHaveBeenCalled();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('isUnsatisfied() ', () => {
it(`should throw an error is reason is empty`, () => {
let error;
try {
controller.isUnsatisfied();
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe(`You must indicate a reason`);
});
it(`should make a query an then call three methods`, () => {
const today = Date.vnNew();
jest.spyOn(controller, 'getWeekData').mockReturnThis();
jest.spyOn(controller, 'getMailStates').mockReturnThis();
jest.spyOn(controller.vnApp, 'showSuccess');
controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())};
controller.worker = {id: 1};
controller.date = today;
controller.weekNumber = 1;
controller.reason = 'reason';
$httpBackend.expect('POST', 'WorkerTimeControls/updateWorkerTimeControlMail').respond();
controller.isSatisfied();
$httpBackend.flush();
expect(controller.getMailStates).toHaveBeenCalledWith(controller.date);
expect(controller.getWeekData).toHaveBeenCalled();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('resendEmail() ', () => {
it(`should make a query an then call showSuccess method`, () => {
const today = Date.vnNew();
jest.spyOn(controller, 'getWeekData').mockReturnThis();
jest.spyOn(controller.vnApp, 'showSuccess');
controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())};
controller.worker = {id: 1};
controller.worker = {user: {emailUser: {email: 'employee@verdnatura.es'}}};
controller.date = today;
controller.weekNumber = 1;
$httpBackend.expect('POST', 'WorkerTimeControls/weekly-hour-hecord-email').respond();
controller.resendEmail();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('getMailStates() ', () => {
it(`should make a query an then call showSuccess method`, () => {
const today = Date.vnNew();
jest.spyOn(controller, 'repaint').mockReturnThis();
controller.$params = {id: 1};
$httpBackend.expect('GET', `WorkerTimeControls/1/getMailStates?month=1&year=2001`).respond();
controller.getMailStates(today);
$httpBackend.flush();
expect(controller.repaint).toHaveBeenCalled();
});
});
}); });
}); });

View File

@ -14,3 +14,9 @@ The entry type can't be empty: El tipo de fichada no puede quedar vacía
Satisfied: Conforme Satisfied: Conforme
Not satisfied: No conforme Not satisfied: No conforme
Reason: Motivo Reason: Motivo
Resend: Reenviar
Email sended: Email enviado
You must indicate a reason: Debes indicar un motivo
Send time control email: Enviar email control horario
Are you sure you want to send it?: ¿Seguro que quieres enviarlo?
Resend email of this week to the user: Reenviar email de esta semana al usuario

View File

@ -24,8 +24,29 @@ vn-worker-time-control {
.totalBox { .totalBox {
max-width: none max-width: none
} }
}
.reasonDialog{
min-width: 500px;
} }
.edit-time-entry { .edit-time-entry {
width: 200px width: 200px
} }
.right {
float: right;
}
.confirmed {
color: #97B92F;
}
.revise {
color: #f61e1e;
}
.sended {
color: #d19b25;
}

View File

@ -38,6 +38,9 @@
"inflation": { "inflation": {
"type": "number" "type": "number"
}, },
"m3Max": {
"type": "number"
},
"itemMaxSize": { "itemMaxSize": {
"type": "number" "type": "number"
} }

View File

@ -30,14 +30,21 @@
rule> rule>
</vn-autocomplete> </vn-autocomplete>
<vn-input-number <vn-input-number
vn-one vn-one
label="Maximum m³" label="Max m³"
ng-model="$ctrl.zone.itemMaxSize" ng-model="$ctrl.zone.itemMaxSize"
min="0" min="0"
step="0.01" vn-acl="deliveryBoss"
vn-acl="deliveryBoss" rule>
rule> </vn-input-number>
</vn-input-number> <vn-input-number
vn-one
label="Maximum m³"
ng-model="$ctrl.zone.m3Max"
min="0"
vn-acl="deliveryBoss"
rule>
</vn-input-number>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-input-number <vn-input-number

View File

@ -13,6 +13,7 @@ Indefinitely: Indefinido
Inflation: Inflación Inflation: Inflación
Locations: Localizaciones Locations: Localizaciones
Maximum m³: M³ máximo Maximum m³: M³ máximo
Max m³: Medida máxima
New zone: Nueva zona New zone: Nueva zona
One day: Un día One day: Un día
Pick up: Recogida Pick up: Recogida

139
package-lock.json generated
View File

@ -17,6 +17,7 @@
"form-data": "^4.0.0", "form-data": "^4.0.0",
"fs-extra": "^5.0.0", "fs-extra": "^5.0.0",
"ftps": "^1.2.0", "ftps": "^1.2.0",
"gm": "^1.25.0",
"got": "^10.7.0", "got": "^10.7.0",
"helmet": "^3.21.2", "helmet": "^3.21.2",
"i18n": "^0.8.4", "i18n": "^0.8.4",
@ -3625,6 +3626,16 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/array-parallel": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz",
"integrity": "sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w=="
},
"node_modules/array-series": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz",
"integrity": "sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg=="
},
"node_modules/array-slice": { "node_modules/array-slice": {
"version": "1.1.0", "version": "1.1.0",
"dev": true, "dev": true,
@ -9284,6 +9295,67 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/gm": {
"version": "1.25.0",
"resolved": "https://registry.npmjs.org/gm/-/gm-1.25.0.tgz",
"integrity": "sha512-4kKdWXTtgQ4biIo7hZA396HT062nDVVHPjQcurNZ3o/voYN+o5FUC5kOwuORbpExp3XbTJ3SU7iRipiIhQtovw==",
"dependencies": {
"array-parallel": "~0.1.3",
"array-series": "~0.1.5",
"cross-spawn": "^4.0.0",
"debug": "^3.1.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/gm/node_modules/cross-spawn": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
"integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==",
"dependencies": {
"lru-cache": "^4.0.1",
"which": "^1.2.9"
}
},
"node_modules/gm/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/gm/node_modules/lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"dependencies": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
}
},
"node_modules/gm/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/gm/node_modules/which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"which": "bin/which"
}
},
"node_modules/gm/node_modules/yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
},
"node_modules/google-auth-library": { "node_modules/google-auth-library": {
"version": "3.1.2", "version": "3.1.2",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -28673,6 +28745,16 @@
} }
} }
}, },
"array-parallel": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz",
"integrity": "sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w=="
},
"array-series": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz",
"integrity": "sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg=="
},
"array-slice": { "array-slice": {
"version": "1.1.0", "version": "1.1.0",
"dev": true "dev": true
@ -32611,6 +32693,63 @@
"sparkles": "^1.0.0" "sparkles": "^1.0.0"
} }
}, },
"gm": {
"version": "1.25.0",
"resolved": "https://registry.npmjs.org/gm/-/gm-1.25.0.tgz",
"integrity": "sha512-4kKdWXTtgQ4biIo7hZA396HT062nDVVHPjQcurNZ3o/voYN+o5FUC5kOwuORbpExp3XbTJ3SU7iRipiIhQtovw==",
"requires": {
"array-parallel": "~0.1.3",
"array-series": "~0.1.5",
"cross-spawn": "^4.0.0",
"debug": "^3.1.0"
},
"dependencies": {
"cross-spawn": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
"integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==",
"requires": {
"lru-cache": "^4.0.1",
"which": "^1.2.9"
}
},
"debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"requires": {
"ms": "^2.1.1"
}
},
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"requires": {
"isexe": "^2.0.0"
}
},
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
}
}
},
"google-auth-library": { "google-auth-library": {
"version": "3.1.2", "version": "3.1.2",
"requires": { "requires": {

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-back", "name": "salix-back",
"version": "23.10.01", "version": "23.12.01",
"author": "Verdnatura Levante SL", "author": "Verdnatura Levante SL",
"description": "Salix backend", "description": "Salix backend",
"license": "GPL-3.0", "license": "GPL-3.0",
@ -20,6 +20,7 @@
"form-data": "^4.0.0", "form-data": "^4.0.0",
"fs-extra": "^5.0.0", "fs-extra": "^5.0.0",
"ftps": "^1.2.0", "ftps": "^1.2.0",
"gm": "^1.25.0",
"got": "^10.7.0", "got": "^10.7.0",
"helmet": "^3.21.2", "helmet": "^3.21.2",
"i18n": "^0.8.4", "i18n": "^0.8.4",

View File

@ -17,8 +17,8 @@
</p> </p>
<h4 style="text-align: center; margin-top: 10%">{{$t('Agree') | uppercase}}</h4> <h4 style="text-align: center; margin-top: 10%">{{$t('Agree') | uppercase}}</h4>
<p style="margin-top: 8%; text-align: justify"> <p style="margin-top: 8%; text-align: justify">
{{$t('Date')}} {{client.payed | date('%d-%m-%Y')}} {{$t('Compensate')}} {{client.amountPaid}} € {{$t('Date')}} {{formatDate(receipt.payed, '%d-%m-%Y')}} {{$t('Compensate')}} {{receipt.amountPaid}} €
{{$t('From client')}} {{client.name}} {{$t('Against the balance of')}}: {{client.invoiceFk}}. {{$t('From client')}} {{client.name}} {{$t('Against the balance of')}}: {{receipt.description}}.
</p> </p>
<p style="margin-top: 8%"> <p style="margin-top: 8%">
{{$t('Reception')}} <span style="color: blue">administracion@verdnatura.es</span> {{$t('Reception')}} <span style="color: blue">administracion@verdnatura.es</span>

View File

@ -1,12 +1,31 @@
const vnReport = require('../../../core/mixins/vn-report.js'); const vnReport = require('../../../core/mixins/vn-report.js');
const app = require('vn-loopback/server/server');
module.exports = { module.exports = {
name: 'balance-compensation', name: 'balance-compensation',
mixins: [vnReport], mixins: [vnReport],
async serverPrefetch() { async serverPrefetch() {
this.client = await this.findOneFromDef('client', [this.id]); this.receipt = await app.models.Receipt.findOne({
this.checkMainEntity(this.client); fields: ['amountPaid', 'payed', 'clientFk', 'companyFk', 'description'],
this.company = await this.findOneFromDef('company', [this.id]); include: [
{
relation: 'client',
scope: {
fields: ['name', 'street', 'fi', 'city'],
}
}, {
relation: 'supplier',
scope: {
fields: ['name', 'street', 'nif', 'city'],
}
}
],
where: {id: this.id}
});
this.client = this.receipt.client();
this.company = this.receipt.supplier();
this.checkMainEntity(this.receipt);
}, },
props: { props: {
id: { id: {

View File

@ -1,12 +0,0 @@
SELECT
c.name,
c.socialName,
c.street,
c.fi,
c.city,
r.invoiceFk,
r.amountPaid,
r.payed
FROM client c
JOIN receipt r ON r.clientFk = c.id
WHERE r.id = ?

View File

@ -1,8 +0,0 @@
SELECT
s.name,
s.nif,
s.street,
s.city
FROM supplier s
JOIN receipt r ON r.companyFk = s.id
WHERE r.id = ?;

View File

@ -51,7 +51,7 @@
<td>{{entry.supplierName}}</td> <td>{{entry.supplierName}}</td>
<td>{{entry.reference}}</td> <td>{{entry.reference}}</td>
<td class="number">{{entry.volumeKg | number($i18n.locale)}}</td> <td class="number">{{entry.volumeKg | number($i18n.locale)}}</td>
<td class="number">{{entry.loadedKg | number($i18n.locale)}}</td> <td class="number">{{entry.loadedkg | number($i18n.locale)}}</td>
<td class="number">{{entry.stickers}}</td> <td class="number">{{entry.stickers}}</td>
</tr> </tr>
<tr v-if="!travel.entries"> <tr v-if="!travel.entries">

Some files were not shown because too many files have changed in this diff Show More