diff --git a/.vscode/settings.json b/.vscode/settings.json index 159cecdc9..05d23f3bb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,9 @@ "source.fixAll.eslint": true }, "search.useIgnoreFiles": false, - "editor.defaultFormatter": "dbaeumer.vscode-eslint" + "editor.defaultFormatter": "dbaeumer.vscode-eslint", + "eslint.format.enable": true, + "[javascript]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint" + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index dde790aaa..c5ee05fe4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,10 @@ 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 +## [2314.01] - 2023-04-20 ### Added -- +- (Facturas recibidas -> Bases negativas) Nueva sección ### Changed - @@ -16,6 +16,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - +## [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 + ## [2310.01] - 2023-03-23 ### Added diff --git a/back/methods/account/change-password.js b/back/methods/account/change-password.js index c0956b193..b8f9de341 100644 --- a/back/methods/account/change-password.js +++ b/back/methods/account/change-password.js @@ -2,6 +2,7 @@ module.exports = Self => { Self.remoteMethod('changePassword', { description: 'Changes the user password', + accessType: 'WRITE', accepts: [ { arg: 'id', diff --git a/back/methods/account/set-password.js b/back/methods/account/set-password.js index ab4d3b3fe..093935948 100644 --- a/back/methods/account/set-password.js +++ b/back/methods/account/set-password.js @@ -1,6 +1,7 @@ module.exports = Self => { Self.remoteMethod('setPassword', { description: 'Sets the user password', + accessType: 'WRITE', accepts: [ { arg: 'id', diff --git a/back/methods/chat/send.js b/back/methods/chat/send.js index 915120d49..79b20e307 100644 --- a/back/methods/chat/send.js +++ b/back/methods/chat/send.js @@ -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; } diff --git a/back/methods/chat/sendCheckingPresence.js b/back/methods/chat/sendCheckingPresence.js index 883a1b693..29232490a 100644 --- a/back/methods/chat/sendCheckingPresence.js +++ b/back/methods/chat/sendCheckingPresence.js @@ -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; }; }; diff --git a/back/methods/chat/sendQueued.js b/back/methods/chat/sendQueued.js index 66fbfcdc5..ef1a417ab 100644 --- a/back/methods/chat/sendQueued.js +++ b/back/methods/chat/sendQueued.js @@ -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 diff --git a/back/methods/chat/spec/sendQueued.spec.js b/back/methods/chat/spec/sendQueued.spec.js index ed791756b..67cd47f4a 100644 --- a/back/methods/chat/spec/sendQueued.spec.js +++ b/back/methods/chat/spec/sendQueued.spec.js @@ -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 }; diff --git a/back/methods/collection/spec/setSaleQuantity.spec.js b/back/methods/collection/spec/setSaleQuantity.spec.js index 63dc3bd2d..acdf2ebb5 100644 --- a/back/methods/collection/spec/setSaleQuantity.spec.js +++ b/back/methods/collection/spec/setSaleQuantity.spec.js @@ -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({}); diff --git a/back/methods/osticket/closeTicket.js b/back/methods/osticket/closeTicket.js index 32b369c8d..aa827bbbb 100644 --- a/back/methods/osticket/closeTicket.js +++ b/back/methods/osticket/closeTicket.js @@ -69,15 +69,15 @@ module.exports = Self => { const result = response.headers.get('set-cookie'); const [firtHeader] = result.split(' '); - const firtCookie = firtHeader.substring(0, firtHeader.length - 1); + const cookie = firtHeader.substring(0, firtHeader.length - 1); const body = await response.text(); const dom = new jsdom.JSDOM(body); const token = dom.window.document.querySelector('[name="__CSRFToken__"]').value; - await login(token, firtCookie); + await login(token, cookie); } - async function login(token, firtCookie) { + async function login(token, cookie) { const data = { __CSRFToken__: token, do: 'scplogin', @@ -90,21 +90,18 @@ module.exports = Self => { body: new URLSearchParams(data), headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - 'Cookie': firtCookie + 'Cookie': cookie } }; - const response = await fetch(ostUri, params); - const result = response.headers.get('set-cookie'); - const [firtHeader] = result.split(' '); - const secondCookie = firtHeader.substring(0, firtHeader.length - 1); + await fetch(ostUri, params); - await close(token, secondCookie); + await close(token, cookie); } - async function close(token, secondCookie) { + async function close(token, cookie) { for (const ticketId of ticketsId) { try { - const lock = await getLockCode(token, secondCookie, ticketId); + const lock = await getLockCode(token, cookie, ticketId); if (!lock.code) { let error = `Can't get lock code`; if (lock.msg) error += `: ${lock.msg}`; @@ -127,7 +124,7 @@ module.exports = Self => { method: 'POST', body: form, headers: { - 'Cookie': secondCookie + 'Cookie': cookie } }; await fetch(ostUri, params); @@ -139,13 +136,13 @@ module.exports = Self => { } } - async function getLockCode(token, secondCookie, ticketId) { + async function getLockCode(token, cookie, ticketId) { const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`; const params = { method: 'POST', headers: { 'X-CSRFToken': token, - 'Cookie': secondCookie + 'Cookie': cookie } }; const response = await fetch(ostUri, params); diff --git a/back/models/user.json b/back/models/user.json index 921362e0e..d992fd9db 100644 --- a/back/models/user.json +++ b/back/models/user.json @@ -4,7 +4,8 @@ "options": { "mysql": { "table": "salix.User" - } + }, + "resetPasswordTokenTTL": "604800" }, "properties": { "id": { diff --git a/db/changes/231201/.gitkeep b/db/changes/231201/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/db/changes/231201/00-chatRefactor.sql b/db/changes/231201/00-chatRefactor.sql new file mode 100644 index 000000000..66d1bf3bf --- /dev/null +++ b/db/changes/231201/00-chatRefactor.sql @@ -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; diff --git a/db/changes/231201/00-invoiceInSerial.sql b/db/changes/231201/00-invoiceInSerial.sql new file mode 100644 index 000000000..de476027c --- /dev/null +++ b/db/changes/231201/00-invoiceInSerial.sql @@ -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'); diff --git a/db/changes/231201/00-itemType_isFragile.sql b/db/changes/231201/00-itemType_isFragile.sql new file mode 100644 index 000000000..ecc1bfbb2 --- /dev/null +++ b/db/changes/231201/00-itemType_isFragile.sql @@ -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'); + diff --git a/db/changes/231201/00-supplierAccount_deleteTriggers.sql b/db/changes/231201/00-supplierAccount_deleteTriggers.sql new file mode 100644 index 000000000..d7f9f734a --- /dev/null +++ b/db/changes/231201/00-supplierAccount_deleteTriggers.sql @@ -0,0 +1,3 @@ +DROP TRIGGER `vn`.`supplierAccount_afterInsert`; +DROP TRIGGER `vn`.`supplierAccount_afterUpdate`; +DROP TRIGGER `vn`.`supplierAccount_afterDelete`; diff --git a/db/changes/231201/00-ticket_getWarnings.sql b/db/changes/231201/00-ticket_getWarnings.sql new file mode 100644 index 000000000..5253b58ab --- /dev/null +++ b/db/changes/231201/00-ticket_getWarnings.sql @@ -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 ; diff --git a/db/changes/231201/00-wagon.sql b/db/changes/231201/00-wagon.sql new file mode 100644 index 000000000..3e4d225d7 --- /dev/null +++ b/db/changes/231201/00-wagon.sql @@ -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'); diff --git a/db/changes/231401/00-negativeBases.sql b/db/changes/231401/00-negativeBases.sql new file mode 100644 index 000000000..0bdc6f2dc --- /dev/null +++ b/db/changes/231401/00-negativeBases.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('InvoiceIn', 'negativeBases', 'READ', 'ALLOW', 'ROLE', 'administrative'), + ('InvoiceIn', 'negativeBasesCsv', 'READ', 'ALLOW', 'ROLE', 'administrative'); diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 2145f8429..9006c6676 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -501,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 @@ -738,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); @@ -838,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 @@ -2487,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 @@ -2631,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`) @@ -2837,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); + + + diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 0762a79f0..32a60a4e2 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -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(5) td.after', }, ticketSummary: { header: 'vn-ticket-summary > vn-card > h5', @@ -1128,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)', @@ -1139,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}}"]', diff --git a/e2e/paths/04-item/10_item_log.spec.js b/e2e/paths/04-item/10_item_log.spec.js index 46979a761..6a7bd7ae2 100644 --- a/e2e/paths/04-item/10_item_log.spec.js +++ b/e2e/paths/04-item/10_item_log.spec.js @@ -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'); diff --git a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js index 36161ae1d..f9b520981 100644 --- a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js +++ b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js @@ -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); diff --git a/e2e/paths/05-ticket/01_observations.spec.js b/e2e/paths/05-ticket/01_observations.spec.js index 45b4ebb3e..cf37f9ff1 100644 --- a/e2e/paths/05-ticket/01_observations.spec.js +++ b/e2e/paths/05-ticket/01_observations.spec.js @@ -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'); }); diff --git a/e2e/paths/09-invoice-in/05_negative_bases.spec.js b/e2e/paths/09-invoice-in/05_negative_bases.spec.js new file mode 100644 index 000000000..4c9fe651f --- /dev/null +++ b/e2e/paths/09-invoice-in/05_negative_bases.spec.js @@ -0,0 +1,29 @@ +import getBrowser from '../../helpers/puppeteer'; + +describe('InvoiceIn negative bases path', () => { + let browser; + let page; + const httpRequests = []; + + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + page.on('request', req => { + if (req.url().includes(`InvoiceIns/negativeBases`)) + httpRequests.push(req.url()); + }); + await page.loginAndModule('administrative', 'invoiceIn'); + await page.accessToSection('invoiceIn.negative-bases'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it('should show negative bases in a date range', async() => { + const request = httpRequests.find(req => + req.includes(`from`) && req.includes(`to`)); + + expect(request).toBeDefined(); + }); +}); diff --git a/e2e/paths/09-invoice-in/05_serial.spec.js b/e2e/paths/09-invoice-in/05_serial.spec.js new file mode 100644 index 000000000..3aa94f48c --- /dev/null +++ b/e2e/paths/09-invoice-in/05_serial.spec.js @@ -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'); + }); +}); diff --git a/e2e/paths/10-travel/02_basic_data_and_log.spec.js b/e2e/paths/10-travel/02_basic_data_and_log.spec.js index 341b38f59..5abf8a65e 100644 --- a/e2e/paths/10-travel/02_basic_data_and_log.spec.js +++ b/e2e/paths/10-travel/02_basic_data_and_log.spec.js @@ -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!'); diff --git a/e2e/paths/10-travel/03_descriptor.spec.js b/e2e/paths/10-travel/03_descriptor.spec.js index 79dcad514..3752400c6 100644 --- a/e2e/paths/10-travel/03_descriptor.spec.js +++ b/e2e/paths/10-travel/03_descriptor.spec.js @@ -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(); diff --git a/e2e/paths/10-travel/05_thermograph.spec.js b/e2e/paths/10-travel/05_thermograph.spec.js index a99dc8352..c9709f2f5 100644 --- a/e2e/paths/10-travel/05_thermograph.spec.js +++ b/e2e/paths/10-travel/05_thermograph.spec.js @@ -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'); }); diff --git a/e2e/paths/10-travel/06_search_panel.spec.js b/e2e/paths/10-travel/06_search_panel.spec.js new file mode 100644 index 000000000..420ceaf48 --- /dev/null +++ b/e2e/paths/10-travel/06_search_panel.spec.js @@ -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'); + }); +}); diff --git a/front/core/locale/es.yml b/front/core/locale/es.yml index d849fcdd2..f654c61cf 100644 --- a/front/core/locale/es.yml +++ b/front/core/locale/es.yml @@ -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 \ No newline at end of file +Load more results: Cargar más resultados diff --git a/front/core/styles/variables.scss b/front/core/styles/variables.scss index bcc9fab66..c280838ca 100644 --- a/front/core/styles/variables.scss +++ b/front/core/styles/variables.scss @@ -2,6 +2,7 @@ $font-size: 11pt; $menu-width: 256px; +$right-menu-width: 318px; $topbar-height: 56px; $mobile-width: 800px; $float-spacing: 20px; diff --git a/front/salix/components/layout/style.scss b/front/salix/components/layout/style.scss index 612366228..6697bb1b0 100644 --- a/front/salix/components/layout/style.scss +++ b/front/salix/components/layout/style.scss @@ -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 { diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 95bf16d66..d6588c0b2 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -271,5 +271,6 @@ "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", - "This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado" + "This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado", + "Insert a date range": "Inserte un rango de fechas" } diff --git a/loopback/server/connectors/vn-mysql.js b/loopback/server/connectors/vn-mysql.js index 5c1ceaa32..728454d86 100644 --- a/loopback/server/connectors/vn-mysql.js +++ b/loopback/server/connectors/vn-mysql.js @@ -1,8 +1,7 @@ const mysql = require('mysql'); -const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const MySQL = require('loopback-connector-mysql').MySQL; const EnumFactory = require('loopback-connector-mysql').EnumFactory; -const Transaction = require('loopback-connector').Transaction; +const { Transaction, SQLConnector, ParameterizedSQL } = require('loopback-connector'); const fs = require('fs'); const limitSet = new Set([ @@ -254,49 +253,49 @@ class VnMySQL extends MySQL { } create(model, data, opts, cb) { - const ctx = {data}; + const ctx = { data }; this.invokeMethod('create', arguments, model, ctx, opts, cb); } createAll(model, data, opts, cb) { - const ctx = {data}; + const ctx = { data }; this.invokeMethod('createAll', arguments, model, ctx, opts, cb); } save(model, data, opts, cb) { - const ctx = {data}; + const ctx = { data }; this.invokeMethod('save', arguments, model, ctx, opts, cb); } updateOrCreate(model, data, opts, cb) { - const ctx = {data}; + const ctx = { data }; this.invokeMethod('updateOrCreate', arguments, model, ctx, opts, cb); } replaceOrCreate(model, data, opts, cb) { - const ctx = {data}; + const ctx = { data }; this.invokeMethod('replaceOrCreate', arguments, model, ctx, opts, cb); } destroyAll(model, where, opts, cb) { - const ctx = {where}; + const ctx = { where }; this.invokeMethod('destroyAll', arguments, model, ctx, opts, cb); } update(model, where, data, opts, cb) { - const ctx = {where, data}; + const ctx = { where, data }; this.invokeMethod('update', arguments, model, ctx, opts, cb); } replaceById(model, id, data, opts, cb) { - const ctx = {id, data}; + const ctx = { id, data }; this.invokeMethod('replaceById', arguments, model, ctx, opts, cb); } @@ -321,16 +320,16 @@ class VnMySQL extends MySQL { let tx; if (!opts.transaction) { tx = await Transaction.begin(this, {}); - opts = Object.assign({transaction: tx, httpCtx: opts.httpCtx}, opts); + opts = Object.assign({ transaction: tx, httpCtx: opts.httpCtx }, opts); } try { // Fetch old values (update|delete) or login let where, id, data, idName, limit, op, oldInstances, newInstances; const hasGrabUser = settings.log && settings.log.grabUser; - if(hasGrabUser){ + if (hasGrabUser) { const userId = opts.httpCtx && opts.httpCtx.active.accessToken.userId; - const user = await Model.app.models.Account.findById(userId, {fields: ['name']}, opts); + const user = await Model.app.models.Account.findById(userId, { fields: ['name'] }, opts); await this.executeP(`CALL account.myUser_loginWithName(?)`, [user.name], opts); } else { @@ -344,18 +343,18 @@ class VnMySQL extends MySQL { op = opMap.get(method); if (!where) { - if (id) where = {[idName]: id}; - else where = {[idName]: data[idName]}; + if (id) where = { [idName]: id }; + else where = { [idName]: data[idName] }; } // Fetch old values switch (op) { - case 'update': - case 'delete': - // Single entity operation - const stmt = this.buildSelectStmt(op, data, idName, model, where, limit); - stmt.merge(`FOR UPDATE`); - oldInstances = await this.executeStmt(stmt, opts); + case 'update': + case 'delete': + // Single entity operation + const stmt = this.buildSelectStmt(op, data, idName, model, where, limit); + stmt.merge(`FOR UPDATE`); + oldInstances = await this.executeStmt(stmt, opts); } } @@ -365,30 +364,30 @@ class VnMySQL extends MySQL { super[method].apply(this, fnArgs); }); - if(hasGrabUser) + if (hasGrabUser) await this.executeP(`CALL account.myUser_logout()`, null, opts); else { // Fetch new values const ids = []; switch (op) { - case 'insert': - case 'update': { + case 'insert': + case 'update': { switch (method) { - case 'createAll': - for (const row of res[1]) - ids.push(row[idName]); - break; - case 'create': - ids.push(res[1]); - break; - case 'update': - if (data[idName] != null) - ids.push(data[idName]); - break; + case 'createAll': + for (const row of res[1]) + ids.push(row[idName]); + break; + case 'create': + ids.push(res[1]); + break; + case 'update': + if (data[idName] != null) + ids.push(data[idName]); + break; } - const newWhere = ids.length ? {[idName]: ids} : where; + const newWhere = ids.length ? { [idName]: ids } : where; const stmt = this.buildSelectStmt(op, data, idName, model, newWhere, limit); newInstances = await this.executeStmt(stmt, opts); @@ -431,9 +430,9 @@ class VnMySQL extends MySQL { const stmt = new ParameterizedSQL( 'SELECT ' + - this.buildColumnNames(model, {fields}) + - ' FROM ' + - this.tableEscaped(model) + this.buildColumnNames(model, { fields }) + + ' FROM ' + + this.tableEscaped(model) ); stmt.merge(this.buildWhere(model, where)); if (limit) stmt.merge(`LIMIT 1`); @@ -505,8 +504,8 @@ class VnMySQL extends MySQL { if (oldI) { Object.keys(oldI).forEach(prop => { const hasChanges = oldI[prop] instanceof Date ? - oldI[prop]?.getTime() != newI[prop]?.getTime() : - oldI[prop] != newI[prop]; + oldI[prop]?.getTime() != newI[prop]?.getTime() : + oldI[prop] != newI[prop]; if (!hasChanges) { delete oldI[prop]; @@ -537,13 +536,13 @@ exports.initialize = function initialize(dataSource, callback) { modelBuilder.defineValueType.bind(modelBuilder) : modelBuilder.constructor.registerType.bind(modelBuilder.constructor); - defineType(function Point() {}); + defineType(function Point() { }); dataSource.EnumFactory = EnumFactory; if (callback) { if (dataSource.settings.lazyConnect) { - process.nextTick(function() { + process.nextTick(function () { callback(); }); } else @@ -551,13 +550,13 @@ exports.initialize = function initialize(dataSource, callback) { } }; -MySQL.prototype.connect = function(callback) { +MySQL.prototype.connect = function (callback) { const self = this; const options = generateOptions(this.settings); if (this.client) { if (callback) { - process.nextTick(function() { + process.nextTick(function () { callback(null, self.client); }); } @@ -566,7 +565,7 @@ MySQL.prototype.connect = function(callback) { function connectionHandler(options, callback) { const client = mysql.createPool(options); - client.getConnection(function(err, connection) { + client.getConnection(function (err, connection) { const conn = connection; if (!err) { if (self.debug) @@ -645,3 +644,31 @@ function generateOptions(settings) { } return options; } + + +SQLConnector.prototype.all = function find(model, filter, options, cb) { + const self = this; + // Order by id if no order is specified + filter = filter || {}; + const stmt = this.buildSelect(model, filter, options); + this.execute(stmt.sql, stmt.params, options, function (err, data) { + if (err) { + return cb(err, []); + } + + try { + const objs = data.map(function (obj) { + return self.fromRow(model, obj); + }); + if (filter && filter.include) { + self.getModelDefinition(model).model.include( + objs, filter.include, options, cb, + ); + } else { + cb(null, objs); + } + } catch (error) { + cb(error, []) + } + }); +}; \ No newline at end of file diff --git a/modules/claim/back/methods/claim-state/isEditable.js b/modules/claim/back/methods/claim-state/isEditable.js index 2d0a8dc44..ad51d543a 100644 --- a/modules/claim/back/methods/claim-state/isEditable.js +++ b/modules/claim/back/methods/claim-state/isEditable.js @@ -26,13 +26,13 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); - - const state = await models.ClaimState.findById(id, { - include: { - relation: 'writeRole' - } - }, myOptions); - const roleWithGrants = state && state.writeRole().name; - return await models.Account.hasRole(userId, roleWithGrants, myOptions); + + const state = await models.ClaimState.findById(id, { + include: { + relation: 'writeRole' + } + }, myOptions); + const roleWithGrants = state && state.writeRole().name; + return await models.Account.hasRole(userId, roleWithGrants, myOptions); }; }; diff --git a/modules/claim/back/methods/claim/logs.js b/modules/claim/back/methods/claim/logs.js index c7e69680b..f47513e9e 100644 --- a/modules/claim/back/methods/claim/logs.js +++ b/modules/claim/back/methods/claim/logs.js @@ -1,7 +1,7 @@ const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const buildFilter = require('vn-loopback/util/filter').buildFilter; -const {mergeFilters, mergeWhere} = require('vn-loopback/util/filter'); +const { mergeFilters, mergeWhere } = require('vn-loopback/util/filter'); module.exports = Self => { Self.remoteMethodCtx('logs', { @@ -12,27 +12,27 @@ module.exports = Self => { arg: 'id', type: 'Number', description: 'The claim id', - http: {source: 'path'} + http: { source: 'path' } }, { arg: 'filter', type: 'object', - http: {source: 'query'} + http: { source: 'query' } }, { arg: 'search', type: 'string', - http: {source: 'query'} + http: { source: 'query' } }, { arg: 'userFk', type: 'number', - http: {source: 'query'} + http: { source: 'query' } }, { arg: 'created', type: 'date', - http: {source: 'query'} + http: { source: 'query' } }, ], returns: { @@ -45,7 +45,7 @@ module.exports = Self => { } }); - Self.logs = async(ctx, id, filter, options) => { + Self.logs = async (ctx, id, filter, options) => { const conn = Self.dataSource.connector; const args = ctx.args; const myOptions = {}; @@ -56,25 +56,25 @@ module.exports = Self => { let where = buildFilter(args, (param, value) => { switch (param) { - case 'search': - return { - or: [ - {changedModel: {like: `%${value}%`}}, - {oldInstance: {like: `%${value}%`}} - ] - }; - case 'userFk': - return {'cl.userFk': value}; - case 'created': - value.setHours(0, 0, 0, 0); - to = new Date(value); - to.setHours(23, 59, 59, 999); + case 'search': + return { + or: [ + { changedModel: { like: `%${value}%` } }, + { oldInstance: { like: `%${value}%` } } + ] + }; + case 'userFk': + return { 'cl.userFk': value }; + case 'created': + value.setHours(0, 0, 0, 0); + to = new Date(value); + to.setHours(23, 59, 59, 999); - return {creationDate: {between: [value, to]}}; + return { creationDate: { between: [value, to] } }; } }); - where = mergeWhere(where, {['cl.originFk']: id}); - filter = mergeFilters(args.filter, {where}); + where = mergeWhere(where, { ['cl.originFk']: id }); + filter = mergeFilters(args.filter, { where }); const stmts = []; @@ -102,8 +102,8 @@ module.exports = Self => { const logs = []; for (const row of result) { const changes = []; - const oldInstance = JSON.parse(row.oldInstance); - const newInstance = JSON.parse(row.newInstance); + const oldInstance = JSON.parse(row.oldInstance) || {}; + const newInstance = JSON.parse(row.newInstance) || {}; const mergedProperties = [...Object.keys(oldInstance), ...Object.keys(newInstance)]; const properties = new Set(mergedProperties); for (const property of properties) { diff --git a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js index bf26d2255..276843c32 100644 --- a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js +++ b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js @@ -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({}); diff --git a/modules/claim/front/detail/index.js b/modules/claim/front/detail/index.js index 833519579..56f39e074 100644 --- a/modules/claim/front/detail/index.js +++ b/modules/claim/front/detail/index.js @@ -151,7 +151,7 @@ class Controller extends Section { isClaimEditable() { if (!this.claim) return; - this.$http.get(`ClaimStates/${this.claim.id}/isEditable`).then(res => { + this.$http.get(`ClaimStates/${this.claim.claimStateFk}/isEditable`).then(res => { this.isRewritable = res.data; }); } diff --git a/modules/claim/front/detail/index.spec.js b/modules/claim/front/detail/index.spec.js index 8f3049339..1ef779fd7 100644 --- a/modules/claim/front/detail/index.spec.js +++ b/modules/claim/front/detail/index.spec.js @@ -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: {}}]; diff --git a/modules/client/front/locale/es.yml b/modules/client/front/locale/es.yml index de4b91e0b..adbca8dbf 100644 --- a/modules/client/front/locale/es.yml +++ b/modules/client/front/locale/es.yml @@ -63,4 +63,4 @@ Consumption: Consumo Compensation Account: Cuenta para compensar Amount to return: Cantidad a devolver Delivered amount: Cantidad entregada -Unpaid: Impagado \ No newline at end of file +Unpaid: Impagado diff --git a/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js b/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js index 86ed9dddc..99d2df67b 100644 --- a/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js +++ b/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js @@ -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}; diff --git a/modules/invoiceIn/back/methods/invoice-in/getSerial.js b/modules/invoiceIn/back/methods/invoice-in/getSerial.js new file mode 100644 index 000000000..dcc1fbc3c --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/getSerial.js @@ -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; + }; +}; diff --git a/modules/invoiceIn/back/methods/invoice-in/negativeBases.js b/modules/invoiceIn/back/methods/invoice-in/negativeBases.js new file mode 100644 index 000000000..4d5975fab --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/negativeBases.js @@ -0,0 +1,112 @@ +const UserError = require('vn-loopback/util/user-error'); +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; + +module.exports = Self => { + Self.remoteMethodCtx('negativeBases', { + description: 'Find all negative bases', + accessType: 'READ', + accepts: [ + { + arg: 'from', + type: 'date', + description: 'From date' + }, + { + arg: 'to', + type: 'date', + description: 'To date' + }, + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string' + }, + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/negativeBases`, + verb: 'GET' + } + }); + + Self.negativeBases = async(ctx, options) => { + const conn = Self.dataSource.connector; + const args = ctx.args; + + if (!args.from || !args.to) + throw new UserError(`Insert a date range`); + + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const stmts = []; + let stmt; + stmts.push(`DROP TEMPORARY TABLE IF EXISTS tmp.ticket`); + + stmts.push(new ParameterizedSQL( + `CREATE TEMPORARY TABLE tmp.ticket + (KEY (ticketFk)) + ENGINE = MEMORY + SELECT id ticketFk + FROM ticket t + WHERE shipped BETWEEN ? AND ? + AND refFk IS NULL`, [args.from, args.to])); + stmts.push(`CALL vn.ticket_getTax(NULL)`); + stmts.push(`DROP TEMPORARY TABLE IF EXISTS tmp.filter`); + stmts.push(new ParameterizedSQL( + `CREATE TEMPORARY TABLE tmp.filter + ENGINE = MEMORY + SELECT + co.code company, + cou.country, + c.id clientId, + c.socialName clientSocialName, + SUM(s.quantity * s.price * ( 100 - s.discount ) / 100) amount, + negativeBase.taxableBase, + negativeBase.ticketFk, + c.isActive, + c.hasToInvoice, + c.isTaxDataChecked, + w.id comercialId, + CONCAT(w.firstName, ' ', w.lastName) comercialName + FROM vn.ticket t + JOIN vn.company co ON co.id = t.companyFk + JOIN vn.sale s ON s.ticketFk = t.id + JOIN vn.client c ON c.id = t.clientFk + JOIN vn.country cou ON cou.id = c.countryFk + LEFT JOIN vn.worker w ON w.id = c.salesPersonFk + LEFT JOIN ( + SELECT ticketFk, taxableBase + FROM tmp.ticketAmount + GROUP BY ticketFk + HAVING taxableBase < 0 + ) negativeBase ON negativeBase.ticketFk = t.id + WHERE t.shipped BETWEEN ? AND ? + AND t.refFk IS NULL + AND c.typeFk IN ('normal','trust') + GROUP BY t.clientFk, negativeBase.taxableBase + HAVING amount <> 0`, [args.from, args.to])); + + stmt = new ParameterizedSQL(` + SELECT f.* + FROM tmp.filter f`); + + stmt.merge(conn.makeWhere(args.filter.where)); + stmt.merge(conn.makeOrderBy(args.filter.order)); + + const negativeBasesIndex = stmts.push(stmt) - 1; + + stmts.push(`DROP TEMPORARY TABLE tmp.filter, tmp.ticket, tmp.ticketTax, tmp.ticketAmount`); + + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); + + return negativeBasesIndex === 0 ? result : result[negativeBasesIndex]; + }; +}; + diff --git a/modules/invoiceIn/back/methods/invoice-in/negativeBasesCsv.js b/modules/invoiceIn/back/methods/invoice-in/negativeBasesCsv.js new file mode 100644 index 000000000..963151b7d --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/negativeBasesCsv.js @@ -0,0 +1,53 @@ +const {toCSV} = require('vn-loopback/util/csv'); + +module.exports = Self => { + Self.remoteMethodCtx('negativeBasesCsv', { + description: 'Returns the negative bases as .csv', + accessType: 'READ', + accepts: [{ + arg: 'negativeBases', + type: ['object'], + required: true + }, + { + arg: 'from', + type: 'date', + description: 'From date' + }, + { + arg: 'to', + type: 'date', + description: 'To date' + }], + returns: [ + { + arg: 'body', + type: 'file', + root: true + }, { + arg: 'Content-Type', + type: 'String', + http: {target: 'header'} + }, { + arg: 'Content-Disposition', + type: 'String', + http: {target: 'header'} + } + ], + http: { + path: '/negativeBasesCsv', + verb: 'GET' + } + }); + + Self.negativeBasesCsv = async ctx => { + const args = ctx.args; + const content = toCSV(args.negativeBases); + + return [ + content, + 'text/csv', + `attachment; filename="negative-bases-${new Date(args.from).toLocaleDateString()}-${new Date(args.to).toLocaleDateString()}.csv"` + ]; + }; +}; diff --git a/modules/invoiceIn/back/methods/invoice-in/specs/getSerial.spec.js b/modules/invoiceIn/back/methods/invoice-in/specs/getSerial.spec.js new file mode 100644 index 000000000..6224ce9ac --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/specs/getSerial.spec.js @@ -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); + }); +}); diff --git a/modules/invoiceIn/back/methods/invoice-in/specs/negativeBases.spec.js b/modules/invoiceIn/back/methods/invoice-in/specs/negativeBases.spec.js new file mode 100644 index 000000000..a5c6e3102 --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/specs/negativeBases.spec.js @@ -0,0 +1,47 @@ +const models = require('vn-loopback/server/server').models; + +describe('invoiceIn negativeBases()', () => { + it('should return all negative bases in a date range', async() => { + const tx = await models.InvoiceIn.beginTransaction({}); + const options = {transaction: tx}; + const ctx = { + args: { + from: new Date().setMonth(new Date().getMonth() - 12), + to: new Date(), + filter: {} + } + }; + + try { + const result = await models.InvoiceIn.negativeBases(ctx, options); + + expect(result.length).toBeGreaterThan(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should throw an error if a date range is not in args', async() => { + let error; + const tx = await models.InvoiceIn.beginTransaction({}); + const options = {transaction: tx}; + const ctx = { + args: { + filter: {} + } + }; + + try { + await models.InvoiceIn.negativeBases(ctx, options); + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`Insert a date range`); + }); +}); diff --git a/modules/invoiceIn/back/models/invoice-in-config.json b/modules/invoiceIn/back/models/invoice-in-config.json index 5cf0ed64c..c0236e654 100644 --- a/modules/invoiceIn/back/models/invoice-in-config.json +++ b/modules/invoiceIn/back/models/invoice-in-config.json @@ -17,6 +17,9 @@ }, "retentionName": { "type": "string" + }, + "daysAgo": { + "type": "number" } }, "relations": { diff --git a/modules/invoiceIn/back/models/invoice-in.js b/modules/invoiceIn/back/models/invoice-in.js index 95ccc7b20..167f2ac34 100644 --- a/modules/invoiceIn/back/models/invoice-in.js +++ b/modules/invoiceIn/back/models/invoice-in.js @@ -6,4 +6,7 @@ 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); + require('../methods/invoice-in/negativeBases')(Self); + require('../methods/invoice-in/negativeBasesCsv')(Self); }; diff --git a/modules/invoiceIn/front/index.js b/modules/invoiceIn/front/index.js index 7b6d6a77c..c0374e996 100644 --- a/modules/invoiceIn/front/index.js +++ b/modules/invoiceIn/front/index.js @@ -13,3 +13,6 @@ import './dueDay'; import './intrastat'; import './create'; import './log'; +import './serial'; +import './serial-search-panel'; +import './negative-bases'; diff --git a/modules/invoiceIn/front/locale/es.yml b/modules/invoiceIn/front/locale/es.yml index 35b43f9f6..017e89dd4 100644 --- a/modules/invoiceIn/front/locale/es.yml +++ b/modules/invoiceIn/front/locale/es.yml @@ -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,5 @@ 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 +Negative bases: Bases negativas diff --git a/modules/invoiceIn/front/negative-bases/index.html b/modules/invoiceIn/front/negative-bases/index.html new file mode 100644 index 000000000..368f44461 --- /dev/null +++ b/modules/invoiceIn/front/negative-bases/index.html @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Company + + Country + + Id Client + + Client + + Amount + + Base + + Id Ticket + + Active + + Has To Invoice + + Verified data + + Comercial +
{{client.company | dashIfEmpty}}{{client.country | dashIfEmpty}} + + {{::client.clientId | dashIfEmpty}} + + {{client.clientSocialName | dashIfEmpty}}{{client.amount | currency: 'EUR':2 | dashIfEmpty}}{{client.taxableBase | dashIfEmpty}} + + {{::client.ticketFk | dashIfEmpty}} + + + + + + + + + + + + + {{::client.comercialName | dashIfEmpty}} + +
+
+
+
+ + + + + + diff --git a/modules/invoiceIn/front/negative-bases/index.js b/modules/invoiceIn/front/negative-bases/index.js new file mode 100644 index 000000000..0f6f04692 --- /dev/null +++ b/modules/invoiceIn/front/negative-bases/index.js @@ -0,0 +1,84 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; +import './style.scss'; + +export default class Controller extends Section { + constructor($element, $, vnReport) { + super($element, $); + + this.vnReport = vnReport; + const now = new Date(); + const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); + const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0); + this.params = { + from: firstDayOfMonth, + to: lastDayOfMonth + }; + this.$checkAll = false; + + this.smartTableOptions = { + activeButtons: { + search: true, + }, columns: [ + { + field: 'isActive', + searchable: false + }, + { + field: 'hasToInvoice', + searchable: false + }, + { + field: 'isTaxDataChecked', + searchable: false + }, + ] + }; + } + + exprBuilder(param, value) { + switch (param) { + case 'company': + return {'company': value}; + case 'country': + return {'country': value}; + case 'clientId': + return {'clientId': value}; + case 'clientSocialName': + return {'clientSocialName': value}; + case 'amount': + return {'amount': value}; + case 'taxableBase': + return {'taxableBase': value}; + case 'ticketFk': + return {'ticketFk': value}; + case 'comercialName': + return {'comercialName': value}; + } + } + + downloadCSV() { + const data = []; + this.$.model._orgData.forEach(element => { + data.push(Object.keys(element).map(key => { + return {newName: this.$t(key), value: element[key]}; + }).filter(item => item !== null) + .reduce((result, item) => { + result[item.newName] = item.value; + return result; + }, {})); + }); + this.vnReport.show('InvoiceIns/negativeBasesCsv', { + negativeBases: data, + from: this.params.from, + to: this.params.to + }); + } +} + +Controller.$inject = ['$element', '$scope', 'vnReport']; + +ngModule.vnComponent('vnNegativeBases', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/invoiceIn/front/negative-bases/locale/es.yml b/modules/invoiceIn/front/negative-bases/locale/es.yml new file mode 100644 index 000000000..9095eee22 --- /dev/null +++ b/modules/invoiceIn/front/negative-bases/locale/es.yml @@ -0,0 +1,14 @@ +Has To Invoice: Facturar +Download as CSV: Descargar como CSV +company: Compañía +country: País +clientId: Id Cliente +clientSocialName: Cliente +amount: Importe +taxableBase: Base +ticketFk: Id Ticket +isActive: Activo +hasToInvoice: Facturar +isTaxDataChecked: Datos comprobados +comercialId: Id Comercial +comercialName: Comercial diff --git a/modules/invoiceIn/front/negative-bases/style.scss b/modules/invoiceIn/front/negative-bases/style.scss new file mode 100644 index 000000000..2d628cb94 --- /dev/null +++ b/modules/invoiceIn/front/negative-bases/style.scss @@ -0,0 +1,10 @@ +@import "./variables"; + +vn-negative-bases { + vn-date-picker{ + padding-right: 5%; + } + slot-actions{ + align-items: center; + } +} diff --git a/modules/invoiceIn/front/routes.json b/modules/invoiceIn/front/routes.json index 4867b7db9..40d061d1b 100644 --- a/modules/invoiceIn/front/routes.json +++ b/modules/invoiceIn/front/routes.json @@ -9,10 +9,9 @@ ], "menus": { "main": [ - { - "state": "invoiceIn.index", - "icon": "icon-invoice-in" - } + { "state": "invoiceIn.index", "icon": "icon-invoice-in"}, + { "state": "invoiceIn.serial", "icon": "icon-invoice-in"}, + { "state": "invoiceIn.negative-bases", "icon": "icon-ticket"} ], "card": [ { @@ -54,6 +53,24 @@ "administrative" ] }, + { + "url": "/negative-bases", + "state": "invoiceIn.negative-bases", + "component": "vn-negative-bases", + "description": "Negative bases", + "acl": [ + "administrative" + ] + }, + { + "url": "/serial", + "state": "invoiceIn.serial", + "component": "vn-invoice-in-serial", + "description": "InvoiceIn Serial", + "acl": [ + "administrative" + ] + }, { "url": "/:id", "state": "invoiceIn.card", @@ -133,4 +150,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/modules/invoiceIn/front/serial-search-panel/index.html b/modules/invoiceIn/front/serial-search-panel/index.html new file mode 100644 index 000000000..0dda54852 --- /dev/null +++ b/modules/invoiceIn/front/serial-search-panel/index.html @@ -0,0 +1,27 @@ + + + + + + + + + +
+ + {{$ctrl.$t('Serial')}}: {{$ctrl.filter.serial}} + +
+
diff --git a/modules/invoiceIn/front/serial-search-panel/index.js b/modules/invoiceIn/front/serial-search-panel/index.js new file mode 100644 index 000000000..b11911ee3 --- /dev/null +++ b/modules/invoiceIn/front/serial-search-panel/index.js @@ -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: '<' + } +}); diff --git a/modules/invoiceIn/front/serial-search-panel/index.spec.js b/modules/invoiceIn/front/serial-search-panel/index.spec.js new file mode 100644 index 000000000..b5228e126 --- /dev/null +++ b/modules/invoiceIn/front/serial-search-panel/index.spec.js @@ -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(''); + 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); + }); + }); + }); +}); diff --git a/modules/invoiceIn/front/serial-search-panel/style.scss b/modules/invoiceIn/front/serial-search-panel/style.scss new file mode 100644 index 000000000..4abfcbfa2 --- /dev/null +++ b/modules/invoiceIn/front/serial-search-panel/style.scss @@ -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; + } +} diff --git a/modules/invoiceIn/front/serial/index.html b/modules/invoiceIn/front/serial/index.html new file mode 100644 index 000000000..1649ec7d7 --- /dev/null +++ b/modules/invoiceIn/front/serial/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + Serial + Pending + Total + + + + + + {{::invoiceIn.serial}} + {{::invoiceIn.pending}} + {{::invoiceIn.total}} + + + + + + + + + diff --git a/modules/invoiceIn/front/serial/index.js b/modules/invoiceIn/front/serial/index.js new file mode 100644 index 000000000..193a57492 --- /dev/null +++ b/modules/invoiceIn/front/serial/index.js @@ -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 +}); diff --git a/modules/invoiceIn/front/serial/locale/es.yml b/modules/invoiceIn/front/serial/locale/es.yml new file mode 100644 index 000000000..92a49cc82 --- /dev/null +++ b/modules/invoiceIn/front/serial/locale/es.yml @@ -0,0 +1,3 @@ +Serial: Serie +Pending: Pendientes +Go to InvoiceIn: Ir al listado de facturas recibidas diff --git a/modules/item/back/methods/fixed-price/specs/upsertFixedPrice.spec.js b/modules/item/back/methods/fixed-price/specs/upsertFixedPrice.spec.js index 5a47de6bf..86f73122d 100644 --- a/modules/item/back/methods/fixed-price/specs/upsertFixedPrice.spec.js +++ b/modules/item/back/methods/fixed-price/specs/upsertFixedPrice.spec.js @@ -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() => { diff --git a/modules/item/back/methods/item-image-queue/download.js b/modules/item/back/methods/item-image-queue/download.js index cdc0fe049..5f1b460fc 100644 --- a/modules/item/back/methods/item-image-queue/download.js +++ b/modules/item/back/methods/item-image-queue/download.js @@ -1,7 +1,7 @@ const axios = require('axios'); const uuid = require('uuid'); const fs = require('fs/promises'); -const { createWriteStream } = require('fs'); +const {createWriteStream} = require('fs'); const path = require('path'); const gm = require('gm'); @@ -15,7 +15,7 @@ module.exports = Self => { }, }); - Self.download = async () => { + Self.download = async() => { const models = Self.app.models; const tempContainer = await models.TempContainer.container( 'salix-image' @@ -32,13 +32,13 @@ module.exports = Self => { let tempFilePath; let queueRow; try { - const myOptions = { transaction: tx }; + const myOptions = {transaction: tx}; queueRow = await Self.findOne( { fields: ['id', 'itemFk', 'url', 'attempts'], where: { - url: { neq: null }, + url: {neq: null}, attempts: { lt: maxAttempts, }, @@ -59,7 +59,7 @@ module.exports = Self => { 'model', 'property', ], - where: { name: collectionName }, + where: {name: collectionName}, include: { relation: 'sizes', scope: { @@ -116,16 +116,16 @@ module.exports = Self => { const collectionDir = path.join(rootPath, collectionName); // To max size - const { maxWidth, maxHeight } = collection; + const {maxWidth, maxHeight} = collection; const fullSizePath = path.join(collectionDir, 'full'); const toFullSizePath = `${fullSizePath}/${fileName}`; - await fs.mkdir(fullSizePath, { recursive: true }); + await fs.mkdir(fullSizePath, {recursive: true}); await new Promise((resolve, reject) => { gm(tempFilePath) .resize(maxWidth, maxHeight, '>') .setFormat('png') - .write(toFullSizePath, function (err) { + .write(toFullSizePath, function(err) { if (err) reject(err); if (!err) resolve(); }); @@ -133,12 +133,12 @@ module.exports = Self => { // To collection sizes for (const size of collection.sizes()) { - const { width, height } = size; + const {width, height} = size; const sizePath = path.join(collectionDir, `${width}x${height}`); const toSizePath = `${sizePath}/${fileName}`; - await fs.mkdir(sizePath, { recursive: true }); + await fs.mkdir(sizePath, {recursive: true}); await new Promise((resolve, reject) => { const gmInstance = gm(tempFilePath); @@ -153,7 +153,7 @@ module.exports = Self => { gmInstance .setFormat('png') - .write(toSizePath, function (err) { + .write(toSizePath, function(err) { if (err) reject(err); if (!err) resolve(); }); diff --git a/modules/item/back/methods/item-image-queue/downloadImages.js b/modules/item/back/methods/item-image-queue/downloadImages.js deleted file mode 100644 index 7f53df95a..000000000 --- a/modules/item/back/methods/item-image-queue/downloadImages.js +++ /dev/null @@ -1,105 +0,0 @@ -const https = require('https'); -const fs = require('fs-extra'); -const path = require('path'); -const uuid = require('uuid'); - -module.exports = Self => { - Self.remoteMethod('downloadImages', { - description: 'Returns last entries', - accessType: 'WRITE', - returns: { - type: ['Object'], - root: true - }, - http: { - path: `/downloadImages`, - verb: 'POST' - } - }); - - Self.downloadImages = async() => { - const models = Self.app.models; - const container = await models.TempContainer.container('salix-image'); - const tempPath = path.join(container.client.root, container.name); - const maxAttempts = 3; - - const images = await Self.find({ - where: {attempts: {eq: maxAttempts}} - }); - - for (let image of images) { - const currentStamp = Date.vnNew().getTime(); - const updatedStamp = image.updated.getTime(); - const graceTime = Math.abs(currentStamp - updatedStamp); - const maxTTL = 3600 * 48 * 1000; // 48 hours in ms; - - if (graceTime >= maxTTL) - await Self.destroyById(image.itemFk); - } - - download(); - - async function download() { - const image = await Self.findOne({ - where: {url: {neq: null}, attempts: {lt: maxAttempts}}, - order: 'priority, attempts, updated' - }); - - if (!image) return; - - const fileName = `${uuid.v4()}.png`; - const filePath = path.join(tempPath, fileName); - const imageUrl = image.url.replace('http://', 'https://'); - - https.get(imageUrl, async response => { - if (response.statusCode != 200) { - const error = new Error(`Could not download the image. Status code ${response.statusCode}`); - - return await errorHandler(image.itemFk, error, filePath); - } - - const writeStream = fs.createWriteStream(filePath); - writeStream.on('open', () => response.pipe(writeStream)); - writeStream.on('error', async error => - await errorHandler(image.itemFk, error, filePath)); - writeStream.on('finish', () => writeStream.end()); - - writeStream.on('close', async function() { - try { - await models.Image.registerImage('catalog', filePath, fileName, image.itemFk); - await image.destroy(); - - download(); - } catch (error) { - await errorHandler(image.itemFk, error, filePath); - } - }); - }).on('error', async error => { - await errorHandler(image.itemFk, error, filePath); - }); - } - - async function errorHandler(rowId, error, filePath) { - try { - const row = await Self.findById(rowId); - - if (!row) return; - - if (row.attempts < maxAttempts) { - await row.updateAttributes({ - error: error, - attempts: row.attempts + 1, - updated: Date.vnNew() - }); - } - - if (filePath && fs.existsSync(filePath)) - await fs.unlink(filePath); - - download(); - } catch (err) { - throw new Error(`Image download failed: ${err}`); - } - } - }; -}; diff --git a/modules/item/back/methods/item/specs/clone.spec.js b/modules/item/back/methods/item/specs/clone.spec.js index 1f4a87ac1..01210677e 100644 --- a/modules/item/back/methods/item/specs/clone.spec.js +++ b/modules/item/back/methods/item/specs/clone.spec.js @@ -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 diff --git a/modules/item/back/methods/item/specs/new.spec.js b/modules/item/back/methods/item/specs/new.spec.js index e34ab2cf5..a1c741649 100644 --- a/modules/item/back/methods/item/specs/new.spec.js +++ b/modules/item/back/methods/item/specs/new.spec.js @@ -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}; diff --git a/modules/item/back/methods/item/specs/regularize.spec.js b/modules/item/back/methods/item/specs/regularize.spec.js index ea0cdfa5a..e7df9a003 100644 --- a/modules/item/back/methods/item/specs/regularize.spec.js +++ b/modules/item/back/methods/item/specs/regularize.spec.js @@ -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}; diff --git a/modules/item/front/locale/es.yml b/modules/item/front/locale/es.yml index 0fc014742..37f774e4e 100644 --- a/modules/item/front/locale/es.yml +++ b/modules/item/front/locale/es.yml @@ -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 diff --git a/modules/monitor/back/methods/sales-monitor/salesFilter.js b/modules/monitor/back/methods/sales-monitor/salesFilter.js index 881fc637a..8f7b336ab 100644 --- a/modules/monitor/back/methods/sales-monitor/salesFilter.js +++ b/modules/monitor/back/methods/sales-monitor/salesFilter.js @@ -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, ';'); diff --git a/modules/monitor/front/index/locale/es.yml b/modules/monitor/front/index/locale/es.yml index 126528caa..f114a2259 100644 --- a/modules/monitor/front/index/locale/es.yml +++ b/modules/monitor/front/index/locale/es.yml @@ -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 \ No newline at end of file +Toggle auto-refresh every 2 minutes: Conmuta el refresco automático cada 2 minutos +Is fragile: Es frágil diff --git a/modules/monitor/front/index/tickets/index.html b/modules/monitor/front/index/tickets/index.html index b8559154e..539d4b3cb 100644 --- a/modules/monitor/front/index/tickets/index.html +++ b/modules/monitor/front/index/tickets/index.html @@ -19,13 +19,13 @@ Tickets monitor - + - State + Zone @@ -80,7 +81,7 @@ @@ -169,12 +170,20 @@ class="link"> {{ticket.refFk}} - {{ticket.state}} + + + + - Filter by selection - Exclude selection - Remove filter - Remove all filters - Copy value diff --git a/modules/route/back/methods/route/specs/clone.spec.js b/modules/route/back/methods/route/specs/clone.spec.js index 9192854f8..496ae1c89 100644 --- a/modules/route/back/methods/route/specs/clone.spec.js +++ b/modules/route/back/methods/route/specs/clone.spec.js @@ -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]; diff --git a/modules/route/back/methods/route/specs/updateWorkCenter.spec.js b/modules/route/back/methods/route/specs/updateWorkCenter.spec.js index 5328dc240..baa63f226 100644 --- a/modules/route/back/methods/route/specs/updateWorkCenter.spec.js +++ b/modules/route/back/methods/route/specs/updateWorkCenter.spec.js @@ -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() => { diff --git a/modules/ticket/back/methods/sale/specs/canEdit.spec.js b/modules/ticket/back/methods/sale/specs/canEdit.spec.js index a6c299321..62f98421a 100644 --- a/modules/ticket/back/methods/sale/specs/canEdit.spec.js +++ b/modules/ticket/back/methods/sale/specs/canEdit.spec.js @@ -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() => { diff --git a/modules/ticket/back/methods/sale/specs/deleteSales.spec.js b/modules/ticket/back/methods/sale/specs/deleteSales.spec.js index 82cf916b3..3d3e06e22 100644 --- a/modules/ticket/back/methods/sale/specs/deleteSales.spec.js +++ b/modules/ticket/back/methods/sale/specs/deleteSales.spec.js @@ -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({}); diff --git a/modules/ticket/back/methods/sale/specs/reserve.spec.js b/modules/ticket/back/methods/sale/specs/reserve.spec.js index 7ab79f9c0..259cb8cd5 100644 --- a/modules/ticket/back/methods/sale/specs/reserve.spec.js +++ b/modules/ticket/back/methods/sale/specs/reserve.spec.js @@ -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({}); diff --git a/modules/ticket/back/methods/sale/specs/updateConcept.spec.js b/modules/ticket/back/methods/sale/specs/updateConcept.spec.js index 0e7e9bf0f..1b42e7140 100644 --- a/modules/ticket/back/methods/sale/specs/updateConcept.spec.js +++ b/modules/ticket/back/methods/sale/specs/updateConcept.spec.js @@ -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; diff --git a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js index 51cd2403f..133be8de3 100644 --- a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js +++ b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js @@ -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}, diff --git a/modules/ticket/back/methods/sale/specs/updateQuantity.spec.js b/modules/ticket/back/methods/sale/specs/updateQuantity.spec.js index 53a05cd7e..4778f6b6d 100644 --- a/modules/ticket/back/methods/sale/specs/updateQuantity.spec.js +++ b/modules/ticket/back/methods/sale/specs/updateQuantity.spec.js @@ -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}, diff --git a/modules/ticket/back/methods/ticket/specs/addSale.spec.js b/modules/ticket/back/methods/ticket/specs/addSale.spec.js index cfd149511..2e568716a 100644 --- a/modules/ticket/back/methods/ticket/specs/addSale.spec.js +++ b/modules/ticket/back/methods/ticket/specs/addSale.spec.js @@ -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({}); diff --git a/modules/ticket/back/methods/ticket/specs/merge.spec.js b/modules/ticket/back/methods/ticket/specs/merge.spec.js index 78eb0c8f3..3254e58a8 100644 --- a/modules/ticket/back/methods/ticket/specs/merge.spec.js +++ b/modules/ticket/back/methods/ticket/specs/merge.spec.js @@ -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); diff --git a/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js b/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js index 1f6712087..41de1fd6e 100644 --- a/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js +++ b/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js @@ -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() => { diff --git a/modules/ticket/front/expedition/locale/es.yml b/modules/ticket/front/expedition/locale/es.yml index 278dcc8f2..1933bac20 100644 --- a/modules/ticket/front/expedition/locale/es.yml +++ b/modules/ticket/front/expedition/locale/es.yml @@ -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 diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index 8764417a8..6fc986e8f 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -28,8 +28,9 @@ + disabled="!$ctrl.hasSelectedSales()" + vn-tooltip="Select lines to see the options" + ng-click="moreOptions.show($event)"> { 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 diff --git a/modules/travel/front/extra-community/index.html b/modules/travel/front/extra-community/index.html index 376c81aaf..c888f97da 100644 --- a/modules/travel/front/extra-community/index.html +++ b/modules/travel/front/extra-community/index.html @@ -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"> @@ -48,6 +48,9 @@ Agency + + Amount + Reference @@ -107,6 +110,7 @@ {{::travel.cargoSupplierNickname}} + {{::travel.agencyModeName}} @@ -157,22 +161,15 @@ {{::entry.supplierName}} + {{::entry.invoiceAmount | currency: 'EUR': 2}} - {{::entry.ref}} + {{::entry.invoiceNumber}} {{::entry.stickers}} {{::entry.loadedkg}} {{::entry.volumeKg}} - - - {{::entry.notes}} - - - - - {{::entry.evaNotes}} - - + + diff --git a/modules/travel/front/extra-community/locale/es.yml b/modules/travel/front/extra-community/locale/es.yml index dc231226f..ed6179c91 100644 --- a/modules/travel/front/extra-community/locale/es.yml +++ b/modules/travel/front/extra-community/locale/es.yml @@ -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 \ No newline at end of file +W. Landed: F. llegada diff --git a/modules/travel/front/index/index.html b/modules/travel/front/index/index.html index 27a700083..a768e4a29 100644 --- a/modules/travel/front/index/index.html +++ b/modules/travel/front/index/index.html @@ -2,6 +2,15 @@ + + + + @@ -9,23 +18,22 @@ - Id Reference Agency Warehouse Out Shipped - Delivered + Warehouse In Landed - Received + + Total entries - - {{::travel.id}} {{::travel.ref}} {{::travel.agencyModeName}} {{::travel.warehouseOutName}} @@ -34,14 +42,27 @@ {{::travel.shipped | date:'dd/MM/yyyy'}} - + + + + {{::travel.warehouseInName}} {{::travel.landed | date:'dd/MM/yyyy'}} - + + + + + {{::travel.totalEntries}} - @@ -78,38 +99,9 @@ fixed-bottom-right> - - - - - Filter by selection - - - Exclude selection - - - Remove filter - - - Remove all filters - - - Copy value - - - \ No newline at end of file diff --git a/modules/travel/front/index/index.js b/modules/travel/front/index/index.js index 7701289b7..a570146fe 100644 --- a/modules/travel/front/index/index.js +++ b/modules/travel/front/index/index.js @@ -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', { diff --git a/modules/travel/front/index/style.scss b/modules/travel/front/index/style.scss new file mode 100644 index 000000000..ca1cf538b --- /dev/null +++ b/modules/travel/front/index/style.scss @@ -0,0 +1,11 @@ +@import "variables"; + +vn-travel-index { + vn-icon { + color: $color-font-secondary + } + + vn-icon.active { + color: $color-success + } +} diff --git a/modules/travel/front/locale/es.yml b/modules/travel/front/locale/es.yml index 7231d37cd..043702b99 100644 --- a/modules/travel/front/locale/es.yml +++ b/modules/travel/front/locale/es.yml @@ -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 \ No newline at end of file +Thermographs: Termógrafos diff --git a/modules/travel/front/main/index.html b/modules/travel/front/main/index.html index feb1e8b01..131d9409e 100644 --- a/modules/travel/front/main/index.html +++ b/modules/travel/front/main/index.html @@ -1,20 +1,6 @@ - - - - - \ No newline at end of file + diff --git a/modules/travel/front/main/index.js b/modules/travel/front/main/index.js index fbaf78c16..6a153f21a 100644 --- a/modules/travel/front/main/index.js +++ b/modules/travel/front/main/index.js @@ -4,28 +4,6 @@ import ModuleMain from 'salix/components/module-main'; export default class Travel extends ModuleMain { constructor() { super(); - - this.filterParams = { - scopeDays: 1 - }; - } - - fetchParams($params) { - if (!Object.entries($params).length) - $params.scopeDays = 1; - - if (typeof $params.scopeDays === 'number') { - const shippedFrom = Date.vnNew(); - shippedFrom.setHours(0, 0, 0, 0); - - const shippedTo = new Date(shippedFrom.getTime()); - shippedTo.setDate(shippedTo.getDate() + $params.scopeDays); - shippedTo.setHours(23, 59, 59, 999); - - Object.assign($params, {shippedFrom, shippedTo}); - } - - return $params; } } diff --git a/modules/travel/front/main/index.spec.js b/modules/travel/front/main/index.spec.js deleted file mode 100644 index bf5a27b41..000000000 --- a/modules/travel/front/main/index.spec.js +++ /dev/null @@ -1,49 +0,0 @@ -import './index.js'; - -describe('Travel Component vnTravel', () => { - let controller; - - beforeEach(ngModule('travel')); - - beforeEach(inject($componentController => { - let $element = angular.element(`
`); - controller = $componentController('vnTravel', {$element}); - })); - - describe('fetchParams()', () => { - it('should return a range of dates with passed scope days', () => { - let params = controller.fetchParams({ - scopeDays: 2 - }); - const shippedFrom = Date.vnNew(); - shippedFrom.setHours(0, 0, 0, 0); - const shippedTo = new Date(shippedFrom.getTime()); - shippedTo.setDate(shippedTo.getDate() + params.scopeDays); - shippedTo.setHours(23, 59, 59, 999); - - const expectedParams = { - shippedFrom, - scopeDays: params.scopeDays, - shippedTo - }; - - expect(params).toEqual(expectedParams); - }); - - it('should return default value for scope days', () => { - let params = controller.fetchParams({ - scopeDays: 1 - }); - - expect(params.scopeDays).toEqual(1); - }); - - it('should return the given scope days', () => { - let params = controller.fetchParams({ - scopeDays: 2 - }); - - expect(params.scopeDays).toEqual(2); - }); - }); -}); diff --git a/modules/travel/front/search-panel/index.html b/modules/travel/front/search-panel/index.html index 2e9c796c3..dd8d98af1 100644 --- a/modules/travel/front/search-panel/index.html +++ b/modules/travel/front/search-panel/index.html @@ -1,109 +1,146 @@ -
-
- - - - - - - - - - - - - - - - -
- - - - - - Or - - - - - -
- - - - - - - - - - - - - - - - - - - -
-
\ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Id/{{$ctrl.$t('Reference')}}: {{$ctrl.filter.search}} + + + {{$ctrl.$t('Agency')}}: {{agency.selection.name}} + + + {{$ctrl.$t('Warehouse Out')}}: {{warehouseOut.selection.name}} + + + {{$ctrl.$t('Warehouse In')}}: {{warehouseIn.selection.name}} + + + {{$ctrl.$t('Days onward')}}: {{$ctrl.filter.scopeDays}} + + + {{$ctrl.$t('Landed from')}}: {{$ctrl.filter.landedFrom | date:'dd/MM/yyyy'}} + + + {{$ctrl.$t('Landed to')}}: {{$ctrl.filter.landedTo | date:'dd/MM/yyyy'}} + + + {{$ctrl.$t('Continent Out')}}: {{continent.selection.name}} + + + {{$ctrl.$t('Total entries')}}: {{$ctrl.filter.totalEntries}} + +
+
diff --git a/modules/travel/front/search-panel/index.js b/modules/travel/front/search-panel/index.js index 877d4f9d3..9cf417da1 100644 --- a/modules/travel/front/search-panel/index.js +++ b/modules/travel/front/search-panel/index.js @@ -1,43 +1,69 @@ 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 = this.$.filter; + this.initFilter(); + this.fetchData(); } - get shippedFrom() { - return this._shippedFrom; + $onChanges() { + if (this.model) + this.applyFilters(); } - set shippedFrom(value) { - this._shippedFrom = value; - this.filter.scopeDays = null; + fetchData() { + this.$http.get('AgencyModes').then(res => { + this.agencyModes = res.data; + }); + this.$http.get('Warehouses').then(res => { + this.warehouses = res.data; + }); + this.$http.get('Continents').then(res => { + this.continents = res.data; + }); } - get shippedTo() { - return this._shippedTo; + initFilter() { + this.filter = {}; + if (this.$params.q) { + this.filter = JSON.parse(this.$params.q); + this.search = this.filter.search; + this.totalEntries = this.filter.totalEntries; + } + if (!this.filter.scopeDays) this.filter.scopeDays = 7; } - set shippedTo(value) { - this._shippedTo = value; - this.filter.scopeDays = null; + applyFilters(param) { + this.model.applyFilter({}, this.filter) + .then(() => { + if (param && this.model._orgData.length === 1) + this.$state.go('travel.card.summary', {id: this.model._orgData[0].id}); + else + this.$state.go(this.$state.current.name, {q: JSON.stringify(this.filter)}, {location: 'replace'}); + }); } - get scopeDays() { - return this._scopeDays; + removeParamFilter(param) { + if (this[param]) delete this[param]; + delete this.filter[param]; + this.applyFilters(); } - set scopeDays(value) { - this._scopeDays = value; - - this.filter.shippedFrom = null; - this.filter.shippedTo = null; + onKeyPress($event, param) { + if ($event.key === 'Enter') { + this.filter[param] = this[param]; + this.applyFilters(param === 'search'); + } } } ngModule.vnComponent('vnTravelSearchPanel', { template: require('./index.html'), - controller: Controller + controller: Controller, + bindings: { + model: '<' + } }); diff --git a/modules/travel/front/search-panel/index.spec.js b/modules/travel/front/search-panel/index.spec.js index 884f4fb17..488143e80 100644 --- a/modules/travel/front/search-panel/index.spec.js +++ b/modules/travel/front/search-panel/index.spec.js @@ -8,41 +8,31 @@ describe('Travel Component vnTravelSearchPanel', () => { beforeEach(inject($componentController => { controller = $componentController('vnTravelSearchPanel', {$element: null}); controller.$t = () => {}; - controller.filter = {}; })); - describe('shippedFrom() setter', () => { - it('should clear the scope days when setting the from property', () => { - controller.filter.scopeDays = 1; + describe('applyFilters()', () => { + it('should apply filters', async() => { + controller.filter = {foo: 'bar'}; + controller.model = { + applyFilter: jest.fn().mockResolvedValue(), + _orgData: [{id: 1}] + }; + controller.$state = { + current: { + name: 'foo' + }, + go: jest.fn() + }; - controller.shippedFrom = Date.vnNew(); + await controller.applyFilters(true); - expect(controller.filter.scopeDays).toBeNull(); - expect(controller.shippedFrom).toBeDefined(); - }); - }); + expect(controller.model.applyFilter).toHaveBeenCalledWith({}, controller.filter); + expect(controller.$state.go).toHaveBeenCalledWith('travel.card.summary', {id: 1}); - describe('shippedTo() setter', () => { - it('should clear the scope days when setting the to property', () => { - controller.filter.scopeDays = 1; + await controller.applyFilters(false); - controller.shippedTo = Date.vnNew(); - - expect(controller.filter.scopeDays).toBeNull(); - expect(controller.shippedTo).toBeDefined(); - }); - }); - - describe('scopeDays() setter', () => { - it('should clear the date range when setting the scopeDays property', () => { - controller.filter.shippedFrom = Date.vnNew(); - controller.filter.shippedTo = Date.vnNew(); - - controller.scopeDays = 1; - - expect(controller.filter.shippedFrom).toBeNull(); - expect(controller.filter.shippedTo).toBeNull(); - expect(controller.scopeDays).toBeDefined(); + expect(controller.$state.go).toHaveBeenCalledWith(controller.$state.current.name, + {q: JSON.stringify(controller.filter)}, {location: 'replace'}); }); }); }); diff --git a/modules/travel/front/search-panel/style.scss b/modules/travel/front/search-panel/style.scss new file mode 100644 index 000000000..94fe7b239 --- /dev/null +++ b/modules/travel/front/search-panel/style.scss @@ -0,0 +1,37 @@ +@import "variables"; + +vn-travel-search-panel vn-side-menu { + .menu { + min-width: $right-menu-width; + } + & > div { + .input { + padding-left: $spacing-md; + padding-right: $spacing-md; + border-color: $color-spacer; + border-bottom: $border-thin; + } + .horizontal { + padding-left: $spacing-md; + padding-right: $spacing-md; + 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; + } + + .or { + align-self: center; + font-weight: bold; + font-size: 26px; + color: $color-font-secondary; + } + } +} diff --git a/modules/travel/front/summary/locale/es.yml b/modules/travel/front/summary/locale/es.yml index aa002fad0..aa6adc938 100644 --- a/modules/travel/front/summary/locale/es.yml +++ b/modules/travel/front/summary/locale/es.yml @@ -1,9 +1,9 @@ Reference: Referencia -Warehouse In: Almacén entrada -Warehouse Out: Almacén salida +Warehouse In: Alm. entrada +Warehouse Out: Alm. salida Shipped: F. envío Landed: F. entrega -Total entries: Entradas totales +Total entries: Ent. totales Delivered: Enviada Received: Recibida Agency: Agencia diff --git a/modules/wagon/back/methods/wagonType/createWagonType.js b/modules/wagon/back/methods/wagonType/createWagonType.js new file mode 100644 index 000000000..fed915b28 --- /dev/null +++ b/modules/wagon/back/methods/wagonType/createWagonType.js @@ -0,0 +1,57 @@ +module.exports = Self => { + Self.remoteMethodCtx('createWagonType', { + description: 'Creates a new wagon type', + accessType: 'WRITE', + accepts: [ + { + arg: 'name', + type: 'String', + required: true + }, + { + arg: 'divisible', + type: 'boolean', + required: true + }, { + arg: 'trays', + type: 'any', + required: true + } + ], + http: { + path: `/createWagonType`, + verb: 'PATCH' + } + }); + + Self.createWagonType = async(ctx, options) => { + const args = 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; + } + + try { + const newWagonType = await models.WagonType.create({name: args.name, divisible: args.divisible}, myOptions); + args.trays.forEach(async tray => { + await models.WagonTypeTray.create({ + typeFk: newWagonType.id, + height: tray.position, + colorFk: tray.color.id + }, myOptions); + }); + + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/wagon/back/methods/wagonType/deleteWagonType.js b/modules/wagon/back/methods/wagonType/deleteWagonType.js new file mode 100644 index 000000000..46b65e32f --- /dev/null +++ b/modules/wagon/back/methods/wagonType/deleteWagonType.js @@ -0,0 +1,43 @@ +module.exports = Self => { + Self.remoteMethodCtx('deleteWagonType', { + description: 'Deletes a wagon type', + accessType: 'WRITE', + accepts: [ + { + arg: 'id', + type: 'Number', + required: true + } + ], + http: { + path: `/deleteWagonType`, + verb: 'DELETE' + } + }); + + Self.deleteWagonType = async(ctx, options) => { + const args = 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; + } + + try { + await models.Wagon.destroyAll({typeFk: args.id}, myOptions); + await models.WagonTypeTray.destroyAll({typeFk: args.id}, myOptions); + await models.WagonType.destroyAll({id: args.id}, myOptions); + + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/wagon/back/methods/wagonType/editWagonType.js b/modules/wagon/back/methods/wagonType/editWagonType.js new file mode 100644 index 000000000..bd5ad1f16 --- /dev/null +++ b/modules/wagon/back/methods/wagonType/editWagonType.js @@ -0,0 +1,64 @@ +module.exports = Self => { + Self.remoteMethodCtx('editWagonType', { + description: 'Edits a new wagon type', + accessType: 'WRITE', + accepts: [ + { + arg: 'id', + type: 'String', + required: true + }, + { + arg: 'name', + type: 'String', + required: true + }, + { + arg: 'divisible', + type: 'boolean', + required: true + }, { + arg: 'trays', + type: 'any', + required: true + } + ], + http: { + path: `/editWagonType`, + verb: 'PATCH' + } + }); + + Self.editWagonType = async(ctx, options) => { + const args = 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; + } + + try { + const wagonType = await models.WagonType.findById(args.id, null, myOptions); + wagonType.updateAttributes({name: args.name, divisible: args.divisible}, myOptions); + models.WagonTypeTray.destroyAll({typeFk: args.id}, myOptions); + args.trays.forEach(async tray => { + await models.WagonTypeTray.create({ + typeFk: args.id, + height: tray.position, + colorFk: tray.color.id + }, myOptions); + }); + + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/wagon/back/methods/wagonType/specs/crudWagonType.spec.js b/modules/wagon/back/methods/wagonType/specs/crudWagonType.spec.js new file mode 100644 index 000000000..92ac61060 --- /dev/null +++ b/modules/wagon/back/methods/wagonType/specs/crudWagonType.spec.js @@ -0,0 +1,63 @@ +const models = require('vn-loopback/server/server').models; + +describe('WagonType crudWagonType()', () => { + const ctx = { + args: { + name: 'Mock wagon type', + divisible: true, + trays: [{position: 0, color: {id: 1}}, + {position: 50, color: {id: 2}}, + {position: 100, color: {id: 3}}] + } + }; + + it(`should create, edit and delete a new wagon type and its trays`, async() => { + const tx = await models.WagonType.beginTransaction({}); + + try { + const options = {transaction: tx}; + + // create + await models.WagonType.createWagonType(ctx, options); + + const newWagonType = await models.WagonType.findOne({where: {name: ctx.args.name}}, options); + const newWagonTrays = await models.WagonTypeTray.find({where: {typeFk: newWagonType.id}}, options); + + expect(newWagonType).not.toEqual(null); + expect(newWagonType.name).toEqual(ctx.args.name); + expect(newWagonType.divisible).toEqual(ctx.args.divisible); + expect(newWagonTrays.length).toEqual(ctx.args.trays.length); + + ctx.args = { + id: newWagonType.id, + name: 'Edited wagon type', + divisible: false, + trays: [{position: 0, color: {id: 1}}] + }; + + // edit + await models.WagonType.editWagonType(ctx, options); + + const editedWagonType = await models.WagonType.findById(newWagonType.id, null, options); + const editedWagonTrays = await models.WagonTypeTray.find({where: {typeFk: newWagonType.id}}, options); + + expect(editedWagonType.name).toEqual(ctx.args.name); + expect(editedWagonType.divisible).toEqual(ctx.args.divisible); + expect(editedWagonTrays.length).toEqual(ctx.args.trays.length); + + // delete + await models.WagonType.deleteWagonType(ctx, options); + + const deletedWagonType = await models.WagonType.findById(newWagonType.id, null, options); + const deletedWagonTrays = await models.WagonTypeTray.find({where: {typeFk: newWagonType.id}}, options); + + expect(deletedWagonType).toEqual(null); + expect(deletedWagonTrays).toEqual([]); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/wagon/back/model-config.json b/modules/wagon/back/model-config.json new file mode 100644 index 000000000..279d55e5c --- /dev/null +++ b/modules/wagon/back/model-config.json @@ -0,0 +1,23 @@ +{ + "Wagon": { + "dataSource": "vn" + }, + "WagonType": { + "dataSource": "vn" + }, + "WagonTypeColor": { + "dataSource": "vn" + }, + "WagonTypeTray": { + "dataSource": "vn" + }, + "WagonConfig": { + "dataSource": "vn" + }, + "CollectionWagon": { + "dataSource": "vn" + }, + "CollectionWagonTicket": { + "dataSource": "vn" + } +} diff --git a/modules/wagon/back/models/collection-wagon-ticket.json b/modules/wagon/back/models/collection-wagon-ticket.json new file mode 100644 index 000000000..04527205c --- /dev/null +++ b/modules/wagon/back/models/collection-wagon-ticket.json @@ -0,0 +1,43 @@ +{ + "name": "CollectionWagonTicket", + "base": "VnModel", + "options": { + "mysql": { + "table": "collectionWagonTicket" + } + }, + "properties": { + "ticketFk": { + "id": true, + "type": "number" + }, + "wagonFk": { + "type": "number", + "required": true + }, + "trayFk": { + "type": "number", + "required": true + }, + "side": { + "type": "string" + } + }, + "relations": { + "ticket": { + "type": "belongsTo", + "model": "Ticket", + "foreignKey": "ticketFk" + }, + "wagon": { + "type": "belongsTo", + "model": "Wagon", + "foreignKey": "wagonFk" + }, + "tray": { + "type": "belongsTo", + "model": "WagonTypeTray", + "foreignKey": "trayFk" + } + } +} diff --git a/modules/wagon/back/models/collection-wagon.json b/modules/wagon/back/models/collection-wagon.json new file mode 100644 index 000000000..f3f237428 --- /dev/null +++ b/modules/wagon/back/models/collection-wagon.json @@ -0,0 +1,34 @@ +{ + "name": "CollectionWagon", + "base": "VnModel", + "options": { + "mysql": { + "table": "collectionWagon" + } + }, + "properties": { + "collectionFk": { + "id": true, + "type": "number" + }, + "wagonFk": { + "type": "number", + "required": true + }, + "position": { + "type": "number" + } + }, + "relations": { + "collection": { + "type": "belongsTo", + "model": "Collection", + "foreignKey": "collectionFk" + }, + "wagon": { + "type": "belongsTo", + "model": "Wagon", + "foreignKey": "wagonFk" + } + } +} diff --git a/modules/wagon/back/models/wagon-config.json b/modules/wagon/back/models/wagon-config.json new file mode 100644 index 000000000..3d96e2864 --- /dev/null +++ b/modules/wagon/back/models/wagon-config.json @@ -0,0 +1,30 @@ +{ + "name": "WagonConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "wagonConfig" + } + }, + "properties": { + "id": { + "id": true, + "type": "number" + }, + "width": { + "type": "number" + }, + "height": { + "type": "string" + }, + "maxWagonHeight": { + "type": "number" + }, + "minHeightBetweenTrays": { + "type": "number" + }, + "maxTrays": { + "type": "number" + } + } +} diff --git a/modules/wagon/back/models/wagon-type-color.json b/modules/wagon/back/models/wagon-type-color.json new file mode 100644 index 000000000..573fd60f5 --- /dev/null +++ b/modules/wagon/back/models/wagon-type-color.json @@ -0,0 +1,21 @@ +{ + "name": "WagonTypeColor", + "base": "VnModel", + "options": { + "mysql": { + "table": "wagonTypeColor" + } + }, + "properties": { + "id": { + "id": true, + "type": "number" + }, + "name": { + "type": "string" + }, + "rgb": { + "type": "string" + } + } +} diff --git a/modules/wagon/back/models/wagon-type-tray.json b/modules/wagon/back/models/wagon-type-tray.json new file mode 100644 index 000000000..b61510bcf --- /dev/null +++ b/modules/wagon/back/models/wagon-type-tray.json @@ -0,0 +1,36 @@ +{ + "name": "WagonTypeTray", + "base": "VnModel", + "options": { + "mysql": { + "table": "wagonTypeTray" + } + }, + "properties": { + "id": { + "id": true, + "type": "number" + }, + "typeFk": { + "type": "number" + }, + "height": { + "type": "number" + }, + "colorFk": { + "type": "number" + } + }, + "relations": { + "type": { + "type": "belongsTo", + "model": "WagonType", + "foreignKey": "typeFk" + }, + "color": { + "type": "belongsTo", + "model": "WagonTypeColor", + "foreignKey": "colorFk" + } + } +} diff --git a/modules/wagon/back/models/wagon-type.js b/modules/wagon/back/models/wagon-type.js new file mode 100644 index 000000000..bebf7a9d9 --- /dev/null +++ b/modules/wagon/back/models/wagon-type.js @@ -0,0 +1,5 @@ +module.exports = Self => { + require('../methods/wagonType/createWagonType')(Self); + require('../methods/wagonType/editWagonType')(Self); + require('../methods/wagonType/deleteWagonType')(Self); +}; diff --git a/modules/wagon/back/models/wagon-type.json b/modules/wagon/back/models/wagon-type.json new file mode 100644 index 000000000..f57bf957d --- /dev/null +++ b/modules/wagon/back/models/wagon-type.json @@ -0,0 +1,21 @@ +{ + "name": "WagonType", + "base": "VnModel", + "options": { + "mysql": { + "table": "wagonType" + } + }, + "properties": { + "id": { + "id": true, + "type": "number" + }, + "name": { + "type": "string" + }, + "divisible": { + "type": "boolean" + } + } +} diff --git a/modules/wagon/back/models/wagon.json b/modules/wagon/back/models/wagon.json new file mode 100644 index 000000000..61ee61e61 --- /dev/null +++ b/modules/wagon/back/models/wagon.json @@ -0,0 +1,34 @@ +{ + "name": "Wagon", + "base": "VnModel", + "options": { + "mysql": { + "table": "wagon" + } + }, + "properties": { + "id": { + "id": true, + "type": "number" + }, + "label": { + "type": "number" + }, + "volume": { + "type": "number" + }, + "plate": { + "type": "string" + }, + "typeFk": { + "type": "number" + } + }, + "relations": { + "type": { + "type": "belongsTo", + "model": "WagonType", + "foreignKey": "typeFk" + } + } +} diff --git a/package.json b/package.json index 3d0fc4aed..8fa177646 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "23.12.01", + "version": "23.14.01", "author": "Verdnatura Levante SL", "description": "Salix backend", "license": "GPL-3.0", diff --git a/print/templates/reports/delivery-note/delivery-note.html b/print/templates/reports/delivery-note/delivery-note.html index eb133c0cd..0be5a30f0 100644 --- a/print/templates/reports/delivery-note/delivery-note.html +++ b/print/templates/reports/delivery-note/delivery-note.html @@ -33,7 +33,7 @@
{{$t('deliveryAddress')}}
-
+

{{address.nickname}}

{{address.street}}
{{address.postalCode}}, {{address.city}} ({{address.province}})
@@ -245,13 +245,8 @@
- + \ No newline at end of file diff --git a/print/templates/reports/invoice/invoice.html b/print/templates/reports/invoice/invoice.html index 2d180878a..7a1ed3319 100644 --- a/print/templates/reports/invoice/invoice.html +++ b/print/templates/reports/invoice/invoice.html @@ -240,13 +240,18 @@
-
+
{{$t('observations')}}
-
{{$t('wireTransfer')}}
-
{{$t('accountNumber', [invoice.iban])}}
+
+
{{$t('wireTransfer')}}
+
{{$t('accountNumber', [invoice.iban])}}
+
+
+ {{ticketObservations}} +
diff --git a/print/templates/reports/invoice/invoice.js b/print/templates/reports/invoice/invoice.js index b135e152b..42988113f 100755 --- a/print/templates/reports/invoice/invoice.js +++ b/print/templates/reports/invoice/invoice.js @@ -21,11 +21,15 @@ module.exports = { const map = new Map(); + this.ticketObservations = ''; for (let ticket of tickets) { ticket.sales = []; map.set(ticket.id, ticket); + + if (ticket.description) this.ticketObservations += ticket.description + ' '; } + this.ticketObservations = this.ticketObservations.trim(); for (let sale of sales) { const ticket = map.get(sale.ticketFk); diff --git a/print/templates/reports/invoice/sql/tickets.sql b/print/templates/reports/invoice/sql/tickets.sql index 162f043e2..a8385599c 100644 --- a/print/templates/reports/invoice/sql/tickets.sql +++ b/print/templates/reports/invoice/sql/tickets.sql @@ -1,8 +1,12 @@ SELECT t.id, t.shipped, - t.nickname -FROM invoiceOut io - JOIN ticket t ON t.refFk = io.ref + t.nickname, + tto.description +FROM invoiceOut io + JOIN ticket t ON t.refFk = io.REF + LEFT JOIN observationType ot ON ot.code = 'invoiceOut' + LEFT JOIN ticketObservation tto ON tto.ticketFk = t.id + AND tto.observationTypeFk = ot.id WHERE t.refFk = ? -ORDER BY t.shipped \ No newline at end of file +ORDER BY t.shipped