Merge branch 'dev' of https://git.verdnatura.es/salix into dev

This commit is contained in:
Javi Gallego 2018-06-09 11:56:29 +02:00
commit 1b4c97fa6a
100 changed files with 2308 additions and 1287 deletions

View File

@ -75,20 +75,20 @@
</vn-autocomplete>
<vn-textfield
vn-two
margin-large-right
label="Description"
model="observation.description"
rule="addressObservation.description">
</vn-textfield>
<vn-auto pad-medium-top>
<vn-icon
pointer
<vn-none>
<vn-icon-button
medium-grey
margin-medium-v
vn-tooltip="Remove note"
icon="remove_circle_outline"
ng-click="$ctrl.removeObservation($index)">
</vn-icon>
</vn-one>
ng-click="$ctrl.removeObservation($index)"
tabindex="-1">
</vn-icon-button>
</vn-none>
</vn-horizontal>
</div>
<vn-icon-button

View File

@ -4,7 +4,7 @@
<vn-title vn-one>Addresses</vn-title>
<vn-horizontal ng-repeat="address in index.model.items track by address.id" class="pad-medium-top" style="align-items: center;">
<vn-one border-radius class="pad-small border-solid"
ng-class="{'bg-dark-item': address.isDefaultAddress,'bg-opacity-item': !address.isActive && !address.isDefaultAddress}">
ng-class="{'bg-main': address.isDefaultAddress,'bg-opacity-item': !address.isActive && !address.isDefaultAddress}">
<vn-horizontal style="align-items: center;">
<vn-none pad-medium-h>
<i class="material-icons"

View File

@ -2,9 +2,9 @@
<vn-card pad-large>
<vn-title vn-one>Contract credit insurance</vn-title>
<vn-horizontal ng-repeat="classification in $ctrl.classifications track by classification.id" class="pad-medium-top" style="align-items: center;">
<vn-one border-radius class="pad-small border-solid" ng-class="{'bg-dark-item': !classification.finished,'bg-opacity-item': classification.finished}">
<vn-one border-radius class="pad-small border-solid" ng-class="{'bg-main': !classification.finished,'bg-opacity-item': classification.finished}">
<vn-horizontal style="align-items: center;">
<vn-none pad-medium-h style="color:#FFA410;">
<vn-none pad-medium-h orange>
<i class="material-icons pointer"
ng-if="!classification.finished"
vn-tooltip="Close contract"

View File

@ -1,25 +1,30 @@
<mg-ajax path="/client/api/Clients/filter" options="vnIndexNonAuto"></mg-ajax>
<vn-crud-model
vn-id="model"
url="/item/api/Clients"
filter="::$ctrl.filter"
limit="8"
data="clients"
auto-load="false">
</vn-crud-model>
<div margin-medium>
<div class="vn-list">
<vn-card>
<vn-horizontal>
<vn-searchbar vn-one
index="index"
on-search="$ctrl.search(index)"
advanced="true"
popover="vn-client-search-panel"
ignore-keys = "['page', 'size', 'search']">
</vn-searchbar>
</vn-horizontal>
<vn-card pad-medium-h>
<vn-searchbar
panel="vn-client-search-panel"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
</vn-searchbar>
</vn-card>
<vn-card margin-medium-top>
<vn-card margin-medium-v>
<vn-item-client
ng-repeat="client in index.model.instances track by client.id"
client="client">
ng-repeat="client in clients track by client.id"
client="::client">
</vn-item-client>
</vn-card>
<vn-paging index="index" total="index.model.count"></vn-paging>
<!-- <vn-auto-paging index="index" total="index.model.count" items="$ctrl.clients"></vn-auto-paging> -->
<vn-pagination
model="model"
scroll-selector="ui-view">
</vn-pagination>
</div>
</div>
<a ui-sref="client.create" vn-bind="+" fixed-bottom-right>
@ -30,4 +35,4 @@
<tpl-body>
<vn-client-summary client="$ctrl.clientSelected"></vn-client-summary>
</tpl-body>
</vn-dialog>
</vn-dialog>

View File

