Tests passed

This commit is contained in:
Juan Ferrer 2019-10-11 17:38:04 +02:00
parent a9d5e87078
commit a72c12e1c9
58 changed files with 1361 additions and 1365 deletions

View File

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

View File

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

View File

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

View File

@ -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() => {

View File

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

View File

@ -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 = `<input ng-model="$ctrl.field"></input>`;
let template = `<input type="${type}" ng-model="$ctrl.field"></input>`;
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: '<?',
tabIndex: '<?',
onChange: '&?', // FIXME: Not implemented, nesessary?
rule: '@?' // FIXME: Not implemented
rule: '@?'
}
});

View File

@ -3,15 +3,13 @@ import Input from '../../lib/input';
import './style.scss';
export default class InputFile extends Input {
constructor($element, $scope, $attrs, vnTemplate) {
constructor($element, $scope) {
super($element, $scope);
this.element = $element[0];
this.hasFocus = false;
this._multiple = false;
this._value = 'Select a file';
vnTemplate.normalizeInputAttrs($attrs);
this.registerEvents();
}
@ -108,7 +106,7 @@ export default class InputFile extends Input {
}
}
InputFile.$inject = ['$element', '$scope', '$attrs', 'vnTemplate'];
InputFile.$inject = ['$element', '$scope'];
ngModule.component('vnInputFile', {
template: require('./index.html'),

View File

@ -23,15 +23,15 @@ describe('Component vnInputNumber', () => {
$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();
});
});

View File

@ -1,2 +1,2 @@
<div class="table" ng-transclude>
<div class="vn-table" ng-transclude>
</div>

View File

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

View File

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

View File

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

View File

@ -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(`
<form>
<input
type="text"
ng-model="model.field"
rule="invalidLowerCamelCaseModel">
</input>
</form>
`);
}).toThrow(new Error(`rule: Attribute must have this syntax: [ModelName[.fieldName]]`));
});
it('should throw an error if cannot retrieve model or field', () => {
expect(() => {
compile(`
<form>
<input
type="text"
ng-model="model"
rule>
</input>
</form>
`);
}).toThrow(new Error(`rule: Cannot retrieve model or field attribute`));
});
it('should throw an error if the model is not defined', () => {
expect(() => {
compile(`
<form>
<input
type="text"
ng-model="model.field"
rule="NonExistentModel.field">
</input>
</form>
`);
}).toThrow(new Error(`rule: Model 'NonExistentModel' doesn't exist`));
});
});
describe('Validator extended', () => {
let html = `
<form>
<input
type="text"
ng-model="model.field"
rule="Model.field">
</input>
</form>
`;
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 = `
<input
type="text"
ng-model="model.field"
rule>
</input>
`;
it('should validate with empty rule and without specifying a parent form', () => {
compile(html, 'invalidValue');
expect(element.classList).toContain('ng-invalid');
});
});
});

View File

@ -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 = `<input type="name" ng-model="user.name" vn-validation="user"/>`;
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 = `<input type="name" ng-model="user.name" vn-validation="user.name"/>`;
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 = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
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 = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
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 = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
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 = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
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 = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
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 = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
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 = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
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 = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
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 = `<form><input type="text" ng-model="user.phone" vn-validation="user.phone"/></form>`;
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 = `<form><input type="text" ng-model="user.phone" vn-validation="user.phone"/></form>`;
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 = `<form><input type="text" ng-model="user.phone" vn-validation="user.phone"/></form>`;
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 = `<form><input type="text" ng-model="user.phone" vn-validation="user.phone"/></form>`;
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 = `<form><input type="text" ng-model="user.email" vn-validation="user.email"/></form>`;
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');
});
});
});

View File

@ -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('<span class="mdl-textfield__error"></span>');
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);

View File

@ -1,7 +1,5 @@
import './module-loader';
import './crud';
import './acl-service';
import './template';
import './copy';
import './equals';
import './modified';

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import './acl-service';
import './app';
import './auth';
import './token';

View File

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

View File

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

View File

@ -8,8 +8,7 @@
<form
name="form"
ng-submit="$ctrl.onSubmit()"
class="vn-w-md"
rule="Zone">
class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield

View File

@ -3,8 +3,7 @@
vn-id="watcher"
data="$ctrl.zone"
form="form"
save="post"
rule="Zone">
save="post">
</vn-watcher>
<div class="content-block">
<form name="form" vn-http-submit="$ctrl.onSubmit()" compact>

View File

@ -7,8 +7,7 @@
</vn-watcher>
<form
name="form"
ng-submit="$ctrl.onSubmit()"
rule="Zone">
ng-submit="$ctrl.onSubmit()">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield

View File

@ -173,7 +173,7 @@
<!-- Transfer Popover -->
<vn-popover class="lastTicketsPopover" vn-id="lastTicketsPopover">
<div class="ticketList vn-pa-md">
<vn-table model="lastTicketsModel" auto-load="false" class="vn-grid">
<vn-table model="lastTicketsModel" auto-load="false">
<vn-thead>
<vn-tr>
<vn-th field="id" number>ID</vn-th>

View File

@ -146,7 +146,7 @@
vn-two
label="Description"
ng-model="observation.description"
rule="addressObservation.description">
rule="AddressObservation">
</vn-textfield>
<vn-none>
<vn-icon-button

View File

@ -18,13 +18,13 @@
vn-one
label="Name"
ng-model="contact.name"
rule="clientContact.name">
rule="ClientContact">
</vn-textfield>
<vn-textfield
vn-one
label="Phone"
ng-model="contact.phone"
rule="clientContact.phone"
rule="ClientContact"
vn-focus>
</vn-textfield>
<vn-none>

View File

@ -6,7 +6,7 @@
min="0"
label="Credit"
ng-model="$ctrl.creditClassification.credit"
rule="creditInsurance.credit"
rule
vn-focus>
</vn-input-number>
<vn-input-number
@ -15,7 +15,7 @@
step="1"
label="Grade"
ng-model="$ctrl.creditClassification.grade"
rule="CreditInsurance.grade">
rule>
</vn-input-number>
<vn-date-picker
vn-one

View File

@ -13,7 +13,7 @@
min="0"
label="Credit"
ng-model="$ctrl.insurance.credit"
rule="CreditInsurance.credit"
rule="CreditInsurance"
vn-focus>
</vn-input-number>
<vn-date-picker
@ -29,7 +29,7 @@
step="1"
label="Grade"
ng-model="$ctrl.insurance.grade"
rule="CreditInsurance.grade">
rule="CreditInsurance">
</vn-input-number>
</vn-horizontal>
</vn-card>

