Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Joan Sanchez 2023-04-05 08:39:43 +02:00
commit ca30aaa18a
127 changed files with 2597 additions and 860 deletions

View File

@ -5,5 +5,6 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"search.useIgnoreFiles": false
"search.useIgnoreFiles": false,
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}

View File

@ -5,14 +5,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2312.01] - 2023-04-06
### Added
- (Monitor tickets) Muestra un icono al lado de la zona, si el ticket es frágil y se envía por agencia
### Changed
- (Monitor tickets) Cuando se filtra por 'Pendiente' ya no muestra los estados de 'Previa'
- (Envíos -> Extra comunitarios) Se agrupan las entradas del mismo travel. Añadidos campos Referencia y Importe.
- (Envíos -> Índice) Cambiado el buscador superior por uno lateral
### Fixed
-
## [2310.01] - 2023-03-23
### Added
- (Trabajadores -> Control de horario) Ahora se puede confirmar/no confirmar el registro horario de cada semana desde esta sección
### Changed
-
### Fixed
- (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo"
- (General) Al pasar el ratón por encima del icono de "Borrar" en un campo, se hacía más grande afectando a la interfaz

View File

@ -30,16 +30,23 @@ module.exports = Self => {
const recipient = to.replace('@', '');
if (sender.name != recipient) {
await models.Chat.create({
const chat = await models.Chat.create({
senderFk: sender.id,
recipient: to,
dated: Date.vnNew(),
checkUserStatus: 0,
message: message,
status: 0,
status: 'sending',
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;
}

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

View File

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

View File

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

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
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() => {
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

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

View File

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

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,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,4 @@
ALTER TABLE `vn`.`invoiceInConfig` ADD daysAgo INT UNSIGNED DEFAULT 45 COMMENT 'Días en el pasado para mostrar facturas en invoiceIn series en salix';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('InvoiceIn', 'getSerial', 'READ', 'ALLOW', 'ROLE', 'administrative');

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 `vn`.`itemCategory` ic
JOIN `vn`.`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,3 @@
DROP TRIGGER `vn`.`supplierAccount_afterInsert`;
DROP TRIGGER `vn`.`supplierAccount_afterUpdate`;
DROP TRIGGER `vn`.`supplierAccount_afterDelete`;

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

@ -0,0 +1,72 @@
CREATE TABLE `vn`.`wagonType` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL UNIQUE,
`divisible` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
CREATE TABLE `vn`.`wagonTypeColor` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL UNIQUE,
`rgb` varchar(30) NOT NULL UNIQUE,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
CREATE TABLE `vn`.`wagonTypeTray` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`typeFk` int(11) unsigned,
`height` int(11) unsigned NOT NULL,
`colorFk` int(11) unsigned,
PRIMARY KEY (`id`),
UNIQUE KEY (`typeFk`,`height`),
CONSTRAINT `wagonTypeTray_type` FOREIGN KEY (`typeFk`) REFERENCES `wagonType` (`id`) ON UPDATE CASCADE,
CONSTRAINT `wagonTypeTray_color` FOREIGN KEY (`colorFk`) REFERENCES `wagonTypeColor` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
CREATE TABLE `vn`.`wagonConfig` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`width` int(11) unsigned DEFAULT 1350,
`height` int(11) unsigned DEFAULT 1900,
`maxWagonHeight` int(11) unsigned DEFAULT 200,
`minHeightBetweenTrays` int(11) unsigned DEFAULT 50,
`maxTrays` int(11) unsigned DEFAULT 6,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
CREATE TABLE `vn`.`collectionWagon` (
`collectionFk` int(11) NOT NULL,
`wagonFk` int(11) NOT NULL,
`position` int(11) unsigned,
PRIMARY KEY (`collectionFk`,`position`),
UNIQUE KEY `collectionWagon_unique` (`collectionFk`,`wagonFk`),
CONSTRAINT `collectionWagon_collection` FOREIGN KEY (`collectionFk`) REFERENCES `collection` (`id`) ON UPDATE CASCADE,
CONSTRAINT `collectionWagon_wagon` FOREIGN KEY (`wagonFk`) REFERENCES `wagon` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
CREATE TABLE `vn`.`collectionWagonTicket` (
`ticketFk` int(11) NOT NULL,
`wagonFk` int(11) NOT NULL,
`trayFk` int(11) unsigned NOT NULL,
`side` SET('L', 'R') NULL,
PRIMARY KEY (`ticketFk`),
CONSTRAINT `collectionWagonTicket_ticket` FOREIGN KEY (`ticketFk`) REFERENCES `ticket` (`id`) ON UPDATE CASCADE,
CONSTRAINT `collectionWagonTicket_wagon` FOREIGN KEY (`wagonFk`) REFERENCES `wagon` (`id`) ON UPDATE CASCADE,
CONSTRAINT `collectionWagonTicket_tray` FOREIGN KEY (`trayFk`) REFERENCES `wagonTypeTray` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
ALTER TABLE `vn`.`wagon` ADD `typeFk` int(11) unsigned NOT NULL;
ALTER TABLE `vn`.`wagon` ADD `label` int(11) unsigned NOT NULL;
ALTER TABLE `vn`.`wagon` ADD CONSTRAINT `wagon_type` FOREIGN KEY (`typeFk`) REFERENCES `wagonType` (`id`) ON UPDATE CASCADE;
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('WagonType', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
('WagonTypeColor', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
('WagonTypeTray', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
('WagonConfig', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
('CollectionWagon', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
('CollectionWagonTicket', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
('Wagon', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
('WagonType', 'createWagonType', '*', 'ALLOW', 'ROLE', 'productionAssi'),
('WagonType', 'deleteWagonType', '*', 'ALLOW', 'ROLE', 'productionAssi'),
('WagonType', 'editWagonType', '*', 'ALLOW', 'ROLE', 'productionAssi');

View File

@ -173,6 +173,10 @@ INSERT INTO `vn`.`sector`(`id`, `description`, `warehouseFk`, `isPreviousPrepare
(1, 'First sector', 1, 1, 'FIRST'),
(2, 'Second sector', 2, 0, 'SECOND');
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
(1, 'printer1', 'path1', 0, 1 , NULL),
@ -295,7 +299,8 @@ INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt
INSERT INTO `vn`.`payDem`(`id`, `payDem`)
VALUES
(1, 10),
(2, 20);
(2, 20),
(7, 0);
INSERT INTO `vn`.`autonomy`(`id`, `name`, `countryFk`)
VALUES
@ -496,7 +501,8 @@ INSERT INTO `vn`.`observationType`(`id`,`description`, `code`)
(3, 'Delivery', 'delivery'),
(4, 'SalesPerson', 'salesPerson'),
(5, 'Administrative', 'administrative'),
(6, 'Weight', 'weight');
(6, 'Weight', 'weight'),
(7, 'InvoiceOut', 'invoiceOut');
INSERT INTO `vn`.`addressObservation`(`id`,`addressFk`,`observationTypeFk`,`description`)
VALUES
@ -733,7 +739,9 @@ INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `des
(9, 23, 5, 'care with the dog'),
(10, 23, 4, 'Reclama ticket: 8'),
(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
-- 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'),
('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
(1, 'CRI', 'Crisantemo', 2, 31, 35, 0, 'cool'),
(2, 'ITG', 'Anthurium', 1, 31, 35, 0, 'cool'),
(3, 'WPN', 'Paniculata', 2, 31, 35, 0, 'cool'),
(4, 'PRT', 'Delivery ports', 3, NULL, 35, 1, 'warm'),
(5, 'CON', 'Container', 3, NULL, 35, 1, 'warm'),
(6, 'ALS', 'Alstroemeria', 1, 31, 16, 0, 'warm');
(1, 'CRI', 'Crisantemo', 2, 31, 35, 0, 'cool', 0),
(2, 'ITG', 'Anthurium', 1, 31, 35, 0, 'cool', 1),
(3, 'WPN', 'Paniculata', 2, 31, 35, 0, 'cool', 0),
(4, 'PRT', 'Delivery ports', 3, NULL, 35, 1, 'warm', 0),
(5, 'CON', 'Container', 3, NULL, 35, 1, 'warm', 0),
(6, 'ALS', 'Alstroemeria', 1, 31, 16, 0, 'warm', 1);
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`)
VALUES
@ -2482,9 +2490,9 @@ REPLACE INTO `vn`.`invoiceIn`(`id`, `serialNumber`,`serial`, `supplierFk`, `issu
(9, 1009, 'R', 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1242, 1, 442, 1),
(10, 1010, 'R', 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1243, 1, 442, 1);
INSERT INTO `vn`.`invoiceInConfig` (`id`, `retentionRate`, `retentionName`, `sageWithholdingFk`)
INSERT INTO `vn`.`invoiceInConfig` (`id`, `retentionRate`, `retentionName`, `sageWithholdingFk`, `daysAgo`)
VALUES
(1, -2, '2% retention', 2);
(1, -2, '2% retention', 2, 45);
INSERT INTO `vn`.`invoiceInDueDay`(`invoiceInFk`, `dueDated`, `bankFk`, `amount`)
VALUES
@ -2626,8 +2634,8 @@ INSERT INTO `vn`.`supplierAgencyTerm` (`agencyFk`, `supplierFk`, `minimumPackage
INSERT INTO `vn`.`chat` (`senderFk`, `recipient`, `dated`, `checkUserStatus`, `message`, `status`, `attempts`)
VALUES
(1101, '@PetterParker', util.VN_CURDATE(), 1, 'First test message', 0, 0),
(1101, '@PetterParker', util.VN_CURDATE(), 0, 'Second 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, 'pending');
INSERT INTO `vn`.`mobileAppVersionControl` (`appName`, `version`, `isVersionCritical`)
@ -2832,4 +2840,27 @@ INSERT INTO `vn`.`workerTimeControlMail` (`id`, `workerFk`, `year`, `week`, `sta
(3, 9, 2000, 51, 'CONFIRMED', util.VN_NOW(), 1, NULL),
(4, 9, 2001, 1, 'SENDED', util.VN_NOW(), 1, NULL);
INSERT INTO `vn`.`wagonConfig` (`id`, `width`, `height`, `maxWagonHeight`, `minHeightBetweenTrays`, `maxTrays`)
VALUES
(1, 1350, 1900, 200, 50, 6);
INSERT INTO `vn`.`wagonTypeColor` (`id`, `name`, `rgb`)
VALUES
(1, 'white', '#ffffff'),
(2, 'red', '#ff0000'),
(3, 'green', '#00ff00'),
(4, 'blue', '#0000ff');
INSERT INTO `vn`.`wagonType` (`id`, `name`, `divisible`)
VALUES
(1, 'Wagon Type #1', 1);
INSERT INTO `vn`.`wagonTypeTray` (`id`, `typeFk`, `height`, `colorFk`)
VALUES
(1, 1, 100, 1),
(2, 1, 50, 2),
(3, 1, 0, 3);

View File

@ -524,7 +524,6 @@ export default {
},
itemLog: {
anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr',
fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(4) td.after',
},
ticketSummary: {
header: 'vn-ticket-summary > vn-card > h5',
@ -1040,7 +1039,6 @@ export default {
boss: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bossFk"]',
role: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.roleFk"]',
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"]',
},
workerPda: {
@ -1129,6 +1127,15 @@ export default {
saveButton: 'vn-invoice-in-tax vn-submit',
},
invoiceInIndex: {
topbarSearchParams: 'vn-searchbar div.search-params > span',
},
invoiceInSerial: {
daysAgo: 'vn-invoice-in-serial-search-panel vn-input-number[ng-model="$ctrl.filter.daysAgo"]',
serial: 'vn-invoice-in-serial-search-panel vn-textfield[ng-model="$ctrl.filter.serial"]',
chip: 'vn-chip > vn-icon',
goToIndex: 'vn-invoice-in-serial vn-icon-button[icon="icon-invoice-in"]',
},
travelIndex: {
anySearchResult: 'vn-travel-index vn-tbody > a',
firstSearchResult: 'vn-travel-index vn-tbody > a:nth-child(1)',
@ -1140,7 +1147,16 @@ export default {
landingDate: 'vn-travel-create vn-date-picker[ng-model="$ctrl.travel.landed"]',
warehouseOut: 'vn-travel-create vn-autocomplete[ng-model="$ctrl.travel.warehouseOutFk"]',
warehouseIn: 'vn-travel-create vn-autocomplete[ng-model="$ctrl.travel.warehouseInFk"]',
save: 'vn-travel-create vn-submit > button'
save: 'vn-travel-create vn-submit > button',
generalSearchFilter: 'vn-travel-search-panel vn-textfield[ng-model="$ctrl.search"]',
agencyFilter: 'vn-travel-search-panel vn-autocomplete[ng-model="$ctrl.filter.agencyModeFk"]',
warehouseOutFilter: 'vn-travel-search-panel vn-autocomplete[ng-model="$ctrl.filter.warehouseOutFk"]',
warehouseInFilter: 'vn-travel-search-panel vn-autocomplete[ng-model="$ctrl.filter.warehouseInFk"]',
scopeDaysFilter: 'vn-travel-search-panel vn-input-number[ng-model="$ctrl.filter.scopeDays"]',
continentFilter: 'vn-travel-search-panel vn-autocomplete[ng-model="$ctrl.filter.continent"]',
totalEntriesFilter: 'vn-travel-search-panel vn-input-number[ng-model="$ctrl.totalEntries"]',
chip: 'vn-travel-search-panel vn-chip > vn-icon',
},
travelExtraCommunity: {
anySearchResult: 'vn-travel-extra-community > vn-card div > tbody > tr[ng-attr-id="{{::travel.id}}"]',

View File

@ -27,7 +27,7 @@ describe('Worker time control path', () => {
date.setMonth(date.getMonth() + 1);
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');
expect(result).toContain(month);
@ -36,7 +36,7 @@ describe('Worker time control path', () => {
date.setDate(1);
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');
expect(result).toContain(month);
@ -49,7 +49,7 @@ describe('Worker time control path', () => {
await page.loginAndModule('salesBoss', 'worker');
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');

View File

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

View File

@ -48,14 +48,14 @@ describe('Item log path', () => {
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);
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
.waitToGetProperty(selectors.itemLog.fifthLineCreatedProperty, 'innerText');

View File

@ -197,6 +197,7 @@ describe('Ticket Edit sale path', () => {
});
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.waitToClick(selectors.ticketSales.firstSaleHistoryButton);
await page.waitForSelector(selectors.ticketSales.firstSaleHistory);

View File

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

View File

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

View File

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

View File

@ -0,0 +1,48 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('InvoiceIn serial path', () => {
let browser;
let page;
let httpRequest;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('administrative', 'invoiceIn');
await page.accessToSection('invoiceIn.serial');
page.on('request', req => {
if (req.url().includes(`InvoiceIns/getSerial`))
httpRequest = req.url();
});
});
afterAll(async() => {
await browser.close();
});
it('should check that passes the correct params to back', async() => {
await page.overwrite(selectors.invoiceInSerial.daysAgo, '30');
await page.keyboard.press('Enter');
expect(httpRequest).toContain('daysAgo=30');
await page.overwrite(selectors.invoiceInSerial.serial, 'R');
await page.keyboard.press('Enter');
expect(httpRequest).toContain('serial=R');
await page.click(selectors.invoiceInSerial.chip);
});
it('should go to index and check if the search-panel has the correct params', async() => {
await page.click(selectors.invoiceInSerial.goToIndex);
const params = await page.$$(selectors.invoiceInIndex.topbarSearchParams);
const serial = await params[0].getProperty('title');
const isBooked = await params[1].getProperty('title');
const from = await params[2].getProperty('title');
expect(await serial.jsonValue()).toContain('serial');
expect(await isBooked.jsonValue()).toContain('not isBooked');
expect(await from.jsonValue()).toContain('from');
});
});

View File

@ -9,7 +9,8 @@ describe('Travel basic data path', () => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'travel');
await page.accessToSearchResult('3');
await page.write(selectors.travelIndex.generalSearchFilter, '3');
await page.keyboard.press('Enter');
await page.accessToSection('travel.card.basicData');
});
@ -89,11 +90,13 @@ describe('Travel basic data path', () => {
});
it('should navigate to the travel logs', async() => {
pending('https://redmine.verdnatura.es/issues/5455');
await page.accessToSection('travel.card.log');
await page.waitForState('travel.card.log');
});
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');
expect(result).toContain('new reference!');

View File

@ -9,7 +9,8 @@ describe('Travel descriptor path', () => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'travel');
await page.accessToSearchResult('1');
await page.write(selectors.travelIndex.generalSearchFilter, '1');
await page.keyboard.press('Enter');
await page.waitForState('travel.card.summary');
});
@ -81,7 +82,8 @@ describe('Travel descriptor path', () => {
await page.waitToClick('.cancel');
await page.waitToClick(selectors.globalItems.homeButton);
await page.selectModule('travel');
await page.accessToSearchResult('3');
await page.write(selectors.travelIndex.generalSearchFilter, '3');
await page.keyboard.press('Enter');
await page.waitForState('travel.card.summary');
const state = await page.getState();

View File

@ -10,7 +10,8 @@ describe('Travel thermograph path', () => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'travel');
await page.accessToSearchResult('3');
await page.write(selectors.travelIndex.generalSearchFilter, '3');
await page.keyboard.press('Enter');
await page.accessToSection('travel.card.thermograph.index');
});

View File

@ -0,0 +1,62 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Travel search panel path', () => {
let browser;
let page;
let httpRequest;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'travel');
page.on('request', req => {
if (req.url().includes(`Travels/filter`))
httpRequest = req.url();
});
});
afterAll(async() => {
await browser.close();
});
it('should filter using all the fields', async() => {
await page.click(selectors.travelIndex.chip);
await page.write(selectors.travelIndex.generalSearchFilter, 'travel');
await page.keyboard.press('Enter');
expect(httpRequest).toContain('search=travel');
await page.click(selectors.travelIndex.chip);
await page.autocompleteSearch(selectors.travelIndex.agencyFilter, 'Entanglement');
expect(httpRequest).toContain('agencyModeFk');
await page.click(selectors.travelIndex.chip);
await page.autocompleteSearch(selectors.travelIndex.warehouseOutFilter, 'Warehouse One');
expect(httpRequest).toContain('warehouseOutFk');
await page.click(selectors.travelIndex.chip);
await page.autocompleteSearch(selectors.travelIndex.warehouseInFilter, 'Warehouse Two');
expect(httpRequest).toContain('warehouseInFk');
await page.click(selectors.travelIndex.chip);
await page.overwrite(selectors.travelIndex.scopeDaysFilter, '15');
await page.keyboard.press('Enter');
expect(httpRequest).toContain('scopeDays=15');
await page.click(selectors.travelIndex.chip);
await page.autocompleteSearch(selectors.travelIndex.continentFilter, 'Asia');
expect(httpRequest).toContain('continent');
await page.click(selectors.travelIndex.chip);
await page.write(selectors.travelIndex.totalEntriesFilter, '1');
await page.keyboard.press('Enter');
expect(httpRequest).toContain('totalEntries=1');
});
});

View File

@ -114,12 +114,14 @@ export default class Calendar extends FormInput {
let day = date.getDate();
let wday = date.getDay();
let month = date.getMonth();
let year = date.getFullYear();
const currentDay = Date.vnNew().getDate();
const currentMonth = Date.vnNew().getMonth();
const currentYear = Date.vnNew().getFullYear();
let classes = {
today: day === currentDay && month === currentMonth,
today: day === currentDay && month === currentMonth && year === currentYear,
weekend: wday === 6 || wday === 0,
previous: month < this.month,
current: month == this.month,

View File

@ -26,7 +26,7 @@ Value should have at most %s characters: El valor debe tener un máximo de %s ca
Enter a new search: Introduce una nueva búsqueda
No results: Sin resultados
Ups! It seems there was an error: ¡Vaya! Parece que ha habido un error
General search: Busqueda general
General search: Búsqueda general
January: Enero
February: Febrero
March: Marzo
@ -42,9 +42,9 @@ December: Diciembre
Monday: Lunes
Tuesday: Martes
Wednesday: Miércoles
Thursday: Jueves
Friday: Viernes
Saturday: Sábado
Thursday: Jueves
Friday: Viernes
Saturday: Sábado
Sunday: Domingo
Has delivery: Hay reparto
Loading: Cargando
@ -63,4 +63,4 @@ Loading...: Cargando...
No results found: Sin resultados
No data: Sin datos
Undo changes: Deshacer cambios
Load more results: Cargar más resultados
Load more results: Cargar más resultados

View File

@ -2,6 +2,7 @@
$font-size: 11pt;
$menu-width: 256px;
$right-menu-width: 318px;
$topbar-height: 56px;
$mobile-width: 800px;
$float-spacing: 20px;

View File

@ -88,13 +88,13 @@ vn-layout {
}
&.right-menu {
& > vn-topbar > .end {
width: 80px + $menu-width;
width: 80px + $right-menu-width;
}
& > .main-view {
padding-right: $menu-width;
padding-right: $right-menu-width;
}
[fixed-bottom-right] {
right: $menu-width;
right: $right-menu-width;
}
}
& > .main-view {

View File

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

View File

@ -1,4 +1,5 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('claim regularizeClaim()', () => {
const userId = 18;
@ -39,6 +40,20 @@ describe('claim regularizeClaim()', () => {
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() => {
const tx = await models.Claim.beginTransaction({});

View File

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

View File

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

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,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

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

View File

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

View File

@ -1,6 +1,22 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
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() => {
const tx = await models.Buy.beginTransaction({});
const options = {transaction: tx};

View File

@ -0,0 +1,65 @@
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('getSerial', {
description: 'Return invoiceIn serial',
accessType: 'READ',
accepts: [{
arg: 'filter',
type: 'object'
}, {
arg: 'daysAgo',
type: 'number',
required: true
}, {
arg: 'serial',
type: 'string'
}],
returns: {
type: 'object',
root: true
},
http: {
path: '/getSerial',
verb: 'GET'
}
});
Self.getSerial = async(ctx, options) => {
const conn = Self.dataSource.connector;
const args = ctx.args;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const issued = Date.vnNew();
const where = buildFilter(args, (param, value) => {
switch (param) {
case 'daysAgo':
issued.setDate(issued.getDate() - value);
return {'i.issued': {gte: issued}};
case 'serial':
return {'i.serial': {like: `%${value}%`}};
}
});
filter = mergeFilters(args.filter, {where});
const stmt = new ParameterizedSQL(
`SELECT i.serial, SUM(IF(i.isBooked, 0,1)) pending, COUNT(*) total
FROM vn.invoiceIn i`
);
stmt.merge(conn.makeWhere(filter.where));
stmt.merge(`GROUP BY i.serial`);
stmt.merge(conn.makeOrderBy(filter.order));
stmt.merge(conn.makeLimit(filter));
const result = await conn.executeStmt(stmt, myOptions);
return result;
};
};

View File

@ -0,0 +1,24 @@
const models = require('vn-loopback/server/server').models;
describe('invoiceIn getSerial()', () => {
it('should check that returns without serial param', async() => {
const ctx = {args: {daysAgo: 45}};
const result = await models.InvoiceIn.getSerial(ctx);
expect(result.length).toBeGreaterThan(0);
});
it('should check that returns with serial param', async() => {
const ctx = {args: {daysAgo: 45, serial: 'R'}};
const result = await models.InvoiceIn.getSerial(ctx);
expect(result.length).toBeGreaterThan(0);
});
it('should check that returns with non exist serial param', async() => {
const ctx = {args: {daysAgo: 45, serial: 'Mock serial'}};
const result = await models.InvoiceIn.getSerial(ctx);
expect(result.length).toEqual(0);
});
});

View File

@ -17,6 +17,9 @@
},
"retentionName": {
"type": "string"
},
"daysAgo": {
"type": "number"
}
},
"relations": {

View File

@ -6,4 +6,5 @@ module.exports = Self => {
require('../methods/invoice-in/getTotals')(Self);
require('../methods/invoice-in/invoiceInPdf')(Self);
require('../methods/invoice-in/invoiceInEmail')(Self);
require('../methods/invoice-in/getSerial')(Self);
};

View File

@ -13,3 +13,5 @@ import './dueDay';
import './intrastat';
import './create';
import './log';
import './serial';
import './serial-search-panel';

View File

@ -7,6 +7,7 @@ Foreign value: Divisa
InvoiceIn: Facturas recibidas
InvoiceIn cloned: Factura clonada
InvoiceIn deleted: Factura eliminada
InvoiceIn Serial: Facturas por series
Invoice list: Listado de facturas recibidas
InvoiceIn booked: Factura contabilizada
Net: Neto
@ -22,3 +23,4 @@ Total stems: Total tallos
Show agricultural receipt as PDF: Ver recibo agrícola como PDF
Send agricultural receipt as PDF: Enviar recibo agrícola como PDF
New InvoiceIn: Nueva Factura
Days ago: Últimos días

View File

@ -12,6 +12,10 @@
{
"state": "invoiceIn.index",
"icon": "icon-invoice-in"
},
{
"state": "invoiceIn.serial",
"icon": "icon-invoice-in"
}
],
"card": [
@ -54,6 +58,15 @@
"administrative"
]
},
{
"url": "/serial",
"state": "invoiceIn.serial",
"component": "vn-invoice-in-serial",
"description": "InvoiceIn Serial",
"acl": [
"administrative"
]
},
{
"url": "/:id",
"state": "invoiceIn.card",
@ -133,4 +146,4 @@
]
}
]
}
}

View File

@ -0,0 +1,27 @@
<vn-side-menu side="right">
<vn-horizontal class="input">
<vn-input-number
label="Days ago"
ng-model="$ctrl.filter.daysAgo"
vn-focus
ng-keydown="$ctrl.onKeyPress($event)"
min="0">
</vn-input-number>
</vn-horizontal>
<vn-horizontal class="input">
<vn-textfield
label="Serial"
ng-model="$ctrl.filter.serial"
ng-keydown="$ctrl.onKeyPress($event)">
</vn-textfield>
</vn-horizontal>
<div class="chips">
<vn-chip
ng-if="$ctrl.filter.serial"
removable="true"
on-remove="$ctrl.removeItemFilter('serial')"
class="colored">
<span>{{$ctrl.$t('Serial')}}: {{$ctrl.filter.serial}}</span>
</vn-chip>
</div>
</vn-side-menu>

View File

@ -0,0 +1,44 @@
import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
import './style.scss';
class Controller extends SearchPanel {
constructor($element, $) {
super($element, $);
this.filter = {};
const filter = {
fields: ['daysAgo']
};
this.$http.get('InvoiceInConfigs', {filter}).then(res => {
if (res.data) {
this.invoiceInConfig = res.data[0];
this.addFilters();
}
});
}
removeItemFilter(param) {
this.filter[param] = null;
this.addFilters();
}
onKeyPress($event) {
if ($event.key === 'Enter')
this.addFilters();
}
addFilters() {
if (!this.filter.daysAgo)
this.filter.daysAgo = this.invoiceInConfig.daysAgo;
return this.model.addFilter({}, this.filter);
}
}
ngModule.component('vnInvoiceInSerialSearchPanel', {
template: require('./index.html'),
controller: Controller,
bindings: {
model: '<'
}
});

View File

@ -0,0 +1,43 @@
import './index.js';
describe('InvoiceIn', () => {
describe('Component serial-search-panel', () => {
let controller;
let $scope;
beforeEach(ngModule('invoiceIn'));
beforeEach(inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
const $element = angular.element('<vn-invoice-in-serial-search-panel></vn-invoice-in-serial-search-panel>');
controller = $componentController('vnInvoiceInSerialSearchPanel', {$element, $scope});
controller.model = {
addFilter: jest.fn(),
};
controller.invoiceInConfig = {
daysAgo: 45,
};
}));
describe('addFilters()', () => {
it('should add default daysAgo if it is not already set', () => {
controller.filter = {
serial: 'R',
};
controller.addFilters();
expect(controller.filter.daysAgo).toEqual(controller.invoiceInConfig.daysAgo);
});
it('should not add default daysAgo if it is already set', () => {
controller.filter = {
daysAgo: 1,
serial: 'R',
};
controller.addFilters();
expect(controller.filter.daysAgo).toEqual(1);
});
});
});
});

View File

@ -0,0 +1,24 @@
@import "variables";
vn-invoice-in-serial-search-panel vn-side-menu div {
& > .input {
padding-left: $spacing-md;
padding-right: $spacing-md;
border-color: $color-spacer;
border-bottom: $border-thin;
}
& > .horizontal {
grid-auto-flow: column;
grid-column-gap: $spacing-sm;
align-items: center;
}
& > .chips {
display: flex;
flex-wrap: wrap;
padding: $spacing-md;
overflow: hidden;
max-width: 100%;
border-color: $color-spacer;
border-top: $border-thin;
}
}

View File

@ -0,0 +1,40 @@
<vn-crud-model
vn-id="model"
url="InvoiceIns/getSerial"
limit="20">
</vn-crud-model>
<vn-portal slot="topbar">
</vn-portal>
<vn-invoice-in-serial-search-panel
model="model">
</vn-invoice-in-serial-search-panel>
<vn-data-viewer
model="model"
class="vn-w-lg">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="serial">Serial</vn-th>
<vn-th field="pending">Pending</vn-th>
<vn-th field="total">Total</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="invoiceIn in model.data">
<vn-td>{{::invoiceIn.serial}}</vn-td>
<vn-td>{{::invoiceIn.pending}}</vn-td>
<vn-td>{{::invoiceIn.total}}</vn-td>
<vn-td shrink>
<vn-icon-button
vn-click-stop="$ctrl.goToIndex(model.userParams.daysAgo, invoiceIn.serial)"
vn-tooltip="Go to InvoiceIn"
icon="icon-invoice-in">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>

View File

@ -0,0 +1,22 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
}
goToIndex(daysAgo, serial) {
const issued = Date.vnNew();
issued.setDate(issued.getDate() - daysAgo);
this.$state.go('invoiceIn.index',
{q: `{"serial": "${serial}", "isBooked": false, "from": ${issued.getTime()}}`});
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnInvoiceInSerial', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,3 @@
Serial: Serie
Pending: Pendientes
Go to InvoiceIn: Ir al listado de facturas recibidas

View File

@ -1,4 +1,5 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('upsertFixedPrice()', () => {
const now = Date.vnNew();
@ -7,6 +8,17 @@ describe('upsertFixedPrice()', () => {
beforeAll(async() => {
originalFixedPrice = await models.FixedPrice.findById(fixedPriceId);
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it(`should toggle the hasMinPrice boolean if there's a minPrice and update the rest of the data`, async() => {

View File

@ -1,7 +1,21 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('item clone()', () => {
let nextItemId;
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
beforeEach(async() => {
let query = `SELECT i1.id + 1 as id FROM vn.item i1

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('item new()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should create a new item, adding the name as a tag', async() => {
const tx = await models.Item.beginTransaction({});
const options = {transaction: tx};

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('regularize()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 18},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should create a new ticket and add a line', async() => {
const tx = await models.Item.beginTransaction({});
const options = {transaction: tx};

View File

@ -86,11 +86,11 @@
class="expendable">
<span
vn-tooltip="
{{::$ctrl.$t('Cost')}}: {{::entry.buyingValue | currency: 'EUR':2 | dashIfEmpty}}<br>
{{::$ctrl.$t('Package')}}: {{::entry.packageValue | currency: 'EUR':2 | dashIfEmpty}}<br>
{{::$ctrl.$t('Freight')}}: {{::entry.freightValue | currency: 'EUR':2 | dashIfEmpty}}<br>
{{::$ctrl.$t('Comission')}}: {{::entry.comissionValue | currency: 'EUR':2 | dashIfEmpty}}">
{{::entry.cost | currency: 'EUR':2 | dashIfEmpty}}
{{::$ctrl.$t('Cost')}}: {{::entry.buyingValue | currency: 'EUR':3 | dashIfEmpty}}<br>
{{::$ctrl.$t('Package')}}: {{::entry.packageValue | currency: 'EUR':3 | dashIfEmpty}}<br>
{{::$ctrl.$t('Freight')}}: {{::entry.freightValue | currency: 'EUR':3 | dashIfEmpty}}<br>
{{::$ctrl.$t('Comission')}}: {{::entry.comissionValue | currency: 'EUR':3 | dashIfEmpty}}">
{{::entry.cost | currency: 'EUR':3 | dashIfEmpty}}
</span>
</vn-td>
<vn-td number>{{::entry.weight | dashIfEmpty}}</vn-td>

View File

@ -40,11 +40,11 @@ Create: Crear
Client card: Ficha del cliente
Shipped: F. envío
stems: Tallos
Weight/Piece: Peso/tallo
Weight/Piece: Peso (gramos)/tallo
Search items by id, name or barcode: Buscar articulos por identificador, nombre o codigo de barras
SalesPerson: Comercial
Concept: Concepto
Units/Box: Unidades/Caja
Units/Box: Unidades/caja
# Sections
Items: Artículos

View File

@ -295,11 +295,26 @@ module.exports = Self => {
risk = t.debt + t.credit, totalProblems = totalProblems + 1
`);
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getWarnings');
stmt = new ParameterizedSQL(`
SELECT t.*, tp.*,
((tp.risk) + cc.riskTolerance < 0) AS hasHighRisk
CREATE TEMPORARY TABLE tmp.sale_getWarnings
(INDEX (ticketFk, agencyModeFk))
ENGINE = MEMORY
SELECT f.id ticketFk, f.agencyModeFk
FROM tmp.filter f`);
stmts.push(stmt);
stmts.push('CALL ticket_getWarnings()');
stmt = new ParameterizedSQL(`
SELECT t.*,
tp.*,
((tp.risk) + cc.riskTolerance < 0) AS hasHighRisk,
tw.*
FROM tmp.tickets t
LEFT JOIN tmp.ticket_problems tp ON tp.ticketFk = t.id
LEFT JOIN tmp.ticket_warnings tw ON tw.ticketFk = t.id
JOIN clientConfig cc`);
const hasProblems = args.problems;
@ -352,20 +367,37 @@ module.exports = Self => {
return {'t.alertLevel': value};
case 'pending':
if (value) {
return {and: [
{'t.alertLevel': 0},
{'t.alertLevelCode': {nin: [
'OK',
'BOARDING',
'PRINTED',
'PRINTED_AUTO',
'PICKER_DESIGNED'
]}}
]};
return {'t.alertLevelCode': {inq: [
'FIXING',
'FREE',
'NOT_READY',
'BLOCKED',
'EXPANDABLE',
'CHAINED',
'WAITING_FOR_PAYMENT'
]}};
} else {
return {and: [
{'t.alertLevel': {gt: 0}}
]};
return {'t.alertLevelCode': {inq: [
'ON_PREPARATION',
'ON_CHECKING',
'CHECKED',
'PACKING',
'PACKED',
'INVOICED',
'ON_DELIVERY',
'PREPARED',
'WAITING_FOR_PICKUP',
'DELIVERED',
'PRINTED_BACK',
'LAST_CALL',
'PREVIOUS_PREPARATION',
'ASSISTED_PREPARATION',
'BOARD',
'PRINTED STOWAWAY',
'OK STOWAWAY',
'HALF_PACKED',
'COOLER_PREPARATION'
]}};
}
case 'agencyModeFk':
case 'warehouseFk':
@ -387,6 +419,8 @@ module.exports = Self => {
tmp.filter,
tmp.ticket_problems,
tmp.sale_getProblems,
tmp.sale_getWarnings,
tmp.ticket_warnings,
tmp.risk`);
const sql = ParameterizedSQL.join(stmts, ';');

View File

@ -12,4 +12,5 @@ Theoretical: Teórica
Practical: Práctica
Preparation: Preparación
Auto-refresh: Auto-refresco
Toggle auto-refresh every 2 minutes: Conmuta el refresco automático cada 2 minutos
Toggle auto-refresh every 2 minutes: Conmuta el refresco automático cada 2 minutos
Is fragile: Es frágil

View File

@ -19,13 +19,13 @@
<vn-horizontal class="header">
<vn-one translate>
Tickets monitor
</vn-one>
</vn-one>
</vn-horizontal>
<vn-card>
<smart-table
model="model"
view-config-id="ticketsMonitor"
options="$ctrl.smartTableOptions"
<smart-table
model="model"
view-config-id="ticketsMonitor"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-actions>
<vn-check
@ -68,6 +68,7 @@
<th field="stateFk">
<span translate>State</span>
</th>
<th field="isFragile"></th>
<th field="zoneFk">
<span translate>Zone</span>
</th>
@ -80,7 +81,7 @@
<tbody>
<tr ng-repeat="ticket in model.data track by ticket.id"
vn-anchor="{
state: 'ticket.card.summary',
state: 'ticket.card.summary',
params: {id: ticket.id},
target: '_blank'
}">
@ -169,12 +170,20 @@
class="link">
{{ticket.refFk}}
</span>
<span
<span
ng-show="!ticket.refFk"
class="chip {{ticket.classColor}}">
{{ticket.state}}
</span>
</td>
<td number>
<vn-icon
ng-show="ticket.isFragile"
translate-attr="{title: 'Is fragile'}"
class="bright"
icon="local_bar">
</vn-icon>
</td>
<td name="zone">
<span
title="{{ticket.zoneName}}"
@ -191,8 +200,8 @@
<td actions>
<vn-icon-button
vn-anchor="{
state: 'ticket.card.sale',
params: {id: ticket.id},
state: 'ticket.card.sale',
params: {id: ticket.id},
target: '_blank'
}"
vn-tooltip="Go to lines"
@ -213,7 +222,7 @@
<vn-ticket-descriptor-popover
vn-id="ticketDescriptor">
</vn-ticket-descriptor-popover>
<vn-worker-descriptor-popover
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-client-descriptor-popover
@ -236,22 +245,22 @@
ng-click="contextmenu.filterBySelection()">
Filter by selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.excludeSelection()">
Exclude selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.removeFilter()">
Remove filter
</vn-item>
<vn-item translate
<vn-item translate
ng-click="contextmenu.removeAllFilters()">
Remove all filters
</vn-item>
<vn-item translate
ng-if="contextmenu.isActionAllowed()"
<vn-item translate
ng-if="contextmenu.isActionAllowed()"
ng-click="contextmenu.copyValue()">
Copy value
</vn-item>

View File

@ -1,6 +1,22 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('route clone()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
const createdDate = Date.vnNew();
it('should throw an error if the amount of ids pased to the clone function do no match the database', async() => {
const ids = [996, 997, 998, 999];

View File

@ -1,6 +1,20 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('route updateWorkCenter()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
const routeId = 1;
it('should set the commission work center if the worker has workCenter', async() => {

View File

@ -1,11 +1,10 @@
module.exports = Self => {
Self.remoteMethod('newSupplier', {
Self.remoteMethodCtx('newSupplier', {
description: 'Creates a new supplier and returns it',
accessType: 'WRITE',
accepts: [{
arg: 'params',
type: 'object',
http: {source: 'body'}
arg: 'name',
type: 'string'
}],
returns: {
type: 'string',
@ -17,29 +16,18 @@ module.exports = Self => {
}
});
Self.newSupplier = async params => {
Self.newSupplier = async(ctx, options) => {
const models = Self.app.models;
const args = ctx.args;
const myOptions = {};
if (typeof(params) == 'string')
params = JSON.parse(params);
if (typeof options == 'object')
Object.assign(myOptions, options);
params.nickname = params.name;
delete args.ctx;
const data = {...args, ...{nickname: args.name}};
const supplier = await models.Supplier.create(data, myOptions);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const supplier = await models.Supplier.create(params, myOptions);
if (tx) await tx.commit();
return supplier;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
return supplier;
};
};

View File

@ -1,31 +1,39 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('Supplier newSupplier()', () => {
const newSupp = {
name: 'TestSupplier-1'
};
const administrativeId = 5;
const activeCtx = {
accessToken: {userId: administrativeId},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {req: activeCtx};
it('should create a new supplier containing only the name', async() => {
pending('https://redmine.verdnatura.es/issues/5203');
const activeCtx = {
accessToken: {userId: administrativeId},
};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
let result = await app.models.Supplier.newSupplier(JSON.stringify(newSupp));
it('should create a new supplier containing only the name', async() => {
const tx = await models.Supplier.beginTransaction({});
expect(result.name).toEqual('TestSupplier-1');
expect(result.id).toEqual(443);
try {
const options = {transaction: tx};
ctx.args = {
name: 'newSupplier'
};
const createdSupplier = await app.models.Supplier.findById(result.id);
const result = await models.Supplier.newSupplier(ctx, options);
expect(createdSupplier.id).toEqual(result.id);
expect(createdSupplier.name).toEqual(result.name);
expect(createdSupplier.payDemFk).toEqual(7);
expect(createdSupplier.nickname).toEqual(result.name);
expect(result.name).toEqual('newSupplier');
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,7 +1,8 @@
<vn-watcher
vn-id="watcher"
url="suppliers/newSupplier"
url="Suppliers/newSupplier"
data="$ctrl.supplier"
params="$ctrl.supplier"
insert-mode="true"
form="form">
</vn-watcher>
@ -9,8 +10,8 @@
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
label="Supplier name"
ng-model="$ctrl.supplier.name"
label="Supplier name"
ng-model="$ctrl.supplier.name"
vn-focus>
</vn-textfield>
</vn-horizontal>

View File

@ -1,7 +1,21 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('sale canEdit()', () => {
const employeeId = 1;
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
describe('sale editTracked', () => {
it('should return true if the role is production regardless of the saleTrackings', async() => {

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('sale deleteSales()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should throw an error if the ticket of the given sales is not editable', async() => {
const tx = await models.Sale.beginTransaction({});

View File

@ -1,4 +1,5 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('sale reserve()', () => {
const ctx = {
@ -9,6 +10,20 @@ describe('sale reserve()', () => {
}
};
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should throw an error if the ticket can not be modified', async() => {
const tx = await models.Sale.beginTransaction({});

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('sale updateConcept()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
const ctx = {req: {accessToken: {userId: 9}}};
const saleId = 25;

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('sale updatePrice()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 18},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
const ctx = {
req: {
accessToken: {userId: 18},

View File

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

View File

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

View File

@ -1,7 +1,21 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('ticket addSale()', () => {
const ticketId = 13;
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should create a new sale for the ticket with id 13', async() => {
const tx = await models.Ticket.beginTransaction({});

View File

@ -10,11 +10,15 @@ describe('ticket merge()', () => {
workerFk: 1
};
const activeCtx = {
accessToken: {userId: 9},
};
beforeEach(() => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
@ -35,16 +39,16 @@ describe('ticket merge()', () => {
try {
const options = {transaction: tx};
const chatNotificationBeforeMerge = await models.Chat.find();
const chatNotificationBeforeMerge = await models.Chat.find(null, options);
await models.Ticket.merge(ctx, [tickets], options);
const createdTicketLog = await models.TicketLog.find({where: {originFk: tickets.originId}}, options);
const createdTicketLog = await models.TicketLog.find({where: {originFk: tickets.destinationId}}, options);
const deletedTicket = await models.Ticket.findOne({where: {id: tickets.originId}}, options);
const salesTicketFuture = await models.Sale.find({where: {ticketFk: tickets.destinationId}}, options);
const chatNotificationAfterMerge = await models.Chat.find();
const chatNotificationAfterMerge = await models.Chat.find(null, options);
expect(createdTicketLog.length).toEqual(2);
expect(createdTicketLog.length).toEqual(1);
expect(deletedTicket.isDeleted).toEqual(true);
expect(salesTicketFuture.length).toEqual(2);
expect(chatNotificationBeforeMerge.length).toEqual(chatNotificationAfterMerge.length - 2);

View File

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

View File

@ -1,6 +1,20 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('sale updateDiscount()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
const originalSaleId = 8;
it('should throw an error if no sales were selected', async() => {

View File

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

View File

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

View File

@ -1,4 +1,4 @@
Status log: Hitorial de estados
Status log: Historial de estados
Expedition removed: Expedición eliminada
Move: Mover
New ticket without route: Nuevo ticket sin ruta

View File

@ -28,8 +28,9 @@
</vn-button-menu>
<vn-button icon="keyboard_arrow_down"
label="More"
ng-click="moreOptions.show($event)"
ng-show="$ctrl.hasSelectedSales()">
disabled="!$ctrl.hasSelectedSales()"
vn-tooltip="Select lines to see the options"
ng-click="moreOptions.show($event)">
</vn-button>
<vn-button
disabled="!$ctrl.hasSelectedSales() || !$ctrl.isEditable"

View File

@ -40,3 +40,4 @@ Refund: Abono
Promotion mana: Maná promoción
Claim mana: Maná reclamación
History: Historial
Select lines to see the options: Seleccione lineas para ver las opciones

View File

@ -130,6 +130,7 @@ module.exports = Self => {
SUM(b.stickers) AS stickers,
s.id AS cargoSupplierFk,
s.nickname AS cargoSupplierNickname,
s.name AS supplierName,
CAST(SUM(b.weight * b.stickers) as DECIMAL(10,0)) as loadedKg,
CAST(SUM(vc.aerealVolumetricDensity * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000) as DECIMAL(10,0)) as volumeKg
FROM travel t
@ -167,6 +168,7 @@ module.exports = Self => {
SUM(b.stickers) AS stickers,
e.evaNotes,
e.notes,
e.invoiceAmount,
CAST(SUM(b.weight * b.stickers) AS DECIMAL(10,0)) as loadedkg,
CAST(SUM(vc.aerealVolumetricDensity * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000) AS DECIMAL(10,0)) as volumeKg
FROM tmp.travel tr

View File

@ -3,7 +3,7 @@
url="Travels/extraCommunityFilter"
user-params="::$ctrl.defaultFilter"
data="travels"
order="shipped ASC, landed ASC, travelFk, loadPriority, agencyModeFk, evaNotes"
order="landed ASC, shipped ASC, travelFk, loadPriority, agencyModeFk, supplierName, evaNotes"
limit="20"
auto-load="true">
</vn-crud-model>
@ -48,6 +48,9 @@
<th field="agencyModeFk">
<span translate>Agency</span>
</th>
<th field="invoiceAmount">
<span translate>Amount</span>
</th>
<th field="ref">
<span translate>Reference</span>
</th>
@ -107,6 +110,7 @@
{{::travel.cargoSupplierNickname}}
</span>
</td>
<td></td>
<td>{{::travel.agencyModeName}}</td>
<td vn-click-stop>
<vn-td-editable name="reference" expand>
@ -157,22 +161,15 @@
{{::entry.supplierName}}
</span>
</td>
<td number>{{::entry.invoiceAmount | currency: 'EUR': 2}}</td>
<td></td>
<td class="td-editable">{{::entry.ref}}</td>
<td class="td-editable">{{::entry.invoiceNumber}}</td>
<td number>{{::entry.stickers}}</td>
<td number></td>
<td number>{{::entry.loadedkg}}</td>
<td number>{{::entry.volumeKg}}</td>
<td>
<span ng-if="::entry.notes" vn-tooltip="{{::entry.notes}}">
{{::entry.notes}}
</span>
</td>
<td>
<span ng-if="::entry.evaNotes" vn-tooltip="{{::entry.evaNotes}}">
{{::entry.evaNotes}}
</span>
</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>

View File

@ -6,6 +6,6 @@ Phy. KG: KG físico
Vol. KG: KG Vol.
Search by travel id or reference: Buscar por id de travel o referencia
Search by extra community travel: Buscar por envío extra comunitario
Continent Out: Continente salida
Continent Out: Cont. salida
W. Shipped: F. envío
W. Landed: F. llegada
W. Landed: F. llegada

View File

@ -2,6 +2,15 @@
<vn-auto-search
model="model">
</vn-auto-search>
<vn-travel-search-panel
model="model">
</vn-travel-search-panel>
<vn-crud-model
vn-id="model"
url="Travels/filter"
limit="20"
order="shipped DESC, landed DESC">
</vn-crud-model>
<vn-data-viewer
model="model"
class="vn-mb-xl vn-w-xl">
@ -9,23 +18,22 @@
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="id" number filter-enabled="false">Id</vn-th>
<vn-th field="ref">Reference</vn-th>
<vn-th field="agencyModeFk">Agency</vn-th>
<vn-th field="warehouseOutFk">Warehouse Out</vn-th>
<vn-th field="shipped" center shrink-date>Shipped</vn-th>
<vn-th field="isDelivered" center>Delivered</vn-th>
<vn-th shrink></vn-th>
<vn-th field="warehouseInFk">Warehouse In</vn-th>
<vn-th field="landed" center shrink-date>Landed</vn-th>
<vn-th field="isReceived" center>Received</vn-th>
<vn-th shrink></vn-th>
<vn-th shrink field="totalEntries">Total entries</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="travel in model.data"
<a ng-repeat="travel in model.data"
class="clickable vn-tr search-result"
ui-sref="travel.card.summary({id: {{::travel.id}}})">
<vn-td number>{{::travel.id}}</vn-td>
<vn-td>{{::travel.ref}}</vn-td>
<vn-td>{{::travel.agencyModeName}}</vn-td>
<vn-td>{{::travel.warehouseOutName}}</vn-td>
@ -34,14 +42,27 @@
{{::travel.shipped | date:'dd/MM/yyyy'}}
</span>
</vn-td>
<vn-td center><vn-check ng-model="travel.isDelivered" disabled="true"></vn-check></vn-td>
<vn-td shrink>
<vn-icon
icon="flight_takeoff"
translate-attr="{title: 'Delivered'}"
ng-class="{active: travel.isDelivered}">
</vn-icon>
</vn-td>
<vn-td expand>{{::travel.warehouseInName}}</vn-td>
<vn-td center shrink-date>
<span class="chip {{$ctrl.compareDate(travel.landed)}}">
{{::travel.landed | date:'dd/MM/yyyy'}}
</span>
</vn-td>
<vn-td center><vn-check ng-model="travel.isReceived" disabled="true"></vn-check></vn-td>
<vn-td shrink>
<vn-icon
icon="flight_land"
translate-attr="{title: 'Received'}"
ng-class="{active: travel.isReceived}">
</vn-icon>
</vn-td>
<vn-td shrink>{{::travel.totalEntries}}</vn-td>
<vn-td shrink>
<vn-horizontal class="buttons">
<vn-icon-button
@ -49,7 +70,7 @@
vn-tooltip="Clone"
icon="icon-clone">
</vn-icon-button>
<vn-icon-button
<vn-icon-button
vn-anchor="::{state: 'entry.create', params: {travelFk: travel.id}}"
vn-tooltip="Add entry"
icon="icon-ticket">
@ -78,38 +99,9 @@
fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>
<vn-confirm
<vn-confirm
vn-id="clone"
on-accept="$ctrl.onCloneAccept($data)"
question="Do you want to clone this travel?"
message="All it's properties will be copied">
</vn-confirm>
<vn-contextmenu vn-id="contextmenu" targets="['vn-data-viewer']" model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-menu>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.filterBySelection()">
Filter by selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.excludeSelection()">
Exclude selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.removeFilter()">
Remove filter
</vn-item>
<vn-item translate
ng-click="contextmenu.removeAllFilters()" >
Remove all filters
</vn-item>
<vn-item translate
ng-if="contextmenu.isActionAllowed()"
ng-click="contextmenu.copyValue()">
Copy value
</vn-item>
</slot-menu>
</vn-contextmenu>

View File

@ -1,5 +1,6 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
preview(travel) {
@ -30,37 +31,6 @@ export default class Controller extends Section {
if (timeDifference == 0) return 'warning';
if (timeDifference < 0) return 'success';
}
exprBuilder(param, value) {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {'t.id': value}
: {'t.ref': {like: `%${value}%`}};
case 'ref':
return {'t.ref': {like: `%${value}%`}};
case 'shipped':
return {'t.shipped': {between: this.dateRange(value)}};
case 'landed':
return {'t.landed': {between: this.dateRange(value)}};
case 'id':
case 'agencyModeFk':
case 'warehouseOutFk':
case 'warehouseInFk':
case 'totalEntries':
param = `t.${param}`;
return {[param]: value};
}
}
dateRange(value) {
const minHour = new Date(value);
minHour.setHours(0, 0, 0, 0);
const maxHour = new Date(value);
maxHour.setHours(23, 59, 59, 59);
return [minHour, maxHour];
}
}
ngModule.vnComponent('vnTravelIndex', {

View File

@ -0,0 +1,11 @@
@import "variables";
vn-travel-index {
vn-icon {
color: $color-font-secondary
}
vn-icon.active {
color: $color-success
}
}

View File

@ -1,7 +1,7 @@
#Ordenar alfabeticamente
Reference: Referencia
Warehouse Out: Almacén salida
Warehouse In: Almacén llegada
Warehouse Out: Alm salida
Warehouse In: Alm llegada
Shipped from: Salida desde
Shipped to: Salida hasta
Landed from: Llegada desde
@ -10,12 +10,12 @@ Shipped: F. salida
Landed: F. llegada
Delivered: Enviado
Received: Recibido
Travel id: Id envío
Search travels by id: Buscar envíos por identificador
Travel id: Id
Search travels by id: Buscar envíos por identificador o referencia
New travel: Nuevo envío
travel: envío
# Sections
Travels: Envíos
Log: Historial
Thermographs: Termógrafos
Thermographs: Termógrafos

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