Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 1773-1774-travel_summary
gitea/salix/1773-1774-travel_summary This commit looks good Details

This commit is contained in:
Bernat 2019-10-24 12:49:23 +02:00
commit afd7c5684a
137 changed files with 2603 additions and 2299 deletions

View File

@ -471,7 +471,7 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF
(11, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'NY roofs', 122, NULL, 0, 3, CURDATE()), (11, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'NY roofs', 122, NULL, 0, 3, CURDATE()),
(12, 1, 1, 1, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 1, CURDATE()), (12, 1, 1, 1, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 1, CURDATE()),
(13, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 3, CURDATE()), (13, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 3, CURDATE()),
(14, 1, 2, 1, NULL, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()), (14, 1, 2, 1, NULL, CURDATE(), CURDATE(), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()),
(15, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 105, 'Plastic Cell', 125, NULL, 0, 3, CURDATE()), (15, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 105, 'Plastic Cell', 125, NULL, 0, 3, CURDATE()),
(16, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()), (16, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
(17, 1, 7, 2, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()), (17, 1, 7, 2, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),

View File

@ -61,7 +61,7 @@ export default {
fiscalDataButton: 'vn-left-menu a[ui-sref="client.card.fiscalData"]', fiscalDataButton: 'vn-left-menu a[ui-sref="client.card.fiscalData"]',
socialNameInput: `vn-textfield input[name="socialName"]`, socialNameInput: `vn-textfield input[name="socialName"]`,
fiscalIdInput: `vn-textfield input[name="fi"]`, fiscalIdInput: `vn-textfield input[name="fi"]`,
equalizationTaxCheckbox: 'vn-check[label="Is equalizated"]', equalizationTaxCheckbox: 'vn-check[ng-model="$ctrl.client.isEqualizated"]',
acceptPropagationButton: 'vn-client-fiscal-data > vn-confirm button[response=ACCEPT]', acceptPropagationButton: 'vn-client-fiscal-data > vn-confirm button[response=ACCEPT]',
addressInput: `vn-textfield input[name="street"]`, addressInput: `vn-textfield input[name="street"]`,
postcodeInput: `vn-textfield input[name="postcode"]`, postcodeInput: `vn-textfield input[name="postcode"]`,
@ -136,7 +136,7 @@ export default {
addCreditFloatButton: `vn-float-button`, addCreditFloatButton: `vn-float-button`,
creditInput: `vn-input-number input[name="credit"]`, creditInput: `vn-input-number input[name="credit"]`,
saveButton: `button[type=submit]`, saveButton: `button[type=submit]`,
firstCreditText: 'vn-client-credit-index vn-card > div vn-table vn-tbody > vn-tr' firstCreditText: 'vn-client-credit-index vn-card vn-table vn-tbody > vn-tr'
}, },
clientGreuge: { clientGreuge: {
addGreugeFloatButton: `vn-float-button`, addGreugeFloatButton: `vn-float-button`,
@ -144,13 +144,13 @@ export default {
descriptionInput: `vn-textfield input[name="description"]`, descriptionInput: `vn-textfield input[name="description"]`,
typeAutocomplete: 'vn-autocomplete[ng-model="$ctrl.greuge.greugeTypeFk"]', typeAutocomplete: 'vn-autocomplete[ng-model="$ctrl.greuge.greugeTypeFk"]',
saveButton: `button[type=submit]`, saveButton: `button[type=submit]`,
firstGreugeText: 'vn-client-greuge-index vn-card > div vn-table vn-tbody > vn-tr' firstGreugeText: 'vn-client-greuge-index vn-card vn-table vn-tbody > vn-tr'
}, },
clientMandate: { clientMandate: {
firstMandateText: 'vn-client-mandate vn-card > div vn-table vn-tbody > vn-tr' firstMandateText: 'vn-client-mandate vn-card vn-table vn-tbody > vn-tr'
}, },
clientInvoices: { clientInvoices: {
firstInvoiceText: 'vn-client-invoice vn-card > div vn-table vn-tbody > vn-tr' firstInvoiceText: 'vn-client-invoice vn-card vn-table vn-tbody > vn-tr'
}, },
clientLog: { clientLog: {
logButton: 'vn-left-menu a[ui-sref="client.card.log"]', logButton: 'vn-left-menu a[ui-sref="client.card.log"]',
@ -307,7 +307,7 @@ export default {
fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) > vn-td > vn-one:nth-child(3) > div span:nth-child(3)', fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) > vn-td > vn-one:nth-child(3) > div span:nth-child(3)',
}, },
ticketSummary: { ticketSummary: {
header: 'vn-ticket-summary > vn-card > div > h5', header: 'vn-ticket-summary > vn-card > h5',
state: 'vn-ticket-summary vn-label-value[label="State"] > section > span', state: 'vn-ticket-summary vn-label-value[label="State"] > section > span',
route: 'vn-ticket-summary vn-label-value[label="Route"] > section > a', route: 'vn-ticket-summary vn-label-value[label="Route"] > section > a',
total: 'vn-ticket-summary vn-one.taxes > p:nth-child(3) > strong', total: 'vn-ticket-summary vn-one.taxes > p:nth-child(3) > strong',
@ -319,14 +319,14 @@ export default {
popoverDiaryButton: '.vn-popover.shown vn-item-descriptor vn-icon[icon="icon-transaction"]', popoverDiaryButton: '.vn-popover.shown vn-item-descriptor vn-icon[icon="icon-transaction"]',
firstSaleQuantity: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3)', firstSaleQuantity: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3)',
firstSaleDiscount: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6)', firstSaleDiscount: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6)',
invoiceOutRef: 'vn-ticket-summary > vn-card > div > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(6) > section > span', invoiceOutRef: 'vn-ticket-summary > vn-card > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(6) > section > span',
setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button' setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button'
}, },
ticketsIndex: { ticketsIndex: {
openAdvancedSearchButton: 'vn-ticket-index vn-searchbar .append vn-icon[icon="arrow_drop_down"]', openAdvancedSearchButton: 'vn-ticket-index vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[ng-model="filter.refFk"] input', advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[ng-model="filter.refFk"] input',
newTicketButton: 'vn-ticket-index > a', newTicketButton: 'vn-ticket-index > a',
searchResult: 'vn-ticket-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr', searchResult: 'vn-ticket-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
searchWeeklyResult: 'vn-ticket-weekly-index vn-table vn-tbody > vn-tr', searchWeeklyResult: 'vn-ticket-weekly-index vn-table vn-tbody > vn-tr',
searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)', searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)',
searchTicketInput: `vn-ticket-index vn-textfield input`, searchTicketInput: `vn-ticket-index vn-textfield input`,
@ -403,7 +403,7 @@ export default {
saleDescriptorPopoverSummaryButton: '.vn-popover.shown vn-item-descriptor a[ui-sref="item.card.summary({id: $ctrl.item.id})"]', saleDescriptorPopoverSummaryButton: '.vn-popover.shown vn-item-descriptor a[ui-sref="item.card.summary({id: $ctrl.item.id})"]',
descriptorItemDiaryButton: 'vn-item-descriptor .quicklinks.ng-scope > vn-horizontal > a > vn-icon > i', descriptorItemDiaryButton: 'vn-item-descriptor .quicklinks.ng-scope > vn-horizontal > a > vn-icon > i',
newItemFromCatalogButton: 'vn-ticket-sale vn-float-button[icon="add"]', newItemFromCatalogButton: 'vn-ticket-sale vn-float-button[icon="add"]',
newItemButton: 'vn-ticket-sale > vn-vertical > vn-card > div > vn-vertical > vn-one > vn-icon-button > button > vn-icon > i', newItemButton: 'vn-ticket-sale > vn-vertical > vn-card > vn-vertical > vn-one > vn-icon-button > button > vn-icon > i',
moreMenu: 'vn-ticket-sale vn-tool-bar > vn-button-menu[vn-id="more-button"] > div > button', moreMenu: 'vn-ticket-sale vn-tool-bar > vn-button-menu[vn-id="more-button"] > div > button',
moreMenuCreateClaim: '.vn-popover.shown .vn-drop-down li[name="Add claim"]', moreMenuCreateClaim: '.vn-popover.shown .vn-drop-down li[name="Add claim"]',
moreMenuReserve: '.vn-popover.shown .vn-drop-down li[name="Mark as reserved"]', moreMenuReserve: '.vn-popover.shown .vn-drop-down li[name="Mark as reserved"]',
@ -443,7 +443,7 @@ export default {
secondSaleQuantity: 'vn-ticket-sale vn-table vn-tr:nth-child(2) vn-input-number input', secondSaleQuantity: 'vn-ticket-sale vn-table vn-tr:nth-child(2) vn-input-number input',
secondSaleConceptCell: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(2) > vn-td-editable:nth-child(6)', secondSaleConceptCell: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(2) > vn-td-editable:nth-child(6)',
secondSaleConceptInput: 'vn-ticket-sale vn-table vn-tr:nth-child(2) > vn-td-editable.ng-isolate-scope.selected vn-textfield input', secondSaleConceptInput: 'vn-ticket-sale vn-table vn-tr:nth-child(2) > vn-td-editable.ng-isolate-scope.selected vn-textfield input',
totalImport: 'vn-ticket-sale > vn-vertical > vn-card > div > vn-vertical > vn-horizontal > vn-one > p:nth-child(3) > strong', totalImport: 'vn-ticket-sale > vn-vertical > vn-card > vn-vertical > vn-horizontal > vn-one > p:nth-child(3) > strong',
selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check', selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check',
secondSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(2) vn-check[ng-model="sale.checked"]', secondSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(2) vn-check[ng-model="sale.checked"]',
thirdSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(3) vn-check[ng-model="sale.checked"]', thirdSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(3) vn-check[ng-model="sale.checked"]',
@ -472,7 +472,7 @@ export default {
zoneAutocomplete: 'vn-autocomplete[ng-model="$ctrl.zoneId"]', zoneAutocomplete: 'vn-autocomplete[ng-model="$ctrl.zoneId"]',
nextStepButton: 'vn-step-control .buttons > section:last-child vn-button', nextStepButton: 'vn-step-control .buttons > section:last-child vn-button',
finalizeButton: 'vn-step-control .buttons > section:last-child button[type=submit]', finalizeButton: 'vn-step-control .buttons > section:last-child button[type=submit]',
stepTwoTotalPriceDif: 'vn-ticket-basic-data-step-two > form > vn-card > div > vn-horizontal > table > tfoot > tr > td:nth-child(4)', stepTwoTotalPriceDif: 'vn-ticket-basic-data-step-two > form > vn-card > vn-horizontal > table > tfoot > tr > td:nth-child(4)',
chargesReasonAutocomplete: 'vn-autocomplete[ng-model="$ctrl.ticket.option"]', chargesReasonAutocomplete: 'vn-autocomplete[ng-model="$ctrl.ticket.option"]',
}, },
ticketComponents: { ticketComponents: {
@ -481,8 +481,8 @@ export default {
ticketRequests: { ticketRequests: {
addRequestButton: 'vn-ticket-request-index > a > vn-float-button > button', addRequestButton: 'vn-ticket-request-index > a > vn-float-button > button',
request: 'vn-ticket-request-index vn-table vn-tr', request: 'vn-ticket-request-index vn-table vn-tr',
descriptionInput: 'vn-ticket-request-create > form > div > vn-card > div > vn-horizontal:nth-child(1) > vn-textfield input', descriptionInput: 'vn-ticket-request-create > form > div > vn-card > vn-horizontal:nth-child(1) > vn-textfield input',
atenderAutocomplete: 'vn-ticket-request-create vn-autocomplete[ng-model="$ctrl.ticketRequest.atenderFk"]', atenderAutocomplete: 'vn-ticket-request-create vn-autocomplete[ng-model="$ctrl.ticketRequest.attenderFk"]',
quantityInput: 'vn-ticket-request-create vn-input-number input[name=quantity]', quantityInput: 'vn-ticket-request-create vn-input-number input[name=quantity]',
priceInput: 'vn-ticket-request-create vn-input-number input[name=price]', priceInput: 'vn-ticket-request-create vn-input-number input[name=price]',
firstRemoveRequestButton: 'vn-ticket-request-index vn-icon[icon="delete"]:nth-child(1)', firstRemoveRequestButton: 'vn-ticket-request-index vn-icon[icon="delete"]:nth-child(1)',
@ -505,7 +505,7 @@ export default {
firstVatTypeAutocomplete: 'vn-ticket-service vn-autocomplete[label="Tax class"]', firstVatTypeAutocomplete: 'vn-ticket-service vn-autocomplete[label="Tax class"]',
fistDeleteServiceButton: 'vn-ticket-service form vn-horizontal:nth-child(1) vn-icon-button[icon="delete"]', fistDeleteServiceButton: 'vn-ticket-service form vn-horizontal:nth-child(1) vn-icon-button[icon="delete"]',
newDescriptionInput: 'vn-ticket-service > vn-dialog vn-textfield[ng-model="$ctrl.newServiceType.name"] input', newDescriptionInput: 'vn-ticket-service > vn-dialog vn-textfield[ng-model="$ctrl.newServiceType.name"] input',
serviceLine: 'vn-ticket-service > form > vn-card > div > vn-one:nth-child(2) > vn-horizontal', serviceLine: 'vn-ticket-service > form > vn-card > vn-one:nth-child(2) > vn-horizontal',
saveServiceButton: `button[type=submit]`, saveServiceButton: `button[type=submit]`,
saveDescriptionButton: 'vn-ticket-service > vn-dialog[vn-id="createServiceTypeDialog"] > div > form > div.buttons > tpl-buttons > button' saveDescriptionButton: 'vn-ticket-service > vn-dialog[vn-id="createServiceTypeDialog"] > div > form > div.buttons > tpl-buttons > button'
}, },
@ -517,7 +517,7 @@ export default {
}, },
claimsIndex: { claimsIndex: {
searchClaimInput: `vn-claim-index vn-textfield input`, searchClaimInput: `vn-claim-index vn-textfield input`,
searchResult: 'vn-claim-index vn-card > div > vn-table > div > vn-tbody > a', searchResult: 'vn-claim-index vn-card > vn-table > div > vn-tbody > a',
searchButton: 'vn-claim-index vn-searchbar vn-icon[icon="search"]' searchButton: 'vn-claim-index vn-searchbar vn-icon[icon="search"]'
}, },
claimDescriptor: { claimDescriptor: {
@ -526,7 +526,7 @@ export default {
acceptDeleteClaim: 'vn-claim-descriptor > vn-confirm[vn-id="confirm-delete-claim"] button[response="ACCEPT"]' acceptDeleteClaim: 'vn-claim-descriptor > vn-confirm[vn-id="confirm-delete-claim"] button[response="ACCEPT"]'
}, },
claimSummary: { claimSummary: {
header: 'vn-claim-summary > vn-card > div > h5', header: 'vn-claim-summary > vn-card > h5',
state: 'vn-claim-summary vn-label-value[label="State"] > section > span', state: 'vn-claim-summary vn-label-value[label="State"] > section > span',
observation: 'vn-claim-summary vn-textarea[ng-model="$ctrl.summary.claim.observation"] textarea', observation: 'vn-claim-summary vn-textarea[ng-model="$ctrl.summary.claim.observation"] textarea',
firstSaleItemId: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(4) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(1) > span', firstSaleItemId: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(4) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(1) > span',
@ -534,8 +534,8 @@ export default {
itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor', itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
itemDescriptorPopoverItemDiaryButton: '.vn-popover.shown vn-item-descriptor a[href="#!/item/2/diary"]', itemDescriptorPopoverItemDiaryButton: '.vn-popover.shown vn-item-descriptor a[href="#!/item/2/diary"]',
firstDevelopmentWorker: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > span', firstDevelopmentWorker: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > span',
firstDevelopmentWorkerGoToClientButton: '.vn-popover.shown vn-worker-descriptor div.quicklinks > a[href="#!/client/21/summary"]', firstDevelopmentWorkerGoToClientButton: '.vn-popover.shown vn-worker-descriptor vn-quick-links > a[href="#!/client/21/summary"]',
firstActionTicketId: 'vn-claim-summary > vn-card > div > vn-horizontal > vn-auto:nth-child(6) vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > span', firstActionTicketId: 'vn-claim-summary > vn-card > vn-horizontal > vn-auto:nth-child(6) vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > span',
firstActionTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor' firstActionTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor'
}, },
claimBasicData: { claimBasicData: {
@ -545,19 +545,19 @@ export default {
saveButton: `button[type=submit]` saveButton: `button[type=submit]`
}, },
claimDetail: { claimDetail: {
secondItemDiscount: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(6) > span', secondItemDiscount: 'vn-claim-detail > vn-vertical > vn-card > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(6) > span',
discountInput: '.vn-popover.shown vn-input-number[ng-model="$ctrl.newDiscount"] input', discountInput: '.vn-popover.shown vn-input-number[ng-model="$ctrl.newDiscount"] input',
discoutPopoverMana: '.vn-popover.shown .content > div > vn-horizontal > h5', discoutPopoverMana: '.vn-popover.shown .content > div > vn-horizontal > h5',
addItemButton: 'vn-claim-detail a vn-float-button', addItemButton: 'vn-claim-detail a vn-float-button',
firstClaimableSaleFromTicket: 'vn-claim-detail > vn-dialog vn-tbody > vn-tr', firstClaimableSaleFromTicket: 'vn-claim-detail > vn-dialog vn-tbody > vn-tr',
claimDetailLine: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr', claimDetailLine: 'vn-claim-detail > vn-vertical > vn-card > vn-vertical > vn-table > div > vn-tbody > vn-tr',
firstItemQuantityInput: 'vn-claim-detail vn-tr:nth-child(1) vn-input-number[ng-model="saleClaimed.quantity"] input', firstItemQuantityInput: 'vn-claim-detail vn-tr:nth-child(1) vn-input-number[ng-model="saleClaimed.quantity"] input',
totalClaimed: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-horizontal > div > vn-label-value:nth-child(2) > section > span', totalClaimed: 'vn-claim-detail > vn-vertical > vn-card > vn-vertical > vn-horizontal > div > vn-label-value:nth-child(2) > section > span',
secondItemDeleteButton: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(8) > vn-icon-button > button > vn-icon > i' secondItemDeleteButton: 'vn-claim-detail > vn-vertical > vn-card > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(8) > vn-icon-button > button > vn-icon > i'
}, },
claimDevelopment: { claimDevelopment: {
addDevelopmentButton: 'vn-claim-development > vn-vertical > vn-card > div > vn-vertical > vn-one > vn-icon-button > button > vn-icon', addDevelopmentButton: 'vn-claim-development > vn-vertical > vn-card > vn-vertical > vn-one > vn-icon-button > button > vn-icon',
firstDeleteDevelopmentButton: 'vn-claim-development > vn-vertical > vn-card > div > vn-vertical > form > vn-horizontal:nth-child(2) > vn-icon-button > button > vn-icon', firstDeleteDevelopmentButton: 'vn-claim-development > vn-vertical > vn-card > vn-vertical > form > vn-horizontal:nth-child(2) > vn-icon-button > button > vn-icon',
firstClaimReasonAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimReasonFk"]', firstClaimReasonAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimReasonFk"]',
firstClaimResultAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResultFk"]', firstClaimResultAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResultFk"]',
firstClaimResponsibleAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResponsibleFk"]', firstClaimResponsibleAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResponsibleFk"]',
@ -580,7 +580,7 @@ export default {
isPaidWithManaCheckbox: 'vn-check[ng-model="$ctrl.claim.isChargedToMana"]' isPaidWithManaCheckbox: 'vn-check[ng-model="$ctrl.claim.isChargedToMana"]'
}, },
ordersIndex: { ordersIndex: {
searchResult: 'vn-order-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr', searchResult: 'vn-order-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
searchResultDate: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(4)', searchResultDate: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(4)',
searchResultAddress: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(6)', searchResultAddress: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(6)',
searchOrderInput: `vn-order-index vn-textfield input`, searchOrderInput: `vn-order-index vn-textfield input`,
@ -641,7 +641,7 @@ export default {
volume: 'vn-route-descriptor vn-label-value[label="Volume"] > section > span' volume: 'vn-route-descriptor vn-label-value[label="Volume"] > section > span'
}, },
routeSummary: { routeSummary: {
routeId: 'vn-route-summary > vn-card > div > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(1) > section > span' routeId: 'vn-route-summary > vn-card > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(1) > section > span'
}, },
routeBasicData: { routeBasicData: {
workerAutoComplete: 'vn-route-basic-data vn-autocomplete[ng-model="$ctrl.route.workerFk"]', workerAutoComplete: 'vn-route-basic-data vn-autocomplete[ng-model="$ctrl.route.workerFk"]',
@ -671,57 +671,58 @@ export default {
}, },
workerTimeControl: { workerTimeControl: {
timeDialogInput: '.vn-dialog.shown [ng-model="$ctrl.newTime"]', timeDialogInput: '.vn-dialog.shown [ng-model="$ctrl.newTime"]',
mondayAddTimeButton: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(1) > vn-icon-button > button > vn-icon', mondayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(1) > vn-icon-button',
tuesdayAddTimeButton: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(2) > vn-icon-button > button > vn-icon', tuesdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(2) > vn-icon-button',
wednesdayAddTimeButton: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(3) > vn-icon-button > button > vn-icon', wednesdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(3) > vn-icon-button',
thursdayAddTimeButton: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(4) > vn-icon-button > button > vn-icon', thursdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(4) > vn-icon-button',
fridayAddTimeButton: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(5) > vn-icon-button > button > vn-icon', fridayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(5) > vn-icon-button',
saturdayAddTimeButton: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(6) > vn-icon-button > button > vn-icon', saturdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(6) > vn-icon-button',
sundayAddTimeButton: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(7) > vn-icon-button > button > vn-icon', sundayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(7) > vn-icon-button',
confirmButton: 'vn-worker-time-control > vn-dialog > div > form > div.buttons > tpl-buttons > button', confirmButton: 'vn-worker-time-control > vn-dialog > div > form > div.buttons > tpl-buttons > button',
firstEntryOfMonday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(1) > span', firstEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(1) > span',
firstEntryOfTuesday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(1) > span', firstEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(1) > span',
firstEntryOfWednesday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(1) > span', firstEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(1) > span',
firstEntryOfThursday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(1) > span', firstEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(1) > span',
firstEntryOfFriday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(1) > span', firstEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(1) > span',
firstEntryOfSaturday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(1) > span', firstEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(1) > span',
firstEntryOfSunday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(1) > span', firstEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(1) > span',
secondEntryOfMonday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(2) > span', secondEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(2) > span',
secondEntryOfTuesday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(2) > span', secondEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(2) > span',
secondEntryOfWednesday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(2) > span', secondEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(2) > span',
secondEntryOfThursday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(2) > span', secondEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(2) > span',
secondEntryOfFriday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(2) > span', secondEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(2) > span',
secondEntryOfSaturday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(2) > span', secondEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(2) > span',
secondEntryOfSunday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(2) > span', secondEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(2) > span',
thirdEntryOfMonday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > span', thirdEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > span',
thirdEntryOfTuesday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(3) > span', thirdEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(3) > span',
thirdEntryOfWednesday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(3) > span', thirdEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(3) > span',
thirdEntryOfThursday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(3) > span', thirdEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(3) > span',
thirdEntryOfFriday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(3) > span', thirdEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(3) > span',
thirdEntryOfSaturday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(3) > span', thirdEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(3) > span',
thirdEntryOfSunday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(3) > span', thirdEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(3) > span',
fourthEntryOfMonday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(4) > span', fourthEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(4) > span',
fourthEntryOfTuesday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(4) > span', fourthEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(4) > span',
fourthEntryOfWednesday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(4) > span', fourthEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(4) > span',
fourthEntryOfThursday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(4) > span', fourthEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(4) > span',
fourthEntryOfFriday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(4) > span', fourthEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(4) > span',
fourthEntryOfSaturday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(4) > span', fourthEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(4) > span',
fourthEntryOfSunday: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(4) > span', fourthEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(4) > span',
mondayWorkedHours: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(1)', mondayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(1)',
tuesdayWorkedHours: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(2)', tuesdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(2)',
wednesdayWorkedHours: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(3)', wednesdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(3)',
thursdayWorkedHours: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(4)', thursdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(4)',
fridayWorkedHours: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(5)', fridayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(5)',
saturdayWorkedHours: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(6)', saturdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(6)',
sundayWorkedHours: 'vn-worker-time-control > div > vn-card > div > vn-horizontal > vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(7)', sundayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(7)',
weekWorkedHours: 'vn-worker-time-control > div > vn-side-menu > div > vn-vertical > vn-vertical > vn-label-value > section > span', weekWorkedHours: 'vn-worker-time-control vn-side-menu vn-label-value > section > span',
nextMonthButton: 'vn-worker-time-control > div > vn-side-menu > div > vn-calendar > div > vn-horizontal > vn-auto:nth-child(3) > vn-icon', nextMonthButton: 'vn-worker-time-control vn-side-menu vn-calendar vn-button[icon=keyboard_arrow_right]',
secondWeekDay: 'vn-worker-time-control vn-side-menu vn-calendar .day:nth-child(8) > .day-number',
navigateBackToIndex: 'vn-worker-descriptor vn-icon[icon="chevron_left"]' navigateBackToIndex: 'vn-worker-descriptor vn-icon[icon="chevron_left"]'
}, },
invoiceOutIndex: { invoiceOutIndex: {
searchInvoiceOutInput: `vn-invoice-out-index vn-textfield input`, searchInvoiceOutInput: `vn-invoice-out-index vn-textfield input`,
searchButton: 'vn-invoice-out-index vn-searchbar vn-icon[icon="search"]', searchButton: 'vn-invoice-out-index vn-searchbar vn-icon[icon="search"]',
searchResult: 'vn-invoice-out-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr', searchResult: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
}, },
invoiceOutDescriptor: { invoiceOutDescriptor: {
moreMenu: 'vn-invoice-out-descriptor vn-icon-menu[icon=more_vert]', moreMenu: 'vn-invoice-out-descriptor vn-icon-menu[icon=more_vert]',
@ -732,6 +733,6 @@ export default {
acceptBookingButton: 'vn-invoice-out-descriptor > vn-confirm[vn-id="bookConfirmation"] button[response="ACCEPT"]' acceptBookingButton: 'vn-invoice-out-descriptor > vn-confirm[vn-id="bookConfirmation"] button[response="ACCEPT"]'
}, },
invoiceOutSummary: { invoiceOutSummary: {
bookedLabel: 'vn-invoice-out-summary > vn-card > div > vn-horizontal > vn-one > vn-label-value:nth-child(4) > section > span' bookedLabel: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-one > vn-label-value:nth-child(4) > section > span'
} }
}; };

View File

@ -286,11 +286,11 @@ describe('Worker time control path', () => {
}); });
}); });
describe('as salesBoss', () => { describe('as hr', () => {
describe('on Saturday', () => { describe('on Saturday', () => {
beforeAll(() => { beforeAll(() => {
nightmare nightmare
.loginAndModule('salesBoss', 'worker') .loginAndModule('hr', 'worker')
.accessToSearchResult('HankPym') .accessToSearchResult('HankPym')
.accessToSection('worker.card.timeControl'); .accessToSection('worker.card.timeControl');
}); });
@ -358,10 +358,11 @@ describe('Worker time control path', () => {
it(`should check Hank Pym doesn't have hours set on the next months first week`, async() => { it(`should check Hank Pym doesn't have hours set on the next months first week`, async() => {
const wholeWeekHours = await nightmare const wholeWeekHours = await nightmare
.waitToClick(selectors.workerTimeControl.nextMonthButton) .waitToClick(selectors.workerTimeControl.nextMonthButton)
.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '00:00 Hours') .waitToClick(selectors.workerTimeControl.secondWeekDay)
.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '00:00 h.')
.waitToGetProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText'); .waitToGetProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText');
expect(wholeWeekHours).toEqual('00:00 Hours'); expect(wholeWeekHours).toEqual('00:00 h.');
}); });
it(`should check he didn't scan in this week yet`, async() => { it(`should check he didn't scan in this week yet`, async() => {
@ -371,7 +372,7 @@ describe('Worker time control path', () => {
.accessToSection('worker.card.timeControl') .accessToSection('worker.card.timeControl')
.waitToGetProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText'); .waitToGetProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText');
expect(wholeWeekHours).toEqual('00:00 Hours'); expect(wholeWeekHours).toEqual('00:00 h.');
}); });
}); });
}); });
@ -386,10 +387,10 @@ describe('Worker time control path', () => {
it('should Hank Pym check his hours are alright', async() => { it('should Hank Pym check his hours are alright', async() => {
const wholeWeekHours = await nightmare const wholeWeekHours = await nightmare
.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '56:00 Hours') .waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '56:00 h.')
.waitToGetProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText'); .waitToGetProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText');
expect(wholeWeekHours).toEqual('56:00 Hours'); expect(wholeWeekHours).toEqual('56:00 h.');
}); });
}); });
}); });