View File

@ -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">
</vn-textfield>
<vn-textfield

View File

@ -26,7 +26,7 @@
vn-three
label="Code"
ng-model="niche.code"
rule="itemNiche.code"
rule="ItemNiche"
vn-acl="buyer,replenisher">
</vn-textfield>
<vn-none>

View File

@ -39,7 +39,7 @@
vn-three
label="Value"
ng-model="itemTag.value"
rule="itemTag.value">
rule>
</vn-textfield>
<vn-autocomplete
ng-show="tag.selection.isFree === false"
@ -57,7 +57,7 @@
type="number"
label="Relevancy"
ng-model="itemTag.priority"
rule="itemTag.priority">
rule>
</vn-textfield>
<vn-none>
<vn-icon-button

View File

@ -39,7 +39,7 @@
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.summary.total | currency: 'EUR':2}}</strong></p>
</vn-one>
<vn-auto>
<vn-table class="vn-grid">
<vn-table>
<vn-thead>
<vn-tr>
<vn-th shrink></vn-th>

View File

@ -58,7 +58,7 @@
type="number"
on-change="$ctrl.setPriority(ticket.id, ticket.priority)"
ng-model="ticket.priority"
rule="ticket.priority">
rule="Ticket">
</vn-textfield>
</vn-td>
<vn-td number>

View File

@ -1,7 +1,7 @@
<form name="form">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<table class="vn-grid">
<table class="vn-table">
<thead>
<tr>
<th number translate>Item</th>

View File

@ -6,72 +6,64 @@
data="components"
auto-load="true">
</vn-crud-model>
<vn-vertical>
<vn-card class="vn-pa-lg">
<vn-vertical>
<table class="vn-grid">
<thead>
<tr>
<th number translate>Item</th>
<th translate>Description</th>
<th number translate>Quantity</th>
<th translate>Serie</th>
<th translate>Components</th>
<th number translate>Import</th>
<th number translate>Total import</th>
</tr>
</thead>
<tfoot>
<tr>
<td number colspan="7">
<span translate>Base to commission</span> {{$ctrl.base() | currency: 'EUR':3}}
</td>
</tr>
</tfoot>
<tbody ng-repeat="sale in components track by sale.id">
<tr>
<td rowspan="{{::sale.components.length + 1}}" number>
<span
ng-click="$ctrl.showDescriptor($event, sale.itemFk)"
class="link">
{{sale.itemFk | zeroFill:6}}
</span>
</td>
<td rowspan="{{::sale.components.length + 1}}">
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="::sale.concept"
sub-name="::sale.item.subName">
</vn-fetched-tags>
</td>
<td rowspan="{{::sale.components.length + 1}}" number>
{{::sale.quantity}}
</td>
</tr>
<tr
ng-repeat="component in sale.components track by component.componentFk">
<td ng-class="::{first: $index == 0,last: $index == sale.components.length - 1}">
{{::component.componentRate.componentType.type}}
</td>
<td ng-class="::{first: $index == 0,last: $index == sale.components.length - 1}">
{{::component.componentRate.name}}
</td>
<td ng-class="::{first: $index == 0,last: $index == sale.components.length - 1}" number>
{{::component.value | currency: 'EUR':3}}
</td>
<td ng-class="::{first: $index == 0,last: $index == sale.components.length - 1}" number>
{{::sale.quantity * component.value | currency: 'EUR':3}}
</td>
</tr>
<tr ng-if="model.data.length === 0" class="list list-element">
<td colspan="7" style="text-align: center" translate>No results</td>
</tr>
</tbody>
</table>
</vn-vertical>
<vn-data-viewer model="model" class="vn-w-xl">
<vn-card class="vn-pa-lg text-right" name="base-sum">
<span translate>Base to commission</span> {{$ctrl.base() | currency: 'EUR':3}}
</vn-card>
</vn-vertical>
<vn-card class="vn-mt-md">
<table class="vn-table">
<thead>
<tr>
<th number translate>Item</th>
<th translate expand>Description</th>
<th number translate>Quantity</th>
<th translate>Serie</th>
<th translate>Components</th>
<th number translate>Import</th>
<th number translate>Total</th>
</tr>
</thead>
<tbody ng-repeat="sale in components track by sale.id">
<tr style="height: initial;">
<td rowspan="{{::sale.components.length + 1}}" number>
<span
ng-click="$ctrl.showDescriptor($event, sale.itemFk)"
class="link">
{{sale.itemFk | zeroFill:6}}
</span>
</td>
<td rowspan="{{::sale.components.length + 1}}" expand>
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="::sale.concept"
sub-name="::sale.item.subName">
</vn-fetched-tags>
</td>
<td rowspan="{{::sale.components.length + 1}}" number>
{{::sale.quantity}}
</td>
</tr>
<tr
ng-repeat="component in sale.components track by component.componentFk"
class="components">
<td>
{{::component.componentRate.componentType.type}}
</td>
<td>
{{::component.componentRate.name}}
</td>
<td number>
{{::component.value | currency: 'EUR':3}}
</td>
<td number>
{{::sale.quantity * component.value | currency: 'EUR':3}}
</td>
</tr>
</tbody>
</table>
</vn-card>
</vn-data-viewer>
<vn-item-descriptor-popover
vn-id="descriptor"
quicklinks="$ctrl.quicklinks">

View File

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

View File

