Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4074-download-user-ACL
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Alexandre Riera 2023-03-29 07:16:34 +02:00
commit 4efcf09c68
218 changed files with 4442 additions and 8669 deletions

View File

@ -4,5 +4,7 @@
"files.eol": "\n", "files.eol": "\n",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": true
} },
"search.useIgnoreFiles": false,
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
} }

View File

@ -5,13 +5,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2312.01] - 2023-04-06
### Added
- (Monitor tickets) Muestra un icono al lado de la zona, si el ticket es frágil y se envía por agencia
### Changed
- (Envíos -> Extra comunitarios) Se agrupan las entradas del mismo travel. Añadidos campos Referencia y Importe.
### Fixed
-
## [2310.01] - 2023-03-23 ## [2310.01] - 2023-03-23
### Added ### Added
- - (Trabajadores -> Control de horario) Ahora se puede confirmar/no confirmar el registro horario de cada semana desde esta sección
### Changed
-
### Fixed ### Fixed
- (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo" - (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo"

View File

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

View File

@ -30,16 +30,23 @@ module.exports = Self => {
const recipient = to.replace('@', ''); const recipient = to.replace('@', '');
if (sender.name != recipient) { if (sender.name != recipient) {
await models.Chat.create({ const chat = await models.Chat.create({
senderFk: sender.id, senderFk: sender.id,
recipient: to, recipient: to,
dated: Date.vnNew(), dated: Date.vnNew(),
checkUserStatus: 0, checkUserStatus: 0,
message: message, message: message,
status: 0, status: 'sending',
attempts: 0 attempts: 0
}); });
try {
await Self.sendMessage(chat.senderFk, chat.recipient, chat.message);
await Self.updateChat(chat, 'sent');
} catch (error) {
await Self.updateChat(chat, 'error', error);
}
return true; return true;
} }

View File

@ -24,18 +24,13 @@ module.exports = Self => {
} }
}); });
Self.sendCheckingPresence = async(ctx, recipientId, message, options) => { Self.sendCheckingPresence = async(ctx, recipientId, message) => {
if (!recipientId) return false; if (!recipientId) return false;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const sender = await models.Account.findById(userId); const sender = await models.Account.findById(userId, {fields: ['id']});
const recipient = await models.Account.findById(recipientId, null, myOptions); const recipient = await models.Account.findById(recipientId, null);
// Prevent sending messages to yourself // Prevent sending messages to yourself
if (recipientId == userId) return false; if (recipientId == userId) return false;
@ -46,16 +41,23 @@ module.exports = Self => {
if (process.env.NODE_ENV == 'test') if (process.env.NODE_ENV == 'test')
message = `[Test:Environment to user ${userId}] ` + message; message = `[Test:Environment to user ${userId}] ` + message;
await models.Chat.create({ const chat = await models.Chat.create({
senderFk: sender.id, senderFk: sender.id,
recipient: `@${recipient.name}`, recipient: `@${recipient.name}`,
dated: Date.vnNew(), dated: Date.vnNew(),
checkUserStatus: 1, checkUserStatus: 1,
message: message, message: message,
status: 0, status: 'sending',
attempts: 0 attempts: 0
}); });
try {
await Self.sendCheckingUserStatus(chat);
await Self.updateChat(chat, 'sent');
} catch (error) {
await Self.updateChat(chat, 'error', error);
}
return true; return true;
}; };
}; };

View File

