Merge pull request '1882-descriptorRefactor' (#250) from 1882-descriptorRefactor into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
This commit is contained in:
Juan Ferrer 2020-05-19 11:15:20 +00:00
commit 1892447b60
249 changed files with 3193 additions and 5199 deletions

View File

@ -31,6 +31,7 @@ rules:
curly: [error, multi-or-nest]
indent: [error, 4]
arrow-parens: [error, as-needed]
jasmine/no-focused-tests: 0
no-multiple-empty-lines: ["error", { "max": 1, "maxEOF": 1 }]
space-in-parens: ["error", "never"]
space-in-parens: ["error", "never"]
jasmine/no-focused-tests: 0
jasmine/prefer-toHaveBeenCalledWith: 0

View File

@ -5,11 +5,11 @@ export default {
userMenuButton: '#user',
logoutButton: '#logout',
applicationsMenuVisible: '.modules-menu',
clientsButton: '.modules-menu > li[ui-sref="client.index"]',
itemsButton: '.modules-menu > li[ui-sref="item.index"]',
ticketsButton: '.modules-menu > li[ui-sref="ticket.index"]',
invoiceOutButton: '.modules-menu > li[ui-sref="invoiceOut.index"]',
claimsButton: '.modules-menu > li[ui-sref="claim.index"]',
clientsButton: '.modules-menu [ui-sref="client.index"]',
itemsButton: '.modules-menu [ui-sref="item.index"]',
ticketsButton: '.modules-menu [ui-sref="ticket.index"]',
invoiceOutButton: '.modules-menu [ui-sref="invoiceOut.index"]',
claimsButton: '.modules-menu [ui-sref="claim.index"]',
returnToModuleIndexButton: 'a[ui-sref="order.index"]',
homeButton: 'vn-topbar > div.side.start > a',
userLocalWarehouse: '.user-popover vn-autocomplete[ng-model="$ctrl.localWarehouseFk"]',
@ -56,8 +56,8 @@ export default {
cancelButton: 'vn-button[href="#!/client/index"]'
},
clientDescriptor: {
moreMenu: 'vn-client-descriptor vn-icon-menu[icon=more_vert]',
simpleTicketButton: '.vn-drop-down.shown li[name="Simple ticket"]'
moreMenu: 'vn-client-descriptor vn-icon-button[icon=more_vert]',
simpleTicketButton: '.vn-menu [name="simpleTicket"]'
},
clientBasicData: {
basicDataButton: 'vn-left-menu a[ui-sref="client.card.basicData"]',
@ -241,14 +241,14 @@ export default {
},
itemDescriptor: {
goBackToModuleIndexButton: 'vn-item-descriptor a[href="#!/item/index"]',
moreMenu: 'vn-item-descriptor vn-icon-menu[icon=more_vert]',
moreMenuRegularizeButton: '.vn-drop-down.shown li[name="Regularize stock"]',
moreMenu: 'vn-item-descriptor vn-icon-button[icon=more_vert]',
moreMenuRegularizeButton: '.vn-menu [name="regularizeStock"]',
regularizeQuantity: '.vn-dialog.shown vn-textfield[ng-model="$ctrl.quantity"]',
regularizeWarehouse: '.vn-dialog.shown vn-autocomplete[ng-model="$ctrl.warehouseFk"]',
editButton: 'vn-item-descriptor vn-float-button[icon="edit"]',
regularizeSaveButton: '.vn-dialog.shown tpl-buttons > button',
inactiveIcon: 'vn-item-descriptor vn-icon[icon="icon-unavailable"]',
navigateBackToIndex: 'vn-item-descriptor vn-icon[icon="chevron_left"]'
navigateBackToIndex: 'vn-item-descriptor [name="goToModuleIndex"]'
},
itemRequest: {
firstRequestItemID: 'vn-item-request vn-tbody > vn-tr:nth-child(1) > vn-td-editable:nth-child(7)',
@ -378,7 +378,7 @@ export default {
topbarSearch: 'vn-searchbar',
advancedSearchButton: 'vn-ticket-search-panel button[type=submit]',
searchButton: 'vn-searchbar vn-icon[icon="search"]',
moreMenu: 'vn-ticket-index vn-icon-menu[icon=more_vert]',
moreMenu: 'vn-ticket-index vn-icon-button[icon=more_vert]',
sixthWeeklyTicket: 'vn-ticket-weekly-index vn-table vn-tr:nth-child(6)',
weeklyTicket: 'vn-ticket-weekly-index vn-table > div > vn-tbody > vn-tr',
firstWeeklyTicketDeleteIcon: 'vn-ticket-weekly-index vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
@ -397,13 +397,13 @@ export default {
idLabelValue: 'vn-ticket-descriptor vn-label-value[label="Id"]',
stateLabelValue: 'vn-ticket-descriptor vn-label-value[label="State"]',
goBackToModuleIndexButton: 'vn-ticket-descriptor a[ui-sref="ticket.index"]',
moreMenu: 'vn-ticket-descriptor vn-icon-menu[icon=more_vert]',
moreMenuAddStowaway: '.vn-drop-down.shown li[name="Add stowaway"]',
moreMenuDeleteStowawayButton: '.vn-drop-down.shown li[name="Delete stowaway"]',
moreMenuAddToTurn: '.vn-drop-down.shown li[name="Add turn"]',
moreMenuDeleteTicket: '.vn-drop-down.shown li[name="Delete ticket"]',
moreMenuMakeInvoice: '.vn-drop-down.shown li[name="Make invoice"]',
moreMenuChangeShippedHour: '.vn-drop-down.shown li[name="Change shipped hour"]',
moreMenu: 'vn-ticket-descriptor vn-icon-button[icon=more_vert]',
moreMenuAddStowaway: '.vn-menu [name="addStowaway"]',
moreMenuDeleteStowawayButton: '.vn-menu [name="deleteStowaway"]',
moreMenuAddToTurn: '.vn-menu [name="addTurn"]',
moreMenuDeleteTicket: '.vn-menu [name="deleteTicket"]',
moreMenuMakeInvoice: '.vn-menu [name="makeInvoice"]',
moreMenuChangeShippedHour: '.vn-menu [name="changeShipped"]',
changeShippedHourDialog: '.vn-dialog.shown',
changeShippedHour: '.vn-dialog.shown vn-input-time[ng-model="$ctrl.newShipped"]',
addStowawayDialogFirstTicket: '.vn-dialog.shown vn-table vn-tbody vn-tr',
@ -412,7 +412,7 @@ export default {
saturdayButton: '.vn-popup.shown vn-tool-bar > vn-button:nth-child(6)',
acceptDeleteButton: '.vn-dialog.shown button[response="accept"]',
acceptChangeHourButton: '.vn-dialog.shown button[response="accept"]',
descriptorDeliveryDate: 'vn-ticket-descriptor > div > div.body > div.attributes > vn-label-value:nth-child(6) > section > span',
descriptorDeliveryDate: 'vn-ticket-descriptor slot-body > .attributes > vn-label-value:nth-child(3) > section > span',
acceptInvoiceOutButton: '.vn-confirm.shown button[response="accept"]',
acceptDeleteStowawayButton: '.vn-dialog.shown button[response="accept"]'
},
@ -442,9 +442,9 @@ export default {
saleLine: 'vn-table div > vn-tbody > vn-tr',
saleDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
saleDescriptorPopoverSummaryButton: '.vn-popover.shown vn-item-descriptor a[ui-sref="item.card.summary({id: $ctrl.item.id})"]',
descriptorItemDiaryButton: 'vn-item-descriptor .quicklinks.ng-scope > vn-horizontal > a > vn-icon > i',
descriptorItemDiaryButton: '.vn-popover vn-item-descriptor vn-quick-link[icon="icon-transaction"] > a',
newItemFromCatalogButton: 'vn-ticket-sale vn-float-button[icon="add"]',
newItemButton: 'vn-ticket-sale > vn-vertical > vn-card > vn-vertical > vn-one > vn-icon-button > button > vn-icon > i',
newItemButton: 'vn-ticket-sale vn-card vn-icon-button[icon="add_circle"]',
moreMenu: 'vn-ticket-sale vn-tool-bar > vn-button-menu[vn-id="more-button"] > div > button',
moreMenuCreateClaim: '.vn-drop-down.shown li[name="Add claim"]',
moreMenuReserve: '.vn-drop-down.shown li[name="Mark as reserved"]',
@ -479,8 +479,8 @@ export default {
secondSaleIdInput: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(4) > vn-autocomplete',
secondSaleIdAutocomplete: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(4) > vn-autocomplete',
secondSaleQuantity: 'vn-ticket-sale vn-table vn-tr:nth-child(2) vn-input-number',
secondSaleConceptCell: 'vn-ticket-sale > vn-vertical > vn-card > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td-editable:nth-child(6)',
secondSaleConceptInput: 'vn-ticket-sale vn-table vn-tr:nth-child(2) > vn-td-editable.ng-isolate-scope.selected vn-textfield',
secondSaleConceptCell: 'vn-ticket-sale vn-tbody > :nth-child(2) > :nth-child(6)',
secondSaleConceptInput: 'vn-ticket-sale vn-tbody > :nth-child(2) > vn-td-editable.ng-isolate-scope.selected vn-textfield',
totalImport: 'vn-ticket-sale > vn-vertical > vn-card > vn-vertical > vn-horizontal > vn-one > p:nth-child(3) > strong',
selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check',
secondSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(2) vn-check[ng-model="sale.checked"]',
@ -558,8 +558,8 @@ export default {
searchButton: 'vn-searchbar vn-icon[icon="search"]'
},
claimDescriptor: {
moreMenu: 'vn-claim-descriptor vn-icon-menu[icon=more_vert]',
moreMenuDeleteClaim: '.vn-drop-down.shown li[name="Delete claim"]',
moreMenu: 'vn-claim-descriptor vn-icon-button[icon=more_vert]',
moreMenuDeleteClaim: '.vn-menu [name="deleteClaim"]',
acceptDeleteClaim: '.vn-confirm.shown button[response="accept"]'
},
claimSummary: {
@ -569,9 +569,9 @@ export default {
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',
itemDescriptorPopoverItemDiaryButton: '.vn-popover.shown vn-item-descriptor a[href="#!/item/2/diary"]',
itemDescriptorPopoverItemDiaryButton: '.vn-popover vn-item-descriptor vn-quick-link[icon="icon-transaction"] > a',
firstDevelopmentWorker: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > span',
firstDevelopmentWorkerGoToClientButton: '.vn-popover.shown vn-worker-descriptor vn-quick-links > a[href="#!/client/21/summary"]',
firstDevelopmentWorkerGoToClientButton: '.vn-popover vn-worker-descriptor vn-quick-link[icon="person"] > a',
firstActionTicketId: 'vn-claim-summary > vn-card > vn-horizontal > vn-auto:nth-child(6) vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > span',
firstActionTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor'
},
@ -787,7 +787,7 @@ export default {
weekWorkedHours: 'vn-worker-time-control vn-side-menu vn-label-value > section > span',
nextMonthButton: 'vn-worker-time-control vn-side-menu vn-calendar vn-button[icon=keyboard_arrow_right]',
secondWeekDay: 'vn-worker-time-control vn-side-menu vn-calendar .day:nth-child(8) > .day-number',
navigateBackToIndex: 'vn-worker-descriptor vn-icon[icon="chevron_left"]',
navigateBackToIndex: 'vn-worker-descriptor [name="goToModuleIndex"]',
acceptDeleteDialog: '.vn-confirm.shown button[response="accept"]'
},
invoiceOutIndex: {
@ -796,10 +796,10 @@ export default {
searchResult: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
},
invoiceOutDescriptor: {
moreMenu: 'vn-invoice-out-descriptor vn-icon-menu[icon=more_vert]',
moreMenuDeleteInvoiceOut: '.vn-drop-down.shown li[name="Delete Invoice"]',
moreMenuBookInvoiceOut: '.vn-drop-down.shown li[name="Book invoice"]',
moreMenuShowInvoiceOutPdf: '.vn-drop-down.shown li[name="Show invoice PDF"]',
moreMenu: 'vn-invoice-out-descriptor vn-icon-button[icon=more_vert]',
moreMenuDeleteInvoiceOut: '.vn-menu [name="deleteInvoice"]',
moreMenuBookInvoiceOut: '.vn-menu [name="bookInvoice"]',
moreMenuShowInvoiceOutPdf: '.vn-menu [name="showInvoicePdf"]',
acceptDeleteButton: '.vn-confirm.shown button[response="accept"]',
acceptBookingButton: '.vn-confirm.shown button[response="accept"]'
},
@ -851,8 +851,8 @@ export default {
confirmed: 'vn-entry-summary vn-check[label="Confirmed"]',
},
entryDescriptor: {
agency: 'vn-entry-descriptor div.body vn-label-value:nth-child(3) span',
travelsQuicklink: 'vn-entry-descriptor vn-quick-links > a:nth-child(1)',
entriesQuicklink: 'vn-entry-descriptor vn-quick-links > a:nth-child(2)'
agency: 'vn-entry-descriptor div.body vn-label-value:nth-child(2) span',
travelsQuicklink: 'vn-entry-descriptor vn-quick-link[icon="local_airport"] > a',
entriesQuicklink: 'vn-entry-descriptor vn-quick-link[icon="icon-entry"] > a'
}
};

View File

@ -16,7 +16,7 @@
</div>
<div class="fix suffix"></div>
<label>
<span translate>{{::$ctrl.label}}</span>
<span translate>{{$ctrl.label}}</span>
<span class="required">*</span>
</label>
</div>

View File

@ -1,10 +1,7 @@
<div>
<tpl-body>
<h6 translate>{{::$ctrl.message}}</h6>
<span translate>{{::$ctrl.question}}</span>
</tpl-body>
<tpl-buttons>
<button response="cancel" translate>Cancel</button>
<button response="accept" translate vn-focus>Accept</button>
</tpl-buttons>
</div>
<tpl-body translate>
{{::$ctrl.question}}
</tpl-body>
<tpl-buttons>
<button response="cancel" translate>Cancel</button>
<button response="accept" translate vn-focus>Accept</button>
</tpl-buttons>

View File

@ -1,20 +1,13 @@
import ngModule from '../../module';
import Dialog from '../dialog';
import template from './confirm.html';
import './style.scss';
export default class Confirm extends Dialog {
constructor($element, $, $transclude) {
super($element, $, $transclude);
this.fillSlots(template);
}
}
export default class Confirm extends Dialog {}
ngModule.vnComponent('vnConfirm', {
slotTemplate: require('./confirm.html'),
controller: Confirm,
transclude: true,
bindings: {
question: '@',
message: '@?'
question: '@'
}
});

View File

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

View File

@ -1,6 +1,6 @@
@import "variables";
vn-debug-info {
.vn-debug-info {
position: fixed;
bottom: 16px;
left: 16px;
@ -13,8 +13,9 @@ vn-debug-info {
box-shadow: $shadow;
transition: opacity 400ms ease-in-out;
&:hover {
opacity: .5;
&.right {
left: auto;
right: 16px;
}
& > h6 {
font-weight: normal;

View File

@ -1,22 +1,31 @@
<div
ng-if="$ctrl.loading"
class="loading-overlap shown">
<vn-spinner enable="true"></vn-spinner>
</div>
<vn-button
ng-click="$ctrl.hide()"
translate-attr="{title: 'Close'}"
icon="clear"
class="flat close">
</vn-button>
<form>
<default>
<div
class="body"
ng-transclude="body">
ng-if="$ctrl.loading"
class="loading-overlap shown">
<vn-spinner enable="true"></vn-spinner>
</div>
<div
class="buttons"
ng-click="$ctrl.onButtonClick($event)"
ng-transclude="buttons">
</div>
</form>
<vn-button
ng-click="$ctrl.hide()"
translate-attr="{title: 'Close'}"
icon="clear"
class="flat close">
</vn-button>
<form>
<h6
ng-if="::$ctrl.message || $ctrl.$transclude.isSlotFilled('title')"
ng-transclude="title">
<tpl-title translate>
{{$ctrl.message}}
</tpl-title>
</h6>
<div
class="body"
ng-transclude="body">
</div>
<div
class="buttons"
ng-click="$ctrl.onButtonClick($event)"
ng-transclude="buttons">
</div>
</form>
</default>

View File

@ -1,6 +1,5 @@
import ngModule from '../../module';
import Popup from '../popup';
import template from './index.html';
import './style.scss';
/**
@ -16,22 +15,6 @@ import './style.scss';
* @slot buttons The dialog HTML buttons
*/
export default class Dialog extends Popup {
constructor($element, $, $transclude) {
super($element, $, $transclude);
this.fillDefaultSlot(template);
}
/**
* Fills the dialog slots, it is intended to be used by child classes.
*
* @param {String} template The HTML template string
*/
fillSlots(template) {
let $template = angular.element(template);
this.fillSlot('body', $template.find('tpl-body'));
this.fillSlot('buttons', $template.find('tpl-buttons'));
}
/**
* Shows the dialog and optionally registers a handler for the response.
*
@ -149,12 +132,15 @@ export default class Dialog extends Popup {
}
ngModule.vnComponent('vnDialog', {
slotTemplate: require('./index.html'),
controller: Dialog,
transclude: {
body: 'tplBody',
title: '?tplTitle',
body: '?tplBody',
buttons: '?tplButtons'
},
bindings: {
message: '@?',
onResponse: '&?',
onAccept: '&?'
}

View File

@ -18,6 +18,9 @@
& > form {
padding: $spacing-lg;
& > h6 {
margin-bottom: $spacing-md;
}
& > .body > tpl-body {
display: block;
min-width: 256px;

View File

@ -1,22 +1,24 @@
<div ng-show="$ctrl.showFilter" class="filter">
<vn-textfield
ng-model="$ctrl.search"
class="dense search"
ng-blur="$ctrl.onFocusOut()"
placeholder="{{::'Search' | translate}}"
autocomplete="off">
</vn-textfield>
</div>
<div class="list" tabindex="-1">
<ul
class="dropdown"
ng-click="$ctrl.onContainerClick($event)">
</ul>
<div
ng-if="$ctrl.statusText"
ng-click="$ctrl.onLoadMoreClick($event)"
class="status"
translate>
{{$ctrl.statusText}}
<default>
<div ng-show="$ctrl.showFilter" class="filter">
<vn-textfield
ng-model="$ctrl.search"
class="dense search"
ng-blur="$ctrl.onFocusOut()"
placeholder="{{::'Search' | translate}}"
autocomplete="off">
</vn-textfield>
</div>
</div>
<div class="list" tabindex="-1">
<ul
class="dropdown"
ng-click="$ctrl.onContainerClick($event)">
</ul>
<div
ng-if="$ctrl.statusText"
ng-click="$ctrl.onLoadMoreClick($event)"
class="status"
translate>
{{$ctrl.statusText}}
</div>
</div>
</default>

View File

@ -1,7 +1,6 @@
import './style.scss';
import ngModule from '../../module';
import Popover from '../popover';
import template from './index.html';
import ArrayModel from '../array-model/array-model';
import CrudModel from '../crud-model/crud-model';
import {mergeWhere} from 'vn-loopback/util/filter';
@ -21,7 +20,6 @@ export default class DropDown extends Popover {
this.showLoadMore = true;
this.showFilter = true;
this.searchDelay = 300;
this.fillDefaultSlot(template);
}
get search() {
@ -458,6 +456,7 @@ function getPosition(parent, event) {
}
ngModule.vnComponent('vnDropDown', {
slotTemplate: require('./index.html'),
controller: DropDown,
transclude: {
tplItem: '?tplItem'

View File

@ -8,7 +8,7 @@
<div class="control"></div>
<div class="fix suffix"></div>
<label>
<span translate>{{::$ctrl.label}}</span>
<span translate>{{$ctrl.label}}</span>
<span class="required">*</span>
</label>
</div>

View File

@ -9,12 +9,6 @@ import Component from '../../lib/component';
* @property {Boolean} disabled Put component in disabled mode
*/
export default class FormInput extends Component {
constructor($element, $scope) {
super($element, $scope);
this.classList = this.element.classList;
this.classList.add(...this.constructor.$classNames);
}
$onInit() {
// XXX: Compatibility with old inputs
let attrs = this.$element[0].attributes;

View File

@ -7,6 +7,8 @@ vn-icon {
& > i,
& > i.material-icons {
display: block;
}
& > i.material-icons {
font-size: inherit;
}
}

View File

@ -19,7 +19,7 @@
</div>
<div class="fix suffix"></div>
<label>
<span translate>{{::$ctrl.label}}</span>
<span translate>{{$ctrl.label}}</span>
<span class="required">*</span>
</label>
</div>

View File

@ -8,7 +8,7 @@
<div class="control"></div>
<div class="fix suffix"></div>
<label>
<span translate>{{::$ctrl.label}}</span>
<span translate>{{$ctrl.label}}</span>
<span class="required">*</span>
</label>
</div>

View File

@ -1,27 +1,11 @@
@import "./effects";
/*
ul.menu {
list-style-type: none;
padding: 0;
padding-top: $spacing-md;
margin: 0;
font-size: inherit;
& > li > a {
@extend %clickable;
display: block;
color: inherit;
padding: 9px 32px;
}
}
*/
vn-list,
.vn-list {
display: block;
max-width: $width-sm;
margin: 0 auto;
padding: 0;
padding: $spacing-sm 0;
list-style-type: none;
vn-list,
@ -32,6 +16,8 @@ vn-list,
}
}
&.separated {
padding: 0;
vn-item,
.vn-item {
border-bottom: $border-thin-light;

View File

@ -1,6 +1,5 @@
import ngModule from '../../module';
import Popup from '../popup';
import template from './index.html';
import isMobile from '../../lib/is-mobile';
import './style.scss';
@ -8,24 +7,27 @@ import './style.scss';
* A simple popover.
*
* @property {HTMLElement} parent The parent element to show drop down relative to
* @property {HTMLElement} content Element holding the popover content
*
* @event open Thrown when popover is displayed
* @event close Thrown when popover is hidden
*/
export default class Popover extends Popup {
constructor($element, $, $transclude) {
super($element, $, $transclude);
constructor(...args) {
super(...args);
this.displayMode = isMobile ? 'centered' : 'relative';
this.template = template;
}
/**
* Shows the popover emitting the open signal. If a parent is specified
* it is shown in a visible relative position to it.
*
* @param {HTMLElement} parent Overrides the parent property
* @param {HTMLElement|Event} parent Overrides the parent property
*/
show(parent) {
if (parent instanceof Event)
parent = event.target;
if (parent) this.parent = parent;
super.show();
this.content = this.popup.querySelector('.content');
@ -44,7 +46,6 @@ export default class Popover extends Popup {
// Bug #2147 Popover loses parent location
set parent(value) {
this.__parent = value;
if (!value) return;
const parentRect = value.getBoundingClientRect();
@ -115,3 +116,7 @@ export default class Popover extends Popup {
ngModule.vnComponent('vnPopover', {
controller: Popover
});
ngModule.run(['$compile', function($compile) {
Popover.prototype.contentLinkFn = $compile(require('./index.html'));
}]);

View File

@ -1,18 +1,15 @@
import ngModule from '../../module';
import Component from '../../lib/component';
import template from './index.html';
import './style.scss';
/**
* Base class for windows displayed over application content.
*/
export default class Popup extends Component {
constructor($element, $scope, $transclude) {
super($element, $scope);
this.$transclude = $transclude;
constructor(...args) {
super(...args);
this._shown = false;
this.displayMode = 'centered';
this.template = template;
}
$onDestroy() {
@ -34,7 +31,7 @@ export default class Popup extends Component {
}
/**
* Displays the dialog to the user.
* Displays the popup.
*/
show() {
if (this.shown) return;
@ -45,11 +42,13 @@ export default class Popup extends Component {
this.onClose();
}
let linkFn = this.$compile(this.template);
this.$contentScope = this.$.$new();
this.popup = linkFn(this.$contentScope, null,
this.contentLinkFn(
this.$contentScope,
element => this.popup = element[0],
{parentBoundTranscludeFn: this.$transclude}
)[0];
);
this.windowEl = this.popup.querySelector('.window');
this.windowEl.focus();
@ -75,7 +74,7 @@ export default class Popup extends Component {
}
/**
* Hides the dialog calling the response handler.
* Hides the popup.
*/
hide() {
if (!this.shown) return;
@ -96,6 +95,11 @@ export default class Popup extends Component {
this.emit('closeStart');
}
/**
* Called when closing transition ends and popup is not visible, it can
* be overriden by child classes (calling the super) to perform additional
* actions.
*/
onClose() {
this.closeTimeout = null;
this.popup.remove();
@ -127,5 +131,10 @@ ngModule.vnComponent('vnPopup', {
transclude: true,
bindings: {
shown: '=?'
}
},
installClasses: false
});
ngModule.run(['$compile', function($compile) {
Popup.prototype.contentLinkFn = $compile(require('./index.html'));
}]);

View File

@ -1,6 +1,9 @@
@import "variables";
vn-scroll-up {
top: 88px;
right: 32px;
right: 0;
top: $topbar-height;
margin: $float-spacing;
display: none;
position: fixed;
}

View File

@ -5,6 +5,7 @@ vn-spinner {
min-height: 28px;
min-width: 28px;
color: $color-main;
overflow: hidden;
& > .loader {
position: relative;

View File

@ -9,7 +9,6 @@ function vnAcl(aclService) {
priority: -1,
link: function(_, $element, $attrs) {
acls = $attrs.vnAcl.split(',').map(i => i.trim());
if (acls[0] == '') return;
let action = $attrs.vnAclAction || 'disable';

View File

@ -0,0 +1,23 @@
import ngModule from '../module';
/*
* Registers a handler for the click event and stops propagation when event
* is thrown, mainly when nesting clickable elements wich ignore the
* Event.defaultPrevented property, like ui-sref.
*/
export function directive($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
const fn = $parse(attrs.vnClickStop);
element.on('click', function(event) {
fn(scope, {$event: event});
event.stopPropagation();
event.preventDefault();
});
}
};
}
directive.$inject = ['$parse'];
ngModule.directive('vnClickStop', directive);

View File

@ -2,6 +2,7 @@ import './id';
import './focus';
import './dialog';
import './popover';
import './click-stop';
import './rule';
import './acl';
import './on-error-src';

View File

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

View File

@ -6,11 +6,9 @@
</vn-icon-button>
<vn-dialog
class="modal-form"
vn-id="uvc">
vn-id="uvc"
message="Fields to show">
<tpl-body>
<vn-horizontal class="vn-pa-md header">
<h5><span translate>Fields to show</span></h5>
</vn-horizontal>
<div class="vn-pa-md">
<vn-horizontal ng-repeat="field in fields">
<vn-check

15
front/core/filters/id.js Normal file
View File

@ -0,0 +1,15 @@
import ngModule from '../module';
/**
* Formats the value as id.
*
* @return {String} The formated value
*/
export default function id() {
return function(value) {
if (value == null || value === '')
return '';
return `#${value}`;
};
}
ngModule.filter('id', id);

View File

@ -4,3 +4,4 @@ import './dash-if-empty';
import './percentage';
import './currency';
import './zero-fill';
import './id';

View File

@ -0,0 +1,21 @@
describe('Id filter', () => {
let idFilter;
beforeEach(ngModule('vnCore'));
beforeEach(inject(_idFilter_ => {
idFilter = _idFilter_;
}));
it('should return empty string for input null', () => {
expect(idFilter(null)).toBe('');
});
it('should return empty stringfor input empty', () => {
expect(idFilter('')).toBe('');
});
it('should prefix a pad character', () => {
expect(idFilter('123')).toBe('#123');
});
});

View File

@ -11,14 +11,29 @@ export default class Component extends EventEmitter {
*
* @param {HTMLElement} $element The main component element
* @param {$rootScope.Scope} $scope The element scope
* @param {Function} $transclude The transclusion function
*/
constructor($element, $scope) {
constructor($element, $scope, $transclude) {
super();
this.$ = $scope;
if (!$element) return;
this.element = $element[0];
this.element.$ctrl = this;
this.$element = $element;
this.$ = $scope;
this.$transclude = $transclude;
this.classList = this.element.classList;
const constructor = this.constructor;
const $options = constructor.$options;
if ($options && $options.installClasses)
this.classList.add(...this.constructor.$classNames);
if ($transclude && constructor.slotTemplates) {
for (let slotTemplate of constructor.slotTemplates)
this.fillSlots(slotTemplate);
}
}
$postLink() {
@ -26,7 +41,7 @@ export default class Component extends EventEmitter {
let attrs = this.$element[0].attributes;
let $scope = this.$;
for (let attr of attrs) {
if (attr.name.substr(0, 2) !== 'on') continue;
if (!attr.name.startsWith('on-')) continue;
let eventName = kebabToCamel(attr.name.substr(3));
let callback = locals => $scope.$parent.$eval(attr.nodeValue, locals);
this.on(eventName, callback);
@ -58,6 +73,72 @@ export default class Component extends EventEmitter {
return this.$translate.instant(string, params);
}
/**
* Fills the default transclude slot.
*
* @param {JQElement|String} template The slot template
*/
fillDefaultSlot(template) {
const linkFn = this.$compile(template);
this.$transclude.$$boundTransclude = this.createBoundTranscludeFn(linkFn);
}
/**
* Fills a named transclude slot.
*
* @param {String} slot The trasnclude slot name
* @param {JQElement|String} template The slot template
*/
fillSlot(slot, template) {
const linkFn = this.$compile(template);
const slots = this.$transclude.$$boundTransclude.$$slots;
slots[slot] = this.createBoundTranscludeFn(linkFn);
}
/**
* Fills component transclude slots using the passed HTML template string
* as source.
*
* @param {String} template The HTML template string
*/
fillSlots(template) {
const name = this.constructor.$options.name;
const transclude = this.constructor.$options.transclude;
if (!transclude)
throw new Error(`No transclusion option defined in '${name}'`);
if (!this.$transclude)
throw new Error(`No $transclude injected in '${name}'`);
let slotMap = {};
for (let slotName in transclude) {
let slotTag = transclude[slotName].match(/\w+$/)[0];
slotMap[slotTag] = slotName;
}
const $template = angular.element(template);
for (let i = 0; i < $template.length; i++) {
let slotElement = $template[i];
if (slotElement.nodeType != Node.ELEMENT_NODE) continue;
let tagName = kebabToCamel(slotElement.tagName.toLowerCase());
if (tagName == 'default')
this.fillDefaultSlot(slotElement.childNodes);
else {
let slotName = slotMap[tagName];
if (!slotName)
throw new Error(`No slot found for '${tagName}' in '${name}'`);
this.fillSlot(slotName, slotElement);
}
}
}
/**
* Creates a bounded transclude function from a linking function.
*
* @param {Function} linkFn The linking function
* @return {Function} The bounded transclude function
*/
createBoundTranscludeFn(linkFn) {
let scope = this.$;
let previousBoundTranscludeFn = this.$transclude.$$boundTransclude;
@ -78,17 +159,6 @@ export default class Component extends EventEmitter {
return vnBoundTranscludeFn;
}
fillDefaultSlot(template) {
let linkFn = this.$compile(template);
this.$transclude.$$boundTransclude = this.createBoundTranscludeFn(linkFn);
}
fillSlot(slot, template) {
let slots = this.$transclude.$$boundTransclude.$$slots;
let linkFn = this.$compile(template);
slots[slot] = this.createBoundTranscludeFn(linkFn);
}
copySlot(slot, $transclude) {
this.$transclude.$$boundTransclude.$$slots[slot] =
$transclude.$$boundTransclude.$$slots[slot];
@ -96,44 +166,25 @@ export default class Component extends EventEmitter {
}
Component.$inject = ['$element', '$scope'];
function runFn(
$translate,
$q,
$http,
$state,
$stateParams,
$timeout,
$transitions,
$compile,
$filter,
$interpolate,
$window,
vnApp,
vnToken,
vnConfig,
aclService) {
Object.assign(Component.prototype, {
$translate,
$q,
$http,
$state,
$params: $stateParams,
$timeout,
$transitions,
$compile,
$filter,
$interpolate,
$window,
vnApp,
vnToken,
vnConfig,
aclService
/*
* Automatically adds the most used services to the prototype, so they are
* available as component properties.
*/
function runFn(...args) {
const proto = Component.prototype;
for (let i = 0; i < runFn.$inject.length; i++)
proto[runFn.$inject[i]] = args[i];
Object.assign(proto, {
$params: proto.$stateParams
});
}
runFn.$inject = [
'$translate',
'$q',
'$http',
'$httpParamSerializer',
'$state',
'$stateParams',
'$timeout',
@ -145,6 +196,7 @@ runFn.$inject = [
'vnApp',
'vnToken',
'vnConfig',
'vnModules',
'aclService'
];

View File

@ -1,6 +1,16 @@
import {ng, ngDeps} from './vendor';
import {camelToKebab} from './lib/string';
/**
* Extended component options.
*
* @property {Boolean} installClassses Whether to install CSS classes equivalent to the component's and parents name
* @property {String} slotTemplate HTML template used to fill component transclude slots
*/
const defaultOptions = {
installClasses: true
};
/**
* Acts like native Module.component() function but merging component options
* with parent component options. This method establishes the $options property
@ -17,7 +27,7 @@ import {camelToKebab} from './lib/string';
function vnComponent(name, options) {
let controller = options.controller;
let parent = Object.getPrototypeOf(controller);
let parentOptions = parent.$options || {};
let parentOptions = parent.$options || defaultOptions;
let parentTransclude = parentOptions.transclude;
let transclude = parentTransclude instanceof Object
@ -32,10 +42,11 @@ function vnComponent(name, options) {
} else if (options.transclude !== undefined)
transclude = options.transclude;
let mergedOptions = Object.assign({},
let $options = Object.assign({},
parentOptions,
options,
{
name,
transclude,
bindings: Object.assign({},
parentOptions.bindings,
@ -47,13 +58,17 @@ function vnComponent(name, options) {
)
}
);
controller.$options = mergedOptions;
let parentSlotTemplates = parent.slotTemplates || [];
if (options.slotTemplate)
controller.slotTemplates = parentSlotTemplates.concat([options.slotTemplate]);
let classNames = [camelToKebab(name)];
if (parent.$classNames) classNames = classNames.concat(parent.$classNames);
controller.$classNames = classNames;
return this.component(name, mergedOptions);
controller.$options = $options;
return this.component(name, $options);
}
const ngModuleFn = ng.module;
@ -72,33 +87,12 @@ export function config($translateProvider, $translatePartialLoaderProvider, $ani
// For CSS browser targeting
document.documentElement.setAttribute('data-browser', navigator.userAgent);
$translatePartialLoaderProvider.addPart('core');
let conf = {urlTemplate: '/locale/{part}/{lang}.json'};
let fallbackLang = 'es';
let langs = ['en', 'es'];
let langAliases = {
en_US: 'en',
en_GB: 'en',
es_ES: 'es',
es_AR: 'es'
};
$translateProvider
.useSanitizeValueStrategy('escape')
.useLoader('$translatePartialLoader', conf)
.registerAvailableLanguageKeys(langs, langAliases)
// FIXME: Circular dependency due to vnInterceptor
// .fallbackLanguage(fallbackLang)
.determinePreferredLanguage(() => {
let locale = $translateProvider.resolveClientLocale();
if (langs.indexOf(locale) !== -1)
return locale;
if (langAliases[locale])
return langAliases[locale];
return fallbackLang;
.useLoader('$translatePartialLoader', {
urlTemplate: '/locale/{part}/{lang}.json'
});
$translatePartialLoaderProvider.addPart('core');
$animateProvider.customFilter(
node => node.tagName == 'UI-VIEW');

View File

@ -10,15 +10,10 @@ export default class Modules {
});
}
reset() {
this.modules = null;
}
get() {
if (this.modules)
return this.modules;
fetch() {
const map = {};
const modules = [];
for (let mod of this.$window.routes) {
if (!mod || !mod.routes) continue;
@ -32,12 +27,15 @@ export default class Modules {
if (res) keyBind = res.key.toUpperCase();
}
modules.push({
const module = {
name: mod.name || mod.module,
icon: mod.icon || null,
route,
keyBind
});
};
modules.push(module);
map[mod.module] = mod;
}
const sortedModules = modules.sort((a, b) => {
@ -46,9 +44,23 @@ export default class Modules {
return translatedNameA.localeCompare(translatedNameB);
});
this.modules = sortedModules;
this.map = map;
}
reset() {
this.modules = null;
this.map = null;
}
get() {
if (!this.modules) this.fetch();
return this.modules;
}
getMap() {
if (!this.map) this.fetch();
return this.map;
}
}
Modules.$inject = ['aclService', '$window', '$translate'];

View File

@ -39,3 +39,8 @@
/* Support for IE. */
font-feature-settings: 'liga';
}
[class^="icon-"] {
padding: .1em;
font-size: .833em;
}

View File

@ -4,17 +4,19 @@
html [vn-horizontal], vn-horizontal, .vn-horizontal,
html [vn-vertical], vn-vertical, .vn-vertical {
display: flex;
& > * {
flex: 1;
}
}
html [vn-horizontal], vn-horizontal, .vn-horizontal {
flex-direction: row;
}
vn-horizontal[reverse] {
flex-direction: row-reverse;
}
html [vn-vertical], vn-vertical, .vn-vertical {
flex-direction: column;
}
vn-vertical[reverse] {
html [reverse] {
flex-direction: column-reverse;
}
html [wrap] {
@ -26,27 +28,11 @@ html [wrap-reverse] {
/* Horizontal & vertical childs */
html [vn-auto],
html [vn-none],
html [vn-one],
html [vn-two],
html [vn-three],
html [vn-four],
html [vn-five],
html [vn-six],
html [vn-seven],
html [vn-eight],
html [vn-nine],
html [vn-ten],
html [vn-eleven],
html [vn-twelve]{
flex-basis: .1px;
}
html [vn-auto], vn-auto, .vn-auto {
flex-basis: auto;
flex: initial;
}
html [vn-none], vn-none, .vn-none {
flex: none;
flex: 0 0 auto;
}
html [vn-one], vn-one, .vn-one {
flex: 1;

View File

@ -4,6 +4,7 @@ $font-size: 12pt;
$menu-width: 256px;
$topbar-height: 56px;
$mobile-width: 800px;
$float-spacing: 20px;
// Width

View File

@ -6,5 +6,4 @@
ng-if="!$ctrl.showLayout">
</ui-view>
<vn-snackbar vn-id="snackbar"></vn-snackbar>
<!-- <vn-debug-info></vn-debug-info> -->
<vn-debug-info></vn-debug-info>

View File

@ -0,0 +1,6 @@
<default>
<div
ng-transclude="descriptor"
class="descriptor-wrapper">
</div>
</default>

View File

@ -0,0 +1,28 @@
import ngModule from '../../module';
import Popover from 'core/components/popover';
import './style.scss';
export default class DescriptorPopover extends Popover {
show(parent, id) {
super.show(parent);
this.id = id;
this.descriptor = this.content.querySelector('.vn-descriptor').$ctrl;
this.descriptor.load(id)
.then(() => this.$.$applyAsync(() => this.relocate()));
}
hide() {
super.hide();
this.id = null;
this.descriptor = null;
}
}
ngModule.vnComponent('vnDescriptorPopover', {
slotTemplate: require('./index.html'),
controller: DescriptorPopover,
transclude: {
descriptor: '?slotDescriptor'
}
});

View File

@ -0,0 +1,11 @@
@import "variables";
.vn-descriptor-popover {
vn-descriptor-content > .descriptor {
width: 260px;
& > .header > a:first-child {
visibility: hidden;
}
}
}

View File

@ -1,8 +1,40 @@
<a ng-repeat="button in $ctrl.links"
vn-tooltip="{{::button.tooltip}}"
class="vn-button colored"
ui-sref="{{::button.state}}">
<vn-icon
icon="{{::button.icon}}">
</vn-icon>
</a>
<vn-spinner
ng-if="$ctrl.descriptor.canceler"
enable="true">
</vn-spinner>
<div
ng-if="!$ctrl.descriptor.canceler"
class="descriptor">
<div class="header">
<a
translate-attr="{title: 'Go to module index'}"
ui-sref="{{::$ctrl.module}}.index"
name="goToModuleIndex">
<vn-icon icon="{{$ctrl.moduleMap[$ctrl.module].icon}}"></vn-icon>
</a>
<a
translate-attr="{title: 'Preview'}"
ui-sref="{{::$ctrl.module}}.card.summary({id: $ctrl.descriptor.id})">
<vn-icon icon="desktop_windows"></vn-icon>
</a>
<vn-icon-button
ng-class="::{invisible: !$ctrl.$transclude.isSlotFilled('menu')}"
icon="more_vert"
vn-popover="menu">
</vn-icon-button>
</div>
<vn-menu vn-id="menu">
<vn-list ng-transclude="menu"></vn-list>
</vn-menu>
<div ng-transclude="before"></div>
<div class="body">
<div class="top">
<h5>{{$ctrl.description}}</h5>
<div class="text-secondary">
{{$ctrl.descriptor.id | id}}
</div>
</div>
<div ng-transclude="body"></div>
</div>
<div ng-transclude="after"></div>
</div>

View File

@ -1,12 +1,141 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
import './quick-link';
export default class QuickLinks {}
/**
* Small card with basing entity information and actions.
*/
export default class Descriptor extends Component {
$postLink() {
const content = this.element.querySelector('vn-descriptor-content');
if (!content) throw new Error('Directive vnDescriptorContent not found');
ngModule.component('vnQuickLinks', {
template: require('./index.html'),
controller: QuickLinks,
angular.element(content)
.controller('vnDescriptorContent')
.descriptor = this;
}
get id() {
return this._id;
}
set id(value) {
this.load(value);
}
get entity() {
return this._entity;
}
set entity(value) {
this._entity = value;
this._id = value && value.id;
}
load(id) {
if (id == this._id)
return this.$q.resolve();
this._id = id;
if (!id) {
this.entity = null;
return this.$q.resolve();
}
return this.loadData();
}
/**
* Reloads the descriptor data. Should be implemented or overriden by child
* classes.
*/
loadData() {
throw new Error('DescriptorPopover::loadData() method not implemented');
}
/**
* Performs a cancellable request.
*
* @param {String} url The http path
* @param {Object} options The request options
* @return {Promise} Resolved with request response
*/
getData(url, options) {
if (this.canceler) this.canceler.resolve();
this.canceler = this.$q.defer();
this.entity = null;
options = Object.assign(options || {}, {
timeout: this.canceler.promise
});
return this.$http.get(url, options)
.finally(() => this.canceler = null);
}
/**
* Shows a report in another window, automatically adds the authorization
* token to params.
*
* @param {String} report The report name
* @param {Object} params The report parameters
*/
showReport(report, params) {
params = Object.assign({
authorization: this.vnToken.token
}, params);
const serializedParams = this.$httpParamSerializer(params);
window.open(`api/report/${report}?${serializedParams}`);
}
/**
* Sends an email displaying a notification when it's sent.
*
* @param {String} report The email report name
* @param {Object} params The email parameters
* @return {Promise} Promise resolved when it's sent
*/
sendEmail(report, params) {
return this.$http.get(`email/${report}`, {params})
.then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
}
}
ngModule.vnComponent('vnDescriptor', {
controller: Descriptor,
bindings: {
links: '<?'
entity: '<?',
id: '<?'
},
transclude: {
btnOne: '?btnOne',
btnTwo: '?btnTwo',
btnThree: '?btnThree'
}
});
export class DescriptorContent {
constructor($transclude, vnModules) {
this.$transclude = $transclude;
this.moduleMap = vnModules.getMap();
}
}
DescriptorContent.$inject = ['$transclude', 'vnModules'];
ngModule.component('vnDescriptorContent', {
template: require('./index.html'),
controller: DescriptorContent,
bindings: {
module: '@',
description: '<',
descriptor: '<?'
},
transclude: {
body: 'slotBody',
before: '?slotBefore',
after: '?slotAfter',
menu: '?slotMenu'
}
});

View File

@ -0,0 +1,8 @@
<a
ui-sref="{{::$ctrl.state[0]}}($ctrl.state[1])"
vn-tooltip="{{::$ctrl.tooltip}}"
class="vn-button colored">
<vn-icon
icon="{{::$ctrl.icon}}">
</vn-icon>
</a>

View File

@ -0,0 +1,13 @@
import ngModule from '../../module';
export default class QuickLink {}
ngModule.component('vnQuickLink', {
template: require('./quick-link.html'),
controller: QuickLink,
bindings: {
state: '<?',
icon: '@?',
tooltip: '@?'
}
});

View File

@ -2,83 +2,102 @@
@import "./effects";
@import "./variables";
.vn-descriptor {
box-shadow: 0 1px 3px $color-shadow;
vn-descriptor-content {
display: block;
& > .header {
display: flex;
background: $color-main;
justify-content: space-between;
align-items: stretch;
color: $color-font-dark;
& > * {
@extend %clickable;
min-width: 45px;
height: 45px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
color: inherit;
& > vn-icon {
padding: 10px;
}
vn-icon {
font-size: 1.75rem;
}
}
& > vn-spinner {
display: block;
height: 40px;
padding: $spacing-md;
}
& > .body {
padding: $spacing-sm;
& > div {
display: block;
box-shadow: 0 1px 3px $color-shadow;
& > * {
padding: $spacing-sm;
}
& > .attributes > h5 {
padding-bottom: $spacing-sm;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
& > .icons {
& > .header {
display: flex;
align-items: center;
justify-content: center;
padding: 0;
& > vn-icon {
padding: $spacing-sm;
color: $color-marginal;
font-size: 1.5rem;
&.bright {
color: $color-main;
opacity: 1;
background: $color-main;
justify-content: space-between;
align-items: stretch;
color: $color-font-dark;
& > * {
display: flex;
min-width: 45px;
height: 45px;
box-sizing: border-box;
align-items: center;
justify-content: center;
}
& > a,
& > vn-icon-button {
@extend %clickable;
color: inherit;
& > vn-icon {
padding: 10px;
}
vn-icon {
font-size: 1.75rem;
}
}
}
.quicklinks,
vn-quick-links {
display: flex;
align-items: center;
justify-content: center;
& > a {
padding: 0 $spacing-md;
margin: 0 $spacing-sm;
& > .body {
display: block;
padding: $spacing-md;
& > vn-icon {
font-size: 1.75rem;
padding: 0;
& > .top {
padding-bottom: $spacing-sm;
& > * {
margin-bottom: 0;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
& > div > slot-body {
& > * {
padding-bottom: $spacing-md;
}
& > :last-child {
padding-bottom: 0;
}
& > .icons {
display: flex;
align-items: center;
justify-content: center;
& > vn-icon {
padding: $spacing-xs $spacing-sm;
color: $color-marginal;
font-size: 1.5rem;
&.bright {
color: $color-main;
opacity: 1;
}
}
}
& > .quicklinks {
display: flex;
align-items: center;
justify-content: center;
vn-quick-link > a {
display: flex;
align-items: center;
justify-content: center;
padding: 0 $spacing-md;
margin: 0 $spacing-sm;
& > vn-icon {
font-size: 1.75rem;
padding: 0;
}
}
}
}
}
}
}
.vn-popover {
.vn-descriptor > .header > a:first-child {
visibility: hidden;
}
}

View File

@ -1,6 +1,7 @@
import './app/app';
import './background/background';
import './descriptor';
import './descriptor-popover';
import './home/home';
import './layout';
import './left-menu/left-menu';

View File

@ -39,18 +39,26 @@
</vn-icon-button>
</div>
<vn-menu vn-id="apps-menu">
<ul class="modules-menu vn-pa-sm">
<li ui-sref="home">
<vn-icon icon="home"></vn-icon>
<span translate>Home</span>
</li>
<li
<vn-list class="modules-menu">
<a class="vn-item" ui-sref="home">
<vn-item-section avatar>
<vn-icon icon="home"></vn-icon>
</vn-item-section>
<vn-item-section translate>
Home
</vn-item-section>
</a>
<a class="vn-item"
ng-repeat="mod in ::$ctrl.modules"
ui-sref="{{::mod.route.state}}">
<vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon>
<span translate>{{::mod.name}}</span>
</li>
</ul>
<vn-item-section avatar>
<vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon>
</vn-item-section>
<vn-item-section translate>
{{::mod.name}}
</vn-item-section>
</a>
</vn-list>
</vn-menu>
<vn-user-popover
vn-id="user-popover">
@ -74,4 +82,5 @@
</a>
</vn-list>
</vn-portal>
<ui-view class="main-view"></ui-view>
<ui-view class="main-view"></ui-view>
<vn-scroll-up></vn-scroll-up>

View File

@ -32,7 +32,7 @@ vn-layout {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding-left: 6px;
padding-left: 10px;
}
& > vn-spinner {
padding: 0 6px;
@ -80,7 +80,7 @@ vn-layout {
padding-right: $menu-width;
}
[fixed-bottom-right] {
right: 64px + $menu-width;
right: $menu-width;
}
}
& > .main-view {
@ -94,8 +94,9 @@ vn-layout {
}
[fixed-bottom-right] {
position: fixed;
bottom: 32px;
right: 32px;
bottom: 0;
right: 0;
margin: $float-spacing;
}
&.ng-enter {
vn-side-menu {
@ -133,7 +134,7 @@ vn-layout {
padding-right: 0;
}
[fixed-bottom-right] {
right: 32px;
right: 0;
}
}
ui-view > * {
@ -142,29 +143,6 @@ vn-layout {
}
}
}
.vn-popover .modules-menu {
list-style-type: none;
margin: 0;
color: $color-font-dark;
& > li {
@extend %clickable-light;
background-color: $color-main;
margin-bottom: 9px;
padding: 12px;
border-radius: 1px;
min-width: 128px;
white-space: nowrap;
&:last-child {
margin-bottom: 0;
}
& > vn-icon {
padding-right: 4px;
vertical-align: middle;
}
}
}
#user {
font-size: 1.5rem;
height: auto;

View File

@ -5,7 +5,7 @@
class="vn-item"
ng-class="{active: item.active && !item.childs, expanded: item.active}"
ng-click="$ctrl.setActive(item)">
<vn-item-section avatar>
<vn-item-section avatar>
<vn-icon icon="{{::item.icon}}" ng-if="::item.icon"></vn-icon>
</vn-item-section>
<vn-item-section translate>

View File

@ -3,16 +3,12 @@
@import "variables";
vn-left-menu {
& > .vn-list {
padding: $spacing-md 0;
& > li > .vn-item {
& > [side] > vn-icon[icon="keyboard_arrow_down"] {
transition: transform 200ms;
}
&.expanded > [side] > vn-icon[icon="keyboard_arrow_down"] {
transform: rotate(180deg);
}
& > .vn-list > li > .vn-item {
& > [side] > vn-icon[icon="keyboard_arrow_down"] {
transition: transform 200ms;
}
&.expanded > [side] > vn-icon[icon="keyboard_arrow_down"] {
transform: rotate(180deg);
}
}
}

View File

@ -6,11 +6,6 @@ import './style.scss';
* Base class for module cards.
*/
export default class ModuleCard extends Component {
constructor($element, $) {
super($element, $);
this.element.classList.add('vn-module-card');
}
$onInit() {
this.reload();
}

View File

@ -2,12 +2,7 @@ import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
export default class ModuleMain extends Component {
constructor($element, $) {
super($element, $);
this.element.classList.add('vn-module-main');
}
}
export default class ModuleMain extends Component {}
ngModule.vnComponent('vnModuleMain', {
template: require('./index.html'),

View File

@ -2,17 +2,7 @@ import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
export default class Section extends Component {
constructor($element, $) {
super($element, $);
this.element.classList.add('vn-section');
}
stopEvent(event) {
event.preventDefault();
event.stopImmediatePropagation();
}
}
export default class Section extends Component {}
ngModule.vnComponent('vnSection', {
controller: Section

View File

@ -1,15 +1,6 @@
import ngModule from '../../module';
import './style.scss';
let languages = {
es: 'Español',
en: 'English',
ca: 'Català',
pt: 'Português',
fr: 'Français',
nl: 'Nederlands',
mn: 'Монгол хэл'
};
import config from '../../config.json';
class Controller {
constructor($, $translate, vnConfig, vnAuth) {
@ -25,7 +16,7 @@ class Controller {
for (let code of $translate.getAvailableLanguageKeys()) {
this.langs.push({
code: code,
name: languages[code] ? languages[code] : code
name: config.languages[code] ? config.languages[code] : code
});
}

25
front/salix/config.json Normal file
View File

@ -0,0 +1,25 @@
{
"imagePath": "//verdnatura.es/vn-image-data",
"langOptions": {
"fallbackLang": "es",
"langs": [
"en",
"es"
],
"langAliases": {
"en_US": "en",
"en_GB": "en",
"es_ES": "es",
"es_AR": "es"
}
},
"languages": {
"es": "Español",
"en": "English",
"ca": "Català",
"pt": "Português",
"fr": "Français",
"nl": "Nederlands",
"mn": "Монгол хэл"
}
}

View File

@ -12,7 +12,7 @@ Name: Nombre
Preview: Vista previa
Profile: Perfil
Push on applications menu: Para abrir un módulo pulsa en el menú de aplicaciones
Return to module index: Volver a la página principal del módulo
Go to module index: Ir al índice del módulo
What is new: Novedades de la versión
Settings: Ajustes

View File

@ -1,4 +1,5 @@
import {ng} from 'core/vendor';
import appConfig from './config.json';
import 'core';
export const appName = 'salix';
@ -8,6 +9,8 @@ export default ngModule;
run.$inject = ['$window', '$rootScope', 'vnAuth', 'vnApp', '$state'];
export function run($window, $rootScope, vnAuth, vnApp, $state) {
$rootScope.imagePath = appConfig.imagePath;
$window.validations = {};
vnApp.name = appName;
@ -57,8 +60,22 @@ export function run($window, $rootScope, vnAuth, vnApp, $state) {
}
ngModule.run(run);
config.$inject = ['$translatePartialLoaderProvider', '$httpProvider', '$compileProvider'];
export function config($translatePartialLoaderProvider, $httpProvider, $compileProvider) {
config.$inject = ['$translateProvider', '$translatePartialLoaderProvider', '$httpProvider', '$compileProvider'];
export function config($translateProvider, $translatePartialLoaderProvider, $httpProvider, $compileProvider) {
const langOptions = appConfig.langOptions;
$translateProvider
.registerAvailableLanguageKeys(langOptions.langs, langOptions.langAliases)
// TODO: Circular dependency due to vnInterceptor
// .fallbackLanguage(langOptions.fallbackLang)
.determinePreferredLanguage(() => {
const locale = $translateProvider.resolveClientLocale();
if (langOptions.langs.indexOf(locale) !== -1)
return locale;
if (langOptions.langAliases[locale])
return langOptions.langAliases[locale];
return fallbackLang;
});
$translatePartialLoaderProvider.addPart(appName);
$httpProvider.interceptors.push('vnInterceptor');

View File

@ -6,6 +6,7 @@
justify-content: center;
& > .product {
flex: initial;
box-sizing: border-box;
padding: $spacing-sm;
width: 448px;

View File

@ -131,5 +131,6 @@
"ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto",
"Distance must be lesser than 1000": "La distancia debe ser inferior a 1000",
"This ticket is deleted": "Este ticket está eliminado",
"A travel with this data already exists": "Ya existe un travel con estos datos"
"A travel with this data already exists": "Ya existe un travel con estos datos",
"AMOUNT_NOT_MATCH_GROUPING": "AMOUNT_NOT_MATCH_GROUPING"
}

View File

@ -7,19 +7,16 @@
auto-save="true"
on-save="$ctrl.onSave()">
</vn-crud-model>
<vn-crud-model auto-load="true"
url="ClaimDestinations"
data="claimDestinations">
</vn-crud-model>
<vn-card class="vn-mb-md vn-pa-lg vn-w-lg" style="text-align: right"
ng-if="$ctrl.salesClaimed.length > 0">
<vn-label-value label="Total claimed"
value="{{$ctrl.claimedTotal | currency: 'EUR':2}}">
</vn-label-value>
</vn-card>
<vn-card class="vn-pa-lg vn-w-lg">
<section class="header">
<vn-tool-bar class="vn-mb-md">
@ -57,7 +54,6 @@
on-change="$ctrl.save({isChargedToMana: value})">
</vn-check>
</section>
<vn-data-viewer model="model">
<vn-table model="model">
<vn-thead>
@ -79,7 +75,7 @@
vn-repeat-last on-last="$ctrl.focusLastInput()">
<vn-td number>
<span
ng-click="$ctrl.showDescriptor($event, saleClaimed.sale.itemFk)"
ng-click="descriptor.show($event, saleClaimed.sale.itemFk)"
class="link">
{{::saleClaimed.sale.itemFk | zeroFill:6}}
</span>
@ -87,7 +83,7 @@
<vn-td number>
<span
class="link"
ng-click="$ctrl.showTicketDescriptor($event, saleClaimed.sale.ticketFk)">
ng-click="ticketDescriptor.show($event, saleClaimed.sale.ticketFk)">
{{::saleClaimed.sale.ticketFk}}
</span>
</vn-td>
@ -121,7 +117,6 @@
</vn-tbody>
</vn-table>
</vn-data-viewer>
<vn-button-bar>
<vn-button
label="Regularize"
@ -130,7 +125,6 @@
</vn-button>
</vn-button-bar>
</vn-card>
<vn-crud-model
vn-id="lastTicketsModel"
url="Tickets"
@ -173,5 +167,5 @@
vn-id="update-greuge"
question="Insert greuges on client card"
message="Do you want to insert greuges?"
on-response="$ctrl.onUpdateGreugeResponse($response)">
on-accept="$ctrl.onUpdateGreugeAccept()">
</vn-confirm>

View File

@ -41,18 +41,10 @@ export default class Controller extends Section {
let query = `ClaimBeginnings/${this.$params.id}/importToNewRefundTicket`;
return this.$http.post(query).then(() => {
this.$.model.refresh();
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
showTicketDescriptor(event, ticketFk) {
this.$.ticketDescriptor.ticketFk = ticketFk;
this.$.ticketDescriptor.parent = event.target;
this.$.ticketDescriptor.show();
event.preventDefault();
}
focusLastInput() {
let inputs = document.querySelectorAll('#claimDestinationFk');
inputs[inputs.length - 1].querySelector('input').focus();
@ -84,8 +76,7 @@ export default class Controller extends Section {
};
this.$.lastTicketsModel.filter = filter;
this.$.lastTicketsModel.refresh();
this.$.lastTicketsPopover.parent = event.target;
this.$.lastTicketsPopover.show();
this.$.lastTicketsPopover.show(event);
}
importTicketLines(ticketFk) {
@ -93,7 +84,7 @@ export default class Controller extends Section {
let query = `ClaimEnds/importTicketSales`;
this.$http.post(query, data).then(() => {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.vnApp.showSuccess(this.$t('Data saved!'));
this.$.lastTicketsPopover.hide();
this.$.model.refresh();
});
@ -105,7 +96,7 @@ export default class Controller extends Section {
if (this.claim.responsibility >= Math.ceil(this.maxResponsibility) / 2)
this.$.updateGreuge.show();
else
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.vnApp.showSuccess(this.$t('Data saved!'));
this.card.reload();
});
@ -130,46 +121,36 @@ export default class Controller extends Section {
});
}
onUpdateGreugeResponse(response) {
if (response == 'accept') {
const promises = [];
promises.push(this.getGreugeTypeId());
promises.push(this.getGreugeConfig());
onUpdateGreugeAccept() {
const promises = [];
promises.push(this.getGreugeTypeId());
promises.push(this.getGreugeConfig());
return Promise.all(promises).then(() => {
const data = {
clientFk: this.claim.clientFk,
description: this.$translate.instant('ClaimGreugeDescription', {
claimId: this.claim.id
}).toUpperCase(),
amount: this.freightPickUpPrice,
greugeTypeFk: this.greugeTypeFreightId,
ticketFk: this.claim.ticketFk
};
return this.$http.post(`Greuges/`, data).then(() => {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.vnApp.showMessage(this.$translate.instant('Greuge inserted'));
});
return Promise.all(promises).then(() => {
const data = {
clientFk: this.claim.clientFk,
description: this.$t('ClaimGreugeDescription', {
claimId: this.claim.id
}).toUpperCase(),
amount: this.freightPickUpPrice,
greugeTypeFk: this.greugeTypeFreightId,
ticketFk: this.claim.ticketFk
};
return this.$http.post(`Greuges`, data).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
this.vnApp.showMessage(this.$t('Greuge inserted'));
});
} else
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
}
showDescriptor(event, itemFk) {
this.$.descriptor.itemFk = itemFk;
this.$.descriptor.parent = event.target;
this.$.descriptor.show();
});
}
save(data) {
const query = `Claims/${this.$params.id}/updateClaimAction`;
this.$http.patch(query, data).then(() => {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
});
this.$http.patch(query, data)
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
}
onSave() {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.vnApp.showSuccess(this.$t('Data saved!'));
}
}

View File

@ -6,20 +6,15 @@ describe('claim', () => {
let controller;
let $httpBackend;
let $state;
let $httpParamSerializer;
let $scope;
beforeEach(ngModule('claim'));
beforeEach(angular.mock.inject(($rootScope, $componentController, _$state_, _$httpBackend_, _$httpParamSerializer_) => {
$scope = $rootScope.$new();
beforeEach(angular.mock.inject(($componentController, _$state_, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
$state = _$state_;
$state.params.id = 1;
const $element = angular.element('<vn-claim-action></vn-claim-action>');
controller = $componentController('vnClaimAction', {$element, $scope});
controller = $componentController('vnClaimAction', {$element: null});
controller.claim = {ticketFk: 1};
controller.$.model = {refresh: () => {}};
controller.$.addSales = {
@ -67,8 +62,8 @@ describe('claim', () => {
controller.importToNewRefundTicket();
$httpBackend.flush();
expect(controller.$.model.refresh).toHaveBeenCalledWith();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
expect(controller.$.model.refresh).toHaveBeenCalled();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
@ -79,8 +74,8 @@ describe('claim', () => {
controller.showLastTickets({});
expect(controller.$.lastTicketsModel.refresh).toHaveBeenCalledWith();
expect(controller.$.lastTicketsPopover.show).toHaveBeenCalledWith();
expect(controller.$.lastTicketsModel.refresh).toHaveBeenCalled();
expect(controller.$.lastTicketsPopover.show).toHaveBeenCalled();
});
});
@ -124,33 +119,21 @@ describe('claim', () => {
controller.save(data);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('onUpdateGreugeResponse()', () => {
describe('onUpdateGreugeAccept()', () => {
const greugeTypeId = 7;
const freightPickUpPrice = 11;
it('should do nothing', () => {
jest.spyOn(controller.$http, 'post');
jest.spyOn(controller.card, 'reload');
jest.spyOn(controller.vnApp, 'showSuccess');
controller.onUpdateGreugeResponse('cancel');
expect(controller.$http.post).not.toHaveBeenCalledWith();
expect(controller.card.reload).not.toHaveBeenCalledWith();
expect(controller.vnApp.showSuccess).not.toHaveBeenCalledWith('Greuge inserted!');
});
it('should make a query and get the greugeTypeId and greuge config', () => {
jest.spyOn(controller.card, 'reload');
jest.spyOn(controller.vnApp, 'showSuccess');
const greugeTypeParams = $httpParamSerializer({filter: {where: {code: 'freightPickUp'}}});
$httpBackend.expect('GET', `GreugeTypes/findOne?${greugeTypeParams}`).respond({id: greugeTypeId});
$httpBackend.expect('GET', `GreugeConfigs/findOne`).respond({freightPickUpPrice});
controller.onUpdateGreugeResponse('accept');
$httpBackend.expectRoute('GET', `GreugeTypes/findOne`).respond({id: greugeTypeId});
$httpBackend.expectGET(`GreugeConfigs/findOne`).respond({freightPickUpPrice});
controller.onUpdateGreugeAccept();
$httpBackend.flush();
expect(controller.greugeTypeFreightId).toEqual(greugeTypeId);
@ -181,7 +164,7 @@ describe('claim', () => {
$httpBackend.expect('POST', `Greuges/`, data).respond(new Promise(resolve => {
return resolve({id: freightPickUpPrice});
}));
controller.onUpdateGreugeResponse('accept').then(res => {
controller.onUpdateGreugeAccept().then(res => {
}).catch(error => {

View File

@ -1,62 +1,81 @@
<div class="vn-descriptor">
<div class="header">
<a translate-attr="{title: 'Return to module index'}" ui-sref="claim.index">
<vn-icon icon="chevron_left"></vn-icon>
<vn-descriptor-content
module="claim"
description="$ctrl.claim.client.name">
<slot-menu>
<a class="vn-item"
ui-sref="ticket.create({clientFk: $ctrl.client.id})"
translate>
Show Pickup order
</a>
<a translate-attr="{title: 'Preview'}" ui-sref="claim.card.summary({id: $ctrl.claim.id})">
<vn-icon icon="desktop_windows"></vn-icon>
</a>
<vn-icon-menu
vn-id="more-button"
icon="more_vert"
show-filter="false"
value-field="callback"
translate-fields="['name']"
data="$ctrl.moreOptions"
on-change="$ctrl.onMoreChange(value)"
on-open="$ctrl.onMoreOpen()">
</vn-icon-menu>
</div>
<div class="body">
<vn-item
ng-click="confirmPickupOrder.show()"
translate>
Send Pickup order
</vn-item>
<vn-item
vn-acl="salesAssistant"
vn-acl-action="remove"
ng-click="confirmDeleteClaim.show()"
name="deleteClaim"
translate>
Delete claim
</vn-item>
</slot-menu>
<slot-body>
<div class="attributes">
<h5>{{$ctrl.claim.id}}</h5>
<vn-label-value label="Client"
value="{{::$ctrl.claim.client.name}}">
</vn-label-value>
<vn-label-value label="State"
<vn-label-value
label="State"
value="{{$ctrl.claim.claimState.description}}">
</vn-label-value>
<vn-label-value label="Created"
value="{{$ctrl.claim.created | date: 'dd/MM/yyyy HH:mm'}}">
<vn-label-value
label="Created"
value="{{$ctrl.claim.created | date: 'dd/MM/yyyy HH:mm'}}">
</vn-label-value>
<vn-label-value label="Salesperson"
<vn-label-value
label="Salesperson"
value="{{$ctrl.claim.client.salesPerson.user.nickname}}">
</vn-label-value>
<vn-label-value label="Attended by"
<vn-label-value
label="Attended by"
value="{{$ctrl.claim.worker.user.nickname}}">
</vn-label-value>
<vn-label-value label="Agency"
value="{{$ctrl.claim.ticket.agencyMode.name}}">
<vn-label-value
label="Agency"
value="{{$ctrl.claim.ticket.agencyMode.name}}">
</vn-label-value>
<vn-label-value label="Ticket"
value="{{$ctrl.claim.ticketFk}}">
<vn-label-value
label="Ticket"
value="{{$ctrl.claim.ticketFk}}">
</vn-label-value>
</div>
<vn-quick-links
links="$ctrl.quicklinks">
</vn-quick-links>
</div>
</div>
<div class="quicklinks">
<div ng-transclude="btnOne">
<vn-quick-link
tooltip="Client ticket list"
state="['client.card.summary', {id: $ctrl.claim.clientFk}]"
icon="person">
</vn-quick-link>
</div>
<div ng-transclude="btnTwo">
<vn-quick-link
tooltip="Claimed ticket"
state="['ticket.card.summary', {id: $ctrl.claim.ticketFk}]"
icon="icon-ticket">
</vn-quick-link>
</div>
<div ng-transclude="btnThree"></div>
</div>
</slot-body>
</vn-descriptor-content>
<vn-confirm
vn-id="confirm-pickup-order"
on-response="$ctrl.sendPickupOrder($response)"
vn-id="confirmPickupOrder"
on-accept="$ctrl.sendPickupOrder()"
question="Send Pickup order"
message="Are you sure you want to send it?">
</vn-confirm>
<vn-confirm
vn-id="confirm-delete-claim"
on-response="$ctrl.deleteClaim($response)"
vn-id="confirmDeleteClaim"
on-accept="$ctrl.deleteClaim()"
question="Delete claim"
message="Are you sure you want to delete this claim?">
</vn-confirm>

View File

@ -1,112 +1,43 @@
import ngModule from '../module';
import Component from 'core/lib/component';
class Controller extends Component {
constructor($element, $, $httpParamSerializer) {
super($element, $);
this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [
{callback: this.showPickupOrder, name: 'Show Pickup order'},
{callback: this.confirmPickupOrder, name: 'Send Pickup order'},
{callback: this.confirmDeleteClaim, name: 'Delete claim', acl: 'salesAssistant'}
];
}
onMoreOpen() {
let options = this.moreOptions.filter(option => {
const hasAclProperty = Object.hasOwnProperty.call(option, 'acl');
return !hasAclProperty || (hasAclProperty && this.aclService.hasAny([option.acl]));
});
this.$.moreButton.data = options;
}
onMoreChange(callback) {
callback.call(this);
}
import Descriptor from 'salix/components/descriptor';
class Controller extends Descriptor {
get claim() {
return this._claim;
return this.entity;
}
set claim(value) {
this._claim = value;
if (!value) return;
this._quicklinks = {
btnOne: {
icon: 'person',
state: `client.card.summary({id: ${value.clientFk}})`,
tooltip: 'Client card'
},
btnTwo: {
icon: 'icon-ticket',
state: `ticket.card.summary({id: ${this.claim.ticketFk}})`,
tooltip: 'Claimed ticket'
}
};
}
set quicklinks(value = {}) {
this._quicklinks = Object.assign(value, this._quicklinks);
}
get quicklinks() {
return this._quicklinks;
this.entity = value;
}
showPickupOrder() {
const params = {
this.showReport('claim-pickup-order', {
clientId: this.claim.clientFk,
claimId: this.claim.id,
authorization: this.vnToken.token
};
const serializedParams = this.$httpParamSerializer(params);
let url = `api/report/claim-pickup-order?${serializedParams}`;
window.open(url);
claimId: this.claim.id
});
}
confirmPickupOrder() {
this.$.confirmPickupOrder.show();
sendPickupOrder() {
return this.sendEmail('claim-pickup-order', {
recipient: this.claim.client.email,
clientId: this.claim.clientFk,
claimId: this.claim.id
});
}
sendPickupOrder(response) {
if (response === 'accept') {
const params = {
recipient: this.claim.client.email,
clientId: this.claim.clientFk,
claimId: this.claim.id
};
this.$http.get(`email/claim-pickup-order`, {params}).then(
() => this.vnApp.showMessage(this.$translate.instant('Notification sent!'))
);
}
}
confirmDeleteClaim() {
this.$.confirmDeleteClaim.show();
}
deleteClaim(response) {
if (response === 'accept') {
this.$http.delete(`Claims/${this.claim.id}`).then(() => {
this.vnApp.showSuccess(this.$translate.instant('Claim deleted!'));
deleteClaim() {
return this.$http.delete(`Claims/${this.claim.id}`)
.then(() => {
this.vnApp.showSuccess(this.$t('Claim deleted!'));
this.$state.go('claim.index');
});
}
}
}
Controller.$inject = ['$element', '$scope', '$httpParamSerializer'];
ngModule.component('vnClaimDescriptor', {
ngModule.vnComponent('vnClaimDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
claim: '<',
tags: '<',
quicklinks: '<'
claim: '<'
}
});

View File

@ -1,96 +1,62 @@
import './index.js';
describe('Item Component vnClaimDescriptor', () => {
let $httpParamSerializer;
let $httpBackend;
let $element;
let $scope;
let controller;
const claim = {
id: 2,
clientFk: 101,
client: {email: 'client@email'}
};
beforeEach(ngModule('claim'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
$scope = $rootScope.$new();
$element = angular.element('<vn-claim-descriptor></vn-claim-descriptor>');
controller = $componentController('vnClaimDescriptor', {$element, $scope});
controller.claim = {id: 2, clientFk: 101, client: {email: 'client@email'}};
controller = $componentController('vnClaimDescriptor', {$element: null}, {claim});
}));
describe('showPickupOrder()', () => {
it('should open a new window showing a pickup order PDF document', () => {
controller.showReport = jest.fn();
const params = {
clientId: controller.claim.clientFk,
claimId: controller.claim.id
clientId: claim.clientFk,
claimId: claim.id
};
const serializedParams = $httpParamSerializer(params);
let expectedPath = `api/report/claim-pickup-order?${serializedParams}`;
jest.spyOn(window, 'open').mockReturnThis();
controller.showPickupOrder();
expect(window.open).toHaveBeenCalledWith(expectedPath);
expect(controller.showReport).toHaveBeenCalledWith('claim-pickup-order', params);
});
});
describe('confirmPickupOrder()', () => {
it('should call confirmPickupOrder.show()', () => {
controller.$.confirmPickupOrder = {
show: jasmine.createSpy('show')
};
controller.claim = {id: 2};
controller.confirmPickupOrder();
expect(controller.$.confirmPickupOrder.show).toHaveBeenCalledWith();
});
});
describe('sendPickupOrder(response)', () => {
describe('sendPickupOrder()', () => {
it('should make a query and call vnApp.showMessage() if the response is accept', () => {
jest.spyOn(controller.vnApp, 'showMessage');
jest.spyOn(controller, 'sendEmail');
const params = {
recipient: 'client@email',
clientId: controller.claim.clientFk,
claimId: controller.claim.id
recipient: claim.client.email,
clientId: claim.clientFk,
claimId: claim.id
};
const serializedParams = $httpParamSerializer(params);
controller.sendPickupOrder();
$httpBackend.when('GET', `email/claim-pickup-order?${serializedParams}`).respond();
$httpBackend.expect('GET', `email/claim-pickup-order?${serializedParams}`).respond();
controller.sendPickupOrder('accept');
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Notification sent!');
expect(controller.sendEmail).toHaveBeenCalledWith('claim-pickup-order', params);
});
});
describe('confirmDeleteClaim()', () => {
it('should call confirmDeleteClaim.show()', () => {
controller.$.confirmDeleteClaim = {
show: jasmine.createSpy('show')
};
controller.claim = {id: 2};
controller.confirmDeleteClaim();
expect(controller.$.confirmDeleteClaim.show).toHaveBeenCalledWith();
});
});
describe('deleteClaime(response)', () => {
describe('deleteClaim()', () => {
it('should perform a query and call showSuccess if the response is accept', () => {
let response = 'accept';
controller.claim = {id: 2};
jest.spyOn(controller.vnApp, 'showSuccess');
jest.spyOn(controller.$state, 'go');
$httpBackend.when('DELETE', `Claims/2`).respond(200);
$httpBackend.expect('DELETE', `Claims/2`);
controller.deleteClaim(response);
$httpBackend.expectDELETE(`Claims/${claim.id}`).respond();
controller.deleteClaim();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Claim deleted!');
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.$state.go).toHaveBeenCalledWith('claim.index');
});
});

View File

@ -46,9 +46,9 @@
</vn-input-number>
</vn-td>
<vn-td expand title="{{::saleClaimed.sale.concept}}">
<span
class="link"
ng-click="$ctrl.showItemDescriptor($event, saleClaimed.sale.itemFk)">
<span
ng-click="itemDescriptor.show($event, saleClaimed.sale.itemFk)"
class="link">
{{::saleClaimed.sale.concept}}
</span>
</vn-td>
@ -81,10 +81,10 @@
</a>
<!-- Add Lines Dialog -->
<vn-dialog vn-id="add-sales" class="modal-form">
<tpl-title>
<span translate>Claimable sales from ticket</span> {{$ctrl.claim.ticketFk}}
</tpl-title>
<tpl-body>
<vn-horizontal class="header vn-pa-md">
<h5><span translate>Claimable sales from ticket</span> {{$ctrl.claim.ticketFk}}</h5>
</vn-horizontal>
<vn-horizontal class="vn-pa-md">
<vn-table>
<vn-thead>
@ -98,14 +98,17 @@
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="sale in $ctrl.salesToClaim" class="clickable" ng-click="$ctrl.addClaimedSale($index)">
<vn-tr
ng-repeat="sale in $ctrl.salesToClaim"
ng-click="$ctrl.addClaimedSale($index)"
class="clickable">
<vn-td number>{{sale.landed | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td number>{{sale.quantity}}</vn-td>
<vn-td expand title="{{::sale.concept}}">
<span
class="link"
ng-click="$ctrl.showItemDescriptor($event, sale.itemFk)">
{{sale.concept}}
<span
vn-click-stop="itemDescriptor.show($event, sale.itemFk)"
class="link">
{{sale.itemFk}} - {{sale.concept}}
</span>
</vn-td>
<vn-td number>{{sale.price | currency: 'EUR':2}}</vn-td>
@ -120,7 +123,7 @@
</tpl-body>
</vn-dialog>
<vn-item-descriptor-popover
vn-id="descriptor">
vn-id="itemDescriptor">
</vn-item-descriptor-popover>
<vn-popover
class="edit"

View File

@ -109,13 +109,6 @@ class Controller extends Section {
return total;
}
showItemDescriptor(event, itemFk) {
event.stopImmediatePropagation();
this.$.descriptor.itemFk = itemFk;
this.$.descriptor.parent = event.target;
this.$.descriptor.show();
}
showEditPopover(event, saleClaimed) {
if (this.isEditable) {
if (!this.aclService.hasAny(['salesAssistant']))

View File

@ -125,25 +125,6 @@ describe('claim', () => {
});
});
describe('showItemDescriptor()', () => {
it('should configure the descriptor then show it', () => {
const itemId = 500;
const event = {
stopImmediatePropagation: () => {},
target: 'the target element'
};
jest.spyOn(event, 'stopImmediatePropagation');
jest.spyOn(controller.$.descriptor, 'show');
controller.showItemDescriptor(event, itemId);
expect(event.stopImmediatePropagation).toHaveBeenCalledWith();
expect(controller.$.descriptor.itemFk).toEqual(itemId);
expect(controller.$.descriptor.parent).toEqual(event.target);
expect(controller.$.descriptor.show).toHaveBeenCalledWith();
});
});
describe('isClaimEditable()', () => {
it('should check if the claim is editable', () => {
controller.isClaimEditable();

View File

@ -42,7 +42,6 @@
<form name="form">
<vn-horizontal ng-repeat="claimDevelopment in claimDevelopments">
<vn-autocomplete
vn-one
vn-focus
label="Reason"
ng-model="claimDevelopment.claimReasonFk"
@ -52,7 +51,6 @@
rule>
</vn-autocomplete>
<vn-autocomplete
vn-one
label="Result"
ng-model="claimDevelopment.claimResultFk"
data="claimResults"
@ -61,7 +59,6 @@
rule>
</vn-autocomplete>
<vn-autocomplete
vn-one
label="Responsible"
ng-model="claimDevelopment.claimResponsibleFk"
data="claimResponsibles"
@ -69,8 +66,7 @@
show-field="description"
rule>
</vn-autocomplete>
<vn-autocomplete
vn-one
<vn-autocomplete
ng-model="claimDevelopment.workerFk"
url="Clients/activeWorkersWithRole"
show-field="nickname"
@ -81,7 +77,6 @@
rule>
</vn-autocomplete>
<vn-autocomplete
vn-one
label="Redelivery"
ng-model="claimDevelopment.claimRedeliveryFk"
data="claimRedeliveries"
@ -90,6 +85,7 @@
rule>
</vn-autocomplete>
<vn-icon-button
vn-none
class="vn-my-md"
vn-tooltip="Remove sale"
icon="delete"

View File

@ -23,15 +23,17 @@
ui-sref="claim.card.summary({id: claim.id})">
<vn-td number>{{::claim.id}}</vn-td>
<vn-td expand>
<span class="link" ng-click="$ctrl.showClientDescriptor($event, claim.clientFk)">
<span
vn-click-stop="clientDescriptor.show($event, claim.clientFk)"
class="link">
{{::claim.name}}
</span>
</vn-td>
<vn-td center>{{::claim.created | date:'dd/MM/yyyy'}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="$ctrl.showWorkerDescriptor($event, claim.workerFk)">
<span
vn-click-stop="workerDescriptor.show($event, claim.workerFk)"
class="link" >
{{::claim.nickName}}
</span>
</vn-td>
@ -42,7 +44,7 @@
</vn-td>
<vn-td shrink>
<vn-icon-button
ng-click="$ctrl.preview($event, claim)"
vn-click-stop="$ctrl.preview(claim)"
vn-tooltip="Preview"
icon="desktop_windows">
</vn-icon-button>
@ -56,10 +58,10 @@
vn-id="clientDescriptor">
</vn-client-descriptor-popover>
<vn-worker-descriptor-popover
vn-id="workerDescriptor"
worker-fk="$ctrl.selectedWorker">
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-popup vn-id="dialog-summary-claim">
<vn-claim-summary claim="$ctrl.claimSelected"></vn-claim-summary>
<vn-popup vn-id="summary">
<vn-claim-summary
claim="$ctrl.claimSelected">
</vn-claim-summary>
</vn-popup>
<vn-scroll-up></vn-scroll-up>

View File

@ -13,31 +13,9 @@ export default class Controller extends Section {
}
}
showClientDescriptor(event, clientFk) {
this.$.clientDescriptor.clientFk = clientFk;
this.$.clientDescriptor.parent = event.target;
this.$.clientDescriptor.show();
event.preventDefault();
event.stopImmediatePropagation();
}
showWorkerDescriptor(event, workerFk) {
event.preventDefault();
event.stopImmediatePropagation();
this.selectedWorker = workerFk;
this.$.workerDescriptor.parent = event.target;
this.$.workerDescriptor.show();
}
preview(event, claim) {
preview(claim) {
this.claimSelected = claim;
this.$.dialogSummaryClaim.show();
event.preventDefault();
event.stopImmediatePropagation();
}
onDescriptorLoad() {
this.$.popover.relocate();
this.$.summary.show();
}
}

View File

@ -1,4 +1,6 @@
<vn-crud-model vn-id="model" auto-load="true"
<vn-crud-model
vn-id="model"
auto-load="true"
url="ClaimDms"
link="{claimFk: $ctrl.$params.id}"
limit="20"
@ -17,7 +19,7 @@
<section class="actions">
<vn-button
class="round"
ng-click="$ctrl.showDeleteConfirm($index)"
ng-click="confirm.show($index)"
title="{{'Remove file' | translate}}"
tabindex="-1"
icon="delete">
@ -25,19 +27,16 @@
</section>
</section>
</vn-horizontal>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-confirm
vn-id="confirm"
message="This file will be deleted"
question="Are you sure you want to continue?"
on-response="$ctrl.deleteDms($response)">
on-accept="$ctrl.deleteDms($data)">
</vn-confirm>
<vn-float-button fixed-bottom-right
<vn-float-button
icon="add"
vn-tooltip="Select photo"
vn-bind="+"
ng-click="$ctrl.openUploadDialog()">
ng-click="$ctrl.openUploadDialog()"
fixed-bottom-right>
</vn-float-button>

View File

@ -3,28 +3,13 @@ import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
showWorkerDescriptor(event, workerFk) {
event.preventDefault();
event.stopImmediatePropagation();
this.$.workerDescriptor.parent = event.target;
this.$.workerDescriptor.workerFk = workerFk;
this.$.workerDescriptor.show();
}
showDeleteConfirm(index) {
this.dmsIndex = index;
this.$.confirm.show();
}
deleteDms(response) {
if (response === 'accept') {
const dmsFk = this.photos[this.dmsIndex].dmsFk;
const query = `claimDms/${dmsFk}/removeFile`;
this.$http.post(query).then(() => {
this.$.model.remove(this.dmsIndex);
this.vnApp.showSuccess(this.$translate.instant('Photo deleted'));
deleteDms(index) {
const dmsFk = this.photos[index].dmsFk;
return this.$http.post(`ClaimDms/${dmsFk}/removeFile`)
.then(() => {
this.$.model.remove(index);
this.vnApp.showSuccess(this.$t('Photo deleted'));
});
}
}
onDrop($event) {
@ -36,10 +21,10 @@ class Controller extends Section {
}
setDefaultParams() {
const params = {filter: {
const filter = {
where: {code: 'claim'}
}};
return this.$http.get('DmsTypes/findOne', {params}).then(res => {
};
return this.$http.get('DmsTypes/findOne', {filter}).then(res => {
const dmsTypeId = res.data && res.data.id;
const companyId = this.vnConfig.companyFk;
const warehouseId = this.vnConfig.warehouseFk;
@ -50,7 +35,7 @@ class Controller extends Section {
warehouseId: warehouseId,
companyId: companyId,
dmsTypeId: dmsTypeId,
description: this.$translate.instant('FileDescription', {
description: this.$t('FileDescription', {
claimId: this.claim.id,
clientId: this.claim.client.id,
clientName: this.claim.client.name
@ -91,7 +76,7 @@ class Controller extends Section {
data: this.dms.files
};
this.$http(options).then(() => {
this.vnApp.showSuccess(this.$translate.instant('Photo uploaded!'));
this.vnApp.showSuccess(this.$t('Photo uploaded!'));
this.$.model.refresh();
});
}

View File

@ -6,16 +6,13 @@ describe('Claim', () => {
let $scope;
let $httpBackend;
let controller;
let $httpParamSerializer;
beforeEach(ngModule('claim'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$httpParamSerializer = _$httpParamSerializer_;
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
const $element = angular.element('<vn-claim-photos></vn-claim-photos>');
controller = $componentController('vnClaimPhotos', {$element, $scope});
controller = $componentController('vnClaimPhotos', {$element: null, $scope});
controller.$.model = crudModel;
controller.claim = {
id: 1,
@ -25,31 +22,25 @@ describe('Claim', () => {
describe('deleteDms()', () => {
it('should make an HTTP Post query', () => {
const dmsId = 1;
const dmsIndex = 0;
jest.spyOn(controller.vnApp, 'showSuccess');
jest.spyOn(controller.$.model, 'remove');
controller.photos = [{dmsFk: 1}];
controller.dmsIndex = dmsIndex;
$httpBackend.when('POST', `claimDms/${dmsId}/removeFile`).respond({});
$httpBackend.expect('POST', `claimDms/${dmsId}/removeFile`);
controller.deleteDms('accept');
const dmsId = 1;
const dmsIndex = 0;
controller.photos = [{dmsFk: 1}];
$httpBackend.expectPOST(`ClaimDms/${dmsId}/removeFile`).respond();
controller.deleteDms(dmsIndex);
$httpBackend.flush();
expect(controller.$.model.remove).toHaveBeenCalledWith(dmsIndex);
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Photo deleted');
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('setDefaultParams()', () => {
it('should make an HTTP GET query, then set all dms properties', () => {
const params = {filter: {
where: {code: 'claim'}
}};
let serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `DmsTypes/findOne?${serializedParams}`).respond({});
$httpBackend.expect('GET', `DmsTypes/findOne?${serializedParams}`);
$httpBackend.expectRoute('GET', `DmsTypes/findOne`).respond({});
controller.setDefaultParams();
$httpBackend.flush();
@ -67,13 +58,12 @@ describe('Claim', () => {
controller.dmsIndex = dmsIndex;
controller.dms = {files: []};
$httpBackend.when('POST', `claims/${claimId}/uploadFile`).respond({});
$httpBackend.expect('POST', `claims/${claimId}/uploadFile`);
$httpBackend.expectPOST(`claims/${claimId}/uploadFile`).respond({});
controller.create();
$httpBackend.flush();
expect(controller.$.model.refresh).toHaveBeenCalledWith();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Photo uploaded!');
expect(controller.$.model.refresh).toHaveBeenCalled();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
});

View File

@ -6,16 +6,20 @@
<h5>{{::$ctrl.summary.claim.id}} - {{::$ctrl.summary.claim.client.name}}</h5>
<vn-horizontal>
<vn-one>
<vn-label-value label="Created"
<vn-label-value
label="Created"
value="{{$ctrl.summary.claim.created | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="State"
<vn-label-value
label="State"
value="{{$ctrl.summary.claim.claimState.description}}">
</vn-label-value>
<vn-label-value label="Salesperson"
<vn-label-value
label="Salesperson"
value="{{$ctrl.summary.claim.client.salesPerson.user.nickname}}">
</vn-label-value>
<vn-label-value label="Attended by"
<vn-label-value
label="Attended by"
value="{{$ctrl.summary.claim.worker.user.nickname}}">
</vn-label-value>
</vn-one>
@ -61,7 +65,7 @@
<vn-tr ng-repeat="saleClaimed in $ctrl.summary.salesClaimed">
<vn-td number>
<span
ng-click="$ctrl.showItemDescriptor($event, saleClaimed.sale)"
ng-click="itemDescriptor.show($event, saleClaimed.sale.itemFk, saleClaimed.sale.id)"
class="link">
{{::saleClaimed.sale.itemFk | zeroFill:6}}
</span>
@ -113,7 +117,7 @@
<vn-td expand>
<span
class="link"
ng-click="$ctrl.showWorkerDescriptor($event, development.workerFk)">
ng-click="workerDescriptor.show($event, development.workerFk)">
{{::development.worker.user.nickname}}
</span>
</vn-td>
@ -144,14 +148,14 @@
<vn-tr ng-repeat="action in $ctrl.summary.actions">
<vn-td number>
<span
ng-click="$ctrl.showItemDescriptor($event, action.sale.itemFk)"
ng-click="itemDescriptor.show($event, action.sale.itemFk, action.sale.id)"
class="link">
{{::action.sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td number>
<span
ng-click="$ctrl.showTicketDescriptor($event, action.sale.ticket.id)"
ng-click="ticketDescriptor.show($event, action.sale.ticket.id)"
class="link">
{{::action.sale.ticket.id | zeroFill:6}}
</span>
@ -177,8 +181,7 @@
vn-id="itemDescriptor">
</vn-item-descriptor-popover>
<vn-worker-descriptor-popover
vn-id="workerDescriptor"
worker-fk="$ctrl.selectedWorker">
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-ticket-descriptor-popover
vn-id="ticketDescriptor">

View File

@ -32,24 +32,6 @@ class Controller extends Section {
this.summary = response.data;
});
}
showItemDescriptor(event, sale) {
this.$.itemDescriptor.itemFk = sale.itemFk;
this.$.itemDescriptor.parent = event.target;
this.$.itemDescriptor.show();
}
showWorkerDescriptor(event, workerFk) {
this.selectedWorker = workerFk;
this.$.workerDescriptor.parent = event.target;
this.$.workerDescriptor.show();
}
showTicketDescriptor(event, ticketId) {
this.$.ticketDescriptor.ticketFk = ticketId;
this.$.ticketDescriptor.parent = event.target;
this.$.ticketDescriptor.show();
}
}
ngModule.component('vnClaimSummary', {

View File

@ -150,9 +150,9 @@
<!-- Create custom agent dialog -->
<vn-dialog class="edit"
vn-id="customAgent"
on-accept="$ctrl.onCustomAgentAccept()">
on-accept="$ctrl.onCustomAgentAccept()"
message="New customs agent">
<tpl-body>
<h5 class="vn-py-sm" translate>New customs agent</h5>
<vn-horizontal>
<vn-textfield vn-one vn-focus
label="NIF"

View File

@ -198,9 +198,9 @@
<!-- Create custom agent dialog -->
<vn-dialog class="edit"
vn-id="customAgent"
on-accept="$ctrl.onCustomAgentAccept()">
on-accept="$ctrl.onCustomAgentAccept()"
message="New customs agent">
<tpl-body>
<h5 class="vn-py-sm" translate>New customs agent</h5>
<vn-horizontal>
<vn-textfield vn-one vn-focus
label="NIF"

View File

@ -1,44 +1,38 @@
<div>
<tpl-body>
<h6 translate>New payment</h6>
<div class="vn-pa-md">
<vn-horizontal>
<vn-date-picker
vn-one
label="Date"
ng-model="$ctrl.receipt.payed">
</vn-date-picker>
<vn-autocomplete
vn-one
url="Companies"
label="Company"
show-field="code"
value-field="id"
ng-model="$ctrl.receipt.companyFk">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
url="Banks"
label="Bank"
show-field="bank"
value-field="id"
ng-model="$ctrl.receipt.bankFk">
</vn-autocomplete>
<vn-input-number
vn-one
vn-focus
label="Amount"
ng-model="$ctrl.receipt.amountPaid"
step="0.01"
rule>
</vn-input-number>
</vn-horizontal>
</div>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate vn-focus>Accept</button>
</tpl-buttons>
</div>
<tpl-title translate>
New payment
</tpl-title>
<tpl-body>
<vn-horizontal>
<vn-date-picker
label="Date"
ng-model="$ctrl.receipt.payed">
</vn-date-picker>
<vn-autocomplete
url="Companies"
label="Company"
show-field="code"
value-field="id"
ng-model="$ctrl.receipt.companyFk">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
url="Banks"
label="Bank"
show-field="bank"
value-field="id"
ng-model="$ctrl.receipt.bankFk">
</vn-autocomplete>
<vn-input-number
vn-focus
label="Amount"
ng-model="$ctrl.receipt.amountPaid"
step="0.01"
rule>
</vn-input-number>
</vn-horizontal>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate vn-focus>Accept</button>
</tpl-buttons>

View File

@ -1,11 +1,9 @@
import ngModule from '../../module';
import Dialog from 'core/components/dialog';
import template from './index.html';
class Controller extends Dialog {
constructor($element, $, $transclude) {
super($element, $, $transclude);
this.fillSlots(template);
this.receipt = {
payed: new Date(),
@ -76,8 +74,8 @@ class Controller extends Dialog {
}
ngModule.vnComponent('vnClientBalanceCreate', {
slotTemplate: require('./index.html'),
controller: Controller,
transclude: true,
bindings: {
payed: '<?',
bankFk: '<?',

View File

@ -1,6 +1,6 @@
<vn-crud-model
vn-id="model"
url="receipts/filter"
url="Receipts/filter"
limit="20"
data="$ctrl.balances">
</vn-crud-model>
@ -66,19 +66,26 @@
</vn-td>
<vn-td>
<span
class="link"
ng-click="$ctrl.showWorkerDescriptor($event, balance.workerFk)">
vn-click-stop="workerDescriptor.show($event, balance.workerFk)"
class="link">
{{::balance.userNickname}}
</span>
</vn-td>
<vn-td expand>
<span
title="{{balance.isInvoice ? 'BILL' : balance.ref | translate: {ref: balance.ref} }}"
ng-class="{'link': balance.isInvoice}"
ng-click="$ctrl.showInvoiceOutDescriptor($event, balance)"
ng-show="balance.ref">
{{balance.isInvoice ? 'BILL' : balance.ref | translate: {ref: balance.ref} }}
</span>
<div ng-show="::balance.ref">
<span
ng-if="balance.isInvoice"
title="{{'BILL' | translate: {ref: balance.ref} }}"
vn-click-stop="invoiceOutDescriptor.show($event, balance)"
ng-class="link">
{{'BILL' | translate: {ref: balance.ref} }}
</span>
<span
ng-if="!balance.isInvoice"
title="{{::balance.ref}}">
{{::balance.ref}}
</span>
</div>
</vn-td>
<vn-td number>{{::balance.bankFk}}</vn-td>
<vn-td number expand>{{::balance.debit | currency: 'EUR':2}}</vn-td>
@ -121,10 +128,8 @@
company-fk="$ctrl.companyId">
</vn-client-balance-create>
<vn-worker-descriptor-popover
vn-id="workerDescriptor"
worker-fk="$ctrl.selectedWorker">
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-invoice-out-descriptor-popover
vn-id="invoiceOutDescriptor"
invoice-out-id="$ctrl.selectedInvoiceOut">
vn-id="invoiceOutDescriptor">
</vn-invoice-out-descriptor-popover>

View File

@ -72,29 +72,6 @@ class Controller extends Section {
}
});
}
showWorkerDescriptor(event, workerFk) {
if (event.defaultPrevented) return;
event.preventDefault();
event.stopPropagation();
this.selectedWorker = workerFk;
this.$.workerDescriptor.parent = event.target;
this.$.workerDescriptor.show();
}
showInvoiceOutDescriptor(event, balance) {
if (!balance.isInvoice) return;
if (event.defaultPrevented) return;
event.preventDefault();
event.stopPropagation();
this.selectedInvoiceOut = balance.id;
this.$.invoiceOutDescriptor.parent = event.target;
this.$.invoiceOutDescriptor.show();
}
}
Controller.$inject = ['$element', '$scope'];

View File

@ -98,9 +98,9 @@
<!-- Create bank entity dialog -->
<vn-dialog class="edit"
vn-id="bankEntityDialog"
on-accept="$ctrl.onBankEntityAccept()">
on-accept="$ctrl.onBankEntityAccept()"
message="New bank entity">
<tpl-body>
<h5 class="vn-py-sm" translate>New bank entity</h5>
<vn-horizontal>
<vn-textfield
vn-one

View File

@ -1,12 +1,3 @@
<vn-popover vn-id="popover">
<vn-spinner
ng-if="$ctrl.client == null"
style="padding: 1em;"
enable="true">
</vn-spinner>
<vn-client-descriptor
ng-if="$ctrl.client"
client="$ctrl.client"
quicklinks="$ctrl.quicklinks">
</vn-client-descriptor>
</vn-popover>
<slot-descriptor>
<vn-client-descriptor></vn-client-descriptor>
</slot-descriptor>

View File

@ -1,67 +1,9 @@
import ngModule from '../module';
import Component from 'core/lib/component';
import './style.scss';
import DescriptorPopover from 'salix/components/descriptor-popover';
class Controller extends Component {
constructor($element, $) {
super($element, $);
this.client = null;
this._quicklinks = {};
}
class Controller extends DescriptorPopover {}
set clientFk(id) {
if (id == this._clientFk) return;
this._clientFk = id;
this.client = null;
this.getCard();
}
set client(value) {
this._client = value;
this.$timeout(() => this.$.popover.relocate());
}
get client() {
return this._client;
}
get quicklinks() {
return this._quicklinks;
}
set quicklinks(value = {}) {
Object.keys(value).forEach(key => {
this._quicklinks[key] = value[key];
});
}
show() {
this.$.popover.parent = this.parent;
this.$.popover.show();
}
getCard() {
if (this.canceler)
this.canceler.resolve();
this.canceler = this.$q.defer();
let options = {timeout: this.canceler.promise};
this.$http.get(`Clients/${this._clientFk}/getCard`, options).then(
response => {
this.client = response.data;
this.canceler = null;
}
);
}
}
ngModule.component('vnClientDescriptorPopover', {
template: require('./index.html'),
controller: Controller,
bindings: {
clientFk: '<',
quicklinks: '<'
}
ngModule.vnComponent('vnClientDescriptorPopover', {
slotTemplate: require('./index.html'),
controller: Controller
});

View File

@ -1,80 +0,0 @@
import './index';
describe('Client', () => {
describe('Component vnClientDescriptorPopover', () => {
let $httpBackend;
let $scope;
let controller;
let $element;
let $timeout;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$timeout_) => {
$httpBackend = _$httpBackend_;
$timeout = _$timeout_;
$element = angular.element(`<div></div>`);
$scope = $rootScope.$new();
$scope.popover = {relocate: () => {}, show: () => {}};
controller = $componentController('vnClientDescriptorPopover', {$scope, $element});
}));
describe('clientFk()', () => {
it(`should not apply any changes if the received id is the same stored in _clientFk`, () => {
controller.client = 'I exist!';
controller._clientFk = 1;
jest.spyOn(controller, 'getCard');
controller.clientFk = 1;
expect(controller.client).toEqual('I exist!');
expect(controller._clientFk).toEqual(1);
expect(controller.getCard).not.toHaveBeenCalled();
});
it(`should set the received id into _clientFk, set the client to null and then call getCard()`, () => {
controller.client = `Please don't`;
controller._clientFk = 1;
jest.spyOn(controller, 'getCard');
controller.clientFk = 999;
expect(controller.client).toBeNull();
expect(controller._clientFk).toEqual(999);
expect(controller.getCard).toHaveBeenCalledWith();
});
});
describe('client()', () => {
it(`should save the client into _client and then call relocate()`, () => {
jest.spyOn(controller.$.popover, 'relocate');
controller.client = `i'm the client!`;
$timeout.flush();
expect(controller._client).toEqual(`i'm the client!`);
expect(controller.$.popover.relocate).toHaveBeenCalledWith();
});
});
describe('show()', () => {
it(`should call the show()`, () => {
jest.spyOn(controller.$.popover, 'show');
controller.show();
expect(controller.$.popover.show).toHaveBeenCalledWith();
});
});
describe('getCard()', () => {
it(`should perform a get query to store the client data into the controller`, () => {
controller.clientFk = 1;
controller.canceler = null;
let response = {};
$httpBackend.when('GET', `Clients/${controller._clientFk}/getCard`).respond(response);
$httpBackend.expect('GET', `Clients/${controller._clientFk}/getCard`);
controller.getCard();
$httpBackend.flush();
expect(controller.client).toEqual(response);
});
});
});
});

View File

@ -1,9 +0,0 @@
vn-client-descriptor-popover {
vn-client-descriptor {
display: block;
width: 256px;
& > vn-card{
margin: 0!important;
}
}
}

View File

@ -1,43 +1,46 @@
<div class="vn-descriptor">
<div class="header">
<a translate-attr="{title: 'Return to module index'}" ui-sref="client.index">
<vn-icon icon="chevron_left"></vn-icon>
<vn-descriptor-content
module="client"
description="$ctrl.client.name">
<slot-menu>
<a class="vn-item"
ui-sref="ticket.create({clientFk: $ctrl.client.id})"
name="simpleTicket"
translate>
Simple ticket
</a>
<a translate-attr="{title: 'Preview'}" ui-sref="client.card.summary({id: $ctrl.client.id})">
<vn-icon icon="desktop_windows"></vn-icon>
</a>
<vn-icon-menu
vn-id="more-button"
icon="more_vert"
show-filter="false"
value-field="callback"
translate-fields="['name']"
data="$ctrl.moreOptions"
on-change="$ctrl.onMoreChange(value)"
on-open="$ctrl.onMoreOpen()">
</vn-icon-menu>
</div>
<div class="body">
<vn-item
ng-click="$ctrl.showSMSDialog()"
translate>
Send SMS
</vn-item>
<vn-item
ng-click="consumerReportDialog.show()"
translate>
Send consumer report
</vn-item>
</slot-menu>
<slot-body>
<div class="attributes">
<h5>{{$ctrl.client.name}}</h5>
<vn-label-value label="Id"
value="{{$ctrl.client.id}}">
</vn-label-value>
<vn-label-value label="Phone"
<vn-label-value
label="Phone"
value="{{$ctrl.client.phone | phone}}">
</vn-label-value>
<vn-label-value label="Credit"
<vn-label-value
label="Credit"
value="{{$ctrl.client.credit | currency: 'EUR': 2}}">
</vn-label-value>
<vn-label-value label="Secured credit"
<vn-label-value
label="Secured credit"
value="{{$ctrl.client.creditInsurance | currency: 'EUR': 2}}">
</vn-label-value>
<vn-label-value label="Risk"
<vn-label-value
label="Risk"
value="{{$ctrl.client.debt | currency: 'EUR':2}}"
ng-class="{alert: $ctrl.client.debt > $ctrl.client.credit}"
info="Invoices minus payments plus orders not yet invoiced">
</vn-label-value>
<vn-label-value label="Sales person"
<vn-label-value
label="Sales person"
value="{{$ctrl.client.salesPerson.user.nickname}}">
</vn-label-value>
</div>
@ -68,36 +71,48 @@
ng-class="{bright: $ctrl.client.isTaxDataChecked == false}">
</vn-icon>
</div>
<vn-quick-links
links="$ctrl.quicklinks">
</vn-quick-links>
</div>
</div>
<!-- SMS Dialog -->
<vn-client-sms vn-id="sms" sms="$ctrl.newSMS"></vn-client-sms>
<!-- SMS Dialog -->
<div class="quicklinks">
<div ng-transclude="btnOne">
<vn-quick-link
tooltip="Client ticket list"
state="['ticket.index', {q: $ctrl.filter}]"
icon="icon-ticket">
</vn-quick-link>
</div>
<div ng-transclude="btnTwo">
<vn-quick-link
tooltip="New order"
state="['order.create', {clientFk: $ctrl.id}]"
icon="icon-basketadd">
</vn-quick-link>
</div>
<div ng-transclude="btnThree">
</div>
</div>
</slot-body>
</vn-descriptor-content>
<vn-client-sms
vn-id="sms"
sms="$ctrl.newSMS">
</vn-client-sms>
<vn-dialog
vn-id="consumerReportDialog"
on-response="$ctrl.sendConsumerReport($response)">
on-accept="$ctrl.onConsumerReportAccept()"
message="Send consumer report">
<tpl-body>
<div>
<h5 style="text-align: center">
<span translate>Send consumer report</span>
</h5>
<vn-date-picker
vn-id="from"
vn-one
ng-model="$ctrl.from"
label="From date"
vn-focus>
</vn-date-picker>
<vn-date-picker
vn-id="to"
vn-one
ng-model="$ctrl.to"
label="To date">
<vn-date-picker
vn-id="from"
vn-one
ng-model="$ctrl.from"
label="From date"
vn-focus>
</vn-date-picker>
<vn-date-picker
vn-id="to"
vn-one
ng-model="$ctrl.to"
label="To date">
</vn-date-picker>
</div>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>

View File

@ -1,97 +1,58 @@
import ngModule from '../module';
import Component from 'core/lib/component';
import Descriptor from 'salix/components/descriptor';
class Controller extends Component {
constructor($element, $, $httpParamSerializer) {
super($element, $);
this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [
{name: 'Simple ticket', callback: this.newTicket},
{name: 'Send SMS', callback: this.showSMSDialog},
{name: 'Send consumer report', callback: this.showConsumerReportDialog}
];
class Controller extends Descriptor {
get entity() {
return super.entity;
}
onMoreChange(callback) {
callback.call(this);
}
get client() {
return this._client;
}
set client(value) {
this._client = value;
if (!value) return;
set entity(value) {
super.entity = value;
if (this.$params.sendSMS)
this.showSMSDialog();
this._quicklinks = {
btnOne: {
icon: 'icon-ticket',
state: `ticket.index({q: '{"clientFk": ${value.id}}'})`,
tooltip: 'Client ticket list'
},
btnTwo: {
icon: 'icon-basketadd',
state: `order.create({clientFk: ${value.id}})`,
tooltip: 'New order'
}
};
}
set quicklinks(value = {}) {
this._quicklinks = Object.assign(value, this._quicklinks);
get client() {
return this.entity;
}
get quicklinks() {
return this._quicklinks;
set client(value) {
this.entity = value;
}
newTicket() {
this.$state.go('ticket.create', {clientFk: this.client.id});
get filter() {
return JSON.stringify({clientFk: this.id});
}
loadData() {
return this.getData(`Clients/${this.id}/getCard`)
.then(res => this.entity = res.data);
}
showSMSDialog() {
const client = this.client;
const phone = this.$params.phone || client.mobile || client.phone;
const message = this.$params.message || '';
const client = this.client || {};
this.newSMS = {
destinationFk: client.id,
destination: phone,
message: message
destinationFk: this.id,
destination: this.$params.phone || client.mobile || client.phone,
message: this.$params.message || ''
};
this.$.sms.open();
}
showConsumerReportDialog() {
this.$.consumerReportDialog.show();
}
sendConsumerReport(response) {
if (response === 'accept') {
const params = {
authorization: this.vnToken.token,
clientId: this.client.id,
from: this.from,
to: this.to,
};
const serializedParams = this.$httpParamSerializer(params);
const url = `api/report/campaign-metrics?${serializedParams}`;
window.open(url);
}
onConsumerReportAccept() {
this.showReport('campaign-metrics', {
clientId: this.id,
from: this.from,
to: this.to,
});
}
}
Controller.$inject = ['$element', '$scope', '$httpParamSerializer'];
ngModule.component('vnClientDescriptor', {
ngModule.vnComponent('vnClientDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
client: '<',
quicklinks: '<'
},
controller: Controller
client: '<'
}
});

View File

@ -0,0 +1,26 @@
import './index';
describe('vnClientDescriptor', () => {
let controller;
let $httpBackend;
beforeEach(ngModule('client'));
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
controller = $componentController('vnClientDescriptor', {$element: null});
}));
describe('loadData()', () => {
it(`should perform a get query to store the client data into the controller`, () => {
const id = 1;
const response = 'foo';
$httpBackend.expectGET(`Clients/${id}/getCard`).respond(response);
controller.id = id;
$httpBackend.flush();
expect(controller.client).toEqual(response);
});
});
});

View File

@ -62,7 +62,7 @@
</vn-td>
<vn-td shrink>
<span class="link"
ng-click="$ctrl.showWorkerDescriptor($event, document.dms.workerFk)">
ng-click="workerDescriptor.show($event, document.dms.workerFk)">
{{::document.dms.worker.user.nickname | dashIfEmpty}}
</span></vn-td>
<vn-td>
@ -86,7 +86,7 @@
<vn-td shrink>
<vn-icon-button
icon="delete"
ng-click="$ctrl.showDeleteConfirm($index)"
ng-click="confirm.show($index)"
title="{{'Remove file' | translate}}"
tabindex="-1">
</vn-icon-button>
@ -109,5 +109,5 @@
vn-id="confirm"
message="This file will be deleted"
question="Are you sure you want to continue?"
on-response="$ctrl.deleteDms($response)">
on-accept="$ctrl.deleteDms($data)">
</vn-confirm>

View File

@ -24,8 +24,7 @@ class Controller extends Section {
scope: {
fields: ['name']
}
},
{
}, {
relation: 'worker',
scope: {
fields: ['userFk'],
@ -42,28 +41,13 @@ class Controller extends Section {
};
}
showWorkerDescriptor(event, workerFk) {
event.preventDefault();
event.stopImmediatePropagation();
this.$.workerDescriptor.parent = event.target;
this.$.workerDescriptor.workerFk = workerFk;
this.$.workerDescriptor.show();
}
showDeleteConfirm(index) {
this.dmsIndex = index;
this.$.confirm.show();
}
deleteDms(response) {
if (response === 'accept') {
const dmsFk = this.clientDms[this.dmsIndex].dmsFk;
const query = `clientDms/${dmsFk}/removeFile`;
this.$http.post(query).then(() => {
this.$.model.remove(this.dmsIndex);
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
deleteDms(index) {
const dmsFk = this.clientDms[index].dmsFk;
return this.$http.post(`ClientDms/${dmsFk}/removeFile`)
.then(() => {
this.$.model.remove(index);
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
}
}

View File

@ -12,27 +12,25 @@ describe('Client', () => {
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
const $element = angular.element('<vn-client-dms-index></vn-client-dms-index>');
controller = $componentController('vnClientDmsIndex', {$element, $scope});
controller = $componentController('vnClientDmsIndex', {$element: null, $scope});
controller.$.model = crudModel;
}));
describe('deleteDms()', () => {
it('should make an HTTP Post query', () => {
const dmsId = 1;
const dmsIndex = 0;
jest.spyOn(controller.vnApp, 'showSuccess');
jest.spyOn(controller.$.model, 'remove');
controller.clientDms = [{dmsFk: 1}];
controller.dmsIndex = dmsIndex;
$httpBackend.when('POST', `clientDms/${dmsId}/removeFile`).respond({});
$httpBackend.expect('POST', `clientDms/${dmsId}/removeFile`);
controller.deleteDms('accept');
const dmsId = 1;
const dmsIndex = 0;
controller.clientDms = [{dmsFk: 1}];
$httpBackend.expectPOST(`ClientDms/${dmsId}/removeFile`).respond();
controller.deleteDms(dmsIndex);
$httpBackend.flush();
expect(controller.$.model.remove).toHaveBeenCalledWith(dmsIndex);
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
});

View File

@ -1,9 +1,9 @@
<vn-dialog class="edit"
vn-id="postcodeDialog"
on-open="$ctrl.onOpen()"
on-accept="$ctrl.onAccept()">
on-accept="$ctrl.onAccept()"
message="New postcode">
<tpl-body>
<h5 class="vn-py-sm" translate>New postcode</h5>
<p translate>Please, ensure you put the correct data!</p>
<vn-horizontal>
<vn-textfield

View File

@ -1,12 +1,12 @@
{
"module": "client",
"name": "Clients",
"icon": "icon-person",
"icon": "person",
"validations" : true,
"dependencies": ["worker", "invoiceOut"],
"menus": {
"main": [
{"state": "client.index", "icon": "icon-person"}
{"state": "client.index", "icon": "person"}
],
"card": [
{"state": "client.card.basicData", "icon": "settings"},

View File

@ -29,9 +29,10 @@
{{::sample.type.description}}
</vn-td>
<vn-td>
<span class="link"
ng-click="$ctrl.showWorkerDescriptor($event, sample.worker.id)">
{{::sample.worker.user.nickname}}
<span
ng-click="workerDescriptor.show($event, sample.worker.id)"
class="link">
{{::sample.worker.user.nickname}}
</span>
</vn-td>
<vn-td>{{::sample.company.code}}</vn-td>
@ -41,8 +42,7 @@
</vn-card>
</vn-data-viewer>
<vn-worker-descriptor-popover
vn-id="workerDescriptor"
worker-fk="$ctrl.selectedWorker">
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<a
ui-sref="client.card.sample.create"

View File

@ -11,8 +11,7 @@ class Controller extends Section {
scope: {
fields: ['code', 'description']
}
},
{
}, {
relation: 'worker',
scope: {
fields: ['userFk'],
@ -23,8 +22,7 @@ class Controller extends Section {
}
}
}
},
{
}, {
relation: 'company',
scope: {
fields: ['code']
@ -33,20 +31,8 @@ class Controller extends Section {
]
};
}
showWorkerDescriptor(event, workerFk) {
if (event.defaultPrevented) return;
event.preventDefault();
event.stopImmediatePropagation();
this.selectedWorker = workerFk;
this.$.workerDescriptor.parent = event.target;
this.$.workerDescriptor.show();
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.component('vnClientSampleIndex', {
template: require('./index.html'),
controller: Controller

View File

@ -1,30 +1,18 @@
<div class="vn-descriptor">
<div class="header">
<a translate-attr="{title: 'Return to module index'}" ui-sref="entry.index">
<vn-icon icon="chevron_left"></vn-icon>
</a>
<a translate-attr="{title: 'Preview'}" ui-sref="entry.card.summary({id: $ctrl.entry.id})">
<vn-icon icon="desktop_windows"></vn-icon>
</a>
<vn-icon-menu
vn-id="more-button"
icon="more_vert"
show-filter="false"
value-field="callback"
translate-fields="['name']"
data="$ctrl.moreOptions"
on-change="$ctrl.onMoreChange(value)"
on-open="$ctrl.onMoreOpen()">
</vn-icon-menu>
</div>
<div class="body">
<vn-descriptor-content
module="entry"
description="$ctrl.entry.supplier.nickname">
<slot-menu>
<vn-item
ng-click="$ctrl.showEntryReport()"
translate>
Show entry report
</vn-item>
</slot-menu>
<slot-body>
<div class="attributes">
<vn-label-value label="Id"
value="{{$ctrl.entry.id}}">
</vn-label-value>
<vn-label-value label="Supplier"
value="{{$ctrl.entry.supplier.nickname}}">
</vn-label-value>
<vn-label-value label="Agency "
value="{{$ctrl.entry.travel.agency.name}}">
</vn-label-value>
@ -47,8 +35,23 @@
ng-class="{bright: $ctrl.entry.isRaid}">
</vn-icon>
</div>
<vn-quick-links
links="$ctrl.quicklinks">
</vn-quick-links>
</div>
</div>
<div class="quicklinks">
<div ng-transclude="btnOne">
<vn-quick-link
tooltip="All travels with current agency"
state="['travel.index', {q: $ctrl.travelFilter}]"
icon="local_airport">
</vn-quick-link>
</div>
<div ng-transclude="btnTwo">
<vn-quick-link
tooltip="All entries with current supplier"
state="['entry.index', {q: $ctrl.entryFilter}]"
icon="icon-entry">
</vn-quick-link>
</div>
<div ng-transclude="btnThree">
</div>
</div>
</slot-body>
</vn-descriptor-content>

View File

@ -1,84 +1,52 @@
import ngModule from '../module';
import Component from 'core/lib/component';
class Controller extends Component {
constructor($element, $, $httpParamSerializer) {
super($element, $);
this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [
{name: 'Show entry report', callback: this.showEntryReport}
];
}
onMoreChange(callback) {
callback.call(this);
}
import Descriptor from 'salix/components/descriptor';
class Controller extends Descriptor {
get entry() {
return this._entry;
return this.entity;
}
set entry(value) {
this._entry = value;
if (!value) return;
this.entity = value;
}
const date = value.travel.landed;
let to = new Date(date);
let from = new Date(date);
get travelFilter() {
return this.entry && JSON.stringify({
agencyFk: this.entry.travel.agencyFk
});
}
get entryFilter() {
if (!this.entry) return null;
const date = new Date(this.entry.travel.landed);
date.setHours(0, 0, 0, 0);
const from = new Date(date.getTime());
from.setDate(from.getDate() - 10);
const to = new Date(date.getTime());
to.setDate(to.getDate() + 10);
to.setHours(0, 0, 0, 0);
from.setDate(from.getDate() - 10);
from.setHours(0, 0, 0, 0);
let links = {
btnOne: {
icon: 'local_airport',
state: `travel.index({q: '{"agencyFk": ${value.travel.agencyFk}}'})`,
tooltip: 'All travels with current agency'
}};
links.btnTwo = {
icon: 'icon-entry',
state: `entry.index({q: '{"supplierFk": ${value.supplierFk}, "to": "${to}", "from": "${from}"}'})`,
tooltip: 'All entries with current supplier'
};
this._quicklinks = links;
}
get quicklinks() {
return this._quicklinks;
}
set quicklinks(value = {}) {
this._quicklinks = Object.assign(value, this._quicklinks);
return JSON.stringify({
supplierFk: this.entry.supplierFk,
from,
to
});
}
showEntryReport() {
const params = {
authorization: this.vnToken.token,
this.showReport('entry-order', {
clientId: this.vnConfig.storage.currentUserWorkerId,
entryId: this.entry.id
};
const serializedParams = this.$httpParamSerializer(params);
let url = `api/report/entry-order?${serializedParams}`;
window.open(url);
});
}
}
Controller.$inject = ['$element', '$scope', '$httpParamSerializer'];
ngModule.component('vnEntryDescriptor', {
ngModule.vnComponent('vnEntryDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
entry: '<',
quicklinks: '<'
},
require: {
card: '^?vnEntryCard'
},
controller: Controller
entry: '<'
}
});

View File

@ -1,35 +1,26 @@
import './index.js';
describe('Entry Component vnEntryDescriptor', () => {
let $httpParamSerializer;
let controller;
let $element;
const entry = {id: 2};
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, $rootScope, _$httpParamSerializer_) => {
$httpParamSerializer = _$httpParamSerializer_;
$element = angular.element(`<vn-entry-descriptor></vn-entry-descriptor>`);
controller = $componentController('vnEntryDescriptor', {$element});
controller._entry = {id: 2};
controller.vnConfig.storage = {currentUserWorkerId: 9};
controller.cardReload = ()=> {
return true;
};
beforeEach(inject($componentController => {
controller = $componentController('vnEntryDescriptor', {$element: null}, {entry});
}));
describe('showEntryReport()', () => {
it('should open a new window showing a delivery note PDF document', () => {
controller.showReport = jest.fn();
const params = {
clientId: controller.vnConfig.storage.currentUserWorkerId,
entryId: controller.entry.id
entryId: entry.id
};
const serializedParams = $httpParamSerializer(params);
let expectedPath = `api/report/entry-order?${serializedParams}`;
jest.spyOn(window, 'open').mockReturnThis();
controller.showEntryReport();
expect(window.open).toHaveBeenCalledWith(expectedPath);
expect(controller.showReport).toHaveBeenCalledWith('entry-order', params);
});
});
});

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