diff --git a/CHANGELOG.md b/CHANGELOG.md index 80d8517dea..1f35709327 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ 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). + ## [2334.01] - 2023-08-24 ### Added @@ -14,17 +15,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -## [2332.01] - 2023-08-09 +## [2332.01] - 2023-08-10 ### Added - (Trabajadores -> Gestión documental) Soporte para Docuware - (General -> Agencia) Soporte para Viaexpress +- (Tickets -> SMS) Nueva sección en Lilium ### Changed - (General -> Tickets) Devuelve el motivo por el cual no es editable - (Desplegables -> Trabajadores) Mejorados +- (General -> Clientes) Razón social y dirección en mayúsculas ### Fixed +- (Clientes -> SMS) Al pasar el ratón por encima muestra el mensaje completo ## [2330.01] - 2023-07-27 diff --git a/back/methods/vn-user/recover-password.js b/back/methods/vn-user/recover-password.js index b87bb14d46..b0f7122b43 100644 --- a/back/methods/vn-user/recover-password.js +++ b/back/methods/vn-user/recover-password.js @@ -7,6 +7,11 @@ module.exports = Self => { type: 'string', description: 'The user name or email', required: true + }, + { + arg: 'app', + type: 'string', + description: 'The directory for mail' } ], http: { @@ -15,7 +20,7 @@ module.exports = Self => { } }); - Self.recoverPassword = async function(user) { + Self.recoverPassword = async function(user, app) { const models = Self.app.models; const usesEmail = user.indexOf('@') !== -1; @@ -29,7 +34,7 @@ module.exports = Self => { } try { - await Self.resetPassword({email: user, emailTemplate: 'recover-password'}); + await Self.resetPassword({email: user, emailTemplate: 'recover-password', app}); } catch (err) { if (err.code === 'EMAIL_NOT_FOUND') return; diff --git a/back/methods/vn-user/sign-in.js b/back/methods/vn-user/sign-in.js index 73cc705de0..b9e0d2f705 100644 --- a/back/methods/vn-user/sign-in.js +++ b/back/methods/vn-user/sign-in.js @@ -53,19 +53,13 @@ module.exports = Self => { return Self.validateLogin(user, password); }; - Self.passExpired = async(vnUser, myOptions) => { + Self.passExpired = async vnUser => { const today = Date.vnNew(); today.setHours(0, 0, 0, 0); if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) { - const $ = Self.app.models; - const changePasswordToken = await $.AccessToken.create({ - scopes: ['changePassword'], - userId: vnUser.id - }, myOptions); const err = new UserError('Pass expired', 'passExpired'); - changePasswordToken.twoFactor = vnUser.twoFactor ? true : false; - err.details = {token: changePasswordToken}; + err.details = {userId: vnUser.id, twoFactor: vnUser.twoFactor ? true : false}; throw err; } }; diff --git a/back/models/vn-user.js b/back/models/vn-user.js index 68a556d773..cf210b61b8 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -96,11 +96,21 @@ module.exports = function(Self) { const headers = httpRequest.headers; const origin = headers.origin; + const defaultHash = '/reset-password?access_token=$token$'; + const recoverHashes = { + hedera: 'verificationToken=$token$' + }; + + const app = info.options?.app; + let recoverHash = app ? recoverHashes[app] : defaultHash; + recoverHash = recoverHash.replace('$token$', info.accessToken.id); + const user = await Self.app.models.VnUser.findById(info.user.id); + const params = { recipient: info.email, lang: user.lang, - url: `${origin}/#!/reset-password?access_token=${info.accessToken.id}` + url: origin + '/#!' + recoverHash }; const options = Object.assign({}, info.options); diff --git a/db/changes/233202/00-client_create_upper.sql b/db/changes/233202/00-client_create_upper.sql new file mode 100644 index 0000000000..d5d7258a11 --- /dev/null +++ b/db/changes/233202/00-client_create_upper.sql @@ -0,0 +1,87 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`client_create`( + vFirstname VARCHAR(50), + vSurnames VARCHAR(50), + vFi VARCHAR(9), + vAddress TEXT, + vPostcode CHAR(5), + vCity VARCHAR(25), + vProvinceFk SMALLINT(5), + vCompanyFk SMALLINT(5), + vPhone VARCHAR(11), + vEmail VARCHAR(255), + vUserFk INT +) +BEGIN +/** + * Create new client + * + * @params vFirstname firstName + * @params vSurnames surnames + * @params vFi company code from accounting transactions + * @params vAddress address + * @params vPostcode postCode + * @params vCity city + * @params vProvinceFk province + * @params vCompanyFk company in which he has become a client + * @params vPhone telephone number + * @params vEmail email address + * @params vUserFk user id + */ + DECLARE vPayMethodFk INT; + DECLARE vDueDay INT; + DECLARE vDefaultCredit DECIMAL(10, 2); + DECLARE vIsTaxDataChecked TINYINT(1); + DECLARE vHasCoreVnl BOOLEAN; + DECLARE vMandateTypeFk INT; + + SELECT defaultPayMethodFk, + defaultDueDay, + defaultCredit, + defaultIsTaxDataChecked, + defaultHasCoreVnl, + defaultMandateTypeFk + INTO vPayMethodFk, + vDueDay, + vDefaultCredit, + vIsTaxDataChecked, + vHasCoreVnl, + vMandateTypeFk + FROM clientConfig; + + INSERT INTO `client` + SET id = vUserFk, + name = CONCAT(vFirstname, ' ', vSurnames), + street = vAddress, + fi = TRIM(vFi), + phone = vPhone, + email = vEmail, + provinceFk = vProvinceFk, + city = vCity, + postcode = vPostcode, + socialName = UPPER(CONCAT(vSurnames, ' ', vFirstname)), + payMethodFk = vPayMethodFk, + dueDay = vDueDay, + credit = vDefaultCredit, + isTaxDataChecked = vIsTaxDataChecked, + hasCoreVnl = vHasCoreVnl, + isEqualizated = FALSE + ON duplicate KEY UPDATE + payMethodFk = vPayMethodFk, + dueDay = vDueDay, + credit = vDefaultCredit, + isTaxDataChecked = vIsTaxDataChecked, + hasCoreVnl = vHasCoreVnl, + isActive = TRUE; + + INSERT INTO mandate (clientFk, companyFk, mandateTypeFk) + SELECT vUserFk, vCompanyFk, vMandateTypeFk + WHERE NOT EXISTS ( + SELECT id + FROM mandate + WHERE clientFk = vUserFk + AND companyFk = vCompanyFk + AND mandateTypeFk = vMandateTypeFk + ); +END$$ +DELIMITER ; diff --git a/db/changes/233202/00-ticketSmsACL.sql b/db/changes/233202/00-ticketSmsACL.sql new file mode 100644 index 0000000000..a25a876f86 --- /dev/null +++ b/db/changes/233202/00-ticketSmsACL.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('TicketSms', 'find', 'READ', 'ALLOW', 'ROLE', 'salesPerson'); diff --git a/db/changes/233401/00-setDeleted_acl.sql b/db/changes/233401/00-setDeleted_acl.sql new file mode 100644 index 0000000000..cdeff95228 --- /dev/null +++ b/db/changes/233401/00-setDeleted_acl.sql @@ -0,0 +1,6 @@ +UPDATE `salix`.`ACL` + SET principalId='salesPerson' + WHERE + model='Ticket' + AND property='setDeleted' + AND accessType='WRITE'; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 338a732256..7a572fb043 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -360,13 +360,13 @@ INSERT INTO `vn`.`contactChannel`(`id`, `name`) INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`,`clientTypeFk`, `hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`eypbc`, `businessTypeFk`) VALUES - (1101, 'Bruce Wayne', '84612325V', 'Batman', 'Alfred', '1007 Mountain Drive, Gotham', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), - (1102, 'Petter Parker', '87945234L', 'Spider man', 'Aunt May', '20 Ingram Street, Queens, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), + (1101, 'Bruce Wayne', '84612325V', 'BATMAN', 'Alfred', '1007 MOUNTAIN DRIVE, GOTHAM', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), + (1102, 'Petter Parker', '87945234L', 'SPIDER MAN', 'Aunt May', '20 INGRAM STREET, QUEENS, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), (1103, 'Clark Kent', '06815934E', 'Super man', 'lois lane', '344 Clinton Street, Apartament 3-D', 'Gotham', 46460, 1111111111, 222222222, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 0, 19, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), (1104, 'Tony Stark', '06089160W', 'Iron man', 'Pepper Potts', '10880 Malibu Point, 90265', 'Gotham', 46460, 1111111111, 222222222, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), (1105, 'Max Eisenhardt', '251628698', 'Magneto', 'Rogue', 'Unknown Whereabouts', 'Gotham', 46460, 1111111111, 222222222, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 300, 8, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1, 'florist'), - (1106, 'DavidCharlesHaller', '53136686Q', 'Legion', 'Charles Xavier', 'City of New York, New York, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1, 'florist'), - (1107, 'Hank Pym', '09854837G', 'Ant man', 'Hawk', 'Anthill, San Francisco, California', 'Gotham', 46460, 1111111111, 222222222, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1, 'florist'), + (1106, 'DavidCharlesHaller', '53136686Q', 'LEGION', 'Charles Xavier', 'CITY OF NEW YORK, NEW YORK, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1, 'florist'), + (1107, 'Hank Pym', '09854837G', 'ANT MAN', 'Hawk', 'ANTHILL, SAN FRANCISCO, CALIFORNIA', 'Gotham', 46460, 1111111111, 222222222, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1, 'florist'), (1108, 'Charles Xavier', '22641921P', 'Professor X', 'Beast', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist'), (1109, 'Bruce Banner', '16104829E', 'Hulk', 'Black widow', 'Somewhere in New York', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1, 'florist'), (1110, 'Jessica Jones', '58282869H', 'Jessica Jones', 'Luke Cage', 'NYCC 2015 Poster', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1, 'florist'), diff --git a/e2e/paths/02-client/01_create_client.spec.js b/e2e/paths/02-client/01_create_client.spec.js index 0fd06402ac..744b11a72f 100644 --- a/e2e/paths/02-client/01_create_client.spec.js +++ b/e2e/paths/02-client/01_create_client.spec.js @@ -73,8 +73,8 @@ describe('Client create path', () => { it(`should attempt to create a new user with all it's data but wrong email`, async() => { await page.write(selectors.createClientView.name, 'Carol Danvers'); - await page.write(selectors.createClientView.socialName, 'AVG tax'); - await page.write(selectors.createClientView.street, 'Many places'); + await page.write(selectors.createClientView.socialName, 'AVG TAX'); + await page.write(selectors.createClientView.street, 'MANY PLACES'); await page.clearInput(selectors.createClientView.email); await page.write(selectors.createClientView.email, 'incorrect email format'); await page.waitToClick(selectors.createClientView.createButton); diff --git a/e2e/paths/02-client/03_edit_fiscal_data.spec.js b/e2e/paths/02-client/03_edit_fiscal_data.spec.js index 0babb5396d..9098e816ff 100644 --- a/e2e/paths/02-client/03_edit_fiscal_data.spec.js +++ b/e2e/paths/02-client/03_edit_fiscal_data.spec.js @@ -61,7 +61,7 @@ describe('Client Edit fiscalData path', () => { await page.clearInput(selectors.clientFiscalData.fiscalId); await page.write(selectors.clientFiscalData.fiscalId, 'INVALID!'); await page.clearInput(selectors.clientFiscalData.address); - await page.write(selectors.clientFiscalData.address, 'Somewhere edited'); + await page.write(selectors.clientFiscalData.address, 'SOMEWHERE EDITED'); await page.autocompleteSearch(selectors.clientFiscalData.country, 'España'); await page.autocompleteSearch(selectors.clientFiscalData.province, 'Province one'); await page.clearInput(selectors.clientFiscalData.city); @@ -190,7 +190,7 @@ describe('Client Edit fiscalData path', () => { const verifiedData = await page.checkboxState(selectors.clientFiscalData.verifiedDataCheckbox); expect(fiscalId).toEqual('94980061C'); - expect(address).toEqual('Somewhere edited'); + expect(address).toEqual('SOMEWHERE EDITED'); expect(postcode).toContain('46000'); expect(sageTax).toEqual('Operaciones no sujetas'); expect(sageTransaction).toEqual('Regularización de inversiones'); diff --git a/e2e/paths/02-client/12_lock_of_verified_data.spec.js b/e2e/paths/02-client/12_lock_of_verified_data.spec.js index 139af0cea5..2d7ab75418 100644 --- a/e2e/paths/02-client/12_lock_of_verified_data.spec.js +++ b/e2e/paths/02-client/12_lock_of_verified_data.spec.js @@ -28,7 +28,7 @@ describe('Client lock verified data path', () => { it('should edit the social name', async() => { await page.waitForSelector(selectors.clientFiscalData.socialName); await page.clearInput(selectors.clientFiscalData.socialName); - await page.write(selectors.clientFiscalData.socialName, 'Captain America Civil War'); + await page.write(selectors.clientFiscalData.socialName, 'CAPTAIN AMERICA CIVIL WAR'); await page.waitToClick(selectors.clientFiscalData.saveButton); const message = await page.waitForSnackbar(); @@ -39,7 +39,7 @@ describe('Client lock verified data path', () => { await page.reloadSection('client.card.fiscalData'); const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value'); - expect(result).toEqual('Captain America Civil War'); + expect(result).toEqual('CAPTAIN AMERICA CIVIL WAR'); }); }); @@ -88,7 +88,7 @@ describe('Client lock verified data path', () => { await page.reloadSection('client.card.fiscalData'); const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value'); - expect(result).toEqual('Ant man and the Wasp'); + expect(result).toEqual('ANT MAN AND THE WASP'); }); }); @@ -142,7 +142,7 @@ describe('Client lock verified data path', () => { await page.reloadSection('client.card.fiscalData'); const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value'); - expect(result).toEqual('new social name edition'); + expect(result).toEqual('NEW SOCIAL NAME EDITION'); }); }); diff --git a/e2e/paths/02-client/19_summary.spec.js b/e2e/paths/02-client/19_summary.spec.js index ab39154cfd..b3bf43c5c1 100644 --- a/e2e/paths/02-client/19_summary.spec.js +++ b/e2e/paths/02-client/19_summary.spec.js @@ -36,7 +36,7 @@ describe('Client summary path', () => { it('should display fiscal address details', async() => { const result = await page.waitToGetProperty(selectors.clientSummary.street, 'innerText'); - expect(result).toContain('20 Ingram Street'); + expect(result).toContain('20 INGRAM STREET'); }); it('should display some fiscal data', async() => { diff --git a/e2e/paths/03-worker/06_create.spec.js b/e2e/paths/03-worker/06_create.spec.js index 5440253ccb..2accdfc310 100644 --- a/e2e/paths/03-worker/06_create.spec.js +++ b/e2e/paths/03-worker/06_create.spec.js @@ -23,7 +23,7 @@ describe('Worker create path', () => { await page.write(selectors.workerCreate.fi, '78457139E'); await page.write(selectors.workerCreate.phone, '12356789'); await page.write(selectors.workerCreate.postcode, '46680'); - await page.write(selectors.workerCreate.street, 'S/ Doomstadt'); + await page.write(selectors.workerCreate.street, 'S/ DOOMSTADT'); await page.write(selectors.workerCreate.email, 'doctorDoom@marvel.com'); await page.write(selectors.workerCreate.iban, 'ES9121000418450200051332'); diff --git a/e2e/paths/05-ticket/14_create_ticket.spec.js b/e2e/paths/05-ticket/14_create_ticket.spec.js index 80c288a01b..1f9c0c40a3 100644 --- a/e2e/paths/05-ticket/14_create_ticket.spec.js +++ b/e2e/paths/05-ticket/14_create_ticket.spec.js @@ -10,7 +10,7 @@ describe('Ticket create path', () => { beforeAll(async() => { browser = await getBrowser(); page = browser.page; - await page.loginAndModule('employee', 'ticket'); + await page.loginAndModule('salesPerson', 'ticket'); }); afterAll(async() => { diff --git a/front/salix/components/change-password/index.js b/front/salix/components/change-password/index.js index 797d4dc963..7e30bf54ed 100644 --- a/front/salix/components/change-password/index.js +++ b/front/salix/components/change-password/index.js @@ -15,9 +15,6 @@ export default class Controller { } $onInit() { - if (!this.$state.params.id) - this.$state.go('login'); - this.$http.get('UserPasswords/findOne') .then(res => { this.passRequirements = res.data; @@ -25,7 +22,7 @@ export default class Controller { } submit() { - const userId = this.$state.params.userId; + const userId = parseInt(this.$state.params.userId); const oldPassword = this.oldPassword; const newPassword = this.newPassword; const repeatPassword = this.repeatPassword; @@ -36,18 +33,13 @@ export default class Controller { if (newPassword != this.repeatPassword) throw new UserError(`Passwords don't match`); - const headers = { - Authorization: this.$state.params.id - }; - this.$http.patch('Accounts/change-password', { - id: userId, + userId, oldPassword, newPassword, code - }, - {headers} + } ).then(() => { this.vnApp.showSuccess(this.$translate.instant('Password updated!')); this.$state.go('login'); diff --git a/front/salix/components/login/index.js b/front/salix/components/login/index.js index be4fb39267..7d8cd20493 100644 --- a/front/salix/components/login/index.js +++ b/front/salix/components/login/index.js @@ -36,7 +36,7 @@ export default class Controller { const err = req.data?.error; if (err?.code == 'passExpired') - this.$state.go('change-password', err.details.token); + this.$state.go('change-password', err.details); this.loading = false; this.password = ''; diff --git a/front/salix/routes.js b/front/salix/routes.js index 5a2c030bc1..8621f83c76 100644 --- a/front/salix/routes.js +++ b/front/salix/routes.js @@ -45,7 +45,7 @@ function config($stateProvider, $urlRouterProvider) { }) .state('change-password', { parent: 'outLayout', - url: '/change-password?id&userId&twoFactor', + url: '/change-password?userId&twoFactor', description: 'Change password', template: '' }) diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 135bb2be9b..46b48e2eae 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -179,6 +179,9 @@ "You can not use the same password": "You can not use the same password", "Valid priorities": "Valid priorities: %d", "Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}", + "This ticket cannot be left empty.": "This ticket cannot be left empty. %s", + "Social name should be uppercase": "Social name should be uppercase", + "Street should be uppercase": "Street should be uppercase", "You don't have enough privileges.": "You don't have enough privileges.", "This ticket is locked.": "This ticket is locked.", "This ticket is not editable.": "This ticket is not editable.", diff --git a/loopback/locale/es.json b/loopback/locale/es.json index ac62d62e13..195b683ad3 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -305,11 +305,15 @@ "The renew period has not been exceeded": "El periodo de renovación no ha sido superado", "Valid priorities": "Prioridades válidas: %d", "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", - "The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias", + "You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado", + "This ticket cannot be left empty.": "Este ticket no se puede dejar vacío. %s", + "The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias", "You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado", "This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado", "You don't have enough privileges.": "No tienes suficientes permisos.", "This ticket is locked.": "Este ticket está bloqueado.", "This ticket is not editable.": "Este ticket no es editable.", - "The ticket doesn't exist.": "No existe el ticket." -} \ No newline at end of file + "The ticket doesn't exist.": "No existe el ticket.", + "Social name should be uppercase": "La razón social debe ir en mayúscula", + "Street should be uppercase": "La dirección fiscal debe ir en mayúscula" +} diff --git a/modules/account/back/methods/account/change-password.js b/modules/account/back/methods/account/change-password.js index a739f37d00..49af93110c 100644 --- a/modules/account/back/methods/account/change-password.js +++ b/modules/account/back/methods/account/change-password.js @@ -1,12 +1,15 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { - Self.remoteMethodCtx('changePassword', { + Self.remoteMethod('changePassword', { description: 'Changes the user password', - accessType: 'WRITE', - accessScopes: ['changePassword'], accepts: [ { + arg: 'userId', + type: 'integer', + description: 'The user id', + required: true + }, { arg: 'oldPassword', type: 'string', description: 'The old password', @@ -28,9 +31,7 @@ module.exports = Self => { } }); - Self.changePassword = async function(ctx, oldPassword, newPassword, code, options) { - const userId = ctx.req.accessToken.userId; - + Self.changePassword = async function(userId, oldPassword, newPassword, code, options) { const myOptions = {}; if (typeof options == 'object') Object.assign(myOptions, options); diff --git a/modules/account/back/methods/account/specs/change-password.spec.js b/modules/account/back/methods/account/specs/change-password.spec.js index 045f984931..2fa3010afb 100644 --- a/modules/account/back/methods/account/specs/change-password.spec.js +++ b/modules/account/back/methods/account/specs/change-password.spec.js @@ -1,7 +1,7 @@ const {models} = require('vn-loopback/server/server'); describe('account changePassword()', () => { - const ctx = {req: {accessToken: {userId: 70}}}; + const userId = 70; const unauthCtx = { req: { headers: {}, @@ -20,7 +20,7 @@ describe('account changePassword()', () => { try { const options = {transaction: tx}; - await models.Account.changePassword(ctx, 'wrongPassword', 'nightmare.9999', null, options); + await models.Account.changePassword(userId, 'wrongPassword', 'nightmare.9999', null, options); await tx.rollback(); } catch (e) { await tx.rollback(); @@ -37,8 +37,8 @@ describe('account changePassword()', () => { try { const options = {transaction: tx}; - await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', null, options); - await models.Account.changePassword(ctx, 'nightmare.9999', 'nightmare.9999', null, options); + await models.Account.changePassword(userId, 'nightmare', 'nightmare.9999', null, options); + await models.Account.changePassword(userId, 'nightmare.9999', 'nightmare.9999', null, options); await tx.rollback(); } catch (e) { await tx.rollback(); @@ -54,7 +54,7 @@ describe('account changePassword()', () => { try { const options = {transaction: tx}; - await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', null, options); + await models.Account.changePassword(userId, 'nightmare', 'nightmare.9999', null, options); await tx.rollback(); } catch (e) { await tx.rollback(); @@ -86,8 +86,8 @@ describe('account changePassword()', () => { } try { - const authCode = await models.AuthCode.findOne({where: {userFk: 70}}, options); - await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', authCode.code, options); + const authCode = await models.AuthCode.findOne({where: {userFk: userId}}, options); + await models.Account.changePassword(userId, 'nightmare', 'nightmare.9999', authCode.code, options); await tx.rollback(); } catch (e) { await tx.rollback(); diff --git a/modules/client/back/methods/client/specs/createWithUser.spec.js b/modules/client/back/methods/client/specs/createWithUser.spec.js index 69440c1d1c..03106acc11 100644 --- a/modules/client/back/methods/client/specs/createWithUser.spec.js +++ b/modules/client/back/methods/client/specs/createWithUser.spec.js @@ -7,8 +7,8 @@ describe('Client Create', () => { email: 'Deadpool@marvel.com', fi: '16195279J', name: 'Wade', - socialName: 'Deadpool Marvel', - street: 'Wall Street', + socialName: 'DEADPOOL MARVEL', + street: 'WALL STREET', city: 'New York', businessTypeFk: 'florist', provinceFk: 1 diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index 8369fa9061..129924b784 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -36,6 +36,20 @@ module.exports = Self => { min: 3, max: 10 }); + Self.validatesFormatOf('street', { + message: 'Street should be uppercase', + allowNull: false, + allowBlank: false, + with: /^[^a-z]*$/ + }); + + Self.validatesFormatOf('socialName', { + message: 'Social name should be uppercase', + allowNull: false, + allowBlank: false, + with: /^[^a-z]*$/ + }); + Self.validateAsync('socialName', socialNameIsUnique, { message: 'The company name must be unique' }); diff --git a/modules/client/front/fiscal-data/index.html b/modules/client/front/fiscal-data/index.html index 18127db6a0..c2bf86f70d 100644 --- a/modules/client/front/fiscal-data/index.html +++ b/modules/client/front/fiscal-data/index.html @@ -33,6 +33,7 @@ ng-model="$ctrl.client.socialName" info="Only letters, numbers and spaces can be used" required="true" + ng-keyup="$ctrl.client.socialName = $ctrl.client.socialName.toUpperCase()" rule> diff --git a/modules/client/front/sms/index.html b/modules/client/front/sms/index.html index 9abadd3128..e2bc0785e3 100644 --- a/modules/client/front/sms/index.html +++ b/modules/client/front/sms/index.html @@ -8,7 +8,7 @@ auto-load="true"> - + @@ -27,7 +27,7 @@ {{::clientSms.sms.destination}} - {{::clientSms.sms.message}} + {{::clientSms.sms.message}} {{::clientSms.sms.status}} {{::clientSms.sms.created | date:'dd/MM/yyyy HH:mm'}} diff --git a/modules/item/front/fetched-tags/index.html b/modules/item/front/fetched-tags/index.html index 472fa676b7..df5936871c 100644 --- a/modules/item/front/fetched-tags/index.html +++ b/modules/item/front/fetched-tags/index.html @@ -1,40 +1,40 @@ - -
+
{{::$ctrl.item.value5}}
-
{{::$ctrl.item.value6}}
-
{{::$ctrl.item.value7}}
-
{{::$ctrl.item.value8}}
-
{{::$ctrl.item.value9}}
-
{{::$ctrl.item.value10}}
- \ No newline at end of file + diff --git a/modules/item/front/fetched-tags/style.scss b/modules/item/front/fetched-tags/style.scss index 2cd7afbb21..250ca07abf 100644 --- a/modules/item/front/fetched-tags/style.scss +++ b/modules/item/front/fetched-tags/style.scss @@ -28,7 +28,7 @@ vn-fetched-tags { & > vn-horizontal { align-items: center; - + max-width: 210px; & > vn-auto { flex-wrap: wrap; @@ -43,19 +43,19 @@ vn-fetched-tags { & > .inline-tag { color: $color-font-secondary; text-align: center; - font-size: .75rem; - height: 12px; + font-size: .8rem; + height: 13px; padding: 1px; width: 64px; min-width: 64px; max-width: 64px; flex: 1; - border: 1px solid $color-spacer; - + border: 1px solid $color-font-secondary; + &.empty { - border: 1px solid $color-spacer-light; + border: 1px solid darken($color-font-secondary, 30%); } } } } -} \ No newline at end of file +} diff --git a/modules/route/back/methods/route/cmr.js b/modules/route/back/methods/route/cmr.js new file mode 100644 index 0000000000..cd7ef57cee --- /dev/null +++ b/modules/route/back/methods/route/cmr.js @@ -0,0 +1,36 @@ +module.exports = Self => { + Self.remoteMethodCtx('cmr', { + description: 'Returns the cmr', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'The cmr 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: '/:id/cmr', + verb: 'GET' + } + }); + + Self.cmr = (ctx, id) => Self.printReport(ctx, id, 'cmr'); +}; diff --git a/modules/route/back/methods/route/getExternalCmrs.js b/modules/route/back/methods/route/getExternalCmrs.js new file mode 100644 index 0000000000..5b08cf34ae --- /dev/null +++ b/modules/route/back/methods/route/getExternalCmrs.js @@ -0,0 +1,133 @@ +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.remoteMethod('getExternalCmrs', { + description: 'Returns an array of external cmrs', + accessType: 'READ', + accepts: [ + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', + }, + { + arg: 'cmrFk', + type: 'integer', + description: 'Searchs the route by id', + }, + { + arg: 'ticketFk', + type: 'integer', + description: 'The worker id', + }, + { + arg: 'country', + type: 'string', + description: 'The agencyMode id', + }, + { + arg: 'clientFk', + type: 'integer', + description: 'The vehicle id', + }, + { + arg: 'hasCmrDms', + type: 'boolean', + description: 'The vehicle id', + }, + { + arg: 'shipped', + type: 'date', + description: 'The to date filter', + }, + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/getExternalCmrs`, + verb: 'GET' + } + }); + + Self.getExternalCmrs = async( + filter, + cmrFk, + ticketFk, + country, + clientFk, + hasCmrDms, + shipped, + options + ) => { + const params = { + cmrFk, + ticketFk, + country, + clientFk, + hasCmrDms, + shipped, + }; + const conn = Self.dataSource.connector; + + let where = buildFilter(params, (param, value) => {return {[param]: value}}); + filter = mergeFilters(filter, {where}); + + if (!filter.where) { + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + filter.where = {'shipped': yesterday.toISOString().split('T')[0]} + } + + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + let stmts = []; + const stmt = new ParameterizedSQL(` + SELECT * + FROM ( + SELECT t.cmrFk, + t.id ticketFk, + co.country, + t.clientFk, + sub.id hasCmrDms, + DATE(t.shipped) shipped + FROM ticket t + JOIN ticketState ts ON ts.ticketFk = t.id + JOIN state s ON s.id = ts.stateFk + JOIN alertLevel al ON al.id = s.alertLevel + JOIN client c ON c.id = t.clientFk + JOIN address a ON a.id = t.addressFk + JOIN province p ON p.id = a.provinceFk + JOIN country co ON co.id = p.countryFk + JOIN agencyMode am ON am.id = t.agencyModeFk + JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk + JOIN warehouse w ON w.id = t.warehouseFk + LEFT JOIN ( + SELECT td.ticketFk, d.id + FROM ticketDms td + JOIN dms d ON d.id = td.dmsFk + JOIN dmsType dt ON dt.id = d.dmsTypeFk + WHERE dt.name = 'cmr' + ) sub ON sub.ticketFk = t.id + WHERE co.code <> 'ES' + AND am.name <> 'ABONO' + AND w.code = 'ALG' + AND dm.code = 'DELIVERY' + AND t.cmrFk + ) sub + `); + + stmt.merge(conn.makeSuffix(filter)); + const itemsIndex = stmts.push(stmt) - 1; + + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql); + return itemsIndex === 0 ? result : result[itemsIndex]; + }; +}; diff --git a/modules/route/back/models/route.js b/modules/route/back/models/route.js index 883f4597e4..7e61acf25f 100644 --- a/modules/route/back/models/route.js +++ b/modules/route/back/models/route.js @@ -14,6 +14,8 @@ module.exports = Self => { require('../methods/route/driverRouteEmail')(Self); require('../methods/route/sendSms')(Self); require('../methods/route/downloadZip')(Self); + require('../methods/route/cmr')(Self); + require('../methods/route/getExternalCmrs')(Self); Self.validate('kmStart', validateDistance, { message: 'Distance must be lesser than 1000' @@ -28,5 +30,5 @@ module.exports = Self => { const routeMaxKm = 1000; if (routeTotalKm > routeMaxKm || this.kmStart > this.kmEnd) err(); - } + }; }; diff --git a/modules/route/front/roadmap/index/index.js b/modules/route/front/roadmap/index/index.js index 3ffc5b4b1b..c5f5ef9d1c 100644 --- a/modules/route/front/roadmap/index/index.js +++ b/modules/route/front/roadmap/index/index.js @@ -46,8 +46,6 @@ class Controller extends Section { } deleteRoadmaps() { - console.log(this.checked); - for (const roadmap of this.checked) { this.$http.delete(`Roadmaps/${roadmap.id}`) .then(() => this.$.model.refresh()) diff --git a/modules/ticket/back/methods/ticket/closure.js b/modules/ticket/back/methods/ticket/closure.js index eee5e28e26..9f9aec9bd1 100644 --- a/modules/ticket/back/methods/ticket/closure.js +++ b/modules/ticket/back/methods/ticket/closure.js @@ -5,177 +5,177 @@ const config = require('vn-print/core/config'); const storage = require('vn-print/core/storage'); module.exports = async function(ctx, Self, tickets, reqArgs = {}) { - const userId = ctx.req.accessToken.userId; - if (tickets.length == 0) return; + const userId = ctx.req.accessToken.userId; + if (tickets.length == 0) return; - const failedtickets = []; - for (const ticket of tickets) { - try { - await Self.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id], {userId}); + const failedtickets = []; + for (const ticket of tickets) { + try { + await Self.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id], {userId}); - const [invoiceOut] = await Self.rawSql(` - SELECT io.id, io.ref, io.serial, cny.code companyCode, io.issued - FROM ticket t - JOIN invoiceOut io ON io.ref = t.refFk - JOIN company cny ON cny.id = io.companyFk - WHERE t.id = ? - `, [ticket.id]); + const [invoiceOut] = await Self.rawSql(` + SELECT io.id, io.ref, io.serial, cny.code companyCode, io.issued + FROM ticket t + JOIN invoiceOut io ON io.ref = t.refFk + JOIN company cny ON cny.id = io.companyFk + WHERE t.id = ? + `, [ticket.id]); - const mailOptions = { - overrideAttachments: true, - attachments: [] - }; + const mailOptions = { + overrideAttachments: true, + attachments: [] + }; - const isToBeMailed = ticket.recipient && ticket.salesPersonFk && ticket.isToBeMailed; + const isToBeMailed = ticket.recipient && ticket.salesPersonFk && ticket.isToBeMailed; - if (invoiceOut) { - const args = { - reference: invoiceOut.ref, - recipientId: ticket.clientFk, - recipient: ticket.recipient, - replyTo: ticket.salesPersonEmail - }; + if (invoiceOut) { + const args = { + reference: invoiceOut.ref, + recipientId: ticket.clientFk, + recipient: ticket.recipient, + replyTo: ticket.salesPersonEmail + }; - const invoiceReport = new Report('invoice', args); - const stream = await invoiceReport.toPdfStream(); + const invoiceReport = new Report('invoice', args); + const stream = await invoiceReport.toPdfStream(); - const issued = invoiceOut.issued; - const year = issued.getFullYear().toString(); - const month = (issued.getMonth() + 1).toString(); - const day = issued.getDate().toString(); + const issued = invoiceOut.issued; + const year = issued.getFullYear().toString(); + const month = (issued.getMonth() + 1).toString(); + const day = issued.getDate().toString(); - const fileName = `${year}${invoiceOut.ref}.pdf`; + const fileName = `${year}${invoiceOut.ref}.pdf`; - // Store invoice - await storage.write(stream, { - type: 'invoice', - path: `${year}/${month}/${day}`, - fileName: fileName - }); + // Store invoice + await storage.write(stream, { + type: 'invoice', + path: `${year}/${month}/${day}`, + fileName: fileName + }); - await Self.rawSql('UPDATE invoiceOut SET hasPdf = true WHERE id = ?', [invoiceOut.id], {userId}); + await Self.rawSql('UPDATE invoiceOut SET hasPdf = true WHERE id = ?', [invoiceOut.id], {userId}); - if (isToBeMailed) { - const invoiceAttachment = { - filename: fileName, - content: stream - }; + if (isToBeMailed) { + const invoiceAttachment = { + filename: fileName, + content: stream + }; - if (invoiceOut.serial == 'E' && invoiceOut.companyCode == 'VNL') { - const exportation = new Report('exportation', args); - const stream = await exportation.toPdfStream(); - const fileName = `CITES-${invoiceOut.ref}.pdf`; + if (invoiceOut.serial == 'E' && invoiceOut.companyCode == 'VNL') { + const exportation = new Report('exportation', args); + const stream = await exportation.toPdfStream(); + const fileName = `CITES-${invoiceOut.ref}.pdf`; - mailOptions.attachments.push({ - filename: fileName, - content: stream - }); - } + mailOptions.attachments.push({ + filename: fileName, + content: stream + }); + } - mailOptions.attachments.push(invoiceAttachment); + mailOptions.attachments.push(invoiceAttachment); - const email = new Email('invoice', args); - await email.send(mailOptions); - } - } else if (isToBeMailed) { - const args = { - id: ticket.id, - recipientId: ticket.clientFk, - recipient: ticket.recipient, - replyTo: ticket.salesPersonEmail - }; + const email = new Email('invoice', args); + await email.send(mailOptions); + } + } else if (isToBeMailed) { + const args = { + id: ticket.id, + recipientId: ticket.clientFk, + recipient: ticket.recipient, + replyTo: ticket.salesPersonEmail + }; - const email = new Email('delivery-note-link', args); - await email.send(); - } + const email = new Email('delivery-note-link', args); + await email.send(); + } - // Incoterms authorization - const [{firstOrder}] = await Self.rawSql(` - SELECT COUNT(*) as firstOrder - FROM ticket t - JOIN client c ON c.id = t.clientFk - WHERE t.clientFk = ? - AND NOT t.isDeleted - AND c.isVies - `, [ticket.clientFk]); + // Incoterms authorization + const [{firstOrder}] = await Self.rawSql(` + SELECT COUNT(*) as firstOrder + FROM ticket t + JOIN client c ON c.id = t.clientFk + WHERE t.clientFk = ? + AND NOT t.isDeleted + AND c.isVies + `, [ticket.clientFk]); - if (firstOrder == 1) { - const args = { - id: ticket.clientFk, - companyId: ticket.companyFk, - recipientId: ticket.clientFk, - recipient: ticket.recipient, - replyTo: ticket.salesPersonEmail - }; + if (firstOrder == 1) { + const args = { + id: ticket.clientFk, + companyId: ticket.companyFk, + recipientId: ticket.clientFk, + recipient: ticket.recipient, + replyTo: ticket.salesPersonEmail + }; - const email = new Email('incoterms-authorization', args); - await email.send(); + const email = new Email('incoterms-authorization', args); + await email.send(); - const [sample] = await Self.rawSql( - `SELECT id - FROM sample - WHERE code = 'incoterms-authorization' - `); + const [sample] = await Self.rawSql( + `SELECT id + FROM sample + WHERE code = 'incoterms-authorization' + `); - await Self.rawSql(` - INSERT INTO clientSample (clientFk, typeFk, companyFk) VALUES(?, ?, ?) - `, [ticket.clientFk, sample.id, ticket.companyFk], {userId}); - } - } catch (error) { - // Domain not found - if (error.responseCode == 450) - return invalidEmail(ticket); + await Self.rawSql(` + INSERT INTO clientSample (clientFk, typeFk, companyFk) VALUES(?, ?, ?) + `, [ticket.clientFk, sample.id, ticket.companyFk], {userId}); + }; + } catch (error) { + // Domain not found + if (error.responseCode == 450) + return invalidEmail(ticket); - // Save tickets on a list of failed ids - failedtickets.push({ - id: ticket.id, - stacktrace: error - }); - } - } + // Save tickets on a list of failed ids + failedtickets.push({ + id: ticket.id, + stacktrace: error + }); + } + } - // Send email with failed tickets - if (failedtickets.length > 0) { - let body = 'This following tickets have failed:

'; + // Send email with failed tickets + if (failedtickets.length > 0) { + let body = 'This following tickets have failed:

'; - for (const ticket of failedtickets) { - body += `Ticket: ${ticket.id} -
${ticket.stacktrace}

`; - } + for (const ticket of failedtickets) { + body += `Ticket: ${ticket.id} +
${ticket.stacktrace}

`; + } - smtp.send({ - to: config.app.reportEmail, - subject: '[API] Nightly ticket closure report', - html: body - }); - } + smtp.send({ + to: config.app.reportEmail, + subject: '[API] Nightly ticket closure report', + html: body + }); + } - async function invalidEmail(ticket) { - await Self.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [ - ticket.clientFk - ], {userId}); + async function invalidEmail(ticket) { + await Self.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [ + ticket.clientFk + ], {userId}); - const oldInstance = `{"email": "${ticket.recipient}"}`; - const newInstance = `{"email": ""}`; - await Self.rawSql(` - INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance) - VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [ - ticket.clientFk, - oldInstance, - newInstance - ], {userId}); + const oldInstance = `{"email": "${ticket.recipient}"}`; + const newInstance = `{"email": ""}`; + await Self.rawSql(` + INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance) + VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [ + ticket.clientFk, + oldInstance, + newInstance + ], {userId}); - const body = `No se ha podido enviar el albarán ${ticket.id} - al cliente ${ticket.clientFk} - ${ticket.clientName} - porque la dirección de email "${ticket.recipient}" no es correcta - o no está disponible.

- Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente. - Actualiza la dirección de email con una correcta.`; + const body = `No se ha podido enviar el albarán ${ticket.id} + al cliente ${ticket.clientFk} - ${ticket.clientName} + porque la dirección de email "${ticket.recipient}" no es correcta + o no está disponible.

+ Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente. + Actualiza la dirección de email con una correcta.`; - smtp.send({ - to: ticket.salesPersonEmail, - subject: 'No se ha podido enviar el albarán', - html: body - }); - } + smtp.send({ + to: ticket.salesPersonEmail, + subject: 'No se ha podido enviar el albarán', + html: body + }); + } }; diff --git a/modules/ticket/back/methods/ticket/componentUpdate.js b/modules/ticket/back/methods/ticket/componentUpdate.js index b5ff50d59f..8aad8959bb 100644 --- a/modules/ticket/back/methods/ticket/componentUpdate.js +++ b/modules/ticket/back/methods/ticket/componentUpdate.js @@ -1,4 +1,5 @@ const loggable = require('vn-loopback/util/log'); +const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('componentUpdate', { @@ -112,7 +113,6 @@ module.exports = Self => { } try { - const userId = ctx.req.accessToken.userId; const models = Self.app.models; const $t = ctx.req.__; // $translate await models.Ticket.isEditableOrThrow(ctx, args.id, myOptions); @@ -127,11 +127,8 @@ module.exports = Self => { args.warehouseFk, myOptions); - if (!zoneShipped || zoneShipped.zoneFk != args.zoneFk) { - const error = `You don't have privileges to change the zone`; - - throw new UserError(error); - } + if (!zoneShipped || zoneShipped.zoneFk != args.zoneFk) + throw new UserError(`You don't have privileges to change the zone`); } if (args.isWithoutNegatives) { diff --git a/modules/ticket/back/methods/ticket/filter.js b/modules/ticket/back/methods/ticket/filter.js index 262b3fd743..eb3da39aff 100644 --- a/modules/ticket/back/methods/ticket/filter.js +++ b/modules/ticket/back/methods/ticket/filter.js @@ -248,6 +248,7 @@ module.exports = Self => { am.name AS agencyMode, am.id AS agencyModeFk, st.name AS state, + st.classColor, wk.lastName AS salesPerson, ts.stateFk AS stateFk, ts.alertLevel AS alertLevel, @@ -339,7 +340,8 @@ module.exports = Self => { {'tp.isFreezed': hasProblem}, {'tp.risk': hasProblem}, {'tp.hasTicketRequest': hasProblem}, - {'tp.itemShortage': range} + {'tp.itemShortage': range}, + {'tp.hasRounding': hasProblem} ]}; if (hasWhere) diff --git a/modules/ticket/back/methods/ticket/getTicketsFuture.js b/modules/ticket/back/methods/ticket/getTicketsFuture.js index 901e546f75..fa24cc379d 100644 --- a/modules/ticket/back/methods/ticket/getTicketsFuture.js +++ b/modules/ticket/back/methods/ticket/getTicketsFuture.js @@ -194,7 +194,8 @@ module.exports = Self => { {'tp.hasTicketRequest': hasProblem}, {'tp.itemShortage': range}, {'tp.hasComponentLack': hasProblem}, - {'tp.isTooLittle': hasProblem} + {'tp.isTooLittle': hasProblem}, + {'tp.hasRounding': hasProblem} ] }; diff --git a/modules/ticket/back/methods/ticket/specs/transferSales.spec.js b/modules/ticket/back/methods/ticket/specs/transferSales.spec.js index ef92e88c0f..69829fe668 100644 --- a/modules/ticket/back/methods/ticket/specs/transferSales.spec.js +++ b/modules/ticket/back/methods/ticket/specs/transferSales.spec.js @@ -5,6 +5,8 @@ describe('sale transferSales()', () => { const userId = 1101; const activeCtx = { accessToken: {userId: userId}, + headers: {origin: ''}, + __: value => value }; const ctx = {req: activeCtx}; diff --git a/modules/ticket/back/methods/ticket/transferSales.js b/modules/ticket/back/methods/ticket/transferSales.js index a48c5683ca..1124f7ec70 100644 --- a/modules/ticket/back/methods/ticket/transferSales.js +++ b/modules/ticket/back/methods/ticket/transferSales.js @@ -37,6 +37,7 @@ module.exports = Self => { const userId = ctx.req.accessToken.userId; const models = Self.app.models; const myOptions = {userId}; + const $t = ctx.req.__; // $translate let tx; if (typeof options == 'object') @@ -95,9 +96,18 @@ module.exports = Self => { const isTicketEmpty = await models.Ticket.isEmpty(id, myOptions); if (isTicketEmpty) { - await originalTicket.updateAttributes({ - isDeleted: true - }, myOptions); + try { + await models.Ticket.setDeleted(ctx, id, myOptions); + } catch (e) { + if (e.statusCode === 400) { + throw new UserError( + `This ticket cannot be left empty.`, + 'TRANSFER_SET_DELETED', + $t(e.message, ...e.translateArgs) + ); + } + throw e; + } } if (tx) await tx.commit(); diff --git a/modules/ticket/back/models/state.json b/modules/ticket/back/models/state.json index 9d4dd4f5d6..8b7eb69c69 100644 --- a/modules/ticket/back/models/state.json +++ b/modules/ticket/back/models/state.json @@ -27,6 +27,9 @@ "code": { "type": "string", "required": false + }, + "classColor": { + "type": "string" } } } diff --git a/modules/ticket/front/future/index.html b/modules/ticket/front/future/index.html index 78b0f98645..5119d103b5 100644 --- a/modules/ticket/front/future/index.html +++ b/modules/ticket/front/future/index.html @@ -123,6 +123,12 @@ class="bright" icon="icon-components"> + + + + {{::ticket.id}} diff --git a/modules/ticket/front/routes.json b/modules/ticket/front/routes.json index c86b3a1efc..e403ab02dc 100644 --- a/modules/ticket/front/routes.json +++ b/modules/ticket/front/routes.json @@ -26,7 +26,8 @@ {"state": "ticket.card.components", "icon": "icon-components"}, {"state": "ticket.card.saleTracking", "icon": "assignment"}, {"state": "ticket.card.dms.index", "icon": "cloud_download"}, - {"state": "ticket.card.boxing", "icon": "science"} + {"state": "ticket.card.boxing", "icon": "science"}, + {"state": "ticket.card.sms", "icon": "sms"} ] }, "keybindings": [ @@ -287,6 +288,15 @@ "state": "ticket.advance", "component": "vn-ticket-advance", "description": "Advance tickets" + }, + { + "url": "/sms", + "state": "ticket.card.sms", + "component": "vn-ticket-sms", + "description": "Sms", + "params": { + "ticket": "$ctrl.ticket" + } } ] } diff --git a/modules/ticket/front/sms/index.html b/modules/ticket/front/sms/index.html new file mode 100644 index 0000000000..7fb3b870e0 --- /dev/null +++ b/modules/ticket/front/sms/index.html @@ -0,0 +1,2 @@ + + diff --git a/modules/ticket/front/sms/index.js b/modules/ticket/front/sms/index.js new file mode 100644 index 0000000000..69d54aafe8 --- /dev/null +++ b/modules/ticket/front/sms/index.js @@ -0,0 +1,21 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +class Controller extends Section { + constructor($element, $) { + super($element, $); + } + + async $onInit() { + this.$state.go('ticket.card.summary', {id: this.$params.id}); + window.location.href = await this.vnApp.getUrl(`ticket/${this.$params.id}/sms`); + } +} + +ngModule.vnComponent('vnTicketSms', { + template: require('./index.html'), + controller: Controller, + bindings: { + ticket: '<' + } +}); diff --git a/modules/worker/back/methods/worker-dms/filter.js b/modules/worker/back/methods/worker-dms/filter.js index 5f55f1bd71..9d85544848 100644 --- a/modules/worker/back/methods/worker-dms/filter.js +++ b/modules/worker/back/methods/worker-dms/filter.js @@ -71,8 +71,9 @@ module.exports = Self => { 'Stored on': 'created', 'Document ID': 'id' }; + workerDocuware = - await models.Docuware.getById('hr', worker.lastName + worker.firstName, docuwareParse) ?? []; + await models.Docuware.getById('hr', worker.lastName + ' ' + worker.firstName, docuwareParse) ?? []; for (document of workerDocuware) { const defaultData = { file: 'dw' + document.id + '.png', diff --git a/modules/worker/back/methods/worker/specs/new.spec.js b/modules/worker/back/methods/worker/specs/new.spec.js index b2804c2031..d0830b00f6 100644 --- a/modules/worker/back/methods/worker/specs/new.spec.js +++ b/modules/worker/back/methods/worker/specs/new.spec.js @@ -20,11 +20,11 @@ describe('Worker new', () => { const employeeId = 1; const defaultWorker = { fi: '78457139E', - name: 'defaultWorker', - firstName: 'default', - lastNames: 'worker', + name: 'DEFAULTERWORKER', + firstName: 'DEFAULT', + lastNames: 'WORKER', email: 'defaultWorker@mydomain.com', - street: 'S/ defaultWorkerStreet', + street: 'S/ DEFAULTWORKERSTREET', city: 'defaultWorkerCity', provinceFk: 1, companyFk: 442, diff --git a/package-lock.json b/package-lock.json index f87e3f64b8..10b5e6b022 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "23.34.01", + "version": "23.32.02", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/print/templates/reports/cmr/assets/css/import.js b/print/templates/reports/cmr/assets/css/import.js new file mode 100644 index 0000000000..37a98dfddb --- /dev/null +++ b/print/templates/reports/cmr/assets/css/import.js @@ -0,0 +1,12 @@ +const Stylesheet = require(`vn-print/core/stylesheet`); + +const path = require('path'); +const vnPrintPath = path.resolve('print'); + +module.exports = new Stylesheet([ + `${vnPrintPath}/common/css/spacing.css`, + `${vnPrintPath}/common/css/misc.css`, + `${vnPrintPath}/common/css/layout.css`, + `${vnPrintPath}/common/css/report.css`, + `${__dirname}/style.css`]) + .mergeStyles(); diff --git a/print/templates/reports/cmr/assets/css/style.css b/print/templates/reports/cmr/assets/css/style.css new file mode 100644 index 0000000000..201afc3b60 --- /dev/null +++ b/print/templates/reports/cmr/assets/css/style.css @@ -0,0 +1,101 @@ +html { + font-family: "Roboto", "Helvetica", "Arial", sans-serif; + margin: 10px; + font-size: 22px; +} +.mainTable, .specialTable, .categoryTable { + width: 100%; + border-collapse: collapse; + font-size: inherit; +} +.mainTable td { + width: 50%; + border: 1px solid black; + vertical-align: top; + padding: 15px; + font-size: inherit; +} +.signTable { + height: 12%; +} +.signTable td { + width: calc(100% / 3); + border: 1px solid black; + vertical-align: top; + font-size: inherit; + padding: 15px; + border-top: none; +} +#title { + font-weight: bold; + font-size: 85px; +} +hr { + border: 1px solid #cccccc; + height: 0px; + border-radius: 25px; +} +#cellHeader { + border: 0px; + text-align: center; + vertical-align: middle; +} +#label, #merchandiseLabels { + font-size: 13px; +} +#merchandiseLabels { + border: none; +} +.imgSection { + text-align: center; + height: 200px; + overflow: hidden; +} +img { + object-fit: contain; + width: 100%; + height: 100%; +} +#lineBreak { + white-space: pre-line; +} +.specialTable td { + border: 1px solid black; + vertical-align: top; + padding: 15px; + font-size: inherit; + border-top: none; + border-bottom: none; +} +.specialTable #itemCategoryList { + width: 70%; + padding-top: 10px; +} +.categoryTable { + padding-bottom: none; +} +.categoryTable td { + vertical-align: top; + font-size: inherit; + border: none; + padding: 5px; + overflow: hidden; +} +.categoryTable #merchandiseLabels { + border-bottom: 4px solid #cccccc; + padding: none; +} +#merchandiseDetail { + font-weight: bold; + padding-top: 10px; +} +#merchandiseData { + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +#merchandiseLabels td { + padding-bottom: 11px; + max-width: 300px; +} \ No newline at end of file diff --git a/print/templates/reports/cmr/cmr.html b/print/templates/reports/cmr/cmr.html new file mode 100644 index 0000000000..c6a9e79d6e --- /dev/null +++ b/print/templates/reports/cmr/cmr.html @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+ 1. Remitente / Expediteur / Sender +
+ {{data.senderName}}
+ {{data.senderStreet}}
+ {{data.senderPostCode}} {{data.senderCity}} {{(data.senderCountry) ? `(${data.senderCountry})` : null}} +
+ CMR
+ {{data.cmrFk}} +
+ 2. Consignatario / Destinataire / Consignee +
+ {{data.deliveryAddressFk}}
+ {{data.deliveryName}}
+ {{data.deliveryPhone || data.clientPhone}} + {{((data.deliveryPhone || data.clientPhone) && data.deliveryMobile) ? '/' : null}} + {{data.deliveryMobile}}
+
+ 16. Transportista / Transporteur / Carrier +
+ {{data.carrierName}}
+ {{data.carrierStreet}}
+ {{data.carrierPostalCode}} {{data.carrierCity}} {{(data.carrierCountry) ? `(${data.carrierCountry})` : null}} +
+ + 3. Lugar y fecha de entrega / + Lieu et date de livraison / + Place and date of delivery + +
+ {{data.deliveryStreet}}
+ {{data.deliveryPostalCode}} {{data.deliveryCity}} {{(data.deliveryCountry) ? `(${data.deliveryCountry})` : null}}
+ {{(data.ead) ? formatDate(data.ead, '%d/%m/%Y') : null}}
+ +
+ 17. Porteadores sucesivos / Transporteurs succesifs / Succesive Carriers +
+
+ + 4. Lugar y fecha de carga / + Lieu et date del prise en charge de la merchandise / + Place and date of taking over the goods + +
+ {{data.loadStreet}}
+ {{data.loadPostalCode}} {{data.loadCity}} {{(data.loadCountry) ? `(${data.loadCountry})` : null}}
+ {{formatDate(data.created, '%d/%m/%Y')}}
+
+ + 18. Obervaciones del transportista / + Reserves et observations du transporteur / + Carrier's reservations and observations + +
+ {{data.truckPlate}}
+ {{data.observations}} +
+ 5. Documentos anexos / Documents annexes / Documents attached +
+
+ + + + + +
+ + 7 & 8. Número de bultos y clase de embalage / + Number of packages and packaging class / + Nombre de colis et classe d'emballage + +
+
+ {{data.packagesList}} +
+
+ + + + + + + + + + + + + + + +
6. Marcas y números / Brands and numbers / Marques et numéros9. Naturaleza de la merc. / Nature of goods / Nature des marchandises10. nº Estadístico / Statistical no. / n° statistique11. Peso bruto / Gross weight / Poids brut (kg)12. Volumen / Volume (m3)
{{merchandise.ticketFk}}{{merchandise.name}}N/A{{merchandise.weight}}{{merchandise.volume}}
+
+ {{data.merchandiseDetail}} +
+
+ + + + + + + + + + + + + +
+ + 13. Instrucciones del remitente / + Instrunstions de l'expèditeur / Sender + instruccions + +
+ {{data.senderInstruccions}} +
+ + 19. Estipulaciones particulares / + Conventions particulieres / + Special agreements + +
+ {{data.specialAgreements}} +
+ + 14. Forma de pago / + Prescriptions d'affranchissement / + Instruction as to payment for carriage + +
+ {{data.paymentInstruccions}} +
+ 20. A pagar por / Être payé pour / To be paid by +
+
+ 21. Formalizado en / Etabile a / Estabilshed in +
+ {{data.loadStreet}}
+ {{data.loadPostalCode}} {{data.loadCity}} {{(data.loadCountry) ? `(${data.loadCountry})` : null}}
+
+ 15. Reembolso / Remboursement / Cash on delivery +
+
+ + + + + + + + + +
+ + 22. Firma y sello del remitente / + Signature et timbre de l'expèditeur / + Signature and stamp of the sender + +
+
+ +
+
+ + 23. Firma y sello del transportista / + Signature et timbre du transporteur / + Signature and stamp of the carrier + +
+
+ +
+
+ + 24. Firma y sello del consignatario / + Signature et timbre du destinataire / + Signature and stamp of the consignee + +
+
+ +
+
+ + + \ No newline at end of file diff --git a/print/templates/reports/cmr/cmr.js b/print/templates/reports/cmr/cmr.js new file mode 100644 index 0000000000..c939e51525 --- /dev/null +++ b/print/templates/reports/cmr/cmr.js @@ -0,0 +1,45 @@ +const config = require(`vn-print/core/config`); +const vnReport = require('../../../core/mixins/vn-report.js'); +const md5 = require('md5'); +const fs = require('fs-extra'); + +const prefixBase64 = 'data:image/png;base64,'; + +module.exports = { + name: 'cmr', + mixins: [vnReport], + async serverPrefetch() { + this.data = await this.findOneFromDef('data', [this.id]); + if (this.data.ticketFk) { + this.merchandises = await this.rawSqlFromDef('merchandise', [this.data.ticketFk]); + this.signature = await this.findOneFromDef('signature', [this.data.ticketFk]); + } else + this.merchandises = null; + + this.senderStamp = (this.data.senderStamp) + ? `${prefixBase64} ${this.data.senderStamp.toString('base64')}` + : null; + this.deliveryStamp = (this.data.deliveryStamp) + ? `${prefixBase64} ${this.data.deliveryStamp.toString('base64')}` + : null; + }, + props: { + id: { + type: Number, + required: true, + description: 'The cmr id' + }, + }, + computed: { + signPath() { + if (!this.signature) return; + + const signatureName = this.signature.signature + const hash = md5(signatureName.toString()).substring(0, 3); + const file = `${config.storage.root}/${hash}/${signatureName}.png`; + if (!fs.existsSync(file)) return null; + + return `${prefixBase64} ${Buffer.from(fs.readFileSync(file), 'utf8').toString('base64')}`; + }, + } +}; \ No newline at end of file diff --git a/print/templates/reports/cmr/locale/es.yml b/print/templates/reports/cmr/locale/es.yml new file mode 100644 index 0000000000..79f481b352 --- /dev/null +++ b/print/templates/reports/cmr/locale/es.yml @@ -0,0 +1 @@ +reportName: cmr \ No newline at end of file diff --git a/print/templates/reports/cmr/options.json b/print/templates/reports/cmr/options.json new file mode 100644 index 0000000000..9151ca63b1 --- /dev/null +++ b/print/templates/reports/cmr/options.json @@ -0,0 +1,3 @@ +{ + "format": "A4" +} \ No newline at end of file diff --git a/print/templates/reports/cmr/sql/data.sql b/print/templates/reports/cmr/sql/data.sql new file mode 100644 index 0000000000..9708c4483c --- /dev/null +++ b/print/templates/reports/cmr/sql/data.sql @@ -0,0 +1,52 @@ +SELECT c.id cmrFk, + t.id ticketFk, + c.truckPlate, + c.observations, + c.senderInstruccions, + c.paymentInstruccions, + c.specialAgreements, + c.created, + c.packagesList, + c.merchandiseDetail, + c.ead, + s.name carrierName, + s.street carrierStreet, + s.postCode carrierPostCode, + s.city carrierCity, + cou.country carrierCountry, + s2.name senderName, + s2.street senderStreet, + s2.postCode senderPostCode, + s2.city senderCity, + cou2.country senderCountry, + a.street deliveryStreet, + a.id deliveryAddressFk, + a.postalCode deliveryPostalCode, + a.city deliveryCity, + a.nickname deliveryName, + a.phone deliveryPhone, + a.mobile deliveryMobile, + cou3.country deliveryCountry, + cl.phone clientPhone, + a2.street loadStreet, + a2.postalCode loadPostalCode, + a2.city loadCity, + cou4.country loadCountry, + co.stamp senderStamp, + s.stamp deliveryStamp + FROM cmr c + LEFT JOIN supplier s ON s.id = c.supplierFk + LEFT JOIN country cou ON cou.id = s.countryFk + LEFT JOIN company co ON co.id = c.companyFk + LEFT JOIN supplierAccount sa ON sa.id = co.supplierAccountFk + LEFT JOIN supplier s2 ON s2.id = sa.supplierFk + LEFT JOIN country cou2 ON cou2.id = s2.countryFk + LEFT JOIN `address` a ON a.id = c.addressToFk + LEFT JOIN province p ON p.id = a.provinceFk + LEFT JOIN country cou3 ON cou3.id = p.countryFk + LEFT JOIN client cl ON cl.id = a.clientFk + LEFT JOIN `address` a2 ON a2.id = c.addressFromFk + LEFT JOIN province p2 ON p2.id = a2.provinceFk + LEFT JOIN country cou4 ON cou4.id = p2.countryFk + LEFT JOIN ticket t ON t.cmrFk = c.id + WHERE c.id = ? \ No newline at end of file diff --git a/print/templates/reports/cmr/sql/merchandise.sql b/print/templates/reports/cmr/sql/merchandise.sql new file mode 100644 index 0000000000..cab597caa2 --- /dev/null +++ b/print/templates/reports/cmr/sql/merchandise.sql @@ -0,0 +1,11 @@ +SELECT s.ticketFk, + ic.name, + CAST(SUM(sv.weight) AS DECIMAL(10,2)) `weight`, + CAST(SUM(sv.volume) AS DECIMAL(10,3)) volume + FROM sale s + JOIN saleVolume sv ON sv.saleFk = s.id + JOIN item i ON i.id = s.itemFk + JOIN itemType it ON it.id = i.typeFk + JOIN itemCategory ic ON ic.id = it.categoryFk + WHERE sv.ticketFk = ? + GROUP BY ic.id \ No newline at end of file diff --git a/print/templates/reports/cmr/sql/signature.sql b/print/templates/reports/cmr/sql/signature.sql new file mode 100644 index 0000000000..7ec7380a58 --- /dev/null +++ b/print/templates/reports/cmr/sql/signature.sql @@ -0,0 +1,5 @@ +SELECT dc.id `signature` + FROM ticket t + JOIN ticketDms dt ON dt.ticketFk = t.id + LEFT JOIN dms dc ON dc.id = dt.dmsFk + WHERE t.id = ? \ No newline at end of file