@ -3,7 +3,6 @@ module.exports = Self => {
Self.remoteMethodCtx('sendQueued', { Self.remoteMethodCtx('sendQueued', {
description: 'Send a RocketChat message', description: 'Send a RocketChat message',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [],
returns: { returns: {
type: 'object', type: 'object',
root: true root: true
@ -16,14 +15,17 @@ module.exports = Self => {
Self.sendQueued = async() => { Self.sendQueued = async() => {
const models = Self.app.models; const models = Self.app.models;
const maxAttempts = 3;
const sentStatus = 1;
const errorStatus = 2;
const chats = await models.Chat.find({ const chats = await models.Chat.find({
where: { where: {
status: {neq: sentStatus}, status: {
attempts: {lt: maxAttempts} nin: [
'sent',
'sending'
]
},
attempts: {lt: 3}
} }
}); });
@ -31,16 +33,16 @@ module.exports = Self => {
if (chat.checkUserStatus) { if (chat.checkUserStatus) {
try { try {
await Self.sendCheckingUserStatus(chat); await Self.sendCheckingUserStatus(chat);
await updateChat(chat, sentStatus); await Self.updateChat(chat, 'sent');
} catch (error) { } catch (error) {
await updateChat(chat, errorStatus, error); await Self.updateChat(chat, 'error', error);
} }
} else { } else {
try { try {
await Self.sendMessage(chat.senderFk, chat.recipient, chat.message); await Self.sendMessage(chat.senderFk, chat.recipient, chat.message);
await updateChat(chat, sentStatus); await Self.updateChat(chat, 'sent');
} catch (error) { } catch (error) {
await updateChat(chat, errorStatus, error); await Self.updateChat(chat, 'error', error);
} }
} }
} }
@ -128,15 +130,17 @@ module.exports = Self => {
* @param {object} chat - The chat * @param {object} chat - The chat
* @param {string} status - The new status * @param {string} status - The new status
* @param {string} error - The error * @param {string} error - The error
* @param {object} options - Query options
* @return {Promise} - The request promise * @return {Promise} - The request promise
*/ */
async function updateChat(chat, status, error) {
Self.updateChat = async(chat, status, error) => {
return chat.updateAttributes({ return chat.updateAttributes({
status: status, status: status,
attempts: ++chat.attempts, attempts: ++chat.attempts,
error: error error: error
}); });
} };
/** /**
* Returns the current user status on Rocketchat * Returns the current user status on Rocketchat

View File

@ -10,7 +10,7 @@ describe('Chat sendCheckingPresence()', () => {
const chat = { const chat = {
checkUserStatus: 1, checkUserStatus: 1,
status: 0, status: 'pending',
attempts: 0 attempts: 0
}; };
@ -27,7 +27,7 @@ describe('Chat sendCheckingPresence()', () => {
const chat = { const chat = {
checkUserStatus: 0, checkUserStatus: 0,
status: 0, status: 'pending',
attempts: 0 attempts: 0
}; };

View File

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

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('setSaleQuantity()', () => { describe('setSaleQuantity()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should change quantity sale', async() => { it('should change quantity sale', async() => {
const tx = await models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});

View File

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

View File

@ -132,7 +132,7 @@ module.exports = Self => {
WHERE u.id = ?`, [userId], options); WHERE u.id = ?`, [userId], options);
let roles = []; let roles = [];
for (role of result) for (const role of result)
roles.push(role.name); roles.push(role.name);
return roles; return roles;

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
ALTER TABLE vn.invoiceOutSerial
ADD `type` ENUM('global', 'quick') DEFAULT NULL NULL;
UPDATE vn.invoiceOutSerial
SET type = 'global'
WHERE code IN ('A','V');

View File

@ -2,13 +2,15 @@ DROP FUNCTION IF EXISTS `vn`.`invoiceOut_getWeight`;
DELIMITER $$ DELIMITER $$
$$ $$
CREATE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceOut_getWeight`(vInvoice VARCHAR(15)) RETURNS decimal(10,2) CREATE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceOut_getWeight`(
vInvoiceRef VARCHAR(15)
)RETURNS decimal(10,2)
READS SQL DATA READS SQL DATA
BEGIN BEGIN
/** /**
* Calcula el peso de una factura emitida * Calcula el peso de una factura emitida
* *
* @param vInvoice Id de la factura * @param vInvoiceRef referencia de la factura
* @return vTotalWeight peso de la factura * @return vTotalWeight peso de la factura
*/ */
DECLARE vTotalWeight DECIMAL(10,2); DECLARE vTotalWeight DECIMAL(10,2);
@ -22,7 +24,7 @@ BEGIN
JOIN item i ON i.id = s.itemFk JOIN item i ON i.id = s.itemFk
JOIN itemCost ic ON ic.itemFk = i.id JOIN itemCost ic ON ic.itemFk = i.id
AND ic.warehouseFk = t.warehouseFk AND ic.warehouseFk = t.warehouseFk
WHERE t.refFk = vInvoice WHERE t.refFk = vInvoiceRef
AND i.intrastatFk; AND i.intrastatFk;
RETURN vTotalWeight; RETURN vTotalWeight;

View File

@ -0,0 +1,6 @@
UPDATE `vn`.`report`
SET `method`='InvoiceOuts/{refFk}/invoice-out-pdf'
WHERE name='invoice';
ALTER TABLE `vn`.`printQueue` MODIFY COLUMN printerFk tinyint(3) unsigned DEFAULT 82 NOT NULL;

View File

@ -0,0 +1,34 @@
DROP FUNCTION IF EXISTS `vn`.`invoiceOut_getMaxIssued`;
DELIMITER $$
$$
CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceOut_getMaxIssued`(
vSerial VARCHAR(2),
vCompanyFk INT,
vYear INT
) RETURNS DATE
READS SQL DATA
BEGIN
/**
* Retorna la fecha a partir de la cual es válido emitir una factura
*
* @param vSerial Serie de facturación
* @param vCompanyFk Empresa factura emitida
* @param vYear Año contable
* @return vInvoiceOutIssued fecha factura válida
*/
DECLARE vInvoiceOutIssued DATE;
DECLARE vFirstDayOfYear DATE;
SET vFirstDayOfYear := MAKEDATE(vYear, 1);
SELECT IFNULL(MAX(io.issued), vFirstDayOfYear) INTO vInvoiceOutIssued
FROM invoiceOut io
WHERE io.serial = vSerial
AND io.companyFk = vCompanyFk
AND io.issued BETWEEN vFirstDayOfYear
AND util.lastDayOfYear(vFirstDayOfYear);
RETURN vInvoiceOutIssued;
END$$
DELIMITER ;

View File

@ -0,0 +1,258 @@
DROP PROCEDURE IF EXISTS `vn`.`invoiceOut_new`;
DELIMITER $$
$$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceOut_new`(
vSerial VARCHAR(255),
vInvoiceDate DATE,
vTaxArea VARCHAR(25),
OUT vNewInvoiceId INT)
BEGIN
/**
* Creación de facturas emitidas.
* requiere previamente tabla ticketToInvoice(id).
*
* @param vSerial serie a la cual se hace la factura
* @param vInvoiceDate fecha de la factura
* @param vTaxArea tipo de iva en relacion a la empresa y al cliente
* @param vNewInvoiceId id de la factura que se acaba de generar
* @return vNewInvoiceId
*/
DECLARE vIsAnySaleToInvoice BOOL;
DECLARE vIsAnyServiceToInvoice BOOL;
DECLARE vNewRef VARCHAR(255);
DECLARE vWorker INT DEFAULT account.myUser_getId();
DECLARE vCompanyFk INT;
DECLARE vInterCompanyFk INT;
DECLARE vClientFk INT;
DECLARE vCplusStandardInvoiceTypeFk INT DEFAULT 1;
DECLARE vCplusCorrectingInvoiceTypeFk INT DEFAULT 6;
DECLARE vCplusSimplifiedInvoiceTypeFk INT DEFAULT 2;
DECLARE vCorrectingSerial VARCHAR(1) DEFAULT 'R';
DECLARE vSimplifiedSerial VARCHAR(1) DEFAULT 'S';
DECLARE vNewInvoiceInFk INT;
DECLARE vIsInterCompany BOOL DEFAULT FALSE;
DECLARE vIsCEESerial BOOL DEFAULT FALSE;
DECLARE vIsCorrectInvoiceDate BOOL;
DECLARE vMaxShipped DATE;
SET vInvoiceDate = IFNULL(vInvoiceDate, util.CURDATE());
SELECT t.clientFk,
t.companyFk,
MAX(DATE(t.shipped)),
DATE(vInvoiceDate) >= invoiceOut_getMaxIssued(
vSerial,
t.companyFk,
YEAR(vInvoiceDate))
INTO vClientFk,
vCompanyFk,
vMaxShipped,
vIsCorrectInvoiceDate
FROM ticketToInvoice tt
JOIN ticket t ON t.id = tt.id;
IF(vMaxShipped > vInvoiceDate) THEN
CALL util.throw("Invoice date can't be less than max date");
END IF;
IF NOT vIsCorrectInvoiceDate THEN
CALL util.throw('Exists an invoice with a previous date');
END IF;
-- Eliminem de ticketToInvoice els tickets que no han de ser facturats
DELETE ti.*
FROM ticketToInvoice ti
JOIN ticket t ON t.id = ti.id
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
JOIN supplier su ON su.id = t.companyFk
JOIN client c ON c.id = t.clientFk
LEFT JOIN itemTaxCountry itc ON itc.itemFk = i.id AND itc.countryFk = su.countryFk
WHERE (YEAR(t.shipped) < 2001 AND t.isDeleted)
OR c.isTaxDataChecked = FALSE
OR t.isDeleted
OR c.hasToInvoice = FALSE
OR itc.id IS NULL;
SELECT SUM(s.quantity * s.price * (100 - s.discount)/100) <> 0
INTO vIsAnySaleToInvoice
FROM ticketToInvoice t
JOIN sale s ON s.ticketFk = t.id;
SELECT COUNT(*) > 0 INTO vIsAnyServiceToInvoice
FROM ticketToInvoice t
JOIN ticketService ts ON ts.ticketFk = t.id;
IF (vIsAnySaleToInvoice OR vIsAnyServiceToInvoice)
AND (vCorrectingSerial = vSerial OR NOT hasAnyNegativeBase())
THEN
-- el trigger añade el siguiente Id_Factura correspondiente a la vSerial
INSERT INTO invoiceOut(
ref,
serial,
issued,
clientFk,
dued,
companyFk,
cplusInvoiceType477Fk
)
SELECT
1,
vSerial,
vInvoiceDate,
vClientFk,
getDueDate(vInvoiceDate, dueDay),
vCompanyFk,
IF(vSerial = vCorrectingSerial,
vCplusCorrectingInvoiceTypeFk,
IF(vSerial = vSimplifiedSerial,
vCplusSimplifiedInvoiceTypeFk,
vCplusStandardInvoiceTypeFk))
FROM client
WHERE id = vClientFk;
SET vNewInvoiceId = LAST_INSERT_ID();
SELECT `ref`
INTO vNewRef
FROM invoiceOut
WHERE id = vNewInvoiceId;
UPDATE ticket t
JOIN ticketToInvoice ti ON ti.id = t.id
SET t.refFk = vNewRef;
DROP TEMPORARY TABLE IF EXISTS tmp.updateInter;
CREATE TEMPORARY TABLE tmp.updateInter ENGINE = MEMORY
SELECT s.id,ti.id ticket_id,vWorker Id_Trabajador
FROM ticketToInvoice ti
LEFT JOIN ticketState ts ON ti.id = ts.ticket
JOIN state s
WHERE IFNULL(ts.alertLevel,0) < 3 and s.`code` = getAlert3State(ti.id);
INSERT INTO ticketTracking(stateFk,ticketFk,workerFk)
SELECT * FROM tmp.updateInter;
INSERT INTO ticketLog (action, userFk, originFk, description)
SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef)
FROM ticketToInvoice ti;
CALL invoiceExpenceMake(vNewInvoiceId);
CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
UPDATE invoiceOut io
JOIN (
SELECT SUM(amount) total
FROM invoiceOutExpence
WHERE invoiceOutFk = vNewInvoiceId
) base
JOIN (
SELECT SUM(vat) total
FROM invoiceOutTax
WHERE invoiceOutFk = vNewInvoiceId
) vat
SET io.amount = base.total + vat.total
WHERE io.id = vNewInvoiceId;
DROP TEMPORARY TABLE tmp.updateInter;
SELECT COUNT(*), id
INTO vIsInterCompany, vInterCompanyFk
FROM company
WHERE clientFk = vClientFk;
IF (vIsInterCompany) THEN
INSERT INTO invoiceIn(supplierFk, supplierRef, issued, companyFk)
SELECT vCompanyFk, vNewRef, vInvoiceDate, vInterCompanyFk;
SET vNewInvoiceInFk = LAST_INSERT_ID();
DROP TEMPORARY TABLE IF EXISTS tmp.ticket;
CREATE TEMPORARY TABLE tmp.ticket
(KEY (ticketFk))
ENGINE = MEMORY
SELECT id ticketFk
FROM ticketToInvoice;
CALL `ticket_getTax`('NATIONAL');
SET @vTaxableBaseServices := 0.00;
SET @vTaxCodeGeneral := NULL;
INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk)
SELECT vNewInvoiceInFk,
@vTaxableBaseServices,
sub.expenceFk,
sub.taxTypeSageFk,
sub.transactionTypeSageFk
FROM (
SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase,
i.expenceFk,
i.taxTypeSageFk,
i.transactionTypeSageFk,
@vTaxCodeGeneral := i.taxClassCodeFk
FROM tmp.ticketServiceTax tst
JOIN invoiceOutTaxConfig i ON i.taxClassCodeFk = tst.code
WHERE i.isService
HAVING taxableBase
) sub;
INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk)
SELECT vNewInvoiceInFk,
SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral,
@vTaxableBaseServices, 0) taxableBase,
i.expenceFk,
i.taxTypeSageFk ,
i.transactionTypeSageFk
FROM tmp.ticketTax tt
JOIN invoiceOutTaxConfig i ON i.taxClassCodeFk = tt.code
WHERE !i.isService
GROUP BY tt.pgcFk
HAVING taxableBase
ORDER BY tt.priority;
CALL invoiceInDueDay_calculate(vNewInvoiceInFk);
SELECT COUNT(*) INTO vIsCEESerial
FROM invoiceOutSerial
WHERE code = vSerial;
IF vIsCEESerial THEN
INSERT INTO invoiceInIntrastat (
invoiceInFk,
intrastatFk,
amount,
stems,
countryFk,
net)
SELECT
vNewInvoiceInFk,
i.intrastatFk,
SUM(CAST((s.quantity * s.price * (100 - s.discount) / 100 ) AS DECIMAL(10, 2))),
SUM(CAST(IFNULL(i.stems, 1) * s.quantity AS DECIMAL(10, 2))),
su.countryFk,
CAST(SUM(IFNULL(i.stems, 1)
* s.quantity
* IF(ic.grams, ic.grams, IFNULL(i.weightByPiece, 0)) / 1000) AS DECIMAL(10, 2))
FROM sale s
JOIN ticket t ON s.ticketFk = t.id
JOIN supplier su ON su.id = t.companyFk
JOIN item i ON i.id = s.itemFk
LEFT JOIN itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk
WHERE t.refFk = vNewRef
GROUP BY i.intrastatFk;
END IF;
DROP TEMPORARY TABLE tmp.ticket;
DROP TEMPORARY TABLE tmp.ticketAmount;
DROP TEMPORARY TABLE tmp.ticketTax;
DROP TEMPORARY TABLE tmp.ticketServiceTax;
END IF;
END IF;
DROP TEMPORARY TABLE `ticketToInvoice`;
END$$
DELIMITER ;

View File

@ -0,0 +1,141 @@
DROP PROCEDURE IF EXISTS `vn`.`ticketPackaging_add`;
DELIMITER $$
$$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticketPackaging_add`(
vClientFk INT,
vDated DATE,
vCompanyFk INT,
vWithoutPeriodGrace BOOLEAN)
BEGIN
/**
* Genera nuevos tickets de embalajes para los clientes no han los han retornado
* y actualiza los valores para la tabla ticketPackaging
*
* @param vClientFk Cliente en caso de NULL todos los clientes
* @param vDated Fecha hasta la cual se revisan los embalajes
* @param vCompanyFk Empresa de la cual se comprobaran sus clientes
* @param vWithoutPeriodGrace si no se aplica el periodo de gracia de un mes
*/
DECLARE vNewTicket INT;
DECLARE vDateStart DATE;
DECLARE vDateEnd DATE;
DECLARE vGraceDate DATE DEFAULT vDated;
DECLARE vWarehouseInventory INT;
DECLARE vComponentCost INT;
DECLARE vDone INT DEFAULT FALSE;
DECLARE vClientId INT;
DECLARE vCursor CURSOR FOR
SELECT DISTINCT clientFk
FROM (
SELECT clientFk, SUM(quantity) totalQuantity
FROM tmp.packagingToInvoice
GROUP BY itemFk, clientFk
HAVING totalQuantity > 0)sub;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
SELECT id INTO vWarehouseInventory
FROM warehouse
WHERE `code`= 'inv';
SELECT id INTO vComponentCost
FROM component
WHERE `code`= 'purchaseValue';
SELECT packagingInvoicingDated INTO vDateStart
FROM ticketConfig;
IF vWarehouseInventory IS NULL THEN
CALL util.throw('Warehouse inventory not set');
END IF;
IF vComponentCost IS NULL THEN
CALL util.throw('Component cost not set');
END IF;
SET vDateEnd = vDated + INTERVAL 1 DAY;
IF NOT vWithoutPeriodGrace THEN
SET vGraceDate = vGraceDate -INTERVAL 1 MONTH;
END IF;
DROP TEMPORARY TABLE IF EXISTS tmp.packagingToInvoice;
CREATE TEMPORARY TABLE tmp.packagingToInvoice
(INDEX (clientFk))
ENGINE = MEMORY
SELECT p.itemFk,
tp.packagingFk,
tp.quantity,
tp.ticketFk,
p.price,
t.clientFk
FROM ticketPackaging tp
JOIN packaging p ON p.id = tp.packagingFk
JOIN ticket t ON t.id = tp.ticketFk
JOIN client c ON c.id = t.clientFk
WHERE c.isActive
AND (vClientFk IS NULL OR t.clientFk = vClientFk)
AND t.shipped BETWEEN vDateStart AND vDateEnd
AND (tp.quantity < 0 OR (tp.quantity > 0 AND t.shipped < vGraceDate))
AND tp.quantity
AND p.itemFk;
OPEN vCursor;
l: LOOP
FETCH vCursor INTO vClientId;
IF vDone THEN
LEAVE l;
END IF;
START TRANSACTION;
CALL ticket_add(
vClientId,
vDateEnd,
vWarehouseInventory,
vCompanyFk,
NULL,
NULL,
NULL,
vDateEnd,
account.myUser_getId(),
TRUE,
vNewTicket);
INSERT INTO ticketPackaging(ticketFk, packagingFk, quantity, pvp)
SELECT vNewTicket, packagingFk, - SUM(quantity) totalQuantity, price
FROM tmp.packagingToInvoice
WHERE clientFk = vClientId
GROUP BY packagingFk
HAVING IF(vWithoutPeriodGrace, totalQuantity <> 0, totalQuantity < 0);
INSERT INTO sale(ticketFk, itemFk, concept, quantity, price)
SELECT vNewTicket, pti.itemFk, i.name, SUM(pti.quantity) totalQuantity, pti.price
FROM tmp.packagingToInvoice pti
JOIN item i ON i.id = pti.itemFk
WHERE pti.clientFk = vClientId
GROUP BY pti.itemFk
HAVING IF(vWithoutPeriodGrace, totalQuantity <> 0, totalQuantity > 0);
INSERT INTO saleComponent(saleFk, componentFk, value)
SELECT id, vComponentCost, price
FROM sale
WHERE ticketFk = vNewTicket;
COMMIT;
END LOOP;
CLOSE vCursor;
DROP TEMPORARY TABLE tmp.packagingToInvoice;
END$$
DELIMITER ;

View File

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

View File

@ -0,0 +1,16 @@
ALTER TABLE `vn`.`chat` ADD statusNew enum('pending','sent','error','sending') DEFAULT 'pending' NOT NULL;
UPDATE `vn`.`chat`
SET statusNew = 'pending'
WHERE status = 0;
UPDATE `vn`.`chat`
SET statusNew = 'sent'
WHERE status = 1;
UPDATE `vn`.`chat`
SET statusNew = 'error'
WHERE status = 2;
ALTER TABLE `vn`.`chat` CHANGE status status__ tinyint(1) DEFAULT NULL NULL;
ALTER TABLE `vn`.`chat` CHANGE statusNew status enum('pending','sent','error','sending') CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT 'pending' NOT NULL;

View File

@ -0,0 +1,14 @@
ALTER TABLE `vn`.`itemType` ADD isFragile tinyint(1) NULL;
ALTER TABLE `vn`.`itemType` MODIFY COLUMN isFragile tinyint(1) DEFAULT 0 NOT NULL;
UPDATE `vn`.`itemType`
SET isFragile = 1
WHERE code IN ('ZKA', 'ZKE');
UPDATE `vn`.`itemType`
SET isFragile = 1
WHERE id IN (SELECT it.id
FROM itemCategory ic
JOIN itemType it ON it.categoryFk = ic.id
WHERE ic.code = 'plant');

View File

@ -0,0 +1,159 @@
ALTER TABLE `vn`.`operator` ADD sectorFk int(11) NULL;
ALTER TABLE `vn`.`operator` ADD labelerFk tinyint(3) unsigned NULL;
ALTER TABLE `vn`.`operator` ADD CONSTRAINT operator_FK_5 FOREIGN KEY (labelerFk) REFERENCES `vn`.`printer`(id) ON DELETE CASCADE ON UPDATE CASCADE;
UPDATE `vn`.`operator` o
JOIN (SELECT id, sectorFk, labelerFk
FROM `vn`.`worker`) sub ON sub.id = o.workerFk
SET o.sectorFk = sub.sectorFk,
o.labelerFk = sub.labelerFk;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`collection_printSticker`(
vSelf INT,
vLabelCount INT
)
BEGIN
/**
* Prints a yellow label from a collection or a ticket
*
* @param vSelf collection or ticket
* @param vLabelCount number of times the collection has been printed
*/
DECLARE vPrintArgs JSON DEFAULT JSON_OBJECT('collectionOrTicketFk', vSelf);
IF vLabelCount IS NULL THEN
INSERT INTO ticketTrolley
SELECT ticketFk, 1
FROM ticketCollection
WHERE collectionFk = vSelf
ON DUPLICATE KEY UPDATE labelCount = labelCount + 1;
ELSE
SET vPrintArgs = JSON_MERGE_PATCH(vPrintArgs, JSON_OBJECT('labelCount', vLabelCount));
END IF;
CALL report_print(
'LabelCollection',
(SELECT o.labelerFk FROM operator o WHERE o.workerFk = account.myUser_getId()),
account.myUser_getId(),
vPrintArgs,
'high'
);
END$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`expeditionPallet_printLabel`(vSelf INT)
BEGIN
/**
* Calls the report_print procedure and passes it
* the necessary parameters for printing.
*
* @param vSelf expeditioPallet id.
*/
DECLARE vPrinterFk INT;
DECLARE vUserFk INT DEFAULT account.myUser_getId();
SELECT o.labelerFk INTO vPrinterFk
FROM operator o
WHERE o.workerFk = vUserFk;
CALL vn.report_print(
'LabelPalletExpedition',
vPrinterFk,
account.myUser_getId(),
JSON_OBJECT('palletFk', vSelf, 'userFk', vUserFk),
'high'
);
UPDATE vn.expeditionPallet
SET isPrint = TRUE
WHERE id = vSelf;
END$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemShelving_getAlternatives`(vShelvingFk VARCHAR(10))
BEGIN
/**
* Devuelve un listado de posibles ubicaciones alternativas a ubicar los item de la matricula
* del carro que se le ha pasado.
*
* @param vShelvingFk matricula del carro
*/
SELECT is2.id,is2.shelvingFk , p.code, is2.itemFk , is2.visible, p.pickingOrder
FROM itemShelving is2
JOIN shelving sh ON sh.code = is2.shelvingFk
JOIN parking p ON p.id = sh.parkingFk
JOIN sector s ON s.id = p.sectorFk
LEFT JOIN operator o ON o.sectorFk = s.id
LEFT JOIN worker w ON w.sectorFk = s.id AND w.id = account.myUser_getId()
JOIN warehouse wh ON wh.id = s.warehouseFk
JOIN itemShelving is3 ON is3.itemFk = is2.itemFk AND is3.shelvingFk = vShelvingFk COLLATE utf8_unicode_ci
WHERE is2.shelvingFk <> vShelvingFk COLLATE utf8_unicode_ci
GROUP BY is2.id
ORDER BY p.pickingOrder DESC;
END$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`operator_beforeInsert`
BEFORE INSERT ON `operator`
FOR EACH ROW
BEGIN
CALL vn.printer_checkSector(NEW.labelerFk, NEW.sectorFk);
END$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`operator_beforeUpdate`
BEFORE UPDATE ON `operator`
FOR EACH ROW
BEGIN
IF NOT (NEW.labelerFk <=> OLD.labelerFk AND NEW.sectorFk <=> OLD.sectorFk) THEN
CALL vn.printer_checkSector(NEW.labelerFk, NEW.sectorFk);
END IF;
END$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`worker_beforeUpdate`
BEFORE UPDATE ON `worker`
FOR EACH ROW
BEGIN
IF NOT (NEW.labelerFk <=> OLD.labelerFk AND NEW.sectorFk <=> OLD.sectorFk) THEN
CALL vn.printer_checkSector(NEW.labelerFk, NEW.sectorFk);
INSERT IGNORE INTO vn.operator (workerFk)
VALUES (NEW.id);
UPDATE operator
SET labelerFk = NEW.labelerFk,
sectorFk = NEW.sectorFk
WHERE workerFk = NEW.id;
END IF;
END$$
DELIMITER ;
CREATE OR REPLACE DEFINER=`root`@`localhost`
SQL SECURITY DEFINER
VIEW `vn`.`operatorWorkerCode`
AS SELECT `o`.`workerFk` AS `workerFk`,
concat(`w`.`firstName`, ' ', `w`.`lastName`) AS `fullName`,
`w`.`code` AS `code`,
`o`.`numberOfWagons` AS `numberOfWagons`
FROM (
(
`vn`.`worker` `w`
JOIN `vn`.`operator` `o` ON(`o`.`workerFk` = `w`.`id`)
)
JOIN `vn`.`sector` `s` ON(`o`.`sectorFk` = `s`.`id`)
)
WHERE `o`.`sectorFk` IS NOT NULL
AND `s`.`code` IN (
'H2',
'H2',
'PEQUES_H',
'ALTILLO COMP',
'ALTILLO ARTI'
)

View File

@ -0,0 +1,47 @@
DROP PROCEDURE IF EXISTS `vn`.`ticket_getWarnings`;
DELIMITER $$
$$
CREATE PROCEDURE `vn`.`ticket_getWarnings`()
BEGIN
/**
* Calcula las adventencias para un conjunto de tickets.
* Agrupados por ticket
*
* @table tmp.sale_getWarnings(ticketFk) Identificadores de los tickets a calcular
* @return tmp.ticket_warnings
*/
DROP TEMPORARY TABLE IF EXISTS tmp.sale_warnings;
CREATE TEMPORARY TABLE tmp.sale_warnings (
ticketFk INT(11),
saleFk INT(11),
isFragile INTEGER(1) DEFAULT 0,
PRIMARY KEY (ticketFk, saleFk)
) ENGINE = MEMORY;
-- Frágil
INSERT INTO tmp.sale_warnings(ticketFk, saleFk, isFragile)
SELECT tt.ticketFk, s.id, TRUE
FROM tmp.sale_getWarnings tt
LEFT JOIN sale s ON s.ticketFk = tt.ticketFk
LEFT JOIN item i ON i.id = s.itemFk
LEFT JOIN itemType it ON it.id = i.typeFk
LEFT JOIN agencyMode am ON am.id = tt.agencyModeFk
LEFT JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk
WHERE dm.code IN ('AGENCY')
AND it.isFragile;
DROP TEMPORARY TABLE IF EXISTS tmp.ticket_warnings;
CREATE TEMPORARY TABLE tmp.ticket_warnings
(PRIMARY KEY (ticketFk))
ENGINE = MEMORY
SELECT
sw.ticketFk,
MAX(sw.isFragile) AS isFragile
FROM tmp.sale_warnings sw
GROUP BY sw.ticketFk;
DROP TEMPORARY TABLE
tmp.sale_warnings;
END$$
DELIMITER ;

View File

@ -164,7 +164,7 @@ INSERT INTO `vn`.`warehouse`(`id`, `name`, `code`, `isComparative`, `isInventory
(3, 'Warehouse Three', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1), (3, 'Warehouse Three', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1),
(4, 'Warehouse Four', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1), (4, 'Warehouse Four', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1),
(5, 'Warehouse Five', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1), (5, 'Warehouse Five', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1),
(13, 'Inventory', NULL, 1, 1, 1, 0, 0, 0, 2, 1, 0), (13, 'Inventory', 'inv', 1, 1, 1, 0, 0, 0, 2, 1, 0),
(60, 'Algemesi', NULL, 1, 1, 1, 0, 0, 0, 2, 1, 0); (60, 'Algemesi', NULL, 1, 1, 1, 0, 0, 0, 2, 1, 0);
@ -173,10 +173,15 @@ INSERT INTO `vn`.`sector`(`id`, `description`, `warehouseFk`, `isPreviousPrepare
(1, 'First sector', 1, 1, 'FIRST'), (1, 'First sector', 1, 1, 'FIRST'),
(2, 'Second sector', 2, 0, 'SECOND'); (2, 'Second sector', 2, 0, 'SECOND');
INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`) INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPackingTypeFk`, `warehouseFk`, `sectorFk`, `labelerFk`)
VALUES ('1106', '1', '1', 'H', '1', '1', '1'),
('1107', '1', '1', 'V', '1', '2', '1');
INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAddress`)
VALUES VALUES
(1, 'printer1', 'path1', 0, 1), (1, 'printer1', 'path1', 0, 1 , NULL),
(2, 'printer2', 'path2', 1, 1); (2, 'printer2', 'path2', 1, 1 , NULL),
(4, 'printer4', 'path4', 0, NULL, '10.1.10.4');
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`, `sectorFk`, `labelerFk`) INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`, `sectorFk`, `labelerFk`)
VALUES VALUES
@ -294,7 +299,8 @@ INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt
INSERT INTO `vn`.`payDem`(`id`, `payDem`) INSERT INTO `vn`.`payDem`(`id`, `payDem`)
VALUES VALUES
(1, 10), (1, 10),
(2, 20); (2, 20),
(7, 0);
INSERT INTO `vn`.`autonomy`(`id`, `name`, `countryFk`) INSERT INTO `vn`.`autonomy`(`id`, `name`, `countryFk`)
VALUES VALUES
@ -495,7 +501,8 @@ INSERT INTO `vn`.`observationType`(`id`,`description`, `code`)
(3, 'Delivery', 'delivery'), (3, 'Delivery', 'delivery'),
(4, 'SalesPerson', 'salesPerson'), (4, 'SalesPerson', 'salesPerson'),
(5, 'Administrative', 'administrative'), (5, 'Administrative', 'administrative'),
(6, 'Weight', 'weight'); (6, 'Weight', 'weight'),
(7, 'InvoiceOut', 'invoiceOut');
INSERT INTO `vn`.`addressObservation`(`id`,`addressFk`,`observationTypeFk`,`description`) INSERT INTO `vn`.`addressObservation`(`id`,`addressFk`,`observationTypeFk`,`description`)
VALUES VALUES
@ -571,14 +578,13 @@ INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion`
('NATIONAL', 0, 1), ('NATIONAL', 0, 1),
('WORLD', 2, 15); ('WORLD', 2, 15);
INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaFk`, `isCEE`) INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaFk`, `isCEE`, `type`)
VALUES VALUES
('A', 'Global nacional', 1, 'NATIONAL', 0), ('A', 'Global nacional', 1, 'NATIONAL', 0, 'global'),
('T', 'Española rapida', 1, 'NATIONAL', 0), ('T', 'Española rapida', 1, 'NATIONAL', 0, 'quick'),
('V', 'Intracomunitaria global', 0, 'CEE', 1), ('V', 'Intracomunitaria global', 0, 'CEE', 1, 'global'),
('M', 'Múltiple nacional', 1, 'NATIONAL', 0), ('M', 'Múltiple nacional', 1, 'NATIONAL', 0, 'quick'),
('E', 'Exportación rápida', 0, 'WORLD', 0); ('E', 'Exportación rápida', 0, 'WORLD', 0, 'quick');
;
INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`) INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`)
VALUES VALUES
@ -733,7 +739,9 @@ INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `des
(9, 23, 5, 'care with the dog'), (9, 23, 5, 'care with the dog'),
(10, 23, 4, 'Reclama ticket: 8'), (10, 23, 4, 'Reclama ticket: 8'),
(11, 24, 4, 'Reclama ticket: 7'), (11, 24, 4, 'Reclama ticket: 7'),
(12, 11, 3, 'Delivery after 10am'); (12, 11, 3, 'Delivery after 10am'),
(13, 1, 7, 'observation of ticket one'),
(14, 2, 7, 'observation of ticket two');
-- FIX for state hours on local, inter_afterInsert -- FIX for state hours on local, inter_afterInsert
-- UPDATE vncontrol.inter SET odbc_date = DATE_ADD(util.VN_CURDATE(), INTERVAL -10 SECOND); -- UPDATE vncontrol.inter SET odbc_date = DATE_ADD(util.VN_CURDATE(), INTERVAL -10 SECOND);
@ -833,14 +841,14 @@ INSERT INTO `vn`.`temperature`(`code`, `name`, `description`)
('warm', 'Warm', 'Warm'), ('warm', 'Warm', 'Warm'),
('cool', 'Cool', 'Cool'); ('cool', 'Cool', 'Cool');
INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `life`, `workerFk`, `isPackaging`, `temperatureFk`) INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `life`, `workerFk`, `isPackaging`, `temperatureFk`, `isFragile`)
VALUES VALUES
(1, 'CRI', 'Crisantemo', 2, 31, 35, 0, 'cool'), (1, 'CRI', 'Crisantemo', 2, 31, 35, 0, 'cool', 0),
(2, 'ITG', 'Anthurium', 1, 31, 35, 0, 'cool'), (2, 'ITG', 'Anthurium', 1, 31, 35, 0, 'cool', 1),
(3, 'WPN', 'Paniculata', 2, 31, 35, 0, 'cool'), (3, 'WPN', 'Paniculata', 2, 31, 35, 0, 'cool', 0),
(4, 'PRT', 'Delivery ports', 3, NULL, 35, 1, 'warm'), (4, 'PRT', 'Delivery ports', 3, NULL, 35, 1, 'warm', 0),
(5, 'CON', 'Container', 3, NULL, 35, 1, 'warm'), (5, 'CON', 'Container', 3, NULL, 35, 1, 'warm', 0),
(6, 'ALS', 'Alstroemeria', 1, 31, 16, 0, 'warm'); (6, 'ALS', 'Alstroemeria', 1, 31, 16, 0, 'warm', 1);
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`) INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`)
VALUES VALUES
@ -2367,11 +2375,11 @@ INSERT INTO `vn`.`device` (`sn`, `model`, `userFk`)
VALUES VALUES
('aaa', 'android', '9'); ('aaa', 'android', '9');
INSERT INTO `vn`.`queuePriority`(`id`, `priority`) INSERT INTO `vn`.`queuePriority`(`id`, `priority`, `code`)
VALUES VALUES
(1, 'Alta'), (1, 'Alta', 'high'),
(2, 'Normal'), (2, 'Normal', 'normal'),
(3, 'Baja'); (3, 'Baja', 'low');
INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `weekScope`, `dayWorkMax`, `dayStayMax`, `weekMaxBreak`, `weekMaxScope`, `askInOut`) INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `weekScope`, `dayWorkMax`, `dayStayMax`, `weekMaxBreak`, `weekMaxScope`, `askInOut`)
VALUES VALUES
@ -2626,8 +2634,8 @@ INSERT INTO `vn`.`supplierAgencyTerm` (`agencyFk`, `supplierFk`, `minimumPackage
INSERT INTO `vn`.`chat` (`senderFk`, `recipient`, `dated`, `checkUserStatus`, `message`, `status`, `attempts`) INSERT INTO `vn`.`chat` (`senderFk`, `recipient`, `dated`, `checkUserStatus`, `message`, `status`, `attempts`)
VALUES VALUES
(1101, '@PetterParker', util.VN_CURDATE(), 1, 'First test message', 0, 0), (1101, '@PetterParker', util.VN_CURDATE(), 1, 'First test message', 0, 'sent'),
(1101, '@PetterParker', util.VN_CURDATE(), 0, 'Second test message', 0, 0); (1101, '@PetterParker', util.VN_CURDATE(), 0, 'Second test message', 0, 'pending');
INSERT INTO `vn`.`mobileAppVersionControl` (`appName`, `version`, `isVersionCritical`) INSERT INTO `vn`.`mobileAppVersionControl` (`appName`, `version`, `isVersionCritical`)
@ -2783,6 +2791,10 @@ INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
('lilium', 'dev', 'http://localhost:8080/#/'), ('lilium', 'dev', 'http://localhost:8080/#/'),
('salix', 'dev', 'http://localhost:5000/#!/'); ('salix', 'dev', 'http://localhost:5000/#!/');
INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)
VALUES
(3, 'invoice', NULL, 'InvoiceOuts/{refFk}/invoice-out-pdf');
INSERT INTO `vn`.`payDemDetail` (`id`, `detail`) INSERT INTO `vn`.`payDemDetail` (`id`, `detail`)
VALUES VALUES
(1, 1); (1, 1);
@ -2821,4 +2833,11 @@ INSERT INTO `vn`.`deviceProductionUser` (`deviceProductionFk`, `userFk`, `create
(1, 1, util.VN_NOW()), (1, 1, util.VN_NOW()),
(3, 3, util.VN_NOW()); (3, 3, util.VN_NOW());
INSERT INTO `vn`.`workerTimeControlMail` (`id`, `workerFk`, `year`, `week`, `state`, `updated`, `sendedCounter`, `reason`)
VALUES
(1, 9, 2000, 49, 'REVISE', util.VN_NOW(), 1, 'test2'),
(2, 9, 2000, 50, 'SENDED', util.VN_NOW(), 1, NULL),
(3, 9, 2000, 51, 'CONFIRMED', util.VN_NOW(), 1, NULL),
(4, 9, 2001, 1, 'SENDED', util.VN_NOW(), 1, NULL);

View File

@ -19742,6 +19742,102 @@ DELIMITER ;
/*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ; /*!50003 SET collation_connection = @saved_col_connection */ ;
/*!50003 DROP FUNCTION IF EXISTS `CURDATE` */;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8mb4 */ ;
/*!50003 SET character_set_results = utf8mb4 */ ;
/*!50003 SET collation_connection = utf8mb4_unicode_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`CURDATE`() RETURNS date
DETERMINISTIC
BEGIN
/**
* @return The mock date
**/
RETURN DATE(mockTime());
END ;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
/*!50003 DROP FUNCTION IF EXISTS `mockTime` */;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8mb4 */ ;
/*!50003 SET character_set_results = utf8mb4 */ ;
/*!50003 SET collation_connection = utf8mb4_unicode_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`mockTime`() RETURNS datetime
DETERMINISTIC
BEGIN
/**
* Returns the mockTime with predefined timezone or current dateTime
* depending of config.mockEnabled
*
* @return formatted datetime
*/
DECLARE vMockEnabled BOOL;
SELECT mockEnabled INTO vMockEnabled FROM config LIMIT 1;
IF vMockEnabled THEN
RETURN mockTimeBase(FALSE);
ELSE
RETURN NOW();
END IF;
END ;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
/*!50003 DROP FUNCTION IF EXISTS `mockTimeBase` */;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8mb4 */ ;
/*!50003 SET character_set_results = utf8mb4 */ ;
/*!50003 SET collation_connection = utf8mb4_unicode_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`mockTimeBase`(vIsUtc BOOL) RETURNS datetime
DETERMINISTIC
BEGIN
/**
* Returns the date formatted to utc if vIsUtc or config.mocTz if not
*
* @param vIsUtc If date must be returned as UTC format
* @return The formatted mock time
*/
DECLARE vMockUtcTime DATETIME;
DECLARE vMockTz VARCHAR(255);
SELECT mockUtcTime, mockTz
INTO vMockUtcTime, vMockTz
FROM config
LIMIT 1;
IF vIsUtc OR vMockTz IS NULL THEN
RETURN vMockUtcTime;
ELSE
RETURN CONVERT_TZ(vMockUtcTime, '+00:00', vMockTz);
END IF;
END ;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
/*!50003 DROP FUNCTION IF EXISTS `firstDayOfYear` */; /*!50003 DROP FUNCTION IF EXISTS `firstDayOfYear` */;
/*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ;
@ -32632,7 +32728,7 @@ CREATE TABLE `printQueue` (
CONSTRAINT `printQueue_printerFk` FOREIGN KEY (`printerFk`) REFERENCES `printer` (`id`) ON UPDATE CASCADE, CONSTRAINT `printQueue_printerFk` FOREIGN KEY (`printerFk`) REFERENCES `printer` (`id`) ON UPDATE CASCADE,
CONSTRAINT `printQueue_priorityFk` FOREIGN KEY (`priorityFk`) REFERENCES `queuePriority` (`id`) ON UPDATE CASCADE, CONSTRAINT `printQueue_priorityFk` FOREIGN KEY (`priorityFk`) REFERENCES `queuePriority` (`id`) ON UPDATE CASCADE,
CONSTRAINT `printQueue_report` FOREIGN KEY (`reportFk`) REFERENCES `report` (`id`) ON DELETE CASCADE ON UPDATE CASCADE CONSTRAINT `printQueue_report` FOREIGN KEY (`reportFk`) REFERENCES `report` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDBDEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@ -68286,11 +68382,11 @@ BEGIN
FROM ticketConfig; FROM ticketConfig;
IF vWarehouseInventory IS NULL THEN IF vWarehouseInventory IS NULL THEN
CALL util.throw('Warehouse inventory not seted'); CALL util.throw('Warehouse inventory not set');
END IF; END IF;
IF vComponentCost IS NULL THEN IF vComponentCost IS NULL THEN
CALL util.throw('Component cost not seted'); CALL util.throw('Component cost not set');
END IF; END IF;
SET vDateEnd = vDated + INTERVAL 1 DAY; SET vDateEnd = vDated + INTERVAL 1 DAY;
@ -81124,3 +81220,4 @@ USE `vn`;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2023-02-21 8:14:30 -- Dump completed on 2023-02-21 8:14:30

View File

@ -524,7 +524,6 @@ export default {
}, },
itemLog: { itemLog: {
anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr', anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr',
fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(2) td.after',
}, },
ticketSummary: { ticketSummary: {
header: 'vn-ticket-summary > vn-card > h5', header: 'vn-ticket-summary > vn-card > h5',
@ -1040,7 +1039,6 @@ export default {
boss: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bossFk"]', boss: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bossFk"]',
role: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.roleFk"]', role: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.roleFk"]',
iban: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.iban"]', iban: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.iban"]',
switft: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bankEntityFk"]',
createButton: 'vn-worker-create vn-submit[label="Create"]', createButton: 'vn-worker-create vn-submit[label="Create"]',
}, },
workerPda: { workerPda: {
@ -1052,20 +1050,22 @@ export default {
invoiceOutIndex: { invoiceOutIndex: {
topbarSearch: 'vn-searchbar', topbarSearch: 'vn-searchbar',
searchResult: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr', searchResult: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
createInvoice: 'vn-invoice-out-index > div > vn-vertical > vn-button > button vn-icon[icon="add"]', createInvoice: 'vn-invoice-out-index > div > vn-button > button vn-icon[icon="add"]',
createManualInvoice: 'vn-item[name="manualInvoice"]',
createGlobalInvoice: 'vn-item[name="globalInvoice"]',
manualInvoiceForm: '.vn-invoice-out-manual', manualInvoiceForm: '.vn-invoice-out-manual',
manualInvoiceTicket: 'vn-autocomplete[ng-model="$ctrl.invoice.ticketFk"]', manualInvoiceTicket: 'vn-autocomplete[ng-model="$ctrl.invoice.ticketFk"]',
manualInvoiceClient: 'vn-autocomplete[ng-model="$ctrl.invoice.clientFk"]', manualInvoiceClient: 'vn-autocomplete[ng-model="$ctrl.invoice.clientFk"]',
manualInvoiceSerial: 'vn-autocomplete[ng-model="$ctrl.invoice.serial"]', manualInvoiceSerial: 'vn-autocomplete[ng-model="$ctrl.invoice.serial"]',
manualInvoiceTaxArea: 'vn-autocomplete[ng-model="$ctrl.invoice.taxArea"]', manualInvoiceTaxArea: 'vn-autocomplete[ng-model="$ctrl.invoice.taxArea"]',
saveInvoice: 'button[response="accept"]', saveInvoice: 'button[response="accept"]'
globalInvoiceForm: '.vn-invoice-out-global-invoicing', },
globalInvoiceClientsRange: 'vn-radio[val="clientsRange"]', invoiceOutGlobalInvoicing: {
globalInvoiceDate: '[ng-model="$ctrl.invoice.invoiceDate"]', oneClient: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-vertical > vn-radio[val="one"]',
globalInvoiceFromClient: '[ng-model="$ctrl.invoice.fromClientId"]', allClients: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-vertical > vn-radio[val="all"]',
globalInvoiceToClient: '[ng-model="$ctrl.invoice.toClientId"]', clientId: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-autocomplete[ng-model="$ctrl.clientId"]',
printer: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-autocomplete[ng-model="$ctrl.printerFk"]',
makeInvoice: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-submit',
invoiceDate: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-date-picker[ng-model="$ctrl.invoiceDate"]',
maxShipped: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-date-picker[ng-model="$ctrl.maxShipped"]'
}, },
invoiceOutDescriptor: { invoiceOutDescriptor: {
moreMenu: 'vn-invoice-out-descriptor vn-icon-button[icon=more_vert]', moreMenu: 'vn-invoice-out-descriptor vn-icon-button[icon=more_vert]',

View File

@ -27,7 +27,7 @@ describe('Worker time control path', () => {
date.setMonth(date.getMonth() + 1); date.setMonth(date.getMonth() + 1);
let month = date.toLocaleString('default', {month: 'long'}); let month = date.toLocaleString('default', {month: 'long'});
await page.click(selectors.workerTimeControl.nextMonthButton); await page.waitToClick(selectors.workerTimeControl.nextMonthButton);
let result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText'); let result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month); expect(result).toContain(month);
@ -36,7 +36,7 @@ describe('Worker time control path', () => {
date.setDate(1); date.setDate(1);
month = date.toLocaleString('default', {month: 'long'}); month = date.toLocaleString('default', {month: 'long'});
await page.click(selectors.workerTimeControl.previousMonthButton); await page.waitToClick(selectors.workerTimeControl.previousMonthButton);
result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText'); result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month); expect(result).toContain(month);
@ -49,7 +49,7 @@ describe('Worker time control path', () => {
await page.loginAndModule('salesBoss', 'worker'); await page.loginAndModule('salesBoss', 'worker');
await page.goto(`http://localhost:5000/#!/worker/${hankPymId}/time-control?timestamp=${timestamp}`); await page.goto(`http://localhost:5000/#!/worker/${hankPymId}/time-control?timestamp=${timestamp}`);
await page.click(selectors.workerTimeControl.secondWeekDay); await page.waitToClick(selectors.workerTimeControl.secondWeekDay);
result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText'); result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');

View File

@ -26,7 +26,6 @@ describe('Worker create path', () => {
await page.write(selectors.workerCreate.street, 'S/ Doomstadt'); await page.write(selectors.workerCreate.street, 'S/ Doomstadt');
await page.write(selectors.workerCreate.email, 'doctorDoom@marvel.com'); await page.write(selectors.workerCreate.email, 'doctorDoom@marvel.com');
await page.write(selectors.workerCreate.iban, 'ES9121000418450200051332'); await page.write(selectors.workerCreate.iban, 'ES9121000418450200051332');
await page.autocompleteSearch(selectors.workerCreate.switft, 'BBKKESMMMMM');
// should check for autocompleted worker code and worker user name // should check for autocompleted worker code and worker user name
const workerCode = await page const workerCode = await page

View File

@ -48,17 +48,17 @@ describe('Item log path', () => {
await page.accessToSection('item.card.log'); await page.accessToSection('item.card.log');
}); });
it(`should confirm the log is showing 5 entries`, async() => { it(`should confirm the log is showing 4 entries`, async() => {
await page.waitForSelector(selectors.itemLog.anyLineCreated); await page.waitForSelector(selectors.itemLog.anyLineCreated);
const anyLineCreatedCount = await page.countElement(selectors.itemLog.anyLineCreated); const anyLineCreatedCount = await page.countElement(selectors.itemLog.anyLineCreated);
expect(anyLineCreatedCount).toEqual(5); expect(anyLineCreatedCount).toEqual(4);
}); });
it(`should confirm the log is showing the intrastat for the created item`, async() => { xit(`should confirm the log is showing the intrastat for the created item`, async() => {
const fifthLineCreatedProperty = await page const fifthLineCreatedProperty = await page
.waitToGetProperty(selectors.itemLog.fifthLineCreatedProperty, 'innerText'); .waitToGetProperty(selectors.itemLog.fifthLineCreatedProperty, 'innerText');
expect(fifthLineCreatedProperty).toEqual('Coral y materiales similares'); expect(fifthLineCreatedProperty).toEqual('05080000');
}); });
}); });

