Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5395-travel.extra-community
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
commit
6d04933fe0
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -5,16 +5,24 @@ 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/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2312.01] - 2023-04-06
|
||||
|
||||
### Added
|
||||
-
|
||||
|
||||
### Changed
|
||||
-
|
||||
|
||||
### Fixed
|
||||
-
|
||||
|
||||
## [2310.01] - 2023-03-23
|
||||
|
||||
### Added
|
||||
-
|
||||
|
||||
### Changed
|
||||
-
|
||||
- (Trabajadores -> Control de horario) Ahora se puede confirmar/no confirmar el registro horario de cada semana desde esta sección
|
||||
|
||||
### 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
|
||||
|
||||
## [2308.01] - 2023-03-09
|
||||
|
|
|
@ -10,6 +10,7 @@ RUN apt-get update \
|
|||
curl \
|
||||
ca-certificates \
|
||||
gnupg2 \
|
||||
graphicsmagick \
|
||||
&& curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& npm install -g npm@8.19.2
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('previousLabel', {
|
||||
description: 'Returns the previa label pdf',
|
||||
|
@ -33,17 +31,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.previousLabel = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.previousLabel = (ctx, id) => Self.printReport(ctx, id, 'previa-label');
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -9,17 +9,29 @@
|
|||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "number",
|
||||
"forceId": false
|
||||
"type": "number"
|
||||
},
|
||||
"date": {
|
||||
"created": {
|
||||
"type": "date"
|
||||
},
|
||||
"m3":{
|
||||
"longitude":{
|
||||
"type": "number"
|
||||
},
|
||||
"warehouseFk":{
|
||||
"latitude":{
|
||||
"type": "number"
|
||||
},
|
||||
"dated":{
|
||||
"type": "date"
|
||||
},
|
||||
"ticketFk":{
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"ticket": {
|
||||
"type": "belongsTo",
|
||||
"model": "Ticket",
|
||||
"foreignKey": "ticketFk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ module.exports = Self => {
|
|||
require('../methods/dms/removeFile')(Self);
|
||||
require('../methods/dms/updateFile')(Self);
|
||||
require('../methods/dms/deleteTrashFiles')(Self);
|
||||
require('../methods/dms/saveSign')(Self);
|
||||
|
||||
Self.checkRole = async function(ctx, id) {
|
||||
const models = Self.app.models;
|
||||
|
|
|
@ -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 ;
|
|
@ -2825,4 +2825,11 @@ INSERT INTO `vn`.`deviceProductionUser` (`deviceProductionFk`, `userFk`, `create
|
|||
(1, 1, 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);
|
||||
|
||||
|
||||
|
|
|
@ -81220,3 +81220,4 @@ USE `vn`;
|
|||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
-- Dump completed on 2023-02-21 8:14:30
|
||||
|
||||
|
|
|
@ -524,7 +524,7 @@ export default {
|
|||
},
|
||||
itemLog: {
|
||||
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: {
|
||||
header: 'vn-ticket-summary > vn-card > h5',
|
||||
|
|
|
@ -59,6 +59,6 @@ describe('Item log path', () => {
|
|||
const fifthLineCreatedProperty = await page
|
||||
.waitToGetProperty(selectors.itemLog.fifthLineCreatedProperty, 'innerText');
|
||||
|
||||
expect(fifthLineCreatedProperty).toEqual('Coral y materiales similares');
|
||||
expect(fifthLineCreatedProperty).toEqual('05080000');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -37,6 +37,6 @@ describe('Zone descriptor path', () => {
|
|||
await page.accessToSection('ticket.card.log');
|
||||
const lastChanges = await page.waitToGetProperty(selectors.ticketLog.changes, 'innerText');
|
||||
|
||||
expect(lastChanges).toContain('Arreglar');
|
||||
expect(lastChanges).toContain('1');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<div class="weekdays">
|
||||
<section
|
||||
ng-repeat="day in ::$ctrl.weekDays"
|
||||
translate-attr="::{title: day.name}"
|
||||
translate-attr="::{title: day.name}"
|
||||
ng-click="$ctrl.selectWeekDay($event, day.index)">
|
||||
<span>{{::day.localeChar}}</span>
|
||||
</section>
|
||||
|
@ -57,4 +57,4 @@
|
|||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -15,9 +15,9 @@ export default class Calendar extends FormInput {
|
|||
constructor($element, $scope, vnWeekDays, moment) {
|
||||
super($element, $scope);
|
||||
this.weekDays = vnWeekDays.locales;
|
||||
this.defaultDate = Date.vnNew();
|
||||
this.displayControls = true;
|
||||
this.moment = moment;
|
||||
this.defaultDate = Date.vnNew();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,14 +207,23 @@ export default class Calendar extends FormInput {
|
|||
}
|
||||
|
||||
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');
|
||||
for (let i = 0; i < days.length; i++) {
|
||||
this.formatDay({
|
||||
$day: this.days[i],
|
||||
$element: days[i]
|
||||
});
|
||||
if (this.formatWeek) {
|
||||
const weeks = this.element.querySelectorAll('.weeks > .day');
|
||||
for (const week of weeks) {
|
||||
this.formatWeek({
|
||||
$element: week
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -228,6 +237,7 @@ ngModule.vnComponent('vnCalendar', {
|
|||
hasEvents: '&?',
|
||||
getClass: '&?',
|
||||
formatDay: '&?',
|
||||
formatWeek: '&?',
|
||||
displayControls: '<?',
|
||||
hideYear: '<?',
|
||||
hideContiguous: '<?',
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
};
|
|
@ -1,4 +1,3 @@
|
|||
const pick = require('object.pick');
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
module.exports = function(Self) {
|
||||
|
@ -6,344 +5,11 @@ module.exports = function(Self) {
|
|||
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) {
|
||||
const appModels = ctx.Model.app.models;
|
||||
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;
|
||||
ctx.options.httpCtx = LoopBackContext.getCurrentContext();
|
||||
});
|
||||
|
||||
Self.observe('before delete', async function(ctx) {
|
||||
const appModels = ctx.Model.app.models;
|
||||
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;
|
||||
}
|
||||
}
|
||||
ctx.options.httpCtx = LoopBackContext.getCurrentContext();
|
||||
});
|
||||
|
||||
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';
|
||||
}
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ module.exports = function(Self) {
|
|||
|
||||
require('../methods/vn-model/getSetValues')(Self);
|
||||
require('../methods/vn-model/getEnumValues')(Self);
|
||||
require('../methods/vn-model/printService')(Self);
|
||||
|
||||
Object.assign(Self, {
|
||||
setup() {
|
||||
|
|
|
@ -270,5 +270,6 @@
|
|||
"Warehouse inventory not set": "El almacén inventario no está establecido",
|
||||
"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}}",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -2,8 +2,41 @@ const mysql = require('mysql');
|
|||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||
const MySQL = require('loopback-connector-mysql').MySQL;
|
||||
const EnumFactory = require('loopback-connector-mysql').EnumFactory;
|
||||
const Transaction = require('loopback-connector').Transaction;
|
||||
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 {
|
||||
/**
|
||||
* Promisified version of execute().
|
||||
|
@ -219,6 +252,277 @@ class VnMySQL extends MySQL {
|
|||
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;
|
||||
|
|
|
@ -91,7 +91,11 @@ exports.getChanges = (original, changes) => {
|
|||
const isPrivate = firstChar == '$';
|
||||
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];
|
||||
|
||||
if (original[property] != undefined)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const { Report } = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('claimPickupPdf', {
|
||||
description: 'Returns the claim pickup order pdf',
|
||||
|
@ -39,17 +37,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.claimPickupPdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.claimPickupPdf = (ctx, id) => Self.printReport(ctx, id, 'claim-pickup-order');
|
||||
};
|
||||
|
|
|
@ -86,7 +86,20 @@
|
|||
icon="icon-ticket">
|
||||
</vn-quick-link>
|
||||
</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>
|
||||
</slot-body>
|
||||
</vn-descriptor-content>
|
||||
|
|
|
@ -18,3 +18,5 @@ Claim deleted!: Reclamación eliminada!
|
|||
claim: reclamación
|
||||
Photos: Fotos
|
||||
Go to the claim: Ir a la reclamación
|
||||
Sale tracking: Líneas preparadas
|
||||
Ticket tracking: Estados del ticket
|
||||
|
|
|
@ -51,19 +51,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.campaignMetricsEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.campaignMetricsEmail = ctx => Self.sendTemplate(ctx, 'campaign-metrics');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('campaignMetricsPdf', {
|
||||
description: 'Returns the campaign metrics pdf',
|
||||
|
@ -50,17 +48,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.campaignMetricsPdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.campaignMetricsPdf = (ctx, id) => Self.printReport(ctx, id, 'campaign-metrics');
|
||||
};
|
||||
|
|
|
@ -46,19 +46,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.clientDebtStatementEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.clientDebtStatementEmail = ctx => Self.sendTemplate(ctx, 'client-debt-statement');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('clientDebtStatementHtml', {
|
||||
description: 'Returns the client debt statement email preview',
|
||||
|
@ -45,21 +43,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.clientDebtStatementHtml = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.clientDebtStatementHtml = (ctx, id) => Self.printEmail(ctx, id, 'client-debt-statement');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('clientDebtStatementPdf', {
|
||||
description: 'Returns the client debt statement pdf',
|
||||
|
@ -45,17 +43,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.clientDebtStatementPdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.clientDebtStatementPdf = (ctx, id) => Self.printReport(ctx, id, 'client-debt-statement');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('clientWelcomeEmail', {
|
||||
description: 'Sends the client welcome email with an attached PDF',
|
||||
|
@ -41,19 +39,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.clientWelcomeEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.clientWelcomeEmail = ctx => Self.sendTemplate(ctx, 'client-welcome');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('clientWelcomeHtml', {
|
||||
description: 'Returns the client welcome email preview',
|
||||
|
@ -40,19 +38,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.clientWelcomeHtml = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.clientWelcomeHtml = (ctx, id) => Self.printEmail(ctx, id, 'client-welcome');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('clientCreditEmail', {
|
||||
description: 'Sends the credit request email with an attached PDF',
|
||||
|
@ -10,7 +8,7 @@ module.exports = Self => {
|
|||
type: 'number',
|
||||
required: true,
|
||||
description: 'The client id',
|
||||
http: {source: 'path'}
|
||||
http: {source: 'path'},
|
||||
},
|
||||
{
|
||||
arg: 'recipient',
|
||||
|
@ -22,38 +20,25 @@ module.exports = Self => {
|
|||
arg: 'replyTo',
|
||||
type: 'string',
|
||||
description: 'The sender email to reply to',
|
||||
required: false
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
arg: 'recipientId',
|
||||
type: 'number',
|
||||
description: 'The recipient id to send to the recipient preferred language',
|
||||
required: false
|
||||
}
|
||||
description:
|
||||
'The recipient id to send to the recipient preferred language',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
root: true
|
||||
root: true,
|
||||
},
|
||||
http: {
|
||||
path: '/:id/credit-request-email',
|
||||
verb: 'POST'
|
||||
}
|
||||
verb: 'POST',
|
||||
},
|
||||
});
|
||||
|
||||
Self.clientCreditEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.clientCreditEmail = ctx => Self.sendTemplate(ctx, 'credit-request');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('creditRequestHtml', {
|
||||
description: 'Returns the credit request email preview',
|
||||
|
@ -40,21 +38,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.creditRequestHtml = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.creditRequestHtml = (ctx, id) => Self.printEmail(ctx, id, 'credit-request');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('creditRequestPdf', {
|
||||
description: 'Returns the credit request pdf',
|
||||
|
@ -40,17 +38,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.creditRequestPdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.creditRequestPdf = (ctx, id) => Self.printReport(ctx, id, 'credit-request');
|
||||
};
|
||||
|
|
|
@ -97,7 +97,7 @@ module.exports = Self => {
|
|||
|
||||
const stmts = [];
|
||||
const stmt = new ParameterizedSQL(
|
||||
`SELECT
|
||||
`SELECT
|
||||
c.id,
|
||||
c.name,
|
||||
c.socialName,
|
||||
|
|
|
@ -80,7 +80,7 @@ module.exports = function(Self) {
|
|||
const data = await Self.rawSql(query, [id, date], myOptions);
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('incotermsAuthorizationEmail', {
|
||||
description: 'Sends the incoterms authorization email with an attached PDF',
|
||||
|
@ -47,19 +45,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.incotermsAuthorizationEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.incotermsAuthorizationEmail = ctx => Self.sendTemplate(ctx, 'incoterms-authorization');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('incotermsAuthorizationHtml', {
|
||||
description: 'Returns the incoterms authorization email preview',
|
||||
|
@ -46,21 +44,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.incotermsAuthorizationHtml = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.incotermsAuthorizationHtml = (ctx, id) => Self.printEmail(ctx, id, 'incoterms-authorization');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('incotermsAuthorizationPdf', {
|
||||
description: 'Returns the incoterms authorization pdf',
|
||||
|
@ -46,17 +44,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.incotermsAuthorizationPdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.incotermsAuthorizationPdf = (ctx, id) => Self.printReport(ctx, id, 'incoterms-authorization');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('letterDebtorNdEmail', {
|
||||
description: 'Sends the second debtor letter email with an attached PDF',
|
||||
|
@ -47,19 +45,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.letterDebtorNdEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.letterDebtorNdEmail = ctx => Self.sendTemplate(ctx, 'letter-debtor-nd');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('letterDebtorNdHtml', {
|
||||
description: 'Returns the second letter debtor email preview',
|
||||
|
@ -46,21 +44,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.letterDebtorNdHtml = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.letterDebtorNdHtml = (ctx, id) => Self.printEmail(ctx, id, 'letter-debtor-nd');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('letterDebtorPdf', {
|
||||
description: 'Returns the letter debtor pdf',
|
||||
|
@ -46,17 +44,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.letterDebtorPdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.letterDebtorPdf = (ctx, id) => Self.printReport(ctx, id, 'letter-debtor');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('letterDebtorStEmail', {
|
||||
description: 'Sends the printer setup email with an attached PDF',
|
||||
|
@ -47,19 +45,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.letterDebtorStEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.letterDebtorStEmail = ctx => Self.sendTemplate(ctx, 'letter-debtor-st');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('letterDebtorStHtml', {
|
||||
description: 'Returns the letter debtor email preview',
|
||||
|
@ -46,21 +44,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.letterDebtorStHtml = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.letterDebtorStHtml = (ctx, id) => Self.printEmail(ctx, id, 'letter-debtor-st');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('printerSetupEmail', {
|
||||
description: 'Sends the printer setup email with an attached PDF',
|
||||
|
@ -41,19 +39,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.printerSetupEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.printerSetupEmail = ctx => Self.sendTemplate(ctx, 'printer-setup');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('printerSetupHtml', {
|
||||
description: 'Returns the printer setup email preview',
|
||||
|
@ -40,19 +38,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.printerSetupHtml = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.printerSetupHtml = (ctx, id) => Self.printEmail(ctx, id, 'printer-setup');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('sepaCoreEmail', {
|
||||
description: 'Sends the campaign metrics email with an attached PDF',
|
||||
|
@ -47,19 +45,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.sepaCoreEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.sepaCoreEmail = ctx => Self.sendTemplate(ctx, 'sepa-core');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const { Report } = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('balanceCompensationPdf', {
|
||||
description: 'Returns the the debit balances compensation pdf',
|
||||
|
@ -10,7 +8,7 @@ module.exports = Self => {
|
|||
type: 'number',
|
||||
required: true,
|
||||
description: 'The receipt id',
|
||||
http: { source: 'path' }
|
||||
http: {source: 'path'}
|
||||
}
|
||||
],
|
||||
returns: [
|
||||
|
@ -34,17 +32,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.balanceCompensationPdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.balanceCompensationPdf = (ctx, id) => Self.printReport(ctx, id, 'balance-compensation');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('receiptPdf', {
|
||||
description: 'Returns the receipt pdf',
|
||||
|
@ -39,17 +37,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.receiptPdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.receiptPdf = (ctx, id) => Self.printReport(ctx, id, 'receipt');
|
||||
};
|
||||
|
|
|
@ -279,6 +279,18 @@ module.exports = Self => {
|
|||
// Credit changes
|
||||
if (changes.credit !== undefined)
|
||||
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 => {
|
||||
|
|
|
@ -62,6 +62,11 @@
|
|||
"type": "belongsTo",
|
||||
"model": "Bank",
|
||||
"foreignKey": "bankFk"
|
||||
},
|
||||
"supplier": {
|
||||
"type": "belongsTo",
|
||||
"model": "Supplier",
|
||||
"foreignKey": "companyFk"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,11 +70,12 @@
|
|||
icon="icon-no036"
|
||||
ng-if="$ctrl.client.isTaxDataChecked == false">
|
||||
</vn-icon>
|
||||
<vn-icon
|
||||
<vn-icon-button
|
||||
vn-tooltip="{{$ctrl.clientUnpaid()}}"
|
||||
icon="icon-clientUnpaid"
|
||||
ui-sref="client.card.unpaid"
|
||||
ng-if="$ctrl.client.unpaid">
|
||||
</vn-icon>
|
||||
</vn-icon-button>
|
||||
</div>
|
||||
<div class="quicklinks">
|
||||
<div ng-transclude="btnOne">
|
||||
|
|
|
@ -46,8 +46,9 @@ class Controller extends Descriptor {
|
|||
}
|
||||
|
||||
clientUnpaid() {
|
||||
return this.$t(`Unpaid Dated`, {dated: this.client.unpaid.dated}) +
|
||||
'<br/>' + this.$t(`Unpaid Amount`, {amount: this.client.unpaid.amount});
|
||||
return this.$t(`Unpaid`) + '<br/>'
|
||||
+ this.$t(`Unpaid Dated`, {dated: this.client.unpaid.dated}) + '<br/>'
|
||||
+ this.$t(`Unpaid Amount`, {amount: this.client.unpaid.amount});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
</vn-watcher>
|
||||
<form
|
||||
name="form"
|
||||
ng-submit="watcher.submit()"
|
||||
ng-submit="$ctrl.onSubmit()"
|
||||
class="vn-w-md">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-vertical>
|
||||
<vn-check
|
||||
label="Unpaid client"
|
||||
label="Unpaid client"
|
||||
ng-model="watcher.hasData"
|
||||
on-change="$ctrl.setDefaultDate(watcher.hasData)">
|
||||
</vn-check>
|
||||
|
@ -48,4 +48,4 @@
|
|||
</vn-button>
|
||||
</vn-button-bar>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,9 +6,17 @@ export default class Controller extends Section {
|
|||
if (hasData && !this.clientUnpaid.dated)
|
||||
this.clientUnpaid.dated = Date.vnNew();
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.$.watcher.submit()
|
||||
.then(() => this.card.reload());
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.vnComponent('vnClientUnpaid', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller
|
||||
controller: Controller,
|
||||
require: {
|
||||
card: '^vnClientCard'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('entryOrderPdf', {
|
||||
description: 'Returns the entry order pdf',
|
||||
|
@ -38,17 +36,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.entryOrderPdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.entryOrderPdf = (ctx, id) => Self.printReport(ctx, id, 'entry-order');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('invoiceInEmail', {
|
||||
description: 'Sends the invoice in email with an attached PDF',
|
||||
|
@ -35,19 +33,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.invoiceInEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.invoiceInEmail = ctx => Self.sendTemplate(ctx, 'invoiceIn');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('invoiceInPdf', {
|
||||
description: 'Returns the invoiceIn pdf',
|
||||
|
@ -34,17 +32,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.invoiceInPdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.invoiceInPdf = (ctx, id) => Self.printReport(ctx, id, 'invoiceIn');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('exportationPdf', {
|
||||
description: 'Returns the exportation pdf',
|
||||
|
@ -39,17 +37,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.exportationPdf = async(ctx, reference) => {
|
||||
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"`];
|
||||
};
|
||||
Self.exportationPdf = (ctx, reference) => Self.printReport(ctx, reference, 'exportation');
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
};
|
|
@ -62,7 +62,7 @@ module.exports = Self => {
|
|||
writeStream.on('open', () => response.pipe(writeStream));
|
||||
writeStream.on('error', async error =>
|
||||
await errorHandler(image.itemFk, error, filePath));
|
||||
writeStream.on('finish', writeStream.end());
|
||||
writeStream.on('finish', () => writeStream.end());
|
||||
|
||||
writeStream.on('close', async function() {
|
||||
try {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('labelPdf', {
|
||||
description: 'Returns the item label pdf',
|
||||
|
@ -56,17 +54,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.labelPdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.labelPdf = (ctx, id) => Self.printReport(ctx, id, 'item-label');
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/item-image-queue/downloadImages')(Self);
|
||||
require('../methods/item-image-queue/download')(Self);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('driverRouteEmail', {
|
||||
description: 'Sends the driver route email with an attached PDF',
|
||||
|
@ -41,19 +39,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.driverRouteEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.driverRouteEmail = ctx => Self.sendTemplate(ctx, 'driver-route');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('driverRoutePdf', {
|
||||
description: 'Returns the driver route pdf',
|
||||
|
@ -39,17 +37,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.driverRoutePdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.driverRoutePdf = (ctx, id) => Self.printReport(ctx, id, 'driver-route');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('campaignMetricsPdf', {
|
||||
description: 'Returns the campaign metrics pdf',
|
||||
|
@ -49,17 +47,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.campaignMetricsPdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.campaignMetricsPdf = (ctx, id) => Self.printReport(ctx, id, 'supplier-campaign-metrics');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('collectionLabel', {
|
||||
description: 'Returns the collection label',
|
||||
|
@ -39,17 +37,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.collectionLabel = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.collectionLabel = (ctx, id) => Self.printReport(ctx, id, 'collection-label');
|
||||
};
|
||||
|
|
|
@ -165,18 +165,29 @@ module.exports = Self => {
|
|||
'shipped',
|
||||
'landed',
|
||||
'isDeleted',
|
||||
'routeFk'
|
||||
'routeFk',
|
||||
'nickname'
|
||||
],
|
||||
include: [
|
||||
{
|
||||
relation: 'client',
|
||||
scope: {
|
||||
fields: 'salesPersonFk'
|
||||
}
|
||||
}]
|
||||
},
|
||||
include: [
|
||||
{
|
||||
relation: 'address',
|
||||
scope: {
|
||||
fields: 'nickname'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}, myOptions);
|
||||
|
||||
args.routeFk = null;
|
||||
if (args.isWithoutNegatives === false) delete args.isWithoutNegatives;
|
||||
const updatedTicket = Object.assign({}, args);
|
||||
delete updatedTicket.ctx;
|
||||
delete updatedTicket.option;
|
||||
|
@ -224,37 +235,41 @@ module.exports = Self => {
|
|||
}
|
||||
|
||||
const changes = loggable.getChanges(originalTicket, updatedTicket);
|
||||
const oldProperties = await loggable.translateValues(Self, changes.old);
|
||||
const newProperties = await loggable.translateValues(Self, changes.new);
|
||||
const hasChanges = Object.keys(changes.old).length > 0 || Object.keys(changes.new).length > 0;
|
||||
|
||||
await models.TicketLog.create({
|
||||
originFk: args.id,
|
||||
userFk: userId,
|
||||
action: 'update',
|
||||
changedModel: 'Ticket',
|
||||
changedModelId: args.id,
|
||||
oldInstance: oldProperties,
|
||||
newInstance: newProperties
|
||||
}, myOptions);
|
||||
if (hasChanges) {
|
||||
const oldProperties = await loggable.translateValues(Self, changes.old);
|
||||
const newProperties = await loggable.translateValues(Self, changes.new);
|
||||
|
||||
const salesPersonId = originalTicket.client().salesPersonFk;
|
||||
if (salesPersonId) {
|
||||
const origin = ctx.req.headers.origin;
|
||||
await models.TicketLog.create({
|
||||
originFk: args.id,
|
||||
userFk: userId,
|
||||
action: 'update',
|
||||
changedModel: 'Ticket',
|
||||
changedModelId: args.id,
|
||||
oldInstance: oldProperties,
|
||||
newInstance: newProperties
|
||||
}, myOptions);
|
||||
|
||||
let changesMade = '';
|
||||
for (let change in newProperties) {
|
||||
let value = newProperties[change];
|
||||
let oldValue = oldProperties[change];
|
||||
const salesPersonId = originalTicket.client().salesPersonFk;
|
||||
if (salesPersonId) {
|
||||
const origin = ctx.req.headers.origin;
|
||||
|
||||
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;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('deliveryNoteEmail', {
|
||||
description: 'Sends the delivery note email with an attached PDF',
|
||||
|
@ -47,19 +45,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.deliveryNoteEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.deliveryNoteEmail = ctx => Self.sendTemplate(ctx, 'delivery-note');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('deliveryNotePdf', {
|
||||
description: 'Returns the delivery note pdf',
|
||||
|
@ -46,17 +44,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.deliveryNotePdf = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.deliveryNotePdf = (ctx, id) => Self.printReport(ctx, id, 'delivery-note');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('expeditionPalletLabel', {
|
||||
description: 'Returns the expedition pallet label',
|
||||
|
@ -39,17 +37,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.expeditionPalletLabel = async(ctx, id) => {
|
||||
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"`];
|
||||
};
|
||||
Self.expeditionPalletLabel = (ctx, id) => Self.printReport(ctx, id, 'expedition-pallet-label');
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -2,13 +2,13 @@ const models = require('vn-loopback/server/server').models;
|
|||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('ticket merge()', () => {
|
||||
const tickets = [{
|
||||
const tickets = {
|
||||
originId: 13,
|
||||
destinationId: 12,
|
||||
originShipped: Date.vnNew(),
|
||||
destinationShipped: Date.vnNew(),
|
||||
workerFk: 1
|
||||
}];
|
||||
};
|
||||
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
|
@ -37,14 +37,14 @@ describe('ticket merge()', () => {
|
|||
const options = {transaction: tx};
|
||||
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 deletedTicket = await models.Ticket.findOne({where: {id: tickets[0].originId}}, options);
|
||||
const salesTicketFuture = await models.Sale.find({where: {ticketFk: tickets[0].destinationId}}, options);
|
||||
const createdTicketLog = await models.TicketLog.find({where: {originFk: tickets.originId}}, options);
|
||||
const deletedTicket = await models.Ticket.findOne({where: {id: tickets.originId}}, options);
|
||||
const salesTicketFuture = await models.Sale.find({where: {ticketFk: tickets.destinationId}}, options);
|
||||
const chatNotificationAfterMerge = await models.Chat.find();
|
||||
|
||||
expect(createdTicketLog.length).toEqual(1);
|
||||
expect(createdTicketLog.length).toEqual(2);
|
||||
expect(deletedTicket.isDeleted).toEqual(true);
|
||||
expect(salesTicketFuture.length).toEqual(2);
|
||||
expect(chatNotificationBeforeMerge.length).toEqual(chatNotificationAfterMerge.length - 2);
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -105,8 +105,8 @@ module.exports = Self => {
|
|||
originFk: id,
|
||||
userFk: userId,
|
||||
action: 'update',
|
||||
changedModel: 'Ticket',
|
||||
changedModelId: id,
|
||||
changedModel: 'Sale',
|
||||
changedModelId: sale.id,
|
||||
oldInstance: {
|
||||
item: originalSaleData.itemFk,
|
||||
quantity: originalSaleData.quantity,
|
||||
|
@ -126,8 +126,8 @@ module.exports = Self => {
|
|||
originFk: ticketId,
|
||||
userFk: userId,
|
||||
action: 'update',
|
||||
changedModel: 'Ticket',
|
||||
changedModelId: ticketId,
|
||||
changedModel: 'Sale',
|
||||
changedModelId: sale.id,
|
||||
oldInstance: {
|
||||
item: originalSaleData.itemFk,
|
||||
quantity: originalSaleData.quantity,
|
||||
|
@ -177,16 +177,16 @@ module.exports = Self => {
|
|||
|
||||
// Update original sale
|
||||
const rest = originalSale.quantity - sale.quantity;
|
||||
query = `UPDATE sale
|
||||
query = `UPDATE sale
|
||||
SET quantity = ?
|
||||
WHERE id = ?`;
|
||||
await Self.rawSql(query, [rest, sale.id], options);
|
||||
|
||||
// Clone sale with new quantity
|
||||
query = `INSERT INTO sale (itemFk, ticketFk, concept, quantity, originalQuantity, price, discount, priceFixed,
|
||||
query = `INSERT INTO sale (itemFk, ticketFk, concept, quantity, originalQuantity, price, discount, priceFixed,
|
||||
reserved, isPicked, isPriceFixed, isAdded)
|
||||
SELECT itemFk, ?, concept, ?, originalQuantity, price, discount, priceFixed,
|
||||
reserved, isPicked, isPriceFixed, isAdded
|
||||
SELECT itemFk, ?, concept, ?, originalQuantity, price, discount, priceFixed,
|
||||
reserved, isPicked, isPriceFixed, isAdded
|
||||
FROM sale
|
||||
WHERE id = ?`;
|
||||
await Self.rawSql(query, [ticketId, sale.quantity, sale.id], options);
|
||||
|
|
|
@ -39,4 +39,5 @@ module.exports = function(Self) {
|
|||
require('../methods/ticket/isRoleAdvanced')(Self);
|
||||
require('../methods/ticket/collectionLabel')(Self);
|
||||
require('../methods/ticket/expeditionPalletLabel')(Self);
|
||||
require('../methods/ticket/saveSign')(Self);
|
||||
};
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"type": "number"
|
||||
},
|
||||
"updated": {
|
||||
"type": "date",
|
||||
"type": "date",
|
||||
"mysql": {
|
||||
"columnName": "created"
|
||||
}
|
||||
|
@ -44,6 +44,9 @@
|
|||
"isDeleted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isSigned": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"priority": {
|
||||
"type": "number"
|
||||
},
|
||||
|
@ -136,4 +139,4 @@
|
|||
"foreignKey": "zoneFk"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('extraCommunityEmail', {
|
||||
description: 'Sends the extra community email with an attached PDF',
|
||||
|
@ -74,19 +72,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.extraCommunityEmail = async ctx => {
|
||||
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();
|
||||
};
|
||||
Self.extraCommunityEmail = ctx => Self.sendTemplate(ctx, 'extra-community');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const {Report} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('extraCommunityPdf', {
|
||||
description: 'Returns the extra community pdf',
|
||||
|
@ -11,6 +9,16 @@ module.exports = Self => {
|
|||
description: 'The recipient id',
|
||||
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',
|
||||
type: 'date'
|
||||
|
@ -73,17 +81,5 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.extraCommunityPdf = async ctx => {
|
||||
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"`];
|
||||
};
|
||||
Self.extraCommunityPdf = ctx => Self.printReport(ctx, null, 'extra-community');
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="Travels/extraCommunityFilter"
|
||||
filter="::$ctrl.filter"
|
||||
user-params="::$ctrl.defaultFilter"
|
||||
data="travels"
|
||||
order="landed ASC, shipped ASC, travelFk, loadPriority, agencyModeFk, supplierName, evaNotes"
|
||||
limit="20"
|
||||
|
|
|
@ -141,8 +141,11 @@ class Controller extends Section {
|
|||
|
||||
get reportParams() {
|
||||
const userParams = this.$.model.userParams;
|
||||
const currentFilter = this.$.model.currentFilter;
|
||||
|
||||
return Object.assign({
|
||||
authorization: this.vnToken.token
|
||||
authorization: this.vnToken.token,
|
||||
filter: currentFilter
|
||||
}, userParams);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -47,6 +47,10 @@ module.exports = Self => {
|
|||
if (typeof options == 'object')
|
||||
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({
|
||||
where: {
|
||||
workerFk: args.workerId,
|
||||
|
@ -60,8 +64,6 @@ module.exports = Self => {
|
|||
const oldState = workerTimeControlMail.state;
|
||||
const oldReason = workerTimeControlMail.reason;
|
||||
|
||||
if (oldState == args.state) throw new UserError('Already has this status');
|
||||
|
||||
await workerTimeControlMail.updateAttributes({
|
||||
state: args.state,
|
||||
reason: args.reason || null
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
"year": {
|
||||
"type": "number"
|
||||
},
|
||||
"month": {
|
||||
"type": "number"
|
||||
},
|
||||
"week": {
|
||||
"type": "number"
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ module.exports = Self => {
|
|||
require('../methods/worker-time-control/sendMail')(Self);
|
||||
require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self);
|
||||
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
|
||||
require('../methods/worker-time-control/getMailStates')(Self);
|
||||
|
||||
Self.rewriteDbError(function(err) {
|
||||
if (err.code === 'ER_DUP_ENTRY')
|
||||
|
|
|
@ -25,11 +25,11 @@
|
|||
<div class="totalBox vn-mb-sm" style="text-align: center;">
|
||||
<h6>{{'Contract' | translate}} #{{$ctrl.businessId}}</h6>
|
||||
<div>
|
||||
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed || 0}}
|
||||
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed || 0}}
|
||||
{{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}}
|
||||
</div>
|
||||
<div>
|
||||
{{'Spent' | translate}} {{$ctrl.contractHolidays.hoursEnjoyed || 0}}
|
||||
{{'Spent' | translate}} {{$ctrl.contractHolidays.hoursEnjoyed || 0}}
|
||||
{{'of' | translate}} {{$ctrl.contractHolidays.totalHours || 0}} {{'hours' | translate}}
|
||||
</div>
|
||||
<div>
|
||||
|
@ -40,11 +40,11 @@
|
|||
<div class="totalBox" style="text-align: center;">
|
||||
<h6>{{'Year' | translate}} {{$ctrl.year}}</h6>
|
||||
<div>
|
||||
{{'Used' | translate}} {{$ctrl.yearHolidays.holidaysEnjoyed || 0}}
|
||||
{{'Used' | translate}} {{$ctrl.yearHolidays.holidaysEnjoyed || 0}}
|
||||
{{'of' | translate}} {{$ctrl.yearHolidays.totalHolidays || 0}} {{'days' | translate}}
|
||||
</div>
|
||||
<div>
|
||||
{{'Spent' | translate}} {{$ctrl.yearHolidays.hoursEnjoyed || 0}}
|
||||
{{'Spent' | translate}} {{$ctrl.yearHolidays.hoursEnjoyed || 0}}
|
||||
{{'of' | translate}} {{$ctrl.yearHolidays.totalHours || 0}} {{'hours' | translate}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -66,7 +66,7 @@
|
|||
order="businessFk DESC"
|
||||
limit="5">
|
||||
<tpl-item>
|
||||
<div>#{{businessFk}}</div>
|
||||
<div>#{{businessFk}}</div>
|
||||
<div class="text-caption text-secondary">
|
||||
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
|
||||
</div>
|
||||
|
@ -87,17 +87,17 @@
|
|||
<vn-chip>
|
||||
<vn-avatar class="festive">
|
||||
</vn-avatar>
|
||||
<span translate>Festive</span>
|
||||
<span translate>Festive</span>
|
||||
</vn-chip>
|
||||
<vn-chip>
|
||||
<vn-avatar class="today">
|
||||
</vn-avatar>
|
||||
<span translate>Current day</span>
|
||||
<span translate>Current day</span>
|
||||
</vn-chip>
|
||||
</div>
|
||||
</div>
|
||||
</vn-side-menu>
|
||||
<vn-confirm
|
||||
<vn-confirm
|
||||
vn-id="confirm"
|
||||
message="This item will be deleted"
|
||||
question="Are you sure you want to continue?">
|
||||
|
|
|
@ -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() {
|
||||
if (!this.worker || !this.worker.iban) return;
|
||||
|
||||
let bankEntityId = parseInt(this.worker.iban.substr(4, 4));
|
||||
let filter = {where: {id: bankEntityId}};
|
||||
|
||||
if (this.ibanCountry != 'ES') return;
|
||||
|
||||
this.$http.get(`BankEntities`, {filter}).then(response => {
|
||||
const hasData = response.data && response.data[0];
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="WorkerTimeControls/filter"
|
||||
filter="::$ctrl.filter"
|
||||
filter="::$ctrl.filter"
|
||||
data="$ctrl.hours">
|
||||
</vn-crud-model>
|
||||
<vn-card class="vn-pa-lg vn-w-lg">
|
||||
|
@ -16,7 +16,7 @@
|
|||
{{::weekday.dated | date: 'MMMM'}}
|
||||
</span>
|
||||
</div>
|
||||
<vn-chip
|
||||
<vn-chip
|
||||
title="{{::weekday.event.name}}"
|
||||
ng-class="{invisible: !weekday.event}">
|
||||
<vn-avatar
|
||||
|
@ -66,7 +66,7 @@
|
|||
</vn-td>
|
||||
</vn-tr>
|
||||
<vn-tr>
|
||||
<vn-td center ng-repeat="weekday in $ctrl.weekDays">
|
||||
<vn-td center ng-repeat="weekday in $ctrl.weekDays">
|
||||
<vn-icon-button
|
||||
icon="add_circle"
|
||||
vn-tooltip="Add time"
|
||||
|
@ -78,27 +78,43 @@
|
|||
</vn-table>
|
||||
</vn-card>
|
||||
|
||||
<vn-button-bar class="vn-pa-xs vn-w-lg">
|
||||
<vn-button
|
||||
label="Satisfied"
|
||||
<vn-button-bar ng-show="$ctrl.state" class="vn-w-lg">
|
||||
<vn-button
|
||||
label="Satisfied"
|
||||
disabled="$ctrl.state == 'CONFIRMED'"
|
||||
ng-if="$ctrl.isHimSelf"
|
||||
ng-click="$ctrl.isSatisfied()">
|
||||
</vn-button>
|
||||
<vn-button
|
||||
label="Not satisfied"
|
||||
<vn-button
|
||||
label="Not satisfied"
|
||||
disabled="$ctrl.state == 'REVISE'"
|
||||
ng-if="$ctrl.isHimSelf"
|
||||
ng-click="reason.show()">
|
||||
</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-side-menu side="right">
|
||||
<div class="vn-pa-md">
|
||||
<div class="totalBox" style="text-align: center;">
|
||||
<h6 translate>Hours</h6>
|
||||
<vn-label-value
|
||||
label="Week total"
|
||||
<vn-label-value
|
||||
label="Week total"
|
||||
value="{{$ctrl.weekTotalHours}} h.">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Finish at"
|
||||
<vn-label-value
|
||||
label="Finish at"
|
||||
value="{{$ctrl.getFinishTime()}}">
|
||||
</vn-label-value>
|
||||
</div>
|
||||
|
@ -106,6 +122,8 @@
|
|||
vn-id="calendar"
|
||||
class="vn-pt-md"
|
||||
ng-model="$ctrl.date"
|
||||
format-week="$ctrl.formatWeek($element)"
|
||||
on-move="$ctrl.getMailStates($date)"
|
||||
has-events="$ctrl.hasEvents($day)">
|
||||
</vn-calendar>
|
||||
</div>
|
||||
|
@ -166,15 +184,31 @@
|
|||
vn-id="reason"
|
||||
on-accept="$ctrl.isUnsatisfied()">
|
||||
<tpl-body>
|
||||
<vn-textarea
|
||||
label="Reason"
|
||||
ng-model="$ctrl.reason"
|
||||
required="true"
|
||||
rows="3">
|
||||
</vn-textarea>
|
||||
<div class="reasonDialog">
|
||||
<vn-textarea
|
||||
label="Reason"
|
||||
ng-model="$ctrl.reason"
|
||||
disabled="!$ctrl.isHimSelf"
|
||||
rows="5"
|
||||
required="true">
|
||||
</vn-textarea>
|
||||
</div>
|
||||
</tpl-body>
|
||||
<tpl-buttons>
|
||||
<tpl-buttons ng-if="$ctrl.isHimSelf">
|
||||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||
<button response="accept" translate>Save</button>
|
||||
</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>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import ngModule from '../module';
|
||||
import Section from 'salix/components/section';
|
||||
import './style.scss';
|
||||
import UserError from 'core/lib/user-error';
|
||||
|
||||
class Controller extends Section {
|
||||
constructor($element, $, vnWeekDays) {
|
||||
|
@ -24,12 +25,31 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
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() {
|
||||
return this._worker;
|
||||
}
|
||||
|
||||
get weekNumber() {
|
||||
return this.getWeekNumber(this.date);
|
||||
}
|
||||
|
||||
set weekNumber(value) {
|
||||
this._weekNumber = value;
|
||||
}
|
||||
|
||||
set worker(value) {
|
||||
this._worker = value;
|
||||
}
|
||||
|
@ -68,6 +88,27 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
getWeekNumber(currentDate) {
|
||||
const startDate = new Date(currentDate.getFullYear(), 0, 1);
|
||||
let days = Math.floor((currentDate - startDate) /
|
||||
(24 * 60 * 60 * 1000));
|
||||
return Math.ceil(days / 7);
|
||||
getWeekNumber(date) {
|
||||
const tempDate = new Date(date);
|
||||
let dayOfWeek = tempDate.getDay();
|
||||
dayOfWeek = (dayOfWeek === 0) ? 7 : dayOfWeek;
|
||||
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() {
|
||||
const weekNumber = this.getWeekNumber(this.date);
|
||||
const params = {
|
||||
workerId: this.worker.id,
|
||||
year: this.date.getFullYear(),
|
||||
week: weekNumber,
|
||||
week: this.weekNumber,
|
||||
state: 'CONFIRMED'
|
||||
};
|
||||
const query = `WorkerTimeControls/updateWorkerTimeControlMail`;
|
||||
this.$http.post(query, params).then(() => {
|
||||
this.getMailStates(this.date);
|
||||
this.getWeekData();
|
||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||
});
|
||||
}
|
||||
|
||||
isUnsatisfied() {
|
||||
const weekNumber = this.getWeekNumber(this.date);
|
||||
if (!this.reason) throw new UserError(`You must indicate a reason`);
|
||||
|
||||
const params = {
|
||||
workerId: this.worker.id,
|
||||
year: this.date.getFullYear(),
|
||||
week: weekNumber,
|
||||
week: this.weekNumber,
|
||||
state: 'REVISE',
|
||||
reason: this.reason
|
||||
};
|
||||
const query = `WorkerTimeControls/updateWorkerTimeControlMail`;
|
||||
this.$http.post(query, params).then(() => {
|
||||
this.getMailStates(this.date);
|
||||
this.getWeekData();
|
||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||
});
|
||||
}
|
||||
|
||||
changeState(state, reason) {
|
||||
this.state = state;
|
||||
this.reason = reason;
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
save() {
|
||||
try {
|
||||
const entry = this.selectedRow;
|
||||
|
@ -345,6 +400,77 @@ class Controller extends Section {
|
|||
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'];
|
||||
|
|
|
@ -5,12 +5,14 @@ describe('Component vnWorkerTimeControl', () => {
|
|||
let $scope;
|
||||
let $element;
|
||||
let controller;
|
||||
let $httpParamSerializer;
|
||||
|
||||
beforeEach(ngModule('worker'));
|
||||
|
||||
beforeEach(inject(($componentController, $rootScope, $stateParams, _$httpBackend_) => {
|
||||
beforeEach(inject(($componentController, $rootScope, $stateParams, _$httpBackend_, _$httpParamSerializer_) => {
|
||||
$stateParams.id = 1;
|
||||
$httpBackend = _$httpBackend_;
|
||||
$httpParamSerializer = _$httpParamSerializer_;
|
||||
$scope = $rootScope.$new();
|
||||
$element = angular.element('<vn-worker-time-control></vn-worker-time-control>');
|
||||
controller = $componentController('vnWorkerTimeControl', {$element, $scope});
|
||||
|
@ -82,6 +84,9 @@ describe('Component vnWorkerTimeControl', () => {
|
|||
$httpBackend.whenRoute('GET', 'Workers/:id/getWorkedHours')
|
||||
.respond(response);
|
||||
|
||||
$httpBackend.whenRoute('GET', 'WorkerTimeControlMails')
|
||||
.respond([]);
|
||||
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
let weekOffset = today.getDay() - 1;
|
||||
|
@ -97,7 +102,6 @@ describe('Component vnWorkerTimeControl', () => {
|
|||
controller.ended = ended;
|
||||
|
||||
controller.getWorkedHours(controller.started, controller.ended);
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.weekDays.length).toEqual(7);
|
||||
|
@ -152,5 +156,120 @@ describe('Component vnWorkerTimeControl', () => {
|
|||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,4 +13,10 @@ Entry removed: Fichada borrada
|
|||
The entry type can't be empty: El tipo de fichada no puede quedar vacía
|
||||
Satisfied: 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
|
||||
|
|
|
@ -14,7 +14,7 @@ vn-worker-time-control {
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px 0;
|
||||
|
||||
|
||||
& > vn-icon {
|
||||
color: $color-font-secondary;
|
||||
padding-right: 1px;
|
||||
|
@ -24,8 +24,29 @@ vn-worker-time-control {
|
|||
.totalBox {
|
||||
max-width: none
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.reasonDialog{
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
.edit-time-entry {
|
||||
width: 200px
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.confirmed {
|
||||
color: #97B92F;
|
||||
}
|
||||
|
||||
.revise {
|
||||
color: #f61e1e;
|
||||
}
|
||||
|
||||
.sended {
|
||||
color: #d19b25;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,9 @@
|
|||
"inflation": {
|
||||
"type": "number"
|
||||
},
|
||||
"m3Max": {
|
||||
"type": "number"
|
||||
},
|
||||
"itemMaxSize": {
|
||||
"type": "number"
|
||||
}
|
||||
|
|
|
@ -30,14 +30,21 @@
|
|||
rule>
|
||||
</vn-autocomplete>
|
||||
<vn-input-number
|
||||
vn-one
|
||||
label="Maximum m³"
|
||||
ng-model="$ctrl.zone.itemMaxSize"
|
||||
min="0"
|
||||
step="0.01"
|
||||
vn-acl="deliveryBoss"
|
||||
rule>
|
||||
</vn-input-number>
|
||||
vn-one
|
||||
label="Max m³"
|
||||
ng-model="$ctrl.zone.itemMaxSize"
|
||||
min="0"
|
||||
vn-acl="deliveryBoss"
|
||||
rule>
|
||||
</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-input-number
|
||||
|
|
|
@ -13,6 +13,7 @@ Indefinitely: Indefinido
|
|||
Inflation: Inflación
|
||||
Locations: Localizaciones
|
||||
Maximum m³: M³ máximo
|
||||
Max m³: Medida máxima
|
||||
New zone: Nueva zona
|
||||
One day: Un día
|
||||
Pick up: Recogida
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"form-data": "^4.0.0",
|
||||
"fs-extra": "^5.0.0",
|
||||
"ftps": "^1.2.0",
|
||||
"gm": "^1.25.0",
|
||||
"got": "^10.7.0",
|
||||
"helmet": "^3.21.2",
|
||||
"i18n": "^0.8.4",
|
||||
|
@ -3625,6 +3626,16 @@
|
|||
"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": {
|
||||
"version": "1.1.0",
|
||||
"dev": true,
|
||||
|
@ -9284,6 +9295,67 @@
|
|||
"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": {
|
||||
"version": "3.1.2",
|
||||
"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": {
|
||||
"version": "1.1.0",
|
||||
"dev": true
|
||||
|
@ -32611,6 +32693,63 @@
|
|||
"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": {
|
||||
"version": "3.1.2",
|
||||
"requires": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "salix-back",
|
||||
"version": "23.10.01",
|
||||
"version": "23.12.01",
|
||||
"author": "Verdnatura Levante SL",
|
||||
"description": "Salix backend",
|
||||
"license": "GPL-3.0",
|
||||
|
@ -20,6 +20,7 @@
|
|||
"form-data": "^4.0.0",
|
||||
"fs-extra": "^5.0.0",
|
||||
"ftps": "^1.2.0",
|
||||
"gm": "^1.25.0",
|
||||
"got": "^10.7.0",
|
||||
"helmet": "^3.21.2",
|
||||
"i18n": "^0.8.4",
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
</p>
|
||||
<h4 style="text-align: center; margin-top: 10%">{{$t('Agree') | uppercase}}</h4>
|
||||
<p style="margin-top: 8%; text-align: justify">
|
||||
{{$t('Date')}} {{client.payed | date('%d-%m-%Y')}} {{$t('Compensate')}} {{client.amountPaid}} €
|
||||
{{$t('From client')}} {{client.name}} {{$t('Against the balance of')}}: {{client.invoiceFk}}.
|
||||
{{$t('Date')}} {{formatDate(receipt.payed, '%d-%m-%Y')}} {{$t('Compensate')}} {{receipt.amountPaid}} €
|
||||
{{$t('From client')}} {{client.name}} {{$t('Against the balance of')}}: {{receipt.description}}.
|
||||
</p>
|
||||
<p style="margin-top: 8%">
|
||||
{{$t('Reception')}} <span style="color: blue">administracion@verdnatura.es</span>
|
||||
|
|
|
@ -1,12 +1,31 @@
|
|||
const vnReport = require('../../../core/mixins/vn-report.js');
|
||||
const app = require('vn-loopback/server/server');
|
||||
|
||||
module.exports = {
|
||||
name: 'balance-compensation',
|
||||
mixins: [vnReport],
|
||||
async serverPrefetch() {
|
||||
this.client = await this.findOneFromDef('client', [this.id]);
|
||||
this.checkMainEntity(this.client);
|
||||
this.company = await this.findOneFromDef('company', [this.id]);
|
||||
this.receipt = await app.models.Receipt.findOne({
|
||||
fields: ['amountPaid', 'payed', 'clientFk', 'companyFk', 'description'],
|
||||
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: {
|
||||
id: {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue