diff --git a/db/changes/10060-summer/00-ACL.sql b/db/changes/10060-summer/00-ACL.sql index dd9a24d63..fab3cbe72 100644 --- a/db/changes/10060-summer/00-ACL.sql +++ b/db/changes/10060-summer/00-ACL.sql @@ -1,2 +1,4 @@ INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) - VALUES ('Zone', 'editPrices', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss'); + VALUES + ('Zone', 'editPrices', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss'), + ('Ticket', 'addSale', 'WRITE', 'ALLOW', 'ROLE', 'employee'); diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index b48dc1ca2..2d7307f3a 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -398,18 +398,18 @@ export default { firstSaleText: 'vn-table div > vn-tbody > vn-tr:nth-child(1)', firstSaleThumbnailImage: 'vn-ticket-sale:nth-child(1) vn-tr:nth-child(1) vn-td:nth-child(3) > img', firstSaleZoomedImage: 'body > div > div > img', - firstSaleQuantity: 'vn-textfield[model="sale.quantity"]:nth-child(1) input', - firstSaleQuantityCell: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td-editable', + firstSaleQuantity: 'vn-input-number[model="sale.quantity"]:nth-child(1) input', + firstSaleQuantityCell: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td-editable:nth-child(5)', firstSaleQuantityClearInput: 'vn-textfield[model="sale.quantity"] div.suffix > i', - firstSaleID: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(4) > span', + firstSaleID: 'vn-ticket-sale:nth-child(1) vn-td-editable:nth-child(4) text > span', firstSalePrice: 'vn-ticket-sale:nth-child(1) vn-tr:nth-child(1) > vn-td:nth-child(7)', firstSalePriceInput: 'vn-ticket-sale:nth-child(1) vn-popover.edit.dialog-summary.ng-isolate-scope.vn-popover.shown vn-input-number input', firstSaleDiscount: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(8)', firstSaleDiscountInput: 'vn-ticket-sale:nth-child(1) vn-ticket-sale-edit-discount vn-input-number input', firstSaleImport: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(9)', firstSaleReservedIcon: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td:nth-child(2) > vn-icon:nth-child(3)', - firstSaleColour: 'vn-ticket-sale vn-tr:nth-child(1) vn-td:nth-child(6) section:nth-child(1)', - firstSaleLength: 'vn-ticket-sale vn-tr:nth-child(1) vn-td:nth-child(6) section:nth-child(3)', + firstSaleColour: 'vn-ticket-sale vn-tr:nth-child(1) vn-td-editable:nth-child(6) section:nth-child(1)', + firstSaleLength: 'vn-ticket-sale vn-tr:nth-child(1) vn-td-editable:nth-child(6) section:nth-child(3)', firstSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(1) vn-check[field="sale.checked"] md-checkbox', secondSaleColour: 'vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(6) section:nth-child(5)', secondSalePrice: 'vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(7) > span', diff --git a/e2e/paths/03-worker-module/02_time_control.spec.js b/e2e/paths/03-worker-module/02_time_control.spec.js index d53a02056..89273d26c 100644 --- a/e2e/paths/03-worker-module/02_time_control.spec.js +++ b/e2e/paths/03-worker-module/02_time_control.spec.js @@ -17,6 +17,7 @@ describe('Worker time control path', () => { const scanTime = '07:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.mondayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText'); @@ -28,6 +29,7 @@ describe('Worker time control path', () => { const scanTime = '10:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.mondayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText'); @@ -39,6 +41,7 @@ describe('Worker time control path', () => { const scanTime = '15:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.mondayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText'); @@ -50,6 +53,7 @@ describe('Worker time control path', () => { const scanTime = '10:20'; const result = await nightmare .waitToClick(selectors.workerTimeControl.mondayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.fourthEntryOfMonday, 'innerText'); @@ -78,6 +82,7 @@ describe('Worker time control path', () => { const scanTime = '08:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.firstEntryOfTuesday, 'innerText'); @@ -89,6 +94,7 @@ describe('Worker time control path', () => { const scanTime = '10:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.secondEntryOfTuesday, 'innerText'); @@ -100,6 +106,7 @@ describe('Worker time control path', () => { const scanTime = '10:20'; const result = await nightmare .waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.thirdEntryOfTuesday, 'innerText'); @@ -111,6 +118,7 @@ describe('Worker time control path', () => { const scanTime = '16:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.fourthEntryOfTuesday, 'innerText'); @@ -131,6 +139,7 @@ describe('Worker time control path', () => { const scanTime = '09:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.firstEntryOfWednesday, 'innerText'); @@ -142,6 +151,7 @@ describe('Worker time control path', () => { const scanTime = '10:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.secondEntryOfWednesday, 'innerText'); @@ -153,6 +163,7 @@ describe('Worker time control path', () => { const scanTime = '10:20'; const result = await nightmare .waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.thirdEntryOfWednesday, 'innerText'); @@ -164,6 +175,7 @@ describe('Worker time control path', () => { const scanTime = '17:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.fourthEntryOfWednesday, 'innerText'); @@ -184,6 +196,7 @@ describe('Worker time control path', () => { const scanTime = '09:59'; const result = await nightmare .waitToClick(selectors.workerTimeControl.thursdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.firstEntryOfThursday, 'innerText'); @@ -195,6 +208,7 @@ describe('Worker time control path', () => { const scanTime = '10:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.thursdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.secondEntryOfThursday, 'innerText'); @@ -206,6 +220,7 @@ describe('Worker time control path', () => { const scanTime = '10:20'; const result = await nightmare .waitToClick(selectors.workerTimeControl.thursdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.thirdEntryOfThursday, 'innerText'); @@ -217,6 +232,7 @@ describe('Worker time control path', () => { const scanTime = '17:59'; const result = await nightmare .waitToClick(selectors.workerTimeControl.thursdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.fourthEntryOfThursday, 'innerText'); @@ -237,6 +253,7 @@ describe('Worker time control path', () => { const scanTime = '07:30'; const result = await nightmare .waitToClick(selectors.workerTimeControl.fridayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.firstEntryOfFriday, 'innerText'); @@ -248,6 +265,7 @@ describe('Worker time control path', () => { const scanTime = '10:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.fridayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.secondEntryOfFriday, 'innerText'); @@ -259,6 +277,7 @@ describe('Worker time control path', () => { const scanTime = '10:20'; const result = await nightmare .waitToClick(selectors.workerTimeControl.fridayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.thirdEntryOfFriday, 'innerText'); @@ -270,6 +289,7 @@ describe('Worker time control path', () => { const scanTime = '15:30'; const result = await nightmare .waitToClick(selectors.workerTimeControl.fridayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.fourthEntryOfFriday, 'innerText'); @@ -299,6 +319,7 @@ describe('Worker time control path', () => { const scanTime = '06:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.saturdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.firstEntryOfSaturday, 'innerText'); @@ -310,6 +331,7 @@ describe('Worker time control path', () => { const scanTime = '13:40'; const result = await nightmare .waitToClick(selectors.workerTimeControl.saturdayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.secondEntryOfSaturday, 'innerText'); @@ -330,6 +352,7 @@ describe('Worker time control path', () => { const scanTime = '05:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.sundayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.firstEntryOfSunday, 'innerText'); @@ -341,6 +364,7 @@ describe('Worker time control path', () => { const scanTime = '12:40'; const result = await nightmare .waitToClick(selectors.workerTimeControl.sundayAddTimeButton) + .clearInput(selectors.workerTimeControl.timeDialogInput) .write(selectors.workerTimeControl.timeDialogInput, scanTime) .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.secondEntryOfSunday, 'innerText'); diff --git a/e2e/paths/05-ticket-module/07_edit_sale.spec.js b/e2e/paths/05-ticket-module/07_edit_sale.spec.js index 3451b847a..4faab234a 100644 --- a/e2e/paths/05-ticket-module/07_edit_sale.spec.js +++ b/e2e/paths/05-ticket-module/07_edit_sale.spec.js @@ -148,6 +148,7 @@ describe('Ticket Edit sale path', () => { it('should confirm the price have been updated', async() => { const result = await nightmare + .wait(1999) .waitToGetProperty(`${selectors.ticketSales.firstSalePrice} span`, 'innerText'); expect(result).toContain('5.00'); @@ -243,6 +244,7 @@ describe('Ticket Edit sale path', () => { .waitToClick(selectors.ticketSales.thirdSaleCheckbox) .waitToClick(selectors.ticketSales.deleteSaleButton) .waitToClick(selectors.ticketSales.acceptDeleteLineButton) + .waitForSpinnerLoad() .waitForLastSnackbar(); expect(result).toEqual('Data saved!'); diff --git a/front/core/components/autocomplete/autocomplete.js b/front/core/components/autocomplete/autocomplete.js index dc24aecf8..89ad581e0 100755 --- a/front/core/components/autocomplete/autocomplete.js +++ b/front/core/components/autocomplete/autocomplete.js @@ -30,6 +30,8 @@ export default class Autocomplete extends Input { componentHandler.upgradeElement( this.element.querySelector('.mdl-textfield')); + + this.registerEvents(); } $postLink() { @@ -40,6 +42,15 @@ export default class Autocomplete extends Input { this.refreshSelection(); } + /** + * Registers all event emitters + */ + registerEvents() { + this.input.addEventListener('focus', event => { + this.emit('focus', {event}); + }); + } + get model() { return this._model; } @@ -223,7 +234,6 @@ export default class Autocomplete extends Input { const value = item[this.valueField]; this.selection = item; this.setValue(value); - this.field = value; } onClearClick(event) { @@ -232,7 +242,7 @@ export default class Autocomplete extends Input { } onKeyDown(event) { - if (event.defaultPrevented) return; + // if (event.defaultPrevented) return; switch (event.keyCode) { case 38: // Up @@ -241,6 +251,7 @@ export default class Autocomplete extends Input { this.showDropDown(); break; default: + console.log(event.key); if (event.key.length == 1) this.showDropDown(event.key); else diff --git a/front/core/components/calendar/style.scss b/front/core/components/calendar/style.scss index 1ce44982c..333bfb428 100644 --- a/front/core/components/calendar/style.scss +++ b/front/core/components/calendar/style.scss @@ -6,10 +6,6 @@ vn-calendar.small { } } -vn-calendar[disabled] .day .day-number { - cursor: not-allowed -} - vn-calendar { display: block; diff --git a/front/core/components/check/check.html b/front/core/components/check/check.html index 55a2f9c8c..4ca9f83a5 100644 --- a/front/core/components/check/check.html +++ b/front/core/components/check/check.html @@ -4,7 +4,7 @@ ng-disabled="::$ctrl.disabled" ng-checked="$ctrl.isChecked" ng-model="$ctrl.model"> - {{::$ctrl.label}} + {{::$ctrl.label}} vn-th, + & > vn-td { + overflow: hidden; + } + & > vn-th, & > vn-td, & > vn-td-editable { vertical-align: middle; @@ -70,7 +74,6 @@ vn-table { text-align: left; padding: .6em .5em; white-space: nowrap; - overflow: hidden; text-overflow: ellipsis; max-width: 5em; @@ -157,7 +160,7 @@ vn-table { } vn-autocomplete { div.mdl-textfield { - padding: 0px !important; + padding: 0 !important; } label.mdl-textfield__label:after { bottom: 0; diff --git a/front/core/components/td-editable/index.js b/front/core/components/td-editable/index.js index 37a6d30ab..64f29cddc 100644 --- a/front/core/components/td-editable/index.js +++ b/front/core/components/td-editable/index.js @@ -1,42 +1,71 @@ import ngModule from '../../module'; import Component from '../../lib/component'; +import Input from '../../lib/input'; import './style.scss'; export default class Controller extends Component { constructor($element, $scope, $transclude, $timeout) { super($element, $scope); this.$timeout = $timeout; - let element = $element[0]; - element.tabIndex = 0; + this.element.tabIndex = 0; - element.addEventListener('focus', () => { + this.element.addEventListener('focus', () => { if (this.field || this.disabled) return; $transclude((tClone, tScope) => { this.field = tClone; this.tScope = tScope; this.element.querySelector('.field').appendChild(this.field[0]); - element.tabIndex = -1; + this.element.tabIndex = -1; }, null, 'field'); - element.classList.add('selected'); + this.element.classList.add('selected'); }); - element.addEventListener('focusout', event => { - if (!this.field || this.disabled) return; - // this.destroyTimer(); - this.lastEvent = event; - let target = event.relatedTarget; - while (target && target != element) - target = target.parentNode; + this.element.addEventListener('focusout', event => this.hideField(event)); - if (!target) { - this.tScope.$destroy(); - this.field.remove(); - this.field = null; - element.classList.remove('selected'); - element.tabIndex = 0; + this.element.addEventListener('keyup', event => { + if (event.key === 'Enter') + this.hideField(event); + }); + + this.element.addEventListener('click', event => { + if (this.disabled) return; + + let target = event.target; + while (target) { + if (target == this.field[0]) return; + target = target.parentNode; + } + + let inputCtrl = this.field[0].firstElementChild.$ctrl; + if (inputCtrl instanceof Input) { + let evt = new MouseEvent('click', { + bubbles: true, + cancelable: true, + view: window + }); + inputCtrl.input.dispatchEvent(evt); } }); } + + hideField(event) { + if (!this.field || !this.tScope) return; + + this.lastEvent = event; + let target = event.relatedTarget; + while (target && target != this.element) + target = target.parentNode; + + if (!target) { + this.tScope.$destroy(); + this.tScope = null; + this.field.remove(); + this.field = null; + this.element.classList.remove('selected'); + this.element.tabIndex = 0; + } + } + destroyTimer() { if (this.timer) { this.$timeout.cancel(this.timer); @@ -47,6 +76,21 @@ export default class Controller extends Component { $onDestroy() { this.destroyTimer(); } + + get disabled() { + return this._disabled; + } + + set disabled(value) { + this._disabled = value; + + const classList = this.element.classList; + + if (value) + classList.add('disabled'); + else + classList.remove('disabled'); + } } Controller.$inject = ['$element', '$scope', '$transclude', '$timeout']; diff --git a/front/core/components/td-editable/style.scss b/front/core/components/td-editable/style.scss index 19c56cb02..e37e1086c 100644 --- a/front/core/components/td-editable/style.scss +++ b/front/core/components/td-editable/style.scss @@ -1,11 +1,21 @@ @import "variables"; vn-td-editable { + cursor: pointer; + outline: none; + position: relative; + overflow: visible; + text { - border-bottom: 1px solid rgba(0,0,0,.12); + border: 1px dashed rgba(0, 0, 0, .15); + border-radius: 1em; + padding: 5px 10px; min-height: 15px; - cursor: pointer; - display: block + display: block; + overflow: hidden; + &:hover { + border-color: $color-main; + } } text::after { @@ -13,25 +23,17 @@ vn-td-editable { content: ''; clear: both; } - - outline: none; - position: relative; - &:not([disabled="true"]) { - cursor: initial - } - &[disabled="true"] { - cursor: not-allowed; - } &.selected > .text { visibility: hidden; } & > .field { display: none; - position: absolute; - top: 0; - left: 0; width: 100%; height: 100%; + position: absolute; + z-index:10; + top: 0; + left: 0; box-sizing: border-box; align-items: center; padding: .6em; @@ -54,4 +56,11 @@ vn-td-editable { &.selected > .field { display: flex; } +} + +vn-td-editable.disabled { + cursor: initial; +} +vn-td-editable.disabled text { + border: none; } \ No newline at end of file diff --git a/front/core/directives/focus.js b/front/core/directives/focus.js index 91fcffbb2..b57241078 100644 --- a/front/core/directives/focus.js +++ b/front/core/directives/focus.js @@ -1,13 +1,20 @@ import ngModule from '../module'; export function focus(input) { + const element = input; let selector = 'input, textarea, button, submit'; if (!input.matches(selector)) input = input.querySelector(selector); if (!input) { - console.warn(`vnFocus: Can't find a focusable element`); + const focusEvent = new MouseEvent('focus', { + bubbles: true, + cancelable: true, + view: window + }); + element.dispatchEvent(focusEvent); + return; } diff --git a/front/core/directives/specs/focus.spec.js b/front/core/directives/specs/focus.spec.js index 541a0b640..4a8c64f82 100644 --- a/front/core/directives/specs/focus.spec.js +++ b/front/core/directives/specs/focus.spec.js @@ -24,16 +24,9 @@ describe('Directive focus', () => { let childHtml = ''; compile(html, childHtml); - expect($element[0].firstChild.focus).toHaveBeenCalled(); + expect($element[0].firstChild.focus).toHaveBeenCalledWith(); }); - it('should print a warning message on console', () => { - let html = ``; - console.warn = jasmine.createSpy('warn'); - compile(html); - - expect(console.warn).toHaveBeenCalledWith(`vnFocus: Can't find a focusable element`); - }); it('should call focus function on the element', () => { let html = ``; diff --git a/front/core/filters/percentage.js b/front/core/filters/percentage.js index 157871ed8..58e67ffa6 100644 --- a/front/core/filters/percentage.js +++ b/front/core/filters/percentage.js @@ -9,7 +9,7 @@ export default function percentage() { return function(input) { if (input == null || input === '') return null; - return input * 100 + ' %'; + return `${input} %`; }; } ngModule.filter('percentage', percentage); diff --git a/front/salix/styles/display.scss b/front/salix/styles/display.scss index d842c1cc3..8c5827194 100644 --- a/front/salix/styles/display.scss +++ b/front/salix/styles/display.scss @@ -26,10 +26,6 @@ line-height: 0px; } -input:disabled, button:disabled { - cursor: not-allowed !important; -} - input[type="submit"]:disabled, button:disabled { opacity: 0.7; } diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 7d998c3f3..4a86734fd 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -49,5 +49,6 @@ "NO_ZONE_FOR_THIS_PARAMETERS": "NO_ZONE_FOR_THIS_PARAMETERS", "This client can't be invoiced": "This client can't be invoiced", "The introduced hour already exists": "The introduced hour already exists", - "Invalid parameters to create a new ticket": "Invalid parameters to create a new ticket" + "Invalid parameters to create a new ticket": "Invalid parameters to create a new ticket", + "Concept cannot be blank": "Concept cannot be blank" } \ No newline at end of file diff --git a/loopback/locale/es.json b/loopback/locale/es.json index bc1bfe3aa..34be59419 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -76,7 +76,6 @@ "We weren't able to send this SMS": "No hemos podido enviar el SMS", "This client can't be invoiced": "Este cliente no puede ser facturado", "This ticket can't be invoiced": "Este ticket no puede ser facturado", - "That item is not available on that day": "El item no esta disponible para esa fecha", "You cannot add or modify services to an invoiced ticket": "No puedes añadir o modificar servicios a un ticket facturado", "This ticket can not be modified": "Este ticket no puede ser modificado", "The introduced hour already exists": "Esta hora ya ha sido introducida", @@ -92,5 +91,7 @@ "This item doesn't exists": "El artículo no existe", "NOT_ZONE_WITH_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada", "Extension format is invalid": "El formato de la extensión es inválido", - "Invalid parameters to create a new ticket": "Parámetros inválidos para crear un nuevo ticket" + "Invalid parameters to create a new ticket": "Parámetros inválidos para crear un nuevo ticket", + "This item is not available": "Este artículo no está disponible", + "Concept cannot be blank": "Concept cannot be blank" } \ No newline at end of file diff --git a/modules/client/back/models/client.json b/modules/client/back/models/client.json index fc542ce14..c62ae3832 100644 --- a/modules/client/back/models/client.json +++ b/modules/client/back/models/client.json @@ -197,7 +197,7 @@ "foreignKey": "clientFk" }, "claimsRatio": { - "type": "hasMany", + "type": "hasOne", "model": "ClaimRatio", "foreignKey": "clientFk" } diff --git a/modules/client/front/summary/index.html b/modules/client/front/summary/index.html index 37bee2814..69a08332e 100644 --- a/modules/client/front/summary/index.html +++ b/modules/client/front/summary/index.html @@ -155,13 +155,13 @@ value="{{$ctrl.summary.mana.mana | currency: 'EUR':2}}"> + value="{{$ctrl.claimRate($ctrl.summary.claimsRatio.priceIncreasing) | percentage}}"> + value="{{$ctrl.claimingRate($ctrl.summary.claimsRatio.claimingRate) | percentage}}"> diff --git a/modules/client/front/summary/index.js b/modules/client/front/summary/index.js index 79f268835..a35f330f1 100644 --- a/modules/client/front/summary/index.js +++ b/modules/client/front/summary/index.js @@ -29,6 +29,16 @@ class Controller { }); return total; } + + claimRate(priceIncreasing) { + if (priceIncreasing) + return priceIncreasing * 100; + } + + claimingRate(rate) { + if (rate) + return rate * 100; + } } Controller.$inject = ['$http']; diff --git a/modules/item/front/fetched-tags/index.html b/modules/item/front/fetched-tags/index.html index c613a042d..6982ab6ac 100644 --- a/modules/item/front/fetched-tags/index.html +++ b/modules/item/front/fetched-tags/index.html @@ -1,7 +1,7 @@ - {{::$ctrl.item.name}} + {{$ctrl.name}} -

{{::$ctrl.subName}}

+

{{$ctrl.subName}}

diff --git a/modules/order/front/line/index.html b/modules/order/front/line/index.html index 91c05657b..bdda0739d 100644 --- a/modules/order/front/line/index.html +++ b/modules/order/front/line/index.html @@ -43,6 +43,7 @@ diff --git a/modules/order/front/summary/index.html b/modules/order/front/summary/index.html index ba769a616..e3022493d 100644 --- a/modules/order/front/summary/index.html +++ b/modules/order/front/summary/index.html @@ -72,6 +72,7 @@ diff --git a/modules/order/front/volume/index.html b/modules/order/front/volume/index.html index 4772ccd43..e368e9c1b 100644 --- a/modules/order/front/volume/index.html +++ b/modules/order/front/volume/index.html @@ -42,6 +42,7 @@ diff --git a/modules/ticket/back/methods/sale/updatePrice.js b/modules/ticket/back/methods/sale/updatePrice.js index e315d6504..e5907aa75 100644 --- a/modules/ticket/back/methods/sale/updatePrice.js +++ b/modules/ticket/back/methods/sale/updatePrice.js @@ -19,7 +19,7 @@ module.exports = Self => { } ], returns: { - type: 'string', + type: 'Number', root: true }, http: { @@ -36,7 +36,6 @@ module.exports = Self => { let options = {transaction: tx}; let filter = { - fields: ['id', 'ticketFk', 'price'], include: { relation: 'ticket', scope: { @@ -88,6 +87,8 @@ module.exports = Self => { await Self.rawSql(query, [salesPerson], options); await tx.commit(); + + return sale; } catch (error) { await tx.rollback(); throw error; diff --git a/modules/ticket/back/methods/ticket-request/confirm.js b/modules/ticket/back/methods/ticket-request/confirm.js index c08ecba9b..79551796d 100644 --- a/modules/ticket/back/methods/ticket-request/confirm.js +++ b/modules/ticket/back/methods/ticket-request/confirm.js @@ -45,20 +45,15 @@ module.exports = Self => { include: {relation: 'ticket'} }); - let query = `CALL vn.getItemVisibleAvailable(?,?,?,?)`; - - let params = [ + let [[stock]] = await Self.rawSql(`CALL vn.getItemVisibleAvailable(?,?,?,?)`, [ ctx.args.itemFk, request.ticket().shipped, request.ticket().warehouseFk, false - ]; - - let [res] = await Self.rawSql(query, params); - let available = res[0].available; - if (!available) - throw new UserError(`That item is not available on that day`); + ]); + if (stock.available < quantity) + throw new UserError(`This item is not available`); if (request.saleFk) { let sale = await models.Sale.findById(request.saleFk); diff --git a/modules/ticket/back/methods/ticket/addSale.js b/modules/ticket/back/methods/ticket/addSale.js new file mode 100644 index 000000000..595d036c5 --- /dev/null +++ b/modules/ticket/back/methods/ticket/addSale.js @@ -0,0 +1,71 @@ + +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('addSale', { + description: 'Inserts a new sale for the current ticket', + accessType: 'WRITE', + accepts: [{ + arg: 'id', + type: 'Number', + required: true, + description: 'The ticket id', + http: {source: 'path'} + }, + { + arg: 'itemId', + type: 'Number', + required: true + }, + { + arg: 'quantity', + type: 'Number', + required: true + }], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/:id/addSale`, + verb: 'POST' + } + }); + + Self.addSale = async(id, itemId, quantity) => { + const models = Self.app.models; + + const isEditable = await models.Ticket.isEditable(id); + if (!isEditable) + throw new UserError(`The sales of this ticket can't be modified`); + + const item = await models.Item.findById(itemId); + const ticket = await models.Ticket.findById(id); + + const shouldRefresh = false; + const [[stock]] = await Self.rawSql(`CALL vn.getItemVisibleAvailable(?, ?, ?, ?)`, [ + itemId, + ticket.shipped, + ticket.warehouseFk, + shouldRefresh + ]); + + if (stock.available < quantity) + throw new UserError(`This item is not available`); + + const newSale = await models.Sale.create({ + ticketFk: id, + itemFk: item.id, + concept: item.name, + quantity: quantity + }); + + await Self.rawSql('CALL vn.ticketCalculateSale(?)', [newSale.id]); + + return models.Sale.findById(newSale.id, { + include: { + relation: 'item' + } + }); + }; +}; diff --git a/modules/ticket/back/methods/ticket/getSales.js b/modules/ticket/back/methods/ticket/getSales.js index 25b8aaff3..0c33d9db9 100644 --- a/modules/ticket/back/methods/ticket/getSales.js +++ b/modules/ticket/back/methods/ticket/getSales.js @@ -51,7 +51,7 @@ module.exports = Self => { claimMap[claim.saleFk] = claim; for (line of lines) { - line.tags = map[line.itemFk]; + line.item = map[line.itemFk]; line.claim = claimMap[line.id]; } diff --git a/modules/ticket/back/methods/ticket/specs/addSale.spec.js b/modules/ticket/back/methods/ticket/specs/addSale.spec.js new file mode 100644 index 000000000..b035a74db --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/addSale.spec.js @@ -0,0 +1,49 @@ +const app = require('vn-loopback/server/server'); + +describe('ticket addSale()', () => { + const ticketId = 13; + let newSale; + + afterAll(async done => { + const sale = await app.models.Sale.findById(newSale.id); + await sale.destroy(); + + done(); + }); + + it('should create a new sale for the ticket with id 13', async() => { + const itemId = 4; + const quantity = 10; + newSale = await app.models.Ticket.addSale(ticketId, itemId, quantity); + + expect(newSale.itemFk).toEqual(4); + }); + + it('should not be able to add a sale if the item quantity is not available', async() => { + const itemId = 11; + const quantity = 10; + + let error; + await app.models.Ticket.addSale(ticketId, itemId, quantity).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`This item is not available`); + }); + + expect(error).toBeDefined(); + }); + + it('should not be able to add a sale if the ticket is not editable', async() => { + const notEditableTicketId = 1; + const itemId = 4; + const quantity = 10; + let error; + await app.models.Ticket.addSale(notEditableTicketId, itemId, quantity).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`The sales of this ticket can't be modified`); + }); + + expect(error).toBeDefined(); + }); +}); diff --git a/modules/ticket/back/methods/ticket/specs/getSales.spec.js b/modules/ticket/back/methods/ticket/specs/getSales.spec.js index cefa9ff4d..4e57f570f 100644 --- a/modules/ticket/back/methods/ticket/specs/getSales.spec.js +++ b/modules/ticket/back/methods/ticket/specs/getSales.spec.js @@ -5,10 +5,10 @@ describe('ticket getSales()', () => { let sales = await app.models.Ticket.getSales(16); expect(sales.length).toEqual(4); - expect(sales[0].tags).toBeDefined(); - expect(sales[1].tags).toBeDefined(); - expect(sales[2].tags).toBeDefined(); - expect(sales[3].tags).toBeDefined(); + expect(sales[0].item).toBeDefined(); + expect(sales[1].item).toBeDefined(); + expect(sales[2].item).toBeDefined(); + expect(sales[3].item).toBeDefined(); expect(sales[0].claim).toBeDefined(); }); }); diff --git a/modules/ticket/back/models/sale.js b/modules/ticket/back/models/sale.js index bc042ed8a..df5b95839 100644 --- a/modules/ticket/back/models/sale.js +++ b/modules/ticket/back/models/sale.js @@ -6,4 +6,8 @@ module.exports = Self => { require('../methods/sale/removes')(Self); require('../methods/sale/updatePrice')(Self); require('../methods/sale/updateQuantity')(Self); + + Self.validatesPresenceOf('concept', { + message: `Concept cannot be blank` + }); }; diff --git a/modules/ticket/back/models/sale.json b/modules/ticket/back/models/sale.json index 17a8c604e..894a2608f 100644 --- a/modules/ticket/back/models/sale.json +++ b/modules/ticket/back/models/sale.json @@ -13,7 +13,8 @@ "description": "Identifier" }, "concept": { - "type": "String" + "type": "String", + "required": true }, "quantity": { "type": "Number" diff --git a/modules/ticket/back/models/ticket.js b/modules/ticket/back/models/ticket.js index 8ee2b4580..608a0b4b4 100644 --- a/modules/ticket/back/models/ticket.js +++ b/modules/ticket/back/models/ticket.js @@ -22,4 +22,5 @@ module.exports = Self => { require('../methods/ticket/checkEmptiness')(Self); require('../methods/ticket/updateDiscount')(Self); require('../methods/ticket/uploadFile')(Self); + require('../methods/ticket/addSale')(Self); }; diff --git a/modules/ticket/front/basic-data/step-two/index.html b/modules/ticket/front/basic-data/step-two/index.html index a8c3859e5..1ecd061e2 100644 --- a/modules/ticket/front/basic-data/step-two/index.html +++ b/modules/ticket/front/basic-data/step-two/index.html @@ -18,7 +18,8 @@ + item="::sale.item" + name="::sale.concept"> {{::sale.quantity}} diff --git a/modules/ticket/front/component/index.html b/modules/ticket/front/component/index.html index 27ba166d0..228a0270c 100644 --- a/modules/ticket/front/component/index.html +++ b/modules/ticket/front/component/index.html @@ -41,6 +41,7 @@ diff --git a/modules/ticket/front/sale-checked/index.html b/modules/ticket/front/sale-checked/index.html index 4126e3477..cc4db0aaf 100644 --- a/modules/ticket/front/sale-checked/index.html +++ b/modules/ticket/front/sale-checked/index.html @@ -37,6 +37,7 @@ diff --git a/modules/ticket/front/sale-tracking/index.html b/modules/ticket/front/sale-tracking/index.html index 020052e01..c3c918c09 100644 --- a/modules/ticket/front/sale-tracking/index.html +++ b/modules/ticket/front/sale-tracking/index.html @@ -41,6 +41,7 @@ diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index 9b370c40c..3e2900101 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -93,66 +93,86 @@ + vn-tooltip="{{::$ctrl.$translate.instant('Reserved')}}"> + - - - - - - {{::sale.itemFk | zeroFill:6}} + + + + + + + {{sale.itemFk | zeroFill:6}} - - - {{sale.quantity}} - - - - - - + + + + + + + + {{sale.quantity}} + + + + + + + + item="sale.item" + name="sale.concept" + sub-name="sale.subName"> - - - - {{sale.price | currency: 'EUR':2}} - - - + + + + + + + + {{sale.price | currency: 'EUR':2}} - - - - {{sale.discount}} % - - - - {{sale.discount}} % - - - {{$ctrl.getSaleTotal(sale) | currency: 'EUR':2}} - - - - - + + + + + {{sale.discount | percentage}} + + + + {{$ctrl.getSaleTotal(sale) | currency: 'EUR':2}} + + + + + + + + @@ -248,7 +268,9 @@ - No results + + No results + \ No newline at end of file diff --git a/modules/ticket/front/sale/index.js b/modules/ticket/front/sale/index.js index bca462cb8..909f6068b 100644 --- a/modules/ticket/front/sale/index.js +++ b/modules/ticket/front/sale/index.js @@ -17,9 +17,10 @@ class Controller { {callback: this.createClaim, name: 'Add claim'}, {callback: this.showSMSDialog, name: 'Send SMS'} ]; - + this._sales = []; this.imagesPath = '//verdnatura.es/vn-image-data/catalog'; } + get sales() { return this._sales; } @@ -38,7 +39,6 @@ class Controller { this.updateNewPrice(); } - refreshTotal() { this.loadSubTotal(); this.loadVAT(); @@ -52,6 +52,9 @@ class Controller { } getSaleTotal(sale) { + if (!sale.quantity || !sale.price) + return; + return sale.quantity * sale.price * ((100 - sale.discount) / 100); } @@ -99,16 +102,45 @@ class Controller { return false; } + /** + * Returns checked instances + * + * @return {Array} Checked instances + */ getCheckedLines() { - let lines = []; - let data = this.sales; - if (data) { - for (let i = 0; i < data.length; i++) { - if (data[i].checked) - lines.push({id: data[i].id, instance: i}); - } - } - return lines; + if (!this.sales) return; + + return this.sales.filter(sale => { + return sale.checked; + }); + } + + /** + * Returns an array of indexes + * from checked instances + * + * @return {Array} Indexes of checked instances + */ + getCheckedLinesIndex() { + if (!this.sales) return; + + let indexes = []; + this.sales.forEach((sale, index) => { + if (sale.checked) indexes.push(index); + }); + + return indexes; + } + + removeCheckedLines() { + const sales = this.getCheckedLines(); + + sales.forEach(sale => { + const index = this.sales.indexOf(sale); + this.sales.splice(index, 1); + }); + + this.refreshTotal(); } onStateOkClick() { @@ -130,21 +162,21 @@ class Controller { onRemoveLinesClick(response) { if (response === 'ACCEPT') { let sales = this.getCheckedLines(); + + // Remove unsaved instances + sales.forEach((sale, index) => { + if (!sale.id) sales.splice(index); + }); + let params = {sales: sales, actualTicketFk: this.ticket.id}; let query = `/api/Sales/removes`; this.$http.post(query, params).then(() => { - this.removeInstances(sales); + this.removeCheckedLines(); this.vnApp.showSuccess(this.$translate.instant('Data saved!')); }); } } - removeInstances(instances) { - for (let i of instances) - this.sales.splice(i.instance, 1); - this.refreshTotal(); - } - showRemoveLinesDialog() { this.$scope.deleteLines.show(); } @@ -208,14 +240,12 @@ class Controller { } createClaim() { - let claim = { + const claim = { ticketFk: this.ticket.id, clientFk: this.ticket.clientFk, ticketCreated: this.ticket.shipped }; - let sales = this.getCheckedLines(); - for (let i = 0; i < sales.length; i++) - sales[i].quantity = this.sales[sales[i].instance].quantity; + const sales = this.getCheckedLines(); this.$http.post(`/api/Claims/createFromSales`, {claim: claim, sales: sales}).then(res => { this.$state.go('claim.card.basicData', {id: res.data.id}); }); @@ -255,6 +285,7 @@ class Controller { } showEditPricePopover(event, sale) { + if (!this.isEditable) return; this.sale = sale; this.editedPrice = this.sale.price; this.edit = { @@ -268,10 +299,11 @@ class Controller { updatePrice() { if (this.editedPrice != this.sale.price) { - this.$http.post(`/api/Sales/${this.edit.id}/updatePrice`, {newPrice: this.editedPrice}).then(() => { - this.sale.price = this.edit.price; + this.$http.post(`/api/Sales/${this.edit.id}/updatePrice`, {newPrice: this.editedPrice}).then(res => { + if (res.data) + this.sale.price = res.data.price; + this.vnApp.showSuccess(this.$translate.instant('Data saved!')); - this.$scope.model.refresh(); }); } @@ -282,7 +314,9 @@ class Controller { this.newPrice = this.sale.quantity * this.editedPrice - ((this.sale.discount * (this.sale.quantity * this.editedPrice)) / 100); } - showEditPopover(event, sale) { + showEditDiscountPopover(event, sale) { + if (!this.isEditable) return; + this.sale = sale; this.edit = [{ ticketFk: this.ticket.id, @@ -310,24 +344,14 @@ class Controller { this.$scope.editPopover.hide(); } - updateQuantity(id, quantity) { - this.$http.post(`/api/Sales/${id}/updateQuantity`, {quantity: parseInt(quantity)}).then(() => { - this.vnApp.showSuccess(this.$translate.instant('Data saved!')); - }).catch(e => { - this.vnApp.showError(e.data.error.message); - }).finally(() => { - this.$scope.model.refresh(); - }); - } - - /** + /* * Unmark sale as reserved */ unmarkAsReserved() { this.setReserved(false); } - /** + /* * Mark sale as reserved */ markAsReserved() { @@ -363,10 +387,9 @@ class Controller { showSMSDialog() { const address = this.ticket.address; - const lines = this.getCheckedLines(); - const items = lines.map(line => { - const instance = this.sales[line.instance]; - return `${instance.quantity} ${instance.concept}`; + const sales = this.getCheckedLines(); + const items = sales.map(sale => { + return `${sale.quantity} ${sale.concept}`; }); const notAvailables = items.join(', '); const params = { @@ -390,6 +413,79 @@ class Controller { hasInvoice() { return this.ticket.refFk !== null; } + + /** + * Inserts a new instance + */ + add() { + this.$scope.model.insert({}); + } + + /* + * Creates a new sale if it's a new instance + * Updates the sale quantity for existing instance + */ + onChangeQuantity(sale) { + if (!sale.id) + this.addSale(sale); + else + this.updateQuantity(sale); + } + + /* + * Updates a sale quantity + */ + updateQuantity(sale) { + const data = {quantity: parseInt(sale.quantity)}; + const query = `/api/Sales/${sale.id}/updateQuantity`; + this.$http.post(query, data).then(() => { + this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + }).catch(e => { + this.$scope.model.refresh(); + throw e; + }); + } + + /* + * Updates a sale concept + */ + updateConcept(sale) { + const data = {concept: sale.concept}; + const query = `/api/Sales/${sale.id}`; + this.$http.patch(query, data).then(() => { + this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + }).catch(e => { + this.$scope.model.refresh(); + throw e; + }); + } + + /* + * Adds a new sale + */ + addSale(sale) { + const data = { + itemId: sale.itemFk, + quantity: sale.quantity + }; + const query = `/api/tickets/${this.ticket.id}/addSale`; + this.$http.post(query, data).then(res => { + if (!res.data) return; + + const newSale = res.data; + + sale.id = newSale.id; + sale.image = newSale.item.image; + sale.subName = newSale.item.subName; + sale.concept = newSale.concept; + sale.quantity = newSale.quantity; + sale.discount = newSale.discount; + sale.price = newSale.price; + sale.item = newSale.item; + + this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + }); + } } Controller.$inject = ['$scope', '$state', '$http', 'vnApp', '$translate']; diff --git a/modules/ticket/front/sale/locale/es.yml b/modules/ticket/front/sale/locale/es.yml index 235ca9e41..b8966232f 100644 --- a/modules/ticket/front/sale/locale/es.yml +++ b/modules/ticket/front/sale/locale/es.yml @@ -1,4 +1,5 @@ New price: Nuevo precio +Add item: Añadir artículo Add turn: Añadir a turno Delete ticket: Borrar ticket Mark as reserved: Marcar como reservado diff --git a/modules/ticket/front/sale/specs/index.spec.js b/modules/ticket/front/sale/specs/index.spec.js index b1cadd0fc..938b4c252 100644 --- a/modules/ticket/front/sale/specs/index.spec.js +++ b/modules/ticket/front/sale/specs/index.spec.js @@ -64,8 +64,10 @@ describe('Ticket', () => { it('should perform a query and call windows open', () => { spyOn(controller.$state, 'go'); - let res = {id: 1}; - $httpBackend.expectPOST(`/api/Claims/createFromSales`).respond(res); + const claim = {id: 1}; + const sales = [{id: 1}, {id: 2}]; + $httpBackend.when('POST', `/api/Claims/createFromSales`, {claim, sales}).respond(claim); + $httpBackend.expect('POST', `/api/Claims/createFromSales`).respond(claim); controller.createClaim(); $httpBackend.flush(); @@ -93,7 +95,7 @@ describe('Ticket', () => { it('should make an array of the instances with the property checked true()', () => { let sale = controller.sales[1]; sale.checked = true; - let expectedResult = [{id: sale.id, instance: 1}]; + let expectedResult = [sale]; expect(controller.getCheckedLines()).toEqual(expectedResult); }); @@ -156,12 +158,10 @@ describe('Ticket', () => { describe('setReserved()', () => { it('should call getCheckedLines, $.index.accept and make a query ', () => { - controller.sales[1].checked = true; + const sale = controller.sales[1]; + sale.checked = true; let expectedRequest = { - sales: [{ - id: sales[1].id, - instance: 1 - }], + sales: [sale], ticketFk: ticket.id, reserved: false }; @@ -184,5 +184,55 @@ describe('Ticket', () => { expect(controller.newSMS.message).not.toEqual(''); }); }); + + describe('updateQuantity()', () => { + it('should make a POST query saving sale quantity', () => { + const data = {quantity: 10}; + const sale = sales[0]; + sale.quantity = 10; + + $httpBackend.when('POST', `/api/Sales/1/updateQuantity`, data).respond(); + $httpBackend.expect('POST', `/api/Sales/1/updateQuantity`, data).respond(); + controller.updateQuantity(sale); + $httpBackend.flush(); + }); + }); + + describe('updateConcept()', () => { + it('should make a POST query saving sale concept', () => { + const data = {concept: 'My new weapon'}; + const sale = sales[0]; + sale.concept = 'My new weapon'; + + $httpBackend.when('PATCH', `/api/Sales/1`, data).respond(); + $httpBackend.expect('PATCH', `/api/Sales/1`, data).respond(); + controller.updateConcept(sale); + $httpBackend.flush(); + }); + }); + + describe('addSale()', () => { + it('should make a POST query adding a new sale', () => { + const newSale = {itemFk: 4, quantity: 10}; + const params = {itemId: 4, quantity: 10}; + + const expectedResult = { + id: 30, + quantity: 10, + discount: 0, + price: 0, + itemFk: 4, + item: { + subName: 'Item subName', + image: '30.png' + } + }; + + $httpBackend.when('POST', `/api/tickets/1/addSale`, params).respond(expectedResult); + $httpBackend.expect('POST', `/api/tickets/1/addSale`, params).respond(expectedResult); + controller.addSale(newSale); + $httpBackend.flush(); + }); + }); }); }); diff --git a/modules/ticket/front/summary/index.html b/modules/ticket/front/summary/index.html index c718e38f0..7105e0515 100644 --- a/modules/ticket/front/summary/index.html +++ b/modules/ticket/front/summary/index.html @@ -101,6 +101,7 @@ {{::sale.price | currency: 'EUR':2}} diff --git a/modules/ticket/front/volume/index.html b/modules/ticket/front/volume/index.html index cfb095bd5..ef21d8f50 100644 --- a/modules/ticket/front/volume/index.html +++ b/modules/ticket/front/volume/index.html @@ -46,6 +46,7 @@ {{::sale.quantity}}