Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into dev
This commit is contained in:
commit
4b0a71644f
|
@ -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');
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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!');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,10 +6,6 @@ vn-calendar.small {
|
|||
}
|
||||
}
|
||||
|
||||
vn-calendar[disabled] .day .day-number {
|
||||
cursor: not-allowed
|
||||
}
|
||||
|
||||
vn-calendar {
|
||||
display: block;
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
ng-disabled="::$ctrl.disabled"
|
||||
ng-checked="$ctrl.isChecked"
|
||||
ng-model="$ctrl.model">
|
||||
<span translate>{{::$ctrl.label}}</span>
|
||||
<span translate ng-if="::$ctrl.label">{{::$ctrl.label}}</span>
|
||||
</md-checkbox>
|
||||
<i class="material-icons"
|
||||
ng-if="::$ctrl.hasInfo"
|
||||
|
|
|
@ -16,6 +16,10 @@ vn-check {
|
|||
}
|
||||
|
||||
md-checkbox {
|
||||
margin-bottom: 0.8em
|
||||
margin-bottom: 0.8em;
|
||||
|
||||
.md-label:empty {
|
||||
margin: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
vn-multi-check {
|
||||
md-checkbox {
|
||||
margin-bottom: 0.8em;
|
||||
|
||||
.md-label {
|
||||
margin: 0
|
||||
}
|
||||
margin-bottom: 0.8em
|
||||
}
|
||||
}
|
|
@ -63,6 +63,10 @@ vn-table {
|
|||
padding-bottom: .8em;
|
||||
}
|
||||
& > 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;
|
||||
|
|
|
@ -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'];
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,16 +24,9 @@ describe('Directive focus', () => {
|
|||
let childHtml = '<input></input>';
|
||||
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 = `<potato vn-focus></potato>`;
|
||||
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 = `<input vn-focus></input>`;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -26,10 +26,6 @@
|
|||
line-height: 0px;
|
||||
}
|
||||
|
||||
input:disabled, button:disabled {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
input[type="submit"]:disabled, button:disabled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -197,7 +197,7 @@
|
|||
"foreignKey": "clientFk"
|
||||
},
|
||||
"claimsRatio": {
|
||||
"type": "hasMany",
|
||||
"type": "hasOne",
|
||||
"model": "ClaimRatio",
|
||||
"foreignKey": "clientFk"
|
||||
}
|
||||
|
|
|
@ -155,13 +155,13 @@
|
|||
value="{{$ctrl.summary.mana.mana | currency: 'EUR':2}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Rate"
|
||||
value="{{$ctrl.summary.claimsRatio[0].priceIncreasing | percentage}}">
|
||||
value="{{$ctrl.claimRate($ctrl.summary.claimsRatio.priceIncreasing) | percentage}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Average invoiced"
|
||||
value="{{$ctrl.summary.averageInvoiced.invoiced | currency: 'EUR':2}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Claims"
|
||||
value="{{$ctrl.summary.claimsRatio[0].claimingRate | percentage}}">
|
||||
value="{{$ctrl.claimingRate($ctrl.summary.claimsRatio.claimingRate) | percentage}}">
|
||||
</vn-label-value>
|
||||
</vn-one>
|
||||
<vn-one>
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<vn-horizontal>
|
||||
<vn-one title="{{::$ctrl.item.name}}">{{::$ctrl.item.name}}</vn-one>
|
||||
<vn-one title="{{$ctrl.name}}">{{$ctrl.name}}</vn-one>
|
||||
<vn-one ng-if="$ctrl.subName">
|
||||
<h3 title="{{::$ctrl.subName}}">{{::$ctrl.subName}}</h3>
|
||||
<h3 title="{{$ctrl.subName}}">{{$ctrl.subName}}</h3>
|
||||
</vn-one>
|
||||
<vn-auto>
|
||||
<section
|
||||
|
|
|
@ -6,6 +6,7 @@ ngModule.component('vnFetchedTags', {
|
|||
bindings: {
|
||||
maxLength: '<',
|
||||
item: '<',
|
||||
name: '<?',
|
||||
subName: '<?'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
<vn-fetched-tags
|
||||
max-length="6"
|
||||
item="::item"
|
||||
name="::item.name"
|
||||
sub-name="::item.subName">
|
||||
</vn-fetched-tags>
|
||||
</vn-td>
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
<vn-fetched-tags
|
||||
max-length="6"
|
||||
item="::row.item"
|
||||
name="::row.item.name"
|
||||
sub-name="::row.item.subName">
|
||||
</vn-fetched-tags>
|
||||
</vn-td>
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
<vn-fetched-tags
|
||||
max-length="6"
|
||||
item="::row.item"
|
||||
name="::row.item.name"
|
||||
sub-name="::row.item.subName">
|
||||
</vn-fetched-tags>
|
||||
</vn-td>
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
<vn-fetched-tags
|
||||
max-length="6"
|
||||
item="::row.item"
|
||||
name="::row.item.name"
|
||||
sub-name="::row.item.subName">
|
||||
</vn-fetched-tags>
|
||||
</vn-td>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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`
|
||||
});
|
||||
};
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
"description": "Identifier"
|
||||
},
|
||||
"concept": {
|
||||
"type": "String"
|
||||
"type": "String",
|
||||
"required": true
|
||||
},
|
||||
"quantity": {
|
||||
"type": "Number"
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
<td expand>
|
||||
<vn-fetched-tags
|
||||
max-length="6"
|
||||
item="::sale.item">
|
||||
item="::sale.item"
|
||||
name="::sale.concept">
|
||||
</vn-fetched-tags>
|
||||
</td>
|
||||
<td number>{{::sale.quantity}}</td>
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
<vn-fetched-tags
|
||||
max-length="6"
|
||||
item="::sale.item"
|
||||
name="::sale.concept"
|
||||
sub-name="::sale.item.subName">
|
||||
</vn-fetched-tags>
|
||||
</td>
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
<vn-fetched-tags
|
||||
max-length="6"
|
||||
item="::sale.item"
|
||||
name="::sale.concept"
|
||||
sub-name="::sale.item.subName">
|
||||
</vn-fetched-tags>
|
||||
</vn-td>
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
<vn-fetched-tags
|
||||
max-length="6"
|
||||
item="::sale.item"
|
||||
name="::sale.concept"
|
||||
sub-name="::sale.item.subName">
|
||||
</vn-fetched-tags>
|
||||
</vn-td>
|
||||
|
|
|
@ -93,66 +93,86 @@
|
|||
</vn-icon>
|
||||
<vn-icon ng-show="sale.reserved"
|
||||
icon="icon-reserve"
|
||||
vn-tooltip="{{::$ctrl.$translate.instant('Reserved')}}"></vn-icon>
|
||||
vn-tooltip="{{::$ctrl.$translate.instant('Reserved')}}">
|
||||
</vn-icon>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<img
|
||||
ng-src="{{::$ctrl.imagesPath}}/50x50/{{::sale.image}}"
|
||||
zoom-image="{{::$ctrl.imagesPath}}/1600x900/{{::sale.image}}"
|
||||
on-error-src/>
|
||||
</vn-td>
|
||||
<vn-td number>
|
||||
<span
|
||||
ng-click="$ctrl.showDescriptor($event, sale.itemFk)"
|
||||
class="link">
|
||||
{{::sale.itemFk | zeroFill:6}}
|
||||
<vn-td shrink>
|
||||
<img
|
||||
ng-src="{{::$ctrl.imagesPath}}/50x50/{{sale.image}}"
|
||||
zoom-image="{{::$ctrl.imagesPath}}/1600x900/{{sale.image}}"
|
||||
on-error-src/>
|
||||
</vn-td>
|
||||
<vn-td-editable disabled="sale.itemFk" vn-focus number>
|
||||
<text>
|
||||
<span class="link" ng-show="sale.itemFk"
|
||||
ng-click="$ctrl.showDescriptor($event, sale.itemFk)">
|
||||
{{sale.itemFk | zeroFill:6}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td-editable editable="!$ctrl.isEditable">
|
||||
<text>{{sale.quantity}}</text>
|
||||
<field>
|
||||
<vn-textfield vn-focus
|
||||
model="sale.quantity"
|
||||
on-change="$ctrl.updateQuantity(sale.id, sale.quantity)"
|
||||
type="text">
|
||||
</vn-textfield>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
<vn-td expand>
|
||||
</text>
|
||||
<field>
|
||||
<vn-autocomplete vn-focus vn-one
|
||||
url="/api/Items"
|
||||
field="sale.itemFk"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
search-function="{or: [{id: $search}, {name: {like: '%' + $search + '%'}}]}">
|
||||
</vn-autocomplete>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
<vn-td-editable disabled="!$ctrl.isEditable" number>
|
||||
<text>{{sale.quantity}}</text>
|
||||
<field>
|
||||
<vn-input-number vn-focus
|
||||
model="sale.quantity"
|
||||
on-change="$ctrl.onChangeQuantity(sale)">
|
||||
</vn-input-number>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
<vn-td-editable disabled="!sale.id || !$ctrl.isEditable" expand>
|
||||
<text>
|
||||
<vn-fetched-tags
|
||||
max-length="6"
|
||||
item="::sale.tags"
|
||||
sub-name="::sale.subName">
|
||||
item="sale.item"
|
||||
name="sale.concept"
|
||||
sub-name="sale.subName">
|
||||
</vn-fetched-tags>
|
||||
</vn-td>
|
||||
<vn-td number ng-if="$ctrl.isEditable">
|
||||
<span class="link"
|
||||
vn-tooltip="Edit price"
|
||||
ng-click="$ctrl.showEditPricePopover($event, sale)">
|
||||
{{sale.price | currency: 'EUR':2}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td number ng-if="!$ctrl.isEditable">
|
||||
</text>
|
||||
<field>
|
||||
<vn-textfield vn-id="concept" vn-focus
|
||||
model="sale.concept"
|
||||
on-change="$ctrl.updateConcept(sale)">
|
||||
</vn-textfield>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
<vn-td number>
|
||||
<span ng-class="{'link': $ctrl.isEditable}"
|
||||
title="{{$ctrl.isEditable ? 'Edit price' : ''}}"
|
||||
ng-click="$ctrl.showEditPricePopover($event, sale)">
|
||||
{{sale.price | currency: 'EUR':2}}
|
||||
</vn-td>
|
||||
<vn-td number ng-if="!$ctrl.ticket.refFk && $ctrl.isEditable">
|
||||
<span class="link"
|
||||
vn-tooltip="Edit discount"
|
||||
ng-click="$ctrl.showEditPopover($event, sale)">
|
||||
{{sale.discount}} %
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td number
|
||||
ng-if="!$ctrl.isEditable">
|
||||
{{sale.discount}} %
|
||||
</vn-td>
|
||||
<vn-td number>
|
||||
{{$ctrl.getSaleTotal(sale) | currency: 'EUR':2}}
|
||||
</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-vertical>
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td number>
|
||||
<span ng-class="{'link': $ctrl.isEditable}"
|
||||
title="{{$ctrl.isEditable ? 'Edit discount' : ''}}"
|
||||
ng-click="$ctrl.showEditDiscountPopover($event, sale)">
|
||||
{{sale.discount | percentage}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td number>
|
||||
{{$ctrl.getSaleTotal(sale) | currency: 'EUR':2}}
|
||||
</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
<vn-one>
|
||||
<vn-icon-button vn-none
|
||||
vn-tooltip="Add item"
|
||||
vn-bind="+"
|
||||
icon="add_circle"
|
||||
ng-click="$ctrl.add()"
|
||||
disabled="!$ctrl.isEditable">
|
||||
</vn-icon-button>
|
||||
</vn-one>
|
||||
</vn-card>
|
||||
<vn-item-descriptor-popover vn-id="descriptor"
|
||||
quicklinks="$ctrl.quicklinks">
|
||||
|
@ -248,7 +268,9 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-if="$ctrl.lastThreeTickets.length === 0" ><td colspan="4" style="text-align: center" translate>No results</td></tr>
|
||||
<tr ng-if="$ctrl.lastThreeTickets.length === 0" >
|
||||
<td colspan="4" style="text-align: center" translate>No results</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="clickable"
|
||||
ng-repeat="ticket in $ctrl.lastThreeTickets track by ticket.id"
|
||||
|
@ -305,7 +327,7 @@
|
|||
ng-show="$ctrl.isEditable"
|
||||
ng-click="$ctrl.newOrderFromTicket()"
|
||||
icon="add"
|
||||
vn-tooltip="New item"
|
||||
vn-tooltip="Add item"
|
||||
vn-bind="+"
|
||||
fixed-bottom-right>
|
||||
</vn-float-button>
|
|
@ -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'];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
<vn-fetched-tags
|
||||
max-length="6"
|
||||
item="::sale.item"
|
||||
name="::sale.concept"
|
||||
sub-name="::sale.item.subName"/>
|
||||
</vn-td>
|
||||
<vn-td number>{{::sale.price | currency: 'EUR':2}}</vn-td>
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
<vn-fetched-tags
|
||||
max-length="6"
|
||||
item="::sale.item"
|
||||
name="::sale.concept"
|
||||
sub-name="::sale.item.subName"/>
|
||||
</vn-td>
|
||||
<vn-td number>{{::sale.quantity}}</vn-td>
|
||||
|
|
Loading…
Reference in New Issue