View File

@ -17,7 +17,7 @@ describe('Ticket Summary path', () => {
it(`should display details from the ticket and it's client on the top of the header`, async() => { it(`should display details from the ticket and it's client on the top of the header`, async() => {
let result = await nightmare let result = await nightmare
.waitForSpinnerLoad() .waitForTextInElement(selectors.ticketSummary.header, 'Bruce Banner')
.waitToGetProperty(selectors.ticketSummary.header, 'innerText'); .waitToGetProperty(selectors.ticketSummary.header, 'innerText');
expect(result).toContain(`Ticket #${ticketId}`); expect(result).toContain(`Ticket #${ticketId}`);

View File

@ -48,5 +48,5 @@
vn-id="drop-down" vn-id="drop-down"
on-select="$ctrl.onDropDownSelect(item)" on-select="$ctrl.onDropDownSelect(item)"
on-data-ready="$ctrl.onDataReady()" on-data-ready="$ctrl.onDataReady()"
on-close-start="$ctrl.focus()"> on-close-start="$ctrl.onDropDownClose()">
</vn-drop-down> </vn-drop-down>

View File

@ -209,6 +209,10 @@ export default class Autocomplete extends Field {
this.field = value; this.field = value;
} }
onDropDownClose() {
setTimeout(() => this.focus());
}
onContainerKeyDown(event) { onContainerKeyDown(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
@ -224,13 +228,10 @@ export default class Autocomplete extends Field {
else else
return; return;
} }
event.preventDefault();
} }
onContainerClick(event) { onContainerClick(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault();
this.showDropDown(); this.showDropDown();
} }

View File

@ -15,6 +15,5 @@
</button> </button>
<vn-drop-down <vn-drop-down
vn-id="drop-down" vn-id="drop-down"
on-select="$ctrl.onDropDownSelect(item)" on-select="$ctrl.onDropDownSelect(item)">
ng-click="$ctrl.onDropDownClick($event)">
</vn-drop-down> </vn-drop-down>

View File

@ -44,15 +44,10 @@ export default class ButtonMenu extends Button {
onClick(event) { onClick(event) {
if (this.disabled) return; if (this.disabled) return;
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault();
this.emit('open'); this.emit('open');
this.showDropDown(); this.showDropDown();
} }
onDropDownClick(event) {
event.preventDefault();
}
onDropDownSelect(item) { onDropDownSelect(item) {
const value = item[this.valueField]; const value = item[this.valueField];
this.field = value; this.field = value;

View File

@ -6,11 +6,8 @@ export default class Button extends FormInput {
constructor($element, $scope) { constructor($element, $scope) {
super($element, $scope); super($element, $scope);
this.design = 'colored'; this.design = 'colored';
this.input = this.element.querySelector('button'); this.initTabIndex();
this.classList.add('vn-button');
let element = this.element;
element.tabIndex = 0;
element.classList.add('vn-button');
this.element.addEventListener('keyup', e => this.onKeyup(e)); this.element.addEventListener('keyup', e => this.onKeyup(e));
this.element.addEventListener('click', e => this.onClick(e)); this.element.addEventListener('click', e => this.onClick(e));
} }
@ -21,16 +18,17 @@ export default class Button extends FormInput {
} }
onKeyup(event) { onKeyup(event) {
if (event.code == 'Space') if (event.defaultPrevented) return;
this.onClick(event); switch (event.code) {
case 'Space':
case 'Enter':
return this.element.click();
}
} }
onClick(event) { onClick(event) {
if (event.defaultPrevented) return; if (this.disabled)
// event.preventDefault(); event.stopImmediatePropagation();
// FIXME: Don't stop event propagation
if (this.disabled) event.stopImmediatePropagation();
} }
} }
Button.$inject = ['$element', '$scope']; Button.$inject = ['$element', '$scope'];

View File

@ -1,82 +1,46 @@
<div> <div>
<vn-horizontal class="header"> <div class="header">
<vn-auto> <vn-button
<vn-icon icon="keyboard_arrow_left"
icon="keyboard_arrow_left" class="flat"
class="pointer" ng-click="$ctrl.movePrevious()"
ng-click="$ctrl.movePrevious($ctrl.skip)" translate-attr="::{title: 'Previous'}"
ng-show="$ctrl.displayControls"/> ng-if="$ctrl.displayControls"/>
</vn-icon> </vn-button>
</vn-auto> <div class="title">
<vn-one> <span translate>{{$ctrl.defaultDate | date: 'MMMM'}}</span>
<div> <span ng-hide="::$ctrl.hideYear">{{$ctrl.defaultDate | date: 'yyyy'}}</span>
<span translate>{{$ctrl.defaultDate | date: 'MMMM'}}</span> </div>
<span>{{$ctrl.defaultDate | date: 'yyyy'}}</span> <vn-button
icon="keyboard_arrow_right"
class="flat"
ng-click="$ctrl.moveNext()"
translate-attr="::{title: 'Next'}"
ng-if="$ctrl.displayControls">
</vn-button>
</div>
<div class="weekdays">
<section
ng-repeat="day in ::$ctrl.weekDays"
translate-attr="::{title: day.name}"
ng-click="$ctrl.selectWeekDay(day.index)">
<span>{{::day.localeChar}}</span>
</section>
</div>
<div
class="days"
ng-class="{'hide-contiguous': $ctrl.hideContiguous}">
<section
ng-repeat="day in $ctrl.days"
class="day"
ng-class="::$ctrl.getDayClasses(day)"
vn-repeat-last
on-last="$ctrl.repeatLast()">
<div
class="day-number"
ng-click="$ctrl.select(day)">
{{::day | date: 'd'}}
</div> </div>
</vn-one> </section>
<vn-auto> </div>
<vn-icon
icon="keyboard_arrow_right"
class="pointer"
ng-click="$ctrl.moveNext($ctrl.skip)"
ng-show="$ctrl.displayControls">
</vn-icon>
</vn-auto>
</vn-horizontal>
<vn-vertical class="body">
<vn-horizontal class="weekdays">
<section title="{{'Monday' | translate}}"
ng-click="$ctrl.selectAll(1)">
<span>L</span>
</section>
<section title="{{'Tuesday' | translate}}"
ng-click="$ctrl.selectAll(2)">
<span>M</span>
</section>
<section title="{{'Wednesday' | translate}}"
ng-click="$ctrl.selectAll(3)">
<span>X</span>
</section>
<section title="{{'Thursday' | translate}}"
ng-click="$ctrl.selectAll(4)">
<span>J</span>
</section>
<section title="{{'Friday' | translate}}"
ng-click="$ctrl.selectAll(5)">
<span>V</span>
</section>
<section title="{{'Saturday' | translate}}"
ng-click="$ctrl.selectAll(6)">
<span>S</span>
</section>
<section title="{{'Sunday' | translate}}"
ng-click="$ctrl.selectAll(0)">
<span>D</span>
</section>
</vn-horizontal>
<vn-horizontal class="days">
<section
ng-repeat="day in $ctrl.days"
class="day {{::$ctrl.getClass({$day: day.dated})}}"
ng-class="::{primary: day.events.length > 0 || $ctrl.hasEvents({$day: day.dated})}">
<div class="content">
<div class="day-number"
title="{{(day.eventName) | translate}}"
ng-style="$ctrl.renderStyle(day.style)"
ng-click="$ctrl.select($index)">
{{::day.dated | date: 'd'}}
</div>
<div ng-if="day.events" class="events">
<div ng-repeat="event in day.events" class="event"
title="{{(event.description || event.name) | translate}}">
<span class="chip ellipsize"
ng-style="::$ctrl.renderStyle(event.style)">
{{::event.name}}
</span>
</div>
</div>
</div>
</section>
</vn-horizontal>
</vn-vertical>
</div> </div>

View File

@ -1,43 +1,26 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Component from '../../lib/component'; import FormInput from '../form-input';
import './style.scss'; import './style.scss';
/** /**
* Flat calendar. * Flat calendar.
* *
* @property {Array} data Array of events * @property {Array} defaultDate Array of events
* @property {Function} hasEvents Determines if an events exists for a day * @property {Function} hasEvents Determines if an events exists for a day
* @property {Function} getClass Class to apply to specific day * @property {Function} getClass Class to apply to specific day
* @event selection Emitted when day or weekday is selected
* @event move Emitted when month changes
*/ */
export default class Calendar extends Component { export default class Calendar extends FormInput {
constructor($element, $scope) { constructor($element, $scope, vnWeekDays) {
super($element, $scope); super($element, $scope);
this.events = []; this.weekDays = vnWeekDays.locales;
this.defaultDate = new Date(); this.defaultDate = new Date();
this.displayControls = true; this.displayControls = true;
this.disabled = false;
this.skip = 1;
this.window.addEventListener('resize', () => {
this.checkSize();
});
} }
/** /**
* Resizes the calendar * The initial date
* based on component height
*/
checkSize() {
const height = this.$element[0].clientHeight;
if (height < 530)
this.$element.addClass('small');
else
this.$element.removeClass('small');
}
/**
* Returns the initial date
* *
* @return {Date} - Default date * @return {Date} - Default date
*/ */
@ -45,11 +28,6 @@ export default class Calendar extends Component {
return this._defaultDate; return this._defaultDate;
} }
/**
* Sets a new initial date
*
* @param {Date} value - New default date
*/
set defaultDate(value) { set defaultDate(value) {
if (value) { if (value) {
value = new Date(value); value = new Date(value);
@ -58,67 +36,10 @@ export default class Calendar extends Component {
} }
this._defaultDate = value; this._defaultDate = value;
this.month = value.getMonth();
this.repaint(); this.repaint();
} }
/**
* Sets events
*
* @param {Array} value - Array of events
* @param {Date} event.dated - Day to add event
* @param {String} event.name - Tooltip description
* @param {String} event.className - ClassName style
* @param {Object} event.style - Style properties
*/
set data(value) {
if (!value) return;
this.events = [];
value.forEach(event => {
event.dated = new Date(event.dated);
event.dated.setHours(0, 0, 0, 0);
this.events.push(event);
});
if (this.defaultDate) {
this.repaint();
this.checkSize();
}
}
/**
* Gets current month date
*/
get currentMonth() {
return this.defaultDate;
}
/**
* Gets next month date
*
* @return {Date}
*/
get nextMonth() {
const newDate = new Date(this.currentMonth);
newDate.setMonth(this.currentMonth.getMonth() + 1);
return newDate;
}
/**
* Gets previous month date
*
* @return {Date}
*/
get previousMonth() {
const newDate = new Date(this.currentMonth);
newDate.setMonth(this.currentMonth.getMonth() - 1);
return newDate;
}
/** /**
* Returns first day of month from a given date * Returns first day of month from a given date
* *
@ -126,185 +47,113 @@ export default class Calendar extends Component {
* @return {Integer} * @return {Integer}
*/ */
firstDay(date) { firstDay(date) {
const newDate = new Date( return new Date(
date.getFullYear(), date.getFullYear(),
date.getMonth(), 1); date.getMonth(),
1
return newDate; );
} }
/** /**
* Returns last day of month from a given date * Repaints the calendar.
*
* @param {Date} date - Origin date
* @return {Integer}
*/ */
lastDay(date) {
const newDate = new Date(
date.getFullYear(),
date.getMonth() + 1, 0);
return newDate;
}
repaint() { repaint() {
const firstWeekday = this.firstDay(this.currentMonth).getDay(); const firstWeekday = this.firstDay(this.defaultDate).getDay() - 1;
const previousLastDay = this.lastDay(this.previousMonth).getDate(); let weekdayOffset = firstWeekday >= 0 ? firstWeekday : 6;
const currentLastDay = this.lastDay(this.currentMonth).getDate();
const maxFields = 42; // Max field limit
let weekdayOffset = firstWeekday > 0 ? firstWeekday : 7; let dayIndex = new Date(this.defaultDate.getTime());
let dayPrevious = previousLastDay - (weekdayOffset - 2); dayIndex.setDate(1 - weekdayOffset);
let dayCurrent = 1;
let dayNext = 1;
this.days = []; this.days = [];
for (let fieldIndex = 1; fieldIndex < maxFields; fieldIndex++) { for (let i = 1; i <= 42; i++) {
// Insert previous month days this.days.push(new Date(dayIndex.getTime()));
if (fieldIndex < weekdayOffset) { dayIndex.setDate(dayIndex.getDate() + 1);
const dated = new Date(
this.previousMonth.getFullYear(),
this.previousMonth.getMonth(), dayPrevious);
this.insertDay(dated, 'gray');
dayPrevious++;
}
// Insert current month days
if (fieldIndex >= weekdayOffset && dayCurrent <= currentLastDay) {
const dated = new Date(
this.currentMonth.getFullYear(),
this.currentMonth.getMonth(), dayCurrent);
this.insertDay(dated);
dayCurrent++;
}
// Insert next month days
if (fieldIndex >= weekdayOffset && dayCurrent > currentLastDay) {
const dated = new Date(
this.nextMonth.getFullYear(),
this.nextMonth.getMonth(), dayNext);
this.insertDay(dated, 'gray');
dayNext++;
}
} }
} }
/** /**
* Inserts a date on an array of month days * Gets CSS classes to apply to the specified day.
* *
* @param {Date} dated - Date of month * @param {Date} day The day
* @param {String} className - Default class style * @return {Object} The CSS classes to apply
*/ */
insertDay(dated) { getDayClasses(day) {
let events = this.events.filter(event => { let wday = day.getDay();
return event.dated >= dated && event.dated <= dated; let month = day.getMonth();
});
const params = {dated: dated, events: events /**/, style: {}}; let classes = {
const isSaturday = dated.getDay() === 6; weekend: wday === 6 || wday === 0,
const isSunday = dated.getDay() === 0; previous: month < this.month,
const isCurrentMonth = dated.getMonth() === this.currentMonth.getMonth(); current: month == this.month,
const hasEvents = events.length > 0; next: month > this.month,
event: this.hasEvents({$day: day})
};
if (isCurrentMonth && isSunday && !hasEvents) let userClass = this.getClass({$day: day});
params.style = {color: '#999'}; if (userClass) classes[userClass] = true;
if (isCurrentMonth && isSaturday && !hasEvents) return classes;
params.style = {color: '#999'};
if (!isCurrentMonth)
params.style = {opacity: '0.5'};
if (events.length > 0) {
const eventStyle = events[0].style;
const eventName = events[0].description || events[0].name;
if (eventStyle)
Object.assign(params.style, eventStyle);
if (eventName)
params.eventName = eventName;
}
this.days.push(params);
} }
/** /**
* Moves to next month(s) * Moves to next month(s)
*
* @param {Integer} skip - Months to skip at once
*/ */
moveNext(skip = 1) { moveNext() {
let next = this.defaultDate.getMonth() + skip; this.move(1);
this.defaultDate.setMonth(next);
this.repaint();
this.emit('moveNext');
} }
/** /**
* Moves to previous month(s) * Moves to previous month(s)
*
* @param {Integer} skip - Months to skip at once
*/ */
movePrevious(skip = 1) { movePrevious() {
let previous = this.defaultDate.getMonth() - skip; this.move(-1);
this.defaultDate.setMonth(previous);
this.repaint();
this.emit('movePrevious');
} }
/** /**
* Day selection event * Moves @direction months backwards/forwards.
* *
* @param {Integer} index - Index from days array * @param {Number} direction Negative to move backwards, positive forwards
*/ */
select(index) { move(direction) {
if (this.disabled) return; let date = new Date(this.defaultDate.getTime());
let day = this.days[index].dated; date.setMonth(date.getMonth() + direction);
this.defaultDate = date;
this.repaint();
this.emit('move', {$date: date});
}
/*
* Day selection event
*/
select(day) {
if (!this.editable) return;
this.field = day;
this.emit('selection', { this.emit('selection', {
$days: [day], $days: [day],
$type: 'day' $type: 'day'
}); });
this.repaint();
} }
/** /*
* WeekDay selection event * WeekDay selection event
*
* @param {Integer} weekday - weekday index
*/ */
selectAll(weekday) { selectWeekDay(weekday) {
if (this.disabled) return; if (!this.editable) return;
let days = []; let days = [];
for (let i in this.days) { for (let day of this.days) {
const day = this.days[i].dated; if (day.getDay() === weekday && day.getMonth() == this.month)
if (day.getDay() === weekday && day.getMonth() == this.defaultDate.getMonth())
days.push(day); days.push(day);
} }
this.field = days[0];
this.emit('selection', { this.emit('selection', {
$days: days, $days: days,
$type: 'weekday', $type: 'weekday',
$weekday: weekday $weekday: weekday
}); });
} this.repaint();
renderStyle(style) {
const normalizedStyle = {};
if (style) {
const properties = Object.keys(style);
properties.forEach(attribute => {
const attrName = attribute.split(/(?=[A-Z])/).
join('-').toLowerCase();
normalizedStyle[attrName] = style[attribute];
});
}
return normalizedStyle;
} }
hasEvents() { hasEvents() {
@ -314,24 +163,31 @@ export default class Calendar extends Component {
getClass() { getClass() {
return ''; return '';
} }
repeatLast() {
if (!this.formatDay) return;
let days = this.element.querySelectorAll('.days > .day');
for (let i = 0; i < days.length; i++) {
this.formatDay({
$day: this.days[i],
$element: days[i]
});
}
}
} }
Calendar.$inject = ['$element', '$scope', 'vnWeekDays'];
Calendar.$inject = ['$element', '$scope']; ngModule.vnComponent('vnCalendar', {
ngModule.component('vnCalendar', {
template: require('./index.html'), template: require('./index.html'),
controller: Calendar, controller: Calendar,
bindings: { bindings: {
model: '<',
data: '<?',
defaultDate: '=?', defaultDate: '=?',
onSelection: '&?',
onMoveNext: '&?',
onMovePrevious: '&?',
hasEvents: '&?', hasEvents: '&?',
getClass: '&?', getClass: '&?',
formatDay: '&?',
displayControls: '<?', displayControls: '<?',
disabled: '<?', hideYear: '<?',
skip: '<?' hideContiguous: '<?'
} }
}); });

View File

@ -2,6 +2,10 @@ describe('Component vnCalendar', () => {
let controller; let controller;
let $element; let $element;
let date = new Date();
date.setHours(0, 0, 0, 0);
date.setDate(1);
beforeEach(angular.mock.module('vnCore', $translateProvider => { beforeEach(angular.mock.module('vnCore', $translateProvider => {
$translateProvider.translations('en', {}); $translateProvider.translations('en', {});
})); }));
@ -9,79 +13,55 @@ describe('Component vnCalendar', () => {
beforeEach(inject(($compile, $rootScope) => { beforeEach(inject(($compile, $rootScope) => {
$element = $compile(`<vn-calendar></vn-calendar`)($rootScope); $element = $compile(`<vn-calendar></vn-calendar`)($rootScope);
controller = $element.controller('vnCalendar'); controller = $element.controller('vnCalendar');
controller.defaultDate = new Date(); controller.defaultDate = date;
})); }));
afterEach(() => { afterEach(() => {
$element.remove(); $element.remove();
}); });
describe('data() setter', () => {
it(`should set an array of events and convert string dates to string object, then call repaint() method`, () => {
spyOn(controller, 'repaint');
let currentDate = new Date().toString();
controller.data = [
{dated: currentDate, name: 'Event 1'},
{dated: currentDate, name: 'Event 2'},
];
expect(controller.events[0].dated instanceof Object).toBeTruthy();
expect(controller.repaint).toHaveBeenCalledWith();
});
});
describe('moveNext()', () => { describe('moveNext()', () => {
it(`should shift to the next n months, then emit a 'moveNext' event`, () => { it(`should shift to the next month, then emit a 'move' event`, () => {
spyOn(controller, 'emit'); spyOn(controller, 'emit');
const currentMonth = controller.defaultDate.getMonth(); let nextMonth = new Date(date.getTime());
let nextMonth = currentMonth + 1; nextMonth.setMonth(nextMonth.getMonth() + 1);
controller.moveNext(1); controller.moveNext();
expect(controller.defaultDate.getMonth()).toEqual(nextMonth); expect(controller.month).toEqual(nextMonth.getMonth());
expect(controller.emit).toHaveBeenCalledWith('moveNext'); expect(controller.emit).toHaveBeenCalledWith('move', {$date: nextMonth});
}); });
}); });
describe('movePrevious()', () => { describe('movePrevious()', () => {
it(`should shift to the previous n months, then emit a 'movePrevious' event`, () => { it(`should shift to the previous month, then emit a 'move' event`, () => {
spyOn(controller, 'emit'); spyOn(controller, 'emit');
const currentMonth = controller.defaultDate.getMonth(); let previousMonth = new Date(date.getTime());
let previousMonth = currentMonth - 1; previousMonth.setMonth(previousMonth.getMonth() - 1);
controller.movePrevious(1); controller.movePrevious();
expect(controller.defaultDate.getMonth()).toEqual(previousMonth); expect(controller.month).toEqual(previousMonth.getMonth());
expect(controller.emit).toHaveBeenCalledWith('movePrevious'); expect(controller.emit).toHaveBeenCalledWith('move', {$date: previousMonth});
}); });
}); });
describe('select()', () => { describe('select()', () => {
it(`should return the selected element, then emit a 'selection' event`, () => { it(`should return the selected element, then emit a 'selection' event`, () => {
spyOn(controller, 'emit'); spyOn(controller, 'emit');
const dated = new Date();
const days = [{dated}]; const day = new Date();
controller.days = days; day.setHours(0, 0, 0, 0);
controller.select(0); controller.select(day);
let res = { let res = {
$days: [dated], $days: [day],
$type: 'day' $type: 'day'
}; };
expect(controller.field).toEqual(day);
expect(controller.emit).toHaveBeenCalledWith('selection', res); expect(controller.emit).toHaveBeenCalledWith('selection', res);
}); });
}); });
describe('renderStyle()', () => {
it(`should normalize CSS attributes`, () => {
const result = controller.renderStyle({
backgroundColor: 'red'
});
expect(result['background-color']).toEqual('red');
});
});
}); });

View File

@ -1,103 +1,97 @@
@import "variables"; @import "variables";
vn-calendar.small {
.events {
display: none
}
}
vn-calendar { vn-calendar {
display: block; display: block;
.header vn-one { & > div {
text-align: center; & > .header {
padding: 0.2em 0; display: flex;
height: 1.5em margin-bottom: 0.5em;
} align-items: center;
.weekdays { height: 2.4em;
color: $color-font-secondary;
margin-bottom: 0.5em;
padding: 0.5em 0;
font-weight: bold;
font-size: 0.8em;
}
.weekdays section {
cursor: pointer
}
.weekdays section, .day {
position: relative;
text-align: center;
box-sizing: border-box;
width: 14.28%;
outline: 0;
}
.days {
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
}
.day {
.content {
position: absolute;
bottom: 0;
right: 0;
left: 0;
top: 0
}
.day-number {
transition: background-color 0.3s;
text-align:center;
float:inline-end;
margin: 0 auto;
border-radius: 50%;
font-size: 0.85em;
width:2.2em;
height: 1.2em;
padding: 0.5em 0;
cursor: pointer;
outline: 0
}
.day-number:hover {
background-color: lighten($color-font-secondary, 20%);
opacity: 0.8
}
}
.day::after {
content: "";
display: block;
padding-top: 100%;
}
.day.primary .day-number {
background-color: $color-main;
color: $color-font-dark;
}
.events {
margin-top: 0.5em;
font-size: 0.6em
}
.events {
color: $color-font-secondary;
.event { & > .title {
margin-bottom: .1em; flex: 1;
text-align: center;
padding: 0.2em 0;
}
& > .vn-button {
color: inherit;
}
}
& > .weekdays {
display: flex;
color: $color-font-secondary;
margin-bottom: 0.5em;
padding: 0.5em 0;
font-weight: bold;
font-size: 0.8em;
text-align: center;
& > section {
width: 14.28%;
cursor: pointer;
}
}
& > .days {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
& > .day {
width: 14.28%;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
&.weekend {
color: $color-font-secondary;
}
&.previous,
&.next {
opacity: .5;
}
&.event .day-number {
background-color: $color-main;
color: $color-font-dark;
}
& > .day-number {
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
font-size: 14px;
width: 2.2em;
height: 2.2em;
cursor: pointer;
outline: 0;
transition: background-color 300ms ease-in-out;
&:hover {
background-color: lighten($color-font-secondary, 20%);
opacity: .8
}
}
}
&.hide-contiguous > .day {
&.previous,
&.next {
visibility: hidden;
}
}
} }
} }
.chip { &.disabled,
background-color: $color-main; &.readonly {
color: $color-font-bg; & > div {
display: inline-block; & > .weekdays > section {
border-radius: .3em; cursor: initial;
padding: 0.3em .8em; }
max-width: 5em; & > .days > .day > .day-number {
} cursor: initial;
.day.gray { }
.day-number {
color: $color-font-secondary
}
}
.day.sunday {
.day-number {
color: $color-alert;
font-weight: bold
} }
} }
} }

View File

@ -1 +0,0 @@
<div></div>

View File

@ -5,12 +5,11 @@ export default function directive() {
return { return {
restrict: 'E', restrict: 'E',
transclude: true, transclude: true,
template: require('./card.html'),
link: function($scope, $element, $attrs, $ctrl, $transclude) { link: function($scope, $element, $attrs, $ctrl, $transclude) {
$element.addClass('demo-card-wide vn-shadow bg-panel'); $element[0].classList.add('vn-shadow', 'bg-panel');
$transclude($scope, function(clone) { $transclude($scope, function(clone) {
angular.element($element[0].querySelector('div')).append(clone); $element.append(clone);
}); });
} }
}; };

View File

@ -0,0 +1,9 @@
<h6>Debug info</h6>
<ul>
<li>
{{$ctrl.env}}
</li>
<li>
<span ng-class="{alert: $root.$$watchersCount > 500}">{{$root.$$watchersCount}}</span> watchers
</li>
</ul>

View File

@ -0,0 +1,27 @@
import ngModule from '../../module';
import './style.scss';
/**
* Floating box displaying debugging information.
* Enabled only in development environment.
*/
export default class Controller {
constructor($element, $) {
this.env = process.env.NODE_ENV || 'development';
if (this.env == 'development')
this.interval = setInterval(() => $.$digest(), 2000);
else
$element[0].style.display = 'none';
}
$onDestroy() {
clearInterval(this.interval);
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.component('vnDebugInfo', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,44 @@
@import "variables";
vn-debug-info {
position: fixed;
bottom: 1em;
left: 1em;
padding: 1em;
min-width: 8em;
background-color: #3f51b5;
color: $color-font-dark;
border-radius: 4px;
z-index: 999;
box-shadow: $shadow;
transition: opacity 400ms ease-in-out;
&:hover {
opacity: .5;
}
& > h6 {
font-weight: normal;
color: rgba(255, 255, 255, .5);
font-size: 1em;
}
ul {
list-style-type: none;
padding: 0;
margin: 0;
& > li {
margin-top: .2em;
font-size: .95em;
& > span {
padding: .05em .2em;
border-radius: 4px;
transition: background-color 200ms ease-in-out;
&.alert {
background-color: $color-alert;
}
}
}
}
}

View File

@ -31,7 +31,7 @@
tpl-body { tpl-body {
display: block; display: block;
min-width: 20em; min-width: 16em;
} }
& > button.close { & > button.close {
@extend %clickable; @extend %clickable;

View File

@ -218,17 +218,14 @@ export default class DropDown extends Component {
onLoadMoreClick(event) { onLoadMoreClick(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault();
this.model.loadMore(); this.model.loadMore();
} }
onContainerClick(event) { onContainerClick(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
let index = getPosition(this.$.ul, event); let index = getPosition(this.$.ul, event);
if (index != -1) { if (index != -1)
event.preventDefault();
this.selectOption(index); this.selectOption(index);
}
} }
onDocKeyDown(event) { onDocKeyDown(event) {

View File

@ -153,13 +153,8 @@ export default class Field extends FormInput {
fix.innerText = text || ''; fix.innerText = text || '';
} }
refreshTabIndex() {
this.input.tabIndex = this.disabled ? -1 : this.tabIndex;
}
onClick() { onClick() {
// if (event.defaultPrevented) return; // if (event.defaultPrevented) return;
// event.preventDefault();
if (this.input !== document.activeElement) if (this.input !== document.activeElement)
this.focus(); this.focus();
@ -177,19 +172,10 @@ export default class Field extends FormInput {
onClear(event) { onClear(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault();
this.field = null; this.field = null;
this.input.dispatchEvent(new Event('change')); this.input.dispatchEvent(new Event('change'));
} }
focus() {
this.input.focus();
}
select() {
this.input.select();
}
buildInput(type) { buildInput(type) {
let template = `<input type="${type}" ng-model="$ctrl.field"></input>`; let template = `<input type="${type}" ng-model="$ctrl.field"></input>`;
this.input = this.$compile(template)(this.$)[0]; this.input = this.$compile(template)(this.$)[0];

View File

@ -49,8 +49,9 @@ export default class FormInput extends Component {
set disabled(value) { set disabled(value) {
this._disabled = boolTag(value); this._disabled = boolTag(value);
this.input.disabled = this._disabled;
this.classList.toggle('disabled', this._disabled); this.classList.toggle('disabled', this._disabled);
if (this.input)
this.input.disabled = this._disabled;
this.refreshTabIndex(); this.refreshTabIndex();
} }
@ -60,8 +61,9 @@ export default class FormInput extends Component {
set readonly(value) { set readonly(value) {
this._readonly = boolTag(value); this._readonly = boolTag(value);
this.input.readOnly = this._readonly;
this.classList.toggle('readonly', this._readonly); this.classList.toggle('readonly', this._readonly);
if (this.input)
this.input.readOnly = this._readonly;
} }
get readonly() { get readonly() {
@ -77,16 +79,30 @@ export default class FormInput extends Component {
return this._tabIndex; return this._tabIndex;
} }
get inputEl() {
return this.input || this.element;
}
get editable() {
return !(this.readonly || this.disabled);
}
select() { select() {
this.input.select(); if (this.inputEl.select)
this.inputEl.select();
} }
focus() { focus() {
this.input.focus(); this.inputEl.focus();
}
initTabIndex() {
if (!this.element.hasAttribute('tabindex'))
this.element.tabIndex = 0;
} }
refreshTabIndex() { refreshTabIndex() {
this.element.tabIndex = this.disabled ? -1 : this.tabIndex; this.inputEl.tabIndex = this.disabled ? -1 : this.tabIndex;
} }
} }

View File

@ -28,6 +28,7 @@ import './check';
import './chip'; import './chip';
import './data-viewer'; import './data-viewer';
import './date-picker'; import './date-picker';
import './debug-info';
import './field'; import './field';
import './float-button'; import './float-button';
import './icon-menu'; import './icon-menu';
@ -44,3 +45,4 @@ import './td-editable';
import './textarea'; import './textarea';
import './th'; import './th';
import './treeview'; import './treeview';
import './wday-picker';

View File

@ -68,7 +68,6 @@ export default class InputNumber extends Field {
onStep(event, way) { onStep(event, way) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault();
if (way == 'up') if (way == 'up')
this.stepUp(); this.stepUp();

View File

@ -1,17 +1,17 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss'; import './style.scss';
export default class Controller { export default class Controller extends Component {
constructor($element, $translate, $attrs) { constructor($element, $, $attrs) {
this.element = $element[0]; super($element, $);
this._ = $translate;
this.hasInfo = Boolean($attrs.info); this.hasInfo = Boolean($attrs.info);
this.info = $attrs.info || null; this.info = $attrs.info || null;
} }
set label(value) { set label(value) {
let label = this.element.querySelector('vn-label'); let label = this.element.querySelector('vn-label');
label.textContent = this._.instant(value); label.textContent = this.$t(value);
this._label = value; this._label = value;
} }
@ -55,7 +55,7 @@ export default class Controller {
element.textContent = hasValue ? this.value : '-'; element.textContent = hasValue ? this.value : '-';
} }
} }
Controller.$inject = ['$element', '$translate', '$attrs']; Controller.$inject = ['$element', '$scope', '$attrs'];
ngModule.component('vnLabelValue', { ngModule.component('vnLabelValue', {
controller: Controller, controller: Controller,

View File

@ -190,7 +190,6 @@ export default class Popover extends Component {
onBgMouseDown(event) { onBgMouseDown(event) {
if (event == this.lastMouseEvent || event.defaultPrevented) return; if (event == this.lastMouseEvent || event.defaultPrevented) return;
event.preventDefault();
this.hide(); this.hide();
} }
} }

View File

@ -65,7 +65,6 @@ export default class Controller extends Component {
openPanel(event) { openPanel(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault();
this.$panelScope = this.$.$new(); this.$panelScope = this.$.$new();
this.$panel = this.$compile(`<${this.panel}/>`)(this.$panelScope); this.$panel = this.$compile(`<${this.panel}/>`)(this.$panelScope);

View File

@ -81,18 +81,6 @@ describe('Component vnSearchbar', () => {
}); });
}); });
describe('openPanel()', () => {
it(`should do nothing if the event is prevented`, () => {
let event = {
defaultPrevented: true,
preventDefault: jasmine.createSpy('preventDefault')
};
controller.openPanel(event);
expect(event.preventDefault).not.toHaveBeenCalledWith();
});
});
describe('onPopoverClose()', () => { describe('onPopoverClose()', () => {
it(`should get rid of $panel and $panelScope`, () => { it(`should get rid of $panel and $panelScope`, () => {
controller.$panel = { controller.$panel = {

View File

@ -10,43 +10,20 @@ import './style.scss';
export default class Toggle extends FormInput { export default class Toggle extends FormInput {
constructor($element, $) { constructor($element, $) {
super($element, $); super($element, $);
this.initTabIndex();
let element = this.element; this.classList.add('vn-toggle');
element.tabIndex = 0; this.element.addEventListener('click', e => this.onClick(e));
element.addEventListener('click', e => this.onClick(e)); this.element.addEventListener('keydown', e => this.onKeydown(e));
element.addEventListener('keydown', e => this.onKeydown(e));
element.classList.add('vn-toggle');
}
set disabled(value) {
this._disabled = value;
this.classList.toggle('disabled', Boolean(value));
this.refreshTabIndex();
}
get disabled() {
return this._disabled;
}
set readonly(value) {
this._readonly = value;
this.classList.toggle('readonly', Boolean(value));
}
get readonly() {
return this._readonly;
} }
onKeydown(event) { onKeydown(event) {
if (event.code == 'Space') if (!event.defaultPrevented && event.code == 'Space')
this.onClick(event); this.element.click();
} }
onClick(event) { onClick(event) {
if (this.disabled || event.defaultPrevented) if (!this.editable || event.defaultPrevented)
return true; return true;
event.preventDefault();
} }
changed() { changed() {

View File

@ -235,7 +235,6 @@ export function directive($document, $compile, $templateRequest) {
$element[0].title = ''; $element[0].title = '';
$element.on('mouseover', function(event) { $element.on('mouseover', function(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault();
tooltip.show($element[0]); tooltip.show($element[0]);
}); });
$element.on('mouseout', function() { $element.on('mouseout', function() {

View File

@ -9,12 +9,12 @@
<section class="buttons" ng-if="::!$ctrl.treeview.readOnly"> <section class="buttons" ng-if="::!$ctrl.treeview.readOnly">
<vn-icon-button translate-attr="::{title: 'Remove'}" <vn-icon-button translate-attr="::{title: 'Remove'}"
icon="delete" icon="delete"
ng-click="$ctrl.treeview.onRemove($ctrl.item)" ng-click="$ctrl.treeview.onRemove($event, $ctrl.item)"
ng-if="$ctrl.item.parent"> ng-if="$ctrl.item.parent">
</vn-icon-button> </vn-icon-button>
<vn-icon-button translate-attr="::{title: 'Create'}" <vn-icon-button translate-attr="::{title: 'Create'}"
icon="add_circle" icon="add_circle"
ng-click="$ctrl.treeview.onCreate($ctrl.item)"> ng-click="$ctrl.treeview.onCreate($event, $ctrl.item)">
</vn-icon-button> </vn-icon-button>
</section> </section>
</div> </div>

View File

@ -2,7 +2,7 @@
<ul ng-if="$ctrl.items"> <ul ng-if="$ctrl.items">
<li ng-repeat="item in $ctrl.items"> <li ng-repeat="item in $ctrl.items">
<vn-treeview-child item="item" ng-class="{expanded: item.active}" <vn-treeview-child item="item" ng-class="{expanded: item.active}"
ng-click="$ctrl.onClick($event, item)"> ng-click="$ctrl.treeview.onToggle($event, item)">
</vn-treeview-child> </vn-treeview-child>
<vn-treeview-childs <vn-treeview-childs
items="item.childs"> items="item.childs">

View File

@ -1,13 +1,6 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Component from '../../lib/component';
class Controller extends Component { class Controller {}
onClick(event, item) {
if (event.defaultPrevented || !item.sons) return;
event.preventDefault();
this.treeview.onToggle(item);
}
}
ngModule.component('vnTreeviewChilds', { ngModule.component('vnTreeviewChilds', {
template: require('./childs.html'), template: require('./childs.html'),

View File

@ -141,7 +141,12 @@ export default class Treeview extends Component {
}); });
} }
onToggle(item) { onToggle(event, item) {
let ignoreEvent = event.defaultPrevented
|| event == this.lastActionEvent
|| !item.sons;
if (ignoreEvent) return;
if (item.active) if (item.active)
this.fold(item); this.fold(item);
else else
@ -175,7 +180,8 @@ export default class Treeview extends Component {
}).then(() => item.active = true); }).then(() => item.active = true);
} }
onRemove(item) { onRemove(event, item) {
this.lastActionEvent = event;
if (this.removeFunc) if (this.removeFunc)
this.removeFunc({$item: item}); this.removeFunc({$item: item});
} }
@ -191,7 +197,8 @@ export default class Treeview extends Component {
if (parent) parent.sons--; if (parent) parent.sons--;
} }
onCreate(parent) { onCreate(event, parent) {
this.lastActionEvent = event;
if (this.createFunc) if (this.createFunc)
this.createFunc({$parent: parent}); this.createFunc({$parent: parent});
} }

View File

@ -102,11 +102,16 @@ describe('Component vnTreeview', () => {
spyOn(controller, 'fold'); spyOn(controller, 'fold');
spyOn(controller, 'unfold'); spyOn(controller, 'unfold');
const item = {name: 'My item'}; let event = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
const item = {name: 'My item', sons: 1};
controller.onToggle(item); controller.onToggle(event, item);
item.active = true; item.active = true;
controller.onToggle(item); controller.onToggle(event, item);
expect(controller.unfold).toHaveBeenCalledWith(item); expect(controller.unfold).toHaveBeenCalledWith(item);
expect(controller.fold).toHaveBeenCalledWith(item); expect(controller.fold).toHaveBeenCalledWith(item);
@ -148,9 +153,15 @@ describe('Component vnTreeview', () => {
describe('onRemove()', () => { describe('onRemove()', () => {
it(`should call the removeFunc() method`, () => { it(`should call the removeFunc() method`, () => {
let event = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
spyOn(controller, 'removeFunc'); spyOn(controller, 'removeFunc');
const item = {name: 'My item'}; const item = {name: 'My item'};
controller.onRemove(item); controller.onRemove(event, item);
expect(controller.removeFunc).toHaveBeenCalledWith({$item: item}); expect(controller.removeFunc).toHaveBeenCalledWith({$item: item});
}); });
@ -172,9 +183,15 @@ describe('Component vnTreeview', () => {
describe('onCreate()', () => { describe('onCreate()', () => {
it(`should call the createFunc() method`, () => { it(`should call the createFunc() method`, () => {
let event = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
spyOn(controller, 'createFunc'); spyOn(controller, 'createFunc');
const parent = {name: 'My item'}; const parent = {name: 'My item'};
controller.onCreate(parent); controller.onCreate(event, parent);
expect(controller.createFunc).toHaveBeenCalledWith({$parent: parent}); expect(controller.createFunc).toHaveBeenCalledWith({$parent: parent});
}); });

View File

@ -181,7 +181,7 @@ export default class Watcher extends Component {
* Notifies the user that the data has been saved. * Notifies the user that the data has been saved.
*/ */
notifySaved() { notifySaved() {
this.vnApp.showSuccess(this._.instant('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
} }
setPristine() { setPristine() {

View File

@ -0,0 +1,8 @@
<span
ng-repeat="day in $ctrl.days"
translate-attr="::{title: day.name}"
ng-class="{marked: $ctrl.field[day.code]}"
ng-click="$ctrl.field[day.code] = !$ctrl.field[day.code]">
{{day.localeChar}}
</span>

View File

@ -0,0 +1,17 @@
import ngModule from '../../module';
import FormInput from '../form-input';
import './style.scss';
export default class WdayPicker extends FormInput {
constructor($element, $scope, vnWeekDays) {
super($element, $scope);
this.days = vnWeekDays.locales;
this.initTabIndex();
}
}
WdayPicker.$inject = ['$element', '$scope', 'vnWeekDays'];
ngModule.vnComponent('vnWdayPicker', {
template: require('./index.html'),
controller: WdayPicker
});

View File

@ -0,0 +1,27 @@
@import "effects";
vn-wday-picker {
text-align: center;
&:focus {
outline: solid 1px rgba(0, 0, 0, .1);
}
& > span {
@extend %clickable;
border-radius: 50%;
padding: .4em;
margin: .2em;
display: inline-flex;
width: 1.5em;
height: 1.5em;
justify-content: center;
align-items: center;
outline: none;
background-color: rgba(0, 0, 0, .05);
&.marked {
background: $color-main;
color: $color-font-dark;
}
}
}

View File

@ -2,15 +2,14 @@ import ngModule from '../module';
directive.$inject = ['$document']; directive.$inject = ['$document'];
export function directive($document) { export function directive($document) {
let modifiers = ["alt", "ctrl", "meta", "shift"]; let modifiers = ['alt', 'ctrl', 'meta', 'shift'];
function checkAttributes($attrs) { function checkAttributes($attrs) {
let shortcut = $attrs.vnBind.split(' '); let shortcut = $attrs.vnBind.split(' ');
let keys = {}; let keys = {};
if (shortcut[0] == "") { if (shortcut[0] == '')
throw new Error('vnBind: Binding keys not defined'); throw new Error('vnBind: Binding keys not defined');
}
if (shortcut.length == 1) { if (shortcut.length == 1) {
keys.altKey = true; keys.altKey = true;
@ -41,7 +40,8 @@ export function directive($document) {
let correctShortcut = true; let correctShortcut = true;
for (const key in shortcut) { for (const key in shortcut) {
correctShortcut = correctShortcut && shortcut[key] == event[key]; correctShortcut = correctShortcut
&& shortcut[key] == event[key];
} }
if (correctShortcut) { if (correctShortcut) {
event.preventDefault(); event.preventDefault();
@ -49,7 +49,7 @@ export function directive($document) {
} }
} }
$document.on("keyup", onKeyUp); $document.on('keyup', onKeyUp);
$element.on('$destroy', function() { $element.on('$destroy', function() {
$document.off('keyup', onKeyUp); $document.off('keyup', onKeyUp);
}); });

View File

@ -13,11 +13,11 @@ export default function directive() {
restrict: 'A', restrict: 'A',
link: function($scope, $element, $attrs) { link: function($scope, $element, $attrs) {
$element.on('click', function(event) { $element.on('click', function(event) {
if (event.defaultPrevented) return;
let dialogKey = kebabToCamel($attrs.vnDialog); let dialogKey = kebabToCamel($attrs.vnDialog);
let dialog = $scope[dialogKey]; let dialog = $scope[dialogKey];
if (dialog instanceof Dialog) if (dialog instanceof Dialog)
dialog.show(); dialog.show();
event.preventDefault();
}); });
} }
}; };

View File

@ -3,7 +3,7 @@ import ngModule from '../module';
const regex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i; const regex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i;
export const isMobile = regex.test(navigator.userAgent); export const isMobile = regex.test(navigator.userAgent);
export function focus(input) { export function focus($scope, input) {
if (isMobile) return; if (isMobile) return;
const element = input; const element = input;
@ -23,9 +23,9 @@ export function focus(input) {
} }
input.focus(); input.focus();
$scope.$applyAsync(() => {
if (input.select)
input.select(); input.select();
});
} }
/** /**
@ -37,7 +37,7 @@ export function directive() {
return { return {
restrict: 'A', restrict: 'A',
link: function($scope, $element) { link: function($scope, $element) {
$scope.$watch('', () => focus($element[0])); $scope.$watch('', () => focus($scope, $element[0]));
} }
}; };
} }

View File

@ -12,13 +12,13 @@ export function directive() {
restrict: 'A', restrict: 'A',
link: function($scope, $element, $attrs) { link: function($scope, $element, $attrs) {
$element.on('click', function(event) { $element.on('click', function(event) {
if (event.defaultPrevented) return;
let popoverKey = kebabToCamel($attrs.vnPopover); let popoverKey = kebabToCamel($attrs.vnPopover);
let popover = $scope[popoverKey]; let popover = $scope[popoverKey];
if (popover instanceof Popover) { if (popover instanceof Popover) {
popover.parent = $element[0]; popover.parent = $element[0];
popover.show(); popover.show();
} }
event.preventDefault();
}); });
} }
}; };

View File

@ -40,6 +40,7 @@ describe('Directive focus', () => {
it('should call select function on the element', () => { it('should call select function on the element', () => {
let html = `<input vn-focus></input>`; let html = `<input vn-focus></input>`;
compile(html); compile(html);
$scope.$apply();
expect($element[0].select).toHaveBeenCalledWith(); expect($element[0].select).toHaveBeenCalledWith();
}); });

View File

@ -44,7 +44,7 @@ export function directive($timeout) {
restrict: 'A', restrict: 'A',
link: function($scope, $element, $attrs) { link: function($scope, $element, $attrs) {
$element.on('click', function(event) { $element.on('click', function(event) {
event.preventDefault(); if (event.defaultPrevented) return;
let src = $attrs.zoomImage || $attrs.src; let src = $attrs.zoomImage || $attrs.src;
if (src) if (src)

View File

@ -1,3 +1,4 @@
import ngModule from '../module';
import EventEmitter from './event-emitter'; import EventEmitter from './event-emitter';
import {kebabToCamel} from './string'; import {kebabToCamel} from './string';
@ -44,5 +45,30 @@ export default class Component extends EventEmitter {
get document() { get document() {
return this.element.ownerDocument; return this.element.ownerDocument;
} }
/**
* Translates an string.
*
* @param {String} string String to translate
* @param {Array} params Translate parameters
* @return {String} The translated string
*/
$t(string, params) {
return this.$translate.instant(string, params);
}
} }
Component.$inject = ['$element', '$scope']; Component.$inject = ['$element', '$scope'];
function runFn($translate, $q, $http, vnApp, $state, $stateParams) {
Object.assign(Component.prototype, {
$translate,
$q,
$http,
vnApp,
$state,
$params: $stateParams
});
}
runFn.$inject = ['$translate', '$q', '$http', 'vnApp', '$state', '$stateParams'];
ngModule.run(runFn);

View File

@ -1,34 +0,0 @@
import Component from './component';
/**
* Class with commonly injected services assigned as properties. It also has
* abbreviations for commonly used methods like tranlation.
*
* @property {Object} $translate Angular tranlation service
* @property {Object} $http Angular HTTP service
* @property {Object} $state Router state service
* @property {Object} $stateParams Router state parameters
*/
export default class Section extends Component {
constructor($element, $scope, $translate, $http, $state) {
super($element, $scope);
Object.assign(this, {
$translate,
$http,
$state,
$stateParams: $state.params
});
}
/**
* Translates an string.
*
* @param {String} string String to translate
* @param {Array} params Translate parameters
* @return {String} The translated string
*/
_(string, params) {
return this.$translate.instant(string, params, );
}
}
Section.$inject = ['$element', '$scope', '$translate', '$http', '$state'];

View File

@ -4,10 +4,12 @@ class AclService {
constructor($http) { constructor($http) {
this.$http = $http; this.$http = $http;
} }
reset() { reset() {
this.user = null; this.user = null;
this.roles = null; this.roles = null;
} }
load() { load() {
return this.$http.get('/api/Accounts/acl').then(res => { return this.$http.get('/api/Accounts/acl').then(res => {
this.user = res.data.user; this.user = res.data.user;
@ -19,6 +21,7 @@ class AclService {
} }
}); });
} }
hasAny(roles) { hasAny(roles) {
if (this.roles) { if (this.roles) {
for (let role of roles) { for (let role of roles) {

View File

@ -20,6 +20,7 @@ export default class Auth {
loggedIn: false loggedIn: false
}); });
} }
initialize() { initialize() {
let criteria = { let criteria = {
to: state => state.name != 'login' to: state => state.name != 'login'
@ -42,6 +43,7 @@ export default class Auth {
return redirectToLogin(); return redirectToLogin();
}); });
} }
login(user, password, remember) { login(user, password, remember) {
if (!user) if (!user)
return this.$q.reject(new UserError('Please enter your username')); return this.$q.reject(new UserError('Please enter your username'));
@ -54,6 +56,7 @@ export default class Auth {
return this.$http.post('/api/Accounts/login', params).then( return this.$http.post('/api/Accounts/login', params).then(
json => this.onLoginOk(json, remember)); json => this.onLoginOk(json, remember));
} }
onLoginOk(json, remember) { onLoginOk(json, remember) {
this.vnToken.set(json.data.token, remember); this.vnToken.set(json.data.token, remember);
@ -65,6 +68,7 @@ export default class Auth {
this.$state.go('home'); this.$state.go('home');
}); });
} }
logout() { logout() {
let promise = this.$http.post('/api/Accounts/logout', null, { let promise = this.$http.post('/api/Accounts/logout', null, {
headers: {Authorization: this.vnToken.token} headers: {Authorization: this.vnToken.token}
@ -78,6 +82,7 @@ export default class Auth {
return promise; return promise;
} }
loadAcls() { loadAcls() {
return this.aclService.load() return this.aclService.load()
.then(() => { .then(() => {

View File

@ -6,3 +6,4 @@ import './token';
import './modules'; import './modules';
import './interceptor'; import './interceptor';
import './config'; import './config';
import './week-days';

View File

@ -0,0 +1,71 @@
import ngModule from '../module';
class WeekDays {
constructor($translate) {
this.$translate = $translate;
this.days = [
{
code: 'sun',
name: 'Sunday'
}, {
code: 'mon',
name: 'Monday'
}, {
code: 'tue',
name: 'Tuesday'
}, {
code: 'wed',
name: 'Wednesday'
}, {
code: 'thu',
name: 'Thursday'
}, {
code: 'fri',
name: 'Friday'
}, {
code: 'sat',
name: 'Saturday'
}
];
this.map = {};
for (let i = 0; i < this.days.length; i++) {
let day = this.days[i];
day.index = i;
day.char = day.name.substr(0, 1);
day.abr = day.name.substr(0, 3);
this.map[day.code] = day;
}
this.getLocales();
}
getLocales() {
for (let day of this.days) {
let locale = this.$translate.instant(day.name);
Object.assign(day, {
locale,
localeChar: locale.substr(0, 1),
localeAbr: locale.substr(0, 3)
});
}
this.localeCodes = [
'mon',
'tue',
'wed',
'thu',
'fri',
'sat',
'sun'
];
this.locales = [];
for (let code of this.localeCodes)
this.locales.push(this.map[code]);
}
}
WeekDays.$inject = ['$translate'];
ngModule.service('vnWeekDays', WeekDays);

View File

@ -1,9 +1,12 @@
@import "variables"; @import "variables";
html, body { html {
background-color: $color-bg; background-color: $color-bg;
overflow: auto; overflow: auto;
height: 100%; height: 100%;
}
body {
height: 100%;
font-family: vn-font; font-family: vn-font;
color: $color-font; color: $color-font;
font-size: $font-size; font-size: $font-size;

View File

@ -42,13 +42,13 @@
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
padding: 0; padding: 0;
margin-top: 0; margin-top: 0;
margin-bottom: .2em; margin-bottom: .3em;
} }
/* Colors */ /* Colors */
.text-primary { .text-primary {
color: $color-font; color: $color-main;
} }
.text-secondary { .text-secondary {
color: $color-font-secondary; color: $color-font-secondary;

View File

@ -26,3 +26,4 @@
<vn-home></vn-home> <vn-home></vn-home>
</div> </div>
<vn-snackbar vn-id="snackbar"></vn-snackbar> <vn-snackbar vn-id="snackbar"></vn-snackbar>
<vn-debug-info></vn-debug-info>

View File

@ -1,7 +1,7 @@
<a ng-if="$ctrl.links.btnOne" <a ng-if="$ctrl.links.btnOne"
vn-tooltip="{{::$ctrl.links.btnOne.tooltip}}" vn-tooltip="{{::$ctrl.links.btnOne.tooltip}}"
class="vn-button colored" class="vn-button colored"
ui-sref="{{::$ctrl.links.btnOne.state}}" target="_blank"> ui-sref="{{::$ctrl.links.btnOne.state}}">
<vn-icon <vn-icon
icon="{{::$ctrl.links.btnOne.icon}}"> icon="{{::$ctrl.links.btnOne.icon}}">
</vn-icon> </vn-icon>
@ -9,7 +9,7 @@
<a ng-if="$ctrl.links.btnTwo" <a ng-if="$ctrl.links.btnTwo"
vn-tooltip="{{::$ctrl.links.btnTwo.tooltip}}" vn-tooltip="{{::$ctrl.links.btnTwo.tooltip}}"
class="vn-button colored" class="vn-button colored"
ui-sref="{{::$ctrl.links.btnTwo.state}}" target="_blank"> ui-sref="{{::$ctrl.links.btnTwo.state}}">
<vn-icon <vn-icon
icon="{{::$ctrl.links.btnTwo.icon}}"> icon="{{::$ctrl.links.btnTwo.icon}}">
</vn-icon> </vn-icon>
@ -17,7 +17,7 @@
<a ng-if="$ctrl.links.btnThree" <a ng-if="$ctrl.links.btnThree"
vn-tooltip="{{::$ctrl.links.btnThree.tooltip}}" vn-tooltip="{{::$ctrl.links.btnThree.tooltip}}"
class="vn-button colored" class="vn-button colored"
ui-sref="{{::$ctrl.links.btnThree.state}}" target="_blank"> ui-sref="{{::$ctrl.links.btnThree.state}}">
<vn-icon <vn-icon
icon="{{::$ctrl.links.btnThree.icon}}"> icon="{{::$ctrl.links.btnThree.icon}}">
</vn-icon> </vn-icon>

View File

@ -1,16 +1,17 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss'; import './style.scss';
export default class Controller { export default class Controller extends Component {
constructor(vnModules, $state, $translate, $sce) { constructor($element, $, vnModules, $sce) {
super($element, $);
this.modules = vnModules.get(); this.modules = vnModules.get();
this.$state = $state;
this._ = $translate;
this.$sce = $sce; this.$sce = $sce;
} }
getModuleName(mod) { getModuleName(mod) {
let getName = mod => { let getName = mod => {
let name = this._.instant(mod.name); let name = this.$t(mod.name);
let upper = name.toUpperCase(); let upper = name.toUpperCase();
if (!mod.keyBind) return name; if (!mod.keyBind) return name;
let index = upper.indexOf(mod.keyBind); let index = upper.indexOf(mod.keyBind);
@ -25,8 +26,7 @@ export default class Controller {
return this.$sce.trustAsHtml(getName(mod)); return this.$sce.trustAsHtml(getName(mod));
} }
} }
Controller.$inject = ['$element', '$scope', 'vnModules', '$sce'];
Controller.$inject = ['vnModules', '$state', '$translate', '$sce'];
ngModule.component('vnHome', { ngModule.component('vnHome', {
template: require('./home.html'), template: require('./home.html'),

View File

@ -18,6 +18,9 @@ vn-main-menu {
vertical-align: middle; vertical-align: middle;
font-weight: bold; font-weight: bold;
margin-right: .2em; margin-right: .2em;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
cursor: pointer; cursor: pointer;
} }
& > .vn-button { & > .vn-button {

View File

@ -39,7 +39,6 @@ export default class SideMenu {
onEscape(event) { onEscape(event) {
if (!event.defaultPrevented && event.key == 'Escape') { if (!event.defaultPrevented && event.key == 'Escape') {
event.preventDefault();
this.hide(); this.hide();
this.$.$digest(); this.$.$digest();
} }

View File

@ -4,50 +4,48 @@
margin: 0 auto; margin: 0 auto;
max-width: 950px; max-width: 950px;
& > div { & > h5 {
& > h5 { padding: $spacing-sm;
padding: $spacing-sm; border: none;
border: none; background: $color-main;
background: $color-main; color: $color-font-dark;
color: $color-font-dark; margin: 0;
margin: 0; text-align: center;
text-align: center; line-height: 1.3em;
line-height: 1.3em; white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
& > vn-horizontal {
flex-wrap: wrap;
padding: $spacing-md;
overflow: hidden;
align-items: flex-start;
h4 {
margin-bottom: $spacing-md;
text-transform: uppercase;
font-size: 15pt;
line-height: 1;
padding: 7px;
padding-bottom: 4px; /* Bottom line-height fix */
font-weight: lighter;
background-color: $color-main-light;
border-bottom: .1em solid $color-main;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
& > vn-horizontal { & > * {
flex-wrap: wrap; margin: $spacing-sm;
padding: $spacing-md; min-width: 14em;
overflow: hidden; padding: 0;
align-items: flex-start; }
& > vn-auto {
h4 { width: 100%;
margin-bottom: $spacing-md; }
text-transform: uppercase; vn-label-value > section {
font-size: 15pt; margin-bottom: .3em;
line-height: 1;
padding: 7px;
padding-bottom: 4px; /* Bottom line-height fix */
font-weight: lighter;
background-color: $color-main-light;
border-bottom: .1em solid $color-main;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
& > * {
margin: $spacing-sm;
min-width: 14em;
padding: 0;
}
& > vn-auto {
width: 100%;
}
vn-label-value > section {
margin-bottom: .3em;
}
} }
} }
p:after { p:after {

View File

@ -61,11 +61,11 @@ vn-bg-title {
} }
.totalBox { .totalBox {
border: 1px solid #CCC; border: 1px solid #CCC;
text-align: right !important; text-align: right;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 18px; padding: $spacing-md;
max-width: 12em; max-width: 14em;
} }
.form { .form {
height: 100%; height: 100%;

View File

@ -11,7 +11,7 @@
width: 28em; width: 28em;
overflow: hidden; overflow: hidden;
& > vn-card > div { & > vn-card {
display: flex; display: flex;
height: 12em; height: 12em;

View File

@ -1,22 +1,28 @@
<vn-calendar <vn-card>
vn-id="stMonth" <div class="header">
skip="2" <vn-button
has-events="$ctrl.hasEvents($day)" icon="navigate_before"
get-class="$ctrl.getClass($day)" ng-click="$ctrl.step(-1)"
on-selection="$ctrl.onSelection($days, $type, $weekday)" class="flat">
on-move-next="ndMonth.moveNext(2)" </vn-button>
on-move-previous="ndMonth.movePrevious(2)" <span>{{$ctrl.firstDay | date:'MMMM yyyy'}} - {{$ctrl.lastDay | date:'MMMM yyyy'}}</span>
vn-acl="deliveryBoss" <vn-button
class="vn-pa-md"> icon="navigate_next"
</vn-calendar> ng-click="$ctrl.step(1)"
<vn-calendar class="flat">
vn-id="ndMonth" </vn-button>
skip="2" </div>
has-events="$ctrl.hasEvents($day)" <div class="calendars vn-pa-md">
get-class="$ctrl.getClass($day)" <vn-calendar
on-selection="$ctrl.onSelection($days, $type, $weekday)" ng-repeat="date in $ctrl.months track by date.getTime()"
default-date="$ctrl.ndMonthDate" default-date="date"
vn-acl="deliveryBoss" display-controls="false"
display-controls="false" hide-contiguous="true"
class="vn-pa-md"> has-events="$ctrl.hasEvents($day)"
</vn-calendar> get-class="$ctrl.getClass($day)"
on-selection="$ctrl.onSelection($days, $type, $weekday)"
class="vn-pa-md"
style="min-width: 250px; flex: 1;">
</vn-calendar>
</div>
</vn-card>

View File

@ -1,100 +1,155 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'core/lib/section'; import Component from 'core/lib/component';
import './style.scss'; import './style.scss';
class Controller extends Section { class Controller extends Component {
constructor($el, $, $t, $http, $state) { constructor($element, $) {
super($el, $, $t, $http, $state); super($element, $);
this.nMonths = 4;
this.excls = {}; let date = new Date();
this.resetEvents(); date.setDate(1);
this.ndMonthDate = new Date(); date.setHours(0, 0, 0, 0);
this.ndMonthDate.setMonth(this.ndMonthDate.getMonth() + 1); this.date = date;
}
get date() {
return this._date;
}
set date(value) {
this._date = value;
let stamp = value.getTime();
let firstDay = new Date(stamp);
firstDay.setDate(1);
this.firstDay = firstDay;
let lastDay = new Date(stamp);
lastDay.setMonth(lastDay.getMonth() + this.nMonths);
lastDay.setDate(0);
this.lastDay = lastDay;
this.months = [];
for (let i = 0; i < this.nMonths; i++) {
let monthDate = new Date(stamp);
monthDate.setMonth(value.getMonth() + i);
this.months.push(monthDate);
}
this.refreshEvents();
}
step(direction) {
let date = new Date(this.date.getTime());
date.setMonth(date.getMonth() + (this.nMonths * direction));
this.date = date;
}
get data() {
return this._data;
}
set data(value) {
this._data = value;
value = value || {};
this.events = value.events;
this.exclusions = value.exclusions;
if (this.events) {
let codes = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
for (event of this.events) {
if (!event.weekDays) continue;
let weekDays = event.weekDays.split(',');
event.wdays = [];
for (let wday of weekDays) {
let index = codes.indexOf(wday);
if (index !== -1) event.wdays[index] = true;
}
}
}
this.refreshEvents();
let calendars = this.element.querySelectorAll('vn-calendar');
for (let calendar of calendars)
calendar.$ctrl.repaint();
}
refreshEvents() {
function getDate(date) {
return date && new Date(date).setHours(0, 0, 0, 0);
}
let exclusionsMap = {};
if (this.exclusions) {
for (let exclusion of this.exclusions)
exclusionsMap[getDate(exclusion.day)] = exclusion;
}
this.days = {};
let day = new Date(this.firstDay.getTime());
while (day < this.lastDay) {
let stamp = day.getTime();
let wday = day.getDay();
let dayEvents = [];
if (this.events) {
for (let event of this.events) {
let match;
let from = getDate(event.from);
let to = getDate(event.to);
if (event.from && event.to) {
match = stamp >= from && stamp <= to
&& event.wdays[wday];
} else if (event.from)
match = from == stamp;
else
match = event.wdays[wday];
if (match)
dayEvents.push(event);
}
}
let exclusion = exclusionsMap[stamp];
if (dayEvents.length || exclusion) {
let dayData = {};
if (dayEvents.length) dayData.events = dayEvents;
if (exclusion) dayData.exclusion = exclusion;
this.days[stamp] = dayData;
}
day.setDate(day.getDate() + 1);
}
} }
onSelection($days, $type, $weekday) { onSelection($days, $type, $weekday) {
this.emit('selection', {$days, $type, $weekday}); let $data = [];
} for (let day of $days) {
let dayData = this.days[day.getTime()];
resetEvents() { if (dayData) $data.push(dayData);
this.wdays = [];
this.days = {};
this.ranges = [];
}
get events() {
return this._events;
}
set events(value) {
this._events = value;
this.resetEvents();
if (!value) return;
function setWdays(wdays, weekDays) {
let codes = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
if (!weekDays) return [];
weekDays = weekDays.split(',');
for (let wday of weekDays)
wdays[codes.indexOf(wday)] = true;
return wdays;
} }
for (let event of value) { this.emit('selection', {
if (event.from && event.to) { $days,
this.ranges.push({ $type,
from: new Date(event.from).setHours(0, 0, 0, 0), $weekday,
to: new Date(event.to).setHours(0, 0, 0, 0), $data
wdays: setWdays([], event.weekDays)
});
} else if (event.from) {
let day = new Date(event.from).setHours(0, 0, 0, 0);
this.days[day] = true;
} else
setWdays(this.wdays, event.weekDays);
}
this.repaint();
}
get exclusions() {
return this._exclusions;
}
set exclusions(value) {
this._exclusions = value;
this.excls = {};
if (!value) return;
value.forEach(exclusion => {
let day = new Date(exclusion.day).setHours(0, 0, 0, 0);
this.excls[day] = true;
}); });
this.repaint();
}
hasRange(time, wday) {
let range = this.ranges.find(e => e.from <= time && e.to >= time);
return range && range.wdays[wday];
} }
hasEvents(day) { hasEvents(day) {
let time = day.getTime(); return this.days[day.getTime()] != null;
let wday = day.getDay();
return this.wdays[wday]
|| this.days[time]
|| this.hasRange(time, wday);
} }
getClass(day) { getClass(day) {
if (this.excls[day.getTime()]) let dayData = this.days[day.getTime()];
return 'excluded'; return dayData && dayData.exclusion ? 'excluded' : '';
}
repaint() {
this.$.stMonth.repaint();
this.$.ndMonth.repaint();
} }
} }
@ -102,7 +157,6 @@ ngModule.component('vnZoneCalendar', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller,
bindings: { bindings: {
events: '<?', data: '<?'
exclusions: '<?'
} }
}); });

View File

@ -1,13 +1,40 @@
@import "variables"; @import "variables";
vn-zone-calendar { vn-zone-calendar {
vn-calendar .day { display: block;
&.primary .day-number {
background-color: $color-success; & > vn-card {
& > .header {
display: flex;
align-items: center;
justify-content: space-between;
background-color: $color-main;
color: white;
font-weight: bold;
height: 45px;
& > .vn-button {
color: inherit;
height: 100%;
}
} }
&.excluded .day-number { & > .calendars {
background-color: $color-alert; display: flex;
color: $color-font-dark; flex-wrap: wrap;
justify-content: space-evenly;
& > vn-calendar {
max-width: 18em;
.day {
&.event .day-number {
background-color: $color-success;
}
&.excluded .day-number {
background-color: $color-alert;
}
}
}
} }
} }
} }

View File

@ -1,47 +1,43 @@
<div style="margin: 0 auto; max-width: 40em;"> <div class="vn-w-md">
<form ng-submit="$ctrl.onSubmit()"> <form ng-submit="$ctrl.onSubmit()">
<vn-card class="vn-pa-md"> <vn-card class="vn-pa-lg">
<vn-vertical> <vn-horizontal>
<vn-horizontal> <vn-autocomplete
<vn-autocomplete vn-one
vn-one label="Agency"
label="Agency" ng-model="$ctrl.params.agencyModeFk"
ng-model="$ctrl.params.agencyModeFk" url="/api/AgencyModes/isActive">
url="/api/AgencyModes/isActive"> </vn-autocomplete>
</vn-autocomplete> </vn-horizontal>
</vn-horizontal> <vn-horizontal>
<vn-horizontal> <vn-autocomplete
<vn-autocomplete vn-one
vn-one label="Province"
label="Province" ng-model="$ctrl.params.provinceFk"
ng-model="$ctrl.params.provinceFk" url="/api/Provinces"
url="/api/Provinces" fields="['countryFk']"
fields="['countryFk']" include="'country'"
include="'country'" style="margin-right: .5em;">
style="margin-right: .5em;"> <tpl-item>
<tpl-item> <div>{{name}}</div>
<div>{{name}}</div> <div style="font-size: .9em; color: gray; line-height: .8em;">
<div style="font-size: .9em; color: gray; line-height: .8em;"> {{country.country}}
{{country.country}} </div>
</div> </tpl-item>
</tpl-item> </vn-autocomplete>
</vn-autocomplete> <vn-textfield
<vn-textfield vn-one
vn-one label="Postcode"
label="Postcode" ng-model="$ctrl.params.postCode">
ng-model="$ctrl.params.postCode"> </vn-textfield>
</vn-textfield> </vn-horizontal>
</vn-horizontal>
</vn-vertical>
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>
<vn-submit label="Query"></vn-submit> <vn-submit label="Query"></vn-submit>
</vn-button-bar> </vn-button-bar>
</form> </form>
<vn-card class="vn-pa-md vn-mt-md"> <vn-zone-calendar
<vn-zone-calendar data="data"
events="events.events" class="vn-mt-md">
exclusions="events.exclusions"> </vn-zone-calendar>
</vn-zone-calendar>
</vn-card>
</div> </div>

View File

@ -11,7 +11,7 @@ class Controller {
onSubmit() { onSubmit() {
this.$http.get(`/api/Zones/getEvents`, {params: this.params}) this.$http.get(`/api/Zones/getEvents`, {params: this.params})
.then(res => this.$.events = res.data); .then(res => this.$.data = res.data);
} }
} }
Controller.$inject = ['$scope', '$http']; Controller.$inject = ['$scope', '$http'];

View File

@ -1,27 +1,60 @@
<div class="main-with-right-menu"> <div class="main-with-right-menu">
<vn-data-viewer <vn-zone-calendar
id="calendar"
data="data" data="data"
is-loading="!data" on-selection="$ctrl.onSelection($days, $type, $weekday, $data)"
class="vn-w-md">
</vn-zone-calendar>
</div>
<vn-side-menu side="right">
<div class="vn-pa-md">
<h6
class="text-secondary"
style="font-weight: normal;"
translate>
Edit mode
</h6>
<vn-vertical>
<vn-radio
label="Include"
val="include"
ng-model="$ctrl.editMode">
</vn-radio>
<vn-radio
label="Exclude"
val="exclude"
ng-model="$ctrl.editMode">
</vn-radio>
</vn-vertical>
</div>
<h6
class="text-secondary vn-px-md"
style="font-weight: normal;"
translate>
Events
</h6>
<vn-data-viewer
data="data.events"
is-loading="!data.events"
class="vn-w-sm"> class="vn-w-sm">
<vn-card>
<div class="vn-list"> <div class="vn-list">
<a <a
ng-repeat="row in data" ng-repeat="row in data.events"
translate-attr="{title: 'Edit'}" translate-attr="{title: 'Edit'}"
ng-click="$ctrl.onEdit(row, $event)" ng-click="$ctrl.onEditClick(row, $event)"
class="vn-list-item"> class="vn-list-item">
<vn-horizontal> <vn-horizontal>
<vn-one> <vn-one>
<div <div
ng-if="::row.from && !row.to" ng-if="::row.from && !row.to"
class="vn-mb-sm"> class="vn-mb-sm">
{{::row.from | date:'dd/MM/yyyy'}} {{::row.from | date:'dd/MM/yy'}}
</div> </div>
<div <div
ng-if="::!row.from || row.to" ng-if="::!row.from || row.to"
class="vn-mb-sm"> class="vn-mb-sm ellipsize">
<span ng-if="row.to"> <span ng-if="row.to">
{{::row.from | date:'dd/MM/yyyy'}} - {{::row.to | date:'dd/MM/yyyy'}} {{::row.from | date:'dd/MM/yy'}} - {{::row.to | date:'dd/MM/yy'}}
</span> </span>
<span ng-if="!row.to" translate> <span ng-if="!row.to" translate>
Indefinitely Indefinitely
@ -51,21 +84,13 @@
<vn-icon-button <vn-icon-button
icon="delete" icon="delete"
translate-attr="{title: 'Delete'}" translate-attr="{title: 'Delete'}"
ng-click="$ctrl.onDelete(row.id, $event)"> ng-click="$ctrl.onDeleteClick(row.id, $event)">
</vn-icon-button> </vn-icon-button>
</vn-horizontal> </vn-horizontal>
</vn-horizontal> </vn-horizontal>
</a> </a>
</div> </div>
</vn-card>
</vn-data-viewer> </vn-data-viewer>
</div>
<vn-side-menu side="right">
<vn-zone-calendar
events="data"
exclusions="exclusions"
on-selection="$ctrl.onCreate($days, $type, $weekday)">
</vn-zone-calendar>
</vn-side-menu> </vn-side-menu>
<vn-dialog <vn-dialog
vn-id="dialog" vn-id="dialog"
@ -89,16 +114,11 @@
val="range"> val="range">
</vn-radio> </vn-radio>
</vn-vertical> </vn-vertical>
<div <vn-wday-picker
ng-if="$ctrl.eventType != 'day'" ng-if="$ctrl.eventType != 'day'"
class="week-days"> ng-model="$ctrl.selected.wdays"
<span class="vn-mt-sm vn-mb-md">
ng-repeat="wday in $ctrl.wdays" </vn-wday-picker>
ng-class="{marked: $ctrl.selected.wdays[wday.code]}"
ng-click="$ctrl.selected.wdays[wday.code] = !$ctrl.selected.wdays[wday.code]">
{{wday.abr}}
</span>
</div>
<vn-date-picker <vn-date-picker
ng-if="$ctrl.eventType == 'day'" ng-if="$ctrl.eventType == 'day'"
label="Day" label="Day"
@ -140,8 +160,21 @@
</vn-vertical> </vn-vertical>
</tpl-body> </tpl-body>
<tpl-buttons> <tpl-buttons>
<input type="button" response="CANCEL" translate-attr="{value: 'Cancel'}"/> <input
<button response="ACCEPT" translate>Save</button> type="button"
response="CANCEL"
translate-attr="{value: 'Cancel'}">
</input>
<button
ng-if="!$ctrl.isNew"
response="DELETE"
translate>
Delete
</button>
<button response="ACCEPT">
<span ng-if="$ctrl.isNew" translate>Add</span>
<span ng-if="!$ctrl.isNew" translate>Save</span>
</button>
</tpl-buttons> </tpl-buttons>
</vn-dialog> </vn-dialog>
<vn-confirm <vn-confirm

View File

@ -1,53 +1,27 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'core/lib/section'; import Component from 'core/lib/component';
import './style.scss';
class Controller extends Section { class Controller extends Component {
constructor($el, $, $t, $http, $state) { constructor($element, $, vnWeekDays) {
super($el, $, $t, $http, $state); super($element, $);
this.vnWeekDays = vnWeekDays;
this.editMode = 'include';
this.wdays = [ this.path = `api/Zones/${this.$params.id}/events`;
{ this.exclusionsPath = `api/Zones/${this.$params.id}/exclusions`;
code: 'mon',
name: 'Monday'
}, {
code: 'tue',
name: 'Tuesday'
}, {
code: 'wed',
name: 'Wednesday'
}, {
code: 'thu',
name: 'Thursday'
}, {
code: 'fri',
name: 'Friday'
}, {
code: 'sat',
name: 'Saturday'
}, {
code: 'sun',
name: 'Sunday'
}
];
this.abrWdays = {};
for (let wday of this.wdays) {
let locale = this._(wday.name);
this.abrWdays[wday.code] = locale.substr(0, 3);
wday.abr = locale.substr(0, 1);
}
this.$http.get(`/api/Zones/${this.$stateParams.id}/exclusions`)
.then(res => this.$.exclusions = res.data);
this.path = `/api/Zones/${this.$stateParams.id}/events`;
this.refresh(); this.refresh();
} }
refresh() { refresh() {
this.$http.get(this.path) let data = {};
.then(res => this.$.data = res.data); this.$q.all([
this.$http.get(this.path)
.then(res => data.events = res.data),
this.$http.get(this.exclusionsPath)
.then(res => data.exclusions = res.data)
]).finally(() => {
this.$.data = data;
});
} }
formatWdays(weekDays) { formatWdays(weekDays) {
@ -55,16 +29,42 @@ class Controller extends Section {
let abrWdays = []; let abrWdays = [];
for (let wday of weekDays.split(',')) for (let wday of weekDays.split(','))
abrWdays.push(this.abrWdays[wday]); abrWdays.push(this.vnWeekDays.map[wday].localeAbr);
return abrWdays.length < 7 return abrWdays.length < 7
? abrWdays.join(', ') ? abrWdays.join(', ')
: this._('Everyday'); : this._('Everyday');
} }
onEdit(row, event) { onSelection(days, type, weekday, data) {
if (event.defaultPrevented) return; if (this.editMode == 'include') {
let dayData = data[0] || {};
let event = dayData.events && dayData.events[0];
if (event)
this.edit(event);
else
this.create(days, type, weekday);
} else {
let exclusions = [];
for (let dayData of data) {
if (dayData.exclusion)
exclusions.push(dayData.exclusion.id);
}
if (exclusions.length)
this.exclusionDelete(exclusions);
else
this.exclusionCreate(days);
}
}
onEditClick(row, event) {
if (event.defaultPrevented) return;
this.edit(row);
}
edit(row) {
this.isNew = false; this.isNew = false;
if (row.from && !row.to) if (row.from && !row.to)
@ -86,67 +86,74 @@ class Controller extends Section {
this.$.dialog.show(); this.$.dialog.show();
} }
onCreate($day, $type, $weekday) { create(days, type, weekday) {
this.isNew = true; this.isNew = true;
this.eventType = $type == 'day' ? 'day' : 'indefinitely'; this.eventType = type == 'day' ? 'day' : 'indefinitely';
if ($type == 'weekday') { if (type == 'weekday') {
let wdays = []; let wdays = [];
let index = $weekday - 1; let code = this.vnWeekDays.days[weekday].code;
if (index < 0) index = 7 - index; wdays[code] = true;
wdays[this.wdays[index].code] = true;
this.selected = {wdays}; this.selected = {wdays};
} else } else
this.selected = {from: $day[0]}; this.selected = {from: days[0]};
this.$.dialog.show(); this.$.dialog.show();
} }
onSave(response) { onSave(response) {
if (response != 'ACCEPT') return; if (response == 'ACCEPT') {
let selected = this.selected;
let selected = this.selected; if (this.eventType == 'indefinitely') {
selected.from = null;
if (this.eventType == 'indefinitely') { selected.to = null;
selected.from = null;
selected.to = null;
}
if (this.eventType != 'day') {
let weekDays = [];
for (let wday in selected.wdays) {
if (selected.wdays[wday])
weekDays.push(wday);
} }
selected.weekDays = weekDays.join(','); if (this.eventType != 'day') {
} else { let weekDays = [];
selected.to = null;
selected.weekDays = ''; for (let wday in selected.wdays) {
if (selected.wdays[wday])
weekDays.push(wday);
}
selected.weekDays = weekDays.join(',');
} else {
selected.to = null;
selected.weekDays = '';
}
let req;
if (this.isNew)
req = this.$http.post(this.path, selected);
else
req = this.$http.put(`${this.path}/${selected.id}`, selected);
req.then(() => {
this.selected = null;
this.isNew = null;
this.$.dialog.hide();
this.refresh();
});
return false;
} else if (response == 'DELETE') {
this.onDelete(this.selected.id);
return false;
} }
let req;
if (this.isNew)
req = this.$http.post(this.path, selected);
else
req = this.$http.put(`${this.path}/${selected.id}`, selected);
req.then(() => {
this.selected = null;
this.isNew = null;
this.$.dialog.hide();
this.refresh();
});
return false;
} }
onDelete(id, event) { onDeleteClick(id, event) {
if (event.defaultPrevented) return;
event.preventDefault(); event.preventDefault();
this.$.confirm.show(); this.onDelete(id);
}
onDelete(id) {
this.deleteId = id; this.deleteId = id;
this.$.confirm.show();
} }
delete(response) { delete(response) {
@ -156,9 +163,33 @@ class Controller extends Section {
.then(() => { .then(() => {
this.refresh(); this.refresh();
this.deleteId = null; this.deleteId = null;
this.$.dialog.hide();
}); });
} }
exclusionCreate(days) {
let exclusions = days.map(day => {
return {day};
});
this.$http.post(this.exclusionsPath, exclusions)
.then(() => this.refresh());
}
exclusionDelete(ids) {
let promises = [];
for (let id of ids) {
promises.push(
this.$http.delete(`${this.exclusionsPath}/${id}`)
);
}
this.$q.all(promises)
.then(() => this.refresh());
}
} }
Controller.$inject = ['$element', '$scope', 'vnWeekDays'];
ngModule.component('vnZoneEvents', { ngModule.component('vnZoneEvents', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -0,0 +1,4 @@
Edit mode: Modo de edición
Include: Incluir
Exclude: Excluir
Events: Eventos

View File

@ -1,28 +0,0 @@
@import "effects";
vn-zone-events {
.week-days {
margin-top: $spacing-sm;
margin-bottom: $spacing-md;
text-align: center;
& > span {
@extend %clickable;
border-radius: 50%;
padding: .4em;
margin: .2em;
display: inline-flex;
width: 1.5em;
height: 1.5em;
justify-content: center;
align-items: center;
outline: none;
background-color: rgba(0, 0, 0, .05);
&.marked {
background: $color-main;
color: $color-font-dark;
}
}
}
}

View File

@ -1,30 +0,0 @@
<div class="main-with-right-menu">
<vn-data-viewer
data="data"
is-loading="!data"
class="vn-w-xs">
<vn-card>
<vn-table>
<vn-tbody>
<vn-tr ng-repeat="row in data | orderBy:'day'">
<vn-td>{{::row.day | date:'dd/MM/yyyy'}}</vn-td>
<vn-td shrink>
<vn-icon-button
icon="delete"
translate-attr="{title: 'Delete'}"
ng-click="$ctrl.onDelete(row.id)">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
</div>
<vn-side-menu side="right">
<vn-zone-calendar
events="events"
exclusions="data"
on-selection="$ctrl.onCreate($days)">
</vn-zone-calendar>
</vn-side-menu>

View File

@ -1,35 +0,0 @@
import ngModule from '../module';
import Section from 'core/lib/section';
class Controller extends Section {
constructor($el, $, $t, $http, $state) {
super($el, $, $t, $http, $state);
this.$http.get(`/api/Zones/${this.$stateParams.id}/events`)
.then(res => this.$.events = res.data);
this.path = `/api/Zones/${this.$stateParams.id}/exclusions`;
this.refresh();
}
refresh() {
this.$http.get(this.path)
.then(res => this.$.data = res.data);
}
onCreate($days) {
this.$http.post(this.path, {day: $days[0]})
.then(() => this.refresh());
}
onDelete(id) {
if (!id) return;
this.$http.delete(`${this.path}/${id}`)
.then(() => this.refresh());
}
}
ngModule.component('vnZoneExclusions', {
template: require('./index.html'),
controller: Controller
});

View File

@ -12,6 +12,5 @@ import './basic-data';
import './warehouses'; import './warehouses';
import './events'; import './events';
import './calendar'; import './calendar';
import './exclusions';
import './location'; import './location';
import './calendar'; import './calendar';

View File

@ -1,6 +1,6 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="/api/Zones/{{$ctrl.$stateParams.id}}/getLeaves" url="/api/Zones/{{$ctrl.$params.id}}/getLeaves"
filter="::$ctrl.filter"> filter="::$ctrl.filter">
</vn-crud-model> </vn-crud-model>
<div class="vn-w-md"> <div class="vn-w-md">
@ -19,6 +19,7 @@
ng-model="item.selected" ng-model="item.selected"
on-change="$ctrl.onSelection(value, item)" on-change="$ctrl.onSelection(value, item)"
triple-state="true" triple-state="true"
ng-click="$event.preventDefault()"
label="{{::item.name}}"> label="{{::item.name}}">
</vn-check> </vn-check>
</vn-treeview> </vn-treeview>

View File

@ -1,8 +1,8 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'core/lib/section'; import Component from 'core/lib/component';
import './style.scss'; import './style.scss';
class Controller extends Section { class Controller extends Component {
onSearch(params) { onSearch(params) {
this.$.model.applyFilter({}, params).then(() => { this.$.model.applyFilter({}, params).then(() => {
const data = this.$.model.data; const data = this.$.model.data;

View File

@ -8,8 +8,7 @@
{"state": "zone.card.basicData", "icon": "settings"}, {"state": "zone.card.basicData", "icon": "settings"},
{"state": "zone.card.location", "icon": "my_location"}, {"state": "zone.card.location", "icon": "my_location"},
{"state": "zone.card.warehouses", "icon": "home"}, {"state": "zone.card.warehouses", "icon": "home"},
{"state": "zone.card.events", "icon": "today"}, {"state": "zone.card.events", "icon": "today"}
{"state": "zone.card.exclusions", "icon": "block"}
], ],
"routes": [ "routes": [
{ {
@ -65,11 +64,6 @@
"state": "zone.card.events", "state": "zone.card.events",
"component": "vn-zone-events", "component": "vn-zone-events",
"description": "Calendar" "description": "Calendar"
}, {
"url": "/exclusions",
"state": "zone.card.exclusions",
"component": "vn-zone-exclusions",
"description": "Exclusions"
}, { }, {
"url": "/location?q", "url": "/location?q",
"state": "zone.card.location", "state": "zone.card.location",

View File

@ -1,11 +1,11 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'core/lib/section'; import Component from 'core/lib/component';
class Controller extends Section { class Controller extends Component {
constructor($el, $, $t, $http, $state) { constructor($element, $) {
super($el, $, $t, $http, $state); super($element, $);
this.path = `/api/Zones/${this.$stateParams.id}/warehouses`; this.path = `/api/Zones/${this.$params.id}/warehouses`;
this.refresh(); this.refresh();
} }

View File

@ -1,10 +1,14 @@
<vn-crud-model auto-load="true" <vn-crud-model vn-id="model" auto-load="true" auto-save="true"
vn-id="model"
url="claim/api/ClaimEnds" url="claim/api/ClaimEnds"
filter="$ctrl.filter" filter="$ctrl.filter"
data="$ctrl.salesClaimed"> data="$ctrl.salesClaimed">
</vn-crud-model> </vn-crud-model>
<vn-crud-model auto-load="true"
url="/claim/api/ClaimDestinations"
data="claimDestinations">
</vn-crud-model>
<vn-card class="vn-mb-md vn-pa-lg vn-w-lg" style="text-align: right" <vn-card class="vn-mb-md vn-pa-lg vn-w-lg" style="text-align: right"
ng-if="$ctrl.salesClaimed.length > 0"> ng-if="$ctrl.salesClaimed.length > 0">
<vn-label-value label="Total claimed" <vn-label-value label="Total claimed"
@ -28,7 +32,6 @@
translate-attr="{title: 'Imports ticket lines'}"> translate-attr="{title: 'Imports ticket lines'}">
</vn-button> </vn-button>
<vn-range <vn-range
vn-one
label="Responsability" label="Responsability"
min-label="Company" min-label="Company"
max-label="Sales/Client" max-label="Sales/Client"
@ -82,14 +85,12 @@
</span> </span>
</vn-td> </vn-td>
<vn-td> <vn-td>
<vn-autocomplete vn-one <vn-autocomplete vn-one id="claimDestinationFk"
id="claimDestinationFk"
ng-model="saleClaimed.claimDestinationFk" ng-model="saleClaimed.claimDestinationFk"
url="/claim/api/ClaimDestinations" data="claimDestinations"
fields="['id','description']" fields="['id','description']"
value-field="id" value-field="id"
show-field="description" show-field="description">
on-change="$ctrl.setClaimDestination(saleClaimed.id, value)">
</vn-autocomplete> </vn-autocomplete>
</vn-td> </vn-td>
<vn-td>{{::saleClaimed.sale.ticket.landed | date: 'dd/MM/yyyy'}}</vn-td> <vn-td>{{::saleClaimed.sale.ticket.landed | date: 'dd/MM/yyyy'}}</vn-td>

View File

@ -19,7 +19,8 @@ class Controller {
} }
} }
}, },
{relation: 'claimBeggining'} {relation: 'claimBeggining'},
{relation: 'claimDestination'}
] ]
}; };
this.resolvedState = 3; this.resolvedState = 3;
@ -82,16 +83,6 @@ class Controller {
this.calculateTotals(); this.calculateTotals();
} }
setClaimDestination(id, claimDestinationFk) {
if (claimDestinationFk) {
let params = {id: id, claimDestinationFk: claimDestinationFk};
let query = `claim/api/ClaimEnds/`;
this.$http.patch(query, params).then(() => {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
});
}
}
calculateTotals() { calculateTotals() {
this.claimedTotal = 0; this.claimedTotal = 0;
this.salesClaimed.forEach(sale => { this.salesClaimed.forEach(sale => {

View File

@ -82,17 +82,6 @@ describe('claim', () => {
}); });
}); });
describe('setClaimDestination(id, claimDestinationFk)', () => {
it('should make a patch and call refresh and showSuccess', () => {
spyOn(controller.vnApp, 'showSuccess');
$httpBackend.expectPATCH(`claim/api/ClaimEnds/`).respond({});
controller.setClaimDestination(1, 1);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
});
});
describe('calculateTotals()', () => { describe('calculateTotals()', () => {
it('should calculate the total price of the items claimed', () => { it('should calculate the total price of the items claimed', () => {
controller.salesClaimed = [ controller.salesClaimed = [

View File

@ -29,10 +29,9 @@
</vn-textfield> </vn-textfield>
<vn-none> <vn-none>
<vn-icon-button <vn-icon-button
pointer
class="vn-my-md"
vn-tooltip="Remove contact" vn-tooltip="Remove contact"
icon="delete" icon="delete"
tabindex="-1"
ng-click="model.remove($index)"> ng-click="model.remove($index)">
</vn-icon-button> </vn-icon-button>
</vn-none> </vn-none>

View File

@ -13,6 +13,10 @@ class Controller {
callback.call(this); callback.call(this);
} }
get client() {
return this._client;
}
set client(value) { set client(value) {
this._client = value; this._client = value;
@ -32,10 +36,6 @@ class Controller {
}; };
} }
get client() {
return this._client;
}
set quicklinks(value = {}) { set quicklinks(value = {}) {
this._quicklinks = Object.assign(value, this._quicklinks); this._quicklinks = Object.assign(value, this._quicklinks);
} }

View File

@ -52,24 +52,31 @@ class Controller {
get freeLineIndex() { get freeLineIndex() {
let lines = this.$scope.model.data; let lines = this.$scope.model.data;
let currentDate = new Date(); let minDate = new Date();
currentDate.setHours(0, 0, 0); minDate.setHours(0, 0, 0, 0);
let maxDate = new Date();
maxDate.setHours(23, 59, 59, 59);
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
let isFutureDate = new Date(lines[i].date) >= currentDate; const dated = new Date(lines[i].date);
if (isFutureDate) let isForFuture = dated > maxDate;
let isForToday = (dated >= minDate && dated <= maxDate);
if (isForFuture || isForToday)
return i; return i;
} }
} }
get onPreparationLineIndex() { get onPreparationLineIndex() {
let lines = this.$scope.model.data; let lines = this.$scope.model.data;
for (let i = this.freeLineIndex; i >= 0; i--) { for (let i = this.freeLineIndex; i >= 0; i--) {
let line = lines[i]; let line = lines[i];
let currentDate = new Date(); let currentDate = new Date();
currentDate.setHours(0, 0, 0); currentDate.setHours(0, 0, 0, 0);
let isPastDate = new Date(lines[i].date) < currentDate; let isPastDate = new Date(lines[i].date) < currentDate;
let isPicked = line.alertLevel == 1 && line.isPicked; let isPicked = line.alertLevel == 1 && line.isPicked;
@ -95,6 +102,7 @@ class Controller {
let selectedTicketLineIndex = this.givenTicketIndex; let selectedTicketLineIndex = this.givenTicketIndex;
let lineIndex = this.onPreparationLineIndex; let lineIndex = this.onPreparationLineIndex;
let lines = body.querySelector('vn-tbody').children; let lines = body.querySelector('vn-tbody').children;
if (lineIndex == undefined || !lines.length) return; if (lineIndex == undefined || !lines.length) return;
@ -120,7 +128,6 @@ class Controller {
offsetTop = onPreparationLine.offsetTop - headerHeight; offsetTop = onPreparationLine.offsetTop - headerHeight;
this.$window.scrollTo(0, offsetTop); this.$window.scrollTo(0, offsetTop);
this.ticketFk = null; this.ticketFk = null;
} }

View File

@ -8,12 +8,11 @@
<div class="content-block"> <div class="content-block">
<vn-card class="vn-pa-md vn-w-sm"> <vn-card class="vn-pa-md vn-w-sm">
<vn-horizontal style="align-items: center;"> <vn-horizontal style="align-items: center;">
<vn-searchbar <vn-searchbar vn-focus
panel="vn-item-search-panel" panel="vn-item-search-panel"
on-search="$ctrl.onSearch($params)" on-search="$ctrl.onSearch($params)"
info="Search items by id, name or barcode" info="Search items by id, name or barcode"
suggested-filter="{isActive: true}" suggested-filter="{isActive: true}">
vn-focus>
</vn-searchbar> </vn-searchbar>
<vn-icon-menu <vn-icon-menu
vn-id="more-button" vn-id="more-button"

View File

@ -16,7 +16,7 @@
</vn-textfield> </vn-textfield>
<vn-autocomplete <vn-autocomplete
vn-one vn-one
ng-model="filter.atenderFk" ng-model="filter.attenderFk"
url="/client/api/Clients/activeWorkersWithRole" url="/client/api/Clients/activeWorkersWithRole"
search-function="{firstName: $search}" search-function="{firstName: $search}"
value-field="id" value-field="id"

View File

@ -1,121 +1,116 @@
<vn-crud-model <vn-crud-model auto-load="true"
vn-id="model" vn-id="model"
url="/ticket/api/TicketRequests/filter" url="/ticket/api/TicketRequests/filter"
limit="20" limit="20"
data="requests" data="requests"
order="isOk ASC" order="shipped DESC, isOk ASC">
auto-load="false">
</vn-crud-model> </vn-crud-model>
<form name="form"> <form name="form">
<div class="vn-ma-md"> <div class="vn-ma-md">
<vn-card class="vn-pa-md vn-list"> <vn-card class="vn-pa-md vn-list">
<vn-horizontal> <vn-searchbar vn-one vn-focus
<vn-searchbar auto-load="false"
auto-load="false" panel="vn-request-search-panel"
panel="vn-request-search-panel" on-search="$ctrl.onSearch($params)"
on-search="$ctrl.onSearch($params)" info="Search request by id or alias"
info="Search request by id or alias" suggested-filter="$ctrl.filter.where">
vn-one </vn-searchbar>
vn-focus>
</vn-searchbar>
</vn-horizontal>
</vn-card> </vn-card>
<vn-card class="vn-my-md"> <vn-data-viewer model="model" class="vn-my-md">
<vn-table model="model" auto-load="false"> <vn-card>
<vn-thead> <vn-table model="model">
<vn-tr> <vn-thead>
<vn-th field="ticketFk" number>Ticket ID</vn-th> <vn-tr>
<vn-th field="shipped">Shipped</vn-th> <vn-th field="ticketFk" number>Ticket ID</vn-th>
<vn-th field="warehouse">Warehouse</vn-th> <vn-th field="shipped">Shipped</vn-th>
<vn-th field="salesPersonNickname">SalesPerson</vn-th> <vn-th field="warehouse">Warehouse</vn-th>
<vn-th field="description">Description</vn-th> <vn-th field="salesPersonNickname">SalesPerson</vn-th>
<vn-th field="quantity" number editable>Quantity</vn-th> <vn-th field="description">Description</vn-th>
<vn-th field="price" number>Price</vn-th> <vn-th field="quantity" number editable>Requested</vn-th>
<vn-th field="atenderNickname">Atender</vn-th> <vn-th field="price" number>Price</vn-th>
<vn-th field="itemFk">Item</vn-th> <vn-th field="atenderNickname">Atender</vn-th>
<vn-th field="description">Concept</vn-th> <vn-th field="itemFk">Item</vn-th>
<vn-th field="saleQuantity" number>Sale quantity</vn-th> <vn-th field="saleQuantity">Achieved</vn-th>
<vn-th field="isOk">State</vn-th> <vn-th field="description">Concept</vn-th>
</vn-tr> <vn-th field="isOk">State</vn-th>
</vn-thead> </vn-tr>
<vn-tbody> </vn-thead>
<vn-tr ng-repeat="request in requests"> <vn-tbody>
<vn-td number> <vn-tr ng-repeat="request in requests">
<span class="link" <vn-td number>
ng-click="$ctrl.showTicketDescriptor($event, request.ticketFk)"> <span class="link"
{{request.ticketFk}} ng-click="$ctrl.showTicketDescriptor($event, request.ticketFk)">
</span> {{request.ticketFk}}
</vn-td> </span>
<vn-td> </vn-td>
<span title="{{::request.shipped | date: 'dd/MM/yyyy'}}" <vn-td>
class="chip {{$ctrl.compareDate(request.shipped)}}"> <span title="{{::request.shipped | date: 'dd/MM/yyyy'}}"
{{::request.shipped | date: 'dd/MM/yyyy'}} class="chip {{$ctrl.compareDate(request.shipped)}}">
</span> {{::request.shipped | date: 'dd/MM/yyyy'}}
</vn-td> </span>
<vn-td>{{::request.warehouse}}</vn-td> </vn-td>
<vn-td> <vn-td>{{::request.warehouse}}</vn-td>
<span <vn-td>
<span
class="link"
ng-click="$ctrl.showWorkerDescriptor($event, request.salesPersonFk)">
{{::request.salesPersonNickname}}
</span>
</vn-td>
<vn-td title="{{::request.description}}">{{::request.description}}</vn-td>
<vn-td number>{{::request.quantity}}</vn-td>
<vn-td number>{{::request.price | currency: 'EUR':2}}</vn-td>
<vn-td>
<span
class="link" class="link"
ng-click="$ctrl.showWorkerDescriptor($event, request.salesPersonFk)"> ng-click="$ctrl.showWorkerDescriptor($event, request.attenderFk)">
{{::request.salesPersonNickname}} {{::request.atenderNickname}}
</span> </span>
</vn-td> </vn-td>
<vn-td title="{{::request.description}}">{{::request.description}}</vn-td> <vn-td-editable disabled="request.isOk != null" number>
<vn-td number>{{::request.quantity}}</vn-td> <text>{{request.itemFk}}</text>
<vn-td number>{{::request.price | currency: 'EUR':2}}</vn-td> <field>
<vn-td> <vn-input-number class="dense" vn-focus
<span ng-model="request.itemFk">
class="link" </vn-input-number>
ng-click="$ctrl.showWorkerDescriptor($event, request.atenderFk)"> </field>
{{::request.atenderNickname}} </vn-td-editable>
</span> <vn-td-editable disabled="!request.itemFk || request.isOk != null" number>
</vn-td> <text number>{{request.saleQuantity}}</text>
<vn-td-editable disabled="request.isOk === 0" number> <field>
<text>{{request.itemFk}}</text> <vn-input-number class="dense" vn-focus
<field> ng-model="request.saleQuantity"
<vn-input-number on-change="$ctrl.changeQuantity(request)">
ng-model="request.itemFk" </vn-input-number>
on-change="$ctrl.confirmRequest(request)"> </field>
</vn-input-number> </vn-td-editable>
</field> <vn-td>
</vn-td-editable> <span
<vn-td> class="link"
<span ng-click="$ctrl.showItemDescriptor($event, request.itemFk)"
class="link" title="{{request.itemDescription}}">
ng-click="$ctrl.showItemDescriptor($event, request.itemFk)" {{request.itemDescription}}
title="{{::request.itemDescription}}"> </span>
{{::request.itemDescription}} </vn-td>
</span> <vn-td>{{$ctrl.getState(request.isOk)}}</vn-td>
</vn-td> <vn-td>
<vn-td-editable disabled="request.isOk === 0" number> <vn-icon
<text number>{{request.saleQuantity}}</text> ng-if="request.response.length"
<field> ranslate-attr="{title: request.response}"
<vn-input-number icon="insert_drive_file">
ng-model="request.saleQuantity" </vn-icon>
on-change="$ctrl.changeQuantity(request)"> <vn-icon-button
</vn-input-number> ng-if="request.isOk != 0"
</field> icon="thumb_down"
</vn-td-editable> ng-click="$ctrl.showDenyReason($event, request)"
<vn-td>{{::$ctrl.getState(request.isOk)}}</vn-td> translate-attr="{title: 'Discard'}">
<vn-td> </vn-icon-button>
<vn-icon </vn-td>
ng-if="request.response.length" </vn-tr>
vn-tooltip="{{request.response}}" </vn-tbody>
icon="insert_drive_file"> </vn-table>
</vn-icon> </vn-card>
<vn-icon-button </vn-data-viewer>
ng-if="request.isOk != 0"
number
icon="thumb_down"
ng-click="$ctrl.showDenyReason($event, request.id)"
vn-tooltip="Discard">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
<vn-pagination model="model"></vn-pagination>
</div> </div>
</form> </form>
<vn-worker-descriptor-popover <vn-worker-descriptor-popover
@ -129,21 +124,17 @@
</vn-item-descriptor-popover> </vn-item-descriptor-popover>
<vn-dialog <vn-dialog
vn-id="denyReason" vn-id="denyReason"
class="modal-form"> on-response="$ctrl.denyRequest(response)">
<tpl-body> <tpl-body>
<vn-horizontal class="header"> <h5 class="vn-pa-md" translate>Specify the reasons to deny this request</h5>
<h5><span translate>Indicate the reasons to deny this request</span></h5>
</vn-horizontal>
<vn-horizontal class="vn-pa-md"> <vn-horizontal class="vn-pa-md">
<vn-textarea <vn-textarea
ng-model="$ctrl.denyObservation"> ng-model="$ctrl.denyObservation">
</vn-textarea> </vn-textarea>
</vn-horizontal> </vn-horizontal>
<vn-horizontal class="vn-pa-md">
<vn-button
label="Save"
ng-click="$ctrl.denyRequest()">
</vn-button>
</vn-horizontal>
</tpl-body> </tpl-body>
<tpl-buttons>
<input type="button" response="CANCEL" translate-attr="{value: 'Cancel'}"/>
<button response="ACCEPT" translate>Save</button>
</tpl-buttons>
</vn-dialog> </vn-dialog>

View File

@ -1,16 +1,28 @@
import ngModule from '../module'; import ngModule from '../module';
import Component from 'core/lib/component';
import './style.scss'; import './style.scss';
export default class Controller { export default class Controller extends Component {
constructor($, vnApp, $translate, $http, $state, $stateParams) { constructor($element, $) {
this.$state = $state; super($element, $);
this.$stateParams = $stateParams;
this.$http = $http; if (!this.$state.q) {
this.$ = $; const today = new Date();
this.vnApp = vnApp; today.setHours(23, 59, 59, 59);
this._ = $translate;
if (!$stateParams.q) const lastWeek = new Date();
this.filter = {isOk: false, mine: true}; lastWeek.setHours(0, 0, 0, 0);
lastWeek.setDate(lastWeek.getDate() - 7);
this.filter = {
where: {
isOk: false,
mine: true,
from: lastWeek,
to: today
}
};
}
} }
$postLink() { $postLink() {
@ -21,7 +33,7 @@ export default class Controller {
getState(isOk) { getState(isOk) {
if (isOk === null) if (isOk === null)
return 'Nueva'; return 'Nueva';
else if (isOk === -1 || isOk === 1) else if (isOk === -1 || isOk)
return 'Aceptada'; return 'Aceptada';
else else
return 'Denegada'; return 'Denegada';
@ -34,14 +46,12 @@ export default class Controller {
quantity: request.saleQuantity quantity: request.saleQuantity
}; };
let endpoint = `/api/TicketRequests/${request.id}/confirm`; let query = `/api/TicketRequests/${request.id}/confirm`;
this.$http.post(query, params).then(res => {
request.itemDescription = res.data.concept;
request.isOk = true;
this.$http.post(endpoint, params).then(() => { this.vnApp.showSuccess(this.$t('Data saved!'));
this.vnApp.showSuccess(this._.instant('Data saved!'));
this.$.model.refresh();
}).catch( e => {
this.$.model.refresh();
throw e;
}); });
} }
} }
@ -55,11 +65,8 @@ export default class Controller {
let endpoint = `/api/Sales/${request.saleFk}/`; let endpoint = `/api/Sales/${request.saleFk}/`;
this.$http.patch(endpoint, params).then(() => { this.$http.patch(endpoint, params).then(() => {
this.vnApp.showSuccess(this._.instant('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
}).catch( e => { }).then(() => this.confirmRequest(request));
this.$.model.refresh();
throw e;
});
} else } else
this.confirmRequest(request); this.confirmRequest(request);
} }
@ -86,7 +93,7 @@ export default class Controller {
} }
showDenyReason(event, requestId) { showDenyReason(event, requestId) {
this.denyRequestId = requestId; this.selectedRequest = requestId;
this.$.denyReason.parent = event.target; this.$.denyReason.parent = event.target;
this.$.denyReason.show(); this.$.denyReason.show();
document.querySelector('vn-item-request vn-textarea textArea').focus(); document.querySelector('vn-item-request vn-textarea textArea').focus();
@ -96,17 +103,21 @@ export default class Controller {
delete this.denyRequestId; delete this.denyRequestId;
} }
denyRequest() { denyRequest(response) {
if (response !== 'ACCEPT') return;
let params = { let params = {
observation: this.denyObservation observation: this.denyObservation
}; };
let endpoint = `/api/TicketRequests/${this.denyRequestId}/deny`; let query = `/api/TicketRequests/${this.selectedRequest.id}/deny`;
this.$http.post(query, params).then(res => {
const request = res.data;
this.selectedRequest.isOk = request.isOk;
this.selectedRequest.attenderFk = request.attenderFk;
this.selectedRequest.response = request.response;
this.$http.post(endpoint, params).then(() => { this.vnApp.showSuccess(this.$t('Data saved!'));
this.vnApp.showSuccess(this._.instant('Data saved!'));
this.$.model.refresh();
this.$.denyReason.hide();
this.denyObservation = null; this.denyObservation = null;
}); });
} }
@ -140,8 +151,6 @@ export default class Controller {
} }
} }
Controller.$inject = ['$scope', 'vnApp', '$translate', '$http', '$state', '$stateParams'];
ngModule.component('vnItemRequest', { ngModule.component('vnItemRequest', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller controller: Controller

View File

@ -4,6 +4,7 @@ import crudModel from 'core/mocks/crud-model';
describe('Item', () => { describe('Item', () => {
describe('Component vnItemRequest', () => { describe('Component vnItemRequest', () => {
let $scope; let $scope;
let $element;
let controller; let controller;
let $httpBackend; let $httpBackend;
@ -16,9 +17,15 @@ describe('Item', () => {
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$scope.model = crudModel; $scope.model = crudModel;
$scope.denyReason = {hide: () => {}}; $scope.denyReason = {hide: () => {}};
controller = $componentController('vnItemRequest', {$scope: $scope}); $element = angular.element('<vn-item-request></vn-item-request>');
controller = $componentController('vnItemRequest', {$element, $scope});
})); }));
afterAll(() => {
$scope.$destroy();
$element.remove();
});
describe('getState()', () => { describe('getState()', () => {
it(`should return an string depending to the isOK value`, () => { it(`should return an string depending to the isOK value`, () => {
let isOk = null; let isOk = null;
@ -53,15 +60,15 @@ describe('Item', () => {
let model = controller.$.model; let model = controller.$.model;
spyOn(model, 'refresh'); spyOn(model, 'refresh');
const expectedResult = {concept: 'Melee Weapon'};
let request = {itemFk: 1, saleQuantity: 1, id: 1}; let request = {itemFk: 1, saleQuantity: 1, id: 1};
$httpBackend.when('POST', `/api/TicketRequests/${request.id}/confirm`).respond(); $httpBackend.when('POST', `/api/TicketRequests/${request.id}/confirm`).respond(expectedResult);
$httpBackend.expect('POST', `/api/TicketRequests/${request.id}/confirm`).respond(); $httpBackend.expect('POST', `/api/TicketRequests/${request.id}/confirm`).respond(expectedResult);
controller.confirmRequest(request); controller.confirmRequest(request);
$httpBackend.flush(); $httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
expect($scope.model.refresh).toHaveBeenCalledWith();
}); });
}); });
@ -110,20 +117,17 @@ describe('Item', () => {
describe('denyRequest()', () => { describe('denyRequest()', () => {
it(`should perform a query and call vnApp.showSuccess(), refresh(), hide() and set denyObservation to null in the controller`, () => { it(`should perform a query and call vnApp.showSuccess(), refresh(), hide() and set denyObservation to null in the controller`, () => {
spyOn(controller.vnApp, 'showSuccess'); spyOn(controller.vnApp, 'showSuccess');
let model = controller.$.model;
spyOn(model, 'refresh');
spyOn(controller.$.denyReason, 'hide');
controller.denyRequestId = 1; const request = {id: 1};
const expectedResult = {isOk: false, attenderFk: 106, response: 'Denied!'};
controller.selectedRequest = request;
$httpBackend.when('POST', `/api/TicketRequests/${controller.denyRequestId}/deny`).respond(); $httpBackend.when('POST', `/api/TicketRequests/${request.id}/deny`).respond(expectedResult);
$httpBackend.expect('POST', `/api/TicketRequests/${controller.denyRequestId}/deny`).respond(); $httpBackend.expect('POST', `/api/TicketRequests/${request.id}/deny`).respond(expectedResult);
controller.denyRequest(); controller.denyRequest('ACCEPT');
$httpBackend.flush(); $httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
expect($scope.model.refresh).toHaveBeenCalledWith();
expect($scope.denyReason.hide).toHaveBeenCalledWith();
}); });
}); });
}); });

View File

@ -1,5 +1,6 @@
Discard: Descartar Discard: Descartar
Indicate the reasons to deny this request: Indique las razones para descartar esta peticion Specify the reasons to deny this request: Especifica las razones para descartar la petición
Buy requests: Peticiones de compra Buy requests: Peticiones de compra
Search request by id or alias: Buscar peticiones por identificador o alias Search request by id or alias: Buscar peticiones por identificador o alias
Sale quantity: C. conseguida Requested: Solicitado
Achieved: Conseguido

View File

@ -28,12 +28,6 @@ module.exports = Self => {
fields: ['id', 'packages', 'warehouseFk', 'nickname', 'clientFk', 'priority', 'addressFk'], fields: ['id', 'packages', 'warehouseFk', 'nickname', 'clientFk', 'priority', 'addressFk'],
order: 'priority', order: 'priority',
include: [ include: [
{
relation: 'client',
scope: {
fields: ['id', 'street', 'postcode'],
}
},
{ {
relation: 'state', relation: 'state',
scope: { scope: {

View File

@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Route getSelectedItems() should return the selected items 1`] = `
Array [
Object {
"checked": true,
"id": 1,
},
Object {
"checked": true,
"id": 3,
},
Object {
"checked": true,
"id": 5,
},
]
`;

View File

@ -1,104 +1,105 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="/api/Routes/{{$ctrl.$stateParams.id}}/getTickets" url="api/Routes/{{$ctrl.$stateParams.id}}/getTickets"
order="priority ASC" order="priority ASC"
data="$ctrl.tickets" data="$ctrl.tickets"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<form name="form"> <vn-data-viewer model="model">
<vn-card class="vn-pa-lg"> <form name="form">
<vn-tool-bar class="vn-mb-sm"> <vn-card class="vn-pa-lg">
<vn-button <vn-tool-bar class="vn-mb-sm">
icon="icon-wand" <vn-button
ng-click="$ctrl.guessPriority()" icon="icon-wand"
vn-tooltip="Sort routes"> ng-click="$ctrl.guessPriority()"
</vn-button> vn-tooltip="Sort routes">
<vn-button </vn-button>
disabled="!$ctrl.isChecked" <vn-button
ng-click="$ctrl.goToBuscaman()" disabled="!$ctrl.isChecked"
vn-tooltip="Open buscaman" ng-click="$ctrl.goToBuscaman()"
tooltip-position="up" vn-tooltip="Open buscaman"
icon="icon-buscaman"> tooltip-position="up"
</vn-button> icon="icon-buscaman">
</vn-tool-bar> </vn-button>
<vn-icon-button </vn-tool-bar>
vn-tooltip="Load more" <vn-icon-button
ng-click="$ctrl.goToBuscaman()"> vn-tooltip="Load more"
</vn-icon-button> ng-click="$ctrl.goToBuscaman()">
<vn-table model="model" auto-load="false"> </vn-icon-button>
<vn-thead> <vn-table model="model" auto-load="false">
<vn-tr> <vn-thead>
<vn-th shrink> <vn-tr>
<vn-multi-check <vn-th shrink>
model="model"> <vn-multi-check
</vn-multi-check> model="model">
</vn-th> </vn-multi-check>
<vn-th>Order</vn-th> </vn-th>
<vn-th number>Ticket</vn-th> <vn-th>Order</vn-th>
<vn-th>Client</vn-th> <vn-th number>Ticket</vn-th>
<vn-th number shrink>Packages</vn-th> <vn-th>Client</vn-th>
<vn-th shrink></vn-th> <vn-th number shrink>Packages</vn-th>
<vn-th shrink>Warehouse</vn-th> <vn-th shrink></vn-th>
<vn-th shrink>PC</vn-th> <vn-th shrink>Warehouse</vn-th>
<vn-th>Street</vn-th> <vn-th expand>Postcode</vn-th>
<vn-th shrink></vn-th> <vn-th>Street</vn-th>
<vn-th shrink></vn-th> <vn-th shrink></vn-th>
</vn-tr> <vn-th shrink></vn-th>
</vn-thead> </vn-tr>
<vn-tbody> </vn-thead>
<vn-tr ng-repeat="ticket in $ctrl.tickets"> <vn-tbody>
<vn-td shrink> <vn-tr ng-repeat="ticket in $ctrl.tickets">
<vn-check <vn-td shrink>
ng-model="ticket.checked"> <vn-check
</vn-check> ng-model="ticket.checked">
</vn-td> </vn-check>
<vn-td> </vn-td>
<vn-input-number <vn-td>
on-change="$ctrl.setPriority(ticket.id, ticket.priority)" <vn-input-number
ng-model="ticket.priority" on-change="$ctrl.setPriority(ticket.id, ticket.priority)"
rule="Ticket"> ng-model="ticket.priority"
</vn-input-number> rule="Ticket">
</vn-td> </vn-input-number>
<vn-td number> </vn-td>
<span <vn-td number>
ng-click="$ctrl.showTicketDescriptor($event, ticket.id)" <span
class="link"> ng-click="$ctrl.showTicketDescriptor($event, ticket.id)"
{{ticket.id}} class="link">
</span> {{ticket.id}}
</vn-td> </span>
<vn-td> </vn-td>
<span <vn-td>
ng-click="$ctrl.showClientDescriptor($event, ticket.clientFk)" <span
class="link"> ng-click="$ctrl.showClientDescriptor($event, ticket.clientFk)"
{{ticket.nickname}} class="link">
</span> {{ticket.nickname}}
</vn-td> </span>
<vn-td number shrink>{{ticket.packages}}</vn-td> </vn-td>
<vn-td shrink>{{ticket.volume}}</vn-td> <vn-td number shrink>{{ticket.packages}}</vn-td>
<vn-td shrink>{{ticket.warehouse.name}}</vn-td> <vn-td shrink>{{ticket.volume}}</vn-td>
<vn-td shrink>{{ticket.client.postcode}}</vn-td> <vn-td shrink>{{ticket.warehouse.name}}</vn-td>
<vn-td expand title="{{ticket.client.street}}">{{ticket.client.street}}</vn-td> <vn-td number shrink>{{ticket.address.postalCode}}</vn-td>
<vn-td shrink> <vn-td expand title="{{ticket.address.street}}">{{ticket.address.street}}</vn-td>
<vn-icon-button <vn-td shrink>
ng-if="ticket.notes.length" <vn-icon-button
title="::{{ticket.notes[0].description}}" ng-if="ticket.notes.length"
icon="insert_drive_file"> title="::{{ticket.notes[0].description}}"
</vn-icon-button> icon="insert_drive_file">
</vn-td> </vn-icon-button>
<vn-td> </vn-td>
<vn-icon-button <vn-td>
translate-attr="::{title: 'Remove ticket'}" <vn-icon-button
icon="delete" translate-attr="::{title: 'Remove ticket'}"
ng-click="$ctrl.showDeleteConfirm(ticket.id)" icon="delete"
tabindex="-1"> ng-click="$ctrl.showDeleteConfirm(ticket.id)"
</vn-icon-button> tabindex="-1">
</vn-td> </vn-icon-button>
</vn-tr> </vn-td>
</vn-tbody> </vn-tr>
</vn-table> </vn-tbody>
</vn-card> </vn-table>
</form> </vn-card>
</form>
</vn-data-viewer>
<vn-ticket-descriptor-popover <vn-ticket-descriptor-popover
vn-id="ticketDescriptor"> vn-id="ticketDescriptor">
</vn-ticket-descriptor-popover> </vn-ticket-descriptor-popover>
@ -110,3 +111,71 @@
question="Delete ticket from route?" question="Delete ticket from route?"
on-response="$ctrl.removeTicketFromRoute(response)"> on-response="$ctrl.removeTicketFromRoute(response)">
</vn-confirm> </vn-confirm>
<vn-crud-model
vn-id="possibleTicketsModel"
url="api/Tickets"
filter="$ctrl.possibleTicketsFilter"
data="$ctrl.possibleTickets">
</vn-crud-model>
<vn-dialog
vn-id="possibleTicketsDialog"
on-response="$ctrl.setTicketsRoute(response)">
<tpl-body>
<section class="header vn-pa-md">
<h5><span translate>Tickets to add</span></h5>
</section>
<vn-data-viewer class="vn-pa-md" model="possibleTicketsModel">
<vn-table model="possibleTicketsModel" auto-load="false">
<vn-thead>
<vn-tr>
<vn-th shrink>
<vn-multi-check
model="possibleTicketsModel">
</vn-multi-check>
</vn-th>
<vn-th number>Ticket</vn-th>
<vn-th>Client</vn-th>
<vn-th number shrink>Packages</vn-th>
<vn-th shrink>Warehouse</vn-th>
<vn-th expand>Postcode</vn-th>
<vn-th>Address</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="ticket in $ctrl.possibleTickets">
<vn-td shrink>
<vn-check
ng-model="ticket.checked">
</vn-check>
</vn-td>
<vn-td number>{{ticket.id}}</vn-td>
<vn-td number>
<span
ng-click="$ctrl.showClientDescriptor($event, ticket.clientFk)"
class="link">
{{ticket.nickname}}
</span>
</vn-td>
<vn-td number shrink>{{ticket.packages}}</vn-td>
<vn-td expand>{{ticket.warehouse.name}}</vn-td>
<vn-td number shrink>{{ticket.address.postalCode}}</vn-td>
<vn-td expand title="{{ticket.address.street}}">{{ticket.address.street}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-data-viewer>
</tpl-body>
<tpl-buttons>
<input type="button" response="CANCEL" translate-attr="{value: 'Cancel'}"/>
<button response="ACCEPT" translate>Add</button>
</tpl-buttons>
</vn-dialog>
<vn-float-button
icon="add"
ng-click="$ctrl.openPossibleTicketsDialog()"
vn-tooltip="Add ticket"
vn-acl="delivery"
vn-acl-action="remove"
vn-bind="+"
fixed-bottom-right>
</vn-float-button>

View File

@ -2,12 +2,23 @@ import ngModule from '../module';
import './style.scss'; import './style.scss';
class Controller { class Controller {
constructor($stateParams, $, $translate, $http, vnApp) { constructor($stateParams, $scope, $translate, $http, vnApp, $filter) {
this.$translate = $translate; this.$translate = $translate;
this.$stateParams = $stateParams; this.$stateParams = $stateParams;
this.$ = $; this.$ = $scope;
this.$http = $http; this.$http = $http;
this.vnApp = vnApp; this.vnApp = vnApp;
this.$filter = $filter;
}
set route(value) {
this._route = value;
if (value)
this.buildPossibleTicketsFilter();
}
get route() {
return this._route;
} }
get isChecked() { get isChecked() {
@ -19,13 +30,37 @@ class Controller {
return false; return false;
} }
buildPossibleTicketsFilter() {
let minDate = new Date(this.route.finished);
minDate.setHours(0, 0, 0, 0);
let maxDate = new Date(this.route.finished);
maxDate.setHours(23, 59, 59, 59);
this.possibleTicketsFilter = {
where: {
zoneFk: this.route.zoneFk,
routeFk: null,
landed: {between: [minDate, maxDate]},
},
include: [
{
relation: 'warehouse',
scope: {
fields: ['name']
},
}, {
relation: 'address'
}
]
};
}
getHighestPriority() { getHighestPriority() {
let max = 0; let highestPriority = Math.max(...this.$.model.data.map(tag => {
this.$.model.data.forEach(tag => { return tag.priority;
if (tag.priority > max) }));
max = tag.priority; return highestPriority + 1;
});
return max + 1;
} }
setPriority(id, priority) { setPriority(id, priority) {
@ -37,16 +72,16 @@ class Controller {
}); });
} }
getCheckedLines() { getSelectedItems(items) {
let lines = []; const selectedItems = [];
let data = this.tickets;
if (data) { if (items) {
for (let i = 0; i < data.length; i++) { for (let i = 0; i < items.length; i++) {
if (data[i].checked) if (items[i].checked)
lines.push(data[i]); selectedItems.push(items[i]);
} }
} }
return lines; return selectedItems;
} }
goToBuscaman() { goToBuscaman() {
@ -54,7 +89,7 @@ class Controller {
let firstAddress = `46460 Av Espioca 100-46460 Silla`; let firstAddress = `46460 Av Espioca 100-46460 Silla`;
let addresses = firstAddress; let addresses = firstAddress;
let lines = this.getCheckedLines(); let lines = this.getSelectedItems(this.tickets);
let url = 'http://gps.buscalia.com/usuario/localizar.aspx?bmi=true&addr='; let url = 'http://gps.buscalia.com/usuario/localizar.aspx?bmi=true&addr=';
lines.forEach(line => { lines.forEach(line => {
@ -64,8 +99,8 @@ class Controller {
window.open(url + addresses, '_blank'); window.open(url + addresses, '_blank');
} }
showDeleteConfirm(ticket) { showDeleteConfirm(id) {
this.selectedTicket = ticket; this.selectedTicket = id;
this.$.confirm.show(); this.$.confirm.show();
} }
@ -109,14 +144,38 @@ class Controller {
this.$.clientDescriptor.show(); this.$.clientDescriptor.show();
event.preventDefault(); event.preventDefault();
} }
openPossibleTicketsDialog() {
this.$.possibleTicketsModel.refresh();
this.$.possibleTicketsDialog.show();
}
setTicketsRoute(response) {
if (response === 'ACCEPT') {
let tickets = this.getSelectedItems(this.possibleTickets);
for (let i = 0; i < tickets.length; i++) {
delete tickets[i].checked;
tickets[i].routeFk = this.route.id;
}
return this.$.possibleTicketsModel.save().then(() => {
this.$.model.data = this.$.model.data.concat(tickets);
});
}
return Promise.resolve();
}
} }
Controller.$inject = ['$stateParams', '$scope', '$translate', '$http', 'vnApp']; Controller.$inject = ['$stateParams', '$scope', '$translate', '$http', 'vnApp', '$filter'];
ngModule.component('vnRouteTickets', { ngModule.component('vnRouteTickets', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller,
require: { require: {
card: '^vnRouteCard' card: '^vnRouteCard'
}, },
controller: Controller bindings: {
route: '<'
}
}); });

View File

@ -0,0 +1,288 @@
import './index.js';
describe('Route', () => {
let controller;
let $httpBackend;
beforeEach(angular.mock.module('route', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
controller = $componentController('vnRouteTickets');
}));
describe('route setter/getter', () => {
it('should return the route id', () => {
controller.route = 2;
expect(controller.route).toEqual(2);
});
});
describe('isChecked getter', () => {
it('should return false if none of the tickets is checked or there are no tickets', () => {
expect(controller.isChecked).toBeFalsy();
});
it('should return true if any of the tickets is checked', () => {
controller.tickets = [{checked: true}];
expect(controller.isChecked).toBeTruthy();
});
});
describe('buildPossibleTicketsFilter()', () => {
it('should build the possible tickets filter', () => {
let expectedFilter = {
include: [
{
relation: 'warehouse',
scope: {
fields: ['name']
}
}, {
relation: 'address'
}
],
where: {
landed: {
between: [
jasmine.any(Date),
jasmine.any(Date)
]
},
routeFk: null,
zoneFk: 67
}
};
controller.route = {
finished: new Date(),
routeFk: null,
zoneFk: 67
};
controller.buildPossibleTicketsFilter();
expect(controller.possibleTicketsFilter).toEqual(expectedFilter);
});
});
describe('getHighestPriority()', () => {
it('should return the highest value found in priorities plus 1', () => {
controller.$.model = {data: [
{priority: 99},
{priority: 1},
{priority: 2},
{priority: 3},
{priority: 4},
{priority: 5},
]};
let result = controller.getHighestPriority();
expect(result).toEqual(100);
});
});
describe('setPriority()', () => {
it('should set a ticket priority', () => {
controller.$.model = {refresh: () => {}};
spyOn(controller.$.model, 'refresh');
spyOn(controller.vnApp, 'showSuccess');
const ticketId = 1;
const priority = 999;
$httpBackend.expectPATCH(`/api/Tickets/${ticketId}/`).respond('ok');
controller.setPriority(ticketId, priority);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
expect(controller.$.model.refresh).toHaveBeenCalledWith();
});
});
describe('getSelectedItems()', () => {
it('should return the selected items', () => {
let items = [
{id: 1, checked: true},
{id: 2, checked: false},
{id: 3, checked: true},
{id: 4, checked: false},
{id: 5, checked: true},
];
let selectedItems = controller.getSelectedItems(items);
expect(selectedItems).toMatchSnapshot();
});
});
describe('goToBuscaman()', () => {
it('should open buscaman with the given arguments', () => {
spyOn(window, 'open');
const expectedUrl = 'http://gps.buscalia.com/usuario/localizar.aspx?bmi=true&addr=46460 Av Espioca 100-46460 Silla+to:n19 my street-n19 London';
controller.tickets = [
{
id: 1,
checked: true,
address: {
street: 'my street',
postalCode: 'n19',
city: 'London'
}
},
];
controller.goToBuscaman();
expect(window.open).toHaveBeenCalledWith(expectedUrl, '_blank');
});
});
describe('showDeleteConfirm()', () => {
it('should open a confirm dialog after setting the selected ticket into the controller', () => {
controller.$.confirm = {show: () => {}};
spyOn(controller.$.confirm, 'show');
let ticketId = 1;
controller.showDeleteConfirm(ticketId);
expect(controller.selectedTicket).toEqual(ticketId);
expect(controller.$.confirm.show).toHaveBeenCalledWith();
});
});
describe('removeTicketFromRoute()', () => {
it('should perform a patch query then call showSuccess and updateVolume methods', () => {
spyOn(controller, 'updateVolume');
spyOn(controller.vnApp, 'showSuccess');
let ticketId = 1;
controller.selectedTicket = ticketId;
$httpBackend.expectPATCH(`/api/Tickets/${ticketId}/`).respond('ok');
controller.removeTicketFromRoute('ACCEPT');
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Ticket removed from route');
expect(controller.updateVolume).toHaveBeenCalledWith();
});
});
describe('updateVolume()', () => {
it('should perform a POST query then call both reload and refresh methods', () => {
controller.$.model = {refresh: () => {}};
controller.card = {reload: () => {}};
controller.$stateParamds = {id: 999};
spyOn(controller.$.model, 'refresh');
spyOn(controller.card, 'reload');
let ticketId = 1;
controller.selectedTicket = ticketId;
const url = `/route/api/Routes/${controller.$stateParams.id}/updateVolume`;
$httpBackend.expectPOST(url).respond('ok');
controller.updateVolume();
$httpBackend.flush();
expect(controller.$.model.refresh).toHaveBeenCalledWith();
expect(controller.card.reload).toHaveBeenCalledWith();
});
});
describe('guessPriority()', () => {
it('should perform a GET query then call both refresh and showSuccess methods', () => {
controller.$.model = {refresh: () => {}};
spyOn(controller.$.model, 'refresh');
spyOn(controller.vnApp, 'showSuccess');
controller.$stateParams = {id: 99};
const url = `/api/Routes/${controller.$stateParams.id}/guessPriority/`;
$httpBackend.expectGET(url).respond('ok');
controller.guessPriority();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Order changed');
expect(controller.$.model.refresh).toHaveBeenCalledWith();
});
});
describe('showTicketDescriptor()', () => {
it('should call the descriptor show function after setting the parent and the ticket id', () => {
controller.$.ticketDescriptor = {show: () => {}};
spyOn(controller.$.ticketDescriptor, 'show');
const event = {target: {}, preventDefault: () => {}};
spyOn(event, 'preventDefault');
const ticketId = 999;
controller.showTicketDescriptor(event, ticketId);
expect(controller.$.ticketDescriptor.ticketFk).toEqual(ticketId);
expect(controller.$.ticketDescriptor.show).toHaveBeenCalledWith();
expect(event.preventDefault).toHaveBeenCalledWith();
});
});
describe('showClientDescriptor()', () => {
it('should call the descriptor show method after setting the parent and the client id', () => {
controller.$.clientDescriptor = {show: () => {}};
spyOn(controller.$.clientDescriptor, 'show');
const event = {target: {}, preventDefault: () => {}};
spyOn(event, 'preventDefault');
const clientId = 999;
controller.showClientDescriptor(event, clientId);
expect(controller.$.clientDescriptor.clientFk).toEqual(clientId);
expect(controller.$.clientDescriptor.show).toHaveBeenCalledWith();
expect(event.preventDefault).toHaveBeenCalledWith();
});
});
describe('openPossibleTicketsDialog()', () => {
it('should call both refresh and show methods in posible tickets model and dialog', () => {
controller.$.possibleTicketsModel = {refresh: () => {}};
spyOn(controller.$.possibleTicketsModel, 'refresh');
controller.$.possibleTicketsDialog = {show: () => {}};
spyOn(controller.$.possibleTicketsDialog, 'show');
controller.openPossibleTicketsDialog();
expect(controller.$.possibleTicketsModel.refresh).toHaveBeenCalledWith();
expect(controller.$.possibleTicketsDialog.show).toHaveBeenCalledWith();
});
});
describe('setTicketsRoute()', () => {
it('should perform a POST query to add tickets to the route', done => {
controller.$.possibleTicketsModel = {save: () => {}};
spyOn(controller.$.possibleTicketsModel, 'save').and.returnValue(Promise.resolve());
controller.$.model = {data: [
{id: 1, checked: false}
]};
controller.route = {id: 111};
controller.possibleTickets = [
{id: 2, checked: false},
{id: 3, checked: true},
{id: 4, checked: false},
{id: 5, checked: true},
];
let expectedResult = [
{checked: false, id: 1},
{id: 3, routeFk: 111},
{id: 5, routeFk: 111}
];
controller.setTicketsRoute('ACCEPT').then(() => {
expect(controller.$.model.data).toEqual(expectedResult);
done();
}).catch(done.fail);
});
it('should just return a promise', () => {
expect(controller.setTicketsRoute('CANCEL')).toEqual(jasmine.any(Promise));
});
});
});

View File

@ -4,3 +4,5 @@ Ticket removed from route: Ticket borrado de la ruta
Order changed: Orden cambiado Order changed: Orden cambiado
Delete ticket from route?: ¿Borrar ticket de la ruta? Delete ticket from route?: ¿Borrar ticket de la ruta?
Sort routes: Ordenar rutas Sort routes: Ordenar rutas
Add ticket: Añadir ticket
Tickets to add: Tickets a añadir

View File

@ -38,27 +38,27 @@ module.exports = Self => {
try { try {
let options = {transaction: tx}; let options = {transaction: tx};
let item = await models.Item.findById(ctx.args.itemFk); let item = await models.Item.findById(ctx.args.itemFk, null, options);
if (!item) if (!item)
throw new UserError(`That item doesn't exists`); throw new UserError(`That item doesn't exists`);
let request = await models.TicketRequest.findById(ctx.args.id, { let request = await models.TicketRequest.findById(ctx.args.id, {
include: {relation: 'ticket'} include: {relation: 'ticket'}
}); }, options);
let [[stock]] = await Self.rawSql(`CALL vn.getItemVisibleAvailable(?,?,?,?)`, [ let [[stock]] = await Self.rawSql(`CALL vn.getItemVisibleAvailable(?,?,?,?)`, [
ctx.args.itemFk, ctx.args.itemFk,
request.ticket().shipped, request.ticket().shipped,
request.ticket().warehouseFk, request.ticket().warehouseFk,
false false
]); ], options);
if (stock.available < 0) if (stock.available < 0)
throw new UserError(`This item is not available`); throw new UserError(`This item is not available`);
if (request.saleFk) { if (request.saleFk) {
sale = await models.Sale.findById(request.saleFk); sale = await models.Sale.findById(request.saleFk, null, options);
sale.updateAttributes({ sale.updateAttributes({
itemFk: ctx.args.itemFk, itemFk: ctx.args.itemFk,
quantity: ctx.args.quantity, quantity: ctx.args.quantity,
@ -71,7 +71,11 @@ module.exports = Self => {
quantity: ctx.args.quantity, quantity: ctx.args.quantity,
concept: item.name concept: item.name
}, options); }, options);
request.updateAttributes({saleFk: sale.id, itemFk: sale.itemFk, isOk: true}, options); request.updateAttributes({
saleFk: sale.id,
itemFk: sale.itemFk,
isOk: true
}, options);
} }
query = `CALL vn.ticketCalculateSale(?)`; query = `CALL vn.ticketCalculateSale(?)`;
@ -86,6 +90,8 @@ module.exports = Self => {
}, options); }, options);
await tx.commit(); await tx.commit();
return sale;
} catch (error) { } catch (error) {
await tx.rollback(); await tx.rollback();
throw error; throw error;

View File

@ -29,7 +29,7 @@ module.exports = Self => {
let params = { let params = {
isOk: false, isOk: false,
atenderFk: worker.id, attenderFk: worker.id,
response: ctx.args.observation, response: ctx.args.observation,
}; };

View File

@ -28,7 +28,7 @@ module.exports = Self => {
type: 'Number', type: 'Number',
description: `Search by warehouse` description: `Search by warehouse`
}, { }, {
arg: 'atenderFk', arg: 'attenderFk',
type: 'Number', type: 'Number',
description: `Search requests atended by the given worker` description: `Search requests atended by the given worker`
}, { }, {
@ -65,7 +65,7 @@ module.exports = Self => {
let worker = await Self.app.models.Worker.findOne({where: {userFk: userId}}); let worker = await Self.app.models.Worker.findOne({where: {userFk: userId}});
if (ctx.args.mine) if (ctx.args.mine)
ctx.args.atenderFk = worker.id; ctx.args.attenderFk = worker.id;
let where = buildFilter(ctx.args, (param, value) => { let where = buildFilter(ctx.args, (param, value) => {
switch (param) { switch (param) {
@ -75,7 +75,7 @@ module.exports = Self => {
: {'t.nickname': {like: `%${value}%`}}; : {'t.nickname': {like: `%${value}%`}};
case 'ticketFk': case 'ticketFk':
return {'t.id': value}; return {'t.id': value};
case 'atenderFk': case 'attenderFk':
return {'tr.atenderFk': value}; return {'tr.atenderFk': value};
case 'isOk': case 'isOk':
return {'tr.isOk': value}; return {'tr.isOk': value};
@ -106,13 +106,13 @@ module.exports = Self => {
tr.ticketFk, tr.ticketFk,
tr.quantity, tr.quantity,
tr.price, tr.price,
tr.atenderFk, tr.atenderFk attenderFk,
tr.description, tr.description,
tr.response, tr.response,
tr.saleFk, tr.saleFk,
tr.isOk, tr.isOk,
s.quantity AS saleQuantity, s.quantity AS saleQuantity,
s.itemFK, s.itemFk,
i.name AS itemDescription, i.name AS itemDescription,
t.shipped, t.shipped,
t.nickname, t.nickname,

View File

@ -1,27 +1,14 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('ticket-request confirm()', () => { describe('ticket-request confirm()', () => {
let request; let originalRequest;
let sale; let originalSale;
let createdSaleId; let createdSaleId;
afterAll(async done => { afterAll(async done => {
const paramsForRequest = { await originalRequest.updateAttributes(originalRequest);
saleFk: request.saleFk, await originalSale.updateAttributes(originalSale);
isOk: request.isOk, await app.models.Sale.destroyById(createdSaleId);
itemFk: request.itemFk,
ticketFk: request.ticketFk
};
const paramsForSale = {
itemFk: sale.itemFk,
quantity: sale.quantity,
concept: sale.concept,
};
await request.updateAttributes(paramsForRequest);
await sale.updateAttributes(paramsForSale);
app.models.Sale.destroyById(createdSaleId);
done(); done();
}); });
@ -65,10 +52,11 @@ describe('ticket-request confirm()', () => {
const itemId = 1; const itemId = 1;
const quantity = 10; const quantity = 10;
request = await app.models.TicketRequest.findById(requestId); originalRequest = await app.models.TicketRequest.findById(requestId);
sale = await app.models.Sale.findById(saleId); originalSale = await app.models.Sale.findById(saleId);
request.updateAttributes({saleFk: saleId}); const request = await app.models.TicketRequest.findById(requestId);
await request.updateAttributes({saleFk: saleId});
let ctx = {req: {accessToken: {userId: 9}}, args: { let ctx = {req: {accessToken: {userId: 9}}, args: {
itemFk: itemId, itemFk: itemId,
@ -89,7 +77,8 @@ describe('ticket-request confirm()', () => {
const itemId = 1; const itemId = 1;
const quantity = 10; const quantity = 10;
request.updateAttributes({saleFk: null}); const request = await app.models.TicketRequest.findById(requestId);
await request.updateAttributes({saleFk: null});
let ctx = {req: {accessToken: {userId: 9}}, args: { let ctx = {req: {accessToken: {userId: 9}}, args: {
itemFk: itemId, itemFk: itemId,

View File

@ -5,7 +5,7 @@ describe('ticket-request deny()', () => {
afterAll(async done => { afterAll(async done => {
let params = { let params = {
isOk: null, isOk: null,
atenderFk: request.atenderFk, attenderFk: request.attenderFk,
response: null, response: null,
}; };

Some files were not shown because too many files have changed in this diff Show More