This commit is contained in:
Carlos Jimenez Ruiz 2019-07-02 14:44:39 +02:00
commit 4b0a71644f
46 changed files with 601 additions and 200 deletions

View File

@ -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');

View File

@ -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',

View File

@ -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');

View File

@ -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!');

View File

@ -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

View File

@ -6,10 +6,6 @@ vn-calendar.small {
}
}
vn-calendar[disabled] .day .day-number {
cursor: not-allowed
}
vn-calendar {
display: block;

View File

@ -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"

View File

@ -16,6 +16,10 @@ vn-check {
}
md-checkbox {
margin-bottom: 0.8em
margin-bottom: 0.8em;
.md-label:empty {
margin: 0
}
}
}

View File

@ -1,9 +1,5 @@
vn-multi-check {
md-checkbox {
margin-bottom: 0.8em;
.md-label {
margin: 0
}
margin-bottom: 0.8em
}
}

View File

@ -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;

View File

@ -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.element.addEventListener('focusout', event => this.hideField(event));
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 != element)
while (target && target != this.element)
target = target.parentNode;
if (!target) {
this.tScope.$destroy();
this.tScope = null;
this.field.remove();
this.field = null;
element.classList.remove('selected');
element.tabIndex = 0;
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'];

View File

@ -1,11 +1,21 @@
@import "variables";
vn-td-editable {
text {
border-bottom: 1px solid rgba(0,0,0,.12);
min-height: 15px;
cursor: pointer;
display: block
outline: none;
position: relative;
overflow: visible;
text {
border: 1px dashed rgba(0, 0, 0, .15);
border-radius: 1em;
padding: 5px 10px;
min-height: 15px;
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;
@ -55,3 +57,10 @@ vn-td-editable {
display: flex;
}
}
vn-td-editable.disabled {
cursor: initial;
}
vn-td-editable.disabled text {
border: none;
}

View File

@ -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;
}

View File

@ -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>`;

View File

@ -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);

View File

@ -26,10 +26,6 @@
line-height: 0px;
}
input:disabled, button:disabled {
cursor: not-allowed !important;
}
input[type="submit"]:disabled, button:disabled {
opacity: 0.7;
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -197,7 +197,7 @@
"foreignKey": "clientFk"
},
"claimsRatio": {
"type": "hasMany",
"type": "hasOne",
"model": "ClaimRatio",
"foreignKey": "clientFk"
}

View File

@ -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>

View File

@ -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'];

View File

@ -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

View File

@ -6,6 +6,7 @@ ngModule.component('vnFetchedTags', {
bindings: {
maxLength: '<',
item: '<',
name: '<?',
subName: '<?'
}
});

View File

@ -75,6 +75,7 @@
<vn-fetched-tags
max-length="6"
item="::item"
name="::item.name"
sub-name="::item.subName">
</vn-fetched-tags>
</vn-td>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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);

View File

@ -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'
}
});
};
};

View File

@ -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];
}

View File

@ -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();
});
});

View File

@ -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();
});
});

View File

@ -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`
});
};

View File

@ -13,7 +13,8 @@
"description": "Identifier"
},
"concept": {
"type": "String"
"type": "String",
"required": true
},
"quantity": {
"type": "Number"

View File

@ -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);
};

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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}}"
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-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>
<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-textfield vn-focus
<vn-input-number vn-focus
model="sale.quantity"
on-change="$ctrl.updateQuantity(sale.id, sale.quantity)"
type="text">
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.item"
name="sale.concept"
sub-name="sale.subName">
</vn-fetched-tags>
</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 expand>
<vn-fetched-tags
max-length="6"
item="::sale.tags"
sub-name="::sale.subName">
</vn-fetched-tags>
</vn-td>
<vn-td number ng-if="$ctrl.isEditable">
<span class="link"
vn-tooltip="Edit price"
<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}}
</span>
</vn-td>
<vn-td number ng-if="!$ctrl.isEditable">
{{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}} %
<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
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>
<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>

View File

@ -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});
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;
}
return lines;
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'];

View File

@ -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

View File

@ -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();
});
});
});
});

View File

@ -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>

View File

@ -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>