@ -2,23 +2,42 @@ import ngModule from '../module';
import './item-client';
export default class Controller {
constructor($scope) {
this.$scope = $scope;
this.$ = $scope;
this.clientSelected = null;
}
search(index) {
index.accept();
/* this.clients = [];
index.accept().then(res => {
this.clients = res.instances;
}); */
exprBuilder(param, value) {
switch (param) {
case 'search':
return {
or: [
{id: value},
{name: {regexp: value}}
]
};
case 'phone':
return {
or: [
{phone: value},
{mobile: value}
]
};
case 'name':
case 'socialName':
case 'city':
return {[param]: {regexp: value}};
case 'id':
case 'fi':
case 'postcode':
case 'email':
return {[param]: value};
}
}
openSummary(client) {
this.clientSelected = client;
this.$scope.dialogSummaryClient.show();
this.$.dialogSummaryClient.show();
}
}
Controller.$inject = ['$scope'];

View File

@ -2,4 +2,5 @@ Client id: Id cliente
Phone: Teléfono
Town/City: Ciudad
Email: Correo electrónico
Create client: Crear cliente
Create client: Crear cliente
View client: Ver cliente

View File

@ -1,22 +1,22 @@
<div pad-large style="min-width: 30em">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield vn-one label="Client id" model="$ctrl.filter.id" vn-focus></vn-textfield>
<vn-textfield vn-one label="Tax number" model="$ctrl.filter.fi"></vn-textfield>
<vn-textfield vn-one label="Client id" model="filter.id" vn-focus></vn-textfield>
<vn-textfield vn-one label="Tax number" model="filter.fi"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Name" model="$ctrl.filter.name"></vn-textfield>
<vn-textfield vn-one label="Name" model="filter.name"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Social name" model="$ctrl.filter.socialName"></vn-textfield>
<vn-textfield vn-one label="Social name" model="filter.socialName"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Town/City" model="$ctrl.filter.city"></vn-textfield>
<vn-textfield vn-one label="Postcode" model="$ctrl.filter.postcode"></vn-textfield>
<vn-textfield vn-one label="Town/City" model="filter.city"></vn-textfield>
<vn-textfield vn-one label="Postcode" model="filter.postcode"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Email" model="$ctrl.filter.email"></vn-textfield>
<vn-textfield vn-one label="Phone" model="$ctrl.filter.phone"></vn-textfield>
<vn-textfield vn-one label="Email" model="filter.email"></vn-textfield>
<vn-textfield vn-one label="Phone" model="filter.phone"></vn-textfield>
</vn-horizontal>
<vn-horizontal margin-large-top>
<vn-submit label="Search"></vn-submit>

View File

@ -1,18 +1,7 @@
import ngModule from '../module';
export default class Controller {
constructor() {
// onSubmit() is defined by @vnSearchbar
this.onSubmit = () => {};
}
onSearch() {
this.onSubmit(this.filter);
}
}
Controller.$inject = [];
import SearchPanel from 'core/src/components/searchbar/search-panel';
ngModule.component('vnClientSearchPanel', {
template: require('./index.html'),
controller: Controller
controller: SearchPanel
});

View File

@ -1,7 +1,7 @@
@import "colors";
vn-grid-header {
border-bottom: 3px solid $main-header;
border-bottom: 3px solid $lines;
font-weight: bold;
.orderly{
text-align: center;

View File

@ -19,10 +19,10 @@
}
}
& > thead, & > tbody {
border-bottom: 3px solid $main-header;
border-bottom: 3px solid $lines;
}
& > tbody > tr {
border-bottom: 1px solid $main-header;
border-bottom: 1px solid $lines;
transition: background-color 200ms ease-in-out;
&.clickable {

View File

@ -3,7 +3,8 @@ import './style.scss';
export default class IconButton {
constructor($element) {
$element[0].tabIndex = 0;
if ($element[0].getAttribute('tabindex') == null)
$element[0].tabIndex = 0;
$element.on("keyup", event => this.onKeyDown(event, $element));
}

View File

@ -2,32 +2,16 @@
vn-icon-button {
display: inline-block;
text-align: center;
color: rgba($main-01, 0.7);
font-size: 18pt;
transition: color 200ms ease-in-out;
cursor: pointer;
&.button {
background-color: $main-01;
color: white;
width: 64px;
height: 36px;
box-shadow: rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px;
border-radius: 2px;
}
&.button:focus {
will-change: box-shadow;
box-shadow: 0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);
transition: box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1);
}
&.button i {
margin-top: 6px;
}
& > i,
& > i.material-icons {
& > vn-icon {
display: block;
font-size: inherit;
color: inherit;
margin: 0 auto;
}
&:not(.button):hover {
color: $main-01;

View File

@ -3,7 +3,6 @@ import './rest-model/crud-model';
import './rest-model/rest-model';
import './watcher/watcher';
import './textfield/textfield';
import './paging/paging';
import './icon/icon';
import './dialog/dialog';
import './confirm/confirm';
@ -34,4 +33,7 @@ import './switch/switch';
import './float-button/float-button';
import './step-control/step-control';
import './label-value/label-value';
import './paging/paging';
import './auto-paging/auto-paging';
import './pagination/pagination';
import './searchbar/searchbar';

View File

@ -2,7 +2,7 @@ import ngModule from '../../module';
import './style.scss';
ngModule.component('vnLabelValue', {
template: require('../label-value/label-value.html'),
template: require('./label-value.html'),
replace: true,
transclude: true,
bindings: {

View File

@ -12,13 +12,17 @@ export default class ModelProxy {
set orgData(value) {
this._orgData = value;
this._data = [];
// this._data.splice(0, this._data.length);
for (let i = 0; i < value.length; i++) {
let row = new this.Row(value[i], i);
this._data.push(row);
}
if (this.Row) {
this._data = [];
for (let i = 0; i < value.length; i++) {
let row = new this.Row(value[i], i);
this._data.push(row);
}
} else
this._data = value;
this.resetChanges();
}
@ -112,6 +116,11 @@ export default class ModelProxy {
this.resetChanges();
}
dataChanged() {
if (this.onDataChange)
this.onDataChange();
}
}
ngModule.component('vnModelProxy', {
@ -120,6 +129,7 @@ ngModule.component('vnModelProxy', {
orgData: '<?',
data: '=?',
fields: '<?',
link: '<?'
link: '<?',
onDataChange: '&?'
}
});

View File

@ -0,0 +1,13 @@
<div
ng-if="$ctrl.model.moreRows">
<vn-icon-button
ng-if="!$ctrl.model.isLoading"
icon="more_horiz"
vn-tooltip="Load more"
ng-click="$ctrl.model.loadMore()">
</vn-icon-button>
<vn-spinner
ng-if="$ctrl.model.isLoading"
enable="$ctrl.model.isLoading">
</vn-spinner>
</div>

View File

@ -0,0 +1,75 @@
import ngModule from '../../module';
import Component from '../../lib/component';
import './style.scss';
/**
* Pagination component that automatically loads more rows when
* the user scrolls down an element.
*
* @property {CrudModel} model The model used for pagination
* @property {String} scrollSelector The the scrollable element selector
* @property {HTMLElement} scrollElement The scrollable element
* @property {Number} scrollOffset The distance, in pixels, until the end that activates the loading of the next rows
*/
class Pagination extends Component {
constructor($element, $scope) {
super($element, $scope);
this.scrollOffset = 20;
this.scrollHandler = e => this.onScroll(e);
}
$onInit() {
if (!this._scrollElement)
this.scrollElement = document.body;
}
set scrollSelector(value) {
this._scrollSelector = value;
this.scrollElement = document.querySelector(value);
}
get scrollSelector() {
return this._scrollSelector;
}
set scrollElement(value) {
if (this._scrollElement)
this._scrollElement.removeEventListener('scroll', this.scrollHandler);
this._scrollElement = value;
if (value)
this._scrollElement.addEventListener('scroll', this.scrollHandler);
}
get scrollElement() {
return this._scrollElement;
}
onScroll() {
let scrollElement = this.scrollElement;
let shouldLoad =
scrollElement.scrollTop + scrollElement.clientHeight >= (scrollElement.scrollHeight - this.scrollOffset)
&& !this.model.isLoading;
if (shouldLoad) {
this.model.loadMore();
this.$.$apply();
}
}
$onDestroy() {
this.scrollElement = null;
}
}
ngModule.component('vnPagination', {
template: require('./pagination.html'),
bindings: {
model: '<',
scrollSelector: '@?',
scrollElement: '<?',
scrollOffset: '<?'
},
controller: Pagination
});

View File

@ -0,0 +1,9 @@
vn-pagination {
display: block;
text-align: center;
& > div > vn-icon-button {
font-size: 2em;
}
}

View File

@ -10,32 +10,80 @@ export default class CrudModel extends ModelProxy {
this.autoLoad = true;
}
get isLoading() {
return this.canceler != null;
}
$onInit() {
if (this.autoLoad)
this.refresh();
}
refresh() {
refresh(usFilter, usData) {
if (!this.url) return;
let myFilter = {
fields: this.fields,
where: mergeWhere(this.link, this.where),
include: this.include,
order: this.order,
limit: this.limit,
userData: this.userData
};
let filter = this.filter;
filter = mergeFilters(myFilter, filter);
filter = mergeFilters(usFilter, filter);
return this.sendRequest(filter);
}
if (!filter) {
let where = Object.assign({}, this.link, this.where);
filter = {
fields: this.fields,
where: where,
include: this.include,
order: this.order,
limit: this.limit
};
cancelRequest() {
if (this.canceler) {
this.canceler.resolve();
this.canceler = null;
}
}
let urlFilter = encodeURIComponent(JSON.stringify(filter));
sendRequest(filter, append) {
this.cancelRequest();
this.canceler = this.$q.defer();
let options = {timeout: this.canceler.promise};
let json = encodeURIComponent(JSON.stringify(filter));
return this.$http.get(`${this.url}?filter=${json}`, options).then(
json => this.onRemoteDone(json, filter, append),
json => this.onRemoteError(json)
);
}
return this.$http.get(`${this.url}?filter=${urlFilter}`).then(res => {
this.orgData = res.data;
});
onRemoteDone(json, filter, append) {
let data = json.data;
if (append)
this.orgData = this.orgData.concat(data);
else
this.orgData = data;
this.currentFilter = filter;
this.moreRows = filter.limit && data.length == filter.limit;
this.onRequestEnd();
this.dataChanged();
}
onRemoteError(err) {
this.onRequestEnd();
throw err;
}
onRequestEnd() {
this.canceler = null;
}
loadMore() {
if (this.moreRows) {
let filter = Object.assign({}, this.currentFilter);
filter.skip = (filter.skip || 0) + filter.limit;
this.sendRequest(filter, true);
}
}
getChanges() {
@ -97,7 +145,87 @@ ngModule.component('vnCrudModel', {
order: '@?',
limit: '<?',
filter: '<?',
userData: '<?',
primaryKey: '@?',
autoLoad: '<?'
}
});
/**
* Passes a loopback fields filter to an object.
*
* @param {Object} fields The fields object or array
* @return {Object} The fields as object
*/
function fieldsToObject(fields) {
let fieldsObj = {};
if (Array.isArray(fields))
for (let field of fields)
fieldsObj[field] = true;
else if (typeof fields == 'object')
for (let field in fields)
if (fields[field])
fieldsObj[field] = true;
return fieldsObj;
}
/**
* Merges two loopback fields filters.
*
* @param {Object|Array} src The source fields
* @param {Object|Array} dst The destination fields
* @return {Array} The merged fields as an array
*/
function mergeFields(src, dst) {
let fields = {};
Object.assign(fields,
fieldsToObject(src),
fieldsToObject(dst)
);
return Object.keys(fields);
}
/**
* Merges two loopback where filters.
*
* @param {Object|Array} src The source where
* @param {Object|Array} dst The destination where
* @return {Array} The merged wheres
*/
function mergeWhere(src, dst) {
let and = [];
if (src) and.push(src);
if (dst) and.push(dst);
return and.length > 1 ? {and} : and[0];
}
/**
* Merges two loopback filters returning the merged filter.
*
* @param {Object} src The source filter
* @param {Object} dst The destination filter
* @return {Object} The result filter
*/
function mergeFilters(src, dst) {
let res = Object.assign({}, dst);
if (!src)
return res;
if (src.fields)
res.fields = mergeFields(src.fields, res.fields);
if (src.where)
res.where = mergeWhere(res.where, src.where);
if (src.include)
res.include = src.include;
if (src.order)
res.order = src.order;
if (src.limit)
res.limit = src.limit;
if (src.userData)
res.userData = src.userData;
return res;
}

View File

@ -0,0 +1,18 @@
import Component from '../../lib/component';
export default class extends Component {
set filter(value) {
this.$.filter = value;
}
get filter() {
return this.$.filter;
}
onSearch() {
if (!this.onSubmit)
throw new Error('SearchPanel::onSubmit() method not defined');
this.onSubmit(this.filter);
}
}

View File

@ -5,11 +5,11 @@
ng-click="$ctrl.clearFilter(); $ctrl.onSubmit()"
style="cursor: pointer; padding-top: 23px">
</vn-icon-button>
<vn-textfield vn-one label="Search" model="$ctrl.stringSearch"></vn-textfield>
<vn-textfield vn-one label="Search" model="$ctrl.searchString"></vn-textfield>
<vn-icon
pad-medium-top
ng-if="$ctrl.advanced"
ng-click="$ctrl.onpenFilters($event)"
ng-if="$ctrl.panel"
ng-click="$ctrl.openPanel($event)"
icon="keyboard_arrow_down"
style="cursor: pointer; color: #aaa">
</vn-icon>

View File

@ -0,0 +1,200 @@
import ngModule from '../../module';
import Component from '../../lib/component';
import './style.scss';
/**
* An input specialized to perform searches, it allows to use a panel
* for advanced searches when the panel property is defined.
* When model and exprBuilder properties are used, the model is updated
* automatically with an and-filter exprexion in which each operand is built
* by calling the exprBuilder function for each non-null parameter.
*
* @property {Object} filter A key-value object with filter parameters
* @property {Function} onSearch Function to call when search is submited
* @property {SearchPanel} panel The panel used for advanced searches
* @property {CrudModel} model The model used for searching
* @property {Function} exprBuilder If defined, is used to build each non-null param expresion
*/
export default class Controller extends Component {
constructor($element, $scope, $compile, $state, $transitions) {
super($element, $scope);
this.$compile = $compile;
this.$state = $state;
this.deregisterCallback = $transitions.onStart({},
transition => this.changeState(transition));
this.filter = {};
this.searchString = '';
}
$onInit() {
if (this.$state.params.q)
this.filter = JSON.parse(decodeURIComponent(this.$state.params.q));
this.refreshString();
this.doSearch();
}
changeState(transition) {
return transition._targetState._identifier.name !== this.$state.current.name;
}
openPanel(event) {
if (event.defaultPrevented) return;
event.preventDefault();
this.$panel = this.$compile(`<${this.panel}/>`)(this.$.$new());
let panel = this.$panel.isolateScope().$ctrl;
panel.filter = this.filter;
panel.onSubmit = filter => this.onPanelSubmit(filter);
this.$.popover.parent = this.element;
this.$.popover.child = this.$panel[0];
this.$.popover.show();
}
onPopoverClose() {
this.$panel.scope().$destroy();
this.$panel.remove();
this.$panel = null;
}
onPanelSubmit(filter) {
this.$.popover.hide();
for (let param in filter)
if (filter[param] == null)
delete filter[param];
this.filter = filter;
this.refreshString();
this.doSearch();
}
refreshString() {
this.searchString = this.getStringFromObject(this.filter);
}
onSubmit() {
this.filter = this.getObjectFromString(this.searchString);
this.doSearch();
}
doSearch() {
this.pushFilterToState(this.filter);
if (this.onSearch)
this.onSearch({filter: this.filter});
if (this.model) {
if (!this.exprBuilder)
throw new Error('exprBuilder property should be defined when model is assigned');
let and = [];
for (let param in this.filter) {
let value = this.filter[param];
if (value == null) continue;
let expr = this.exprBuilder({param, value});
if (expr) and.push(expr);
}
let lbFilter = and.length > 0 ? {where: {and}} : null;
this.model.refresh(lbFilter);
}
}
pushFilterToState(filter) {
let history = window.history || {pushState: () => {
console.error('Error in history.pushState(): Browser incompatibility error');
}};
let aux = window.location.hash.split('?q=');
if (Object.keys(filter).length)
history.pushState({}, null, `${aux[0]}?q=${encodeURIComponent(JSON.stringify(filter))}`);
else
history.pushState({}, null, aux[0]);
}
/**
* Finds pattern key:value or key:(extra value) and passes it to object.
*
* @param {String} searchString The search string
* @return {Object} The parsed object
*/
getObjectFromString(searchString) {
let result = {};
if (searchString) {
let regex = /((([\w_]+):([\w_]+))|([\w_]+):\(([\w_ ]+)\))/gi;
let findPattern = searchString.match(regex);
let remnantString = searchString.replace(regex, '').trim();
if (findPattern) {
for (let i = 0; i < findPattern.length; i++) {
let aux = findPattern[i].split(':');
let property = aux[0];
let value = aux[1].replace(/\(|\)/g, '');
result[property] = value.trim();
}
}
if (remnantString)
result.search = remnantString;
}
return result;
}
/**
* Passes an object to pattern key:value or key:(extra value).
*
* @param {Object} searchObject The search object
* @return {String} The passed string
*/
getStringFromObject(searchObject) {
let search = [];
if (searchObject) {
let keys = Object.keys(searchObject);
keys.forEach(key => {
if (key == 'search') return;
let value = searchObject[key];
let valueString;
if (typeof value === 'string' && value.indexOf(' ') !== -1)
valueString = `(${value})`;
else if (value instanceof Date)
valueString = value.toJSON();
else
switch (typeof value) {
case 'number':
case 'string':
case 'boolean':
valueString = `${value}`;
}
if (valueString)
search.push(`${key}:${valueString}`);
});
if (searchObject.search)
search.unshift(searchObject.search);
}
return search.length ? search.join(' ') : '';
}
$onDestroy() {
this.deregisterCallback();
}
}
Controller.$inject = ['$element', '$scope', '$compile', '$state', '$transitions'];
ngModule.component('vnSearchbar', {
template: require('./searchbar.html'),
bindings: {
filter: '<?',
onSearch: '&',
panel: '@',
model: '<?',
exprBuilder: '&?'
},
controller: Controller
});

View File

@ -1,7 +1,6 @@
vn-searchbar {
padding-top: 6px;
padding-left: 16px;
padding-right: 16px;
display: block;
& > form > vn-horizontal > vn-icon-button {
color: black;

View File

@ -11,4 +11,5 @@ Hide: Hide
Next: Next
Finalize: Finalize
Previous: Back
Load more: Load more
Auto-scroll interrupted, please adjust the search: Auto-scroll interrupted, please adjust the search

View File

@ -11,4 +11,5 @@ Hide: Ocultar
Next: Siguiente
Finalize: Finalizar
Previous: Anterior
Load more: Cargar más
Auto-scroll interrupted, please adjust the search: Auto-scroll interrumpido, por favor ajusta la búsqueda

View File

@ -20,5 +20,5 @@
.icon-ticket:before { content: '\e821'; } /* '' */
.icon-tax:before { content: '\e822'; } /* '' */
.icon-no036:before { content: '\e823'; } /* '' */
.icon-mana:before { content: '\e824'; } /* '' */
.icon-transaction:before { content: '\e826'; } /* '' */
.icon-solunion:before { content: '\e827'; } /* '' */

View File

@ -2,4 +2,4 @@ import './mdl-override.scss';
import './mdi-override.css';
import './zoom-image.scss';
import './fontello-head.css';
import './fontello-icons.css';
import './fontello-codes.css';

View File

@ -109,6 +109,18 @@
"params": {
"item": "$ctrl.item"
}
}, {
"url" : "/diary",
"state": "item.card.diary",
"component": "vn-item-diary",
"params": {
"item": "$ctrl.item"
},
"menu": {
"description": "Diary",
"icon": "icon-transaction"
},
"acl": ["employee"]
}
]
}

View File

@ -25,8 +25,8 @@
<vn-auto pad-medium>
<h5>{{$ctrl.item.id}}</h5>
<vn-label-value label="Name"
value="{{$ctrl.item.name}}">
</vn-label-value>
value="{{$ctrl.item.name}}">
</vn-label-value>
<vn-label-value label="Buyer"
value="{{$ctrl.item.itemType.worker.firstName}} {{$ctrl.item.itemType.worker.name}}">
</vn-label-value>

View File

@ -0,0 +1,49 @@
<vn-vertical>
<vn-card pad-large>
<vn-vertical>
<vn-title>Item diary</vn-title>
<vn-horizontal>
<vn-autocomplete
vn-focus
url="/item/api/Warehouses"
show-field="name"
value-field="id"
initial-data="$ctrl.warehouseFk"
field="$ctrl.warehouseFk"
label="Select warehouse">
</vn-autocomplete>
</vn-horizontal>
<table class="vn-grid">
<thead>
<tr>
<th number translate>Date</th>
<th number translate>State</th>
<th number translate>Origin</th>
<th number translate>Reference</th>
<th style="text-align: center" translate>Name</th>
<th number translate>In</th>
<th number translate>Out</th>
<th number translate>Balance</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="diary in $ctrl.diary">
<td number>{{diary.date | date:'dd/MM/yyyy HH:mm' }}</td>
<td number>{{diary.alertLevel | dashIfEmpty}}</td>
<td number>{{diary.origin | dashIfEmpty}}</td>
<td number>{{diary.reference | dashIfEmpty}}</td>
<td style="text-align: center">{{diary.name | dashIfEmpty}}</td>
<td number>{{diary.in | dashIfEmpty}}</td>
<td number>{{diary.out | dashIfEmpty}}</td>
<td number>{{diary.balance | dashIfEmpty}}</td>
</tr>
<tr ng-if="$ctrl.diary.length === 0" class="list list-element">
<td colspan="8" style="text-align: center" translate>No results</td>
</tr>
</tbody>
</table>
</vn-vertical>
</vn-card>
<vn-paging margin-large-top vn-one index="$ctrl.diary" total="$ctrl.diary.count"></vn-paging>
<!-- <vn-auto-paging margin-large-top vn-one index="index" total="index.model.count" items="$ctrl.instances"></vn-auto-paging> -->
</vn-vertical>

View File

@ -0,0 +1,38 @@
import ngModule from '../module';
import './style.scss';
class Controller {
constructor($scope, $http) {
this.$ = $scope;
this.$http = $http;
this.diary = [];
}
set warehouseFk(value) {
this._getItemDiary(value);
this._warehouseFk = value;
}
get warehouseFk() {
return this._warehouseFk;
}
_getItemDiary(warehouse) {
if (warehouse == null)
return;
let params = {itemFk: this.item.id, warehouseFk: warehouse};
this.$http.get(`/item/api/Items/getDiary?params=${JSON.stringify(params)}`).then(res => {
this.diary = res.data;
});
}
}
Controller.$inject = ['$scope', '$http'];
ngModule.component('vnItemDiary', {
template: require('./index.html'),
controller: Controller,
bindings: {
item: '<'
}
});

View File

@ -0,0 +1,43 @@
import './index.js';
describe('Item', () => {
describe('Component vnItemDiary', () => {
let $componentController;
let $scope;
let controller;
let $httpBackend;
beforeEach(() => {
angular.mock.module('item');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope = $rootScope.$new();
controller = $componentController('vnItemDiary', {$scope: $scope});
controller.item = {id: 3};
}));
describe('set warehouseFk()', () => {
it(`should call _getItemDiary() with 2 and set warehouseFk`, () => {
spyOn(controller, '_getItemDiary');
controller.warehouseFk = 2;
expect(controller._getItemDiary).toHaveBeenCalledWith(2);
expect(controller.warehouseFk).toEqual(2);
});
});
describe('_getItemDiary()', () => {
it(`should make a request to get the diary hwen is called with a number`, () => {
$httpBackend.whenGET('/item/api/Items/getDiary?params={"itemFk":3,"warehouseFk":2}').respond({data: 'item'});
$httpBackend.expectGET('/item/api/Items/getDiary?params={"itemFk":3,"warehouseFk":2}');
controller._getItemDiary(2);
$httpBackend.flush();
});
});
});
});

View File

@ -0,0 +1,8 @@
vn-item-diary {
& vn-horizontal {
justify-content: center;
}
& vn-autocomplete > div{
width: 400px;
}
}

View File

@ -1,77 +0,0 @@
<div pad-large style="min-width: 30em">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield
vn-one
label="Id"
model="$ctrl.filter.id"
vn-focus>
</vn-textfield>
<vn-textfield
vn-one
label="Name"
model="$ctrl.filter.name"
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Description"
model="$ctrl.filter.description">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
url="/item/api/ItemTypes"
label="Type"
show-field="name"
value-field="id"
field="$ctrl.filter.typeFk">
</vn-autocomplete>
<vn-autocomplete
vn-one
url="/item/api/Producers"
label="Producer"
show-field="name"
value-field="id"
field="$ctrl.filter.producerFk">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
url="/item/api/Origins"
label="Origin"
show-field="name"
value-field="id"
field="$ctrl.filter.originFk">
</vn-autocomplete>
<vn-autocomplete
vn-one
url="/item/api/Inks"
label="Ink"
show-field="name"
value-field="id"
field="$ctrl.filter.inkFk">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Category"
model="$ctrl.filter.category">
</vn-textfield>
<vn-textfield
vn-one
label="Size"
type="number"
model="$ctrl.filter.itemSize">
</vn-textfield>
</vn-horizontal>
<vn-horizontal margin-large-top>
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -1,16 +0,0 @@
import ngModule from '../module';
class Controller {
constructor() {
this.onSubmit = () => {};
}
onSearch() {
this.onSubmit(this.filter);
}
}
ngModule.component('vnItemFilterPanel', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,26 +1,30 @@
<mg-ajax path="/item/api/Items/filter" options="vnIndexNonAuto"></mg-ajax>
<vn-crud-model
vn-id="model"
url="/item/api/Items"
filter="::$ctrl.filter"
limit="8"
data="items"
auto-load="false">
</vn-crud-model>
<div margin-medium>
<div class="vn-list">
<vn-card>
<vn-horizontal>
<vn-searchbar
vn-one
index="index"
on-search="$ctrl.search(index)"
advanced="true"
popover="vn-item-filter-panel"
ignore-keys = "['page', 'size', 'search']">
</vn-searchbar>
</vn-horizontal>
<vn-card pad-medium-h>
<vn-searchbar
panel="vn-item-search-panel"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
</vn-searchbar>
</vn-card>
<vn-card margin-medium-top>
<vn-card margin-medium-v>
<vn-item-product
ng-repeat="item in index.model.instances track by item.id"
item="item">
ng-repeat="item in items track by item.id"
item="::item">
</vn-item-product>
</vn-card>
<vn-paging index="index" total="index.model.count"></vn-paging>
<!-- <vn-auto-paging index="index" total="index.model.count" items="$ctrl.items"></vn-auto-paging> -->
<vn-pagination
model="model"
scroll-selector="ui-view">
</vn-pagination>
</div>
</div>
<a ui-sref="item.create" vn-bind="+" fixed-bottom-right>

View File

@ -3,46 +3,68 @@ import './product';
import './style.scss';
class Controller {
constructor($http, $state, $scope) {
this.$http = $http;
this.$state = $state;
this.$scope = $scope;
this.model = {};
this.$ = $scope;
this.itemSelected = null;
this.items = [];
this.filter = {
include: [
{relation: 'itemType',
scope: {
fields: ['name', 'workerFk'],
include: {
relation: 'worker',
fields: ['firstName', 'name']
}
}
}
],
order: 'name ASC'
};
}
search(index) {
index.accept();
/* this.items = [];
index.accept().then(res => {
this.items = res.instances;
}); */
exprBuilder(param, value) {
switch (param) {
case 'search':
return {
or: [
{id: value},
{name: {regexp: value}}
]
};
case 'name':
case 'description':
return {[param]: {regexp: value}};
case 'id':
case 'typeFk':
return {[param]: value};
}
}
cloneItem(item) {
this.itemSelected = item;
this.$scope.clone.show();
this.$.clone.show();
}
onCloneAccept(response) {
if (response == 'ACCEPT' && this.itemSelected) {
this.$http.post(`/item/api/Items/${this.itemSelected.id}/clone`).then(res => {
if (res && res.data && res.data.id) {
this.$state.go('item.card.tags', {id: res.data.id});
}
});
}
if (!(response == 'ACCEPT' && this.itemSelected))
return;
this.$http.post(`/item/api/Items/${this.itemSelected.id}/clone`).then(res => {
if (res && res.data && res.data.id)
this.$state.go('item.card.tags', {id: res.data.id});
});
this.itemSelected = null;
}
showItemPreview(item) {
this.itemSelected = item;
this.$scope.preview.show();
this.$.preview.show();
}
}
Controller.$inject = ['$http', '$state', '$scope'];
ngModule.component('vnItemIndex', {

View File

@ -1,3 +1,4 @@
@import "./colors";
vn-item-product {
display: block;
@ -12,4 +13,4 @@ vn-item-product {
border-radius: .2em;
}
}
}
}

View File

@ -2,7 +2,8 @@ export * from './module';
import './index';
import './filter-item-list';
import './filter-panel';
import './search-panel';
import './diary';
import './create';
import './card';
import './descriptor';

View File

@ -29,10 +29,16 @@ Value: Valor
Priority: Prioridad
Item tax: Tasas del artículo
Country: País
Select warehouse: Selecione almacén
Class: Clase
Item niches: Nichos del artículo
Item diary: Registro de compra-venta
Diary: Registro
Warehouse: Almacén
Code: Código
State: Estado
In: Entrada
Out: Salida
Botanical: Botánico
Species: Especie
Add tag: Añadir etiqueta
@ -42,4 +48,5 @@ Remove niche: Quitar nicho
Add barcode: Añadir código de barras
Remove barcode: Quitar código de barras
Buyer: Comprador
No results: Sin resultados
No results: Sin resultados
Tag: Etiqueta

View File

@ -0,0 +1,84 @@
<mg-ajax path="/item/api/Tags" options="mgIndex as tags"></mg-ajax>
<div style="min-width: 30em; max-height: 540px; overflow: auto;">
<form pad-large ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield
vn-one
label="Id"
model="filter.id"
vn-focus>
</vn-textfield>
<vn-textfield
vn-one
label="Name"
model="filter.name">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
vn-focus
url="/item/api/ItemTypes"
label="Type"
show-field="name"
value-field="id"
field="filter.typeFk">
</vn-autocomplete>
<vn-textfield
vn-one
label="Description"
model="filter.description">
</vn-textfield>
</vn-horizontal>
<!--
<vn-horizontal ng-repeat="itemTag in filter.tags">
<vn-autocomplete
vn-id="tag"
vn-one
field="itemTag.tagFk"
data="tags.model"
show-field="name"
label="Tag"
on-change="itemTag.value = null">
</vn-autocomplete>
<vn-textfield
vn-two
ng-show="tag.selection.isFree !== false"
vn-id="text"
label="Value"
model="itemTag.value">
</vn-textfield>
<vn-autocomplete
vn-two
ng-show="tag.selection.isFree === false"
url="{{$ctrl.getSourceTable(tag.selection)}}"
label="Value"
field="itemTag.value"
show-field="name"
value-field="name">
</vn-autocomplete>
<vn-none>
<vn-icon-button
medium-grey
margin-medium-v
vn-tooltip="Remove tag"
icon="remove_circle_outline"
ng-click="filter.tags.splice($index, 1)"
tabindex="-1">
</vn-icon-button>
</vn-none>
</vn-horizontal>
<vn-horizontal>
<vn-icon-button
vn-bind="+"
vn-tooltip="Add tag"
icon="add_circle"
ng-click="filter.tags.push({})">
</vn-icon-button>
</vn-horizontal>
-->
<vn-horizontal margin-large-top>
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -0,0 +1,32 @@
import ngModule from '../module';
import SearchPanel from 'core/src/components/searchbar/search-panel';
class Controller extends SearchPanel {
set filter(value) {
if (!value.tags)
value.tags = [{}];
this.$.filter = value;
}
get filter() {
return this.$.filter;
}
getSourceTable(selection) {
if (!selection || selection.isFree === true)
return null;
if (selection.sourceTable)
return '/api/'
+ selection.sourceTable.charAt(0).toUpperCase()
+ selection.sourceTable.substring(1) + 's';
else if (selection.sourceTable == null)
return `/api/ItemTags/filterItemTags/${selection.id}`;
}
}
ngModule.component('vnItemSearchPanel', {
template: require('./index.html'),
controller: Controller
});

View File

@ -48,14 +48,16 @@
rule="itemTag.priority"
vn-acl="buyer">
</vn-textfield>
<vn-icon
pad-medium-top
pointer
<vn-none>
<vn-icon-button
medium-grey
margin-medium-v
vn-tooltip="Remove tag"
icon="remove_circle_outline"
ng-click="$ctrl.removeTag($index)">
</vn-icon>
ng-click="$ctrl.removeTag($index)"
tabindex="-1">
</vn-icon-button>
</vn-none>
</vn-horizontal>
<vn-one>
<vn-icon-button

View File

@ -16,14 +16,14 @@ class Controller {
getTags() {
let filter = {
where: {itemFk: this.params.id},
order: "priority ASC",
include: {relation: "tag"}
order: 'priority ASC',
include: {relation: 'tag'}
};
this.$http.get(`/item/api/ItemTags?filter=${JSON.stringify(filter)}`).then(response => {
this.removedTags = [];
this.itemTags = JSON.parse(JSON.stringify(this.tags));
this.tags = response.data;
this.itemTags = JSON.parse(JSON.stringify(this.tags));
this.orgTags = {};
this.tags.forEach(tag => {
this.orgTags[tag.id] = Object.assign({}, tag);
@ -63,7 +63,7 @@ class Controller {
return null;
if (selection.sourceTable) {
return "/api/" + selection.sourceTable.charAt(0).toUpperCase() +
return '/api/' + selection.sourceTable.charAt(0).toUpperCase() +
selection.sourceTable.substring(1) + 's';
} else if (selection.sourceTable == null) {
return `/api/ItemTags/filterItemTags/${selection.id}`;
@ -107,7 +107,6 @@ ngModule.component('vnItemTags', {
template: require('./index.html'),
controller: Controller,
bindings: {
itemTags: '=',
selection: '<?'
itemTags: '='
}
});

View File

@ -21,7 +21,7 @@
show-field="name"
value-field="id"
field="$ctrl.filter.warehouseFk"
url="/production/api/Warehouses/production"
url="/production/api/Warehouses"
on-change = "$ctrl.onChangeWareHouse(item)"
label="Store">
</vn-autocomplete>

View File

@ -21,7 +21,7 @@
show-field="name"
value-field="id"
field="$ctrl.filter.warehouseFk"
url="/production/api/Warehouses/production"
url="/production/api/Warehouses"
on-change = "$ctrl.onChangeWareHouse(item)"
label="Store">
</vn-autocomplete>

View File

@ -4,4 +4,3 @@ import './main-menu/main-menu';
import './left-menu/left-menu';
import './left-menu/menu-item';
import './topbar/topbar';
import './searchbar/searchbar';

View File

@ -1,160 +0,0 @@
import ngModule from '../../module';
import './style.scss';
export default class Controller {
constructor($element, $scope, $compile, $timeout, $state, $transitions) {
this.element = $element[0];
this.$ = $scope;
this.$compile = $compile;
this.$timeout = $timeout;
this.stringSearch = '';
this.$state = $state;
this.deregisterCallback = $transitions.onStart({},
transition => this.changeState(transition));
}
clearFilter() {
this.index.filter = {
page: 1,
size: 20
};
}
setFilter(filterObject) {
this.clearFilter();
Object.assign(this.index.filter, filterObject);
}
getFiltersFromString(stringSearch) {
let result = {};
if (stringSearch) {
// find pattern key:value or key:(extra value) and returns array
let findPattern = stringSearch.match(/((([\w_]+):([\w_]+))|([\w_]+):\(([\w_ ]+)\))/gi);
let remnantString = (stringSearch.replace(/((([\w_]+):([\w_]+))|([\w_]+):\(([\w_ ]+)\))/gi, '')).trim();
if (findPattern) {
for (let i = 0; i < findPattern.length; i++) {
let aux = findPattern[i].split(':');
let property = aux[0];
let value = aux[1].replace(/\(|\)/g, '');
result[property] = value.trim();
}
}
if (remnantString) {
result.search = remnantString;
}
}
return result;
}
createStringFromObject(filterObject) {
let search = [];
let keys = Object.keys(filterObject);
if (keys.length) {
keys.forEach(k => {
let ignore = (this.ignoreKeys && this.ignoreKeys instanceof Array && this.ignoreKeys.indexOf(k) !== -1);
if (!ignore) {
let value = filterObject[k];
if (typeof value === 'string' && value.indexOf(' ') !== -1) {
search.push(`${k}:(${value})`);
} else if (typeof value !== 'object') {
search.push(`${k}:${value}`);
}
}
});
if (filterObject.search)
search.unshift(filterObject.search);
}
return (search.length) ? search.join(' ') : '';
}
changeState(transition) {
return !(transition._targetState._identifier.name === this.$state.current.name);
}
pushFiltersToState(filters) {
let history = window.history || {pushState: () => {
console.error('Error in history.pushState(): Browser incompatibility error');
}};
let aux = window.location.hash.split('?q=');
if (Object.keys(filters).length)
history.pushState({}, null, `${aux[0]}?q=${encodeURIComponent(JSON.stringify(filters))}`);
else
history.pushState({}, null, aux[0]);
}
onpenFilters(event) {
let filter = {};
if (this.stringSearch) {
filter = this.getFiltersFromString(this.stringSearch);
}
this.$child = this.$compile(`<${this.popover}/>`)(this.$.$new());
var childCtrl = this.$child.isolateScope().$ctrl;
childCtrl.filter = Object.assign({}, filter);
childCtrl.onSubmit = filter => this.onChildSubmit(filter);
if (this.data)
childCtrl.data = Object.assign({}, this.data);
event.preventDefault();
this.$.popover.parent = this.element;
this.$.popover.child = this.$child[0];
this.$.popover.show();
}
onPopoverClose() {
this.$child.scope().$destroy();
this.$child.remove();
this.$child = null;
}
onChildSubmit(filter) {
this.$.popover.hide();
this.stringSearch = this.createStringFromObject(filter);
this.clearFilter();
this.$timeout(() => this.onSubmit());
}
onSubmit() {
let filter = {};
if (this.stringSearch) {
filter = this.getFiltersFromString(this.stringSearch);
}
this.setFilter(filter);
if (this.onSearch)
this.onSearch();
this.pushFiltersToState(filter);
}
$onDestroy() {
this.clearFilter();
this.deregisterCallback();
}
$onInit() {
if (this.$state.params.q) {
let filter = JSON.parse(decodeURIComponent(this.$state.params.q));
this.stringSearch = this.createStringFromObject(filter);
}
this.$timeout(() => this.onSubmit());
}
}
Controller.$inject = ['$element', '$scope', '$compile', '$timeout', '$state', '$transitions'];
ngModule.component('vnSearchbar', {
template: require('./searchbar.html'),
bindings: {
index: '<',
onSearch: '&',
advanced: '=',
popover: '@',
ignoreKeys: '<?',
data: '<?'
},
controller: Controller
});

View File

@ -10,6 +10,7 @@ $main-01-03: rgba($main-01, 0.3);
$main-02: #a3d131;
$main-02-05: rgba($main-02, 0.5);
$main-02-03: rgba($main-02, 0.3);
$lines: #9b9b9b;
$color-green: #a3d131;
$color-orange: #f7931e;

View File

@ -64,7 +64,6 @@ html [vn-auto], vn-auto, .vn-auto {
flex-basis: auto;
}
html [vn-none], vn-none, .vn-none {
flex: 1;
flex: none;
}
html [vn-one], vn-one, .vn-one {

View File

@ -64,7 +64,7 @@ html [vn-center], .vn-center{
.list-element{
padding: 8px 0 0 0;
border-bottom: 1px solid $main-header;
border-bottom: 1px solid $lines;
i {
color: $main-01;
}
@ -81,7 +81,7 @@ html [vn-center], .vn-center{
}
.list-footer{
font-family: vn-font-bold;
border-top: 3px solid $main-header;
border-top: 3px solid $lines;
}
.list-element.warning{
background-color: $color-medium-orange;

View File

@ -1,70 +1,69 @@
<mg-ajax path="/ticket/api/Tickets/filter" options="vnIndexNonAuto"></mg-ajax>
<div margin-large>
<div>
<vn-card pad-medium>
<vn-title>TICKETS</vn-title>
<vn-horizontal class="vn-list">
<vn-searchbar vn-one
index="index"
on-search="$ctrl.search(index)"
ignore-keys = "['page', 'size', 'search']">
</vn-searchbar>
</vn-horizontal>
<vn-horizontal>
<table class="vn-grid">
<thead>
<tr>
<th></th>
<th number translate>ID Ticket</th>
<th translate>Comercial</th>
<th translate>Date</th>
<th translate>Hora</th>
<th translate>Alias</th>
<th translate>Provincia</th>
<th translate>Estado</th>
<th translate>Agencia</th>
<th translate>Almacen</th>
<th number translate>Factura</th>
<th number translate>Ruta</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="ticket in index.model.instances track by ticket.id"
class="{{::$ctrl.compareDate(ticket.shipped)}} clickable"
ui-sref="ticket.card.summary({id: {{::ticket.id}}})">
<td>
<!-- <i pointer
class="material-icons"
vn-tooltip="delete expedition"
ng-click="$ctrl.deleteExpedition(expedition)">warning</i> -->
</td>
<th number>{{::ticket.id}}</th>
<td translate>{{::ticket.client.salesPerson.name | dashIfEmpty}}</td>
<td translate>{{::ticket.shipped | date:'dd/MM/yyyy'}}</td>
<td translate>{{::ticket.shipped | date:'HH:MM'}}</td>
<td translate>{{::ticket.nickname}}</td>
<td translate>{{::ticket.address.province.name}}</td>
<td translate>{{::ticket.tracking.state.name}}</td>
<td translate>{{::ticket.agencyMode.name}}</td>
<td translate>{{::ticket.warehouse.name}}</td>
<td number translate>{{::ticket.refFk | dashIfEmpty}}</td>
<td number translate>{{::ticket.routeFk | dashIfEmpty}}</td>
<td>
<vn-icon-button
ng-click="$ctrl.preview($event, ticket)"
vn-tooltip="Preview"
icon="desktop_windows">
</vn-icon-button>
</td>
</tr>
</tbody>
</table>
</vn-horizontal>
<vn-crud-model
vn-id="model"
url="/ticket/api/Tickets"
filter="::$ctrl.filter"
limit="20"
data="tickets"
auto-load="false">
</vn-crud-model>
<div margin-medium>
<div class="vn-list">
<vn-card pad-medium-h>
<vn-searchbar
panel="vn-ticket-search-panel"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
</vn-searchbar>
</vn-card>
<vn-paging vn-one index="index" total="index.model.count"></vn-paging>
<!-- <vn-auto-paging vn-one index="index" total="index.model.count" items="$ctrl.tickets"></vn-auto-paging> -->
</div>
<vn-card margin-medium-v pad-medium>
<table class="vn-grid">
<thead>
<tr>
<th translate number>Id</th>
<th translate>Salesperson</th>
<th translate>Date</th>
<th translate>Hour</th>
<th translate>Alias</th>
<th translate>Province</th>
<th translate>State</th>
<th translate>Agency</th>
<th translate>Warehouse</th>
<th translate number>Invoice</th>
<th translate number>Route</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="ticket in tickets"
class="{{::$ctrl.compareDate(ticket.shipped)}} clickable"
ui-sref="ticket.card.summary({id: {{::ticket.id}}})">
<th number>{{::ticket.id}}</th>
<td >{{::ticket.client.salesPerson.name | dashIfEmpty}}</td>
<td >{{::ticket.shipped | date:'dd/MM/yyyy'}}</td>
<td >{{::ticket.shipped | date:'HH:MM'}}</td>
<td >{{::ticket.nickname}}</td>
<td >{{::ticket.address.province.name}}</td>
<td >{{::ticket.tracking.state.name}}</td>
<td >{{::ticket.agencyMode.name}}</td>
<td >{{::ticket.warehouse.name}}</td>
<td number >{{::ticket.refFk | dashIfEmpty}}</td>
<td number >{{::ticket.routeFk | dashIfEmpty}}</td>
<td>
<vn-icon-button
ng-click="$ctrl.preview($event, ticket)"
vn-tooltip="Preview"
icon="desktop_windows">
</vn-icon-button>
</td>
</tr>
</tbody>
</table>
</vn-card>
<vn-pagination
model="model"
scroll-selector="ui-view">
</vn-pagination>
</div>
<a ui-sref="ticket.create" vn-bind="+" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>

View File

@ -1,11 +1,82 @@
import ngModule from '../module';
import './ticket-item';
import './style.scss';
export default class Controller {
constructor($scope) {
this.$scope = $scope;
this.$ = $scope;
this.ticketSelected = null;
this.filter = {
include: [
{
relation: 'address',
scope: {
fields: ['provinceFk'],
include: {
relation: 'province',
scope: {
fields: ['name']
}
}
}
}, {
relation: 'warehouse',
scope: {
fields: ['name']
}
}, {
relation: 'agencyMode',
scope: {
fields: ['name']
}
}, {
relation: 'tracking',
scope: {
fields: ['stateFk'],
include: {
relation: 'state',
scope: {
fields: ['name']
}
}
}
}, {
relation: 'client',
scope: {
fields: ['salesPersonFk'],
include: {
relation: 'salesPerson',
scope: {
fields: ['name']
}
}
}
}
],
order: 'shipped DESC'
};
}
exprBuilder(param, value) {
switch (param) {
case 'search':
return {
or: [
{id: value},
{nickname: {regexp: value}}
]
};
case 'from':
return {shipped: {gte: value}};
case 'to':
return {shipped: {lte: value}};
case 'nickname':
return {[param]: {regexp: value}};
case 'id':
case 'clientFk':
case 'agencyModeFk':
case 'warehouseFk':
return {[param]: value};
}
}
compareDate(date) {
@ -26,17 +97,9 @@ export default class Controller {
preview(event, ticket) {
event.preventDefault();
event.stopImmediatePropagation();
this.$scope.dialogSummaryTicket.show();
this.$.dialogSummaryTicket.show();
this.ticketSelected = ticket;
}
search(index) {
index.accept();
/* this.tickets = [];
index.accept().then(res => {
this.tickets = res.instances;
}); */
}
}
Controller.$inject = ['$scope'];

View File

@ -34,14 +34,14 @@ describe('ticket', () => {
it('should call preventDefault and stopImmediatePropagation from event and show', () => {
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopImmediatePropagation']);
controller.$scope = {dialogSummaryTicket: {show: () => {}}};
spyOn(controller.$scope.dialogSummaryTicket, 'show');
controller.$ = {dialogSummaryTicket: {show: () => {}}};
spyOn(controller.$.dialogSummaryTicket, 'show');
let ticket = {};
controller.preview(event, ticket);
expect(event.preventDefault).toHaveBeenCalledWith();
expect(event.stopImmediatePropagation).toHaveBeenCalledWith();
expect(controller.$scope.dialogSummaryTicket.show).toHaveBeenCalledWith();
expect(controller.$.dialogSummaryTicket.show).toHaveBeenCalledWith();
});
});
});

View File

@ -1,3 +0,0 @@
vn-ticket-item {
display: block;
}

View File

@ -1,20 +0,0 @@
<a
ui-sref="ticket.card.summary({ id: {{::$ctrl.ticket.id}} })"
translate-attr="{title: 'View ticket'}"
class="vn-list-item">
<vn-horizontal ng-click="$ctrl.onClick($event)">
<vn-one>
<h6>{{::$ctrl.ticket.nickname}}</h6>
<vn-label-value label="Id"
value="{{::$ctrl.ticket.id}}">
</vn-label-value>
</vn-one>
<vn-horizontal class="buttons">
<vn-icon
ng-click="$ctrl.preview($event)"
vn-tooltip="Preview"
icon="desktop_windows">
</vn-icon>
</vn-horizontal>
</vn-horizontal>
</a>

View File

@ -1,24 +0,0 @@
import ngModule from '../module';
class Controller {
onClick(event) {
if (event.defaultPrevented)
event.stopImmediatePropagation();
}
preview(event) {
event.preventDefault();
this.index.openSummary(this.ticket);
}
}
ngModule.component('vnTicketItem', {
controller: Controller,
template: require('./ticket-item.html'),
bindings: {
ticket: '<'
},
require: {
index: '^vnTicketIndex'
}
});

View File

@ -5,31 +5,50 @@
<vn-title>Sale</vn-title>
<vn-tool-bar margin-medium-bottom>
<vn-button
disabled="$ctrl.ticket.tracking.state.alertLevel != 0"
disabled="!$ctrl.isEditable"
label="Ok"
ng-click="$ctrl.onStateOkClick()">
</vn-button>
<vn-icon-menu
disabled="$ctrl.ticket.tracking.state.alertLevel != 0"
disabled="!$ctrl.isEditable"
label="State"
url="/ticket/api/States/alertLevelIs0"
on-change="$ctrl.onStateChange(value)">
</vn-icon-menu>
<vn-icon-menu
label="More"
show-filter="false"
value-field="callback"
data="::$ctrl.moreOptions"
translate-fields="['name']"
on-change="$ctrl.onMoreChange(value)">
</vn-icon-menu>
<vn-button
disabled="!$ctrl.isChecked"
disabled="!$ctrl.isChecked || !$ctrl.isEditable"
ng-click="$ctrl.onRemoveLinesClick()"
vn-tooltip="Remove lines"
vn-tooltip="Remove lines"
tooltip-position="up"
icon="delete">
</vn-button>
<vn-button
disabled="!$ctrl.isChecked || !$ctrl.isEditable"
ng-click="$ctrl.showTransferPopover($event);"
vn-tooltip="Transfer lines"
tooltip-position="right"
icon="call_split">
</vn-button>
</vn-tool-bar>
<table class="vn-grid">
<thead>
<tr>
<th number>
<vn-multi-check data="index.model.instances"></vn-multi-check>
<vn-multi-check
data="index.model.instances"
disabled="!$ctrl.isEditable">
</vn-multi-check>
</th>
<th number translate>Item</th>
<th translate>Description</th>
<th translate style="text-align:center">Description</th>
<th number translate>Quantity</th>
<th number translate>Price</th>
<th number translate>Discount</th>
@ -39,7 +58,10 @@
<tbody>
<tr ng-repeat="sale in index.model.instances track by sale.id">
<td number>
<vn-check field="sale.checked"></vn-check>
<vn-check
field="sale.checked"
disabled="!$ctrl.isEditable">
</vn-check>
</td>
<td
pointer
@ -48,10 +70,27 @@
{{::sale.itemFk}}
</td>
<td><vn-fetched-tags sale="sale"/></td>
<td number>{{::sale.quantity}}</td>
<td number>{{::sale.price | currency:'€':2}}</td>
<td number>{{::sale.discount}} %</td>
<td number>{{::sale.quantity * sale.price | currency:'€':2}}</td>
<td number>{{sale.quantity}}</td>
<!--<td ng-if="$ctrl.ticket.tracking.state.alertLevel == 0">
<vn-textfield
model="sale.quantity"
type="number"
ng-blur="updateLine()">
</vn-textfield>
</td>-->
<td number>{{sale.price | currency:'€':2}}</td>
<td number>{{sale.discount}} %</td>
<td number>{{sale.quantity * sale.price | currency:'€':2}}</td>
<!--<td number>
<vn-icon-button
ng-if="$ctrl.ticket.tracking.state.alertLevel == 0"
pointer
vn-tooltip="Add note"
tooltip-position="left"
icon="mode_edit"
ng-click="$ctrl.showEditPopover($event, sale)">
</vn-icon-button>
</td>-->
</tr>
<tr ng-if="index.model.count === 0" class="list list-element">
<td colspan="6" style="text-align: center" translate>No results</td>
@ -61,6 +100,135 @@
</vn-vertical>
</vn-card>
<vn-paging vn-one margin-large-top index="index" total="index.model.count"></vn-paging>
<!-- <vn-auto-paging vn-one margin-large-top index="index" total="index.model.count" items="index.model.instances"></vn-auto-paging> -->
<vn-item-descriptor-popover vn-id="descriptor"></vn-item-descriptor-popover>
<vn-item-descriptor-popover vn-id="descriptor">
</vn-item-descriptor-popover>
<!-- Create Ticket Dialog -->
<!-- <vn-ticket-create-dialog
vn-id="newTicket"
callback="$ctrl.moveLines(res)"
ticket="$ctrl.ticket">
</vn-ticket-create-dialog>
-->
<!-- Add Turn Dialog -->
<vn-dialog class="dialog-summary"
vn-id="addTurn">
<tpl-body>
<div>
<h5 style="text-align: center">
<span translate>In which day you want to add the ticket?</span>
</h5>
<vn-tool-bar margin-medium-top>
<vn-button
label="Monday"
ng-click="$ctrl.addTurn(0)">
</vn-button>
<vn-button
label="Tuesday"
ng-click="$ctrl.addTurn(1)">
</vn-button>
<vn-button
label="Wednesday"
ng-click="$ctrl.addTurn(2)">
</vn-button>
<vn-button
label="Thursday"
ng-click="$ctrl.addTurn(3)">
</vn-button>
<vn-button
label="Friday"
ng-click="$ctrl.addTurn(4)">
</vn-button>
<vn-button
label="Saturday"
ng-click="$ctrl.addTurn(5)">
</vn-button>
<vn-button
label="Sunday"
ng-click="$ctrl.addTurn(6)">
</vn-button>
</vn-tool-bar>
</div>
</tpl-body>
</vn-dialog>
<!-- Edit Popover -->
<vn-popover class="edit" vn-id="edit">
<vn-horizontal pad-medium class="header">
<h5>MANÁ: {{$ctrl.workerMana}}</h5>
</vn-horizontal>
<div pad-medium>
<h5>{{$ctrl.client.name}}</h5>
<vn-textfield
label="Quantity"
model="$ctrl.edit.quantity"
type="number">
</vn-textfield>
<vn-textfield
label="Price"
model="$ctrl.edit.price"
type="number">
</vn-textfield>
<vn-textfield
label="Discount"
model="$ctrl.edit.discount"
type="number">
</vn-textfield>
<vn-button
label="Save"
ng-click="$ctrl.updateLine()">
</vn-button>
</div>
</vn-popover>
<!-- Transfer Popover -->
<vn-popover class="transfer" vn-id="transfer">
<div pad-medium>
<table class="vn-grid">
<thead>
<tr>
<th number translate>ID</th>
<th number translate>F. envio</th>
<th number translate>Agencia</th>
<th number translate>Almacen</th>
</tr>
</thead>
<tbody>
<tr ng-if="$ctrl.lastThreeTickets.length === 0" ><td colspan="4" style="text-align: center" translate>No results</td></tr>
<tr
class="clickable"
ng-repeat="ticket in $ctrl.lastThreeTickets track by ticket.id"
ng-click="$ctrl.moveLines(ticket.id)">
<td number>{{::ticket.id}}</td>
<td number>{{::ticket.shipped | date: 'dd/MM/yyyy HH:mm'}}</td>
<td number>{{::ticket.agencyName}}</td>
<td number>{{::ticket.warehouseName}}</td>
</tr>
</tbody>
</table>
<vn-horizontal>
<vn-textfield
label="Move to ticket"
model="$ctrl.moveToTicketFk"
type="number">
</vn-textfield>
<vn-icon-button
pointer
icon="arrow_forward_ios"
ng-click="$ctrl.moveLines($ctrl.moveToTicketFk)">
</vn-icon-button>
</vn-horizontal>
<!-- <vn-button
pointer
label="New ticket"
ng-click="$ctrl.showticketCreate()">
</vn-button> -->
</div>
</vn-popover>
</vn-vertical>
<vn-confirm
vn-id="deleteConfirmation"
on-response="$ctrl.returnDeleteTicketDialog(response)"
question="You are going to delete this ticket"
message="Continue anyway?">
</vn-confirm>

View File

@ -1,13 +1,29 @@
import ngModule from '../module';
import FilterTicketList from '../filter-ticket-list';
import './style.scss';
class Controller extends FilterTicketList {
constructor($scope, $timeout, $stateParams, $http) {
constructor($scope, $timeout, $stateParams, $http, $state, vnApp) {
super($scope, $timeout, $stateParams);
this.$ = $scope;
this.vnApp = vnApp;
this.$timeout = $timeout;
this.onOrder('itemFk', 'ASC');
this.$state = $stateParams;
this.$http = $http;
this.deletable = false;
this.moreOptions = [
{callback: this.showAddTurnDialog, name: "Add turn"},
{callback: this.showDeleteTicketDialog, name: "Delete ticket"}
];
}
get isEditable() {
try {
return !this.ticket.tracking.state.alertLevel;
} catch (e) {}
return true;
}
get isChecked() {
@ -20,6 +36,22 @@ class Controller extends FilterTicketList {
return false;
}
getCheckedLines() {
let lines = [];
let data = this.$.index.model.instances;
if (data)
for (let i = 0; i < data.length; i++)
if (data[i].checked)
lines.push({id: data[i].id, instance: i});
return lines;
}
onMoreChange(callback) {
callback.call(this);
}
// Change State
onStateOkClick() {
let filter = {where: {code: "OK"}, fields: ["id"]};
let json = encodeURIComponent(JSON.stringify(filter));
@ -30,41 +62,153 @@ class Controller extends FilterTicketList {
onStateChange(value) {
let params = {ticketFk: this.$state.params.id, stateFk: value};
this.$http.post(`/ticket/api/TicketTrackings`, params).then(() => {
this.$http.post(`/ticket/api/TicketTrackings/changeState`, params).then(() => {
this.card.reload();
this.vnApp.showMessage(this.translate.instant('Data saved'));
});
}
onRemoveLinesClick() {
let lines = {
delete: []
};
let data = this.$.index.model.instances;
if (data)
for (let i = 0; i < data.length;) {
if (data[i].checked) {
lines.delete.push(data[i].id);
data.splice(i, 1);
} else {
i++;
}
}
let query = `/ticket/api/Sales/crudSale`;
this.$http.post(query, lines);
// Add Turn
showAddTurnDialog() {
this.$.addTurn.show();
}
addTurn(day) {
let params = {ticketFk: this.$state.params.id, weekDay: day};
this.$http.patch(`/ticket/api/TicketWeeklies`, params).then(() => {
this.$.addTurn.hide();
});
}
// Delete Ticket
showDeleteTicketDialog() {
this.$.deleteConfirmation.show();
}
returnDeleteTicketDialog(response) {
if (response === 'ACCEPT')
this.deleteTicket();
}
deleteTicket() {
let params = {id: this.$state.params.id};
this.$http.post(`/ticket/api/Tickets/deleted`, params).then(() => {
this.$state.go('ticket.list');
});
}
// Remove Lines
onRemoveLinesClick() {
let sales = this.getCheckedLines();
let params = {sales: sales, actualTicketFk: this.ticket.id};
let query = `/ticket/api/Sales/removes`;
this.$http.post(query, params).then(() => {
this.removeInstances(sales);
});
}
// Move Lines
showTransferPopover(event) {
let filter = {clientFk: this.ticket.clientFk, ticketFk: this.ticket.id};
let json = encodeURIComponent(JSON.stringify(filter));
this.$http.get(`/ticket/api/Tickets/threeLastActive?filter=${json}`).then(res => {
this.lastThreeTickets = res.data;
});
this.$.transfer.parent = event.target;
this.$.transfer.show();
}
moveLines(ticketID) {
let sales = this.getCheckedLines();
let params = {sales: sales, newTicketFk: ticketID, actualTicketFk: this.ticket.id};
this.$http.post(`/ticket/api/Sales/moveToTicket`, params).then(() => {
this.goToTicket(ticketID);
});
}
/* newTicket() {
let params = [this.ticket.clientFk, this.ticket.warehouseFk, this.ticket.companyFk, this.ticket.addressFk, this.ticket.agencyModeFk, null];
this.$http.post(`/ticket/api/Tickets/create`, params).then(res => {
console.log(res);
});
}*/
goToTicket(ticketID) {
this.$state.go("ticket.card.sale", {id: ticketID});
}
removeInstances(instances) {
for (let i = instances.length - 1; i >= 0; i--) {
this.$.index.model.instances.splice(instances[i].instance, 1);
}
}
// Item Descriptor
showDescriptor(event, itemFk) {
this.$.descriptor.itemFk = itemFk;
this.$.descriptor.parent = event.target;
this.$.descriptor.show();
}
onDescriptorLoad() {
this.$.popover.relocate();
}
// Ticket Create
showticketCreate() {
console.log(this);
this.$.newTicket.show();
}
onResponse(response) {
if (response === 'ACCEPT') {
let newTicketID = this.$.newTicket.dialog.createTicket();
console.log(newTicketID);
}
}
// Edit Line
_getworkerMana() {
this.$http.get(`/api/WorkerManas/getCurrentWorkerMana`).then(res => {
this.workerMana = res.data[0].mana;
});
}
showEditPopover(event, sale) {
this.sale = sale;
this.edit = {
id: sale.id,
quantity: sale.quantity,
price: sale.price,
discount: sale.discount
};
this.$.edit.parent = event.target;
this._getworkerMana();
this.$.edit.show();
}
updateLine() {
if (this.edit.quantity != this.sale.quantity) {
this.$http.post(`/ticket/api/Sales/updateQuantity`, {id: this.edit.id, quantity: this.edit.quantity}).then(() => {
this.sale.quantity = this.edit.quantity;
});
}
if (this.edit.price != this.sale.price) {
this.$http.post(`/ticket/api/Sales/updatePrice`, {id: this.edit.id, price: this.edit.price}).then(() => {
this.sale.price = this.edit.price;
});
}
if (this.edit.discount != this.sale.discount) {
this.$http.post(`/ticket/api/Sales/updateDiscount`, {id: this.edit.id, discount: this.edit.discount}).then(() => {
this.sale.discount = this.edit.discount;
});
}
this.$.edit.hide();
}
}
Controller.$inject = ['$scope', '$timeout', '$state', '$http'];
Controller.$inject = ['$scope', '$timeout', '$state', '$http', 'vnApp'];
ngModule.component('vnTicketSale', {
template: require('./index.html'),

View File

@ -1,6 +1,6 @@
import './index.js';
describe('Ticket', () => {
xdescribe('Ticket', () => {
describe('Component vnTicketSale', () => {
let $componentController;
let controller;
@ -57,7 +57,7 @@ describe('Ticket', () => {
describe('onStateChange()', () => {
it('should perform a post and then call a function', () => {
$httpBackend.expectPOST(`/ticket/api/TicketTrackings`).respond();
$httpBackend.expectPOST(`/ticket/api/TicketTrackings/changeState`).respond();
controller.card = {reload: () => {}};
controller.onStateChange(3);
$httpBackend.flush();

View File

@ -0,0 +1,44 @@
@import "colors";
vn-popover.edit {
& div.popover{
width: 200px;
}
& vn-horizontal.header{
background-color: $main-01;
text-align: center;
& h5{
color: white;
}
}
}
vn-ticket-sale{
& tr .mdl-textfield{
width: inherit;
max-width: 100%;
}
}
vn-popover.transfer{
& table {
min-width: 650px;
margin-bottom: 10px;
}
& i {
padding-top: 0.2em;
font-size: 1.8em;
}
}
vn-dialog.ticket-create{
& vn-button[label=Cancel]{
display: none;
}
& vn-card.vn-ticket-create{
padding: 0!important;
}
}

View File

@ -0,0 +1,58 @@
<div pad-large style="min-width: 30em">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield
vn-one
label="Nickname"
model="filter.nickname"
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Ticket id"
model="filter.id">
</vn-textfield>
<vn-textfield
vn-one
label="Client id"
model="filter.clientFk">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-date-picker
vn-one
label="From"
model="filter.from">
</vn-date-picker>
<vn-date-picker
vn-one
label="To"
model="filter.to">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
label="Agency"
field="filter.agencyModeFk"
url="/api/AgencyModes"
show-field="name"
value-field="id">
<tpl-item>{{name}}</tpl-item>
</vn-autocomplete>
<vn-autocomplete
vn-one
label="Warehouse"
field="filter.warehouseFk"
url="/api/Warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal margin-large-top>
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -0,0 +1,7 @@
import ngModule from '../module';
import SearchPanel from 'core/src/components/searchbar/search-panel';
ngModule.component('vnTicketSearchPanel', {
template: require('./index.html'),
controller: SearchPanel
});

View File

@ -0,0 +1,7 @@
Ticket id: Id ticket
Client id: Id cliente
Nickname: Alias
From: Desde
To: Hasta
Agency: Agencia
Warehouse: Almacén

View File

@ -1,5 +1,6 @@
export * from './module';
import './search-panel';
import './index';
import './create';
import './card';

View File

@ -192,7 +192,7 @@ export default {
itemTags: {
goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]',
tagsButton: `vn-menu-item a[ui-sref="item.card.tags"]`,
firstRemoveTagButton: `vn-item-tags vn-horizontal:nth-child(2) > vn-icon[icon="remove_circle_outline"]`,
firstRemoveTagButton: `vn-item-tags vn-horizontal:nth-child(2) vn-icon-button[icon="remove_circle_outline"]`,
firstTagSelect: `vn-item-tags vn-horizontal:nth-child(2) > vn-autocomplete[field="itemTag.tagFk"] input`,
firstTagDisabled: `vn-item-tags vn-horizontal:nth-child(2) > vn-autocomplete > div > div > input`,
firstTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(2) > vn-autocomplete[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`,
@ -218,7 +218,7 @@ export default {
fifthTagSelectOptionFive: `vn-item-tags vn-horizontal:nth-child(6) > vn-autocomplete[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(5)`,
fifthValueInput: `vn-item-tags vn-horizontal:nth-child(6) > vn-textfield[label="Value"] > div > input`,
fifthRelevancyInput: `vn-horizontal:nth-child(6) > vn-textfield[label="Relevancy"] > div > input`,
addItemTagButton: `vn-icon[icon="add_circle"]`,
addItemTagButton: `vn-icon-button[icon="add_circle"]`,
submitItemTagsButton: `${components.vnSubmit}`
},
itemTax: {

View File

@ -23,7 +23,7 @@ describe('Ticket', () => {
it('should search for the ticket with id 1', () => {
return nightmare
.wait(selectors.ticketsIndex.searchTicketInput)
.type(selectors.ticketsIndex.searchTicketInput, '1')
.type(selectors.ticketsIndex.searchTicketInput, 'id:1')
.click(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1)
.countSearchResults(selectors.ticketsIndex.searchResult)

View File

@ -23,7 +23,7 @@ describe('Ticket', () => {
it('should search for the ticket with id 1', () => {
return nightmare
.wait(selectors.ticketsIndex.searchTicketInput)
.type(selectors.ticketsIndex.searchTicketInput, '1')
.type(selectors.ticketsIndex.searchTicketInput, 'id:1')
.click(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1)
.countSearchResults(selectors.ticketsIndex.searchResult)

View File

@ -25,7 +25,7 @@ describe('Ticket', () => {
it('should search for the ticket 1', () => {
return nightmare
.wait(selectors.ticketsIndex.searchResult)
.type(selectors.ticketsIndex.searchTicketInput, 1)
.type(selectors.ticketsIndex.searchTicketInput, 'id:1')
.click(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1)
.countSearchResults(selectors.ticketsIndex.searchResult)

View File

@ -25,7 +25,7 @@
// it('should search for the ticket 1', () => {
// return nightmare
// .wait(selectors.ticketsIndex.searchResult)
// .type(selectors.ticketsIndex.searchTicketInput, 1)
// .type(selectors.ticketsIndex.searchTicketInput, 'id:1')
// .click(selectors.ticketsIndex.searchButton)
// .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1)
// .countSearchResults(selectors.ticketsIndex.searchResult)

View File

@ -25,7 +25,7 @@ describe('Ticket', () => {
it('should search for the ticket 1', () => {
return nightmare
.wait(selectors.ticketsIndex.searchResult)
.type(selectors.ticketsIndex.searchTicketInput, 1)
.type(selectors.ticketsIndex.searchTicketInput, 'id:1')
.click(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1)
.countSearchResults(selectors.ticketsIndex.searchResult)

View File

@ -17,6 +17,7 @@
"angular-translate-loader-partial": "^2.18.1",
"flatpickr": "^4.4.6",
"fs-extra": "^5.0.0",
"js-yaml": "^3.10.0",
"material-design-lite": "^1.3.0",
"mg-crud": "^1.1.2",
"npm": "^5.8.0",
@ -55,7 +56,6 @@
"html-loader": "^0.4.4",
"jasmine": "^2.9.0",
"jasmine-spec-reporter": "^4.2.1",
"js-yaml": "^3.10.0",
"karma": "^1.7.1",
"karma-chrome-launcher": "^2.2.0",
"karma-firefox-launcher": "^1.1.0",

View File

@ -0,0 +1,32 @@
USE `vn`;
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `buy` AS
SELECT
`c`.`Id_Compra` AS `id`,
`c`.`Id_Entrada` AS `entryFk`,
`c`.`Id_Article` AS `itemFk`,
`c`.`Costefijo` AS `buyingValue`,
`c`.`Cantidad` AS `quantity`,
`c`.`Id_Cubo` AS `packageFk`,
`c`.`Etiquetas` AS `stickers`,
`c`.`Portefijo` AS `freightValue`,
`c`.`Embalajefijo` AS `packageValue`,
`c`.`Comisionfija` AS `comissionValue`,
`c`.`Packing` AS `packing`,
`c`.`grouping` AS `grouping`,
`c`.`caja` AS `groupingMode`,
`c`.`Nicho` AS `location`,
`c`.`Tarifa1` AS `price1`,
`c`.`Tarifa2` AS `price2`,
`c`.`Tarifa3` AS `price3`,
`c`.`PVP` AS `minPrice`,
`c`.`Productor` AS `producer`,
`c`.`Vida` AS `printedStickers`,
`c`.`punteo` AS `isChecked`,
`c`.`buy_edi_id` AS `ektFk`,
`c`.`Novincular` AS `isIgnored`
FROM
`vn2008`.`Compres` `c`;

View File

@ -0,0 +1,45 @@
USE `edi`;
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `edi`.`ekt` AS
SELECT
`vn2008`.`buy_edi`.`id` AS `id`,
`vn2008`.`buy_edi`.`barcode` AS `barcode`,
`vn2008`.`buy_edi`.`entry_year` AS `entry_year`,
`vn2008`.`buy_edi`.`delivery_number` AS `delivery_number`,
`vn2008`.`buy_edi`.`fec` AS `fec`,
`vn2008`.`buy_edi`.`hor` AS `hor`,
`vn2008`.`buy_edi`.`now` AS `now`,
`vn2008`.`buy_edi`.`ptj` AS `ptj`,
`vn2008`.`buy_edi`.`ref` AS `ref`,
`vn2008`.`buy_edi`.`item` AS `item`,
`vn2008`.`buy_edi`.`pac` AS `pac`,
`vn2008`.`buy_edi`.`qty` AS `qty`,
`vn2008`.`buy_edi`.`ori` AS `ori`,
`vn2008`.`buy_edi`.`cat` AS `cat`,
`vn2008`.`buy_edi`.`agj` AS `agj`,
`vn2008`.`buy_edi`.`kop` AS `kop`,
`vn2008`.`buy_edi`.`ptd` AS `ptd`,
`vn2008`.`buy_edi`.`sub` AS `sub`,
`vn2008`.`buy_edi`.`pro` AS `pro`,
`vn2008`.`buy_edi`.`pri` AS `pri`,
`vn2008`.`buy_edi`.`package` AS `package`,
`vn2008`.`buy_edi`.`auction` AS `auction`,
`vn2008`.`buy_edi`.`klo` AS `klo`,
`vn2008`.`buy_edi`.`k01` AS `k01`,
`vn2008`.`buy_edi`.`k02` AS `k02`,
`vn2008`.`buy_edi`.`k03` AS `k03`,
`vn2008`.`buy_edi`.`k04` AS `k04`,
`vn2008`.`buy_edi`.`s1` AS `s1`,
`vn2008`.`buy_edi`.`s2` AS `s2`,
`vn2008`.`buy_edi`.`s3` AS `s3`,
`vn2008`.`buy_edi`.`s4` AS `s4`,
`vn2008`.`buy_edi`.`s5` AS `s5`,
`vn2008`.`buy_edi`.`s6` AS `s6`,
`vn2008`.`buy_edi`.`ok` AS `ok`,
`vn2008`.`buy_edi`.`trolley_id` AS `trolley_id`,
`vn2008`.`buy_edi`.`scanned` AS `scanned`
FROM
`vn2008`.`buy_edi`;

View File

@ -0,0 +1,40 @@
USE `vn`;
DROP procedure IF EXISTS `itemLastEntries`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `itemLastEntries`(vItem INT, vDays DATE)
BEGIN
SELECT
w.id AS warehouseFk,
tr.landed,
b.entryFk,
b.isIgnored,
b.price2,
b.price3,
b.stickers,
b.packing,
b.grouping,
i.stems,
b.quantity,
b.buyingValue,
b.packageFk ,
s.id AS supplierFk
FROM itemType it
RIGHT JOIN (entry e
LEFT JOIN supplier s ON s.id = e.supplierFk
RIGHT JOIN buy b ON b.entryFk = e.id
LEFT JOIN item i ON i.id = b.itemFk
LEFT JOIN ink ON ink.id = i.inkFk
LEFT JOIN travel tr ON tr.id = e.travelFk
LEFT JOIN warehouse w ON w.id = tr.warehouseInFk
LEFT JOIN origin o ON o.id = i.originFk
) ON it.id = i.typeFk
LEFT JOIN edi.ekt ek ON b.ektFk = ek.id
WHERE b.itemFk = vItem And tr.shipped BETWEEN vDays AND CURDATE()
ORDER BY tr.landed DESC , b.id DESC;
END$$
DELIMITER ;

View File

@ -1,54 +0,0 @@
USE `vn`;
DROP procedure IF EXISTS `ticketComponentMakeUpdate`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `ticketComponentMakeUpdate`(
vTicketFk INT,
vClientFk INT,
vAgencyModeFk INT,
vAddressFk INT,
vWarehouseFk INT,
vShipped DATETIME,
vLanded DATE,
vIsDeleted BOOLEAN,
vHasToBeUnrouted BOOLEAN,
vOption INT)
BEGIN
/**
* Calcula los componentes de un ticket
* y los actualiza con los nuevos datos.
*
* @param vTicketFk Id del ticket
* @param vClientFk Id del cliente
* @param vAgencyModeFk Id del tipo de agencia
* @param vAddressFk Id del consignatario
* @param vWarehouseFk Id del almacén
* @param vShipped Fecha de salida
* @param vLanded Fecha de llegada
* @param vIsDeleted Marcado como eliminado
* @param vHasToBeUnrouted Marcado para sacar de ruta
* @param vOption Id de la acción ticketUpdateAction
*/
CALL vn.ticketComponentPreview (vTicketFk, vLanded, vAddressFk, vAgencyModeFk, vWarehouseFk);
CALL vn.ticketComponentUpdate (
vTicketFk,
vClientFk,
vAgencyModeFk,
vAddressFk,
vWarehouseFk,
vShipped,
vLanded,
vIsDeleted,
vHasToBeUnrouted,
vOption
);
DROP TEMPORARY TABLE
tmp.ticketComponent,
tmp.ticketComponentPrice;
END$$
DELIMITER ;

View File

@ -1,74 +0,0 @@
USE `vn`;
DROP procedure IF EXISTS `ticketComponentPreview`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticketComponentPreview`(
vTicketFk INT,
vDate DATE,
vAddressFk INT,
vAgencyModeFk INT,
vWarehouseFk SMALLINT)
BEGIN
/**
* Devuelve un listado previo de
* componentes para un ticket
*
* @param vTicketFk Id del ticket
* @param vDate Fecha de envío
* @param vAddressFk Id del consignatario
* @param vAgencyModeFk Id del modo de agencia
* @param vWarehouseFk Id del almacén
*/
DECLARE vAgencyFk INT;
DECLARE vShipped DATE;
DECLARE vBuyOrderItem INT DEFAULT 100;
SELECT agencyFk INTO vAgencyFk
FROM agencyMode
WHERE id = vAgencyModeFk;
CALL agencyHourOffer(vDate, vAddressFk, vAgencyFk);
SELECT shipped INTO vShipped
FROM tmp.agencyHourOffer
WHERE warehouseFk = vWarehouseFK;
CALL buyUltimate(vWarehouseFK, vShipped);
DROP TEMPORARY TABLE IF EXISTS tmp.ticketLot;
CREATE TEMPORARY TABLE tmp.ticketLot ENGINE = MEMORY (
SELECT
vWarehouseFK AS warehouseFk,
NULL AS available,
s.itemFk,
bu.buyFk
FROM sale s
LEFT JOIN tmp.buyUltimate bu ON bu.itemFk = s.itemFk
WHERE s.ticketFk = vTicketFk
AND s.itemFk != vBuyOrderItem
GROUP BY bu.warehouseFk, bu.itemFk);
CALL ticketComponentCalculate(vAddressFk, vAgencyModeFk);
REPLACE INTO tmp.ticketComponent (warehouseFk, itemFk, componentFk, cost)
SELECT t.warehouseFk, s.itemFk, sc.componentFk, sc.value
FROM saleComponent sc
JOIN sale s ON s.id = sc.saleFk
JOIN ticket t ON t.id = s.ticketFk
JOIN componentRate cr ON cr.id = sc.componentFk
WHERE s.ticketFk = vTicketFk AND NOT cr.isRenewable;
SET @shipped = vShipped;
DROP TEMPORARY TABLE
tmp.agencyHourOffer,
tmp.buyUltimate,
tmp.ticketLot;
IF IFNULL(vShipped, CURDATE() - 1) < CURDATE() THEN
CALL util.throw('NO_AGENCY_AVAILABLE');
END IF;
END$$
DELIMITER ;

View File

@ -1,55 +0,0 @@
USE `vn`;
DROP procedure IF EXISTS `ticketComponentPriceDifference`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticketComponentPriceDifference`(
vTicketFk INT,
vDate DATE,
vAddressFk INT,
vAgencyModeFk INT,
vWarehouseFk INT)
BEGIN
/**
* Devuelve las diferencias de precio
* de los movimientos de un ticket.
*
* @param vTicketFk Id del ticket
* @param vDate Fecha de envío
* @param vAddressFk Id del consignatario
* @param vAgencyModeFk Id del modo de agencia
* @param vWarehouseFk Id del almacén
*/
CALL vn.ticketComponentPreview(vTicketFk, vDate, vAddressFk, vAgencyModeFk, vWarehouseFk);
SELECT
s.itemFk,
i.name,
i.size,
i.category,
IFNULL(s.quantity, 0) AS quantity,
IFNULL(s.price, 0) AS price,
ROUND(SUM(tc.cost), 4) AS newPrice,
s.quantity * (s.price - ROUND(SUM(cost), 4)) difference,
s.id AS saleFk
FROM sale s
JOIN item i ON i.id = s.itemFk
JOIN ticket t ON t.id = s.ticketFk
LEFT JOIN tmp.ticketComponent tc ON tc.itemFk = s.itemFk
AND tc.warehouseFk = t.warehouseFk
LEFT JOIN saleComponent sc ON sc.saleFk = s.id
AND sc.componentFk = tc.componentFk
LEFT JOIN componentRate cr ON cr.id = tc.componentFk
WHERE
t.id = vTicketFk
AND IF(sc.componentFk IS NULL
AND cr.classRate IS NOT NULL, FALSE, TRUE)
GROUP BY s.id ORDER BY s.id;
DROP TEMPORARY TABLE
tmp.ticketComponent,
tmp.ticketComponentPrice;
END$$
DELIMITER ;

View File

@ -1,74 +0,0 @@
USE `vn`;
DROP procedure IF EXISTS `ticketComponentUpdate`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticketComponentUpdate`(
vTicketFk INT,
vClientFk INT,
vAgencyModeFk INT,
vAddressFk INT,
vWarehouseFk INT,
vShipped DATETIME,
vLanded DATE,
vIsDeleted BOOLEAN,
vHasToBeUnrouted BOOLEAN,
vOption INT)
BEGIN
/**
* Actualiza un ticket y sus componentes
* con los nuevos datos.
*
* @param vTicketFk Id del ticket
* @param vClientFk Id del cliente
* @param vAgencyModeFk Id del tipo de agencia
* @param vAddressFk Id del consignatario
* @param vWarehouseFk Id del almacén
* @param vShipped Fecha de salida
* @param vLanded Fecha de llegada
* @param vIsDeleted Marcado como eliminado
* @param vHasToBeUnrouted Marcado para sacar de ruta
* @param vOption Id de la acción ticketUpdateAction
*/
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
UPDATE ticket t
SET
t.clientFk = vClientFk,
t.agencyModeFk = vAgencyModeFk,
t.addressFk = vAddressFk,
t.warehouseFk = vWarehouseFk,
t.landed = vLanded,
t.shipped = vShipped,
t.isDeleted = vIsDeleted
WHERE
t.id = vTicketFk;
IF vHasToBeUnrouted THEN
UPDATE ticket t SET t.routeFk = NULL
WHERE t.id = vTicketFk;
END IF;
IF vOption <> 8 THEN
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
CREATE TEMPORARY TABLE tmp.sale
(PRIMARY KEY (saleFk))
ENGINE = MEMORY
SELECT id AS saleFk, vWarehouseFk warehouseFk
FROM sale s WHERE s.ticketFk = vTicketFk;
CALL ticketComponentUpdateSale (vOption);
DROP TEMPORARY TABLE tmp.sale;
END IF;
COMMIT;
END$$
DELIMITER ;

File diff suppressed because it is too large Load Diff

View File

@ -328,16 +328,16 @@ INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `des
INSERT INTO `vn`.`ticketTracking`(`id`, `ticketFk`, `stateFk`, `workerFk`, `created`)
VALUES
(1, 1, 1, 5, CURDATE()),
(2, 2, 2, 5, CURDATE()),
(3, 3, 3, 5, CURDATE()),
(4, 4, 1, 5, CURDATE()),
(5, 5, 2, 18, CURDATE()),
(6, 6, 3, 18, CURDATE()),
(7, 7, 1, 18, CURDATE()),
(8, 8, 2, 19, CURDATE()),
(9, 9, 3, 19, CURDATE()),
(10, 10, 3, 19, CURDATE()),
(1, 1, 13, 5, CURDATE()),
(2, 2, 15, 5, CURDATE()),
(3, 3, 16, 5, CURDATE()),
(4, 4, 13, 5, CURDATE()),
(5, 5, 15, 18, CURDATE()),
(6, 6, 16, 18, CURDATE()),
(7, 7, 13, 18, CURDATE()),
(8, 8, 15, 19, CURDATE()),
(9, 9, 16, 19, CURDATE()),
(10, 10, 13, 19, CURDATE()),
(11, 11, 3, 19, CURDATE()),
(12, 12, 3, 19, CURDATE()),
(13, 13, 3, 19, CURDATE()),
@ -346,9 +346,9 @@ INSERT INTO `vn`.`ticketTracking`(`id`, `ticketFk`, `stateFk`, `workerFk`, `crea
(16, 16, 1, 19, CURDATE()),
(17, 17, 1, 19, CURDATE()),
(18, 18, 1, 19, CURDATE()),
(19, 19, 1, 19, CURDATE()),
(20, 20, 1, 19, CURDATE()),
(21, 21, 1, 19, CURDATE());
(19, 19, 13, 19, CURDATE()),
(20, 20, 15, 19, CURDATE()),
(21, 21, 16, 19, CURDATE());
INSERT INTO `vn`.`vehicle`(`id`, `numberPlate`, `tradeMark`, `model`, `companyFk`, `warehouseFk`, `description`, `m3`, `isActive`)
VALUES
@ -631,12 +631,12 @@ INSERT INTO `vn`.`ticketWeekly`(`ticketFk`, `weekDay`)
( 4, 4),
( 5, 6);
INSERT INTO `vn`.`travel`(`id`, `landed`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `m3`, `kg`)
INSERT INTO `vn`.`travel`(`id`,`shipped`, `landed`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `m3`, `kg`)
VALUES
( 1, CURDATE(), 1, 2, 1, 100.00, 1000),
( 2, CURDATE(), 1, 2, 1, 150, 2000),
( 3, CURDATE(), 1, 2, 1, 0.00, 0.00),
( 4, CURDATE(), 1, 2, 1, 50.00, 500);
( 1, CURDATE(), CURDATE(), 1, 2, 1, 100.00, 1000),
( 2, CURDATE(), CURDATE(), 1, 2, 1, 150, 2000),
( 3, CURDATE(), CURDATE(), 1, 2, 1, 0.00, 0.00),
( 4, CURDATE(), CURDATE(), 1, 2, 1, 50.00, 500);
INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `companyFk`)
VALUES

View File

@ -1,29 +0,0 @@
{
"name": "Warehouse",
"base": "VnModel",
"options": {
"mysql": {
"table": "warehouse",
"database": "vn"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "String",
"required": true
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -40,6 +40,7 @@ module.exports = function(Self) {
street: data.street,
city: data.city,
provinceFk: data.provinceFk,
countryFk: data.countryFk,
isEqualizated: data.isEqualizated
};
newClient = await Self.create(client);

View File

@ -1,61 +0,0 @@
module.exports = function(Client) {
Client.installMethod('filter', filterClients);
function filterClients(params) {
let filters = {
where: {},
skip: (params.page - 1) * params.size,
limit: params.size
};
delete params.page;
delete params.size;
if (params.search) {
filters.where.and = [
{
or: [
{id: params.search},
{name: {regexp: params.search}}
]
}
];
delete params.search;
}
if (params.phone) {
let phones = [
{phone: params.phone},
{mobile: params.phone}
];
if (filters.where.and) {
filters.where.and.push(
{
or: phones
}
);
} else {
filters.where.or = phones;
}
delete params.phone;
}
let properties = Object.keys(params);
if (properties.length) {
properties.forEach(
property => {
let propertyToBeEqual = (property === 'postcode' || property === 'fi' || property === 'id');
if (filters.where.and) {
let filter = {};
filter[property] = propertyToBeEqual ? params[property] : {regexp: params[property]};
filters.where.and.push(filter);
} else {
filters.where[property] = propertyToBeEqual ? params[property] : {regexp: params[property]};
}
}
);
}
return filters;
}
};

View File

@ -47,9 +47,10 @@ module.exports = Self => {
FROM vn.itemBotanical ib WHERE itemFk = ?`;
promises.push(Self.rawSql(createBotanical, [newItem.id, origin.id]));
let createTags = `INSERT INTO vn.itemTag (itemFk, tagFk, value, priority)
SELECT ?, tagFk, value, priority
FROM vn.itemTag WHERE itemFk = ?`;
let createTags = `INSERT INTO vn.itemTag(itemFk, tagFk, value, priority)
SELECT ?, i.tagFk, i.value,i.priority
FROM vn.itemTag i WHERE i.itemFk = ?
ON DUPLICATE KEY UPDATE value = i.value, priority = i.priority`;
promises.push(Self.rawSql(createTags, [newItem.id, origin.id]));
let createTax = `REPLACE INTO vn.itemTaxCountry (itemFk, countryFk, taxClassFk)

View File

@ -1,62 +0,0 @@
module.exports = Self => {
Self.installMethod('filter', filterParams);
function filterParams(params) {
let filter = {
where: {},
skip: (params.page - 1) * params.size,
limit: params.size,
order: params.order || 'name ASC', // name, relevancy DESC
include: [
{relation: 'itemType',
scope: {
fields: ['name', 'workerFk'],
include: {
relation: 'worker',
fields: ['firstName', 'name']
}
}
},
{relation: 'origin'},
{relation: 'ink'},
{relation: 'producer'},
{relation: 'intrastat'},
{relation: 'expence'}
]
};
delete params.page;
delete params.size;
delete params.order;
if (params.search) {
filter.where.and = [
{
or: [
{id: params.search},
{name: {regexp: params.search}}
]
}
];
delete params.search;
}
if (params.itemSize) {
params.size = params.itemSize;
delete params.itemSize;
}
Object.keys(params).forEach(
key => {
if (filter.where.and) {
let filter = {};
filter[key] = (key === 'description' || key === 'name') ? {regexp: params[key]} : params[key];
filter.where.and.push(filter);
} else {
filter.where[key] = (key === 'description' || key === 'name') ? {regexp: params[key]} : params[key];
}
}
);
return filter;
}
};

View File

@ -0,0 +1,24 @@
module.exports = Self => {
Self.remoteMethod('getDiary', {
description: 'Returns the ',
accessType: 'READ',
accepts: [{
arg: 'params',
type: 'object',
description: 'itemFk, warehouseFk'
}],
returns: {
arg: 'diary',
root: true
},
http: {
path: `/getDiary`,
verb: 'GET'
}
});
Self.getDiary = async params => {
let [diary] = await Self.rawSql(`CALL vn.itemDiary(?, ?)`, [params.itemFk, params.warehouseFk]);
return diary;
};
};

View File

@ -0,0 +1,35 @@
module.exports = Self => {
Self.remoteMethod('moveToTicket', {
description: 'Change the state of a ticket',
accessType: '',
accepts: [{
arg: 'params',
type: 'object',
required: true,
description: '[sales IDs], newTicketFk, actualTicketFk',
http: {source: 'body'}
}],
returns: {
type: 'string',
root: true
},
http: {
path: `/moveToTicket`,
verb: 'post'
}
});
Self.moveToTicket = async params => {
let thisTicketIsEditable = await Self.app.models.Ticket.isEditable(params.actualTicketFk);
if (!thisTicketIsEditable)
throw new Error(`The sales of this ticket can't be modified`);
let newTicketIsEditable = await Self.app.models.Ticket.isEditable(params.newTicketFk);
if (!newTicketIsEditable)
throw new Error(`The sales of this ticket can't be modified`);
for (let i = 0; i < params.sales.length; i++) {
await Self.app.models.Sale.update({id: params.sales[i].id}, {ticketFk: params.newTicketFk});
}
};
};

View File

@ -0,0 +1,31 @@
module.exports = Self => {
Self.remoteMethod('removes', {
description: 'Change the state of a ticket',
accessType: '',
accepts: [{
arg: 'params',
type: 'object',
required: true,
description: '[sales IDs], actualTicketFk',
http: {source: 'body'}
}],
returns: {
type: 'string',
root: true
},
http: {
path: `/removes`,
verb: 'post'
}
});
Self.removes = async params => {
let thisTicketIsEditable = await Self.app.models.Ticket.isEditable(params.actualTicketFk);
if (!thisTicketIsEditable)
throw new Error(`The sales of this ticket can't be modified`);
for (let i = 0; i < params.sales.length; i++) {
await Self.app.models.Sale.destroyById(params.sales[i].id);
}
};
};

View File

@ -0,0 +1,25 @@
module.exports = Self => {
Self.remoteMethod('deleted', {
description: 'Sets the isDeleted value of a ticket to 1',
accessType: '',
accepts: [{
arg: 'ticketFk',
type: 'Object',
required: true,
description: 'TicketFk',
http: {source: 'body'}
}],
returns: {
type: 'string',
root: true
},
http: {
path: `/deleted`,
verb: 'post'
}
});
Self.deleted = async params => {
return await Self.app.models.Ticket.update({id: params.id}, {isDeleted: '1'});
};
};

View File

@ -1,87 +0,0 @@
module.exports = Self => {
Self.installMethod('filter', filterParams);
function filterParams(params) {
let filters = {
where: {},
skip: (params.page - 1) * params.size,
limit: params.size,
order: params.order || 'created DESC',
include: [
{
relation: 'address',
scope: {
fields: ['provinceFk'],
include: {
relation: 'province',
scope: {
fields: ['name']
}
}
}
}, {
relation: 'warehouse',
scope: {
fields: ['name']
}
}, {
relation: 'agencyMode',
scope: {
fields: ['name']
}
}, {
relation: 'tracking',
scope: {
fields: ['stateFk'],
include: {
relation: 'state',
scope: {
fields: ['name']
}
}
}
}, {
relation: 'client',
scope: {
fields: ['salesPersonFk'],
include: {
relation: 'salesPerson',
scope: {
fields: ['name']
}
}
}
}
]
};
delete params.page;
delete params.size;
delete params.order;
if (params.search) {
filters.where.and = [
{
or: [
{id: params.search},
{name: {regexp: params.search}}
]
}
];
delete params.search;
}
Object.keys(params).forEach(
key => {
if (filters.where.and) {
let filter = {};
filter[key] = (key === 'nickname') ? {regexp: params[key]} : params[key];
filters.where.and.push(filter);
} else {
filters.where[key] = (key === 'nickname') ? {regexp: params[key]} : params[key];
}
}
);
return filters;
}
};

View File

@ -21,7 +21,7 @@ module.exports = Self => {
Self.isEditable = async ticketFk => {
let state = await Self.app.models.TicketState.findOne({where: {ticketFk: ticketFk}, fields: 'alertLevel'});
return state != null && state.alertLevel == 0;
let exists = await Self.app.models.Ticket.findOne({where: {id: ticketFk}, fields: 'isDeleted'});
return (exists && state == null && exists.isDeleted == 0) || (exists.isDeleted == 0 && state.alertLevel == 0);
};
};

View File

@ -4,7 +4,7 @@ describe('ticket componentUpdate()', () => {
it('should call the componentUpdate method', done => {
let data = {
agencyModeFk: 1,
addressFk: 121,
addressFk: 121,
warehouseFk: 1,
shipped: Date.now(),
landed: Date.now(),

View File

@ -0,0 +1,35 @@
module.exports = Self => {
Self.remoteMethod('threeLastActive', {
description: 'Returns the last three tickets of a client that have the alertLevel at 0 and the shiped day is gt today',
accessType: '',
accepts: [{
arg: 'filter',
type: 'object',
required: true,
description: 'client id, ticketFk'
}],
returns: {
type: [this.modelName],
root: true
},
http: {
path: `/threeLastActive`,
verb: 'GET'
}
});
Self.threeLastActive = async filter => {
console.log(filter);
let query = `
SELECT t.id,t.shipped,a.name AS agencyName,w.name AS warehouseName
FROM vn.ticket t
JOIN vn.ticketState ts ON t.id = ts.ticketFk
JOIN vn.agencyMode a ON t.agencyModeFk = a.id
JOIN vn.warehouse w ON t.warehouseFk = w.id
WHERE t.shipped > CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0 AND t.id <> ?
ORDER BY t.shipped
LIMIT 3`;
let result = await Self.rawSql(query, [filter.clientFk, filter.ticketFk]);
return result;
};
};

View File

@ -10,7 +10,6 @@ module.exports = Self => {
require('../methods/client/card')(Self);
require('../methods/client/createWithUser')(Self);
require('../methods/client/listWorkers')(Self);
require('../methods/client/filter')(Self);
require('../methods/client/hasCustomerRole')(Self);
require('../methods/client/isValidClient')(Self);
require('../methods/client/activeSalesPerson')(Self);

View File

@ -1,9 +1,9 @@
let UserError = require('../helpers').UserError;
module.exports = Self => {
require('../methods/item/filter')(Self);
require('../methods/item/clone')(Self);
require('../methods/item/updateTaxes')(Self);
require('../methods/item/getDiary')(Self);
Self.validatesPresenceOf('name', {message: 'Cannot be blank'});
Self.validatesPresenceOf('originFk', {message: 'Cannot be blank'});

View File

@ -3,4 +3,9 @@ module.exports = Self => {
require('../methods/sale/saleComponentFilter')(Self);
require('../methods/sale/priceDifference')(Self);
require('../methods/sale/crudSale')(Self);
require('../methods/sale/moveToTicket')(Self);
require('../methods/sale/removes')(Self);
// require('../methods/sale/updateDiscount')(Self);
// require('../methods/sale/updatePrice')(Self);
// require('../methods/sale/updateQuantity')(Self);
};

View File

@ -1,11 +1,14 @@
module.exports = Self => {
require('../methods/ticket/changeTime')(Self);
require('../methods/ticket/changeWorker')(Self);
require('../methods/ticket/filter')(Self);
require('../methods/ticket/getVolume')(Self);
require('../methods/ticket/getTotalVolume')(Self);
require('../methods/ticket/summary')(Self);
require('../methods/ticket/getTotal')(Self);
require('../methods/ticket/getTaxes')(Self);
require('../methods/ticket/componentUpdate')(Self);
// require('../methods/ticket/create')(Self);
require('../methods/ticket/isEditable')(Self);
require('../methods/ticket/threeLastActive')(Self);
require('../methods/ticket/deleted')(Self);
};