diff --git a/back/methods/dms/uploadFile.js b/back/methods/dms/uploadFile.js index 41d551f2b..e077eea30 100644 --- a/back/methods/dms/uploadFile.js +++ b/back/methods/dms/uploadFile.js @@ -9,25 +9,31 @@ module.exports = Self => { { arg: 'warehouseId', type: 'Number', - description: 'The warehouse id' + description: 'The warehouse id', + required: true }, { arg: 'companyId', type: 'Number', - description: 'The company id' + description: 'The company id', + required: true }, { arg: 'dmsTypeId', type: 'Number', - description: 'The dms type id' + description: 'The dms type id', + required: true }, { arg: 'reference', - type: 'String' + type: 'String', + required: true }, { arg: 'description', - type: 'String' + type: 'String', + required: true }, { arg: 'hasFile', type: 'Boolean', - description: 'True if has an attached file' + description: 'True if has an attached file', + required: true }], returns: { type: 'Object', diff --git a/back/model-config.json b/back/model-config.json index b24226507..89f517812 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -52,6 +52,15 @@ }, "Postcode": { "dataSource": "vn" + }, + "UserPhoneType": { + "dataSource": "vn" + }, + "UserPhone": { + "dataSource": "vn" + }, + "UserLog": { + "dataSource": "vn" } } diff --git a/modules/worker/back/models/user-log.json b/back/models/user-log.json similarity index 100% rename from modules/worker/back/models/user-log.json rename to back/models/user-log.json diff --git a/modules/worker/back/models/user-phone-type.json b/back/models/user-phone-type.json similarity index 100% rename from modules/worker/back/models/user-phone-type.json rename to back/models/user-phone-type.json diff --git a/back/models/user-phone.js b/back/models/user-phone.js new file mode 100644 index 000000000..6f6c20049 --- /dev/null +++ b/back/models/user-phone.js @@ -0,0 +1,9 @@ +let UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.rewriteDbError(function(err) { + if (err.code === 'ER_DUP_ENTRY') + return new UserError(`This phone already exists`); + return err; + }); +}; diff --git a/modules/worker/back/models/user-phone.json b/back/models/user-phone.json similarity index 75% rename from modules/worker/back/models/user-phone.json rename to back/models/user-phone.json index c869a808d..f264ff28f 100644 --- a/modules/worker/back/models/user-phone.json +++ b/back/models/user-phone.json @@ -2,7 +2,8 @@ "name": "UserPhone", "base": "Loggable", "log": { - "model":"UserLog" + "model":"UserLog", + "relation": "user" }, "options": { "mysql": { @@ -12,11 +13,15 @@ "properties": { "id": { "id": true, - "type": "String" + "type": "Number" }, "phone": { "type": "Number", "required": true + }, + "typeFk": { + "type": "String", + "required": true } }, "relations": { diff --git a/db/changes/10100-AllSaints/00-userLog.sql b/db/changes/10100-AllSaints/00-userLog.sql index 2e541eaf4..f6972796d 100644 --- a/db/changes/10100-AllSaints/00-userLog.sql +++ b/db/changes/10100-AllSaints/00-userLog.sql @@ -1,6 +1,6 @@ CREATE TABLE `vn`.`userLog` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `originFk` int(11) NOT NULL, + `originFk` int(10) unsigned NOT NULL, `userFk` int(10) unsigned DEFAULT NULL, `action` set('insert','update','delete') COLLATE utf8_unicode_ci NOT NULL, `creationDate` timestamp NULL DEFAULT CURRENT_TIMESTAMP, @@ -13,6 +13,6 @@ CREATE TABLE `vn`.`userLog` ( PRIMARY KEY (`id`), KEY `originFk` (`originFk`), KEY `userFk` (`userFk`), - CONSTRAINT `userLog_ibfk_1` FOREIGN KEY (`originFk`) REFERENCES `client` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `userLog_ibfk_1` FOREIGN KEY (`originFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `userLog_ibfk_2` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; \ No newline at end of file diff --git a/db/changes/10100-AllSaints/01-userPhone.sql b/db/changes/10100-AllSaints/01-userPhone.sql index 488bbb112..e82b48000 100644 --- a/db/changes/10100-AllSaints/01-userPhone.sql +++ b/db/changes/10100-AllSaints/01-userPhone.sql @@ -2,7 +2,7 @@ CREATE TABLE `vn`.`userPhone` ( `id` INT NOT NULL AUTO_INCREMENT, `userFk` INT(10) UNSIGNED NOT NULL, `typeFk` VARCHAR(45) NOT NULL, - `phone` INT(15) NOT NULL, + `phone` VARCHAR(15) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `UserFK_Phone` (`userFk` ASC, `phone` ASC)); @@ -22,7 +22,7 @@ ADD CONSTRAINT `fgnUserFk` ON UPDATE CASCADE; insert into vn.userPhone(userFk,typeFk,phone) - select id,'PersonalPhone', phone + select id,'personalPhone', phone from vn.client where phone is not null; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 3aec7d82c..7a7083313 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -471,7 +471,7 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF (11, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'NY roofs', 122, NULL, 0, 3, CURDATE()), (12, 1, 1, 1, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 1, CURDATE()), (13, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 3, CURDATE()), - (14, 1, 2, 1, NULL, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()), + (14, 1, 2, 1, NULL, CURDATE(), CURDATE(), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()), (15, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 105, 'Plastic Cell', 125, NULL, 0, 3, CURDATE()), (16, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()), (17, 1, 7, 2, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()), @@ -1862,4 +1862,67 @@ INSERT INTO `vn`.`queuePriority`(`id`, `priority`) VALUES (1, 'Alta'), (2, 'Normal'), - (3, 'Baja'); \ No newline at end of file + (3, 'Baja'); + +INSERT INTO `vn`.`userPhone`(`id`, `userFk`, `typeFk`, `phone`) + VALUES + (1, 101, 'personalPhone', 1111111111), + (2, 102, 'personalPhone', 1111111111), + (3, 103, 'personalPhone', 1111111111), + (4, 104, 'personalPhone', 1111111111), + (5, 105, 'personalPhone', 1111111111), + (6, 106, 'personalPhone', 1111111111), + (7, 107, 'personalPhone', 1111111111), + (8, 108, 'personalPhone', 1111111111), + (9, 109, 'personalPhone', 1111111111), + (10, 110, 'personalPhone', 1111111111), + (11, 111, 'personalPhone', 1111111111), + (12, 112, 'personalPhone', 1111111111), + (13, 1, 'personalPhone', 623111111), + (14, 2, 'personalPhone', 623111111), + (15, 3, 'personalPhone', 623111111), + (16, 5, 'personalPhone', 623111111), + (17, 6, 'personalPhone', 623111111), + (18, 9, 'personalPhone', 623111111), + (19, 13, 'personalPhone', 623111111), + (20, 15, 'personalPhone', 623111111), + (21, 16, 'personalPhone', 623111111), + (22, 17, 'personalPhone', 623111111), + (23, 18, 'personalPhone', 623111111), + (24, 19, 'personalPhone', 623111111), + (25, 20, 'personalPhone', 623111111), + (26, 21, 'personalPhone', 623111111), + (27, 22, 'personalPhone', 623111111), + (28, 30, 'personalPhone', 623111111), + (29, 31, 'personalPhone', 623111111), + (30, 32, 'personalPhone', 623111111), + (31, 34, 'personalPhone', 623111111), + (32, 35, 'personalPhone', 623111111), + (33, 36, 'personalPhone', 623111111), + (34, 37, 'personalPhone', 623111111), + (35, 38, 'personalPhone', 623111111), + (36, 39, 'personalPhone', 623111111), + (37, 40, 'personalPhone', 623111111), + (38, 41, 'personalPhone', 623111111), + (39, 42, 'personalPhone', 623111111), + (40, 43, 'personalPhone', 623111111), + (41, 44, 'personalPhone', 623111111), + (42, 45, 'personalPhone', 623111111), + (43, 47, 'personalPhone', 623111111), + (44, 48, 'personalPhone', 623111111), + (45, 50, 'personalPhone', 623111111), + (46, 51, 'personalPhone', 623111111), + (47, 52, 'personalPhone', 623111111), + (48, 54, 'personalPhone', 623111111), + (49, 55, 'personalPhone', 623111111), + (50, 56, 'personalPhone', 623111111), + (51, 57, 'personalPhone', 623111111), + (52, 58, 'personalPhone', 623111111), + (53, 59, 'personalPhone', 623111111), + (54, 60, 'personalPhone', 623111111), + (55, 61, 'personalPhone', 623111111), + (56, 65, 'personalPhone', 623111111), + (57, 66, 'personalPhone', 623111111), + (65, 107, 'businessPhone', 700987987), + (67, 106, 'businessPhone', 1111111112), + (68, 106, 'personalPhone', 1111111113); diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 67fc7e2fd..1787733d4 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -61,7 +61,7 @@ export default { fiscalDataButton: 'vn-left-menu a[ui-sref="client.card.fiscalData"]', socialNameInput: `vn-textfield input[name="socialName"]`, fiscalIdInput: `vn-textfield input[name="fi"]`, - equalizationTaxCheckbox: 'vn-check[label="Is equalizated"]', + equalizationTaxCheckbox: 'vn-check[ng-model="$ctrl.client.isEqualizated"]', acceptPropagationButton: 'vn-client-fiscal-data > vn-confirm button[response=ACCEPT]', addressInput: `vn-textfield input[name="street"]`, postcodeInput: `vn-textfield input[name="postcode"]`, @@ -482,7 +482,7 @@ export default { addRequestButton: 'vn-ticket-request-index > a > vn-float-button > button', request: 'vn-ticket-request-index vn-table vn-tr', descriptionInput: 'vn-ticket-request-create > form > div > vn-card > vn-horizontal:nth-child(1) > vn-textfield input', - atenderAutocomplete: 'vn-ticket-request-create vn-autocomplete[ng-model="$ctrl.ticketRequest.atenderFk"]', + atenderAutocomplete: 'vn-ticket-request-create vn-autocomplete[ng-model="$ctrl.ticketRequest.attenderFk"]', quantityInput: 'vn-ticket-request-create vn-input-number input[name=quantity]', priceInput: 'vn-ticket-request-create vn-input-number input[name=price]', firstRemoveRequestButton: 'vn-ticket-request-index vn-icon[icon="delete"]:nth-child(1)', @@ -534,7 +534,7 @@ export default { itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor', itemDescriptorPopoverItemDiaryButton: '.vn-popover.shown vn-item-descriptor a[href="#!/item/2/diary"]', firstDevelopmentWorker: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > span', - firstDevelopmentWorkerGoToClientButton: '.vn-popover.shown vn-worker-descriptor div.quicklinks > a[href="#!/client/21/summary"]', + firstDevelopmentWorkerGoToClientButton: '.vn-popover.shown vn-worker-descriptor vn-quick-links > a[href="#!/client/21/summary"]', firstActionTicketId: 'vn-claim-summary > vn-card > vn-horizontal > vn-auto:nth-child(6) vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > span', firstActionTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor' }, diff --git a/e2e/paths/05-ticket-module/16_summary.spec.js b/e2e/paths/05-ticket-module/16_summary.spec.js index 0fd3b8b7c..2832da428 100644 --- a/e2e/paths/05-ticket-module/16_summary.spec.js +++ b/e2e/paths/05-ticket-module/16_summary.spec.js @@ -17,7 +17,7 @@ describe('Ticket Summary path', () => { it(`should display details from the ticket and it's client on the top of the header`, async() => { let result = await nightmare - .waitForSpinnerLoad() + .waitForTextInElement(selectors.ticketSummary.header, 'Bruce Banner') .waitToGetProperty(selectors.ticketSummary.header, 'innerText'); expect(result).toContain(`Ticket #${ticketId}`); diff --git a/front/core/directives/focus.js b/front/core/directives/focus.js index 55e7d3496..b9527479b 100644 --- a/front/core/directives/focus.js +++ b/front/core/directives/focus.js @@ -3,7 +3,7 @@ import ngModule from '../module'; const regex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i; export const isMobile = regex.test(navigator.userAgent); -export function focus(input) { +export function focus($scope, input) { if (isMobile) return; const element = input; @@ -23,9 +23,9 @@ export function focus(input) { } input.focus(); - - if (input.select) + $scope.$applyAsync(() => { input.select(); + }); } /** @@ -37,7 +37,7 @@ export function directive() { return { restrict: 'A', link: function($scope, $element) { - $scope.$watch('', () => focus($element[0])); + $scope.$watch('', () => focus($scope, $element[0])); } }; } diff --git a/front/core/directives/specs/focus.spec.js b/front/core/directives/specs/focus.spec.js index ecf28a692..4034657b4 100644 --- a/front/core/directives/specs/focus.spec.js +++ b/front/core/directives/specs/focus.spec.js @@ -40,6 +40,7 @@ describe('Directive focus', () => { it('should call select function on the element', () => { let html = ``; compile(html); + $scope.$apply(); expect($element[0].select).toHaveBeenCalledWith(); }); diff --git a/front/core/styles/global.scss b/front/core/styles/global.scss index 5f001736b..ff49516dc 100644 --- a/front/core/styles/global.scss +++ b/front/core/styles/global.scss @@ -1,9 +1,12 @@ @import "variables"; -html, body { +html { background-color: $color-bg; overflow: auto; height: 100%; +} +body { + height: 100%; font-family: vn-font; color: $color-font; font-size: $font-size; diff --git a/front/salix/components/descriptor/index.html b/front/salix/components/descriptor/index.html index a3ee34563..3804be627 100644 --- a/front/salix/components/descriptor/index.html +++ b/front/salix/components/descriptor/index.html @@ -1,7 +1,7 @@ + ui-sref="{{::$ctrl.links.btnOne.state}}"> @@ -9,7 +9,7 @@ + ui-sref="{{::$ctrl.links.btnTwo.state}}"> @@ -17,7 +17,7 @@ + ui-sref="{{::$ctrl.links.btnThree.state}}"> diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 6683a9228..dffa15af6 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -109,6 +109,7 @@ "is invalid": "is invalid", "The postcode doesn't exists. Ensure you put the correct format": "El código postal no existe. Asegúrate de ponerlo con el formato correcto", "The department name can't be repeated": "El nombre del departamento no puede repetirse", + "This phone already exists": "Este teléfono ya existe", "You cannot move a parent to any of its sons": "You cannot move a parent to any of its sons", "You cannot move a parent to its own sons": "You cannot move a parent to its own sons", "You can't create a claim for a removed ticket": "No puedes crear una reclamación para un ticket eliminado" diff --git a/modules/claim/back/methods/claim/regularizeClaim.js b/modules/claim/back/methods/claim/regularizeClaim.js index a2896174e..136016b1e 100644 --- a/modules/claim/back/methods/claim/regularizeClaim.js +++ b/modules/claim/back/methods/claim/regularizeClaim.js @@ -120,16 +120,19 @@ module.exports = Self => { } async function getTicketId(params, options) { - const currentDate = new Date(); - currentDate.setHours(null, null, null); + const minDate = new Date(); + minDate.setHours(0, 0, 0, 0); + + const maxDate = new Date(); + maxDate.setHours(23, 59, 59, 59); let ticket = await Self.app.models.Ticket.findOne({ where: { addressFk: params.addressFk, companyFk: params.companyFk, warehouseFk: params.warehouseFk, - shipped: currentDate, - landed: currentDate + shipped: {between: [minDate, maxDate]}, + landed: {between: [minDate, maxDate]} } }, options); diff --git a/modules/claim/back/methods/claim/uploadFile.js b/modules/claim/back/methods/claim/uploadFile.js index f6dad3477..c7565701d 100644 --- a/modules/claim/back/methods/claim/uploadFile.js +++ b/modules/claim/back/methods/claim/uploadFile.js @@ -7,36 +7,34 @@ module.exports = Self => { type: 'Number', description: 'The claim id', http: {source: 'path'} - }, - { + }, { arg: 'warehouseId', type: 'Number', - description: '' - }, - { + description: 'The warehouse id', + required: true + }, { arg: 'companyId', type: 'Number', - description: '' - }, - { + description: 'The company id', + required: true + }, { arg: 'dmsTypeId', type: 'Number', - description: '' - }, - { + description: 'The dms type id', + required: true + }, { arg: 'reference', type: 'String', - description: '' - }, - { + required: true + }, { arg: 'description', type: 'String', - description: '' - }, - { + required: true + }, { arg: 'hasFile', type: 'Boolean', - description: '' + description: 'True if has an attached file', + required: true }], returns: { type: 'Object', diff --git a/modules/claim/front/action/index.html b/modules/claim/front/action/index.html index 329514b37..04ab71997 100644 --- a/modules/claim/front/action/index.html +++ b/modules/claim/front/action/index.html @@ -1,10 +1,14 @@ - + + + - + show-field="description"> {{::saleClaimed.sale.ticket.landed | date: 'dd/MM/yyyy'}} diff --git a/modules/claim/front/action/index.js b/modules/claim/front/action/index.js index 439b70d39..63b283f1f 100644 --- a/modules/claim/front/action/index.js +++ b/modules/claim/front/action/index.js @@ -19,7 +19,8 @@ class Controller { } } }, - {relation: 'claimBeggining'} + {relation: 'claimBeggining'}, + {relation: 'claimDestination'} ] }; this.resolvedState = 3; @@ -82,16 +83,6 @@ class Controller { this.calculateTotals(); } - setClaimDestination(id, claimDestinationFk) { - if (claimDestinationFk) { - let params = {id: id, claimDestinationFk: claimDestinationFk}; - let query = `claim/api/ClaimEnds/`; - this.$http.patch(query, params).then(() => { - this.vnApp.showSuccess(this.$translate.instant('Data saved!')); - }); - } - } - calculateTotals() { this.claimedTotal = 0; this.salesClaimed.forEach(sale => { diff --git a/modules/claim/front/action/index.spec.js b/modules/claim/front/action/index.spec.js index d1f8f9766..e526680c3 100644 --- a/modules/claim/front/action/index.spec.js +++ b/modules/claim/front/action/index.spec.js @@ -82,17 +82,6 @@ describe('claim', () => { }); }); - describe('setClaimDestination(id, claimDestinationFk)', () => { - it('should make a patch and call refresh and showSuccess', () => { - spyOn(controller.vnApp, 'showSuccess'); - $httpBackend.expectPATCH(`claim/api/ClaimEnds/`).respond({}); - controller.setClaimDestination(1, 1); - $httpBackend.flush(); - - expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); - }); - }); - describe('calculateTotals()', () => { it('should calculate the total price of the items claimed', () => { controller.salesClaimed = [ diff --git a/modules/client/back/methods/client/uploadFile.js b/modules/client/back/methods/client/uploadFile.js index 6b7a49459..6ec96e301 100644 --- a/modules/client/back/methods/client/uploadFile.js +++ b/modules/client/back/methods/client/uploadFile.js @@ -7,36 +7,34 @@ module.exports = Self => { type: 'Number', description: 'The client id', http: {source: 'path'} - }, - { + }, { arg: 'warehouseId', type: 'Number', - description: '' - }, - { + description: 'The warehouse id', + required: true + }, { arg: 'companyId', type: 'Number', - description: '' - }, - { + description: 'The company id', + required: true + }, { arg: 'dmsTypeId', type: 'Number', - description: '' - }, - { + description: 'The dms type id', + required: true + }, { arg: 'reference', type: 'String', - description: '' - }, - { + required: true + }, { arg: 'description', type: 'String', - description: '' - }, - { + required: true + }, { arg: 'hasFile', type: 'Boolean', - description: '' + description: 'True if has an attached file', + required: true }], returns: { type: 'Object', diff --git a/modules/client/front/descriptor/index.js b/modules/client/front/descriptor/index.js index 1eecdc15a..04d619f50 100644 --- a/modules/client/front/descriptor/index.js +++ b/modules/client/front/descriptor/index.js @@ -13,6 +13,10 @@ class Controller { callback.call(this); } + get client() { + return this._client; + } + set client(value) { this._client = value; @@ -32,10 +36,6 @@ class Controller { }; } - get client() { - return this._client; - } - set quicklinks(value = {}) { this._quicklinks = Object.assign(value, this._quicklinks); } diff --git a/modules/invoiceOut/back/methods/invoiceOut/filter.js b/modules/invoiceOut/back/methods/invoiceOut/filter.js index a0da914f8..8b6d00763 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/filter.js +++ b/modules/invoiceOut/back/methods/invoiceOut/filter.js @@ -92,6 +92,7 @@ module.exports = Self => { case 'companyFk': case 'issued': case 'dued': + param = `i.${param}`; return {[param]: value}; } }); diff --git a/modules/item/back/methods/item/regularize.js b/modules/item/back/methods/item/regularize.js index 17f1dbe1e..b9aa0d85f 100644 --- a/modules/item/back/methods/item/regularize.js +++ b/modules/item/back/methods/item/regularize.js @@ -105,15 +105,18 @@ module.exports = Self => { async function getTicketId(params, options) { - const currentDate = new Date(); - currentDate.setHours(null, null, null); + const minDate = new Date(); + minDate.setHours(0, 0, 0, 0); + + const maxDate = new Date(); + maxDate.setHours(23, 59, 59, 59); let ticket = await Self.app.models.Ticket.findOne({ where: { addressFk: params.addressFk, warehouseFk: params.warehouseFk, - shipped: currentDate, - landed: currentDate + shipped: {between: [minDate, maxDate]}, + landed: {between: [minDate, maxDate]} } }, options); diff --git a/modules/item/front/diary/index.js b/modules/item/front/diary/index.js index a15c32644..e128369a2 100644 --- a/modules/item/front/diary/index.js +++ b/modules/item/front/diary/index.js @@ -52,24 +52,31 @@ class Controller { get freeLineIndex() { let lines = this.$scope.model.data; - let currentDate = new Date(); - currentDate.setHours(0, 0, 0); + let minDate = new Date(); + minDate.setHours(0, 0, 0, 0); + + let maxDate = new Date(); + maxDate.setHours(23, 59, 59, 59); + for (let i = 0; i < lines.length; i++) { - let isFutureDate = new Date(lines[i].date) >= currentDate; + const dated = new Date(lines[i].date); - if (isFutureDate) + let isForFuture = dated > maxDate; + let isForToday = (dated >= minDate && dated <= maxDate); + + if (isForFuture || isForToday) return i; } } get onPreparationLineIndex() { let lines = this.$scope.model.data; - for (let i = this.freeLineIndex; i >= 0; i--) { let line = lines[i]; + let currentDate = new Date(); - currentDate.setHours(0, 0, 0); + currentDate.setHours(0, 0, 0, 0); let isPastDate = new Date(lines[i].date) < currentDate; let isPicked = line.alertLevel == 1 && line.isPicked; @@ -95,6 +102,7 @@ class Controller { let selectedTicketLineIndex = this.givenTicketIndex; let lineIndex = this.onPreparationLineIndex; + let lines = body.querySelector('vn-tbody').children; if (lineIndex == undefined || !lines.length) return; @@ -120,7 +128,6 @@ class Controller { offsetTop = onPreparationLine.offsetTop - headerHeight; this.$window.scrollTo(0, offsetTop); - this.ticketFk = null; } diff --git a/modules/item/front/index/index.html b/modules/item/front/index/index.html index 3ed297baa..2f53c9c08 100644 --- a/modules/item/front/index/index.html +++ b/modules/item/front/index/index.html @@ -8,12 +8,11 @@
- + suggested-filter="{isActive: true}"> + order="shipped DESC, isOk ASC">
- - - - + + - - - - - Ticket ID - Shipped - Warehouse - SalesPerson - Description - Quantity - Price - Atender - Item - Concept - Sale quantity - State - - - - - - - {{request.ticketFk}} - - - - - {{::request.shipped | date: 'dd/MM/yyyy'}} - - - {{::request.warehouse}} - - - {{::request.salesPersonNickname}} - - - {{::request.description}} - {{::request.quantity}} - {{::request.price | currency: 'EUR':2}} - - - {{::request.atenderNickname}} - - - - {{request.itemFk}} - - - - - - - - {{::request.itemDescription}} - - - - {{request.saleQuantity}} - - - - - - {{::$ctrl.getState(request.isOk)}} - - - - - - - - - - - + + + + + + Ticket ID + Shipped + Warehouse + SalesPerson + Description + Requested + Price + Atender + Item + Achieved + Concept + State + + + + + + + {{request.ticketFk}} + + + + + {{::request.shipped | date: 'dd/MM/yyyy'}} + + + {{::request.warehouse}} + + + {{::request.salesPersonNickname}} + + + {{::request.description}} + {{::request.quantity}} + {{::request.price | currency: 'EUR':2}} + + + {{::request.atenderNickname}} + + + + {{request.itemFk}} + + + + + + + {{request.saleQuantity}} + + + + + + + + {{request.itemDescription}} + + + {{$ctrl.getState(request.isOk)}} + + + + + + + + + + +
+ on-response="$ctrl.denyRequest(response)"> - -
Indicate the reasons to deny this request
-
+
Specify the reasons to deny this request
- - - -
+ + + +
\ No newline at end of file diff --git a/modules/item/front/request/index.js b/modules/item/front/request/index.js index f887be9a8..c18800001 100644 --- a/modules/item/front/request/index.js +++ b/modules/item/front/request/index.js @@ -9,8 +9,23 @@ export default class Controller { this.$ = $; this.vnApp = vnApp; this._ = $translate; - if (!$stateParams.q) - this.filter = {isOk: false, mine: true}; + if (!$stateParams.q) { + const today = new Date(); + today.setHours(23, 59, 59, 59); + + const lastWeek = new Date(); + lastWeek.setHours(0, 0, 0, 0); + lastWeek.setDate(lastWeek.getDate() - 7); + + this.filter = { + where: { + isOk: false, + mine: true, + from: lastWeek, + to: today + } + }; + } } $postLink() { @@ -21,7 +36,7 @@ export default class Controller { getState(isOk) { if (isOk === null) return 'Nueva'; - else if (isOk === -1 || isOk === 1) + else if (isOk === -1 || isOk) return 'Aceptada'; else return 'Denegada'; @@ -34,14 +49,12 @@ export default class Controller { quantity: request.saleQuantity }; - let endpoint = `/api/TicketRequests/${request.id}/confirm`; + let query = `/api/TicketRequests/${request.id}/confirm`; + this.$http.post(query, params).then(res => { + request.itemDescription = res.data.concept; + request.isOk = true; - this.$http.post(endpoint, params).then(() => { this.vnApp.showSuccess(this._.instant('Data saved!')); - this.$.model.refresh(); - }).catch( e => { - this.$.model.refresh(); - throw e; }); } } @@ -56,10 +69,7 @@ export default class Controller { this.$http.patch(endpoint, params).then(() => { this.vnApp.showSuccess(this._.instant('Data saved!')); - }).catch( e => { - this.$.model.refresh(); - throw e; - }); + }).then(() => this.confirmRequest(request)); } else this.confirmRequest(request); } @@ -86,7 +96,7 @@ export default class Controller { } showDenyReason(event, requestId) { - this.denyRequestId = requestId; + this.selectedRequest = requestId; this.$.denyReason.parent = event.target; this.$.denyReason.show(); document.querySelector('vn-item-request vn-textarea textArea').focus(); @@ -96,17 +106,21 @@ export default class Controller { delete this.denyRequestId; } - denyRequest() { + denyRequest(response) { + if (response !== 'ACCEPT') return; + let params = { observation: this.denyObservation }; - let endpoint = `/api/TicketRequests/${this.denyRequestId}/deny`; + let query = `/api/TicketRequests/${this.selectedRequest.id}/deny`; + this.$http.post(query, params).then(res => { + const request = res.data; + this.selectedRequest.isOk = request.isOk; + this.selectedRequest.attenderFk = request.attenderFk; + this.selectedRequest.response = request.response; - this.$http.post(endpoint, params).then(() => { this.vnApp.showSuccess(this._.instant('Data saved!')); - this.$.model.refresh(); - this.$.denyReason.hide(); this.denyObservation = null; }); } diff --git a/modules/item/front/request/index.spec.js b/modules/item/front/request/index.spec.js index e27bc3879..1704c61a1 100644 --- a/modules/item/front/request/index.spec.js +++ b/modules/item/front/request/index.spec.js @@ -53,15 +53,15 @@ describe('Item', () => { let model = controller.$.model; spyOn(model, 'refresh'); + const expectedResult = {concept: 'Melee Weapon'}; let request = {itemFk: 1, saleQuantity: 1, id: 1}; - $httpBackend.when('POST', `/api/TicketRequests/${request.id}/confirm`).respond(); - $httpBackend.expect('POST', `/api/TicketRequests/${request.id}/confirm`).respond(); + $httpBackend.when('POST', `/api/TicketRequests/${request.id}/confirm`).respond(expectedResult); + $httpBackend.expect('POST', `/api/TicketRequests/${request.id}/confirm`).respond(expectedResult); controller.confirmRequest(request); $httpBackend.flush(); expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); - expect($scope.model.refresh).toHaveBeenCalledWith(); }); }); @@ -110,20 +110,17 @@ describe('Item', () => { describe('denyRequest()', () => { it(`should perform a query and call vnApp.showSuccess(), refresh(), hide() and set denyObservation to null in the controller`, () => { spyOn(controller.vnApp, 'showSuccess'); - let model = controller.$.model; - spyOn(model, 'refresh'); - spyOn(controller.$.denyReason, 'hide'); - controller.denyRequestId = 1; + const request = {id: 1}; + const expectedResult = {isOk: false, attenderFk: 106, response: 'Denied!'}; + controller.selectedRequest = request; - $httpBackend.when('POST', `/api/TicketRequests/${controller.denyRequestId}/deny`).respond(); - $httpBackend.expect('POST', `/api/TicketRequests/${controller.denyRequestId}/deny`).respond(); - controller.denyRequest(); + $httpBackend.when('POST', `/api/TicketRequests/${request.id}/deny`).respond(expectedResult); + $httpBackend.expect('POST', `/api/TicketRequests/${request.id}/deny`).respond(expectedResult); + controller.denyRequest('ACCEPT'); $httpBackend.flush(); expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); - expect($scope.model.refresh).toHaveBeenCalledWith(); - expect($scope.denyReason.hide).toHaveBeenCalledWith(); }); }); }); diff --git a/modules/item/front/request/locale/es.yml b/modules/item/front/request/locale/es.yml index 760f30bd3..33710a327 100644 --- a/modules/item/front/request/locale/es.yml +++ b/modules/item/front/request/locale/es.yml @@ -1,5 +1,6 @@ Discard: Descartar -Indicate the reasons to deny this request: Indique las razones para descartar esta peticion +Specify the reasons to deny this request: Especifica las razones para descartar la petición Buy requests: Peticiones de compra Search request by id or alias: Buscar peticiones por identificador o alias -Sale quantity: C. conseguida \ No newline at end of file +Requested: Solicitado +Achieved: Conseguido \ No newline at end of file diff --git a/modules/item/front/tags/index.js b/modules/item/front/tags/index.js index b187e5baf..c5e120d82 100644 --- a/modules/item/front/tags/index.js +++ b/modules/item/front/tags/index.js @@ -66,7 +66,7 @@ class Controller { this.$scope.watcher.check(); this.$scope.model.save().then(() => { this.$scope.watcher.notifySaved(); - this.$scope.model.refresh(); + this.$scope.watcher.updateOriginalData(); this.card.reload(); }); } diff --git a/modules/route/back/methods/route/filter.js b/modules/route/back/methods/route/filter.js index 1ea962bed..eeeef1dac 100644 --- a/modules/route/back/methods/route/filter.js +++ b/modules/route/back/methods/route/filter.js @@ -85,10 +85,13 @@ module.exports = Self => { return {'r.m3': value}; case 'description': return {'r.description': {like: `%${value}%`}}; - case 'workerFk': case 'warehouseFk': + param = `v.${param}`; + return {[param]: value}; + case 'workerFk': case 'vehicleFk': case 'agencyModeFk': + param = `r.${param}`; return {[param]: value}; } }); diff --git a/modules/route/back/methods/route/getTickets.js b/modules/route/back/methods/route/getTickets.js index cbe7db9f8..a0014a60d 100644 --- a/modules/route/back/methods/route/getTickets.js +++ b/modules/route/back/methods/route/getTickets.js @@ -28,12 +28,6 @@ module.exports = Self => { fields: ['id', 'packages', 'warehouseFk', 'nickname', 'clientFk', 'priority', 'addressFk'], order: 'priority', include: [ - { - relation: 'client', - scope: { - fields: ['id', 'street', 'postcode'], - } - }, { relation: 'state', scope: { diff --git a/modules/route/front/tickets/__snapshots__/index.spec.js.snap b/modules/route/front/tickets/__snapshots__/index.spec.js.snap new file mode 100644 index 000000000..9476a8e09 --- /dev/null +++ b/modules/route/front/tickets/__snapshots__/index.spec.js.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Route getSelectedItems() should return the selected items 1`] = ` +Array [ + Object { + "checked": true, + "id": 1, + }, + Object { + "checked": true, + "id": 3, + }, + Object { + "checked": true, + "id": 5, + }, +] +`; diff --git a/modules/route/front/tickets/index.html b/modules/route/front/tickets/index.html index 3df0bc346..16b67ed75 100644 --- a/modules/route/front/tickets/index.html +++ b/modules/route/front/tickets/index.html @@ -1,104 +1,105 @@ -
- - - - - - - - - - - - - - - - - Order - Ticket - Client - Packages - - Warehouse - PC - Street - - - - - - - - - - - - - - - - - {{ticket.id}} - - - - - {{ticket.nickname}} - - - {{ticket.packages}} - {{ticket.volume}} - {{ticket.warehouse.name}} - {{ticket.client.postcode}} - {{ticket.client.street}} - - - - - - - - - - - - -
- + +
+ + + + + + + + + + + + + + + + + Order + Ticket + Client + Packages + + Warehouse + Postcode + Street + + + + + + + + + + + + + + + + + {{ticket.id}} + + + + + {{ticket.nickname}} + + + {{ticket.packages}} + {{ticket.volume}} + {{ticket.warehouse.name}} + {{ticket.address.postalCode}} + {{ticket.address.street}} + + + + + + + + + + + + +
+
@@ -109,4 +110,72 @@ vn-id="confirm" question="Delete ticket from route?" on-response="$ctrl.removeTicketFromRoute(response)"> - \ No newline at end of file + + + + + +
+
Tickets to add
+
+ + + + + + + + + Ticket + Client + Packages + Warehouse + Postcode + Address + + + + + + + + + {{ticket.id}} + + + {{ticket.nickname}} + + + {{ticket.packages}} + {{ticket.warehouse.name}} + {{ticket.address.postalCode}} + {{ticket.address.street}} + + + + +
+ + + + +
+ + \ No newline at end of file diff --git a/modules/route/front/tickets/index.js b/modules/route/front/tickets/index.js index ce12d88ac..9b9a49222 100644 --- a/modules/route/front/tickets/index.js +++ b/modules/route/front/tickets/index.js @@ -2,12 +2,23 @@ import ngModule from '../module'; import './style.scss'; class Controller { - constructor($stateParams, $, $translate, $http, vnApp) { + constructor($stateParams, $scope, $translate, $http, vnApp, $filter) { this.$translate = $translate; this.$stateParams = $stateParams; - this.$ = $; + this.$ = $scope; this.$http = $http; this.vnApp = vnApp; + this.$filter = $filter; + } + + set route(value) { + this._route = value; + if (value) + this.buildPossibleTicketsFilter(); + } + + get route() { + return this._route; } get isChecked() { @@ -19,13 +30,37 @@ class Controller { return false; } + buildPossibleTicketsFilter() { + let minDate = new Date(this.route.finished); + minDate.setHours(0, 0, 0, 0); + + let maxDate = new Date(this.route.finished); + maxDate.setHours(23, 59, 59, 59); + + this.possibleTicketsFilter = { + where: { + zoneFk: this.route.zoneFk, + routeFk: null, + landed: {between: [minDate, maxDate]}, + }, + include: [ + { + relation: 'warehouse', + scope: { + fields: ['name'] + }, + }, { + relation: 'address' + } + ] + }; + } + getHighestPriority() { - let max = 0; - this.$.model.data.forEach(tag => { - if (tag.priority > max) - max = tag.priority; - }); - return max + 1; + let highestPriority = Math.max(...this.$.model.data.map(tag => { + return tag.priority; + })); + return highestPriority + 1; } setPriority(id, priority) { @@ -37,16 +72,16 @@ class Controller { }); } - getCheckedLines() { - let lines = []; - let data = this.tickets; - if (data) { - for (let i = 0; i < data.length; i++) { - if (data[i].checked) - lines.push(data[i]); + getSelectedItems(items) { + const selectedItems = []; + + if (items) { + for (let i = 0; i < items.length; i++) { + if (items[i].checked) + selectedItems.push(items[i]); } } - return lines; + return selectedItems; } goToBuscaman() { @@ -54,7 +89,7 @@ class Controller { let firstAddress = `46460 Av Espioca 100-46460 Silla`; let addresses = firstAddress; - let lines = this.getCheckedLines(); + let lines = this.getSelectedItems(this.tickets); let url = 'http://gps.buscalia.com/usuario/localizar.aspx?bmi=true&addr='; lines.forEach(line => { @@ -64,8 +99,8 @@ class Controller { window.open(url + addresses, '_blank'); } - showDeleteConfirm(ticket) { - this.selectedTicket = ticket; + showDeleteConfirm(id) { + this.selectedTicket = id; this.$.confirm.show(); } @@ -109,14 +144,38 @@ class Controller { this.$.clientDescriptor.show(); event.preventDefault(); } + + openPossibleTicketsDialog() { + this.$.possibleTicketsModel.refresh(); + this.$.possibleTicketsDialog.show(); + } + + setTicketsRoute(response) { + if (response === 'ACCEPT') { + let tickets = this.getSelectedItems(this.possibleTickets); + + for (let i = 0; i < tickets.length; i++) { + delete tickets[i].checked; + tickets[i].routeFk = this.route.id; + } + + return this.$.possibleTicketsModel.save().then(() => { + this.$.model.data = this.$.model.data.concat(tickets); + }); + } + return Promise.resolve(); + } } -Controller.$inject = ['$stateParams', '$scope', '$translate', '$http', 'vnApp']; +Controller.$inject = ['$stateParams', '$scope', '$translate', '$http', 'vnApp', '$filter']; ngModule.component('vnRouteTickets', { template: require('./index.html'), + controller: Controller, require: { card: '^vnRouteCard' }, - controller: Controller + bindings: { + route: '<' + } }); diff --git a/modules/route/front/tickets/index.spec.js b/modules/route/front/tickets/index.spec.js new file mode 100644 index 000000000..539579abe --- /dev/null +++ b/modules/route/front/tickets/index.spec.js @@ -0,0 +1,288 @@ +import './index.js'; + +describe('Route', () => { + let controller; + let $httpBackend; + + beforeEach(angular.mock.module('route', $translateProvider => { + $translateProvider.translations('en', {}); + })); + + beforeEach(angular.mock.inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnRouteTickets'); + })); + + describe('route setter/getter', () => { + it('should return the route id', () => { + controller.route = 2; + + expect(controller.route).toEqual(2); + }); + }); + + describe('isChecked getter', () => { + it('should return false if none of the tickets is checked or there are no tickets', () => { + expect(controller.isChecked).toBeFalsy(); + }); + + it('should return true if any of the tickets is checked', () => { + controller.tickets = [{checked: true}]; + + expect(controller.isChecked).toBeTruthy(); + }); + }); + + describe('buildPossibleTicketsFilter()', () => { + it('should build the possible tickets filter', () => { + let expectedFilter = { + include: [ + { + relation: 'warehouse', + scope: { + fields: ['name'] + } + }, { + relation: 'address' + } + ], + where: { + landed: { + between: [ + jasmine.any(Date), + jasmine.any(Date) + ] + }, + routeFk: null, + zoneFk: 67 + } + }; + controller.route = { + finished: new Date(), + routeFk: null, + zoneFk: 67 + }; + + controller.buildPossibleTicketsFilter(); + + expect(controller.possibleTicketsFilter).toEqual(expectedFilter); + }); + }); + + describe('getHighestPriority()', () => { + it('should return the highest value found in priorities plus 1', () => { + controller.$.model = {data: [ + {priority: 99}, + {priority: 1}, + {priority: 2}, + {priority: 3}, + {priority: 4}, + {priority: 5}, + ]}; + + let result = controller.getHighestPriority(); + + expect(result).toEqual(100); + }); + }); + + describe('setPriority()', () => { + it('should set a ticket priority', () => { + controller.$.model = {refresh: () => {}}; + spyOn(controller.$.model, 'refresh'); + spyOn(controller.vnApp, 'showSuccess'); + const ticketId = 1; + const priority = 999; + + $httpBackend.expectPATCH(`/api/Tickets/${ticketId}/`).respond('ok'); + controller.setPriority(ticketId, priority); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + expect(controller.$.model.refresh).toHaveBeenCalledWith(); + }); + }); + + describe('getSelectedItems()', () => { + it('should return the selected items', () => { + let items = [ + {id: 1, checked: true}, + {id: 2, checked: false}, + {id: 3, checked: true}, + {id: 4, checked: false}, + {id: 5, checked: true}, + ]; + + let selectedItems = controller.getSelectedItems(items); + + expect(selectedItems).toMatchSnapshot(); + }); + }); + + describe('goToBuscaman()', () => { + it('should open buscaman with the given arguments', () => { + spyOn(window, 'open'); + const expectedUrl = 'http://gps.buscalia.com/usuario/localizar.aspx?bmi=true&addr=46460 Av Espioca 100-46460 Silla+to:n19 my street-n19 London'; + controller.tickets = [ + { + id: 1, + checked: true, + address: { + street: 'my street', + postalCode: 'n19', + city: 'London' + } + }, + ]; + + controller.goToBuscaman(); + + expect(window.open).toHaveBeenCalledWith(expectedUrl, '_blank'); + }); + }); + + describe('showDeleteConfirm()', () => { + it('should open a confirm dialog after setting the selected ticket into the controller', () => { + controller.$.confirm = {show: () => {}}; + spyOn(controller.$.confirm, 'show'); + let ticketId = 1; + + controller.showDeleteConfirm(ticketId); + + expect(controller.selectedTicket).toEqual(ticketId); + expect(controller.$.confirm.show).toHaveBeenCalledWith(); + }); + }); + + describe('removeTicketFromRoute()', () => { + it('should perform a patch query then call showSuccess and updateVolume methods', () => { + spyOn(controller, 'updateVolume'); + spyOn(controller.vnApp, 'showSuccess'); + let ticketId = 1; + controller.selectedTicket = ticketId; + + $httpBackend.expectPATCH(`/api/Tickets/${ticketId}/`).respond('ok'); + controller.removeTicketFromRoute('ACCEPT'); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Ticket removed from route'); + expect(controller.updateVolume).toHaveBeenCalledWith(); + }); + }); + + describe('updateVolume()', () => { + it('should perform a POST query then call both reload and refresh methods', () => { + controller.$.model = {refresh: () => {}}; + controller.card = {reload: () => {}}; + controller.$stateParamds = {id: 999}; + spyOn(controller.$.model, 'refresh'); + spyOn(controller.card, 'reload'); + + let ticketId = 1; + controller.selectedTicket = ticketId; + + const url = `/route/api/Routes/${controller.$stateParams.id}/updateVolume`; + $httpBackend.expectPOST(url).respond('ok'); + controller.updateVolume(); + $httpBackend.flush(); + + expect(controller.$.model.refresh).toHaveBeenCalledWith(); + expect(controller.card.reload).toHaveBeenCalledWith(); + }); + }); + + describe('guessPriority()', () => { + it('should perform a GET query then call both refresh and showSuccess methods', () => { + controller.$.model = {refresh: () => {}}; + spyOn(controller.$.model, 'refresh'); + spyOn(controller.vnApp, 'showSuccess'); + controller.$stateParams = {id: 99}; + + const url = `/api/Routes/${controller.$stateParams.id}/guessPriority/`; + $httpBackend.expectGET(url).respond('ok'); + controller.guessPriority(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Order changed'); + expect(controller.$.model.refresh).toHaveBeenCalledWith(); + }); + }); + + describe('showTicketDescriptor()', () => { + it('should call the descriptor show function after setting the parent and the ticket id', () => { + controller.$.ticketDescriptor = {show: () => {}}; + spyOn(controller.$.ticketDescriptor, 'show'); + const event = {target: {}, preventDefault: () => {}}; + spyOn(event, 'preventDefault'); + const ticketId = 999; + controller.showTicketDescriptor(event, ticketId); + + expect(controller.$.ticketDescriptor.ticketFk).toEqual(ticketId); + expect(controller.$.ticketDescriptor.show).toHaveBeenCalledWith(); + expect(event.preventDefault).toHaveBeenCalledWith(); + }); + }); + + describe('showClientDescriptor()', () => { + it('should call the descriptor show method after setting the parent and the client id', () => { + controller.$.clientDescriptor = {show: () => {}}; + spyOn(controller.$.clientDescriptor, 'show'); + const event = {target: {}, preventDefault: () => {}}; + spyOn(event, 'preventDefault'); + const clientId = 999; + controller.showClientDescriptor(event, clientId); + + expect(controller.$.clientDescriptor.clientFk).toEqual(clientId); + expect(controller.$.clientDescriptor.show).toHaveBeenCalledWith(); + expect(event.preventDefault).toHaveBeenCalledWith(); + }); + }); + + describe('openPossibleTicketsDialog()', () => { + it('should call both refresh and show methods in posible tickets model and dialog', () => { + controller.$.possibleTicketsModel = {refresh: () => {}}; + spyOn(controller.$.possibleTicketsModel, 'refresh'); + controller.$.possibleTicketsDialog = {show: () => {}}; + spyOn(controller.$.possibleTicketsDialog, 'show'); + + controller.openPossibleTicketsDialog(); + + expect(controller.$.possibleTicketsModel.refresh).toHaveBeenCalledWith(); + expect(controller.$.possibleTicketsDialog.show).toHaveBeenCalledWith(); + }); + }); + + describe('setTicketsRoute()', () => { + it('should perform a POST query to add tickets to the route', done => { + controller.$.possibleTicketsModel = {save: () => {}}; + spyOn(controller.$.possibleTicketsModel, 'save').and.returnValue(Promise.resolve()); + controller.$.model = {data: [ + {id: 1, checked: false} + ]}; + + controller.route = {id: 111}; + + controller.possibleTickets = [ + {id: 2, checked: false}, + {id: 3, checked: true}, + {id: 4, checked: false}, + {id: 5, checked: true}, + ]; + + let expectedResult = [ + {checked: false, id: 1}, + {id: 3, routeFk: 111}, + {id: 5, routeFk: 111} + ]; + + controller.setTicketsRoute('ACCEPT').then(() => { + expect(controller.$.model.data).toEqual(expectedResult); + done(); + }).catch(done.fail); + }); + + it('should just return a promise', () => { + expect(controller.setTicketsRoute('CANCEL')).toEqual(jasmine.any(Promise)); + }); + }); +}); diff --git a/modules/route/front/tickets/locale/es.yml b/modules/route/front/tickets/locale/es.yml index d668e0a8a..7edeff17b 100644 --- a/modules/route/front/tickets/locale/es.yml +++ b/modules/route/front/tickets/locale/es.yml @@ -3,4 +3,6 @@ Open buscaman: Abrir buscaman Ticket removed from route: Ticket borrado de la ruta Order changed: Orden cambiado Delete ticket from route?: ¿Borrar ticket de la ruta? -Sort routes: Ordenar rutas \ No newline at end of file +Sort routes: Ordenar rutas +Add ticket: Añadir ticket +Tickets to add: Tickets a añadir \ No newline at end of file diff --git a/modules/ticket/back/methods/ticket-request/confirm.js b/modules/ticket/back/methods/ticket-request/confirm.js index 92dd06dae..53fb2527c 100644 --- a/modules/ticket/back/methods/ticket-request/confirm.js +++ b/modules/ticket/back/methods/ticket-request/confirm.js @@ -38,27 +38,27 @@ module.exports = Self => { try { let options = {transaction: tx}; - let item = await models.Item.findById(ctx.args.itemFk); + let item = await models.Item.findById(ctx.args.itemFk, null, options); if (!item) throw new UserError(`That item doesn't exists`); let request = await models.TicketRequest.findById(ctx.args.id, { include: {relation: 'ticket'} - }); + }, options); let [[stock]] = await Self.rawSql(`CALL vn.getItemVisibleAvailable(?,?,?,?)`, [ ctx.args.itemFk, request.ticket().shipped, request.ticket().warehouseFk, false - ]); + ], options); if (stock.available < 0) throw new UserError(`This item is not available`); if (request.saleFk) { - sale = await models.Sale.findById(request.saleFk); + sale = await models.Sale.findById(request.saleFk, null, options); sale.updateAttributes({ itemFk: ctx.args.itemFk, quantity: ctx.args.quantity, @@ -71,7 +71,11 @@ module.exports = Self => { quantity: ctx.args.quantity, concept: item.name }, options); - request.updateAttributes({saleFk: sale.id, itemFk: sale.itemFk, isOk: true}, options); + request.updateAttributes({ + saleFk: sale.id, + itemFk: sale.itemFk, + isOk: true + }, options); } query = `CALL vn.ticketCalculateSale(?)`; @@ -86,6 +90,8 @@ module.exports = Self => { }, options); await tx.commit(); + + return sale; } catch (error) { await tx.rollback(); throw error; diff --git a/modules/ticket/back/methods/ticket-request/deny.js b/modules/ticket/back/methods/ticket-request/deny.js index 817c90782..e663ef1bc 100644 --- a/modules/ticket/back/methods/ticket-request/deny.js +++ b/modules/ticket/back/methods/ticket-request/deny.js @@ -29,7 +29,7 @@ module.exports = Self => { let params = { isOk: false, - atenderFk: worker.id, + attenderFk: worker.id, response: ctx.args.observation, }; diff --git a/modules/ticket/back/methods/ticket-request/filter.js b/modules/ticket/back/methods/ticket-request/filter.js index 40c1f7652..82e1ee8e0 100644 --- a/modules/ticket/back/methods/ticket-request/filter.js +++ b/modules/ticket/back/methods/ticket-request/filter.js @@ -28,7 +28,7 @@ module.exports = Self => { type: 'Number', description: `Search by warehouse` }, { - arg: 'atenderFk', + arg: 'attenderFk', type: 'Number', description: `Search requests atended by the given worker` }, { @@ -65,7 +65,7 @@ module.exports = Self => { let worker = await Self.app.models.Worker.findOne({where: {userFk: userId}}); if (ctx.args.mine) - ctx.args.atenderFk = worker.id; + ctx.args.attenderFk = worker.id; let where = buildFilter(ctx.args, (param, value) => { switch (param) { @@ -75,7 +75,7 @@ module.exports = Self => { : {'t.nickname': {like: `%${value}%`}}; case 'ticketFk': return {'t.id': value}; - case 'atenderFk': + case 'attenderFk': return {'tr.atenderFk': value}; case 'isOk': return {'tr.isOk': value}; @@ -106,13 +106,13 @@ module.exports = Self => { tr.ticketFk, tr.quantity, tr.price, - tr.atenderFk, + tr.atenderFk attenderFk, tr.description, tr.response, tr.saleFk, tr.isOk, s.quantity AS saleQuantity, - s.itemFK, + s.itemFk, i.name AS itemDescription, t.shipped, t.nickname, diff --git a/modules/ticket/back/methods/ticket-request/specs/confirm.spec.js b/modules/ticket/back/methods/ticket-request/specs/confirm.spec.js index 574469d86..2383fe560 100644 --- a/modules/ticket/back/methods/ticket-request/specs/confirm.spec.js +++ b/modules/ticket/back/methods/ticket-request/specs/confirm.spec.js @@ -1,27 +1,14 @@ const app = require('vn-loopback/server/server'); describe('ticket-request confirm()', () => { - let request; - let sale; + let originalRequest; + let originalSale; let createdSaleId; afterAll(async done => { - const paramsForRequest = { - saleFk: request.saleFk, - isOk: request.isOk, - itemFk: request.itemFk, - ticketFk: request.ticketFk - }; - - const paramsForSale = { - itemFk: sale.itemFk, - quantity: sale.quantity, - concept: sale.concept, - }; - - await request.updateAttributes(paramsForRequest); - await sale.updateAttributes(paramsForSale); - app.models.Sale.destroyById(createdSaleId); + await originalRequest.updateAttributes(originalRequest); + await originalSale.updateAttributes(originalSale); + await app.models.Sale.destroyById(createdSaleId); done(); }); @@ -65,10 +52,11 @@ describe('ticket-request confirm()', () => { const itemId = 1; const quantity = 10; - request = await app.models.TicketRequest.findById(requestId); - sale = await app.models.Sale.findById(saleId); + originalRequest = await app.models.TicketRequest.findById(requestId); + originalSale = await app.models.Sale.findById(saleId); - request.updateAttributes({saleFk: saleId}); + const request = await app.models.TicketRequest.findById(requestId); + await request.updateAttributes({saleFk: saleId}); let ctx = {req: {accessToken: {userId: 9}}, args: { itemFk: itemId, @@ -89,7 +77,8 @@ describe('ticket-request confirm()', () => { const itemId = 1; const quantity = 10; - request.updateAttributes({saleFk: null}); + const request = await app.models.TicketRequest.findById(requestId); + await request.updateAttributes({saleFk: null}); let ctx = {req: {accessToken: {userId: 9}}, args: { itemFk: itemId, diff --git a/modules/ticket/back/methods/ticket-request/specs/deny.spec.js b/modules/ticket/back/methods/ticket-request/specs/deny.spec.js index cac63586e..04152fa21 100644 --- a/modules/ticket/back/methods/ticket-request/specs/deny.spec.js +++ b/modules/ticket/back/methods/ticket-request/specs/deny.spec.js @@ -5,7 +5,7 @@ describe('ticket-request deny()', () => { afterAll(async done => { let params = { isOk: null, - atenderFk: request.atenderFk, + attenderFk: request.attenderFk, response: null, }; diff --git a/modules/ticket/back/methods/ticket-request/specs/filter.spec.js b/modules/ticket/back/methods/ticket-request/specs/filter.spec.js index 24e74e4df..329688866 100644 --- a/modules/ticket/back/methods/ticket-request/specs/filter.spec.js +++ b/modules/ticket/back/methods/ticket-request/specs/filter.spec.js @@ -37,7 +37,7 @@ describe('ticket-request filter()', () => { }); it('should return the ticket request matching the atender ID', async() => { - let ctx = {req: {accessToken: {userId: 9}}, args: {atenderFk: 35}}; + let ctx = {req: {accessToken: {userId: 9}}, args: {attenderFk: 35}}; let result = await app.models.TicketRequest.filter(ctx); let requestId = result[0].id; diff --git a/modules/ticket/back/methods/ticket/isEmpty.js b/modules/ticket/back/methods/ticket/isEmpty.js index 7f9de98b1..0465589e6 100644 --- a/modules/ticket/back/methods/ticket/isEmpty.js +++ b/modules/ticket/back/methods/ticket/isEmpty.js @@ -40,7 +40,8 @@ module.exports = function(Self) { }, options); const hasPurchaseRequests = await models.TicketRequest.count({ - ticketFk: id + ticketFk: id, + isOk: true }, options); const isEmpty = !hasSales && !hasPackages && diff --git a/modules/ticket/back/methods/ticket/uploadFile.js b/modules/ticket/back/methods/ticket/uploadFile.js index 7c968f5c9..e5ea465cb 100644 --- a/modules/ticket/back/methods/ticket/uploadFile.js +++ b/modules/ticket/back/methods/ticket/uploadFile.js @@ -7,36 +7,34 @@ module.exports = Self => { type: 'Number', description: 'The ticket id', http: {source: 'path'} - }, - { + }, { arg: 'warehouseId', type: 'Number', - description: '' - }, - { + description: 'The warehouse id', + required: true + }, { arg: 'companyId', type: 'Number', - description: '' - }, - { + description: 'The company id', + required: true + }, { arg: 'dmsTypeId', type: 'Number', - description: '' - }, - { + description: 'The dms type id', + required: true + }, { arg: 'reference', type: 'String', - description: '' - }, - { + required: true + }, { arg: 'description', type: 'String', - description: '' - }, - { + required: true + }, { arg: 'hasFile', type: 'Boolean', - description: '' + description: 'True if has an attached file', + required: true }], returns: { type: 'Object', diff --git a/modules/ticket/back/models/ticket-request.json b/modules/ticket/back/models/ticket-request.json index dfb609e3c..7f1cb4b02 100644 --- a/modules/ticket/back/models/ticket-request.json +++ b/modules/ticket/back/models/ticket-request.json @@ -33,9 +33,12 @@ "isOk": { "type": "Boolean" }, - "atenderFk": { + "attenderFk": { "type": "Number", - "required": true + "required": true, + "mysql": { + "columnName": "atenderFk" + } }, "response": { "type": "String" @@ -55,7 +58,7 @@ "atender": { "type": "belongsTo", "model": "Worker", - "foreignKey": "atenderFk" + "foreignKey": "attenderFk" }, "requester": { "type": "belongsTo", diff --git a/modules/ticket/front/descriptor/index.js b/modules/ticket/front/descriptor/index.js index a33e57e14..05b4e719a 100644 --- a/modules/ticket/front/descriptor/index.js +++ b/modules/ticket/front/descriptor/index.js @@ -176,7 +176,7 @@ class Controller { links.btnTwo = { icon: 'icon-stowaway', state: `ticket.card.summary({id: ${value.stowaway.shipFk}})`, - tooltip: 'Ship' + tooltip: 'Ship stowaways' }; } diff --git a/modules/ticket/front/request/create/index.html b/modules/ticket/front/request/create/index.html index 1b7044c20..dd10d8afe 100644 --- a/modules/ticket/front/request/create/index.html +++ b/modules/ticket/front/request/create/index.html @@ -18,7 +18,7 @@ + ng-click="$ctrl.showWorkerDescriptor($event, request.attenderFk)"> {{::request.atender.user.nickname | dashIfEmpty}} diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index 38ed007a0..b818d86e0 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -127,7 +127,7 @@ {{sale.quantity}} - diff --git a/modules/worker/back/methods/worker-time-control/addAutoTime.js b/modules/worker/back/methods/worker-time-control/addAutoTime.js deleted file mode 100644 index 7d58d2c22..000000000 --- a/modules/worker/back/methods/worker-time-control/addAutoTime.js +++ /dev/null @@ -1,48 +0,0 @@ -/* -Author : Enrique Blasco BLanquer -Date: 27 de mayo de 2019 -*/ -module.exports = Self => { - Self.remoteMethodCtx('addAutoTime', { - description: 'Adds a new hour registry by app in manual 0', - accessType: 'WRITE', - accepts: [{ - arg: 'data', - type: 'object', - required: true, - description: 'timed', - http: {source: 'body'} - }], - returns: [{ - type: 'Object', - root: true - }], - http: { - path: `/addAutoTime`, - verb: 'POST' - } - }); - - Self.addAutoTime = async(ctx, data) => { - const myUserId = ctx.req.accessToken.userId; - - // get all worked time control, needed to calculate order - let hours = await Self.rawSql(`SELECT * FROM vn.workerTimeControl - WHERE userFk = ? - AND DATE(timed) = CURDATE() - ORDER BY timed DESC LIMIT 1`, [myUserId]); - - // 1 get next order - let order = 0; - if (hours.length > 0) - order = hours[hours.length - 1].order; - - // 2 create element in db - return Self.create({ - userFk: myUserId, - timed: data.timed, - order: order + 1, - manual: 0 - }); - }; -}; diff --git a/modules/worker/back/methods/worker-time-control/getHoursWorked.js b/modules/worker/back/methods/worker-time-control/getHoursWorked.js deleted file mode 100644 index 315b68cd1..000000000 --- a/modules/worker/back/methods/worker-time-control/getHoursWorked.js +++ /dev/null @@ -1,121 +0,0 @@ -/* -Author : Enrique Blasco BLanquer -Date: 28 de mayo de 2019 -*/ -module.exports = Self => { - Self.remoteMethodCtx('getHoursWorked', { - description: 'Get worked hours in current week, month and year', - accessType: 'WRITE', - returns: [{ - type: 'Object', - root: true - }], - http: { - path: `/getHoursWorked`, - verb: 'GET' - } - }); - - Self.getHoursWorked = async(ctx, data) => { - let totalHours = 0; // total hours worked in one year - let totalMinutes = 0; // total minutes worked in one year - let totalHoursMonth = 0; // total hours worked in one month - let totalMinutesMonth = 0; // total minutes worked in one month - let totalHoursWeek = 0; // total hours worked in one week - let totalMinutesWeek = 0; // total minutes worked in one week - const myUserId = ctx.req.accessToken.userId; // user id - let today = new Date(); // needed to calculate total hours worked to current date - let fromDate = today.getFullYear() + '-01-01'; // from date, current year - let toDate = today.getFullYear() + '-12-31'; // to date, current year - - - // 1 hours worked in a year - let hoursYear = await Self.rawSql(`SELECT wtc.userFk, DATE(wtc.timed) dated, - UNIX_TIMESTAMP(MIN(timed))timedStart, - SEC_TO_TIME(SUM(if( mod(wtc.order,2)=1, - UNIX_TIMESTAMP(timed) *-1, - UNIX_TIMESTAMP(timed)))) timeWorkDay - FROM vn.workerTimeControl wtc - WHERE wtc.timed BETWEEN ? AND ? AND userFk = ? - GROUP BY wtc.userFk,dated ORDER BY dated DESC`, [fromDate, toDate, myUserId]); - - - // 2 Get days of week - let week = []; - // Starting Monday not Sunday - let current = new Date(); - current.setDate((current.getDate() - current.getDay() + 1)); - for (let i = 0; i < 7; i++) { - week.push( - new Date(current) - ); - current.setDate(current.getDate() + 1); - } - - // 3 I have all timed control for one year... NOW I CALCULATE TOTAL HOURS IN YEAR, MONTH, WEEK, Let's GO! - for (hour of hoursYear) { - if (parseInt(hour.timeWorkDay.split(':')[0]) > 0) { - // YEAR - totalHours += parseInt(hour.timeWorkDay.split(':')[0]); - totalMinutes += parseInt(hour.timeWorkDay.split(':')[1]); - // If it exceeds 5 hours we add 20 minutes of breakfast. - if (parseInt(hour.timeWorkDay.split(':')[0]) >= 5) - totalMinutes += 20; - // MONTH - - if ((new Date(hour.dated)).getMonth() == today.getMonth()) { - totalHoursMonth += parseInt(hour.timeWorkDay.split(':')[0]); - totalMinutesMonth += parseInt(hour.timeWorkDay.split(':')[1]); - // If it exceeds 5 hours we add 20 minutes of breakfast. - if (parseInt(hour.timeWorkDay.split(':')[0]) >= 5) - totalMinutesMonth += 20; - } - // WEEK - for (day of week) { - let dayOfWeek = new Date(day); - let dayOfCurrentWeek = new Date(hour.dated); - if (dayOfWeek.getMonth() == dayOfCurrentWeek.getMonth() && dayOfWeek.getDate() == dayOfCurrentWeek.getDate()) { - totalHoursWeek += parseInt(hour.timeWorkDay.split(':')[0]); - totalMinutesWeek += parseInt(hour.timeWorkDay.split(':')[1]); - // If it exceeds 5 hours we add 20 minutes of breakfast. - if (parseInt(hour.timeWorkDay.split(':')[0]) >= 5) - totalMinutesWeek += 20; - break; - } - } - } - } - - // TOTAL WORKED HOURS IN THE YEAR - totalHours += totalMinutes / 60; - totalHours = decimalToHour(totalHours); - - // TOTAL WORKED HOURS IN THE MONTH - totalHoursMonth += totalMinutesMonth / 60; - totalHoursMonth = decimalToHour(totalHoursMonth); - - // TOTAL WORKED HOURS IN THE WEEK - totalHoursWeek += totalMinutesWeek / 60; - totalHoursWeek = decimalToHour(totalHoursWeek); - - return { - 'totalWorekdYear': totalHours, - 'totalWorekdMonth': totalHoursMonth, - 'totalWorkedWeek': totalHoursWeek - }; - }; -}; - -/* -function to calculate hours and minutes from decimal value -*/ -function decimalToHour(value) { - let decimalTime = parseFloat(value); - decimalTime = decimalTime * 60 * 60; - let hoursDay = Math.floor((decimalTime / (60 * 60))); - decimalTime = decimalTime - (hoursDay * 60 * 60); - let minutesDay = Math.floor((decimalTime / 60)); - return hoursDay + ':' + minutesDay; -} - - diff --git a/modules/worker/back/methods/worker-time-control/getWorkedWeek.js b/modules/worker/back/methods/worker-time-control/getWorkedWeek.js deleted file mode 100644 index 67e864344..000000000 --- a/modules/worker/back/methods/worker-time-control/getWorkedWeek.js +++ /dev/null @@ -1,78 +0,0 @@ -/* -Author : Enrique Blasco BLanquer -Date: 29 de mayo de 2019 -*/ -module.exports = Self => { - Self.remoteMethodCtx('getWorkedWeek', { - description: 'get worked week info', - accessType: 'WRITE', - returns: [{ - type: 'Object', - root: true - }], - http: { - path: `/getWorkedWeek`, - verb: 'GET' - } - }); - - Self.getWorkedWeek = async(ctx, data) => { - const myUserId = ctx.req.accessToken.userId; // user id - let lastDate = new Date('1986-09-24'); // reference date - let diff = 0; // difference of value between two dates - let total = 0; // total hours - - // 1 Get days of week - let week = []; - // 2 Starting Monday not Sunday - let current = new Date(); - current.setDate((current.getDate() - current.getDay() + 1)); - for (let i = 0; i < 7; i++) { - week.push( - new Date(current) - ); - current.setDate(current.getDate() + 1); - } - - let fromDate = week[0].getFullYear() + '-' + (week[0].getMonth() + 1) + '-' + week[0].getDate(); - let toDate = week[week.length - 1].getFullYear() + '-' + (week[week.length - 1].getMonth() + 1) + '-' + week[week.length - 1].getDate(); - - - // 3 hours worked in a current week - let hoursWeek = await Self.rawSql(`SELECT wtc.timed ,wtc.order - FROM vn.workerTimeControl wtc - WHERE userFk = ? - AND DATE(timed) BETWEEN ? AND ? ORDER BY timed DESC;`, [myUserId, fromDate, toDate]); - - // 4 treat data - let isFirst = true; - for (let i = hoursWeek.length - 1; i >= 0; i--) { - let d = new Date(hoursWeek[i].timed); - if (isFirst) { - lastDate = d; - isFirst = false; - } else { - if (lastDate.getDate() === d.getDate()) { - diff += Math.abs(d.getTime() - lastDate.getTime()); - lastDate = d; - } else { - total += diff; - diff = 0; - lastDate = d; - } - } - } - total += diff; - - // 5 calculate hours and minutes - let decimalTime = total / 1000 / 3600; - decimalTime = decimalTime * 60 * 60; - let hours = Math.floor((decimalTime / (60 * 60))); - decimalTime = decimalTime - (hours * 60 * 60); - let minutes = Math.floor((decimalTime / 60)); - - return {'timeds': hoursWeek, 'totalWorked': hours + ':' + minutes}; - }; -}; - - diff --git a/modules/worker/back/methods/worker-time-control/specs/addAutoTime.spec.js b/modules/worker/back/methods/worker-time-control/specs/addAutoTime.spec.js deleted file mode 100644 index bdc5d6ecb..000000000 --- a/modules/worker/back/methods/worker-time-control/specs/addAutoTime.spec.js +++ /dev/null @@ -1,11 +0,0 @@ -const app = require('vn-loopback/server/server'); - -describe('workerTimeControl addAutoTime()', () => { - it('should return an undefined value', async() => { - let ctx = {req: {accessToken: {userId: 9}}}; - let data = {'timed': new Date()}; - let result = await app.models.WorkerTimeControl.addAutoTime(ctx, data); - - expect(result).toBeUndefined(); - }); -}); diff --git a/modules/worker/back/methods/worker-time-control/specs/getHoursWorked.spec.js b/modules/worker/back/methods/worker-time-control/specs/getHoursWorked.spec.js deleted file mode 100644 index 2cfd28b77..000000000 --- a/modules/worker/back/methods/worker-time-control/specs/getHoursWorked.spec.js +++ /dev/null @@ -1,10 +0,0 @@ -const app = require('vn-loopback/server/server'); - -describe('workerTimeControl getHoursWorked()', () => { - it('should return an totalWorkedYear to be defined', async() => { - let ctx = {req: {accessToken: {userId: 9}}}; - let result = await app.models.WorkerTimeControl.getHoursWorked(ctx, null); - - expect(result.totalWorekdYear).toBeDefined(); - }); -}); diff --git a/modules/worker/back/methods/worker-time-control/specs/getWorkedWeek.spec.js b/modules/worker/back/methods/worker-time-control/specs/getWorkedWeek.spec.js deleted file mode 100644 index 4ea7bc727..000000000 --- a/modules/worker/back/methods/worker-time-control/specs/getWorkedWeek.spec.js +++ /dev/null @@ -1,10 +0,0 @@ -const app = require('vn-loopback/server/server'); - -describe('workerTimeControl getWorkedWeek()', () => { - it('should return an timeds to be defined', async() => { - let ctx = {req: {accessToken: {userId: 9}}}; - let result = await app.models.WorkerTimeControl.getWorkedWeek(ctx, null); - - expect(result.timeds).toBeDefined(); - }); -}); diff --git a/modules/worker/back/model-config.json b/modules/worker/back/model-config.json index df2b776fb..35a039d3c 100644 --- a/modules/worker/back/model-config.json +++ b/modules/worker/back/model-config.json @@ -49,14 +49,5 @@ }, "Device": { "dataSource": "vn" - }, - "UserPhoneType": { - "dataSource": "vn" - }, - "UserPhone": { - "dataSource": "vn" - }, - "UserLog": { - "dataSource": "vn" } } diff --git a/modules/worker/back/models/worker-time-control.js b/modules/worker/back/models/worker-time-control.js index 4ec5bf8dd..4a065f430 100644 --- a/modules/worker/back/models/worker-time-control.js +++ b/modules/worker/back/models/worker-time-control.js @@ -3,9 +3,6 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { require('../methods/worker-time-control/filter')(Self); require('../methods/worker-time-control/addTime')(Self); - require('../methods/worker-time-control/addAutoTime')(Self); - require('../methods/worker-time-control/getHoursWorked')(Self); - require('../methods/worker-time-control/getWorkedWeek')(Self); Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') diff --git a/modules/worker/back/models/worker.json b/modules/worker/back/models/worker.json index 7456a3caa..c5c770fcd 100644 --- a/modules/worker/back/models/worker.json +++ b/modules/worker/back/models/worker.json @@ -54,6 +54,12 @@ "type": "hasMany", "model": "WorkerTeamCollegues", "foreignKey": "workerFk" + }, + "phones": { + "type": "hasMany", + "model": "UserPhone", + "foreignKey": "userFk", + "primaryKey": "userFk" } } } \ No newline at end of file diff --git a/modules/worker/front/basic-data/index.html b/modules/worker/front/basic-data/index.html index 7303bccc1..0ba32eff9 100644 --- a/modules/worker/front/basic-data/index.html +++ b/modules/worker/front/basic-data/index.html @@ -23,14 +23,6 @@ rule>
- - - -
diff --git a/modules/worker/front/card/index.js b/modules/worker/front/card/index.js index 5e1e33510..eefa6360a 100644 --- a/modules/worker/front/card/index.js +++ b/modules/worker/front/card/index.js @@ -40,6 +40,12 @@ class Controller { relation: 'department' } } + }, { + relation: 'phones', + scope: { + fields: ['phone'], + order: 'typeFk ASC' + } } ] }; diff --git a/modules/worker/front/descriptor/index.html b/modules/worker/front/descriptor/index.html index cf5279ad8..26c2f193e 100644 --- a/modules/worker/front/descriptor/index.html +++ b/modules/worker/front/descriptor/index.html @@ -26,20 +26,16 @@ - +
-
+ + \ No newline at end of file diff --git a/modules/worker/front/descriptor/index.js b/modules/worker/front/descriptor/index.js index c8c2cb5e6..82718a29e 100644 --- a/modules/worker/front/descriptor/index.js +++ b/modules/worker/front/descriptor/index.js @@ -1,7 +1,41 @@ import ngModule from '../module'; +class Controller { + constructor($http, $state) { + this.$state = $state; + this.$http = $http; + } + + get worker() { + return this._worker; + } + + set worker(value) { + this._worker = value; + + if (!value) return; + + this._quicklinks = { + btnOne: { + icon: 'person', + state: `client.card.summary({id: ${value.userFk}})`, + tooltip: 'Go to client' + } + }; + } + + set quicklinks(value = {}) { + this._quicklinks = Object.assign(value, this._quicklinks); + } + + get quicklinks() { + return this._quicklinks; + } +} + ngModule.component('vnWorkerDescriptor', { template: require('./index.html'), + controller: Controller, bindings: { worker: '<' } diff --git a/modules/worker/front/index.js b/modules/worker/front/index.js index 71c529ea8..6f4a7fe34 100644 --- a/modules/worker/front/index.js +++ b/modules/worker/front/index.js @@ -12,3 +12,4 @@ import './department'; import './calendar'; import './time-control'; import './log'; +import './phones'; diff --git a/modules/worker/front/phones/index.html b/modules/worker/front/phones/index.html new file mode 100644 index 000000000..2c172bc2f --- /dev/null +++ b/modules/worker/front/phones/index.html @@ -0,0 +1,54 @@ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
diff --git a/modules/worker/front/phones/index.js b/modules/worker/front/phones/index.js new file mode 100644 index 000000000..3810832d9 --- /dev/null +++ b/modules/worker/front/phones/index.js @@ -0,0 +1,48 @@ +import ngModule from '../module'; + +class Controller { + constructor($scope) { + this.$scope = $scope; + } + + get worker() { + return this._worker; + } + + set worker(value) { + this._worker = value; + if (value) + this.setLink(value); + } + + setLink(value) { + this.$scope.$applyAsync(()=> { + this.$scope.model.link = {userFk: value.userFk}; + this.$scope.model.refresh(); + }); + } + + onSubmit() { + this.$scope.watcher.check(); + this.$scope.model.save().then(() => { + this.$scope.watcher.updateOriginalData(); + this.$scope.watcher.notifySaved(); + this.card.reload(); + }); + } + + add() { + this.$scope.model.insert(); + } +} + +Controller.$inject = ['$scope']; + +ngModule.component('vnWorkerPhones', { + template: require('./index.html'), + controller: Controller, + require: {card: '^vnWorkerCard'}, + bindings: { + worker: '<' + } +}); diff --git a/modules/worker/front/phones/index.spec.js b/modules/worker/front/phones/index.spec.js new file mode 100644 index 000000000..0b3e9dc11 --- /dev/null +++ b/modules/worker/front/phones/index.spec.js @@ -0,0 +1,26 @@ +import './index'; + +describe('Component vnWorkerPhones', () => { + let controller; + + beforeEach(angular.mock.module('worker', $translateProvider => { + $translateProvider.translations('en', {}); + })); + + beforeEach(angular.mock.inject(($componentController, $rootScope) => { + let $scope = $rootScope.$new(); + controller = $componentController('vnWorkerPhones', $scope); + controller.$scope.model = {link: 1}; + controller.$scope.$applyAsync = () => {}; + })); + + describe('setLink()', () => { + it('set the link in the model and refreshes it', () => { + spyOn(controller.$scope, '$applyAsync'); + let value = {userFk: 106}; + controller.setLink(value); + + expect(controller.$scope.$applyAsync).toHaveBeenCalledWith(jasmine.any(Function)); + }); + }); +}); diff --git a/modules/worker/front/phones/locale/es.yml b/modules/worker/front/phones/locale/es.yml new file mode 100644 index 000000000..8628f38ee --- /dev/null +++ b/modules/worker/front/phones/locale/es.yml @@ -0,0 +1,4 @@ +Phones: Teléfonos +Type: Tipo +Remove phone: Eliminar teléfono +Add phone: Añadir teléfono \ No newline at end of file diff --git a/modules/worker/front/routes.json b/modules/worker/front/routes.json index 1b4f9ec2a..e2fa9c165 100644 --- a/modules/worker/front/routes.json +++ b/modules/worker/front/routes.json @@ -7,7 +7,8 @@ {"state": "worker.card.basicData", "icon": "settings"}, {"state": "worker.card.pbx", "icon": "icon-pbx"}, {"state": "worker.card.calendar", "icon": "icon-calendar"}, - {"state": "worker.card.timeControl", "icon": "access_time"} + {"state": "worker.card.timeControl", "icon": "access_time"}, + {"state": "worker.card.phones", "icon": "contact_phone"} ], "routes": [ { @@ -79,6 +80,15 @@ "state": "worker.department", "component": "vn-worker-department", "description": "Departments" + }, + { + "url": "/phones", + "state": "worker.card.phones", + "component": "vn-worker-phones", + "description": "Phones", + "params": { + "worker": "$ctrl.worker" + } } ] } \ No newline at end of file diff --git a/modules/worker/front/summary/index.html b/modules/worker/front/summary/index.html index e2c93ea48..de86c1f84 100644 --- a/modules/worker/front/summary/index.html +++ b/modules/worker/front/summary/index.html @@ -12,8 +12,9 @@ - + diff --git a/modules/worker/front/summary/index.js b/modules/worker/front/summary/index.js index 24656d601..f9f4e688b 100644 --- a/modules/worker/front/summary/index.js +++ b/modules/worker/front/summary/index.js @@ -50,6 +50,12 @@ class Controller { relation: 'department' } } + }, { + relation: 'phones', + scope: { + fields: ['phone'], + order: 'typeFk ASC' + } } ] }; diff --git a/print/config/print.json b/print/config/print.json index cb98421fc..7ad2e1316 100755 --- a/print/config/print.json +++ b/print/config/print.json @@ -5,6 +5,13 @@ "senderMail": "nocontestar@verdnatura.es", "senderName": "Verdnatura" }, + "pdf": { + "format": "A4", + "border": "1.5cm", + "footer": { + "height": "55px" + } + }, "mysql": { "host": "localhost", "port": 3306, diff --git a/print/config/routes.json b/print/config/routes.json index e658f4cc4..58d46193c 100644 --- a/print/config/routes.json +++ b/print/config/routes.json @@ -17,6 +17,7 @@ {"type": "report", "name": "rpt-zone"}, {"type": "report", "name": "rpt-route"}, {"type": "report", "name": "rpt-lcr"}, + {"type": "report", "name": "rpt-item-label"}, {"type": "static", "name": "email-header"}, {"type": "static", "name": "email-footer"}, {"type": "static", "name": "report-header"}, diff --git a/print/lib/reportEngine.js b/print/lib/reportEngine.js index 8c771c449..0903c0f78 100644 --- a/print/lib/reportEngine.js +++ b/print/lib/reportEngine.js @@ -2,9 +2,9 @@ const Vue = require('vue'); const VueI18n = require('vue-i18n'); const renderer = require('vue-server-renderer').createRenderer(); const fs = require('fs-extra'); -// const pdf = require('phantom-html2pdf'); const pdf = require('html-pdf'); const juice = require('juice'); +const config = require('./config'); Vue.use(VueI18n); @@ -104,13 +104,11 @@ module.exports = { async toPdf(name, ctx) { const html = await this.render(name, ctx); - const options = { - format: 'A4', - border: '1.5cm', - footer: { - height: '55px', - } - }; + let options = config.pdf; + + const optionsPath = `${this.path}/${name}/options.json`; + if (fs.existsSync(optionsPath)) + options = Object.assign(options, require(optionsPath)); return new Promise(resolve => { pdf.create(html, options).toStream((err, stream) => { diff --git a/print/package.json b/print/package.json index bb7c32a3e..0332817c2 100755 --- a/print/package.json +++ b/print/package.json @@ -18,6 +18,7 @@ "juice": "^5.0.1", "mysql2": "^1.6.5", "nodemailer": "^4.7.0", + "qrcode": "^1.4.2", "strftime": "^0.10.0", "vue": "^2.6.7", "vue-i18n": "^8.8.2", diff --git a/print/report/rpt-item-label/assets/css/index.js b/print/report/rpt-item-label/assets/css/index.js new file mode 100644 index 000000000..515dea750 --- /dev/null +++ b/print/report/rpt-item-label/assets/css/index.js @@ -0,0 +1,8 @@ +const CssReader = require(`${appPath}/lib/cssReader`); + +module.exports = new CssReader([ + `${appPath}/common/css/layout.css`, + `${appPath}/common/css/report.css`, + `${appPath}/common/css/misc.css`, + `${__dirname}/style.css`]) + .mergeStyles(); diff --git a/print/report/rpt-item-label/assets/css/style.css b/print/report/rpt-item-label/assets/css/style.css new file mode 100644 index 000000000..1101604b9 --- /dev/null +++ b/print/report/rpt-item-label/assets/css/style.css @@ -0,0 +1,88 @@ +* { + box-sizing: border-box; +} +.label { + font-size: 1.2em; +} + +.barcode { + float: left; + width: 40%; +} + +.barcode h1 { + text-align: center; + font-size: 1.8em; + margin: 0 0 10px 0 +} + +.barcode .image { + text-align: center +} + +.barcode .image img { + width: 170px +} + +.data { + float: left; + width: 60%; +} + +.data .header { + background-color: #000; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + margin-bottom: 25px; + text-align: right; + font-size: 1.2em; + padding: 0.2em; + color: #FFF +} + +.data .color, +.data .producer { + text-transform: uppercase; + text-align: right; + font-size: 1.5em; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.data .producer { + text-justify: inter-character; +} + +.data .details { + border-top: 4px solid #000; + padding-top: 2px; +} + +.data .details .package { + padding-right: 5px; + float: left; + width: 50%; +} + +.package .packing, +.package .dated, +.package .labelNumber { + text-align: right +} + +.package .packing { + font-size: 1.8em; + font-weight: 400 +} + +.data .details .size { + background-color: #000; + text-align: center; + font-size: 3em; + padding: 0.2em 0; + float: left; + width: 50%; + color: #FFF +} \ No newline at end of file diff --git a/print/report/rpt-item-label/index.html b/print/report/rpt-item-label/index.html new file mode 100644 index 000000000..0b46aee14 --- /dev/null +++ b/print/report/rpt-item-label/index.html @@ -0,0 +1,28 @@ + + + +
+
+
+

{{item.id}}

+
+ +
+
+
+
{{item.name}}
+
{{tags.color}}
+
{{tags.producer}}
+
+
+
{{packing()}}
+
{{dated}}
+
{{labelPage()}}
+
+
{{item.size}}
+
+
+
+
+ + \ No newline at end of file diff --git a/print/report/rpt-item-label/index.js b/print/report/rpt-item-label/index.js new file mode 100755 index 000000000..922965eb0 --- /dev/null +++ b/print/report/rpt-item-label/index.js @@ -0,0 +1,83 @@ +const database = require(`${appPath}/lib/database`); +const UserException = require(`${appPath}/lib/exceptions/userException`); +const strftime = require('strftime'); +const qrcode = require('qrcode'); + +module.exports = { + name: 'rpt-item-label', + async asyncData(ctx, params) { + Object.assign(this, this.methods); + + if (!params.itemId) + throw new UserException('No item id specified'); + + if (!params.warehouseId) + throw new UserException('No warehouse id specified'); + + const data = { + item: await this.fetchItem(params.itemId, params.warehouseId), + tags: await this.fetchItemTags(params.itemId), + barcode: await this.getBarcodeBase64(params.itemId), + labelNumber: params.labelNumber, + totalLabels: params.totalLabels + }; + + return data; + }, + computed: { + dated() { + return strftime('%W/%d', new Date()); + } + }, + methods: { + fetchItem(id, warehouseId) { + return database.pool.query( + `SELECT + i.id, + i.name, + i.stems, + i.size, + b.packing + FROM vn.item i + JOIN cache.last_buy clb ON clb.item_id = i.id + JOIN vn.buy b ON b.id = clb.buy_id + JOIN vn.entry e ON e.id = b.entryFk + WHERE i.id = ? AND clb.warehouse_id = ?`, [id, warehouseId]) + .then(([rows]) => { + if (rows.length == 0) + throw new UserException(`Item #${id} not found on warehouse #${warehouseId}`); + return rows[0]; + }); + }, + fetchItemTags(itemId) { + return database.pool.query( + `SELECT t.code, t.name, it.value + FROM vn.itemTag it + JOIN vn.tag t ON t.id = it.tagFk + WHERE it.itemFk = ?`, [itemId]) + .then(([rows]) => { + const tags = {}; + rows.forEach(row => tags[row.code] = row.value); + + return tags; + }); + }, + getBarcodeBase64(itemId) { + return qrcode.toDataURL(itemId, {margin: 0}); + }, + packing() { + const stems = this.item.stems ? this.item.stems : 1; + return `${this.item.packing}x${stems}`; + }, + labelPage() { + const labelNumber = this.labelNumber ? this.labelNumber : 1; + const totalLabels = this.totalLabels ? this.totalLabels : 1; + + return `${labelNumber}/${totalLabels}`; + } + }, + components: { + 'report-header': require('../report-header'), + 'report-footer': require('../report-footer'), + }, +}; diff --git a/print/report/rpt-item-label/locale.js b/print/report/rpt-item-label/locale.js new file mode 100644 index 000000000..b8d524274 --- /dev/null +++ b/print/report/rpt-item-label/locale.js @@ -0,0 +1,24 @@ +module.exports = { + messages: { + es: { + title: 'Recibo', + date: 'Fecha', + payed: 'En {0}, a {1} de {2} de {3}', + client: 'Cliente {0}', + months: [ + 'Enero', + 'Febrero', + 'Marzo', + 'Abril', + 'Mayo', + 'Junio', + 'Julio', + 'Agosto', + 'Septiembre', + 'Octubre', + 'Noviembre', + 'Diciembre' + ] + }, + }, +}; diff --git a/print/report/rpt-item-label/options.json b/print/report/rpt-item-label/options.json new file mode 100644 index 000000000..6b00b0443 --- /dev/null +++ b/print/report/rpt-item-label/options.json @@ -0,0 +1,10 @@ +{ + "format": "A4", + "orientation": "landscape", + "width": "10.4cm", + "height": "4.8cm", + "border": "0cm", + "footer": { + "height": "0" + } +} \ No newline at end of file