diff --git a/back/methods/image/download.js b/back/methods/image/download.js new file mode 100644 index 0000000000..6f1e0b8e79 --- /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 0000000000..e646d829d4 --- /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 364fe77d29..9cac20bc00 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 2234766c91..75faaf7220 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 113bc70844..f6f4cf5dbf 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 5b8c76cf1d..047e5f5e4f 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-imageCollection.sql b/db/changes/10250-curfew/00-imageCollection.sql new file mode 100644 index 0000000000..f08f2f6701 --- /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-curfew/00-user.sql b/db/changes/10250-curfew/00-user.sql new file mode 100644 index 0000000000..90bcf8fc5c --- /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/dump/dumpedFixtures.sql b/db/dump/dumpedFixtures.sql index 2c44b81ba9..879b1eb427 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 5503a0f7ae..bf3b4f92f0 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 @@ -2148,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 95ffca91e4..d0d5be5d5d 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -421,6 +421,10 @@ export default { moreMenuRestoreTicket: '.vn-menu [name="restoreTicket"]', moreMenuMakeInvoice: '.vn-menu [name="makeInvoice"]', moreMenuChangeShippedHour: '.vn-menu [name="changeShipped"]', + moreMenuPaymentSMS: '.vn-menu [name="sendPaymentSms"]', + moreMenuSendImportSms: '.vn-menu [name="sendImportSms"]', + SMStext: 'textarea[name="message"]', + sendSMSbutton: 'button[response="accept"]', changeShippedHourDialog: '.vn-dialog.shown', changeShippedHour: '.vn-dialog.shown vn-input-time[ng-model="$ctrl.newShipped"]', addStowawayDialogFirstTicket: '.vn-dialog.shown vn-table vn-tbody vn-tr', @@ -815,6 +819,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 +849,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', }, @@ -927,7 +940,7 @@ export default { }, supplierBasicData: { alias: 'vn-supplier-basic-data vn-textfield[ng-model="$ctrl.supplier.nickname"]', - isOfficial: 'vn-supplier-basic-data vn-check[ng-model="$ctrl.supplier.isOfficial"]', + isSerious: 'vn-supplier-basic-data vn-check[ng-model="$ctrl.supplier.isSerious"]', isActive: 'vn-supplier-basic-data vn-check[ng-model="$ctrl.supplier.isActive"]', notes: 'vn-supplier-basic-data vn-textarea[ng-model="$ctrl.supplier.note"]', saveButton: 'vn-supplier-basic-data button[type="submit"]', diff --git a/e2e/paths/02-client/14_balance.spec.js b/e2e/paths/02-client/14_balance.spec.js index a9d5e304a8..bd00af58ea 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/05-ticket/12_descriptor.spec.js b/e2e/paths/05-ticket/12_descriptor.spec.js index dda549edd6..c1258f9738 100644 --- a/e2e/paths/05-ticket/12_descriptor.spec.js +++ b/e2e/paths/05-ticket/12_descriptor.spec.js @@ -39,7 +39,7 @@ describe('Ticket descriptor path', () => { expect(result).toContain('08:15'); }); - it('should delete the ticket using the descriptor more menu', async() => { + it('should delete the ticket using the descriptor menu', async() => { await page.waitToClick(selectors.ticketDescriptor.moreMenu); await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket); await page.waitToClick(selectors.ticketDescriptor.acceptDialog); @@ -64,7 +64,7 @@ describe('Ticket descriptor path', () => { }); describe('Restore ticket', () => { - it('should restore the ticket using the descriptor more menu', async() => { + it('should restore the ticket using the descriptor menu', async() => { await page.waitToClick(selectors.ticketDescriptor.moreMenu); await page.waitToClick(selectors.ticketDescriptor.moreMenuRestoreTicket); await page.waitToClick(selectors.ticketDescriptor.acceptDialog); @@ -148,7 +148,7 @@ describe('Ticket descriptor path', () => { expect(result).toEqual('-'); }); - it('should invoice the ticket using the descriptor more menu', async() => { + it('should invoice the ticket using the descriptor menu', async() => { await page.waitToClick(selectors.ticketDescriptor.moreMenu); await page.waitForContentLoaded(); await page.waitToClick(selectors.ticketDescriptor.moreMenuMakeInvoice); @@ -165,4 +165,30 @@ describe('Ticket descriptor path', () => { expect(result).toEqual('T4444445'); }); }); + + describe('SMS', () => { + it('should send the payment SMS using the descriptor menu', async() => { + await page.waitToClick(selectors.ticketDescriptor.moreMenu); + await page.waitForContentLoaded(); + await page.waitToClick(selectors.ticketDescriptor.moreMenuPaymentSMS); + await page.waitForSelector(selectors.ticketDescriptor.SMStext); + await page.waitPropertyLength(selectors.ticketDescriptor.SMStext, 'value', 128); + await page.waitToClick(selectors.ticketDescriptor.sendSMSbutton); + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('SMS sent!'); + }); + + it('should send the import SMS using the descriptor menu', async() => { + await page.waitToClick(selectors.ticketDescriptor.moreMenu); + await page.waitForContentLoaded(); + await page.waitToClick(selectors.ticketDescriptor.moreMenuSendImportSms); + await page.waitForSelector(selectors.ticketDescriptor.SMStext); + await page.waitPropertyLength(selectors.ticketDescriptor.SMStext, 'value', 144); + await page.waitToClick(selectors.ticketDescriptor.sendSMSbutton); + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('SMS sent!'); + }); + }); }); 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 0000000000..3f79bea061 --- /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/e2e/paths/13-supplier/02_basic_data.spec.js b/e2e/paths/13-supplier/02_basic_data.spec.js index 9620af98fe..319130b983 100644 --- a/e2e/paths/13-supplier/02_basic_data.spec.js +++ b/e2e/paths/13-supplier/02_basic_data.spec.js @@ -20,7 +20,7 @@ describe('Supplier basic data path', () => { it('should edit the basic data', async() => { await page.clearInput(selectors.supplierBasicData.alias); await page.write(selectors.supplierBasicData.alias, 'Plants Nick SL'); - await page.waitToClick(selectors.supplierBasicData.isOfficial); + await page.waitToClick(selectors.supplierBasicData.isSerious); await page.waitToClick(selectors.supplierBasicData.isActive); await page.write(selectors.supplierBasicData.notes, 'Some notes'); @@ -40,8 +40,8 @@ describe('Supplier basic data path', () => { expect(result).toEqual('Plants Nick SL'); }); - it('should check the isOffical checkbox is now unchecked', async() => { - const result = await page.checkboxState(selectors.supplierBasicData.isOfficial); + it('should check the isSerious checkbox is now unchecked', async() => { + const result = await page.checkboxState(selectors.supplierBasicData.isSerious); expect(result).toBe('unchecked'); }); diff --git a/front/salix/components/layout/index.html b/front/salix/components/layout/index.html index 8c716b84b4..cb0fb93b4d 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 c6bc807347..986f616220 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 18aca1f01b..71dbb91923 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 2b879040f6..05858b3b18 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 4b02edd9f6..9bd0f14117 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 c2fb6a1307..e4d7b44664 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 a0f1a953c5..7a088fc515 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 5f17ed293b..9050c66544 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 88c8232c77..3b97f9db3f 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 9f6767318b..4e0017baa3 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/client/front/descriptor/index.html b/modules/client/front/descriptor/index.html index 1eee5d8536..634436395e 100644 --- a/modules/client/front/descriptor/index.html +++ b/modules/client/front/descriptor/index.html @@ -79,13 +79,20 @@
+ + +
+
-
+
{ expect(controller.save).not.toHaveBeenCalledWith(); expect(controller.checkExistingClient).toHaveBeenCalledWith(); }); + + it('should enable the hasToInvoice property any time the form activates the client with isActive', () => { + $scope.watcher.orgData.isActive = false; + controller.client.isActive = true; + controller.client.hasToInvoice = false; + + controller.onSubmit(); + + expect(controller.client.hasToInvoice).toBe(true); + }); }); describe('checkExistingClient()', () => { diff --git a/modules/item/front/last-entries/index.html b/modules/item/front/last-entries/index.html index 1f7c3c3645..4d8dea570d 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/supplier/back/methods/supplier/getSummary.js b/modules/supplier/back/methods/supplier/getSummary.js index 24a592acf3..e6509eaa24 100644 --- a/modules/supplier/back/methods/supplier/getSummary.js +++ b/modules/supplier/back/methods/supplier/getSummary.js @@ -25,7 +25,7 @@ module.exports = Self => { 'id', 'name', 'nickname', - 'isOfficial', + 'isSerious', 'isActive', 'note', 'nif', @@ -39,6 +39,9 @@ module.exports = Self => { 'payDay', 'account', 'isFarmer', + 'sageTaxTypeFk', + 'sageTransactionTypeFk', + 'sageWithholdingFk', ], include: [ { @@ -64,10 +67,27 @@ module.exports = Self => { scope: { fields: ['id', 'payDem'] } + }, + { + relation: 'sageTaxType', + scope: { + fields: ['id', 'vat'] + } + }, + { + relation: 'sageTransactionType', + scope: { + fields: ['id', 'transaction'] + } + }, + { + relation: 'sageWithholding', + scope: { + fields: ['id', 'withholding'] + } } ] }; - let supplier = await Self.app.models.Supplier.findOne(filter); return supplier; }; diff --git a/modules/supplier/back/models/supplier-contact.json b/modules/supplier/back/models/supplier-contact.json index accb2acd6b..104a3a4047 100644 --- a/modules/supplier/back/models/supplier-contact.json +++ b/modules/supplier/back/models/supplier-contact.json @@ -1,6 +1,10 @@ { "name": "SupplierContact", - "base": "VnModel", + "base": "Loggable", + "log": { + "model":"SupplierLog", + "relation": "supplier" + }, "options": { "mysql": { "table": "supplierContact" diff --git a/modules/supplier/back/models/supplier.json b/modules/supplier/back/models/supplier.json index 596aad745f..eb3c5989e6 100644 --- a/modules/supplier/back/models/supplier.json +++ b/modules/supplier/back/models/supplier.json @@ -51,6 +51,9 @@ "isOfficial": { "type": "Boolean" }, + "isSerious": { + "type": "Boolean" + }, "note": { "type": "String" }, @@ -123,7 +126,22 @@ "model": "Client", "foreignKey": "nif", "primaryKey": "fi" - } + }, + "sageTaxType": { + "type": "belongsTo", + "model": "SageTaxType", + "foreignKey": "sageTaxTypeFk" + }, + "sageTransactionType": { + "type": "belongsTo", + "model": "SageTransactionType", + "foreignKey": "sageTransactionTypeFk" + }, + "sageWithholding": { + "type": "belongsTo", + "model": "SageWithholding", + "foreignKey": "sageWithholdingFk" + } }, "acls": [ { diff --git a/modules/supplier/front/basic-data/index.html b/modules/supplier/front/basic-data/index.html index 03622cf389..a39f07da2f 100644 --- a/modules/supplier/front/basic-data/index.html +++ b/modules/supplier/front/basic-data/index.html @@ -18,8 +18,8 @@ + label="Verified" + ng-model="$ctrl.supplier.isSerious"> diff --git a/modules/supplier/front/descriptor/index.html b/modules/supplier/front/descriptor/index.html index 5e7225e1cb..c98a8e001f 100644 --- a/modules/supplier/front/descriptor/index.html +++ b/modules/supplier/front/descriptor/index.html @@ -29,9 +29,9 @@ ng-class="{bright: $ctrl.supplier.isActive == false}"> + vn-tooltip="Verified supplier" + icon="verified_user" + ng-class="{bright: $ctrl.supplier.isSerious == true}">