diff --git a/back/methods/image/download.js b/back/methods/image/download.js new file mode 100644 index 000000000..6f1e0b8e7 --- /dev/null +++ b/back/methods/image/download.js @@ -0,0 +1,96 @@ +const UserError = require('vn-loopback/util/user-error'); +const fs = require('fs-extra'); + +module.exports = Self => { + Self.remoteMethod('download', { + description: 'Get the user image', + accessType: 'READ', + accepts: [ + { + arg: 'collection', + type: 'String', + description: 'The image collection', + http: {source: 'path'} + }, + { + arg: 'size', + type: 'String', + description: 'The image size', + http: {source: 'path'} + }, + { + arg: 'id', + type: 'Number', + description: 'The user id', + http: {source: 'path'} + } + + ], + 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: `/:collection/:size/:id/download`, + verb: 'GET' + } + }); + + Self.download = async function(collection, size, id) { + const models = Self.app.models; + const filter = { + where: { + name: collection}, + include: { + relation: 'readRole' + } + }; + const imageCollection = await models.ImageCollection.findOne(filter); + const entity = await models[imageCollection.model].findById(id, { + fields: ['id', imageCollection.property] + }); + const image = await models.Image.findOne({where: { + collectionFk: collection, + name: entity[imageCollection.property]} + }); + + if (!image) return false; + + const imageRole = imageCollection.readRole().name; + const hasRole = await models.Account.hasRole(id, imageRole); + if (!hasRole) + throw new UserError(`You don't have enough privileges`); + + let file; + let env = process.env.NODE_ENV; + if (env && env != 'development') { + file = { + path: `/var/lib/salix/image/${collection}/${size}/${image.name}.png`, + contentType: 'image/png', + name: `${image.name}.png` + }; + } else { + file = { + path: `${process.cwd()}/storage/image/${collection}/${size}/${image.name}.png`, + contentType: 'image/png', + name: `${image.name}.png` + }; + } + await fs.access(file.path); + let stream = fs.createReadStream(file.path); + return [stream, file.contentType, `filename="${file.name}"`]; + }; +}; diff --git a/back/methods/image/specs/download.spec.js b/back/methods/image/specs/download.spec.js new file mode 100644 index 000000000..e646d829d --- /dev/null +++ b/back/methods/image/specs/download.spec.js @@ -0,0 +1,21 @@ +const app = require('vn-loopback/server/server'); + +describe('image download()', () => { + const collection = 'user'; + const size = '160x160'; + + it('should return the image content-type of the user', async() => { + const userId = 9; + const image = await app.models.Image.download(collection, size, userId); + const contentType = image[1]; + + expect(contentType).toEqual('image/png'); + }); + + it(`should return false if the user doesn't have image`, async() => { + const userId = 110; + const image = await app.models.Image.download(collection, size, userId); + + expect(image).toBeFalse(); + }); +}); diff --git a/back/models/account.json b/back/models/account.json index 364fe77d2..9cac20bc0 100644 --- a/back/models/account.json +++ b/back/models/account.json @@ -45,6 +45,9 @@ }, "updated": { "type": "date" + }, + "image": { + "type": "String" } }, "relations": { diff --git a/back/models/image-collection.json b/back/models/image-collection.json index 2234766c9..75faaf722 100644 --- a/back/models/image-collection.json +++ b/back/models/image-collection.json @@ -43,7 +43,12 @@ "model": "ImageCollectionSize", "foreignKey": "collectionFk", "property": "id" - } + }, + "readRole": { + "type": "belongsTo", + "model": "Role", + "foreignKey": "readRoleFk" + } }, "acls": [ { diff --git a/back/models/image.js b/back/models/image.js index 113bc7084..f6f4cf5db 100644 --- a/back/models/image.js +++ b/back/models/image.js @@ -3,6 +3,8 @@ const sharp = require('sharp'); const path = require('path'); module.exports = Self => { + require('../methods/image/download')(Self); + Self.getPath = function() { return '/var/lib/salix/image'; }; diff --git a/back/models/image.json b/back/models/image.json index 5b8c76cf1..047e5f5e4 100644 --- a/back/models/image.json +++ b/back/models/image.json @@ -29,6 +29,14 @@ "default": 0 } }, + "relations": { + "collection": { + "type": "belongsTo", + "model": "ImageCollection", + "foreignKey": "collectionFk", + "primaryKey": "name" + } + }, "acls": [ { "accessType": "READ", diff --git a/db/changes/10250-curfew/00-ACL.sql b/db/changes/10250-curfew/00-ACL.sql index 90f673c67..9c7fe1712 100644 --- a/db/changes/10250-curfew/00-ACL.sql +++ b/db/changes/10250-curfew/00-ACL.sql @@ -1,3 +1,11 @@ +UPDATE `salix`.`ACL` SET `principalId` = 'deliveryBoss' WHERE (`id` = '194'); +UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '97'); +UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '100'); +UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '103'); +UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '202'); + +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Town', '*', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss'); +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Province', '*', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss'); INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Supplier', '*', 'READ', 'ALLOW', 'ROLE', 'employee'); INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Supplier', '*', 'WRITE', 'ALLOW', 'ROLE', 'administrative'); INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('SupplierLog', '*', 'READ', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/10250-toqueDeQueda/00-claimState.sql b/db/changes/10250-curfew/00-claimState.sql similarity index 100% rename from db/changes/10250-toqueDeQueda/00-claimState.sql rename to db/changes/10250-curfew/00-claimState.sql diff --git a/db/changes/10250-curfew/00-imageCollection.sql b/db/changes/10250-curfew/00-imageCollection.sql new file mode 100644 index 000000000..f08f2f670 --- /dev/null +++ b/db/changes/10250-curfew/00-imageCollection.sql @@ -0,0 +1,5 @@ + +ALTER TABLE `hedera`.`imageCollection` +ADD COLUMN `readRoleFk` VARCHAR(45) NULL DEFAULT NULL AFTER `column`; + +update `hedera`.`imageCollection` set `readRoleFk` = 1; \ No newline at end of file diff --git a/db/changes/10250-toqueDeQueda/00-observationType.sql b/db/changes/10250-curfew/00-observationType.sql similarity index 100% rename from db/changes/10250-toqueDeQueda/00-observationType.sql rename to db/changes/10250-curfew/00-observationType.sql diff --git a/db/changes/10250-toqueDeQueda/00-supplierLog.sql b/db/changes/10250-curfew/00-supplierLog.sql similarity index 100% rename from db/changes/10250-toqueDeQueda/00-supplierLog.sql rename to db/changes/10250-curfew/00-supplierLog.sql diff --git a/db/changes/10250-toqueDeQueda/00-ticket_beforeUpdate.sql b/db/changes/10250-curfew/00-ticket_beforeUpdate.sql similarity index 100% rename from db/changes/10250-toqueDeQueda/00-ticket_beforeUpdate.sql rename to db/changes/10250-curfew/00-ticket_beforeUpdate.sql diff --git a/db/changes/10250-toqueDeQueda/00-ticket_componentPreview.sql b/db/changes/10250-curfew/00-ticket_componentPreview.sql similarity index 100% rename from db/changes/10250-toqueDeQueda/00-ticket_componentPreview.sql rename to db/changes/10250-curfew/00-ticket_componentPreview.sql diff --git a/db/changes/10250-curfew/00-user.sql b/db/changes/10250-curfew/00-user.sql new file mode 100644 index 000000000..90bcf8fc5 --- /dev/null +++ b/db/changes/10250-curfew/00-user.sql @@ -0,0 +1,2 @@ +ALTER TABLE `account`.`user` +ADD COLUMN `image` VARCHAR(255) NULL AFTER `password`; diff --git a/db/changes/10250-curfew/01-supplierFreighter.sql b/db/changes/10250-curfew/01-supplierFreighter.sql new file mode 100644 index 000000000..56c5f7680 --- /dev/null +++ b/db/changes/10250-curfew/01-supplierFreighter.sql @@ -0,0 +1,20 @@ +CREATE TABLE `vn`.supplierFreighter +( + supplierFk INT NOT NULL, + CONSTRAINT supplierFreighter_pk + PRIMARY KEY (supplierFk), + CONSTRAINT supplier_id_fk + FOREIGN KEY (supplierFk) REFERENCES supplier (id) + ON UPDATE CASCADE ON DELETE CASCADE +); + +INSERT IGNORE INTO `vn`.supplierFreighter (supplierFk) VALUES (286); +INSERT IGNORE INTO `vn`.supplierFreighter (supplierFk) VALUES (454); +INSERT IGNORE INTO `vn`.supplierFreighter (supplierFk) VALUES (582); +INSERT IGNORE INTO `vn`.supplierFreighter (supplierFk) VALUES (470); +INSERT IGNORE INTO `vn`.supplierFreighter (supplierFk) VALUES (775); +INSERT IGNORE INTO `vn`.supplierFreighter (supplierFk) VALUES (812); +INSERT IGNORE INTO `vn`.supplierFreighter (supplierFk) VALUES (1112); +INSERT IGNORE INTO `vn`.supplierFreighter (supplierFk) VALUES (1242); +INSERT IGNORE INTO `vn`.supplierFreighter (supplierFk) VALUES (1281); +INSERT IGNORE INTO `vn`.supplierFreighter (supplierFk) VALUES (1765); \ No newline at end of file diff --git a/db/changes/10250-curfew/02-travel.sql b/db/changes/10250-curfew/02-travel.sql new file mode 100644 index 000000000..3bd84d61f --- /dev/null +++ b/db/changes/10250-curfew/02-travel.sql @@ -0,0 +1,7 @@ +ALTER TABLE `vn`.travel + DROP FOREIGN KEY travel_ibfk_4; + +ALTER TABLE `vn`.travel + ADD CONSTRAINT supplierFreighter_fk_4 + FOREIGN KEY (cargoSupplierFk) REFERENCES supplierFreighter (supplierFk) + ON UPDATE CASCADE ON DELETE SET NULL; diff --git a/db/changes/10250-toqueDeQueda/00-ACL.sql b/db/changes/10250-toqueDeQueda/00-ACL.sql deleted file mode 100644 index 4cdadfd04..000000000 --- a/db/changes/10250-toqueDeQueda/00-ACL.sql +++ /dev/null @@ -1,10 +0,0 @@ -UPDATE `salix`.`ACL` SET `principalId` = 'deliveryBoss' WHERE (`id` = '194'); -UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '97'); -UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '100'); -UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '103'); -UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '202'); - -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Town', '*', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss'); -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Province', '*', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss'); -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('supplier', '*', 'WRITE', 'ALLOW', 'ROLE', 'administrative'); -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('SupplierContact', '*', 'WRITE', 'ALLOW', 'ROLE', 'administrative'); diff --git a/db/dump/dumpedFixtures.sql b/db/dump/dumpedFixtures.sql index 2c44b81ba..879b1eb42 100644 --- a/db/dump/dumpedFixtures.sql +++ b/db/dump/dumpedFixtures.sql @@ -443,7 +443,7 @@ USE `hedera`; LOCK TABLES `imageCollection` WRITE; /*!40000 ALTER TABLE `imageCollection` DISABLE KEYS */; -INSERT INTO `imageCollection` VALUES (1,'catalog','Artículo',3840,2160,'Item','image','vn','item','image'),(4,'link','Enlace',200,200,'Link','image','hedera','link','image'),(5,'news','Noticias',800,1200,'New','image','hedera','news','image'); +INSERT INTO `imageCollection` VALUES (1,'catalog','Artículo',3840,2160,'Item','image','vn','item','image'),(4,'link','Enlace',200,200,'Link','image','hedera','link','image'),(5,'news','Noticias',800,1200,'New','image','hedera','news','image'),('6','user','Usuario','800','1200','Account','image','account','user','image'); /*!40000 ALTER TABLE `imageCollection` ENABLE KEYS */; UNLOCK TABLES; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 8cd6c2b5c..ad8474e38 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -29,8 +29,8 @@ INSERT INTO `vn`.`packagingConfig`(`upperGap`) UPDATE `account`.`role` SET id = 100 WHERE id = 0; CALL `account`.`role_sync`; -INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`) - SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en' +INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`, `image`) + SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en', 'e7723f0b24ff05b32ed09d95196f2f29' FROM `account`.`role` WHERE id <> 20 ORDER BY id; @@ -51,20 +51,20 @@ INSERT INTO `hedera`.`tpvConfig`(`id`, `currency`, `terminal`, `transactionType` VALUES (1, 978, 1, 0, 2000, 9, 0); -INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,`email`,`lang`) +INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,`email`,`lang`, `image`) VALUES - (101, 'BruceWayne', 'Bruce Wayne', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'es'), - (102, 'PetterParker', 'Petter Parker', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'PetterParker@mydomain.com', 'en'), - (103, 'ClarkKent', 'Clark Kent', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'ClarkKent@mydomain.com', 'fr'), - (104, 'TonyStark', 'Tony Stark', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'TonyStark@mydomain.com', 'es'), - (105, 'MaxEisenhardt', 'Max Eisenhardt', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'MaxEisenhardt@mydomain.com', 'pt'), - (106, 'DavidCharlesHaller', 'David Charles Haller', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'DavidCharlesHaller@mydomain.com', 'en'), - (107, 'HankPym', 'Hank Pym', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'HankPym@mydomain.com', 'en'), - (108, 'CharlesXavier', 'Charles Xavier', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'CharlesXavier@mydomain.com', 'en'), - (109, 'BruceBanner', 'Bruce Banner', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'BruceBanner@mydomain.com', 'en'), - (110, 'JessicaJones', 'Jessica Jones', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'JessicaJones@mydomain.com', 'en'), - (111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'), - (112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'); + (101, 'BruceWayne', 'Bruce Wayne', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'es', 'e7723f0b24ff05b32ed09d95196f2f29'), + (102, 'PetterParker', 'Petter Parker', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'PetterParker@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), + (103, 'ClarkKent', 'Clark Kent', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'ClarkKent@mydomain.com', 'fr', 'e7723f0b24ff05b32ed09d95196f2f29'), + (104, 'TonyStark', 'Tony Stark', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'TonyStark@mydomain.com', 'es', 'e7723f0b24ff05b32ed09d95196f2f29'), + (105, 'MaxEisenhardt', 'Max Eisenhardt', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'MaxEisenhardt@mydomain.com', 'pt', 'e7723f0b24ff05b32ed09d95196f2f29'), + (106, 'DavidCharlesHaller', 'David Charles Haller', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'DavidCharlesHaller@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), + (107, 'HankPym', 'Hank Pym', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'HankPym@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), + (108, 'CharlesXavier', 'Charles Xavier', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'CharlesXavier@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), + (109, 'BruceBanner', 'Bruce Banner', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'BruceBanner@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), + (110, 'JessicaJones', 'Jessica Jones', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'JessicaJones@mydomain.com', 'en', NULL), + (111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en', NULL), + (112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en', NULL); INSERT INTO `account`.`mailAlias`(`id`, `alias`, `description`, `isPublic`) VALUES @@ -439,7 +439,7 @@ INSERT INTO `vn`.`bankEntity`(`id`, `countryFk`, `name`, `bic`) (2100, 1, 'Caixa Bank', 'CAIXESBB'); INSERT INTO `vn`.`supplierAccount`(`id`, `supplierFk`, `iban`, `bankEntityFk`) - VALUES + VALUES (241, 442, 'ES111122333344111122221111', 128); INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `sage200Company`, `expired`) @@ -1226,6 +1226,11 @@ INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email (3, 2, 321654987, NULL, 'supplier2@email.es', NULL, NULL), (4, 442, 321654987, NULL, NULL, 'observation442', NULL); +INSERT INTO `vn`.`supplierFreighter` (`supplierFk`) + VALUES + (1), + (2); + INSERT INTO `cache`.`cache_calc`(`id`, `cache_id`, `cacheName`, `params`, `last_refresh`, `expires`, `created`, `connection_id`) VALUES (1, 2, 'available', CONCAT_WS('/',1,CURDATE()), CURRENT_TIMESTAMP(), DATE_ADD(CURRENT_TIMESTAMP(),INTERVAL 15 MINUTE), CURDATE(), NULL), @@ -1240,16 +1245,16 @@ INSERT INTO `vn`.`ticketWeekly`(`ticketFk`, `weekDay`) (4, 4), (5, 6); -INSERT INTO `vn`.`travel`(`id`,`shipped`, `landed`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `m3`, `kg`,`ref`, `totalEntries`) +INSERT INTO `vn`.`travel`(`id`,`shipped`, `landed`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `m3`, `kg`,`ref`, `totalEntries`, `cargoSupplierFk`) VALUES - (1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), DATE_ADD(CURDATE(), INTERVAL -2 MONTH), 1, 2, 1, 100.00, 1000, 'first travel', 1), - (2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 150, 2000, 'second travel', 2), - (3, CURDATE(), CURDATE(), 1, 2, 1, 0.00, 0.00, 'third travel', 1), - (4, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 50.00, 500, 'fourth travel', 0), - (5, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 3, 2, 1, 50.00, 500, 'fifth travel', 1), - (6, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 4, 2, 1, 50.00, 500, 'sixth travel', 1), - (7, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 5, 2, 1, 50.00, 500, 'seventh travel', 2), - (8, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 5, 2, 1, 50.00, 500, 'eight travel', 1); + (1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), DATE_ADD(CURDATE(), INTERVAL -2 MONTH), 1, 2, 1, 100.00, 1000, 'first travel', 1, 1), + (2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 150, 2000, 'second travel', 2, 2), + (3, CURDATE(), CURDATE(), 1, 2, 1, 0.00, 0.00, 'third travel', 1, 1), + (4, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 50.00, 500, 'fourth travel', 0, 2), + (5, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 3, 2, 1, 50.00, 500, 'fifth travel', 1, 1), + (6, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 4, 2, 1, 50.00, 500, 'sixth travel', 1, 2), + (7, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 5, 2, 1, 50.00, 500, 'seventh travel', 2, 1), + (8, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 5, 2, 1, 50.00, 500, 'eight travel', 1, 2); INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `isConfirmed`, `companyFk`, `ref`,`isInventory`, `isRaid`, `notes`, `evaNotes`) VALUES @@ -2143,3 +2148,10 @@ INSERT INTO `vn`.`campaign`(`code`, `dated`) ('allSaints', CONCAT(YEAR(DATE_ADD(CURDATE(), INTERVAL -2 YEAR)), '-11-01')), ('allSaints', CONCAT(YEAR(DATE_ADD(CURDATE(), INTERVAL -3 YEAR)), '-11-01')); +INSERT INTO `hedera`.`image`(`collectionFk`, `name`) + VALUES + ('user', 'e7723f0b24ff05b32ed09d95196f2f29'); + +INSERT INTO `hedera`.`imageCollectionSize`(`id`, `collectionFk`,`width`, `height`) + VALUES + (1, 4, 160, 160); \ No newline at end of file diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index c28743585..83a1f5f35 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -815,6 +815,10 @@ export default { ticketOne: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(1)', ticketTwo: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(2)' }, + travelIndex: { + anySearchResult: 'vn-travel-index vn-tbody > a', + firstSearchResult: 'vn-travel-index vn-tbody > a:nth-child(1)' + }, travelBasicDada: { reference: 'vn-travel-basic-data vn-textfield[ng-model="$ctrl.travel.ref"]', agency: 'vn-travel-basic-data vn-autocomplete[ng-model="$ctrl.travel.agencyModeFk"]', @@ -841,6 +845,11 @@ export default { createdThermograph: 'vn-travel-thermograph-index vn-tbody > vn-tr', upload: 'vn-travel-thermograph-create button[type=submit]' }, + travelDescriptor: { + filterByAgencyButton: 'vn-descriptor-content .quicklinks > div:nth-child(1) > vn-quick-link > a[vn-tooltip="All travels with current agency"]', + dotMenu: 'vn-travel-descriptor vn-icon-button[icon="more_vert"]', + dotMenuClone: '#clone' + }, zoneIndex: { searchResult: 'vn-zone-index a.vn-tr', }, diff --git a/e2e/paths/02-client/14_balance.spec.js b/e2e/paths/02-client/14_balance.spec.js index a9d5e304a..bd00af58e 100644 --- a/e2e/paths/02-client/14_balance.spec.js +++ b/e2e/paths/02-client/14_balance.spec.js @@ -41,7 +41,6 @@ describe('Client balance path', () => { it('should click the new payment button', async() => { await page.closePopup(); await page.reloadSection('client.card.balance.index'); - await page.waitForState('client.card.balance.index'); }); it('should create a new payment that clears the debt', async() => { diff --git a/e2e/paths/10-travel/03_descriptor.spec.js b/e2e/paths/10-travel/03_descriptor.spec.js new file mode 100644 index 000000000..3f79bea06 --- /dev/null +++ b/e2e/paths/10-travel/03_descriptor.spec.js @@ -0,0 +1,45 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +describe('Travel descriptor path', () => { + let browser; + let page; + + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('buyer', 'travel'); + await page.accessToSearchResult('1'); + await page.waitForState('travel.card.summary'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it('should click the descriptor button to navigate to the travel index showing all travels with current agency', async() => { + await page.waitToClick(selectors.travelDescriptor.filterByAgencyButton); + await page.waitForState('travel.index'); + const result = await page.countElement(selectors.travelIndex.anySearchResult); + + expect(result).toBeGreaterThanOrEqual(7); + }); + + it('should navigate to the first search result', async() => { + await page.waitToClick(selectors.travelIndex.firstSearchResult); + await page.waitForState('travel.card.summary'); + const state = await page.getState(); + + expect(state).toBe('travel.card.summary'); + }); + + it('should be redirected to the create travel when using the clone option of the dot menu', async() => { + await page.waitToClick(selectors.travelDescriptor.dotMenu); + await page.waitToClick(selectors.travelDescriptor.dotMenuClone); + await page.respondToDialog('accept'); + await page.waitForState('travel.create'); + const state = await page.getState(); + + expect(state).toBe('travel.create'); + }); +}); diff --git a/front/salix/components/layout/index.html b/front/salix/components/layout/index.html index 8c716b84b..cb0fb93b4 100644 --- a/front/salix/components/layout/index.html +++ b/front/salix/components/layout/index.html @@ -31,12 +31,15 @@ ng-if="$ctrl.rightMenu" ng-click="$ctrl.rightMenu.show()"> - + - + translate-attr="{title: 'Account'}" + on-error-src/> + + diff --git a/front/salix/components/layout/index.js b/front/salix/components/layout/index.js index c6bc80734..986f61622 100644 --- a/front/salix/components/layout/index.js +++ b/front/salix/components/layout/index.js @@ -18,6 +18,14 @@ export class Layout extends Component { window.localStorage.currentUserWorkerId = json.data.id; }); } + + getImageUrl() { + if (!this.$.$root.user) return; + + const userId = this.$.$root.user.id; + const token = this.vnToken.token; + return `/api/Images/user/160x160/${userId}/download?access_token=${token}`; + } } Layout.$inject = ['$element', '$scope', 'vnModules']; diff --git a/front/salix/components/layout/index.spec.js b/front/salix/components/layout/index.spec.js index 18aca1f01..71dbb9192 100644 --- a/front/salix/components/layout/index.spec.js +++ b/front/salix/components/layout/index.spec.js @@ -22,4 +22,19 @@ describe('Component vnLayout', () => { expect(controller.$.$root.user.name).toEqual('batman'); }); }); + + describe('getImageUrl()', () => { + it('should return the url image if the user is defined', () => { + controller.$.$root.user = {id: 1}; + const url = controller.getImageUrl(); + + expect(url).not.toBe(3); + }); + + it('should return undefined if the user is not defined', () => { + const url = controller.getImageUrl(); + + expect(url).not.toBeDefined(); + }); + }); }); diff --git a/front/salix/components/layout/style.scss b/front/salix/components/layout/style.scss index 2b879040f..05858b3b1 100644 --- a/front/salix/components/layout/style.scss +++ b/front/salix/components/layout/style.scss @@ -109,6 +109,14 @@ vn-layout { } } } + img { + width: 40px; + border-radius: 50%; + } + .buttonAccount { + background: none; + border: none; + } @media screen and (max-width: $mobile-width) { & > vn-topbar { & > .start > .logo { @@ -147,3 +155,4 @@ vn-layout { font-size: 1.5rem; height: auto; } + diff --git a/front/salix/components/user-popover/index.html b/front/salix/components/user-popover/index.html index 4b02edd9f..9bd0f1411 100644 --- a/front/salix/components/user-popover/index.html +++ b/front/salix/components/user-popover/index.html @@ -13,7 +13,9 @@
- +
diff --git a/front/salix/components/user-popover/index.js b/front/salix/components/user-popover/index.js index c2fb6a130..e4d7b4466 100644 --- a/front/salix/components/user-popover/index.js +++ b/front/salix/components/user-popover/index.js @@ -3,12 +3,13 @@ import './style.scss'; import config from '../../config.json'; class Controller { - constructor($, $translate, vnConfig, vnAuth) { + constructor($, $translate, vnConfig, vnAuth, vnToken) { Object.assign(this, { $, $translate, vnConfig, vnAuth, + vnToken, lang: $translate.use(), langs: [] }); @@ -77,8 +78,12 @@ class Controller { this.$.companies.refresh(); this.$.popover.show(event.target); } + + getImageUrl(userId) { + return '/api/Images/user/160x160/' + userId + '/download?access_token=' + this.vnToken.token; + } } -Controller.$inject = ['$scope', '$translate', 'vnConfig', 'vnAuth']; +Controller.$inject = ['$scope', '$translate', 'vnConfig', 'vnAuth', 'vnToken']; ngModule.vnComponent('vnUserPopover', { template: require('./index.html'), diff --git a/front/salix/components/user-popover/index.spec.js b/front/salix/components/user-popover/index.spec.js index a0f1a953c..7a088fc51 100644 --- a/front/salix/components/user-popover/index.spec.js +++ b/front/salix/components/user-popover/index.spec.js @@ -57,5 +57,13 @@ describe('Salix', () => { expect(controller.companyFk).toBe(4); }); }); + + describe('getImageUrl()', () => { + it('should return de url image', () => { + const url = controller.getImageUrl(); + + expect(url).toBeDefined(); + }); + }); }); }); diff --git a/front/salix/components/user-popover/style.scss b/front/salix/components/user-popover/style.scss index 5f17ed293..9050c6654 100644 --- a/front/salix/components/user-popover/style.scss +++ b/front/salix/components/user-popover/style.scss @@ -11,6 +11,10 @@ font-size: 5rem; color: $color-font-bg-marginal; } + img { + width: 80px; + border-radius: 50%; + } & > div { display: flex; flex-direction: column; diff --git a/modules/claim/front/detail/index.html b/modules/claim/front/detail/index.html index 88c8232c7..3b97f9db3 100644 --- a/modules/claim/front/detail/index.html +++ b/modules/claim/front/detail/index.html @@ -55,8 +55,8 @@ {{::saleClaimed.sale.price | currency: 'EUR':2}} - {{saleClaimed.sale.discount}} % diff --git a/modules/claim/front/detail/index.js b/modules/claim/front/detail/index.js index 9f6767318..4e0017baa 100644 --- a/modules/claim/front/detail/index.js +++ b/modules/claim/front/detail/index.js @@ -54,6 +54,10 @@ class Controller extends Section { this.updateNewPrice(); } + get isClaimManager() { + return this.aclService.hasAny(['claimManager']); + } + openAddSalesDialog() { this.getClaimableFromTicket(); this.$.addSales.show(); @@ -131,17 +135,6 @@ class Controller extends Section { return total; } - showEditPopover(event, saleClaimed) { - if (this.isEditable) { - if (!this.aclService.hasAny(['claimManager'])) - return this.vnApp.showError(this.$t('Insuficient permisos')); - - this.saleClaimed = saleClaimed; - this.$.editPopover.parent = event.target; - this.$.editPopover.show(); - } - } - getSalespersonMana() { this.$http.get(`Tickets/${this.claim.ticketFk}/getSalesPersonMana`).then(res => { this.mana = res.data; @@ -164,6 +157,14 @@ class Controller extends Section { }); } + showEditPopover(event, saleClaimed) { + if (this.aclService.hasAny(['claimManager'])) { + this.saleClaimed = saleClaimed; + this.$.editPopover.parent = event.target; + this.$.editPopover.show(); + } + } + updateDiscount() { const claimedSale = this.saleClaimed.sale; if (this.newDiscount != claimedSale.discount) { @@ -176,8 +177,6 @@ class Controller extends Section { this.clearDiscount(); this.vnApp.showSuccess(this.$t('Data saved!')); - }).catch(err => { - this.vnApp.showError(err.message); }); } diff --git a/modules/item/front/last-entries/index.html b/modules/item/front/last-entries/index.html index 1f7c3c364..4d8dea570 100644 --- a/modules/item/front/last-entries/index.html +++ b/modules/item/front/last-entries/index.html @@ -50,7 +50,13 @@ {{::entry.warehouse| dashIfEmpty}} {{::entry.landed | date:'dd/MM/yyyy HH:mm'}} - {{::entry.entryFk | dashIfEmpty}} + + + {{::entry.entryFk | dashIfEmpty}} + + {{::entry.price2 | dashIfEmpty}} {{::entry.price3 | dashIfEmpty}} {{entry.stickers | dashIfEmpty}} @@ -77,6 +83,10 @@ + + + diff --git a/modules/ticket/back/methods/ticket/updateDiscount.js b/modules/ticket/back/methods/ticket/updateDiscount.js index 7c9f9ae48..1bebaae8d 100644 --- a/modules/ticket/back/methods/ticket/updateDiscount.js +++ b/modules/ticket/back/methods/ticket/updateDiscount.js @@ -70,13 +70,17 @@ module.exports = Self => { const userId = ctx.req.accessToken.userId; const isLocked = await models.Ticket.isLocked(id); - const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson'); + const roles = await models.Account.getRoles(userId); + const hasAllowedRoles = roles.filter(role => + role == 'salesPerson' || role == 'claimManager' + ); + const state = await Self.app.models.TicketState.findOne({ where: {ticketFk: id} }); const alertLevel = state ? state.alertLevel : null; - if (isLocked || (!isSalesPerson && alertLevel > 0)) + if (isLocked || (!hasAllowedRoles && alertLevel > 0)) throw new UserError(`The sales of this ticket can't be modified`); const usesMana = await models.WorkerMana.findOne({ diff --git a/modules/ticket/back/models/ticket-last-state.json b/modules/ticket/back/models/ticket-last-state.json index 890d800af..6c8bc66d5 100644 --- a/modules/ticket/back/models/ticket-last-state.json +++ b/modules/ticket/back/models/ticket-last-state.json @@ -11,11 +11,10 @@ "type": "string" }, "ticketFk": { - "id": 1, + "id": true, "type": "Number" }, "ticketTrackingFk": { - "id": 2, "type": "Number" } }, diff --git a/modules/travel/back/models/travel.json b/modules/travel/back/models/travel.json index aebf4e3cd..df5b183b1 100644 --- a/modules/travel/back/models/travel.json +++ b/modules/travel/back/models/travel.json @@ -37,6 +37,9 @@ "m3": { "type": "Number" }, + "cargoSupplierFk": { + "type": "Number" + }, "agencyModeFk": { "type": "Number", "mysql": { diff --git a/modules/travel/front/descriptor-menu/index.html b/modules/travel/front/descriptor-menu/index.html new file mode 100644 index 000000000..1eb558008 --- /dev/null +++ b/modules/travel/front/descriptor-menu/index.html @@ -0,0 +1,22 @@ + + + + + + Clone travel + + + + + + + diff --git a/modules/travel/front/descriptor-menu/index.js b/modules/travel/front/descriptor-menu/index.js new file mode 100644 index 000000000..975cd9134 --- /dev/null +++ b/modules/travel/front/descriptor-menu/index.js @@ -0,0 +1,72 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; +import './style.scss'; + +class Controller extends Section { + constructor($element, $) { + super($element, $); + } + + get travelId() { + return this._travelId; + } + + set travelId(value) { + this._travelId = value; + + if (value) this.loadData(); + } + + loadData() { + const filter = { + fields: [ + 'id', + 'ref', + 'shipped', + 'landed', + 'totalEntries', + 'agencyFk', + 'warehouseInFk', + 'warehouseOutFk', + 'cargoSupplierFk' + ], + include: [ + { + relation: 'warehouseIn', + scope: { + fields: ['name'] + } + }, { + relation: 'warehouseOut', + scope: { + fields: ['name'] + } + } + ] + }; + return this.$http.get(`Travels/${this.travelId}`, {filter}) + .then(res => this.travel = res.data); + } + + onCloneAccept() { + const params = JSON.stringify({ + ref: this.travel.ref, + agencyModeFk: this.travel.agencyFk, + shipped: this.travel.shipped, + landed: this.travel.landed, + warehouseInFk: this.travel.warehouseInFk, + warehouseOutFk: this.travel.warehouseOutFk + }); + this.$state.go('travel.create', {q: params}); + } +} + +Controller.$inject = ['$element', '$scope']; + +ngModule.vnComponent('vnTravelDescriptorMenu', { + template: require('./index.html'), + controller: Controller, + bindings: { + travelId: '<', + } +}); diff --git a/modules/travel/front/descriptor-menu/index.spec.js b/modules/travel/front/descriptor-menu/index.spec.js new file mode 100644 index 000000000..d66f3a435 --- /dev/null +++ b/modules/travel/front/descriptor-menu/index.spec.js @@ -0,0 +1,39 @@ +import './index.js'; + +describe('Travel Component vnTravelDescriptorMenu', () => { + let controller; + beforeEach(ngModule('travel')); + + beforeEach(inject(($componentController, $state,) => { + const $element = angular.element(''); + controller = $componentController('vnTravelDescriptorMenu', {$element}); + })); + + describe('onCloneAccept()', () => { + it('should call state.go with the travel data', () => { + jest.spyOn(controller.$state, 'go').mockReturnValue('ok'); + + controller.travel = { + ref: 'the ref', + agencyFk: 'the agency', + shipped: 'the shipped date', + landed: 'the landing date', + warehouseInFk: 'the receiver warehouse', + warehouseOutFk: 'the sender warehouse' + }; + + controller.onCloneAccept(); + + const params = JSON.stringify({ + ref: controller.travel.ref, + agencyModeFk: controller.travel.agencyFk, + shipped: controller.travel.shipped, + landed: controller.travel.landed, + warehouseInFk: controller.travel.warehouseInFk, + warehouseOutFk: controller.travel.warehouseOutFk + }); + + expect(controller.$state.go).toHaveBeenCalledWith('travel.create', {'q': params}); + }); + }); +}); diff --git a/modules/travel/front/descriptor-menu/locale/es.yml b/modules/travel/front/descriptor-menu/locale/es.yml new file mode 100644 index 000000000..117611660 --- /dev/null +++ b/modules/travel/front/descriptor-menu/locale/es.yml @@ -0,0 +1 @@ +Clone travel: Clonar envío \ No newline at end of file diff --git a/modules/travel/front/descriptor-menu/style.scss b/modules/travel/front/descriptor-menu/style.scss new file mode 100644 index 000000000..beab9335e --- /dev/null +++ b/modules/travel/front/descriptor-menu/style.scss @@ -0,0 +1,24 @@ +@import "./effects"; +@import "variables"; + +vn-travel-descriptor-menu { + & > vn-icon-button[icon="more_vert"] { + display: flex; + min-width: 45px; + height: 45px; + box-sizing: border-box; + align-items: center; + justify-content: center; + } + & > vn-icon-button[icon="more_vert"] { + @extend %clickable; + color: inherit; + + & > vn-icon { + padding: 10px; + } + vn-icon { + font-size: 1.75rem; + } + } +} \ No newline at end of file diff --git a/modules/travel/front/descriptor/index.html b/modules/travel/front/descriptor/index.html index a3e77f6d1..28e908d18 100644 --- a/modules/travel/front/descriptor/index.html +++ b/modules/travel/front/descriptor/index.html @@ -1,6 +1,9 @@ + + +
+
diff --git a/modules/travel/front/descriptor/index.js b/modules/travel/front/descriptor/index.js index 6136d5347..dc19f68af 100644 --- a/modules/travel/front/descriptor/index.js +++ b/modules/travel/front/descriptor/index.js @@ -10,6 +10,18 @@ class Controller extends Descriptor { this.entity = value; } + get travelFilter() { + let travelFilter; + const travel = this.travel; + + if (travel && travel.agencyFk) { + travelFilter = this.travel && JSON.stringify({ + agencyFk: this.travel.agencyFk + }); + } + return travelFilter; + } + loadData() { const filter = { fields: [ @@ -19,7 +31,8 @@ class Controller extends Descriptor { 'landed', 'totalEntries', 'warehouseInFk', - 'warehouseOutFk' + 'warehouseOutFk', + 'cargoSupplierFk' ], include: [ { diff --git a/modules/travel/front/index.js b/modules/travel/front/index.js index 28ec27693..52a09af30 100644 --- a/modules/travel/front/index.js +++ b/modules/travel/front/index.js @@ -13,3 +13,4 @@ import './thermograph/index/'; import './thermograph/create/'; import './thermograph/edit/'; import './descriptor-popover'; +import './descriptor-menu'; diff --git a/storage/image/user/160x160/e7723f0b24ff05b32ed09d95196f2f29.png b/storage/image/user/160x160/e7723f0b24ff05b32ed09d95196f2f29.png new file mode 100644 index 000000000..2085c85f3 Binary files /dev/null and b/storage/image/user/160x160/e7723f0b24ff05b32ed09d95196f2f29.png differ