add new item from ticket.sale #1414, checkedLines fixed
This commit is contained in:
parent
dd38a21e8d
commit
bddfbc4b34
|
@ -1,2 +1,4 @@
|
||||||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
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)',
|
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',
|
firstSaleThumbnailImage: 'vn-ticket-sale:nth-child(1) vn-tr:nth-child(1) vn-td:nth-child(3) > img',
|
||||||
firstSaleZoomedImage: 'body > div > div > img',
|
firstSaleZoomedImage: 'body > div > div > img',
|
||||||
firstSaleQuantity: 'vn-textfield[model="sale.quantity"]:nth-child(1) input',
|
firstSaleQuantity: 'vn-input-number[model="sale.quantity"]:nth-child(1) input',
|
||||||
firstSaleQuantityCell: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td-editable',
|
firstSaleQuantityCell: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td-editable:nth-child(5)',
|
||||||
firstSaleQuantityClearInput: 'vn-textfield[model="sale.quantity"] div.suffix > i',
|
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)',
|
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',
|
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)',
|
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',
|
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)',
|
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)',
|
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)',
|
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:nth-child(6) section:nth-child(3)',
|
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',
|
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)',
|
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',
|
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 scanTime = '07:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.mondayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.mondayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText');
|
||||||
|
@ -28,6 +29,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '10:00';
|
const scanTime = '10:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.mondayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.mondayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText');
|
||||||
|
@ -39,6 +41,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '15:00';
|
const scanTime = '15:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.mondayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.mondayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText');
|
||||||
|
@ -50,6 +53,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '10:20';
|
const scanTime = '10:20';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.mondayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.mondayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfMonday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfMonday, 'innerText');
|
||||||
|
@ -78,6 +82,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '08:00';
|
const scanTime = '08:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfTuesday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfTuesday, 'innerText');
|
||||||
|
@ -89,6 +94,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '10:00';
|
const scanTime = '10:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfTuesday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfTuesday, 'innerText');
|
||||||
|
@ -100,6 +106,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '10:20';
|
const scanTime = '10:20';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfTuesday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfTuesday, 'innerText');
|
||||||
|
@ -111,6 +118,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '16:00';
|
const scanTime = '16:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfTuesday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfTuesday, 'innerText');
|
||||||
|
@ -131,6 +139,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '09:00';
|
const scanTime = '09:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfWednesday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfWednesday, 'innerText');
|
||||||
|
@ -142,6 +151,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '10:00';
|
const scanTime = '10:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfWednesday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfWednesday, 'innerText');
|
||||||
|
@ -153,6 +163,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '10:20';
|
const scanTime = '10:20';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfWednesday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfWednesday, 'innerText');
|
||||||
|
@ -164,6 +175,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '17:00';
|
const scanTime = '17:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfWednesday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfWednesday, 'innerText');
|
||||||
|
@ -184,6 +196,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '09:59';
|
const scanTime = '09:59';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfThursday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfThursday, 'innerText');
|
||||||
|
@ -195,6 +208,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '10:00';
|
const scanTime = '10:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfThursday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfThursday, 'innerText');
|
||||||
|
@ -206,6 +220,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '10:20';
|
const scanTime = '10:20';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfThursday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfThursday, 'innerText');
|
||||||
|
@ -217,6 +232,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '17:59';
|
const scanTime = '17:59';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfThursday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfThursday, 'innerText');
|
||||||
|
@ -237,6 +253,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '07:30';
|
const scanTime = '07:30';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.fridayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.fridayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfFriday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfFriday, 'innerText');
|
||||||
|
@ -248,6 +265,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '10:00';
|
const scanTime = '10:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.fridayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.fridayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfFriday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfFriday, 'innerText');
|
||||||
|
@ -259,6 +277,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '10:20';
|
const scanTime = '10:20';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.fridayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.fridayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfFriday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfFriday, 'innerText');
|
||||||
|
@ -270,6 +289,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '15:30';
|
const scanTime = '15:30';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.fridayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.fridayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfFriday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfFriday, 'innerText');
|
||||||
|
@ -299,6 +319,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '06:00';
|
const scanTime = '06:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.saturdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.saturdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfSaturday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfSaturday, 'innerText');
|
||||||
|
@ -310,6 +331,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '13:40';
|
const scanTime = '13:40';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.saturdayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.saturdayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfSaturday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfSaturday, 'innerText');
|
||||||
|
@ -330,6 +352,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '05:00';
|
const scanTime = '05:00';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.sundayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.sundayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfSunday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.firstEntryOfSunday, 'innerText');
|
||||||
|
@ -341,6 +364,7 @@ describe('Worker time control path', () => {
|
||||||
const scanTime = '12:40';
|
const scanTime = '12:40';
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.waitToClick(selectors.workerTimeControl.sundayAddTimeButton)
|
.waitToClick(selectors.workerTimeControl.sundayAddTimeButton)
|
||||||
|
.clearInput(selectors.workerTimeControl.timeDialogInput)
|
||||||
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
.write(selectors.workerTimeControl.timeDialogInput, scanTime)
|
||||||
.waitToClick(selectors.workerTimeControl.confirmButton)
|
.waitToClick(selectors.workerTimeControl.confirmButton)
|
||||||
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfSunday, 'innerText');
|
.waitToGetProperty(selectors.workerTimeControl.secondEntryOfSunday, 'innerText');
|
||||||
|
|
|
@ -148,6 +148,7 @@ describe('Ticket Edit sale path', () => {
|
||||||
|
|
||||||
it('should confirm the price have been updated', async() => {
|
it('should confirm the price have been updated', async() => {
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
|
.wait(1999)
|
||||||
.waitToGetProperty(`${selectors.ticketSales.firstSalePrice} span`, 'innerText');
|
.waitToGetProperty(`${selectors.ticketSales.firstSalePrice} span`, 'innerText');
|
||||||
|
|
||||||
expect(result).toContain('5.00');
|
expect(result).toContain('5.00');
|
||||||
|
@ -243,6 +244,7 @@ describe('Ticket Edit sale path', () => {
|
||||||
.waitToClick(selectors.ticketSales.thirdSaleCheckbox)
|
.waitToClick(selectors.ticketSales.thirdSaleCheckbox)
|
||||||
.waitToClick(selectors.ticketSales.deleteSaleButton)
|
.waitToClick(selectors.ticketSales.deleteSaleButton)
|
||||||
.waitToClick(selectors.ticketSales.acceptDeleteLineButton)
|
.waitToClick(selectors.ticketSales.acceptDeleteLineButton)
|
||||||
|
.waitForSpinnerLoad()
|
||||||
.waitForLastSnackbar();
|
.waitForLastSnackbar();
|
||||||
|
|
||||||
expect(result).toEqual('Data saved!');
|
expect(result).toEqual('Data saved!');
|
||||||
|
|
|
@ -30,6 +30,8 @@ export default class Autocomplete extends Input {
|
||||||
|
|
||||||
componentHandler.upgradeElement(
|
componentHandler.upgradeElement(
|
||||||
this.element.querySelector('.mdl-textfield'));
|
this.element.querySelector('.mdl-textfield'));
|
||||||
|
|
||||||
|
this.registerEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
$postLink() {
|
$postLink() {
|
||||||
|
@ -40,6 +42,15 @@ export default class Autocomplete extends Input {
|
||||||
this.refreshSelection();
|
this.refreshSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers all event emitters
|
||||||
|
*/
|
||||||
|
registerEvents() {
|
||||||
|
this.input.addEventListener('focus', event => {
|
||||||
|
this.emit('focus', {event});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get model() {
|
get model() {
|
||||||
return this._model;
|
return this._model;
|
||||||
}
|
}
|
||||||
|
@ -223,7 +234,6 @@ export default class Autocomplete extends Input {
|
||||||
const value = item[this.valueField];
|
const value = item[this.valueField];
|
||||||
this.selection = item;
|
this.selection = item;
|
||||||
this.setValue(value);
|
this.setValue(value);
|
||||||
this.field = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClearClick(event) {
|
onClearClick(event) {
|
||||||
|
@ -232,7 +242,7 @@ export default class Autocomplete extends Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(event) {
|
onKeyDown(event) {
|
||||||
if (event.defaultPrevented) return;
|
// if (event.defaultPrevented) return;
|
||||||
|
|
||||||
switch (event.keyCode) {
|
switch (event.keyCode) {
|
||||||
case 38: // Up
|
case 38: // Up
|
||||||
|
@ -241,6 +251,7 @@ export default class Autocomplete extends Input {
|
||||||
this.showDropDown();
|
this.showDropDown();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
console.log(event.key);
|
||||||
if (event.key.length == 1)
|
if (event.key.length == 1)
|
||||||
this.showDropDown(event.key);
|
this.showDropDown(event.key);
|
||||||
else
|
else
|
||||||
|
|
|
@ -6,10 +6,6 @@ vn-calendar.small {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vn-calendar[disabled] .day .day-number {
|
|
||||||
cursor: not-allowed
|
|
||||||
}
|
|
||||||
|
|
||||||
vn-calendar {
|
vn-calendar {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
ng-disabled="::$ctrl.disabled"
|
ng-disabled="::$ctrl.disabled"
|
||||||
ng-checked="$ctrl.isChecked"
|
ng-checked="$ctrl.isChecked"
|
||||||
ng-model="$ctrl.model">
|
ng-model="$ctrl.model">
|
||||||
<span translate>{{::$ctrl.label}}</span>
|
<span translate ng-if="::$ctrl.label">{{::$ctrl.label}}</span>
|
||||||
</md-checkbox>
|
</md-checkbox>
|
||||||
<i class="material-icons"
|
<i class="material-icons"
|
||||||
ng-if="::$ctrl.hasInfo"
|
ng-if="::$ctrl.hasInfo"
|
||||||
|
|
|
@ -16,6 +16,10 @@ vn-check {
|
||||||
}
|
}
|
||||||
|
|
||||||
md-checkbox {
|
md-checkbox {
|
||||||
margin-bottom: 0.8em
|
margin-bottom: 0.8em;
|
||||||
|
|
||||||
|
.md-label:empty {
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
vn-multi-check {
|
vn-multi-check {
|
||||||
md-checkbox {
|
md-checkbox {
|
||||||
margin-bottom: 0.8em;
|
margin-bottom: 0.8em
|
||||||
|
|
||||||
.md-label {
|
|
||||||
margin: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -63,6 +63,10 @@ vn-table {
|
||||||
padding-bottom: .8em;
|
padding-bottom: .8em;
|
||||||
}
|
}
|
||||||
& > vn-th,
|
& > vn-th,
|
||||||
|
& > vn-td {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
& > vn-th,
|
||||||
& > vn-td,
|
& > vn-td,
|
||||||
& > vn-td-editable {
|
& > vn-td-editable {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
@ -70,7 +74,6 @@ vn-table {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: .6em .5em;
|
padding: .6em .5em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-width: 5em;
|
max-width: 5em;
|
||||||
|
|
||||||
|
@ -157,7 +160,7 @@ vn-table {
|
||||||
}
|
}
|
||||||
vn-autocomplete {
|
vn-autocomplete {
|
||||||
div.mdl-textfield {
|
div.mdl-textfield {
|
||||||
padding: 0px !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
label.mdl-textfield__label:after {
|
label.mdl-textfield__label:after {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
|
@ -1,42 +1,71 @@
|
||||||
import ngModule from '../../module';
|
import ngModule from '../../module';
|
||||||
import Component from '../../lib/component';
|
import Component from '../../lib/component';
|
||||||
|
import Input from '../../lib/input';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
export default class Controller extends Component {
|
export default class Controller extends Component {
|
||||||
constructor($element, $scope, $transclude, $timeout) {
|
constructor($element, $scope, $transclude, $timeout) {
|
||||||
super($element, $scope);
|
super($element, $scope);
|
||||||
this.$timeout = $timeout;
|
this.$timeout = $timeout;
|
||||||
let element = $element[0];
|
this.element.tabIndex = 0;
|
||||||
element.tabIndex = 0;
|
|
||||||
|
|
||||||
element.addEventListener('focus', () => {
|
this.element.addEventListener('focus', () => {
|
||||||
if (this.field || this.disabled) return;
|
if (this.field || this.disabled) return;
|
||||||
$transclude((tClone, tScope) => {
|
$transclude((tClone, tScope) => {
|
||||||
this.field = tClone;
|
this.field = tClone;
|
||||||
this.tScope = tScope;
|
this.tScope = tScope;
|
||||||
this.element.querySelector('.field').appendChild(this.field[0]);
|
this.element.querySelector('.field').appendChild(this.field[0]);
|
||||||
element.tabIndex = -1;
|
this.element.tabIndex = -1;
|
||||||
}, null, 'field');
|
}, null, 'field');
|
||||||
element.classList.add('selected');
|
this.element.classList.add('selected');
|
||||||
});
|
});
|
||||||
|
|
||||||
element.addEventListener('focusout', event => {
|
this.element.addEventListener('focusout', event => this.hideField(event));
|
||||||
if (!this.field || this.disabled) return;
|
|
||||||
// this.destroyTimer();
|
|
||||||
this.lastEvent = event;
|
|
||||||
let target = event.relatedTarget;
|
|
||||||
while (target && target != element)
|
|
||||||
target = target.parentNode;
|
|
||||||
|
|
||||||
if (!target) {
|
this.element.addEventListener('keyup', event => {
|
||||||
this.tScope.$destroy();
|
if (event.key === 'Enter')
|
||||||
this.field.remove();
|
this.hideField(event);
|
||||||
this.field = null;
|
});
|
||||||
element.classList.remove('selected');
|
|
||||||
element.tabIndex = 0;
|
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() {
|
destroyTimer() {
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
this.$timeout.cancel(this.timer);
|
this.$timeout.cancel(this.timer);
|
||||||
|
@ -47,6 +76,21 @@ export default class Controller extends Component {
|
||||||
$onDestroy() {
|
$onDestroy() {
|
||||||
this.destroyTimer();
|
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'];
|
Controller.$inject = ['$element', '$scope', '$transclude', '$timeout'];
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
@import "variables";
|
@import "variables";
|
||||||
|
|
||||||
vn-td-editable {
|
vn-td-editable {
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
text {
|
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;
|
min-height: 15px;
|
||||||
cursor: pointer;
|
display: block;
|
||||||
display: block
|
overflow: hidden;
|
||||||
|
&:hover {
|
||||||
|
border-color: $color-main;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
text::after {
|
text::after {
|
||||||
|
@ -13,25 +23,17 @@ vn-td-editable {
|
||||||
content: '';
|
content: '';
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
outline: none;
|
|
||||||
position: relative;
|
|
||||||
&:not([disabled="true"]) {
|
|
||||||
cursor: initial
|
|
||||||
}
|
|
||||||
&[disabled="true"] {
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
&.selected > .text {
|
&.selected > .text {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
& > .field {
|
& > .field {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index:10;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .6em;
|
padding: .6em;
|
||||||
|
@ -55,3 +57,10 @@ vn-td-editable {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vn-td-editable.disabled {
|
||||||
|
cursor: initial;
|
||||||
|
}
|
||||||
|
vn-td-editable.disabled text {
|
||||||
|
border: none;
|
||||||
|
}
|
|
@ -1,13 +1,20 @@
|
||||||
import ngModule from '../module';
|
import ngModule from '../module';
|
||||||
|
|
||||||
export function focus(input) {
|
export function focus(input) {
|
||||||
|
const element = input;
|
||||||
let selector = 'input, textarea, button, submit';
|
let selector = 'input, textarea, button, submit';
|
||||||
|
|
||||||
if (!input.matches(selector))
|
if (!input.matches(selector))
|
||||||
input = input.querySelector(selector);
|
input = input.querySelector(selector);
|
||||||
|
|
||||||
if (!input) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,16 +24,9 @@ describe('Directive focus', () => {
|
||||||
let childHtml = '<input></input>';
|
let childHtml = '<input></input>';
|
||||||
compile(html, childHtml);
|
compile(html, childHtml);
|
||||||
|
|
||||||
expect($element[0].firstChild.focus).toHaveBeenCalled();
|
expect($element[0].firstChild.focus).toHaveBeenCalledWith();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print a warning message on console', () => {
|
|
||||||
let html = `<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', () => {
|
it('should call focus function on the element', () => {
|
||||||
let html = `<input vn-focus></input>`;
|
let html = `<input vn-focus></input>`;
|
||||||
|
|
|
@ -9,7 +9,7 @@ export default function percentage() {
|
||||||
return function(input) {
|
return function(input) {
|
||||||
if (input == null || input === '')
|
if (input == null || input === '')
|
||||||
return null;
|
return null;
|
||||||
return input * 100 + ' %';
|
return `${input} %`;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
ngModule.filter('percentage', percentage);
|
ngModule.filter('percentage', percentage);
|
||||||
|
|
|
@ -26,10 +26,6 @@
|
||||||
line-height: 0px;
|
line-height: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:disabled, button:disabled {
|
|
||||||
cursor: not-allowed !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="submit"]:disabled, button:disabled {
|
input[type="submit"]:disabled, button:disabled {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,5 +49,6 @@
|
||||||
"NO_ZONE_FOR_THIS_PARAMETERS": "NO_ZONE_FOR_THIS_PARAMETERS",
|
"NO_ZONE_FOR_THIS_PARAMETERS": "NO_ZONE_FOR_THIS_PARAMETERS",
|
||||||
"This client can't be invoiced": "This client can't be invoiced",
|
"This client can't be invoiced": "This client can't be invoiced",
|
||||||
"The introduced hour already exists": "The introduced hour already exists",
|
"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",
|
"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 client can't be invoiced": "Este cliente no puede ser facturado",
|
||||||
"This ticket can't be invoiced": "Este ticket 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",
|
"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",
|
"This ticket can not be modified": "Este ticket no puede ser modificado",
|
||||||
"The introduced hour already exists": "Esta hora ya ha sido introducida",
|
"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",
|
"This item doesn't exists": "El artículo no existe",
|
||||||
"NOT_ZONE_WITH_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada",
|
"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",
|
"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"
|
"foreignKey": "clientFk"
|
||||||
},
|
},
|
||||||
"claimsRatio": {
|
"claimsRatio": {
|
||||||
"type": "hasMany",
|
"type": "hasOne",
|
||||||
"model": "ClaimRatio",
|
"model": "ClaimRatio",
|
||||||
"foreignKey": "clientFk"
|
"foreignKey": "clientFk"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,13 +155,13 @@
|
||||||
value="{{$ctrl.summary.mana.mana | currency: 'EUR':2}}">
|
value="{{$ctrl.summary.mana.mana | currency: 'EUR':2}}">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
<vn-label-value label="Rate"
|
<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>
|
||||||
<vn-label-value label="Average invoiced"
|
<vn-label-value label="Average invoiced"
|
||||||
value="{{$ctrl.summary.averageInvoiced.invoiced | currency: 'EUR':2}}">
|
value="{{$ctrl.summary.averageInvoiced.invoiced | currency: 'EUR':2}}">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
<vn-label-value label="Claims"
|
<vn-label-value label="Claims"
|
||||||
value="{{$ctrl.summary.claimsRatio[0].claimingRate | percentage}}">
|
value="{{$ctrl.claimingRate($ctrl.summary.claimsRatio.claimingRate) | percentage}}">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
<vn-one>
|
<vn-one>
|
||||||
|
|
|
@ -29,6 +29,16 @@ class Controller {
|
||||||
});
|
});
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
claimRate(priceIncreasing) {
|
||||||
|
if (priceIncreasing)
|
||||||
|
return priceIncreasing * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
claimingRate(rate) {
|
||||||
|
if (rate)
|
||||||
|
return rate * 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller.$inject = ['$http'];
|
Controller.$inject = ['$http'];
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<vn-horizontal>
|
<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">
|
<vn-one ng-if="$ctrl.subName">
|
||||||
<h3 title="{{::$ctrl.subName}}">{{::$ctrl.subName}}</h3>
|
<h3 title="{{$ctrl.subName}}">{{$ctrl.subName}}</h3>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
<vn-auto>
|
<vn-auto>
|
||||||
<section
|
<section
|
||||||
|
|
|
@ -6,6 +6,7 @@ ngModule.component('vnFetchedTags', {
|
||||||
bindings: {
|
bindings: {
|
||||||
maxLength: '<',
|
maxLength: '<',
|
||||||
item: '<',
|
item: '<',
|
||||||
|
name: '<?',
|
||||||
subName: '<?'
|
subName: '<?'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
<vn-fetched-tags
|
<vn-fetched-tags
|
||||||
max-length="6"
|
max-length="6"
|
||||||
item="::item"
|
item="::item"
|
||||||
|
name="::item.name"
|
||||||
sub-name="::item.subName">
|
sub-name="::item.subName">
|
||||||
</vn-fetched-tags>
|
</vn-fetched-tags>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
<vn-fetched-tags
|
<vn-fetched-tags
|
||||||
max-length="6"
|
max-length="6"
|
||||||
item="::row.item"
|
item="::row.item"
|
||||||
|
name="::row.item.name"
|
||||||
sub-name="::row.item.subName">
|
sub-name="::row.item.subName">
|
||||||
</vn-fetched-tags>
|
</vn-fetched-tags>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
<vn-fetched-tags
|
<vn-fetched-tags
|
||||||
max-length="6"
|
max-length="6"
|
||||||
item="::row.item"
|
item="::row.item"
|
||||||
|
name="::row.item.name"
|
||||||
sub-name="::row.item.subName">
|
sub-name="::row.item.subName">
|
||||||
</vn-fetched-tags>
|
</vn-fetched-tags>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
<vn-fetched-tags
|
<vn-fetched-tags
|
||||||
max-length="6"
|
max-length="6"
|
||||||
item="::row.item"
|
item="::row.item"
|
||||||
|
name="::row.item.name"
|
||||||
sub-name="::row.item.subName">
|
sub-name="::row.item.subName">
|
||||||
</vn-fetched-tags>
|
</vn-fetched-tags>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
|
|
|
@ -19,7 +19,7 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: 'string',
|
type: 'Number',
|
||||||
root: true
|
root: true
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -36,7 +36,6 @@ module.exports = Self => {
|
||||||
let options = {transaction: tx};
|
let options = {transaction: tx};
|
||||||
|
|
||||||
let filter = {
|
let filter = {
|
||||||
fields: ['id', 'ticketFk', 'price'],
|
|
||||||
include: {
|
include: {
|
||||||
relation: 'ticket',
|
relation: 'ticket',
|
||||||
scope: {
|
scope: {
|
||||||
|
@ -88,6 +87,8 @@ module.exports = Self => {
|
||||||
await Self.rawSql(query, [salesPerson], options);
|
await Self.rawSql(query, [salesPerson], options);
|
||||||
|
|
||||||
await tx.commit();
|
await tx.commit();
|
||||||
|
|
||||||
|
return sale;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
@ -45,20 +45,15 @@ module.exports = Self => {
|
||||||
include: {relation: 'ticket'}
|
include: {relation: 'ticket'}
|
||||||
});
|
});
|
||||||
|
|
||||||
let query = `CALL vn.getItemVisibleAvailable(?,?,?,?)`;
|
let [[stock]] = await Self.rawSql(`CALL vn.getItemVisibleAvailable(?,?,?,?)`, [
|
||||||
|
|
||||||
let params = [
|
|
||||||
ctx.args.itemFk,
|
ctx.args.itemFk,
|
||||||
request.ticket().shipped,
|
request.ticket().shipped,
|
||||||
request.ticket().warehouseFk,
|
request.ticket().warehouseFk,
|
||||||
false
|
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) {
|
if (request.saleFk) {
|
||||||
let sale = await models.Sale.findById(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;
|
claimMap[claim.saleFk] = claim;
|
||||||
|
|
||||||
for (line of lines) {
|
for (line of lines) {
|
||||||
line.tags = map[line.itemFk];
|
line.item = map[line.itemFk];
|
||||||
line.claim = claimMap[line.id];
|
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);
|
let sales = await app.models.Ticket.getSales(16);
|
||||||
|
|
||||||
expect(sales.length).toEqual(4);
|
expect(sales.length).toEqual(4);
|
||||||
expect(sales[0].tags).toBeDefined();
|
expect(sales[0].item).toBeDefined();
|
||||||
expect(sales[1].tags).toBeDefined();
|
expect(sales[1].item).toBeDefined();
|
||||||
expect(sales[2].tags).toBeDefined();
|
expect(sales[2].item).toBeDefined();
|
||||||
expect(sales[3].tags).toBeDefined();
|
expect(sales[3].item).toBeDefined();
|
||||||
expect(sales[0].claim).toBeDefined();
|
expect(sales[0].claim).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,4 +6,8 @@ module.exports = Self => {
|
||||||
require('../methods/sale/removes')(Self);
|
require('../methods/sale/removes')(Self);
|
||||||
require('../methods/sale/updatePrice')(Self);
|
require('../methods/sale/updatePrice')(Self);
|
||||||
require('../methods/sale/updateQuantity')(Self);
|
require('../methods/sale/updateQuantity')(Self);
|
||||||
|
|
||||||
|
Self.validatesPresenceOf('concept', {
|
||||||
|
message: `Concept cannot be blank`
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
"description": "Identifier"
|
"description": "Identifier"
|
||||||
},
|
},
|
||||||
"concept": {
|
"concept": {
|
||||||
"type": "String"
|
"type": "String",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
"quantity": {
|
"quantity": {
|
||||||
"type": "Number"
|
"type": "Number"
|
||||||
|
|
|
@ -22,4 +22,5 @@ module.exports = Self => {
|
||||||
require('../methods/ticket/checkEmptiness')(Self);
|
require('../methods/ticket/checkEmptiness')(Self);
|
||||||
require('../methods/ticket/updateDiscount')(Self);
|
require('../methods/ticket/updateDiscount')(Self);
|
||||||
require('../methods/ticket/uploadFile')(Self);
|
require('../methods/ticket/uploadFile')(Self);
|
||||||
|
require('../methods/ticket/addSale')(Self);
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
<td expand>
|
<td expand>
|
||||||
<vn-fetched-tags
|
<vn-fetched-tags
|
||||||
max-length="6"
|
max-length="6"
|
||||||
item="::sale.item">
|
item="::sale.item"
|
||||||
|
name="::sale.concept">
|
||||||
</vn-fetched-tags>
|
</vn-fetched-tags>
|
||||||
</td>
|
</td>
|
||||||
<td number>{{::sale.quantity}}</td>
|
<td number>{{::sale.quantity}}</td>
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
<vn-fetched-tags
|
<vn-fetched-tags
|
||||||
max-length="6"
|
max-length="6"
|
||||||
item="::sale.item"
|
item="::sale.item"
|
||||||
|
name="::sale.concept"
|
||||||
sub-name="::sale.item.subName">
|
sub-name="::sale.item.subName">
|
||||||
</vn-fetched-tags>
|
</vn-fetched-tags>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
<vn-fetched-tags
|
<vn-fetched-tags
|
||||||
max-length="6"
|
max-length="6"
|
||||||
item="::sale.item"
|
item="::sale.item"
|
||||||
|
name="::sale.concept"
|
||||||
sub-name="::sale.item.subName">
|
sub-name="::sale.item.subName">
|
||||||
</vn-fetched-tags>
|
</vn-fetched-tags>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
<vn-fetched-tags
|
<vn-fetched-tags
|
||||||
max-length="6"
|
max-length="6"
|
||||||
item="::sale.item"
|
item="::sale.item"
|
||||||
|
name="::sale.concept"
|
||||||
sub-name="::sale.item.subName">
|
sub-name="::sale.item.subName">
|
||||||
</vn-fetched-tags>
|
</vn-fetched-tags>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
|
|
|
@ -93,66 +93,86 @@
|
||||||
</vn-icon>
|
</vn-icon>
|
||||||
<vn-icon ng-show="sale.reserved"
|
<vn-icon ng-show="sale.reserved"
|
||||||
icon="icon-reserve"
|
icon="icon-reserve"
|
||||||
vn-tooltip="{{::$ctrl.$translate.instant('Reserved')}}"></vn-icon>
|
vn-tooltip="{{::$ctrl.$translate.instant('Reserved')}}">
|
||||||
|
</vn-icon>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<img
|
<img
|
||||||
ng-src="{{::$ctrl.imagesPath}}/50x50/{{::sale.image}}"
|
ng-src="{{::$ctrl.imagesPath}}/50x50/{{sale.image}}"
|
||||||
zoom-image="{{::$ctrl.imagesPath}}/1600x900/{{::sale.image}}"
|
zoom-image="{{::$ctrl.imagesPath}}/1600x900/{{sale.image}}"
|
||||||
on-error-src/>
|
on-error-src/>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td number>
|
<vn-td-editable disabled="sale.itemFk" vn-focus number>
|
||||||
<span
|
<text>
|
||||||
ng-click="$ctrl.showDescriptor($event, sale.itemFk)"
|
<span class="link" ng-show="sale.itemFk"
|
||||||
class="link">
|
ng-click="$ctrl.showDescriptor($event, sale.itemFk)">
|
||||||
{{::sale.itemFk | zeroFill:6}}
|
{{sale.itemFk | zeroFill:6}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</text>
|
||||||
<vn-td-editable editable="!$ctrl.isEditable">
|
<field>
|
||||||
<text>{{sale.quantity}}</text>
|
<vn-autocomplete vn-focus vn-one
|
||||||
<field>
|
url="/api/Items"
|
||||||
<vn-textfield vn-focus
|
field="sale.itemFk"
|
||||||
model="sale.quantity"
|
show-field="name"
|
||||||
on-change="$ctrl.updateQuantity(sale.id, sale.quantity)"
|
value-field="id"
|
||||||
type="text">
|
search-function="{or: [{id: $search}, {name: {like: '%' + $search + '%'}}]}">
|
||||||
</vn-textfield>
|
</vn-autocomplete>
|
||||||
</field>
|
</field>
|
||||||
</vn-td-editable>
|
</vn-td-editable>
|
||||||
<vn-td expand>
|
<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
|
<vn-fetched-tags
|
||||||
max-length="6"
|
max-length="6"
|
||||||
item="::sale.tags"
|
item="sale.item"
|
||||||
sub-name="::sale.subName">
|
name="sale.concept"
|
||||||
|
sub-name="sale.subName">
|
||||||
</vn-fetched-tags>
|
</vn-fetched-tags>
|
||||||
</vn-td>
|
</text>
|
||||||
<vn-td number ng-if="$ctrl.isEditable">
|
<field>
|
||||||
<span class="link"
|
<vn-textfield vn-id="concept" vn-focus
|
||||||
vn-tooltip="Edit price"
|
model="sale.concept"
|
||||||
ng-click="$ctrl.showEditPricePopover($event, sale)">
|
on-change="$ctrl.updateConcept(sale)">
|
||||||
{{sale.price | currency: 'EUR':2}}
|
</vn-textfield>
|
||||||
</span>
|
</field>
|
||||||
</vn-td>
|
</vn-td-editable>
|
||||||
<vn-td number ng-if="!$ctrl.isEditable">
|
<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}}
|
{{sale.price | currency: 'EUR':2}}
|
||||||
</vn-td>
|
</span>
|
||||||
<vn-td number ng-if="!$ctrl.ticket.refFk && $ctrl.isEditable">
|
</vn-td>
|
||||||
<span class="link"
|
<vn-td number>
|
||||||
vn-tooltip="Edit discount"
|
<span ng-class="{'link': $ctrl.isEditable}"
|
||||||
ng-click="$ctrl.showEditPopover($event, sale)">
|
title="{{$ctrl.isEditable ? 'Edit discount' : ''}}"
|
||||||
{{sale.discount}} %
|
ng-click="$ctrl.showEditDiscountPopover($event, sale)">
|
||||||
</span>
|
{{sale.discount | percentage}}
|
||||||
</vn-td>
|
</span>
|
||||||
<vn-td number
|
</vn-td>
|
||||||
ng-if="!$ctrl.isEditable">
|
<vn-td number>
|
||||||
{{sale.discount}} %
|
{{$ctrl.getSaleTotal(sale) | currency: 'EUR':2}}
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td number>
|
</vn-tr>
|
||||||
{{$ctrl.getSaleTotal(sale) | currency: 'EUR':2}}
|
</vn-tbody>
|
||||||
</vn-td>
|
</vn-table>
|
||||||
</vn-tr>
|
<vn-one>
|
||||||
</vn-tbody>
|
<vn-icon-button vn-none
|
||||||
</vn-table>
|
vn-tooltip="Add item"
|
||||||
</vn-vertical>
|
vn-bind="+"
|
||||||
|
icon="add_circle"
|
||||||
|
ng-click="$ctrl.add()"
|
||||||
|
disabled="!$ctrl.isEditable">
|
||||||
|
</vn-icon-button>
|
||||||
|
</vn-one>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
<vn-item-descriptor-popover vn-id="descriptor"
|
<vn-item-descriptor-popover vn-id="descriptor"
|
||||||
quicklinks="$ctrl.quicklinks">
|
quicklinks="$ctrl.quicklinks">
|
||||||
|
@ -248,7 +268,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<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
|
<tr
|
||||||
class="clickable"
|
class="clickable"
|
||||||
ng-repeat="ticket in $ctrl.lastThreeTickets track by ticket.id"
|
ng-repeat="ticket in $ctrl.lastThreeTickets track by ticket.id"
|
||||||
|
@ -305,7 +327,7 @@
|
||||||
ng-show="$ctrl.isEditable"
|
ng-show="$ctrl.isEditable"
|
||||||
ng-click="$ctrl.newOrderFromTicket()"
|
ng-click="$ctrl.newOrderFromTicket()"
|
||||||
icon="add"
|
icon="add"
|
||||||
vn-tooltip="New item"
|
vn-tooltip="Add item"
|
||||||
vn-bind="+"
|
vn-bind="+"
|
||||||
fixed-bottom-right>
|
fixed-bottom-right>
|
||||||
</vn-float-button>
|
</vn-float-button>
|
|
@ -17,9 +17,10 @@ class Controller {
|
||||||
{callback: this.createClaim, name: 'Add claim'},
|
{callback: this.createClaim, name: 'Add claim'},
|
||||||
{callback: this.showSMSDialog, name: 'Send SMS'}
|
{callback: this.showSMSDialog, name: 'Send SMS'}
|
||||||
];
|
];
|
||||||
|
this._sales = [];
|
||||||
this.imagesPath = '//verdnatura.es/vn-image-data/catalog';
|
this.imagesPath = '//verdnatura.es/vn-image-data/catalog';
|
||||||
}
|
}
|
||||||
|
|
||||||
get sales() {
|
get sales() {
|
||||||
return this._sales;
|
return this._sales;
|
||||||
}
|
}
|
||||||
|
@ -38,7 +39,6 @@ class Controller {
|
||||||
this.updateNewPrice();
|
this.updateNewPrice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
refreshTotal() {
|
refreshTotal() {
|
||||||
this.loadSubTotal();
|
this.loadSubTotal();
|
||||||
this.loadVAT();
|
this.loadVAT();
|
||||||
|
@ -52,6 +52,9 @@ class Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
getSaleTotal(sale) {
|
getSaleTotal(sale) {
|
||||||
|
if (!sale.quantity || !sale.price)
|
||||||
|
return;
|
||||||
|
|
||||||
return sale.quantity * sale.price * ((100 - sale.discount) / 100);
|
return sale.quantity * sale.price * ((100 - sale.discount) / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,16 +102,45 @@ class Controller {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns checked instances
|
||||||
|
*
|
||||||
|
* @return {Array} Checked instances
|
||||||
|
*/
|
||||||
getCheckedLines() {
|
getCheckedLines() {
|
||||||
let lines = [];
|
if (!this.sales) return;
|
||||||
let data = this.sales;
|
|
||||||
if (data) {
|
return this.sales.filter(sale => {
|
||||||
for (let i = 0; i < data.length; i++) {
|
return sale.checked;
|
||||||
if (data[i].checked)
|
});
|
||||||
lines.push({id: data[i].id, instance: i});
|
}
|
||||||
}
|
|
||||||
}
|
/**
|
||||||
return lines;
|
* 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() {
|
onStateOkClick() {
|
||||||
|
@ -130,21 +162,21 @@ class Controller {
|
||||||
onRemoveLinesClick(response) {
|
onRemoveLinesClick(response) {
|
||||||
if (response === 'ACCEPT') {
|
if (response === 'ACCEPT') {
|
||||||
let sales = this.getCheckedLines();
|
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 params = {sales: sales, actualTicketFk: this.ticket.id};
|
||||||
let query = `/api/Sales/removes`;
|
let query = `/api/Sales/removes`;
|
||||||
this.$http.post(query, params).then(() => {
|
this.$http.post(query, params).then(() => {
|
||||||
this.removeInstances(sales);
|
this.removeCheckedLines();
|
||||||
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
|
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeInstances(instances) {
|
|
||||||
for (let i of instances)
|
|
||||||
this.sales.splice(i.instance, 1);
|
|
||||||
this.refreshTotal();
|
|
||||||
}
|
|
||||||
|
|
||||||
showRemoveLinesDialog() {
|
showRemoveLinesDialog() {
|
||||||
this.$scope.deleteLines.show();
|
this.$scope.deleteLines.show();
|
||||||
}
|
}
|
||||||
|
@ -208,14 +240,12 @@ class Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
createClaim() {
|
createClaim() {
|
||||||
let claim = {
|
const claim = {
|
||||||
ticketFk: this.ticket.id,
|
ticketFk: this.ticket.id,
|
||||||
clientFk: this.ticket.clientFk,
|
clientFk: this.ticket.clientFk,
|
||||||
ticketCreated: this.ticket.shipped
|
ticketCreated: this.ticket.shipped
|
||||||
};
|
};
|
||||||
let sales = this.getCheckedLines();
|
const sales = this.getCheckedLines();
|
||||||
for (let i = 0; i < sales.length; i++)
|
|
||||||
sales[i].quantity = this.sales[sales[i].instance].quantity;
|
|
||||||
this.$http.post(`/api/Claims/createFromSales`, {claim: claim, sales: sales}).then(res => {
|
this.$http.post(`/api/Claims/createFromSales`, {claim: claim, sales: sales}).then(res => {
|
||||||
this.$state.go('claim.card.basicData', {id: res.data.id});
|
this.$state.go('claim.card.basicData', {id: res.data.id});
|
||||||
});
|
});
|
||||||
|
@ -255,6 +285,7 @@ class Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
showEditPricePopover(event, sale) {
|
showEditPricePopover(event, sale) {
|
||||||
|
if (!this.isEditable) return;
|
||||||
this.sale = sale;
|
this.sale = sale;
|
||||||
this.editedPrice = this.sale.price;
|
this.editedPrice = this.sale.price;
|
||||||
this.edit = {
|
this.edit = {
|
||||||
|
@ -268,10 +299,11 @@ class Controller {
|
||||||
|
|
||||||
updatePrice() {
|
updatePrice() {
|
||||||
if (this.editedPrice != this.sale.price) {
|
if (this.editedPrice != this.sale.price) {
|
||||||
this.$http.post(`/api/Sales/${this.edit.id}/updatePrice`, {newPrice: this.editedPrice}).then(() => {
|
this.$http.post(`/api/Sales/${this.edit.id}/updatePrice`, {newPrice: this.editedPrice}).then(res => {
|
||||||
this.sale.price = this.edit.price;
|
if (res.data)
|
||||||
|
this.sale.price = res.data.price;
|
||||||
|
|
||||||
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
|
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);
|
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.sale = sale;
|
||||||
this.edit = [{
|
this.edit = [{
|
||||||
ticketFk: this.ticket.id,
|
ticketFk: this.ticket.id,
|
||||||
|
@ -310,24 +344,14 @@ class Controller {
|
||||||
this.$scope.editPopover.hide();
|
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
|
* Unmark sale as reserved
|
||||||
*/
|
*/
|
||||||
unmarkAsReserved() {
|
unmarkAsReserved() {
|
||||||
this.setReserved(false);
|
this.setReserved(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Mark sale as reserved
|
* Mark sale as reserved
|
||||||
*/
|
*/
|
||||||
markAsReserved() {
|
markAsReserved() {
|
||||||
|
@ -363,10 +387,9 @@ class Controller {
|
||||||
|
|
||||||
showSMSDialog() {
|
showSMSDialog() {
|
||||||
const address = this.ticket.address;
|
const address = this.ticket.address;
|
||||||
const lines = this.getCheckedLines();
|
const sales = this.getCheckedLines();
|
||||||
const items = lines.map(line => {
|
const items = sales.map(sale => {
|
||||||
const instance = this.sales[line.instance];
|
return `${sale.quantity} ${sale.concept}`;
|
||||||
return `${instance.quantity} ${instance.concept}`;
|
|
||||||
});
|
});
|
||||||
const notAvailables = items.join(', ');
|
const notAvailables = items.join(', ');
|
||||||
const params = {
|
const params = {
|
||||||
|
@ -390,6 +413,79 @@ class Controller {
|
||||||
hasInvoice() {
|
hasInvoice() {
|
||||||
return this.ticket.refFk !== null;
|
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'];
|
Controller.$inject = ['$scope', '$state', '$http', 'vnApp', '$translate'];
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
New price: Nuevo precio
|
New price: Nuevo precio
|
||||||
|
Add item: Añadir artículo
|
||||||
Add turn: Añadir a turno
|
Add turn: Añadir a turno
|
||||||
Delete ticket: Borrar ticket
|
Delete ticket: Borrar ticket
|
||||||
Mark as reserved: Marcar como reservado
|
Mark as reserved: Marcar como reservado
|
||||||
|
|
|
@ -64,8 +64,10 @@ describe('Ticket', () => {
|
||||||
it('should perform a query and call windows open', () => {
|
it('should perform a query and call windows open', () => {
|
||||||
spyOn(controller.$state, 'go');
|
spyOn(controller.$state, 'go');
|
||||||
|
|
||||||
let res = {id: 1};
|
const claim = {id: 1};
|
||||||
$httpBackend.expectPOST(`/api/Claims/createFromSales`).respond(res);
|
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();
|
controller.createClaim();
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
@ -93,7 +95,7 @@ describe('Ticket', () => {
|
||||||
it('should make an array of the instances with the property checked true()', () => {
|
it('should make an array of the instances with the property checked true()', () => {
|
||||||
let sale = controller.sales[1];
|
let sale = controller.sales[1];
|
||||||
sale.checked = true;
|
sale.checked = true;
|
||||||
let expectedResult = [{id: sale.id, instance: 1}];
|
let expectedResult = [sale];
|
||||||
|
|
||||||
expect(controller.getCheckedLines()).toEqual(expectedResult);
|
expect(controller.getCheckedLines()).toEqual(expectedResult);
|
||||||
});
|
});
|
||||||
|
@ -156,12 +158,10 @@ describe('Ticket', () => {
|
||||||
|
|
||||||
describe('setReserved()', () => {
|
describe('setReserved()', () => {
|
||||||
it('should call getCheckedLines, $.index.accept and make a query ', () => {
|
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 = {
|
let expectedRequest = {
|
||||||
sales: [{
|
sales: [sale],
|
||||||
id: sales[1].id,
|
|
||||||
instance: 1
|
|
||||||
}],
|
|
||||||
ticketFk: ticket.id,
|
ticketFk: ticket.id,
|
||||||
reserved: false
|
reserved: false
|
||||||
};
|
};
|
||||||
|
@ -184,5 +184,55 @@ describe('Ticket', () => {
|
||||||
expect(controller.newSMS.message).not.toEqual('');
|
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
|
<vn-fetched-tags
|
||||||
max-length="6"
|
max-length="6"
|
||||||
item="::sale.item"
|
item="::sale.item"
|
||||||
|
name="::sale.concept"
|
||||||
sub-name="::sale.item.subName"/>
|
sub-name="::sale.item.subName"/>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td number>{{::sale.price | currency: 'EUR':2}}</vn-td>
|
<vn-td number>{{::sale.price | currency: 'EUR':2}}</vn-td>
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
<vn-fetched-tags
|
<vn-fetched-tags
|
||||||
max-length="6"
|
max-length="6"
|
||||||
item="::sale.item"
|
item="::sale.item"
|
||||||
|
name="::sale.concept"
|
||||||
sub-name="::sale.item.subName"/>
|
sub-name="::sale.item.subName"/>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td number>{{::sale.quantity}}</vn-td>
|
<vn-td number>{{::sale.quantity}}</vn-td>
|
||||||
|
|
Loading…
Reference in New Issue