@ -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">
</vn-crud-model>
<vn-vertical>
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="dmsFk" default-order="DESC" shrink>Id</vn-th>
<vn-th field="dmsTypeFk" shrink>Type</vn-th>
<vn-th field="hardCopyNumber" shrink number>Order</vn-th>
<vn-th field="reference" shrink>Reference</vn-th>
<vn-th expand>Description</vn-th>
<vn-th field="hasFile" shrink>Original</vn-th>
<vn-th shrink>File</vn-th>
<vn-th shrink>Employee</vn-th>
<vn-th field="created">Created</vn-th>
<vn-th shrink></vn-th>
<vn-th shrink></vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="document in $ctrl.ticketDms">
<vn-td number shrink>{{::document.dmsFk}}</vn-td>
<vn-td shrink>
<span title="{{::document.dms.dmsType.name}}">
{{::document.dms.dmsType.name}}
</span>
</vn-td>
<vn-td shrink number>
<span class="chip" title="{{::document.dms.hardCopyNumber}}"
ng-class="{'message': document.dms.hardCopyNumber}">
{{::document.dms.hardCopyNumber}}
</span>
</vn-td>
<vn-td shrink>
<span title="{{::document.dms.reference}}">
{{::document.dms.reference}}
</span>
</vn-td>
<vn-td expand>
<span title="{{::document.dms.description}}">
{{::document.dms.description}}
</span>
</vn-td>
<vn-td shrink>
<vn-check disabled="true"
ng-model="document.dms.hasFile">
</vn-check>
</vn-td>
<vn-td shrink>
<a target="_blank"
title="{{'Download file' | translate}}"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">
{{::document.dms.file}}
</a>
</vn-td>
<vn-td shrink>
<span class="link"
ng-click="$ctrl.showWorkerDescriptor($event, document.dms.workerFk)">
{{::document.dms.worker.user.nickname | dashIfEmpty}}
</span></vn-td>
<vn-td>
{{::document.dms.created | dateTime:'dd/MM/yyyy HH:mm'}}
</vn-td>
<vn-td shrink>
<a target="_blank"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">
<vn-icon-button
icon="cloud_download"
title="{{'Download file' | translate}}">
</vn-icon-button>
</a>
</vn-td>
<vn-td shrink>
<vn-icon-button icon="edit"
ui-sref="ticket.card.dms.edit({dmsId: {{::document.dmsFk}}})"
title="{{'Edit file' | translate}}">
</vn-icon-button>
</vn-td>
<vn-td shrink>
<vn-icon-button icon="delete"
ng-click="$ctrl.showDeleteConfirm($index)"
title="{{'Remove file' | translate}}"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-vertical>
<vn-pagination model="model"></vn-pagination>
<vn-data-viewer model="model">
<vn-card class="vn-w-lg">
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="dmsFk" shrink>Id</vn-th>
<vn-th field="dmsTypeFk" shrink>Type</vn-th>
<vn-th field="hardCopyNumber" shrink number>Order</vn-th>
<vn-th field="reference" shrink>Reference</vn-th>
<vn-th expand>Description</vn-th>
<vn-th field="hasFile" shrink>Original</vn-th>
<vn-th shrink>File</vn-th>
<vn-th shrink>Employee</vn-th>
<vn-th field="created">Created</vn-th>
<vn-th shrink></vn-th>
<vn-th shrink></vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="document in $ctrl.ticketDms">
<vn-td number shrink>{{::document.dmsFk}}</vn-td>
<vn-td shrink>
<span title="{{::document.dms.dmsType.name}}">
{{::document.dms.dmsType.name}}
</span>
</vn-td>
<vn-td shrink number>
<span class="chip" title="{{::document.dms.hardCopyNumber}}"
ng-class="{'message': document.dms.hardCopyNumber}">
{{::document.dms.hardCopyNumber}}
</span>
</vn-td>
<vn-td shrink>
<span title="{{::document.dms.reference}}">
{{::document.dms.reference}}
</span>
</vn-td>
<vn-td expand>
<span title="{{::document.dms.description}}">
{{::document.dms.description}}
</span>
</vn-td>
<vn-td shrink>
<vn-check disabled="true"
ng-model="document.dms.hasFile">
</vn-check>
</vn-td>
<vn-td shrink>
<a target="_blank"
title="{{'Download file' | translate}}"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">
{{::document.dms.file}}
</a>
</vn-td>
<vn-td shrink>
<span class="link"
ng-click="$ctrl.showWorkerDescriptor($event, document.dms.workerFk)">
{{::document.dms.worker.user.nickname | dashIfEmpty}}
</span></vn-td>
<vn-td>
{{::document.dms.created | dateTime:'dd/MM/yyyy HH:mm'}}
</vn-td>
<vn-td shrink>
<a target="_blank"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">
<vn-icon-button
icon="cloud_download"
title="{{'Download file' | translate}}">
</vn-icon-button>
</a>
</vn-td>
<vn-td shrink>
<vn-icon-button icon="edit"
ui-sref="ticket.card.dms.edit({dmsId: {{::document.dmsFk}}})"
title="{{'Edit file' | translate}}">
</vn-icon-button>
</vn-td>
<vn-td shrink>
<vn-icon-button icon="delete"
ng-click="$ctrl.showDeleteConfirm($index)"
title="{{'Remove file' | translate}}"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-vertical>
</vn-data-viewer>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>

View File

