diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 667ab467e..81ad3a0dc 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -219,7 +219,7 @@ export default { goBackToModuleIndexButton: 'vn-item-descriptor a[href="#!/item/index"]', moreMenu: 'vn-item-descriptor vn-icon-menu > div > vn-icon', moreMenuRegularizeButton: '.vn-popover.shown .vn-drop-down li[name="Regularize stock"]', - regularizeQuantityInput: 'vn-item-descriptor > vn-dialog > div > form > div.body > tpl-body > div > vn-textfield > div > div > div.infix > input', + regularizeQuantityInput: 'vn-item-descriptor vn-dialog tpl-body > div > vn-textfield input', regularizeWarehouseAutocomplete: 'vn-item-descriptor vn-dialog vn-autocomplete[ng-model="$ctrl.warehouseFk"]', editButton: 'vn-item-card vn-item-descriptor vn-float-button[icon="edit"]', regularizeSaveButton: 'vn-item-descriptor > vn-dialog > div > form > div.buttons > tpl-buttons > button', @@ -232,7 +232,7 @@ export default { typeAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.typeFk"]', intrastatAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.intrastatFk"]', nameInput: 'vn-textfield[label="Name"] input', - relevancyInput: 'vn-input-number[ng-model="Relevancy"] input', + relevancyInput: 'vn-input-number[ng-model="$ctrl.item.relevancy"] input', originAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.originFk"]', expenceAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.expenceFk"]', longNameInput: 'vn-textfield[ng-model="$ctrl.item.longName"] input', @@ -324,7 +324,7 @@ export default { setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button' }, ticketsIndex: { - openAdvancedSearchButton: 'vn-ticket-index vn-searchbar .append > vn-icon[icon="keyboard_arrow_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', newTicketButton: 'vn-ticket-index > a', searchResult: 'vn-ticket-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr', @@ -332,7 +332,7 @@ export default { searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)', searchTicketInput: `vn-ticket-index ${components.vnTextfield}`, searchWeeklyTicketInput: `vn-ticket-weekly-index ${components.vnTextfield}`, - searchWeeklyClearInput: 'vn-ticket-weekly-index vn-searchbar i[class="material-icons clear"]', + searchWeeklyClearInput: 'vn-ticket-weekly-index vn-searchbar vn-icon[icon=clear]', advancedSearchButton: 'vn-ticket-search-panel vn-submit[label="Search"] input', searchButton: 'vn-ticket-index vn-searchbar vn-icon[icon="search"]', searchWeeklyButton: 'vn-ticket-weekly-index vn-searchbar vn-icon[icon="search"]', @@ -390,7 +390,7 @@ export default { ticketPackages: { packagesButton: 'vn-left-menu a[ui-sref="ticket.card.package"]', firstPackageAutocomplete: 'vn-autocomplete[label="Package"]', - firstQuantityInput: 'vn-input-number[ng-model="Quantity"] input', + firstQuantityInput: 'vn-input-number[ng-model="package.quantity"] input', firstRemovePackageButton: 'vn-icon-button[vn-tooltip="Remove package"]', addPackageButton: 'vn-icon-button[vn-tooltip="Add package"]', clearPackageAutocompleteButton: 'vn-autocomplete[label="Package"] .icons > vn-icon[icon=clear]', @@ -477,20 +477,18 @@ export default { chargesReasonAutocomplete: 'vn-autocomplete[ng-model="$ctrl.ticket.option"]', }, ticketComponents: { - base: 'vn-ticket-components tfoot > tr:nth-child(1) > td', - margin: 'vn-ticket-components tfoot > tr:nth-child(2) > td', - total: 'vn-ticket-components tfoot > tr:nth-child(3) > td' + base: 'vn-ticket-components [name="base-sum"]' }, ticketRequests: { addRequestButton: 'vn-ticket-request-index > a > vn-float-button > button', - request: 'vn-ticket-request-index > form > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr', - descriptionInput: 'vn-ticket-request-create > form > div > vn-card > div > vn-horizontal:nth-child(1) > vn-textfield > div > div > div.infix > input', + 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', atenderAutocomplete: 'vn-ticket-request-create vn-autocomplete[ng-model="$ctrl.ticketRequest.atenderFk"]', quantityInput: 'vn-ticket-request-create vn-input-number input[name=quantity]', priceInput: 'vn-ticket-request-create vn-input-number input[name=price]', firstRemoveRequestButton: 'vn-ticket-request-index vn-icon[icon="delete"]:nth-child(1)', saveButton: 'vn-ticket-request-create > form > div > vn-button-bar > vn-submit[label="Create"] input', - firstDescription: 'vn-ticket-request-index > form > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(2)', + firstDescription: 'vn-ticket-request-index vn-table vn-tr:nth-child(1) > vn-td:nth-child(2)', }, ticketLog: { @@ -506,7 +504,7 @@ export default { firstQuantityInput: 'vn-ticket-service vn-input-number[label="Quantity"] input', firstPriceInput: 'vn-ticket-service vn-input-number[label="Price"] input', firstVatTypeAutocomplete: 'vn-ticket-service vn-autocomplete[label="Tax class"]', - fistDeleteServiceButton: 'vn-ticket-card > vn-main-block > div.content-block.ng-scope > vn-ticket-service > form > vn-card > div > vn-one:nth-child(1) > vn-horizontal:nth-child(1) > vn-auto > 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', serviceLine: 'vn-ticket-service > form > vn-card > div > vn-one:nth-child(2) > vn-horizontal', saveServiceButton: `${components.vnSubmit}`, @@ -531,7 +529,7 @@ export default { claimSummary: { header: 'vn-claim-summary > vn-card > div > h5', state: 'vn-claim-summary vn-label-value[label="State"] > section > span', - observation: 'vn-claim-summary vn-textarea[ng-model="$ctrl.summary.claim.observation"] > div > 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', firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img', itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor', @@ -549,7 +547,7 @@ export default { }, 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', - discountInput: '.vn-popover.shown vn-input-number[ng-model="$ctrl.newDiscount"] > div > div > div.infix > input', + discountInput: '.vn-popover.shown vn-input-number[ng-model="$ctrl.newDiscount"] input', discoutPopoverMana: '.vn-popover.shown .content > div > vn-horizontal > h5', addItemButton: 'vn-claim-detail a vn-float-button', firstClaimableSaleFromTicket: 'vn-claim-detail > vn-dialog vn-tbody > vn-tr', @@ -608,7 +606,7 @@ export default { typeAutocomplete: 'vn-autocomplete[data="$ctrl.itemTypes"]', itemIdInput: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.itemFk"] input', itemTagValueInput: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.value"] input', - openTagSearch: 'vn-catalog-filter > div > vn-vertical > vn-textfield[ng-model="$ctrl.value"] .append > i', + openTagSearch: 'vn-catalog-filter > div > vn-vertical > vn-textfield[ng-model="$ctrl.value"] .append i', tagAutocomplete: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]', tagValueInput: 'vn-order-catalog-search-panel vn-textfield[ng-model="filter.value"] input', searchTagButton: 'vn-order-catalog-search-panel > div > form > vn-horizontal:nth-child(3) > vn-submit > input', diff --git a/e2e/paths/02-client-module/12_lock_of_verified_data.spec.js b/e2e/paths/02-client-module/12_lock_of_verified_data.spec.js index a4c97eb62..52e2db237 100644 --- a/e2e/paths/02-client-module/12_lock_of_verified_data.spec.js +++ b/e2e/paths/02-client-module/12_lock_of_verified_data.spec.js @@ -176,7 +176,7 @@ describe('Client lock verified data path', () => { .wait(selectors.clientFiscalData.socialNameInput) .evaluate(selector => { return document.querySelector(selector).disabled; - }, 'vn-textfield[model="$ctrl.client.socialName"] > div'); + }, 'vn-textfield[ng-model="$ctrl.client.socialName"] > div'); expect(result).toBeFalsy(); }); diff --git a/e2e/paths/05-ticket-module/09_weekly.spec.js b/e2e/paths/05-ticket-module/09_weekly.spec.js index bba4a20e7..a7a301d79 100644 --- a/e2e/paths/05-ticket-module/09_weekly.spec.js +++ b/e2e/paths/05-ticket-module/09_weekly.spec.js @@ -160,7 +160,7 @@ describe('Ticket descriptor path', () => { it('should confirm the sixth weekly ticket was deleted', async() => { const result = await nightmare - .waitToClick('vn-ticket-weekly-index vn-searchbar i[class="material-icons clear"]') + .waitToClick('vn-ticket-weekly-index vn-searchbar vn-icon[icon=clear]') .waitToClick(selectors.ticketsIndex.searchWeeklyButton) .waitForNumberOfElements(selectors.ticketsIndex.searchWeeklyResult, 5) .countElement(selectors.ticketsIndex.searchWeeklyResult); diff --git a/e2e/paths/05-ticket-module/13_services.spec.js b/e2e/paths/05-ticket-module/13_services.spec.js index 31d262778..f9deb6c93 100644 --- a/e2e/paths/05-ticket-module/13_services.spec.js +++ b/e2e/paths/05-ticket-module/13_services.spec.js @@ -78,7 +78,7 @@ describe('Ticket services path', () => { .waitToClick(selectors.ticketService.saveDescriptionButton) .waitForLastSnackbar(); - expect(result).toEqual(`Name can't be empty`); + expect(result).toEqual(`can't be blank`); }); it('should create a new description then add price then create the service', async() => { diff --git a/e2e/paths/08-route-module/03_tickets.spec.js b/e2e/paths/08-route-module/03_tickets.spec.js index c1a0350b3..24a3ebbcf 100644 --- a/e2e/paths/08-route-module/03_tickets.spec.js +++ b/e2e/paths/08-route-module/03_tickets.spec.js @@ -49,7 +49,7 @@ xdescribe('Route basic Data path', () => { it('should count how many tickets are in route', async() => { const result = await nightmare - .countElement('vn-route-tickets vn-textfield[model="ticket.priority"]'); + .countElement('vn-route-tickets vn-textfield[ng-model="ticket.priority"]'); expect(result).toEqual(11); }); @@ -74,7 +74,7 @@ xdescribe('Route basic Data path', () => { it('should now count how many tickets are in route to find one less', async() => { const result = await nightmare - .countElement('vn-route-tickets vn-textfield[model="ticket.priority"]'); + .countElement('vn-route-tickets vn-textfield[ng-model="ticket.priority"]'); expect(result).toEqual(9); }); diff --git a/front/core/components/field/index.js b/front/core/components/field/index.js index a14f21b35..bc60af0d3 100644 --- a/front/core/components/field/index.js +++ b/front/core/components/field/index.js @@ -32,6 +32,7 @@ export default class Field extends FormInput { } set field(value) { + if (value === this.field) return; super.field = value; this.classList.toggle('not-empty', value != null && value !== ''); this.validateValue(); @@ -149,20 +150,28 @@ export default class Field extends FormInput { } set error(value) { + if (value === this.error) return; this._error = value; this.refreshHint(); - this.classList.toggle('invalid', Boolean(value)); } get error() { return this._error; } + get shownError() { + return this.error || this.inputError || null; + } + refreshHint() { - let hint = this.error || this.hint || ''; + let error = this.shownError; + let hint = error || this.hint; + let hintEl = this.element.querySelector('.hint'); - hintEl.innerText = hint; + hintEl.innerText = hint || ''; hintEl.classList.toggle('filled', Boolean(hint)); + + this.classList.toggle('invalid', Boolean(error)); } refreshFix(selector, text) { @@ -205,18 +214,21 @@ export default class Field extends FormInput { } buildInput(type) { - let template = ``; + let template = ``; this.input = this.$compile(template)(this.$)[0]; - this.type = type; } /** * If input value is invalid, sets the error message as hint. */ validateValue() { - this.error = this.input.checkValidity() + let error = this.input.checkValidity() ? null : this.input.validationMessage; + + if (error === this.inputError) return; + this.inputError = error; + this.refreshHint(); } } Field.$inject = ['$element', '$scope', '$compile']; @@ -239,8 +251,7 @@ ngModule.vnComponent('vnField', { hint: '@?', error: ' { $ctrl.min = 0; // FIXME: Input validation doesn't work with Jest? - // expect($ctrl.error).toContain('Please select a value that is no less than 0'); - expect($ctrl.error).toBeNull(); + // expect($ctrl.shownError).toContain('Please select a value that is no less than 0'); + expect($ctrl.shownError).toBeNull(); }); it(`should unset error property when value is upper than min`, () => { $ctrl.field = 1; $ctrl.min = 0; - expect($ctrl.error).toBeNull(); + expect($ctrl.shownError).toBeNull(); }); }); @@ -41,8 +41,8 @@ describe('Component vnInputNumber', () => { $ctrl.max = 0; // FIXME: Input validation doesn't work with Jest? - // expect($ctrl.error).toContain('Please select a value that is no more than 0'); - expect($ctrl.error).toBeNull(); + // expect($ctrl.shownError).toContain('Please select a value that is no more than 0'); + expect($ctrl.shownError).toBeNull(); }); // FIXME: Input validation doesn't work with Jest? @@ -50,7 +50,7 @@ describe('Component vnInputNumber', () => { $ctrl.field = -1; $ctrl.min = 0; - expect($ctrl.error).toBeNull(); + expect($ctrl.shownError).toBeNull(); }); }); diff --git a/front/core/components/table/index.html b/front/core/components/table/index.html index e7ae44886..5c36dd6a0 100644 --- a/front/core/components/table/index.html +++ b/front/core/components/table/index.html @@ -1,2 +1,2 @@ -
+
\ No newline at end of file diff --git a/front/core/components/table/style.scss b/front/core/components/table/style.scss index 9c14e3e03..839e1a7bb 100644 --- a/front/core/components/table/style.scss +++ b/front/core/components/table/style.scss @@ -5,181 +5,169 @@ vn-table { display: block; overflow: auto; width: 100%; +} +.vn-table { + width: 100%; + display: table; + border-collapse: collapse; - & > div { - width: inherit; - display: table; - border-collapse: collapse; + & > vn-thead, + & > thead { + display: table-header-group; + border-bottom: .15em solid $color-spacer; - & > vn-thead { - display: table-header-group; - border-bottom: .15em solid $color-spacer; + & > * > th { + font-weight: normal; + } + & > * > vn-th[field] { + position: relative; + overflow: visible; + cursor: pointer; - & > * > vn-th[field] { - position: relative; - overflow: visible; - cursor: pointer; - - &.active > :after { - color: $color-font; - opacity: 1; - } - &.desc > :after { - content: 'arrow_drop_down'; - } - &.asc > :after { - content: 'arrow_drop_up'; - } - & > :after { - font-family: 'Material Icons'; - content: 'arrow_drop_down'; - position: absolute; - color: $color-spacer; - opacity: 0; - } - &:hover > :after { - opacity: 1; - } + &.active > :after { + color: $color-font; + opacity: 1; + } + &.desc > :after { + content: 'arrow_drop_down'; + } + &.asc > :after { + content: 'arrow_drop_up'; + } + & > :after { + font-family: 'Material Icons'; + content: 'arrow_drop_down'; + position: absolute; + color: $color-spacer; + opacity: 0; + } + &:hover > :after { + opacity: 1; } } - & > vn-tbody { - display: table-row-group; - } - & > vn-tfoot { - border-top: .15em solid $color-spacer; - display: table-footer-group - } - & > * > vn-tr, - & > * > a.vn-tr { + } + & > vn-tbody, + & > tbody { + display: table-row-group; + } + & > vn-tfoot, + & > tfoot { + border-top: .15em solid $color-spacer; + display: table-footer-group + } + & > * > vn-tr, + & > * > a.vn-tr, + & > * > tr { + display: table-row; + height: 3em; + } + vn-thead, vn-tbody, vn-tfoot, + thead, tbody, tfoot { + & > * { display: table-row; - height: 3em; - } - vn-thead, vn-tbody, vn-tfoot { - & > * { - display: table-row; - & > vn-th { - color: $color-font-light; - padding-top: 1em; - padding-bottom: .8em; + & > vn-th, + & > th { + color: $color-font-light; + padding-top: 1em; + padding-bottom: .8em; + } + & > vn-th, + & > vn-td, + & > th, + & > td { + overflow: hidden; + } + & > vn-th, + & > vn-td, + & > vn-td-editable, + & > th, + & > td { + vertical-align: middle; + display: table-cell; + text-align: left; + padding: .6em .5em; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 5em; + + &[number] { + text-align: right; + width: 6em; } - & > vn-th, - & > vn-td { - overflow: hidden; + &[center] { + text-align: center; } - & > vn-th, - & > vn-td, - & > vn-td-editable { - vertical-align: middle; - display: table-cell; - text-align: left; - padding: .6em .5em; - white-space: nowrap; - text-overflow: ellipsis; - max-width: 5em; - - &[number] { - text-align: right; - width: 6em; - } - &[center] { - text-align: center; - } - &[shrink] { - width: 1px; - text-align: center; - } - &[expand] { - max-width: 10em; - min-width: 0; - } - vn-icon.bright, i.bright { - color: #f7931e; - } + &[shrink] { + width: 1px; + text-align: center; } - & > :last-child { - padding-right: 1.4em; + &[expand] { + max-width: 10em; + min-width: 0; } - & > :first-child { - padding-left: 1.4em; + vn-icon.bright, i.bright { + color: #f7931e; } } - & > a.vn-tr { - color: inherit; + & > :last-child { + padding-right: 1.4em; + } + & > :first-child { + padding-left: 1.4em; } } - vn-tbody > * { - border-bottom: .1em solid $color-spacer-light; + & > a.vn-tr { + color: inherit; + } + } + vn-tbody > *, + tbody > * { + border-bottom: .1em solid $color-spacer-light; - &:last-child { - border-bottom: none; - } - &.clickable { - @extend %clickable; - } - & > vn-td .chip { + &:last-child { + border-bottom: none; + } + &.clickable { + @extend %clickable; + } + & > vn-td, + & > td { + .chip { padding: .3em; - border-radius: .3em - } - - & > vn-td .chip.notice { + border-radius: .3em; color: $color-font-bg; - background-color: $color-notice-medium - } - & > vn-td .chip.success { - color: $color-font-bg; - background-color: $color-success-medium + &.notice { + background-color: $color-notice-medium + } + &.success { + background-color: $color-success-medium; + } + &.warning { + background-color: $color-main-medium; + } + &.alert { + background-color: $color-alert-medium; + } + &.message { + color: $color-font-dark; + background-color: $color-bg-dark + } } - - & > vn-td .chip.warning { - color: $color-font-bg; - background-color: $color-main-medium; - } - - & > vn-td .chip.alert { - color: $color-font-bg; - background-color: $color-alert-medium; - } - - & > vn-td .chip.message { - color: $color-font-dark; - background-color: $color-bg-dark - } - - & > vn-td vn-icon-menu { + vn-icon-menu { display: inline-block; color: $color-main; padding: .25em } + } + & > [actions] { + width: 1px; - & > [actions] { - width: 1px; - - & > * { - vertical-align: middle; - } + & > * { + vertical-align: middle; } } - & > vn-empty-rows { - display: table-caption; - caption-side: bottom; - text-align: center; - padding: 1.5em; - width: 100%; - box-sizing: border-box; - } - } - vn-autocomplete { - div.mdl-textfield { - padding: 0 !important; - } - label.mdl-textfield__label:after { - bottom: 0; - } - div.icons { - display: none !important; - } } vn-textfield { float: right; diff --git a/front/core/directives/index.js b/front/core/directives/index.js index 08adfac07..05f21b5cd 100644 --- a/front/core/directives/index.js +++ b/front/core/directives/index.js @@ -2,7 +2,7 @@ import './id'; import './focus'; import './dialog'; import './popover'; -import './validation'; +import './rule'; import './acl'; import './on-error-src'; import './zoom-image'; diff --git a/front/core/directives/rule.js b/front/core/directives/rule.js new file mode 100644 index 000000000..85b6041f8 --- /dev/null +++ b/front/core/directives/rule.js @@ -0,0 +1,72 @@ +import ngModule from '../module'; +import {validateAll} from '../lib/validator'; +import {firstUpper} from '../lib/string'; + +directive.$inject = ['$translate', '$window']; +export function directive($translate, $window) { + return { + restrict: 'A', + link: link, + require: { + ngModel: 'ngModel', + form: '^^?form' + } + }; + + function link($scope, $element, $attrs, $ctrl) { + let vnValidations = $window.validations; + if (!vnValidations) return; + + if (!/^([A-Z]\w+(\.[a-z]\w*)?)?$/.test($attrs.rule)) + throw new Error(`rule: Attribute must have this syntax: [ModelName[.fieldName]]`); + + let rule = $attrs.rule.split('.'); + let modelName = rule.shift(); + let fieldName = rule.shift(); + + let split = $attrs.ngModel.split('.'); + if (!fieldName) fieldName = split.pop() || null; + if (!modelName) modelName = firstUpper(split.pop() || ''); + + if (!modelName || !fieldName) + throw new Error(`rule: Cannot retrieve model or field attribute`); + + let modelValidations = vnValidations[modelName]; + + if (!modelValidations) + throw new Error(`rule: Model '${modelName}' doesn't exist`); + + let validations = modelValidations.validations[fieldName]; + if (!validations || validations.length == 0) + return; + + let ngModel = $ctrl.ngModel; + let form = $ctrl.form; + let field = $element[0].$ctrl; + let error; + + function refreshError() { + if (!field) return; + let canShow = ngModel.$dirty || (form && form.$submitted); + field.error = error && canShow ? error.message : null; + } + + ngModel.$options.$$options.allowInvalid = true; + ngModel.$validators.entity = value => { + try { + error = null; + validateAll($translate, value, validations); + } catch (e) { + error = e; + } + + refreshError(); + return error == null; + }; + + if (form) + $scope.$watch(form.$submitted, refreshError); + } +} +ngModule.directive('rule', directive); + diff --git a/front/core/directives/specs/rule.spec.js b/front/core/directives/specs/rule.spec.js new file mode 100644 index 000000000..f3671a5e2 --- /dev/null +++ b/front/core/directives/specs/rule.spec.js @@ -0,0 +1,115 @@ +describe('Directive rule', () => { + let $scope; + let $element; + let element; + + beforeEach(angular.mock.module('vnCore', $translateProvider => { + $translateProvider.translations('en', {}); + })); + + function compile(html, value) { + inject(($compile, $rootScope, $window) => { + $window.validations = {Model: + {validations: {field: [{validation: 'absence'}]}} + }; + + $scope = $rootScope.$new(); + + $element = angular.element(html); + $compile($element)($scope); + element = $element[0]; + + $scope.model = {field: value}; + $scope.$digest(); + }); + } + + afterEach(() => { + $scope.$destroy(); + $element.remove(); + }); + + describe('Errors', () => { + it(`should throw an error if the rule doesn't have the right syntax`, () => { + expect(() => { + compile(` +
+ + +
+ `); + }).toThrow(new Error(`rule: Attribute must have this syntax: [ModelName[.fieldName]]`)); + }); + + it('should throw an error if cannot retrieve model or field', () => { + expect(() => { + compile(` +
+ + +
+ `); + }).toThrow(new Error(`rule: Cannot retrieve model or field attribute`)); + }); + + it('should throw an error if the model is not defined', () => { + expect(() => { + compile(` +
+ + +
+ `); + }).toThrow(new Error(`rule: Model 'NonExistentModel' doesn't exist`)); + }); + }); + + describe('Validator extended', () => { + let html = ` +
+ + +
+ `; + + it('should not validate the entity as it has a wrong value', () => { + compile(html, 'invalidValue'); + + expect(element.classList).toContain('ng-invalid'); + }); + + it('should validate the entity as it has a valid value', () => { + compile(html, ''); + + expect(element.classList).toContain('ng-valid'); + }); + }); + + describe('Validator minimal', () => { + let html = ` + + + `; + + it('should validate with empty rule and without specifying a parent form', () => { + compile(html, 'invalidValue'); + + expect(element.classList).toContain('ng-invalid'); + }); + }); +}); diff --git a/front/core/directives/specs/validation.spec.js b/front/core/directives/specs/validation.spec.js deleted file mode 100644 index 9f8567df7..000000000 --- a/front/core/directives/specs/validation.spec.js +++ /dev/null @@ -1,186 +0,0 @@ -describe('Directive validation', () => { - let scope; - let element; - let compile; - - beforeEach(angular.mock.module('vnCore', $translateProvider => { - $translateProvider.translations('en', {}); - })); - - compile = (_element, validations, value) => { - inject(($compile, $rootScope, $window) => { - $window.validations = validations; - scope = $rootScope.$new(); - scope.user = {name: value}; - element = angular.element(_element); - $compile(element)(scope); - scope.$digest(); - }); - }; - - it(`should throw an error if the vnValidation doesn't have the right syntax`, () => { - let html = ``; - - expect(() => { - compile(html, {}); - }).toThrow(new Error(`vnValidation: Attribute must have this syntax: [entity].[field]`)); - }); - - it('should throw an error if the window.validations aint defined', () => { - let html = ``; - - expect(() => { - compile(html, {}); - }).toThrow(new Error(`vnValidation: Entity 'User' doesn't exist`)); - }); - - describe('Validator presence()', () => { - it('should not validate the user name as it is an empty string', () => { - let html = `
`; - let validations = {User: {validations: {name: [{validation: 'presence'}]}}}; - compile(html, validations, 'Spiderman'); - scope.user.name = ''; - scope.$digest(); - - expect(element[0].classList).toContain('ng-invalid'); - expect(element[0].classList).not.toContain('ng-valid'); - }); - }); - - describe('Validator absence()', () => { - it('should not validate the entity as it should be an empty string', () => { - let html = `
`; - let validations = {User: {validations: {name: [{validation: 'absence'}]}}}; - compile(html, validations, 'Spiderman'); - scope.$digest(); - - expect(element[0].classList).toContain('ng-invalid'); - expect(element[0].classList).not.toContain('ng-valid'); - }); - - it('should validate the entity as it is an empty string', () => { - let html = `
`; - let validations = {User: {validations: {name: [{validation: 'absence'}]}}}; - compile(html, validations, ''); - scope.$digest(); - - expect(element[0].classList).toContain('ng-valid'); - expect(element[0].classList).not.toContain('ng-invalid'); - }); - }); - - describe('Validator length()', () => { - it('should not validate the user name as it should have min length of 15', () => { - let html = `
`; - let validations = {User: {validations: {name: [{validation: 'length', min: 10, max: 50, is: 15}]}}}; - compile(html, validations, 'fifteen!'); - scope.$digest(); - - expect(element[0].classList).toContain('ng-invalid'); - expect(element[0].classList).not.toContain('ng-valid'); - }); - - it('should validate the user name as it has length of 15', () => { - let html = `
`; - let validations = {User: {validations: {name: [{validation: 'length', min: 10, max: 50, is: 15}]}}}; - compile(html, validations, 'fifteen length!'); - scope.$digest(); - - expect(element[0].classList).toContain('ng-valid'); - expect(element[0].classList).not.toContain('ng-invalid'); - }); - - it('should not validate the user name as it should have min length of 10', () => { - let html = `
`; - let validations = {User: {validations: {name: [{validation: 'length', min: 10}]}}}; - compile(html, validations, 'shortname'); - scope.$digest(); - - expect(element[0].classList).toContain('ng-invalid'); - expect(element[0].classList).not.toContain('ng-valid'); - }); - - it('should validate the user name as its length is greater then the minimum', () => { - let html = `
`; - let validations = {User: {validations: {name: [{validation: 'length', min: 10}]}}}; - compile(html, validations, 'verylongname'); - scope.$digest(); - - expect(element[0].classList).toContain('ng-valid'); - expect(element[0].classList).not.toContain('ng-invalid'); - }); - - it('should not validate the user name as its length is greater then the maximum', () => { - let html = `
`; - let validations = {User: {validations: {name: [{validation: 'length', max: 10}]}}}; - compile(html, validations, 'toolongname'); - scope.$digest(); - - expect(element[0].classList).toContain('ng-invalid'); - expect(element[0].classList).not.toContain('ng-valid'); - }); - }); - - describe('Validator numericality()', () => { - it('should not validate the phone number as it should a integer', () => { - let html = `
`; - let validations = {User: {validations: {phone: [{validation: 'numericality', is: 'what is this?'}]}}}; - compile(html, validations, 'spiderman'); - scope.user.phone = 'this is not a phone number!'; - scope.$digest(); - - expect(element[0].classList).toContain('ng-invalid'); - expect(element[0].classList).not.toContain('ng-valid'); - }); - - it('should validate the phone number as it an integer', () => { - let html = `
`; - let validations = {User: {validations: {phone: [{validation: 'numericality', is: 'what is this?'}]}}}; - compile(html, validations, 'spiderman'); - scope.user.phone = '555555555'; - scope.$digest(); - - expect(element[0].classList).toContain('ng-valid'); - expect(element[0].classList).not.toContain('ng-invalid'); - }); - }); - - describe('Validator inclusion()', () => { - it('should not validate the phone number as it is not an integer', () => { - let html = `
`; - let validations = {User: {validations: {phone: [{validation: 'inclusion'}]}}}; - compile(html, validations, 'spiderman'); - scope.user.phone = 'this is not a phone number!'; - scope.$digest(); - - expect(element[0].classList).toContain('ng-invalid'); - expect(element[0].classList).not.toContain('ng-valid'); - }); - }); - - describe('Validator exclusion()', () => { - it('should validate the phone number as it is an integer', () => { - let html = `
`; - let validations = {User: {validations: {phone: [{validation: 'exclusion'}]}}}; - compile(html, validations, 'spiderman'); - scope.user.phone = '555555555'; - scope.$digest(); - - expect(element[0].classList).toContain('ng-valid'); - expect(element[0].classList).not.toContain('ng-invalid'); - }); - }); - - describe('Validator format()', () => { - it('should not validate the email number as it doesnt contain @', () => { - let html = `
`; - let validations = {User: {validations: {email: [{validation: 'format', with: '@'}]}}}; - compile(html, validations, 'spiderman'); - scope.user.email = 'userverdnatura.es'; - scope.$digest(); - - expect(element[0].classList).toContain('ng-invalid'); - expect(element[0].classList).not.toContain('ng-valid'); - }); - }); -}); diff --git a/front/core/directives/validation.js b/front/core/directives/validation.js deleted file mode 100644 index d234f09bf..000000000 --- a/front/core/directives/validation.js +++ /dev/null @@ -1,80 +0,0 @@ -import ngModule from '../module'; -import {validateAll} from '../lib/validator'; -import {firstUpper} from '../lib/string'; - -directive.$inject = ['$interpolate', '$compile', '$translate', '$window']; -export function directive(interpolate, compile, $translate, $window) { - return { - restrict: 'A', - require: ['ngModel', '^^?form'], - link: link - }; - - function link(scope, element, attrs, ctrl) { - let vnValidations = $window.validations; - - if (!attrs.vnValidation || !vnValidations) - return; - - let split = attrs.vnValidation.split('.'); - - if (split.length !== 2) - throw new Error(`vnValidation: Attribute must have this syntax: [entity].[field]`); - - let entityName = firstUpper(split[0]); - let fieldName = split[1]; - let entity = vnValidations[entityName]; - - if (!entity) - throw new Error(`vnValidation: Entity '${entityName}' doesn't exist`); - - let validations = entity.validations[fieldName]; - if (!validations || validations.length == 0) - return; - - let ngModel = ctrl[0]; - let form = ctrl[1]; - let errorSpan = angular.element(''); - let errorMsg; - let errorShown = false; - - ngModel.$options.$$options.allowInvalid = true; - ngModel.$validators.entity = value => { - try { - validateAll($translate, value, validations); - return true; - } catch (e) { - errorMsg = e.message; - if (errorShown) changeError(); - return false; - } - }; - - scope.$watch(() => { - return (form.$submitted || ngModel.$dirty) && ngModel.$invalid; - }, value => { - let parent = element.parent(); - - if (value) { - changeError(); - parent.addClass('invalid'); - element.after(errorSpan); - } else if (errorShown) { - parent.removeClass('invalid'); - parent.removeAttr('title'); - errorSpan.remove(); - errorSpan.empty(); - } - - errorShown = value; - }); - - function changeError() { - let parent = element.parent(); - errorSpan.text(errorMsg); - parent.attr('title', errorMsg); - } - } -} -ngModule.directive('vnValidation', directive); - diff --git a/front/core/lib/index.js b/front/core/lib/index.js index dac8e460d..2682dfdf9 100644 --- a/front/core/lib/index.js +++ b/front/core/lib/index.js @@ -1,7 +1,5 @@ import './module-loader'; import './crud'; -import './acl-service'; -import './template'; import './copy'; import './equals'; import './modified'; diff --git a/front/core/lib/specs/validator.spec.js b/front/core/lib/specs/validator.spec.js new file mode 100644 index 000000000..c61faa793 --- /dev/null +++ b/front/core/lib/specs/validator.spec.js @@ -0,0 +1,186 @@ +import {validate} from '../validator.js'; + +describe('Validator', () => { + let $translate; + + beforeEach(angular.mock.module('vnCore', $translateProvider => { + $translateProvider.translations('en', {}); + })); + + beforeEach(inject(_$translate_ => { + $translate = _$translate_; + })); + + describe('presence', () => { + let conf = {validation: 'presence'}; + + it('should not validate the value as it should be defined', () => { + expect(() => { + validate($translate, '', conf); + }).toThrowError(); + }); + + it('should validate the value as it should be defined', () => { + expect(() => { + validate($translate, 'aDefinedValue', conf); + }).not.toThrowError(); + }); + }); + + describe('absence', () => { + let conf = {validation: 'absence'}; + + it('should not validate the value as it should be undefined', () => { + expect(() => { + validate($translate, 'aDefinedValue', conf); + }).toThrowError(); + }); + + it('should validate the value as it should be undefined', () => { + expect(() => { + validate($translate, '', conf); + }).not.toThrowError(); + }); + }); + + describe('length', () => { + it('should not validate the value as it should have an specific valid length', () => { + let conf = { + validation: 'length', + is: 25 + }; + + expect(() => { + validate($translate, 'invalidSpecificLengthString', conf); + }).toThrowError(); + }); + + it('should validate the value as it should have an specific valid length', () => { + let conf = { + validation: 'length', + is: 25 + }; + + expect(() => { + validate($translate, 'validSpecificLengthString', conf); + }).not.toThrowError(); + }); + + it('should not validate the value as it should have a min length', () => { + let conf = { + validation: 'length', + min: 10 + }; + + expect(() => { + validate($translate, 'shortName', conf); + }).toThrowError(); + }); + + it('should validate the value as it should have a min length', () => { + let conf = { + validation: 'length', + min: 10 + }; + + expect(() => { + validate($translate, 'veryLongName', conf); + }).not.toThrowError(); + }); + + it('should not validate the value as it should be smaller than the maximum', () => { + let conf = { + validation: 'length', + max: 20 + }; + + expect(() => { + validate($translate, 'chainThatExceedsMaxLength', conf); + }).toThrowError(); + }); + + it('should validate the value as it should be smaller than the maximum', () => { + let conf = { + validation: 'length', + max: 20 + }; + + expect(() => { + validate($translate, 'shortString', conf); + }).not.toThrowError(); + }); + }); + + describe('numericality', () => { + let conf = {validation: 'numericality'}; + + it('should not validate the value as it should be an integer', () => { + expect(() => { + validate($translate, 'notANumber', conf); + }).toThrowError(); + }); + + it('should validate the value as it should be an integer', () => { + expect(() => { + validate($translate, '123456789', conf); + }).not.toThrowError(); + }); + }); + + describe('inclusion', () => { + let conf = { + validation: 'inclusion', + in: ['firstValue', 'seekValue', 'lastValue'] + }; + + it('should not validate the value as it should be in array', () => { + expect(() => { + validate($translate, 'notIncludedValue', conf); + }).toThrowError(); + }); + + it('should validate the value as it should be in array', () => { + expect(() => { + validate($translate, 'seekValue', conf); + }).not.toThrowError(); + }); + }); + + describe('exclusion', () => { + let conf = { + validation: 'exclusion', + in: ['firstValue', 'seekValue', 'lastValue'] + }; + + it('should not validate the value as it should not be in array', () => { + expect(() => { + validate($translate, 'seekValue', conf); + }).toThrowError(); + }); + + it('should validate the value as it should not be in array', () => { + expect(() => { + validate($translate, 'notIncludedValue', conf); + }).not.toThrowError(); + }); + }); + + describe('format', () => { + let conf = { + validation: 'format', + with: /[a-z-]+@[a-z-]+\.[a-z]+/ + }; + + it('should not validate the value as it should match regexp', () => { + expect(() => { + validate($translate, 'wrongValue', conf); + }).toThrowError(); + }); + + it('should validate the value as it should match regexp', () => { + expect(() => { + validate($translate, 'valid-value@domain.com', conf); + }).not.toThrowError(); + }); + }); +}); diff --git a/front/core/lib/string.js b/front/core/lib/string.js index 920640a95..582485cb1 100644 --- a/front/core/lib/string.js +++ b/front/core/lib/string.js @@ -6,7 +6,7 @@ * @return {String} The camelized string */ export function kebabToCamel(str) { - var camelCased = str.replace(/-([a-z])/g, function(g) { + let camelCased = str.replace(/-([a-z])/g, function(g) { return g[1].toUpperCase(); }); return camelCased; diff --git a/front/core/lib/template.js b/front/core/lib/template.js deleted file mode 100644 index ae2f1b188..000000000 --- a/front/core/lib/template.js +++ /dev/null @@ -1,40 +0,0 @@ -import ngModule from '../module'; - -export default class Template { - getNormalized(template, $attrs, defaults) { - this.normalizeInputAttrs($attrs); - return this.get(template, $attrs, defaults); - } - normalizeInputAttrs($attrs) { - const field = $attrs.field || $attrs.model; - const split = field.split('.'); - const len = split.length; - - let i = len - 1; - const fieldName = split[i--]; - const entity = i >= 0 ? split[i--] : 'model'; - const ctrl = i >= 0 ? split[i--] : '$ctrl'; - - if ($attrs.field) { - if (len == 0) - throw new Error(`Attribute 'field' can not be empty`); - if (len > 3) - throw new Error(`Attribute 'field' must have this syntax: [ctrl].[entity].[field]`); - - if ($attrs.model === undefined) - $attrs.model = `${ctrl}.${entity}.${fieldName}`; - if ($attrs.rule === undefined && len >= 2) - $attrs.rule = `${entity}.${fieldName}`; - if ($attrs.label === undefined && len >= 2) - $attrs.label = `${entity}.${fieldName}`; - } - - if ($attrs.name === undefined) - $attrs.name = fieldName; - - if ($attrs.focus !== undefined) - $attrs.focus = 'vn-focus'; - } -} - -ngModule.service('vnTemplate', Template); diff --git a/front/core/lib/acl-service.js b/front/core/services/acl-service.js similarity index 100% rename from front/core/lib/acl-service.js rename to front/core/services/acl-service.js diff --git a/front/core/services/config.js b/front/core/services/config.js index 256308fb6..e2d3035fb 100644 --- a/front/core/services/config.js +++ b/front/core/services/config.js @@ -9,9 +9,9 @@ export default class Config { $http, vnApp, $translate, + storage: $window.localStorage, user: {}, - local: {}, - storage: $window.localStorage + local: {} }); this.params = [ @@ -29,8 +29,8 @@ export default class Config { .then(res => { for (let param of this.params) this.user[param] = res.data[param]; - this.mergeParams(); - }); + }) + .finally(() => this.mergeParams()); } mergeParams() { @@ -47,13 +47,19 @@ export default class Config { this.mergeParams(); let params = {[param]: value}; - return this.$http.post('api/UserConfigs/setUserConfig', params); + return this.$http.post('api/UserConfigs/setUserConfig', params) + .then(() => this.showSaved()); } setLocal(param, value) { this.setItem(param, value); this.local[param] = value; this.mergeParams(); + this.showSaved(); + } + + showSaved() { + this.vnApp.showSuccess(this.$translate.instant('Data saved!')); } getItem(key) { diff --git a/front/core/services/index.js b/front/core/services/index.js index 2f72e3a2d..8ff95188a 100644 --- a/front/core/services/index.js +++ b/front/core/services/index.js @@ -1,4 +1,5 @@ +import './acl-service'; import './app'; import './auth'; import './token'; diff --git a/front/salix/module.js b/front/salix/module.js index 86c8f48d0..58bdec105 100644 --- a/front/salix/module.js +++ b/front/salix/module.js @@ -62,7 +62,7 @@ export function config($translatePartialLoaderProvider, $httpProvider, $compileP $translatePartialLoaderProvider.addPart(appName); $httpProvider.interceptors.push('vnInterceptor'); - $compileProvider.debugInfoEnabled(false); + // $compileProvider.debugInfoEnabled(false); $compileProvider.commentDirectivesEnabled(false); $compileProvider.cssClassDirectivesEnabled(false); } diff --git a/front/salix/styles/misc.scss b/front/salix/styles/misc.scss index cccb0fc92..8066759ac 100644 --- a/front/salix/styles/misc.scss +++ b/front/salix/styles/misc.scss @@ -116,52 +116,6 @@ html [scrollable] { margin-right: auto; max-width: $width-md; } - -.vn-grid { - border-collapse: collapse; - width: 100%; - - & > thead, - & > tbody, - & > tfoot { - tr { - td, th { - text-align: left; - padding: 10px; - - &[number]{ - text-align: right; - } - } - } - } - & > thead, & > tbody { - border-bottom: 3px solid $color-spacer; - } - & > tbody > tr { - border-bottom: 1px solid $color-spacer; - transition: background-color 200ms ease-in-out; - - &.clickable { - @extend %clickable; - } - &.success { - background-color: rgba(163, 209, 49, 0.3); - - &:hover { - background-color: rgba(163, 209, 49, 0.5); - } - } - &.warning { - background-color: rgba(247, 147, 30, 0.3); - - &:hover { - background-color: rgba(247, 147, 30, 0.5); - } - } - } -} - vn-empty-rows { display: block; text-align: center; @@ -180,12 +134,12 @@ vn-empty-rows { [uppercase], .uppercase { text-transform: uppercase; } -html [text-center], .text-center { +html [text-center] { text-align: center; } -html [text-right], .text-right { +html [text-right] { text-align: right; } -html [text-left], .text-left { +html [text-left] { text-align: left; } diff --git a/modules/agency/front/basic-data/index.html b/modules/agency/front/basic-data/index.html index 10c989b12..ec652c0e7 100644 --- a/modules/agency/front/basic-data/index.html +++ b/modules/agency/front/basic-data/index.html @@ -8,8 +8,7 @@
+ class="vn-w-md"> + save="post">
diff --git a/modules/agency/front/edit/index.html b/modules/agency/front/edit/index.html index 4bfb2e778..dbe0a5edf 100644 --- a/modules/agency/front/edit/index.html +++ b/modules/agency/front/edit/index.html @@ -7,8 +7,7 @@ + ng-submit="$ctrl.onSubmit()">
- + ID diff --git a/modules/client/front/address/edit/index.html b/modules/client/front/address/edit/index.html index 44a6fb411..e6bad7c59 100644 --- a/modules/client/front/address/edit/index.html +++ b/modules/client/front/address/edit/index.html @@ -146,7 +146,7 @@ vn-two label="Description" ng-model="observation.description" - rule="addressObservation.description"> + rule="AddressObservation"> + rule="ClientContact"> diff --git a/modules/client/front/credit-insurance/create/index.html b/modules/client/front/credit-insurance/create/index.html index 204bfe0a7..5dbe258b6 100644 --- a/modules/client/front/credit-insurance/create/index.html +++ b/modules/client/front/credit-insurance/create/index.html @@ -6,7 +6,7 @@ min="0" label="Credit" ng-model="$ctrl.creditClassification.credit" - rule="creditInsurance.credit" + rule vn-focus> + rule> + rule="CreditInsurance"> diff --git a/modules/client/front/fiscal-data/index.html b/modules/client/front/fiscal-data/index.html index b1f077fd9..2c502e9d9 100644 --- a/modules/client/front/fiscal-data/index.html +++ b/modules/client/front/fiscal-data/index.html @@ -13,7 +13,7 @@ vn-focus label="Social name" ng-model="$ctrl.client.socialName" - rule="client.socialName" + rule info="You can use letters and spaces"> diff --git a/modules/item/front/tags/index.html b/modules/item/front/tags/index.html index 22522a50e..3db45972e 100644 --- a/modules/item/front/tags/index.html +++ b/modules/item/front/tags/index.html @@ -39,7 +39,7 @@ vn-three label="Value" ng-model="itemTag.value" - rule="itemTag.value"> + rule> + rule> Total {{$ctrl.summary.total | currency: 'EUR':2}}

- + diff --git a/modules/route/front/tickets/index.html b/modules/route/front/tickets/index.html index 6df77d60b..92bf7a2aa 100644 --- a/modules/route/front/tickets/index.html +++ b/modules/route/front/tickets/index.html @@ -58,7 +58,7 @@ type="number" on-change="$ctrl.setPriority(ticket.id, ticket.priority)" ng-model="ticket.priority" - rule="ticket.priority"> + rule="Ticket"> diff --git a/modules/ticket/front/basic-data/step-two/index.html b/modules/ticket/front/basic-data/step-two/index.html index 7713555ae..aa5a23212 100644 --- a/modules/ticket/front/basic-data/step-two/index.html +++ b/modules/ticket/front/basic-data/step-two/index.html @@ -1,7 +1,7 @@ - +
diff --git a/modules/ticket/front/component/index.html b/modules/ticket/front/component/index.html index d79bd524f..2c072f0a3 100644 --- a/modules/ticket/front/component/index.html +++ b/modules/ticket/front/component/index.html @@ -6,72 +6,64 @@ data="components" auto-load="true"> - - - -
Item
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ItemDescriptionQuantitySerieComponentsImportTotal import
- Base to commission {{$ctrl.base() | currency: 'EUR':3}} -
- - {{sale.itemFk | zeroFill:6}} - - - - - - {{::sale.quantity}} -
- {{::component.componentRate.componentType.type}} - - {{::component.componentRate.name}} - - {{::component.value | currency: 'EUR':3}} - - {{::sale.quantity * component.value | currency: 'EUR':3}} -
No results
- + + + Base to commission {{$ctrl.base() | currency: 'EUR':3}} - + + + + + + + + + + + + + + + + + + + + + + + + + + +
ItemDescriptionQuantitySerieComponentsImportTotal
+ + {{sale.itemFk | zeroFill:6}} + + + + + + {{::sale.quantity}} +
+ {{::component.componentRate.componentType.type}} + + {{::component.componentRate.name}} + + {{::component.value | currency: 'EUR':3}} + + {{::sale.quantity * component.value | currency: 'EUR':3}} +
+
+
diff --git a/modules/ticket/front/component/style.scss b/modules/ticket/front/component/style.scss index 85bb673e0..c20b8f720 100644 --- a/modules/ticket/front/component/style.scss +++ b/modules/ticket/front/component/style.scss @@ -1,29 +1,27 @@ +@import "variables"; + vn-ticket-components { - .vn-grid { - tbody:not(:last-child) { + .vn-table > tbody { + &:not(:last-child) { + border-bottom: .1em solid $color-spacer-light; + } + & > tr { border-bottom: none; - } - tfoot tr:first-child td { - padding-top: 10px !important; - } - - tr { - td { - padding-top: .1em !important; - padding-bottom: .1em !important; + &.components { + height: 1em; + + & > td { + padding-top: .1em; + padding-bottom: .1em; + } + &:nth-child(2) > td { + padding-top: 1em; + } + &:last-child > td { + padding-bottom: 1em; + } } - - td.first { - padding-top: 10px !important; - } - - td.last { - padding-bottom: 10px !important; - } - } - tr:not(:first-child):not(:last-child), { - border-bottom: none; } } -} \ No newline at end of file +} diff --git a/modules/ticket/front/dms/index/index.html b/modules/ticket/front/dms/index/index.html index 54b876e21..4ddb2770a 100644 --- a/modules/ticket/front/dms/index/index.html +++ b/modules/ticket/front/dms/index/index.html @@ -4,101 +4,100 @@ link="{ticketFk: $ctrl.$stateParams.id}" filter="::$ctrl.filter" limit="20" - data="$ctrl.ticketDms"> + data="$ctrl.ticketDms" + order="dmsFk DESC" + auto-load="true"> - - - - - - - Id - Type - Order - Reference - Description - Original - File - Employee - Created - - - - - - - - {{::document.dmsFk}} - - - {{::document.dms.dmsType.name}} - - - - - {{::document.dms.hardCopyNumber}} - - - - - {{::document.dms.reference}} - - - - - {{::document.dms.description}} - - - - - - - - - {{::document.dms.file}} - - - - - {{::document.dms.worker.user.nickname | dashIfEmpty}} - - - {{::document.dms.created | dateTime:'dd/MM/yyyy HH:mm'}} - - - - - - - - - - - - - - - - - - - - + + + + + + Id + Type + Order + Reference + Description + Original + File + Employee + Created + + + + + + + + {{::document.dmsFk}} + + + {{::document.dms.dmsType.name}} + + + + + {{::document.dms.hardCopyNumber}} + + + + + {{::document.dms.reference}} + + + + + {{::document.dms.description}} + + + + + + + + + {{::document.dms.file}} + + + + + {{::document.dms.worker.user.nickname | dashIfEmpty}} + + + {{::document.dms.created | dateTime:'dd/MM/yyyy HH:mm'}} + + + + + + + + + + + + + + + + + + - + diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html index a39c2f9c2..4342b2aec 100644 --- a/modules/ticket/front/expedition/index.html +++ b/modules/ticket/front/expedition/index.html @@ -1,66 +1,63 @@ + data="expeditions" + order="created DESC" + auto-load="true"> - - - - - - - - Expedition - Envialia - Item - Name - Package type - Counter - Checked - Worker - Created - - - - - - - - - {{expedition.id | zeroFill:6}} - {{expedition.externalId | zeroFill:6}} - - - {{expedition.itemFk | zeroFill:6}} - - - {{::expedition.namePackage}} - {{::expedition.nameBox}} - {{::expedition.counter}} - {{::expedition.checked}} - - - {{::expedition.userNickname | dashIfEmpty}} - - - {{::expedition.created | dateTime:'dd/MM/yyyy HH:mm'}} - - - - - + + + + + + + Expedition + Envialia + Item + Name + Package type + Counter + Checked + Worker + Created + + + + + + + + + {{expedition.id | zeroFill:6}} + {{expedition.externalId | zeroFill:6}} + + + {{expedition.itemFk | zeroFill:6}} + + + {{::expedition.namePackage}} + {{::expedition.nameBox}} + {{::expedition.counter}} + {{::expedition.checked}} + + + {{::expedition.userNickname | dashIfEmpty}} + + + {{::expedition.created | dateTime:'dd/MM/yyyy HH:mm'}} + + + - - + diff --git a/modules/ticket/front/locale/es.yml b/modules/ticket/front/locale/es.yml index d90c7c707..2b7f75c0c 100644 --- a/modules/ticket/front/locale/es.yml +++ b/modules/ticket/front/locale/es.yml @@ -34,7 +34,7 @@ New price: Nuevo precio New : Nuevo Next: Siguiente Observation type: Tipo de observación -Original quantity: Cantidad original +Original: Original Package size: Bultos Package type: Tipo de porte Phone: Teléfono @@ -50,7 +50,6 @@ Shipped: F. envío Some fields are invalid: Algunos campos no son válidos State: Estado Tickets: Tickets -Total import: Importe total Warehouse: Almacén Worker: Trabajador VAT: IVA diff --git a/modules/ticket/front/note/index.html b/modules/ticket/front/note/index.html index ebf5ddad6..4a6986c40 100644 --- a/modules/ticket/front/note/index.html +++ b/modules/ticket/front/note/index.html @@ -25,14 +25,16 @@ ng-model="observation.observationTypeFk" data="observationTypes" show-field="description" - label="Observation type" vn-one vn-focus> + label="Observation type" + vn-one + vn-focus>
+ rule="TicketObservation"> - + - - - - {{itemFk}} : {{name}} - - - - - - - - - - - - - - - + + + {{itemFk}} : {{name}} + + + + + + + + + + + + diff --git a/modules/ticket/front/request/index/index.html b/modules/ticket/front/request/index/index.html index 218265b84..2e5d60f3d 100644 --- a/modules/ticket/front/request/index/index.html +++ b/modules/ticket/front/request/index/index.html @@ -1,21 +1,19 @@ + data="purchaseRequests" + auto-load="true"> + data="purchaseRequests"> - - - + + @@ -79,9 +77,8 @@ - - + diff --git a/modules/ticket/front/sale-checked/index.html b/modules/ticket/front/sale-checked/index.html index 16037ba02..f61254e1e 100644 --- a/modules/ticket/front/sale-checked/index.html +++ b/modules/ticket/front/sale-checked/index.html @@ -1,54 +1,54 @@ - + data="sales" + order="concept ASC" + auto-load="true"> - - - - - - - Is checked - Item - Description - Quantity - - - - - - - - - - - {{::sale.itemFk | zeroFill:6}} - - - - - - - {{::sale.quantity}} - - - - - + + + + + + Is checked + Item + Description + Quantity + + + + + + + + + + + {{::sale.itemFk | zeroFill:6}} + + + + + + + {{::sale.quantity}} + + + - - + diff --git a/modules/ticket/front/sale-tracking/index.html b/modules/ticket/front/sale-tracking/index.html index 2edca097b..f1ab0500d 100644 --- a/modules/ticket/front/sale-tracking/index.html +++ b/modules/ticket/front/sale-tracking/index.html @@ -1,68 +1,68 @@ - + data="sales" + order="itemFk DESC" + auto-load="true"> - - - - - - - - Item - Description - Quantity - Original quantity - Worker - State - Created - - - - - - - - - - {{sale.itemFk | zeroFill:6}} - - - - - - - {{::sale.quantity}} - {{::sale.originalQuantity}} - - - {{::sale.userNickname | dashIfEmpty}} - - - {{::sale.state}} - {{::sale.created | dateTime: 'dd/MM/yyyy HH:mm'}} - - - - - + + + + + + + Item + Description + Quantity + Original + Worker + State + Created + + + + + + + + + + + {{sale.itemFk | zeroFill:6}} + + + + + + + {{::sale.quantity}} + {{::sale.originalQuantity}} + + + {{::sale.userNickname | dashIfEmpty}} + + + {{::sale.state}} + {{::sale.created | dateTime: 'dd/MM/yyyy HH:mm'}} + + + - + diff --git a/modules/ticket/front/sale/editDiscount.html b/modules/ticket/front/sale/editDiscount.html index c2c6ac4a2..9f40d65b4 100644 --- a/modules/ticket/front/sale/editDiscount.html +++ b/modules/ticket/front/sale/editDiscount.html @@ -1,6 +1,6 @@ - +
MANÁ: {{$ctrl.mana | currency: 'EUR':0}}
- +
{ this.vnApp.showError(e.message); }); @@ -89,8 +89,5 @@ ngModule.component('vnTicketSaleEditDiscount', { mana: ' - - + + + - - + + + + + +
- +
MANÁ: {{$ctrl.mana | currency: 'EUR':0}}
- +
- +
+
- - + + +
- +
+
- - - - - - - - - - - - -
+ + +
+ + +

Sales to transfer

+ + + + Id + Item + Quantity + + + + + {{::sale.itemFk | zeroFill:6}} + + {{::sale.concept}} + + + {{sale.quantity}} + + + + + + + + +
+ - -

Sales to transfer

- - - - Id - Item - Quantity - - - - - {{::sale.itemFk | zeroFill:6}} - - {{::sale.concept}} - - - {{sale.quantity}} - - - - - - - - -
- - -

Destination ticket

- - -
- - - - - - - - - - - - - - - - - - - - -
IDF. envioAgenciaAlmacen
No results
{{::ticket.id}}{{::ticket.shipped | dateTime: 'dd/MM/yyyy'}}{{::ticket.agencyName}}{{::ticket.warehouseName}}
- - - - - - - - - - -
+

Destination ticket

+ +
-
-
+ + + + + + + + + + + + + + + + + + + + +
IdF. envioAgenciaAlmacen
No results
{{::ticket.id}}{{::ticket.shipped | dateTime: 'dd/MM/yyyy'}}{{::ticket.agencyName}}{{::ticket.warehouseName}}
+ + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + - - - - + - - - \ No newline at end of file + \ No newline at end of file diff --git a/modules/ticket/front/sale/style.scss b/modules/ticket/front/sale/style.scss index 7c93ecfee..8abb6d6b4 100644 --- a/modules/ticket/front/sale/style.scss +++ b/modules/ticket/front/sale/style.scss @@ -5,34 +5,34 @@ vn-ticket-sale { justify-content: space-between !important; align-items: center; } - vn-popover.edit { - div.popover { - width: 200px; + + vn-ticket-sale { + tr .mdl-textfield{ + width: inherit; + max-width: 100%; } - vn-horizontal.header { - background-color: $color-main; - color: $color-font-dark; + } + vn-table { + img { + border-radius: 50%; + width: 50px; + height: 50px; + } + } + .taxes { + max-width: 10em; + border: $border-thin-light; + text-align: right; + padding: .5em !important; - h5 { - color: inherit; - margin: 0 auto; - } - } - p.simulatorTitle { - margin-bottom: 0px; - font-size: 12px; - color: $color-main; - } - vn-label-value { - padding-bottom: 20px; - } - div.simulator{ - text-align: center; + & > p { + font-size: 1.2em; + margin: .2em; } } vn-dialog.edit { - @extend vn-popover.edit; - + @extend .edit-price; + &>div{ padding: 0!important; } @@ -52,71 +52,63 @@ vn-ticket-sale { padding-bottom: 0!important; } } - - vn-ticket-sale{ - tr .mdl-textfield{ - width: inherit; - max-width: 100%; - } - } - - vn-popover.transfer{ - vn-textfield { - margin: 0 - } - - vn-horizontal { - & > vn-one:nth-child(1){ - border-right: 1px solid $color-bg; - padding-right: 1em; - } - - & > vn-one:nth-child(2){ - margin-left: 1em - } - } - vn-table, table { - margin-bottom: 10px - } - - vn-table { - overflow-x: hidden; - overflow-y: auto; - max-height: 25em; - width: 30em; - } - - table { - width: 25em - } - } - - vn-dialog.ticket-create{ - vn-button[label=Cancel]{ + vn-dialog.ticket-create { + vn-button[label=Cancel] { display: none; } vn-card.vn-ticket-create { padding: 0!important; } } - - vn-table { - img { - border-radius: 50%; - width: 50px; - height: 50px; +} +.vn-popover .transfer { + vn-textfield { + margin: 0 + } + vn-horizontal { + & > vn-one:nth-child(1) { + border-right: 1px solid $color-bg; + padding-right: 1em; + } + + & > vn-one:nth-child(2) { + margin-left: 1em } } - .taxes { - max-width: 10em; - border: $border-thin-light; - text-align: right; - padding: .5em !important; + vn-table, table { + margin-bottom: 10px + } + vn-table { + overflow-x: hidden; + overflow-y: auto; + max-height: 25em; + width: 30em; + } + table { + width: 25em + } +} +.edit-price { + width: 200px; + .header { + background-color: $color-main; + color: $color-font-dark; - & > p { - font-size: 1.2em; - margin: .2em; + h5 { + color: inherit; + margin: 0 auto; } } + p.simulatorTitle { + margin-bottom: 0px; + font-size: 12px; + color: $color-main; + } + vn-label-value { + padding-bottom: 20px; + } + div.simulator { + text-align: center; + } } \ No newline at end of file diff --git a/modules/ticket/front/services/index.html b/modules/ticket/front/services/index.html index bb9abaf6d..b1ce19f83 100644 --- a/modules/ticket/front/services/index.html +++ b/modules/ticket/front/services/index.html @@ -9,64 +9,61 @@ vn-id="watcher" data="$ctrl.services"> -
+ - - - - - - - - - - - - - - - - - - - + + + + vn-tooltip="New service type" + ng-click="$ctrl.newServiceTypeDialog($index)" + vn-acl="administrative"> - + + + + + + + + + + + + + diff --git a/modules/ticket/front/services/index.js b/modules/ticket/front/services/index.js index 8d2566948..016506e08 100644 --- a/modules/ticket/front/services/index.js +++ b/modules/ticket/front/services/index.js @@ -41,8 +41,7 @@ class Controller { } onNewServiceTypeOpen() { - this.newServiceType.name = ''; - + this.newServiceType = {}; this.nameInput = this.$element[0].querySelector('.edit input'); this.nameInput.focus(); } diff --git a/modules/ticket/front/services/index.spec.js b/modules/ticket/front/services/index.spec.js index 782f0b763..97ce890d9 100644 --- a/modules/ticket/front/services/index.spec.js +++ b/modules/ticket/front/services/index.spec.js @@ -1,7 +1,6 @@ import './index.js'; -import UserError from 'core/lib/user-error'; -describe('Ticket component vnTicketService', () => { +fdescribe('Ticket component vnTicketService', () => { let controller; let $httpBackend; let $httpParamSerializer; @@ -63,7 +62,6 @@ describe('Ticket component vnTicketService', () => { controller.currentServiceIndex = 0; $httpBackend.when('POST', '/api/TicketServiceTypes').respond({id: 4001, name: 'great service!'}); - $httpBackend.expect('POST', '/api/TicketServiceTypes'); controller.onNewServiceTypeResponse('ACCEPT'); $httpBackend.flush(); diff --git a/modules/ticket/front/summary/index.html b/modules/ticket/front/summary/index.html index 0a2ac5798..584360121 100644 --- a/modules/ticket/front/summary/index.html +++ b/modules/ticket/front/summary/index.html @@ -65,7 +65,7 @@

Sale

- + @@ -108,7 +108,8 @@ max-length="6" item="::sale.item" name="::sale.concept" - sub-name="::sale.item.subName"/> + sub-name="::sale.item.subName"> + {{::sale.price | currency: 'EUR':2}} {{::sale.discount}} % diff --git a/modules/ticket/front/tracking/index/index.html b/modules/ticket/front/tracking/index/index.html index 9b4a9e1ea..f919a636b 100644 --- a/modules/ticket/front/tracking/index/index.html +++ b/modules/ticket/front/tracking/index/index.html @@ -1,41 +1,39 @@ - + data="trackings" + order="created DESC" + auto-load="true"> - - - - Tracking - - - - State - Worker - Created - - - - - {{::tracking.state.name}} - - - {{::tracking.worker.user.nickname | dashIfEmpty}} - - - {{::tracking.created | dateTime:'dd/MM/yyyy HH:mm'}} - - - - - + + + + + + State + Worker + Created + + + + + {{::tracking.state.name}} + + + {{::tracking.worker.user.nickname | dashIfEmpty}} + + + {{::tracking.created | dateTime:'dd/MM/yyyy HH:mm'}} + + + - + diff --git a/modules/ticket/front/weekly/index/index.html b/modules/ticket/front/weekly/index/index.html index d3eca89f2..3a251e37a 100644 --- a/modules/ticket/front/weekly/index/index.html +++ b/modules/ticket/front/weekly/index/index.html @@ -1,88 +1,90 @@ - + primary-key="ticketFk" + auto-load="true">
-
- - - - - + + + + + + + + + + Ticket ID + Client + Weekday + Warehouse + Salesperson + + + + + + + + {{weekly.ticketFk}} + + + + + {{::weekly.clientName}} + + + + + + + {{::weekly.warehouseName}} + + + {{::weekly.nickName}} + + + + + + + + + -
-
- - - - - Ticket ID - Client - Weekday - Warehouse - Salesperson - - - - - - - - {{weekly.ticketFk}} - - - - - {{::weekly.clientName}} - - - - - - - {{::weekly.warehouseName}} - - - {{::weekly.nickName}} - - - - - - - - - - - -
+
- + + @@ -95,6 +97,9 @@ question="This ticket will be removed from weekly tickets! Continue anyway?" message="You are going to delete this weekly ticket"> - + \ No newline at end of file