View File

@ -197,6 +197,7 @@ describe('Ticket Edit sale path', () => {
}); });
it('should check in the history that logs has been added', async() => { it('should check in the history that logs has been added', async() => {
pending('https://redmine.verdnatura.es/issues/5455');
await page.reload({waitUntil: ['networkidle0', 'domcontentloaded']}); await page.reload({waitUntil: ['networkidle0', 'domcontentloaded']});
await page.waitToClick(selectors.ticketSales.firstSaleHistoryButton); await page.waitToClick(selectors.ticketSales.firstSaleHistoryButton);
await page.waitForSelector(selectors.ticketSales.firstSaleHistory); await page.waitForSelector(selectors.ticketSales.firstSaleHistory);

View File

@ -9,7 +9,7 @@ describe('Ticket Create notes path', () => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('employee', 'ticket'); await page.loginAndModule('employee', 'ticket');
await page.accessToSearchResult('1'); await page.accessToSearchResult('5');
await page.accessToSection('ticket.card.observation'); await page.accessToSection('ticket.card.observation');
}); });

View File

@ -54,7 +54,7 @@ describe('Ticket Future path', () => {
it('should search with the origin IPT', async() => { it('should search with the origin IPT', async() => {
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.ticketFuture.ipt, 'Horizontal'); await page.autocompleteSearch(selectors.ticketFuture.ipt, 'H');
await page.waitToClick(selectors.ticketFuture.submit); await page.waitToClick(selectors.ticketFuture.submit);
expect(httpRequest).toContain('ipt=H'); expect(httpRequest).toContain('ipt=H');
@ -65,7 +65,7 @@ describe('Ticket Future path', () => {
await page.clearInput(selectors.ticketFuture.ipt); await page.clearInput(selectors.ticketFuture.ipt);
await page.autocompleteSearch(selectors.ticketFuture.futureIpt, 'Horizontal'); await page.autocompleteSearch(selectors.ticketFuture.futureIpt, 'H');
await page.waitToClick(selectors.ticketFuture.submit); await page.waitToClick(selectors.ticketFuture.submit);
expect(httpRequest).toContain('futureIpt=H'); expect(httpRequest).toContain('futureIpt=H');
@ -108,7 +108,7 @@ describe('Ticket Future path', () => {
it('should search in smart-table with an IPT Destination', async() => { it('should search in smart-table with an IPT Destination', async() => {
await page.waitToClick(selectors.ticketFuture.tableButtonSearch); await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.autocompleteSearch(selectors.ticketFuture.tableFutureIpt, 'Horizontal'); await page.autocompleteSearch(selectors.ticketFuture.tableFutureIpt, 'H');
expect(httpRequest).toContain('futureIpt'); expect(httpRequest).toContain('futureIpt');
await page.waitToClick(selectors.ticketFuture.tableButtonSearch); await page.waitToClick(selectors.ticketFuture.tableButtonSearch);

View File

@ -54,7 +54,7 @@ describe('Ticket Advance path', () => {
it('should search with the origin IPT', async() => { it('should search with the origin IPT', async() => {
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.ticketAdvance.futureIpt, 'Horizontal'); await page.autocompleteSearch(selectors.ticketAdvance.futureIpt, 'H');
await page.waitToClick(selectors.ticketAdvance.submit); await page.waitToClick(selectors.ticketAdvance.submit);
expect(httpRequest).toContain('futureIpt=H'); expect(httpRequest).toContain('futureIpt=H');
@ -66,7 +66,7 @@ describe('Ticket Advance path', () => {
it('should search with the destination IPT', async() => { it('should search with the destination IPT', async() => {
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.ticketAdvance.ipt, 'Horizontal'); await page.autocompleteSearch(selectors.ticketAdvance.ipt, 'H');
await page.waitToClick(selectors.ticketAdvance.submit); await page.waitToClick(selectors.ticketAdvance.submit);
expect(httpRequest).toContain('ipt=H'); expect(httpRequest).toContain('ipt=H');
@ -78,7 +78,7 @@ describe('Ticket Advance path', () => {
it('should search in smart-table with an IPT Origin', async() => { it('should search in smart-table with an IPT Origin', async() => {
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch); await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.autocompleteSearch(selectors.ticketAdvance.tableFutureIpt, 'Vertical'); await page.autocompleteSearch(selectors.ticketAdvance.tableFutureIpt, 'V');
expect(httpRequest).toContain('futureIpt'); expect(httpRequest).toContain('futureIpt');
@ -89,7 +89,7 @@ describe('Ticket Advance path', () => {
it('should search in smart-table with an IPT Destination', async() => { it('should search in smart-table with an IPT Destination', async() => {
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch); await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.autocompleteSearch(selectors.ticketAdvance.tableIpt, 'Vertical'); await page.autocompleteSearch(selectors.ticketAdvance.tableIpt, 'V');
expect(httpRequest).toContain('ipt'); expect(httpRequest).toContain('ipt');

View File

@ -17,7 +17,6 @@ describe('InvoiceOut manual invoice path', () => {
it('should open the manual invoice form', async() => { it('should open the manual invoice form', async() => {
await page.waitToClick(selectors.invoiceOutIndex.createInvoice); await page.waitToClick(selectors.invoiceOutIndex.createInvoice);
await page.waitToClick(selectors.invoiceOutIndex.createManualInvoice);
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm); await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm);
}); });
@ -45,7 +44,6 @@ describe('InvoiceOut manual invoice path', () => {
it('should now open the manual invoice form', async() => { it('should now open the manual invoice form', async() => {
await page.waitToClick(selectors.invoiceOutIndex.createInvoice); await page.waitToClick(selectors.invoiceOutIndex.createInvoice);
await page.waitToClick(selectors.invoiceOutIndex.createManualInvoice);
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm); await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm);
}); });

View File

@ -17,47 +17,27 @@ describe('InvoiceOut global invoice path', () => {
await browser.close(); await browser.close();
}); });
let invoicesBefore; let invoicesBeforeOneClient;
let invoicesBeforeAllClients;
let now = Date.vnNew();
it('should count the amount of invoices listed before globla invoces are made', async() => { it('should count the amount of invoices listed before globla invoces are made', async() => {
invoicesBefore = await page.countElement(selectors.invoiceOutIndex.searchResult); invoicesBeforeOneClient = await page.countElement(selectors.invoiceOutIndex.searchResult);
expect(invoicesBefore).toBeGreaterThanOrEqual(4); expect(invoicesBeforeOneClient).toBeGreaterThanOrEqual(4);
}); });
it('should open the global invoice form', async() => { it('should open the global invoice form', async() => {
await page.waitToClick(selectors.invoiceOutIndex.createInvoice); await page.accessToSection('invoiceOut.global-invoicing');
await page.waitToClick(selectors.invoiceOutIndex.createGlobalInvoice);
await page.waitForSelector(selectors.invoiceOutIndex.globalInvoiceForm);
}); });
it('should create a global invoice for charles xavier today', async() => { it('should create a global invoice for charles xavier today', async() => {
await page.pickDate(selectors.invoiceOutIndex.globalInvoiceDate); await page.waitToClick(selectors.invoiceOutGlobalInvoicing.oneClient);
await page.waitToClick(selectors.invoiceOutIndex.globalInvoiceClientsRange); await page.autocompleteSearch(selectors.invoiceOutGlobalInvoicing.clientId, '1108');
await page.autocompleteSearch(selectors.invoiceOutIndex.globalInvoiceFromClient, 'Petter Parker'); await page.pickDate(selectors.invoiceOutGlobalInvoicing.invoiceDate, now);
await page.autocompleteSearch(selectors.invoiceOutIndex.globalInvoiceToClient, 'Petter Parker'); await page.pickDate(selectors.invoiceOutGlobalInvoicing.maxShipped, now);
await page.waitToClick(selectors.invoiceOutIndex.saveInvoice); await page.autocompleteSearch(selectors.invoiceOutGlobalInvoicing.printer, '1');
const message = await page.waitForSnackbar(); await page.waitToClick(selectors.invoiceOutGlobalInvoicing.makeInvoice);
await page.waitForTimeout(1000);
expect(message.text).toContain('Data saved!');
});
it('should count the amount of invoices listed after globla invocing', async() => {
await page.waitToClick('[icon="search"]');
await page.waitForTimeout(1000); // index search needs time to return results
const currentInvoices = await page.countElement(selectors.invoiceOutIndex.searchResult);
expect(currentInvoices).toBeGreaterThan(invoicesBefore);
});
it('should create a global invoice for all clients today', async() => {
await page.waitToClick(selectors.invoiceOutIndex.createInvoice);
await page.waitToClick(selectors.invoiceOutIndex.createGlobalInvoice);
await page.waitForSelector(selectors.invoiceOutIndex.globalInvoiceForm);
await page.pickDate(selectors.invoiceOutIndex.globalInvoiceDate);
await page.waitToClick(selectors.invoiceOutIndex.saveInvoice);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
}); });
}); });

View File

@ -89,11 +89,13 @@ describe('Travel basic data path', () => {
}); });
it('should navigate to the travel logs', async() => { it('should navigate to the travel logs', async() => {
pending('https://redmine.verdnatura.es/issues/5455');
await page.accessToSection('travel.card.log'); await page.accessToSection('travel.card.log');
await page.waitForState('travel.card.log'); await page.waitForState('travel.card.log');
}); });
it('should check the 1st log contains details from the changes made', async() => { it('should check the 1st log contains details from the changes made', async() => {
pending('https://redmine.verdnatura.es/issues/5455');
const result = await page.waitToGetProperty(selectors.travelLog.firstLogFirstTD, 'innerText'); const result = await page.waitToGetProperty(selectors.travelLog.firstLogFirstTD, 'innerText');
expect(result).toContain('new reference!'); expect(result).toContain('new reference!');

View File

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

View File

@ -15,9 +15,9 @@ export default class Calendar extends FormInput {
constructor($element, $scope, vnWeekDays, moment) { constructor($element, $scope, vnWeekDays, moment) {
super($element, $scope); super($element, $scope);
this.weekDays = vnWeekDays.locales; this.weekDays = vnWeekDays.locales;
this.defaultDate = Date.vnNew();
this.displayControls = true; this.displayControls = true;
this.moment = moment; this.moment = moment;
this.defaultDate = Date.vnNew();
} }
/** /**
@ -114,12 +114,14 @@ export default class Calendar extends FormInput {
let day = date.getDate(); let day = date.getDate();
let wday = date.getDay(); let wday = date.getDay();
let month = date.getMonth(); let month = date.getMonth();
let year = date.getFullYear();
const currentDay = Date.vnNew().getDate(); const currentDay = Date.vnNew().getDate();
const currentMonth = Date.vnNew().getMonth(); const currentMonth = Date.vnNew().getMonth();
const currentYear = Date.vnNew().getFullYear();
let classes = { let classes = {
today: day === currentDay && month === currentMonth, today: day === currentDay && month === currentMonth && year === currentYear,
weekend: wday === 6 || wday === 0, weekend: wday === 6 || wday === 0,
previous: month < this.month, previous: month < this.month,
current: month == this.month, current: month == this.month,
@ -207,14 +209,23 @@ export default class Calendar extends FormInput {
} }
repeatLast() { repeatLast() {
if (!this.formatDay) return; if (this.formatDay) {
const days = this.element.querySelectorAll('.days > .day');
for (let i = 0; i < days.length; i++) {
this.formatDay({
$day: this.days[i],
$element: days[i]
});
}
}
let days = this.element.querySelectorAll('.days > .day'); if (this.formatWeek) {
for (let i = 0; i < days.length; i++) { const weeks = this.element.querySelectorAll('.weeks > .day');
this.formatDay({ for (const week of weeks) {
$day: this.days[i], this.formatWeek({
$element: days[i] $element: week
}); });
}
} }
} }
} }
@ -228,6 +239,7 @@ ngModule.vnComponent('vnCalendar', {
hasEvents: '&?', hasEvents: '&?',
getClass: '&?', getClass: '&?',
formatDay: '&?', formatDay: '&?',
formatWeek: '&?',
displayControls: '<?', displayControls: '<?',
hideYear: '<?', hideYear: '<?',
hideContiguous: '<?', hideContiguous: '<?',

117
front/package-lock.json generated
View File

@ -25,8 +25,7 @@
}, },
"node_modules/@uirouter/angularjs": { "node_modules/@uirouter/angularjs": {
"version": "1.0.30", "version": "1.0.30",
"resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.30.tgz", "license": "MIT",
"integrity": "sha512-qkc3RFZc91S5K0gc/QVAXc9LGDPXjR04vDgG/11j8+yyZEuQojXxKxdLhKIepiPzqLmGRVqzBmBc27gtqaEeZg==",
"dependencies": { "dependencies": {
"@uirouter/core": "6.0.8" "@uirouter/core": "6.0.8"
}, },
@ -39,28 +38,22 @@
}, },
"node_modules/@uirouter/core": { "node_modules/@uirouter/core": {
"version": "6.0.8", "version": "6.0.8",
"resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.8.tgz", "license": "MIT",
"integrity": "sha512-Gc/BAW47i4L54p8dqYCJJZuv2s3tqlXQ0fvl6Zp2xrblELPVfxmjnc0eurx3XwfQdaqm3T6uls6tQKkof/4QMw==",
"engines": { "engines": {
"node": ">=4.0.0" "node": ">=4.0.0"
} }
}, },
"node_modules/angular": { "node_modules/angular": {
"version": "1.8.3", "version": "1.8.3",
"resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz", "license": "MIT"
"integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw==",
"deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward."
}, },
"node_modules/angular-animate": { "node_modules/angular-animate": {
"version": "1.8.2", "version": "1.8.2",
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.8.2.tgz", "license": "MIT"
"integrity": "sha512-Jbr9+grNMs9Kj57xuBU3Ju3NOPAjS1+g2UAwwDv7su1lt0/PLDy+9zEwDiu8C8xJceoTbmBNKiWGPJGBdCQLlA==",
"deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward."
}, },
"node_modules/angular-moment": { "node_modules/angular-moment": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz", "license": "MIT",
"integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==",
"dependencies": { "dependencies": {
"moment": ">=2.8.0 <3.0.0" "moment": ">=2.8.0 <3.0.0"
}, },
@ -70,8 +63,7 @@
}, },
"node_modules/angular-translate": { "node_modules/angular-translate": {
"version": "2.19.0", "version": "2.19.0",
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.19.0.tgz", "license": "MIT",
"integrity": "sha512-Z/Fip5uUT2N85dPQ0sMEe1JdF5AehcDe4tg/9mWXNDVU531emHCg53ZND9Oe0dyNiGX5rWcJKmsL1Fujus1vGQ==",
"dependencies": { "dependencies": {
"angular": "^1.8.0" "angular": "^1.8.0"
}, },
@ -81,29 +73,25 @@
}, },
"node_modules/angular-translate-loader-partial": { "node_modules/angular-translate-loader-partial": {
"version": "2.19.0", "version": "2.19.0",
"resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.19.0.tgz", "license": "MIT",
"integrity": "sha512-NnMw13LMV4bPQmJK7/pZOZAnPxe0M5OtUHchADs5Gye7V7feonuEnrZ8e1CKhBlv9a7IQyWoqcBa4Lnhg8gk5w==",
"dependencies": { "dependencies": {
"angular-translate": "~2.19.0" "angular-translate": "~2.19.0"
} }
}, },
"node_modules/argparse": { "node_modules/argparse": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "license": "MIT",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dependencies": { "dependencies": {
"sprintf-js": "~1.0.2" "sprintf-js": "~1.0.2"
} }
}, },
"node_modules/croppie": { "node_modules/croppie": {
"version": "2.6.5", "version": "2.6.5",
"resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz", "license": "MIT"
"integrity": "sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ=="
}, },
"node_modules/esprima": { "node_modules/esprima": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "license": "BSD-2-Clause",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"bin": { "bin": {
"esparse": "bin/esparse.js", "esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js" "esvalidate": "bin/esvalidate.js"
@ -114,8 +102,7 @@
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "3.14.1", "version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "license": "MIT",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dependencies": { "dependencies": {
"argparse": "^1.0.7", "argparse": "^1.0.7",
"esprima": "^4.0.0" "esprima": "^4.0.0"
@ -126,42 +113,36 @@
}, },
"node_modules/mg-crud": { "node_modules/mg-crud": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/mg-crud/-/mg-crud-1.1.2.tgz", "license": "MIT",
"integrity": "sha512-mAR6t0aQHKnT0QHKHpLOi0kNPZfO36iMpIoiLjFHxuio6mIJyuveBJ4VNlNXJRxLh32/FLADEb41/sYo7QUKFw==",
"dependencies": { "dependencies": {
"angular": "^1.6.1" "angular": "^1.6.1"
} }
}, },
"node_modules/moment": { "node_modules/moment": {
"version": "2.29.4", "version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "license": "MIT",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"engines": { "engines": {
"node": "*" "node": "*"
} }
}, },
"node_modules/oclazyload": { "node_modules/oclazyload": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/oclazyload/-/oclazyload-0.6.3.tgz", "license": "MIT"
"integrity": "sha512-HpOSYUgjtt6sTB/C6+FWsExR+9HCnXKsUA96RWkDXfv11C8Cc9X2DlR0WIZwFIiG6FQU0pwB5dhoYyut8bFAOQ=="
}, },
"node_modules/require-yaml": { "node_modules/require-yaml": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz", "license": "BSD",
"integrity": "sha512-M6eVEgLPRbeOhgSCnOTtdrOOEQzbXRchg24Xa13c39dMuraFKdI9emUo97Rih0YEFzSICmSKg8w4RQp+rd9pOQ==",
"dependencies": { "dependencies": {
"js-yaml": "" "js-yaml": ""
} }
}, },
"node_modules/require-yaml/node_modules/argparse": { "node_modules/require-yaml/node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "license": "Python-2.0"
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
}, },
"node_modules/require-yaml/node_modules/js-yaml": { "node_modules/require-yaml/node_modules/js-yaml": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "license": "MIT",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
}, },
@ -171,13 +152,11 @@
}, },
"node_modules/sprintf-js": { "node_modules/sprintf-js": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "license": "BSD-3-Clause"
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
}, },
"node_modules/validator": { "node_modules/validator": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz", "license": "MIT",
"integrity": "sha512-BylxTwhqwjQI5MDJF7amCy/L0ejJO+74DvCsLV52Lq3+3bhVcVMKqNqOiNcQJm2G48u9EAcw4xFERAmFbwXM9Q==",
"engines": { "engines": {
"node": ">= 0.10" "node": ">= 0.10"
} }
@ -186,73 +165,51 @@
"dependencies": { "dependencies": {
"@uirouter/angularjs": { "@uirouter/angularjs": {
"version": "1.0.30", "version": "1.0.30",
"resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.30.tgz",
"integrity": "sha512-qkc3RFZc91S5K0gc/QVAXc9LGDPXjR04vDgG/11j8+yyZEuQojXxKxdLhKIepiPzqLmGRVqzBmBc27gtqaEeZg==",
"requires": { "requires": {
"@uirouter/core": "6.0.8" "@uirouter/core": "6.0.8"
} }
}, },
"@uirouter/core": { "@uirouter/core": {
"version": "6.0.8", "version": "6.0.8"
"resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.8.tgz",
"integrity": "sha512-Gc/BAW47i4L54p8dqYCJJZuv2s3tqlXQ0fvl6Zp2xrblELPVfxmjnc0eurx3XwfQdaqm3T6uls6tQKkof/4QMw=="
}, },
"angular": { "angular": {
"version": "1.8.3", "version": "1.8.3"
"resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz",
"integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw=="
}, },
"angular-animate": { "angular-animate": {
"version": "1.8.2", "version": "1.8.2"
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.8.2.tgz",
"integrity": "sha512-Jbr9+grNMs9Kj57xuBU3Ju3NOPAjS1+g2UAwwDv7su1lt0/PLDy+9zEwDiu8C8xJceoTbmBNKiWGPJGBdCQLlA=="
}, },
"angular-moment": { "angular-moment": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz",
"integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==",
"requires": { "requires": {
"moment": ">=2.8.0 <3.0.0" "moment": ">=2.8.0 <3.0.0"
} }
}, },
"angular-translate": { "angular-translate": {
"version": "2.19.0", "version": "2.19.0",
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.19.0.tgz",
"integrity": "sha512-Z/Fip5uUT2N85dPQ0sMEe1JdF5AehcDe4tg/9mWXNDVU531emHCg53ZND9Oe0dyNiGX5rWcJKmsL1Fujus1vGQ==",
"requires": { "requires": {
"angular": "^1.8.0" "angular": "^1.8.0"
} }
}, },
"angular-translate-loader-partial": { "angular-translate-loader-partial": {
"version": "2.19.0", "version": "2.19.0",
"resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.19.0.tgz",
"integrity": "sha512-NnMw13LMV4bPQmJK7/pZOZAnPxe0M5OtUHchADs5Gye7V7feonuEnrZ8e1CKhBlv9a7IQyWoqcBa4Lnhg8gk5w==",
"requires": { "requires": {
"angular-translate": "~2.19.0" "angular-translate": "~2.19.0"
} }
}, },
"argparse": { "argparse": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": { "requires": {
"sprintf-js": "~1.0.2" "sprintf-js": "~1.0.2"
} }
}, },
"croppie": { "croppie": {
"version": "2.6.5", "version": "2.6.5"
"resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz",
"integrity": "sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ=="
}, },
"esprima": { "esprima": {
"version": "4.0.1", "version": "4.0.1"
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
}, },
"js-yaml": { "js-yaml": {
"version": "3.14.1", "version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"requires": { "requires": {
"argparse": "^1.0.7", "argparse": "^1.0.7",
"esprima": "^4.0.0" "esprima": "^4.0.0"
@ -260,39 +217,27 @@
}, },
"mg-crud": { "mg-crud": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/mg-crud/-/mg-crud-1.1.2.tgz",
"integrity": "sha512-mAR6t0aQHKnT0QHKHpLOi0kNPZfO36iMpIoiLjFHxuio6mIJyuveBJ4VNlNXJRxLh32/FLADEb41/sYo7QUKFw==",
"requires": { "requires": {
"angular": "^1.6.1" "angular": "^1.6.1"
} }
}, },
"moment": { "moment": {
"version": "2.29.4", "version": "2.29.4"
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
}, },
"oclazyload": { "oclazyload": {
"version": "0.6.3", "version": "0.6.3"
"resolved": "https://registry.npmjs.org/oclazyload/-/oclazyload-0.6.3.tgz",
"integrity": "sha512-HpOSYUgjtt6sTB/C6+FWsExR+9HCnXKsUA96RWkDXfv11C8Cc9X2DlR0WIZwFIiG6FQU0pwB5dhoYyut8bFAOQ=="
}, },
"require-yaml": { "require-yaml": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz",
"integrity": "sha512-M6eVEgLPRbeOhgSCnOTtdrOOEQzbXRchg24Xa13c39dMuraFKdI9emUo97Rih0YEFzSICmSKg8w4RQp+rd9pOQ==",
"requires": { "requires": {
"js-yaml": "" "js-yaml": ""
}, },
"dependencies": { "dependencies": {
"argparse": { "argparse": {
"version": "2.0.1", "version": "2.0.1"
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
}, },
"js-yaml": { "js-yaml": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"requires": { "requires": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
} }
@ -300,14 +245,10 @@
} }
}, },
"sprintf-js": { "sprintf-js": {
"version": "1.0.3", "version": "1.0.3"
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
}, },
"validator": { "validator": {
"version": "6.3.0", "version": "6.3.0"
"resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz",
"integrity": "sha512-BylxTwhqwjQI5MDJF7amCy/L0ejJO+74DvCsLV52Lq3+3bhVcVMKqNqOiNcQJm2G48u9EAcw4xFERAmFbwXM9Q=="
} }
} }
} }

View File

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

View File

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

View File

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

View File

@ -152,5 +152,7 @@
"It is not possible to modify sales that their articles are from Floramondo": "It is not possible to modify sales that their articles are from Floramondo", "It is not possible to modify sales that their articles are from Floramondo": "It is not possible to modify sales that their articles are from Floramondo",
"It is not possible to modify cloned sales": "It is not possible to modify cloned sales", "It is not possible to modify cloned sales": "It is not possible to modify cloned sales",
"Valid priorities: 1,2,3": "Valid priorities: 1,2,3", "Valid priorities: 1,2,3": "Valid priorities: 1,2,3",
"Warehouse inventory not set": "Almacén inventario no está establecido",
"Component cost not set": "Componente coste no está estabecido",
"Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2" "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2"
} }

View File

@ -251,6 +251,7 @@
"Receipt's bank was not found": "No se encontró el banco del recibo", "Receipt's bank was not found": "No se encontró el banco del recibo",
"This receipt was not compensated": "Este recibo no ha sido compensado", "This receipt was not compensated": "Este recibo no ha sido compensado",
"Client's email was not found": "No se encontró el email del cliente", "Client's email was not found": "No se encontró el email del cliente",
"Negative basis": "Base negativa",
"This worker code already exists": "Este codigo de trabajador ya existe", "This worker code already exists": "Este codigo de trabajador ya existe",
"This personal mail already exists": "Este correo personal ya existe", "This personal mail already exists": "Este correo personal ya existe",
"This worker already exists": "Este trabajador ya existe", "This worker already exists": "Este trabajador ya existe",
@ -264,7 +265,11 @@
"It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas", "It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas",
"A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país.", "A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país.",
"There is no assigned email for this client": "No hay correo asignado para este cliente", "There is no assigned email for this client": "No hay correo asignado para este cliente",
"Exists an invoice with a previous date": "Existe una factura con fecha anterior",
"Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite",
"Warehouse inventory not set": "El almacén inventario no está establecido",
"This locker has already been assigned": "Esta taquilla ya ha sido asignada", "This locker has already been assigned": "Esta taquilla ya ha sido asignada",
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}", "Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}",
"Not exist this branch": "La rama no existe" "Not exist this branch": "La rama no existe",
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado"
} }

View File

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

View File

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

View File

@ -27,12 +27,12 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const state = await models.ClaimState.findById(id, { const state = await models.ClaimState.findById(id, {
include: { include: {
relation: 'writeRole' relation: 'writeRole'
} }
}, myOptions); }, myOptions);
const roleWithGrants = state && state.writeRole().name; const roleWithGrants = state && state.writeRole().name;
return await models.Account.hasRole(userId, roleWithGrants, myOptions); return await models.Account.hasRole(userId, roleWithGrants, myOptions);
}; };
}; };

View File

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

View File

@ -1,7 +1,7 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const buildFilter = require('vn-loopback/util/filter').buildFilter; const buildFilter = require('vn-loopback/util/filter').buildFilter;
const {mergeFilters, mergeWhere} = require('vn-loopback/util/filter'); const { mergeFilters, mergeWhere } = require('vn-loopback/util/filter');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('logs', { Self.remoteMethodCtx('logs', {
@ -12,27 +12,27 @@ module.exports = Self => {
arg: 'id', arg: 'id',
type: 'Number', type: 'Number',
description: 'The claim id', description: 'The claim id',
http: {source: 'path'} http: { source: 'path' }
}, },
{ {
arg: 'filter', arg: 'filter',
type: 'object', type: 'object',
http: {source: 'query'} http: { source: 'query' }
}, },
{ {
arg: 'search', arg: 'search',
type: 'string', type: 'string',
http: {source: 'query'} http: { source: 'query' }
}, },
{ {
arg: 'userFk', arg: 'userFk',
type: 'number', type: 'number',
http: {source: 'query'} http: { source: 'query' }
}, },
{ {
arg: 'created', arg: 'created',
type: 'date', type: 'date',
http: {source: 'query'} http: { source: 'query' }
}, },
], ],
returns: { returns: {
@ -45,7 +45,7 @@ module.exports = Self => {
} }
}); });
Self.logs = async(ctx, id, filter, options) => { Self.logs = async (ctx, id, filter, options) => {
const conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
const args = ctx.args; const args = ctx.args;
const myOptions = {}; const myOptions = {};
@ -56,25 +56,25 @@ module.exports = Self => {
let where = buildFilter(args, (param, value) => { let where = buildFilter(args, (param, value) => {
switch (param) { switch (param) {
case 'search': case 'search':
return { return {
or: [ or: [
{changedModel: {like: `%${value}%`}}, { changedModel: { like: `%${value}%` } },
{oldInstance: {like: `%${value}%`}} { oldInstance: { like: `%${value}%` } }
] ]
}; };
case 'userFk': case 'userFk':
return {'cl.userFk': value}; return { 'cl.userFk': value };
case 'created': case 'created':
value.setHours(0, 0, 0, 0); value.setHours(0, 0, 0, 0);
to = new Date(value); to = new Date(value);
to.setHours(23, 59, 59, 999); to.setHours(23, 59, 59, 999);
return {creationDate: {between: [value, to]}}; return { creationDate: { between: [value, to] } };
} }
}); });
where = mergeWhere(where, {['cl.originFk']: id}); where = mergeWhere(where, { ['cl.originFk']: id });
filter = mergeFilters(args.filter, {where}); filter = mergeFilters(args.filter, { where });
const stmts = []; const stmts = [];
@ -102,8 +102,8 @@ module.exports = Self => {
const logs = []; const logs = [];
for (const row of result) { for (const row of result) {
const changes = []; const changes = [];
const oldInstance = JSON.parse(row.oldInstance); const oldInstance = JSON.parse(row.oldInstance) || {};
const newInstance = JSON.parse(row.newInstance); const newInstance = JSON.parse(row.newInstance) || {};
const mergedProperties = [...Object.keys(oldInstance), ...Object.keys(newInstance)]; const mergedProperties = [...Object.keys(oldInstance), ...Object.keys(newInstance)];
const properties = new Set(mergedProperties); const properties = new Set(mergedProperties);
for (const property of properties) { for (const property of properties) {

View File

@ -1,4 +1,5 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('claim regularizeClaim()', () => { describe('claim regularizeClaim()', () => {
const userId = 18; const userId = 18;
@ -39,6 +40,20 @@ describe('claim regularizeClaim()', () => {
return await models.ClaimEnd.create(claimEnds, options); return await models.ClaimEnd.create(claimEnds, options);
} }
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should send a chat message with value "Trash" and then change claim state to resolved', async() => { it('should send a chat message with value "Trash" and then change claim state to resolved', async() => {
const tx = await models.Claim.beginTransaction({}); const tx = await models.Claim.beginTransaction({});

View File

@ -82,6 +82,11 @@
"type": "hasMany", "type": "hasMany",
"model": "ClaimDms", "model": "ClaimDms",
"foreignKey": "claimFk" "foreignKey": "claimFk"
},
"lines": {
"type": "hasMany",
"model": "ClaimBeginning",
"foreignKey": "claimFk"
} }
} }
} }

View File

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

View File

@ -151,7 +151,7 @@ class Controller extends Section {
isClaimEditable() { isClaimEditable() {
if (!this.claim) return; if (!this.claim) return;
this.$http.get(`ClaimStates/${this.claim.id}/isEditable`).then(res => { this.$http.get(`ClaimStates/${this.claim.claimStateFk}/isEditable`).then(res => {
this.isRewritable = res.data; this.isRewritable = res.data;
}); });
} }

View File

@ -22,7 +22,8 @@ describe('claim', () => {
controller = $componentController('vnClaimDetail', {$element, $scope}); controller = $componentController('vnClaimDetail', {$element, $scope});
controller.claim = { controller.claim = {
ticketFk: 1, ticketFk: 1,
id: 2} id: 2,
claimStateFk: 2}
; ;
controller.salesToClaim = [{saleFk: 1}, {saleFk: 2}]; controller.salesToClaim = [{saleFk: 1}, {saleFk: 2}];
controller.salesClaimed = [{id: 1, sale: {}}]; controller.salesClaimed = [{id: 1, sale: {}}];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,49 +0,0 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('getTransactions', {
description: 'Returns last entries',
accessType: 'READ',
accepts: [{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/getTransactions`,
verb: 'GET'
}
});
Self.getTransactions = async(filter, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const conn = Self.dataSource.connector;
const stmt = new ParameterizedSQL(`
SELECT
t.id,
t.clientFk,
t.created,
t.amount / 100 amount,
t.receiptFk IS NOT NULL AS isConfirmed,
tt.message responseMessage,
te.message errorMessage
FROM hedera.tpvTransaction t
JOIN hedera.tpvMerchant m ON m.id = t.merchantFk
LEFT JOIN hedera.tpvResponse tt ON tt.id = t.response
LEFT JOIN hedera.tpvError te ON te.code = errorCode`);
stmt.merge(conn.makeSuffix(filter, 't'));
return Self.rawStmt(stmt, myOptions);
};
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,21 +0,0 @@
const models = require('vn-loopback/server/server').models;
describe('Client getTransations', () => {
it('should call getTransations() method to receive a list of Web Payments from Bruce Wayne', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {where: {clientFk: 1101}};
const result = await models.Client.getTransactions(filter, options);
expect(result[1].id).toBeTruthy();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,66 @@
const models = require('vn-loopback/server/server').models;
describe('Client transactions', () => {
it('should call transactions() method to receive a list of Web Payments from Bruce Wayne', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {};
const filter = {where: {clientFk: 1101}};
const result = await models.Client.transactions(ctx, filter, options);
expect(result[1].id).toBeTruthy();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should call transactions() method filtering by orderFk', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {args: {orderFk: 6}};
const filter = {};
const result = await models.Client.transactions(ctx, filter, options);
const firstRow = result[0];
expect(result.length).toEqual(1);
expect(firstRow.id).toEqual(6);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should call transactions() method filtering by amount', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {args: {amount: 40}};
const filter = {};
const result = await models.Client.transactions(ctx, filter, options);
const randomIndex = Math.floor(Math.random() * result.length);
const transaction = result[randomIndex];
expect(transaction.amount).toEqual(40);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,84 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const buildFilter = require('vn-loopback/util/filter').buildFilter;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethodCtx('transactions', {
description: 'Returns customer transactions',
accessType: 'READ',
accepts: [
{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
},
{
arg: 'orderFk',
type: 'number',
http: {source: 'query'}
},
{
arg: 'clientFk',
type: 'number',
http: {source: 'query'}
},
{
arg: 'amount',
type: 'number',
http: {source: 'query'}
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/transactions`,
verb: 'GET'
}
});
Self.transactions = async(ctx, filter, options) => {
const args = ctx.args;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const where = buildFilter(args, (param, value) => {
switch (param) {
case 'orderFk':
return {'t.id': value};
case 'clientFk':
return {'t.clientFk': value};
case 'amount':
return {'t.amount': (value * 100)};
}
});
filter = mergeFilters(filter, {where});
const conn = Self.dataSource.connector;
const stmt = new ParameterizedSQL(`
SELECT
t.id,
t.clientFk,
c.name AS customerName,
t.created,
t.amount / 100 amount,
t.receiptFk IS NOT NULL AS isConfirmed,
tt.message responseMessage,
te.message errorMessage
FROM hedera.tpvTransaction t
JOIN client c ON c.id = t.clientFk
JOIN hedera.tpvMerchant m ON m.id = t.merchantFk
LEFT JOIN hedera.tpvResponse tt ON tt.id = t.response
LEFT JOIN hedera.tpvError te ON te.code = errorCode`);
stmt.merge(conn.makeSuffix(filter, 't'));
return Self.rawStmt(stmt, myOptions);
};
};

View File

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

View File

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

View File

@ -13,7 +13,7 @@ module.exports = Self => {
require('../methods/client/getCard')(Self); require('../methods/client/getCard')(Self);
require('../methods/client/getDebt')(Self); require('../methods/client/getDebt')(Self);
require('../methods/client/getMana')(Self); require('../methods/client/getMana')(Self);
require('../methods/client/getTransactions')(Self); require('../methods/client/transactions')(Self);
require('../methods/client/hasCustomerRole')(Self); require('../methods/client/hasCustomerRole')(Self);
require('../methods/client/isValidClient')(Self); require('../methods/client/isValidClient')(Self);
require('../methods/client/lastActiveTickets')(Self); require('../methods/client/lastActiveTickets')(Self);

View File

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

View File

@ -154,6 +154,11 @@
"model": "Account", "model": "Account",
"foreignKey": "id" "foreignKey": "id"
}, },
"user": {
"type": "belongsTo",
"model": "Account",
"foreignKey": "id"
},
"payMethod": { "payMethod": {
"type": "belongsTo", "type": "belongsTo",
"model": "PayMethod", "model": "PayMethod",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,55 +1,39 @@
<vn-crud-model <vn-crud-model vn-id="model" url="clients/transactions" link="{clientFk: $ctrl.$params.id}" limit="20"
vn-id="model" data="transactions" order="created DESC" auto-load="true">
url="clients/getTransactions"
link="{clientFk: $ctrl.$params.id}"
limit="20"
data="transactions"
order="created DESC"
auto-load="true">
</vn-crud-model> </vn-crud-model>
<vn-data-viewer <vn-data-viewer model="model" class="vn-w-md">
model="model"
class="vn-w-md">
<vn-card> <vn-card>
<vn-table model="model"> <vn-table model="model">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th shrink>State</vn-th> <vn-th shrink>State</vn-th>
<vn-th field="id" number>Id</vn-th> <vn-th field="id" number>Id</vn-th>
<vn-th field="created" expand>Date</vn-th> <vn-th field="created" expand>Date</vn-th>
<vn-th field="amount" number>Amount</vn-th> <vn-th field="amount" number>Amount</vn-th>
<vn-th shrink></vn-th> <vn-th shrink></vn-th>
</vn-tr> </vn-tr>
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
<vn-tr ng-repeat="transaction in transactions"> <vn-tr ng-repeat="transaction in transactions">
<vn-td shrink> <vn-td shrink>
<vn-icon <vn-icon vn-tooltip="{{::$ctrl.getFormattedMessage(transaction)}}"
vn-tooltip="{{::$ctrl.getFormattedMessage(transaction)}}" ng-show="::((transaction.errorMessage || transaction.responseMessage) && !transaction.isConfirmed)"
ng-show="::((transaction.errorMessage || transaction.responseMessage) && !transaction.isConfirmed)" icon="clear">
icon="clear"> </vn-icon>
</vn-icon> <vn-icon vn-tooltip="Confirmed" ng-show="::(transaction.isConfirmed)" icon="check">
<vn-icon </vn-icon>
vn-tooltip="Confirmed" </vn-td>
ng-show="::(transaction.isConfirmed)" <vn-td number>{{::transaction.id}}</vn-td>
icon="check"> <vn-td>{{::transaction.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-icon> <vn-td number>{{::transaction.amount | currency: 'EUR':2}}</vn-td>
</vn-td> <vn-td shrink>
<vn-td number>{{::transaction.id}}</vn-td> <vn-icon-button icon="done_all" vn-acl="administrative" vn-acl-action="remove"
<vn-td>{{::transaction.created | date:'dd/MM/yyyy HH:mm'}}</vn-td> translate-attr="{title: 'Confirm transaction'}" ng-show="::!transaction.isConfirmed"
<vn-td number>{{::transaction.amount | currency: 'EUR':2}}</vn-td> ng-click="$ctrl.confirm(transaction)">
<vn-td shrink> </vn-icon-button>
<vn-icon-button </vn-td>
icon="done_all" </vn-tr>
vn-acl="administrative" </vn-tbody>
vn-acl-action="remove" </vn-table>
translate-attr="{title: 'Confirm transaction'}"
ng-show="::!transaction.isConfirmed"
ng-click="$ctrl.confirm(transaction)">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card> </vn-card>
</vn-data-viewer> </vn-data-viewer>

View File

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

View File

@ -1,6 +1,22 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('Buy editLatestsBuys()', () => { describe('Buy editLatestsBuys()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should change the value of a given column for the selected buys', async() => { it('should change the value of a given column for the selected buys', async() => {
const tx = await models.Buy.beginTransaction({}); const tx = await models.Buy.beginTransaction({});
const options = {transaction: tx}; const options = {transaction: tx};

View File

@ -3,7 +3,8 @@
"base": "Loggable", "base": "Loggable",
"log": { "log": {
"model": "EntryLog", "model": "EntryLog",
"relation": "entry" "relation": "entry",
"grabUser": true
}, },
"options": { "options": {
"mysql": { "mysql": {

View File

@ -2,7 +2,8 @@
"name": "Entry", "name": "Entry",
"base": "Loggable", "base": "Loggable",
"log": { "log": {
"model":"EntryLog" "model":"EntryLog",
"grabUser": true
}, },
"options": { "options": {
"mysql": { "mysql": {

View File

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

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