@ -1,66 +1,63 @@
<vn-crud-model
auto-load="false"
vn-id="model"
url="/ticket/api/Expeditions/filter"
link="{ticketFk: $ctrl.$stateParams.id}"
limit="20"
data="expeditions">
data="expeditions"
order="created DESC"
auto-load="true">
</vn-crud-model>
<vn-vertical>
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th></vn-th>
<vn-th field="itemFk" number>Expedition</vn-th>
<vn-th field="itemFk" number>Envialia</vn-th>
<vn-th field="itemFk" number>Item</vn-th>
<vn-th field="name">Name</vn-th>
<vn-th field="isBox">Package type</vn-th>
<vn-th field="counter" number>Counter</vn-th>
<vn-th field="checked" number>Checked</vn-th>
<vn-th field="worker">Worker</vn-th>
<vn-th field="created" default-order="DESC">Created</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="expedition in expeditions">
<vn-td class="vn-px-md" style="width:30px;color:#FFA410;">
<vn-icon-button icon="delete"
ng-click="$ctrl.deleteExpedition(expedition)"
vn-tooltip="Delete expedition">
</vn-icon-button>
</vn-td>
<vn-td number>{{expedition.id | zeroFill:6}}</vn-td>
<vn-td number>{{expedition.externalId | zeroFill:6}}</vn-td>
<vn-td number>
<span
ng-class="{link: expedition.itemFk}"
ng-click="$ctrl.showItemDescriptor($event, expedition.itemFk)">
{{expedition.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td>{{::expedition.namePackage}}</vn-td>
<vn-td>{{::expedition.nameBox}}</vn-td>
<vn-td number>{{::expedition.counter}}</vn-td>
<vn-td number>{{::expedition.checked}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="$ctrl.showWorkerDescriptor($event, expedition.workerFk)">
{{::expedition.userNickname | dashIfEmpty}}
</span>
</vn-td>
<vn-td>{{::expedition.created | dateTime:'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-vertical>
<vn-pagination model="model"></vn-pagination>
<vn-data-viewer model="model">
<vn-card class="vn-w-xl">
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th></vn-th>
<vn-th field="itemFk" number>Expedition</vn-th>
<vn-th field="itemFk" number>Envialia</vn-th>
<vn-th field="itemFk" number>Item</vn-th>
<vn-th field="name">Name</vn-th>
<vn-th field="isBox">Package type</vn-th>
<vn-th field="counter" number>Counter</vn-th>
<vn-th field="checked" number>Checked</vn-th>
<vn-th field="worker">Worker</vn-th>
<vn-th field="created">Created</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="expedition in expeditions">
<vn-td class="vn-px-md" style="width:30px; color:#FFA410;">
<vn-icon-button icon="delete"
ng-click="$ctrl.deleteExpedition(expedition)"
vn-tooltip="Delete expedition">
</vn-icon-button>
</vn-td>
<vn-td number>{{expedition.id | zeroFill:6}}</vn-td>
<vn-td number>{{expedition.externalId | zeroFill:6}}</vn-td>
<vn-td number>
<span
ng-class="{link: expedition.itemFk}"
ng-click="$ctrl.showItemDescriptor($event, expedition.itemFk)">
{{expedition.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td>{{::expedition.namePackage}}</vn-td>
<vn-td>{{::expedition.nameBox}}</vn-td>
<vn-td number>{{::expedition.counter}}</vn-td>
<vn-td number>{{::expedition.checked}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="$ctrl.showWorkerDescriptor($event, expedition.workerFk)">
{{::expedition.userNickname | dashIfEmpty}}
</span>
</vn-td>
<vn-td>{{::expedition.created | dateTime:'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-vertical>
</vn-data-viewer>
<vn-item-descriptor-popover
vn-id="itemDescriptor"
quicklinks="$ctrl.quicklinks">

View File

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

View File

@ -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>
</vn-autocomplete>
<vn-textfield
vn-two
class="vn-mr-lg"
label="Description"
ng-model="observation.description"
rule="ticketObservation.description">
rule="TicketObservation">
</vn-textfield>
<vn-auto class="vn-pt-md">
<vn-icon-button

View File

@ -11,52 +11,49 @@
data="packages"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" compact>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-one>
<vn-horizontal ng-repeat="package in packages track by $index">
<vn-autocomplete vn-one
vn-focus
url="/ticket/api/Packagings/listPackaging"
label="Package"
show-field="name"
value-field="packagingFk"
search-function="{or: [{itemFk: $search}, {'name': {like: '%'+ $search +'%'}}]}"
ng-model="package.packagingFk">
<tpl-item>{{itemFk}} : {{name}}</tpl-item>
</vn-autocomplete>
<vn-input-number
vn-one
step="1"
label="Quantity"
ng-model="package.quantity"
rule="TicketPackaging.quantity">
</vn-input-number>
<vn-textfield
vn-one
label="Added"
ng-model="package.created | dateTime: 'dd/MM/yyyy'"
disabled="true"
ng-readonly="true">
</vn-textfield>
<vn-auto class="vn-pt-md">
<vn-icon-button
pointer
vn-tooltip="Remove package"
icon="delete"
ng-click="model.remove($index)">
</vn-icon-button>
</vn-one>
</vn-horizontal>
</vn-one>
<vn-one>
<vn-icon-button
vn-tooltip="Add package"
vn-bind="+"
icon="add_circle"
ng-click="$ctrl.add()">
</vn-icon-button>
</vn-one>
<vn-horizontal ng-repeat="package in packages track by $index">
<vn-autocomplete
vn-one
vn-focus
url="/ticket/api/Packagings/listPackaging"
label="Package"
show-field="name"
value-field="packagingFk"
search-function="{or: [{itemFk: $search}, {'name': {like: '%'+ $search +'%'}}]}"
ng-model="package.packagingFk">
<tpl-item>{{itemFk}} : {{name}}</tpl-item>
</vn-autocomplete>
<vn-input-number
vn-one
step="1"
label="Quantity"
ng-model="package.quantity"
rule="TicketPackaging">
</vn-input-number>
<vn-date-picker
vn-one
label="Added"
ng-model="package.created"
disabled="true"
ng-readonly="true">
</vn-date-picker>
<vn-auto>
<vn-icon-button
pointer
vn-tooltip="Remove package"
icon="delete"
ng-click="model.remove($index)">
</vn-icon-button>
</vn-auto>
</vn-horizontal>
<vn-icon-button
vn-tooltip="Add package"
vn-bind="+"
icon="add_circle"
ng-click="$ctrl.add()">
</vn-icon-button>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>

View File

@ -1,21 +1,19 @@
<vn-crud-model
auto-load="false"
vn-id="model"
url="/ticket/api/TicketRequests"
fields="['id', 'description', 'created', 'requesterFk', 'atenderFk', 'quantity', 'price', 'saleFk', 'isOk']"
order="created ASC"
link="{ticketFk: $ctrl.$stateParams.id}"
filter="::$ctrl.filter"
data="purchaseRequests">
data="purchaseRequests"
auto-load="true">
</vn-crud-model>
<vn-watcher
vn-id="watcher"
data="purchaseRequests"
form="form">
data="purchaseRequests">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-data-viewer model="model">
<vn-card class="vn-w-xl">
<vn-table model="model">
<vn-thead>
<vn-tr>
@ -79,9 +77,8 @@
</vn-tr>
</vn-tbody>
</vn-table>
</vn-horizontal>
</vn-card>
</form>
</vn-data-viewer>
<vn-item-descriptor-popover
vn-id="itemDescriptor"
quicklinks="$ctrl.quicklinks">

View File

@ -1,54 +1,54 @@
<vn-crud-model auto-load="false"
<vn-crud-model
vn-id="model"
url="/ticket/api/sales"
filter="::$ctrl.filter"
link="{ticketFk: $ctrl.$stateParams.id}"
limit="20"
data="sales">
data="sales"
order="concept ASC"
auto-load="true">
</vn-crud-model>
<vn-vertical>
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="isChecked" center>Is checked</vn-th>
<vn-th field="itemFk" number>Item</vn-th>
<vn-th field="concept" default-order="ASC">Description</vn-th>
<vn-th field="quantity" number>Quantity</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="sale in sales">
<vn-td center shrink>
<vn-check
vn-one ng-model="sale.isChecked.isChecked"
disabled="true">
</vn-check>
</vn-td>
<vn-td number>
<span
ng-click="$ctrl.showDescriptor($event, sale.itemFk)"
class="link">
{{::sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="::sale.concept"
sub-name="::sale.item.subName">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::sale.quantity}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-vertical>
<vn-pagination model="model"></vn-pagination>
<vn-data-viewer model="model">
<vn-card class="vn-w-lg">
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="isChecked" center>Is checked</vn-th>
<vn-th field="itemFk" number>Item</vn-th>
<vn-th field="concept">Description</vn-th>
<vn-th field="quantity" number>Quantity</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="sale in sales">
<vn-td center shrink>
<vn-check
vn-one ng-model="sale.isChecked.isChecked"
disabled="true">
</vn-check>
</vn-td>
<vn-td number>
<span
ng-click="$ctrl.showDescriptor($event, sale.itemFk)"
class="link">
{{::sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="::sale.concept"
sub-name="::sale.item.subName">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::sale.quantity}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-vertical>
<vn-item-descriptor-popover vn-id="descriptor"
</vn-data-viewer>
<vn-item-descriptor-popover
vn-id="descriptor"
quicklinks="$ctrl.quicklinks">
</vn-item-descriptor-popover>

View File

@ -1,68 +1,68 @@
<vn-crud-model auto-load="false"
<vn-crud-model
vn-id="model"
url="/ticket/api/SaleTrackings/listSaleTracking"
link="{ticketFk: $ctrl.$stateParams.id}"
limit="20"
data="sales">
data="sales"
order="itemFk DESC"
auto-load="true">
</vn-crud-model>
<vn-vertical>
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th></vn-th>
<vn-th field="itemFk" number default-order="DESC">Item</vn-th>
<vn-th>Description</vn-th>
<vn-th field="quantity">Quantity</vn-th>
<vn-th field="originalQuantity">Original quantity</vn-th>
<vn-th field="workerFk" class="ellipsize" style="max-width: 5em">Worker</vn-th>
<vn-th field="state">State</vn-th>
<vn-th field="created">Created</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="sale in sales">
<vn-td>
<vn-icon
class="bright"
icon="warning"
ng-if="sale.quantity != sale.originalQuantity"
vn-tooltip="The quantity do not match"></vn-icon>
</vn-td>
<vn-td number>
<span
ng-click="$ctrl.showItemDescriptor($event, sale.itemFk)"
class="link">
{{sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="::sale.concept"
sub-name="::sale.item.subName">
</vn-fetched-tags>
</vn-td>
<vn-td>{{::sale.quantity}}</vn-td>
<vn-td>{{::sale.originalQuantity}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="$ctrl.showWorkerDescriptor($event, sale.workerFk)">
{{::sale.userNickname | dashIfEmpty}}
</span>
</vn-td>
<vn-td>{{::sale.state}}</vn-td>
<vn-td>{{::sale.created | dateTime: 'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-vertical>
<vn-pagination model="model"></vn-pagination>
<vn-data-viewer model="model">
<vn-card class="vn-w-xl">
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th shrink></vn-th>
<vn-th field="itemFk" number>Item</vn-th>
<vn-th>Description</vn-th>
<vn-th field="quantity" number>Quantity</vn-th>
<vn-th field="originalQuantity" number>Original</vn-th>
<vn-th field="workerFk">Worker</vn-th>
<vn-th field="state">State</vn-th>
<vn-th field="created">Created</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="sale in sales">
<vn-td shrink>
<vn-icon
class="bright"
icon="warning"
ng-if="sale.quantity != sale.originalQuantity"
vn-tooltip="The quantity do not match">
</vn-icon>
</vn-td>
<vn-td number>
<span
ng-click="$ctrl.showItemDescriptor($event, sale.itemFk)"
class="link">
{{sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="::sale.concept"
sub-name="::sale.item.subName">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::sale.quantity}}</vn-td>
<vn-td number>{{::sale.originalQuantity}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="$ctrl.showWorkerDescriptor($event, sale.workerFk)">
{{::sale.userNickname | dashIfEmpty}}
</span>
</vn-td>
<vn-td>{{::sale.state}}</vn-td>
<vn-td>{{::sale.created | dateTime: 'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-vertical>
</vn-data-viewer>
<vn-item-descriptor-popover vn-id="itemDescriptor"
quicklinks="$ctrl.quicklinks">
</vn-item-descriptor-popover>

View File

@ -1,6 +1,6 @@
<vn-horizontal class="header vn-pa-md">
<div class="header vn-pa-md">
<h5>MANÁ: {{$ctrl.mana | currency: 'EUR':0}}</h5>
</vn-horizontal>
</div>
<div class="vn-pa-md">
<vn-input-number
vn-focus

View File

@ -64,7 +64,7 @@ class Controller {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.clearDiscount();
modified = false;
this.vnTicketSale.$scope.model.refresh();
// this.vnTicketSale.$scope.model.refresh();
}).catch(e => {
this.vnApp.showError(e.message);
});
@ -89,8 +89,5 @@ ngModule.component('vnTicketSaleEditDiscount', {
mana: '<?',
bulk: '<?',
onHide: '&'
},
require: {
vnTicketSale: '^vnTicketSale'
}
});

View File

@ -190,25 +190,37 @@
</vn-icon-button>
</vn-one>
</vn-card>
<vn-item-descriptor-popover vn-id="descriptor"
quicklinks="$ctrl.quicklinks">
</vn-item-descriptor-popover>
</vn-vertical>
<vn-float-button
ng-show="$ctrl.isEditable"
ng-click="$ctrl.newOrderFromTicket()"
icon="add"
vn-tooltip="Add item"
vn-bind="+"
fixed-bottom-right>
</vn-float-button>
<!-- Edit Price Popover -->
<vn-popover
class="edit dialog-summary"
vn-id="edit-price-popover"
on-open="$ctrl.getManaSalespersonMana()"
on-close="$ctrl.mana = null">
<vn-item-descriptor-popover
vn-id="descriptor"
quicklinks="$ctrl.quicklinks">
</vn-item-descriptor-popover>
<!-- Edit Price Popover -->
<vn-popover
class="dialog-summary"
vn-id="edit-price-popover"
on-open="$ctrl.getManaSalespersonMana()"
on-close="$ctrl.mana = null">
<div class="edit-price">
<vn-spinner
ng-if="$ctrl.mana == null"
style="padding: 1em;"
enable="true">
</vn-spinner>
<div ng-if="$ctrl.mana != null">
<vn-horizontal class="header vn-pa-md">
<div class="header vn-pa-md">
<h5>MANÁ: {{$ctrl.mana | currency: 'EUR':0}}</h5>
</vn-horizontal>
</div>
<div class="vn-pa-md">
<vn-input-number
vn-focus
@ -225,14 +237,16 @@
</div>
</div>
</div>
</vn-popover>
</div>
</vn-popover>
<!-- Edit Popover -->
<vn-popover
class="edit dialog-summary"
vn-id="edit-popover"
on-open="$ctrl.getManaSalespersonMana()"
on-close="$ctrl.mana = null">
<!-- Edit Popover -->
<vn-popover
class="dialog-summary"
vn-id="edit-popover"
on-open="$ctrl.getManaSalespersonMana()"
on-close="$ctrl.mana = null">
<div class="edit-price">
<vn-spinner
ng-if="$ctrl.mana == null"
style="padding: 1em;"
@ -245,141 +259,137 @@
edit="$ctrl.edit"
on-hide="$ctrl.hideEditPopover()">
</vn-ticket-sale-edit-discount>
</vn-popover>
</div>
</vn-popover>
<!-- Edit Dialog -->
<vn-dialog
vn-id="editDialog"
class="edit"
on-open="$ctrl.getManaSalespersonMana()"
on-close="$ctrl.mana = null">
<tpl-body>
<vn-spinner
ng-if="$ctrl.mana == null"
style="padding: 1em;"
enable="true">
</vn-spinner>
<vn-ticket-sale-edit-discount
ng-if="$ctrl.mana != null"
mana="$ctrl.mana"
bulk="true"
edit="$ctrl.edit"
on-hide="$ctrl.hideEditDialog()">
</vn-ticket-sale-edit-discount>
</tpl-body>
</vn-dialog>
<!-- Transfer Popover -->
<vn-popover class="transfer" vn-id="transfer">
<div class="vn-pa-md">
<!-- Transfer Popover -->
<vn-popover vn-id="transfer">
<div class="vn-pa-md transfer">
<vn-horizontal>
<vn-one>
<h4 translate>Sales to transfer</h4>
<vn-table>
<vn-thead>
<vn-tr>
<vn-th number shrink>Id</vn-th>
<vn-th>Item</vn-th>
<vn-th number shrink>Quantity</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="sale in $ctrl.transfer.sales">
<vn-td number shrink>{{::sale.itemFk | zeroFill:6}}</vn-td>
<vn-td>
<span title="{{::sale.concept}}">{{::sale.concept}}</span>
</vn-td>
<vn-td-editable number shrink>
<text>{{sale.quantity}}</text>
<field>
<vn-input-number
vn-focus
ng-model="sale.quantity">
</vn-input-number>
</field>
</vn-td-editable>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-one>
<vn-one>
<vn-horizontal>
<vn-one>
<h4 translate>Sales to transfer</h4>
<vn-table>
<vn-thead>
<vn-tr>
<vn-th number shrink>Id</vn-th>
<vn-th>Item</vn-th>
<vn-th number shrink>Quantity</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="sale in $ctrl.transfer.sales">
<vn-td number shrink>{{::sale.itemFk | zeroFill:6}}</vn-td>
<vn-td>
<span title="{{::sale.concept}}">{{::sale.concept}}</span>
</vn-td>
<vn-td-editable number shrink>
<text>{{sale.quantity}}</text>
<field>
<vn-input-number
vn-focus
ng-model="sale.quantity">
</vn-input-number>
</field>
</vn-td-editable>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-one>
<vn-one>
<vn-horizontal>
<h4 vn-one translate>Destination ticket</h4>
<vn-icon vn-none
color-secondary
vn-tooltip="You have to allow pop-ups in your web browser to use this functionality"
icon="info">
</vn-icon>
</vn-horizontal>
<table class="vn-grid">
<thead>
<tr>
<th number>ID</th>
<th number>F. envio</th>
<th number>Agencia</th>
<th number>Almacen</th>
</tr>
</thead>
<tbody>
<tr ng-if="$ctrl.transfer.lastActiveTickets.length === 0" >
<td colspan="4" style="text-align: center" translate>No results</td>
</tr>
<tr
class="clickable"
ng-repeat="ticket in $ctrl.transfer.lastActiveTickets track by ticket.id"
ng-click="$ctrl.transferSales(ticket.id)">
<td number>{{::ticket.id}}</td>
<td number>{{::ticket.shipped | dateTime: 'dd/MM/yyyy'}}</td>
<td number>{{::ticket.agencyName}}</td>
<td number>{{::ticket.warehouseName}}</td>
</tr>
</tbody>
</table>
<vn-horizontal class="vn-py-md">
<vn-textfield
vn-one
label="Transfer to ticket"
ng-model="$ctrl.transfer.ticketId"
type="number">
</vn-textfield>
<vn-icon-button
vn-none
icon="arrow_forward_ios"
ng-click="$ctrl.transferSales($ctrl.transfer.ticketId)">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal class="vn-py-md">
<vn-button
label="New ticket"
ng-click="$ctrl.transferSales()">
</vn-button>
</vn-horizontal>
</vn-one>
<h4 vn-one translate>Destination ticket</h4>
<vn-icon vn-none
color-secondary
vn-tooltip="You have to allow pop-ups in your web browser to use this functionality"
icon="info">
</vn-icon>
</vn-horizontal>
</div>
</vn-popover>
<table class="vn-table">
<thead>
<tr>
<th number>Id</th>
<th number>F. envio</th>
<th number>Agencia</th>
<th number>Almacen</th>
</tr>
</thead>
<tbody>
<tr ng-if="$ctrl.transfer.lastActiveTickets.length === 0" >
<td colspan="4" style="text-align: center" translate>No results</td>
</tr>
<tr
class="clickable"
ng-repeat="ticket in $ctrl.transfer.lastActiveTickets track by ticket.id"
ng-click="$ctrl.transferSales(ticket.id)">
<td number>{{::ticket.id}}</td>
<td number>{{::ticket.shipped | dateTime: 'dd/MM/yyyy'}}</td>
<td number>{{::ticket.agencyName}}</td>
<td number>{{::ticket.warehouseName}}</td>
</tr>
</tbody>
</table>
<vn-horizontal class="vn-py-md">
<vn-textfield
vn-one
label="Transfer to ticket"
ng-model="$ctrl.transfer.ticketId"
type="number">
</vn-textfield>
<vn-icon-button
vn-none
icon="arrow_forward_ios"
ng-click="$ctrl.transferSales($ctrl.transfer.ticketId)">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal class="vn-py-md">
<vn-button
label="New ticket"
ng-click="$ctrl.transferSales()">
</vn-button>
</vn-horizontal>
</vn-one>
</vn-horizontal>
</div>
</vn-popover>
<!-- Edit Dialog -->
<vn-dialog
vn-id="editDialog"
class="edit"
on-open="$ctrl.getManaSalespersonMana()"
on-close="$ctrl.mana = null">
<tpl-body>
<vn-spinner
ng-if="$ctrl.mana == null"
style="padding: 1em;"
enable="true">
</vn-spinner>
<vn-ticket-sale-edit-discount
ng-if="$ctrl.mana != null"
mana="$ctrl.mana"
bulk="true"
edit="$ctrl.edit"
on-hide="$ctrl.hideEditDialog()">
</vn-ticket-sale-edit-discount>
</tpl-body>
</vn-dialog>
<!-- SMS Dialog -->
<vn-client-sms
vn-id="sms"
sms="$ctrl.newSMS">
</vn-client-sms>
<!-- SMS Dialog -->
<vn-client-sms vn-id="sms" sms="$ctrl.newSMS"></vn-client-sms>
<!-- SMS Dialog -->
</vn-vertical>
<vn-confirm
vn-id="delete-lines"
question="You are going to delete lines of the ticket"
message="Continue anyway?"
on-response="$ctrl.onRemoveLinesClick(response)">
</vn-confirm>
<vn-confirm
vn-id="delete-ticket"
question="Do you want to delete it?"
message="This ticket is now empty"
on-response="$ctrl.transferSales($ctrl.transfer.ticketId, response)">
</vn-confirm>
<vn-float-button
ng-show="$ctrl.isEditable"
ng-click="$ctrl.newOrderFromTicket()"
icon="add"
vn-tooltip="Add item"
vn-bind="+"
fixed-bottom-right>
</vn-float-button>
</vn-confirm>

View File

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

View File

@ -9,64 +9,61 @@
vn-id="watcher"
data="$ctrl.services">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" compact>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-one>
<vn-horizontal ng-repeat="service in $ctrl.services track by $index">
<vn-autocomplete vn-one
vn-focus
url="/api/TicketServiceTypes"
label="Description"
show-field="name"
value-field="name"
ng-model="service.description">
</vn-autocomplete>
<vn-icon-button
vn-auto
class="vn-my-md"
icon="add_circle"
vn-tooltip="New service type"
ng-click="$ctrl.newServiceTypeDialog($index)"
vn-acl="administrative">
</vn-icon-button>
<vn-input-number
vn-one min="0"
step="1"
label="Quantity"
ng-model="service.quantity"
rule="TicketService.quantity">
</vn-input-number>
<vn-input-number
vn-one
label="Price"
ng-model="service.price"
step="0.01">
</vn-input-number>
<vn-autocomplete vn-one
url="/api/TaxClasses"
label="Tax class"
show-field="description"
value-field="id"
ng-model="service.taxClassFk">
</vn-autocomplete>
<vn-auto class="vn-pt-md">
<vn-icon-button
pointer
vn-tooltip="Remove service"
icon="delete"
ng-click="model.remove($index)">
</vn-icon-button>
</vn-one>
</vn-horizontal>
</vn-one>
<vn-one>
<vn-horizontal ng-repeat="service in $ctrl.services track by $index">
<vn-autocomplete
vn-one
vn-focus
url="/api/TicketServiceTypes"
label="Description"
show-field="name"
value-field="name"
ng-model="service.description">
</vn-autocomplete>
<vn-icon-button
vn-tooltip="Add service"
vn-bind="+"
vn-auto
class="vn-my-md"
icon="add_circle"
ng-click="$ctrl.add()">
vn-tooltip="New service type"
ng-click="$ctrl.newServiceTypeDialog($index)"
vn-acl="administrative">
</vn-icon-button>
</vn-one>
<vn-input-number
vn-one min="0"
step="1"
label="Quantity"
ng-model="service.quantity"
rule="TicketService">
</vn-input-number>
<vn-input-number
vn-one
label="Price"
ng-model="service.price"
step="0.01">
</vn-input-number>
<vn-autocomplete vn-one
url="/api/TaxClasses"
label="Tax class"
show-field="description"
value-field="id"
ng-model="service.taxClassFk">
</vn-autocomplete>
<vn-auto>
<vn-icon-button
pointer
vn-tooltip="Remove service"
icon="delete"
ng-click="model.remove($index)">
</vn-icon-button>
</vn-auto>
</vn-horizontal>
<vn-icon-button
vn-tooltip="Add service"
vn-bind="+"
icon="add_circle"
ng-click="$ctrl.add()">
</vn-icon-button>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>

View File

@ -41,8 +41,7 @@ class Controller {
}
onNewServiceTypeOpen() {
this.newServiceType.name = '';
this.newServiceType = {};
this.nameInput = this.$element[0].querySelector('.edit input');
this.nameInput.focus();
}

View File

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

View File

@ -65,7 +65,7 @@
</vn-one>
<vn-auto name="sales">
<h4 translate>Sale</h4>
<vn-table class="vn-grid">
<vn-table>
<vn-thead>
<vn-tr>
<vn-th shrink></vn-th>
@ -108,7 +108,8 @@
max-length="6"
item="::sale.item"
name="::sale.concept"
sub-name="::sale.item.subName"/>
sub-name="::sale.item.subName">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::sale.price | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::sale.discount}} %</vn-td>

View File

@ -1,41 +1,39 @@
<vn-crud-model auto-load="false"
<vn-crud-model
vn-id="model"
url="/ticket/api/TicketTrackings"
filter="::$ctrl.filter"
link="{ticketFk: $ctrl.$stateParams.id}"
limit="20"
data="trackings">
data="trackings"
order="created DESC"
auto-load="true">
</vn-crud-model>
<vn-vertical compact>
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-title>Tracking</vn-title>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="state">State</vn-th>
<vn-th field="worker">Worker</vn-th>
<vn-th field="created" default-order="DESC">Created</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="tracking in trackings">
<vn-td>{{::tracking.state.name}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="$ctrl.showWorkerDescriptor($event, tracking.worker.user.id)">
{{::tracking.worker.user.nickname | dashIfEmpty}}
</span>
</vn-td>
<vn-td>{{::tracking.created | dateTime:'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-vertical>
<vn-pagination model="model"></vn-pagination>
<vn-data-viewer model="model">
<vn-card class="vn-w-md">
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="state">State</vn-th>
<vn-th field="worker">Worker</vn-th>
<vn-th field="created">Created</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="tracking in trackings">
<vn-td>{{::tracking.state.name}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="$ctrl.showWorkerDescriptor($event, tracking.worker.user.id)">
{{::tracking.worker.user.nickname | dashIfEmpty}}
</span>
</vn-td>
<vn-td>{{::tracking.created | dateTime:'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-vertical>
</vn-data-viewer>
<a ui-sref="ticket.card.tracking.edit" vn-bind="+" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>

View File

@ -1,88 +1,90 @@
<vn-crud-model auto-load="false"
<vn-crud-model
vn-id="model"
url="/ticket/api/TicketWeeklies/filter"
filter="::$ctrl.filter"
limit="20"
data="weeklies"
order="ticketFk"
primary-key="ticketFk">
primary-key="ticketFk"
auto-load="true">
</vn-crud-model>
<div class="content-block">
<div class="vn-list">
<vn-card class="vn-px-md">
<vn-horizontal>
<vn-searchbar
vn-id="turnSearchbar"
style="width: 100%"
on-search="$ctrl.onSearch($params)"
info="Search weekly ticket by id or client id"
vn-focus>
</vn-searchbar>
</vn-horizontal>
<vn-card class="vn-px-md vn-w-sm">
<vn-searchbar
vn-id="turnSearchbar"
style="width: 100%"
on-search="$ctrl.onSearch($params)"
info="Search weekly ticket by id or client id"
vn-focus>
</vn-searchbar>
</vn-card>
<vn-data-viewer
model="model"
class="vn-w-md vn-my-md vn-mb-xl">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="ticketFk" number>Ticket ID</vn-th>
<vn-th field="weekDay">Client</vn-th>
<vn-th>Weekday</vn-th>
<vn-th>Warehouse</vn-th>
<vn-th>Salesperson</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="weekly in weeklies" class="clickable" ui-sref="ticket.card.summary({id: {{::weekly.ticketFk}}})">
<vn-td number>
<span class="link"
ng-click="$ctrl.showTicketDescriptor($event, weekly.ticketFk)">
{{weekly.ticketFk}}
</span>
</vn-td>
<vn-td>
<span class="link"
ng-click="$ctrl.showClientDescriptor($event, weekly.clientFk)"
title ="{{::weekly.clientName}}">
{{::weekly.clientName}}
</span>
</vn-td>
<vn-td ng-click="$ctrl.preventNavigation($event)">
<vn-autocomplete
vn-one
vn-id="weekday"
ng-model="weekly.weekDay"
data="$ctrl.weekdays"
show-field="name"
translate-fields="['name']"
value-field="id"
on-change="$ctrl.onWeekdayUpdate(weekly.ticketFk, weekday.selection.id)"
order="id"
class="dense">
</vn-autocomplete>
</vn-td>
<vn-td>{{::weekly.warehouseName}}</vn-td>
<vn-td>
<span class="link"
ng-click="$ctrl.showWorkerDescriptor($event, weekly.workerFk)">
{{::weekly.nickName}}
</span>
</vn-td>
<vn-td shrink>
<vn-icon-button
icon="delete"
ng-click="$ctrl.deleteWeekly($index)"
vn-tooltip="Delete">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</div>
<div class="vn-ma-md">
<vn-card class="vn-my-md vn-pa-md compact">
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="ticketFk" number>Ticket ID</vn-th>
<vn-th field="weekDay">Client</vn-th>
<vn-th>Weekday</vn-th>
<vn-th>Warehouse</vn-th>
<vn-th>Salesperson</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="weekly in weeklies" class="clickable" ui-sref="ticket.card.summary({id: {{::weekly.ticketFk}}})">
<vn-td number>
<span class="link"
ng-click="$ctrl.showTicketDescriptor($event, weekly.ticketFk)">
{{weekly.ticketFk}}
</span>
</vn-td>
<vn-td>
<span class="link"
ng-click="$ctrl.showClientDescriptor($event, weekly.clientFk)"
title ="{{::weekly.clientName}}">
{{::weekly.clientName}}
</span>
</vn-td>
<vn-td ng-click="$ctrl.preventNavigation($event)">
<vn-autocomplete vn-one
vn-id="weekday"
ng-model="weekly.weekDay"
data="$ctrl.weekdays"
show-field="name"
translate-fields="['name']"
value-field="id"
on-change="$ctrl.onWeekdayUpdate(weekly.ticketFk, weekday.selection.id)"
order="id">
</vn-autocomplete>
</vn-td>
<vn-td>{{::weekly.warehouseName}}</vn-td>
<vn-td>
<span class="link"
ng-click="$ctrl.showWorkerDescriptor($event, weekly.workerFk)">
{{::weekly.nickName}}
</span>
</vn-td>
<vn-td shrink>
<vn-icon-button
icon="delete"
ng-click="$ctrl.deleteWeekly($index)"
vn-tooltip="Delete">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
<vn-pagination model="model"></vn-pagination>
</div>
</vn-data-viewer>
</div>
<vn-client-descriptor-popover vn-id="clientDescriptor"></vn-client-descriptor-popover>
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>
<vn-ticket-descriptor-popover
vn-id="ticketDescriptor">
</vn-ticket-descriptor-popover>
@ -95,6 +97,9 @@
question="This ticket will be removed from weekly tickets! Continue anyway?"
message="You are going to delete this weekly ticket">
</vn-confirm>
<a ui-sref="ticket.weekly.create" vn-tooltip="Add weekly ticket" vn-bind="+" fixed-bottom-right>
<a ui-sref="ticket.weekly.create"
vn-tooltip="Add weekly ticket"
vn-bind="+"
fixed-bottom-right>
<vn-float-button icon="person_add"></vn-float-button>
</a>