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

This commit is contained in:
Carlos Jimenez 2018-10-14 17:07:13 +02:00
commit b6ba23282c
95 changed files with 826 additions and 290 deletions

View File

@ -4,4 +4,5 @@ Attended by: Atendida por
Landed: Recibido
Price: Precio
Claimable sales from ticket: Lineas reclamables del ticket
Detail: Detalles
Detail: Detalles
Add sale item: Añadir artículo

View File

@ -339,7 +339,7 @@
"component": "vn-client-web-payment",
"description": "Web Payment",
"menu": {
"icon": "web"
"icon": ""
}
},
{

View File

@ -14,24 +14,19 @@
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"
orange
<vn-icon-button icon="star"
ng-if="address.isDefaultAddress">
star
</i>
<i class="material-icons"
orange
</vn-icon-button>
<vn-icon-button icon="star_border"
ng-if="!address.isActive"
vn-tooltip="Active first to set as default">
star_border
</i>
<i class="material-icons pointer"
orange
ng-if="address.isActive && !address.isDefaultAddress"
</vn-icon-button>
<vn-icon-button orange
icon="star_border"
vn-tooltip="Set as default"
ng-click="$ctrl.setDefault(address)">
star_border
</i>
ng-click="$ctrl.setDefault(address)"
ng-if="address.isActive && !address.isDefaultAddress">
</vn-icon-button>
</vn-none>
<vn-one border-solid-right>
<vn-horizontal>

View File

@ -8,20 +8,14 @@ export default class Controller {
this.translate = $translate;
}
set client(value) {
this._client = value;
if (value)
this.orgData = Object.assign({}, value);
}
get client() {
return this._client;
}
onSubmit() {
let shouldNotify = false;
if (this.hasPaymethodChanges())
shouldNotify = true;
this.$scope.watcher.submit().then(() => {
if (this.hasPaymethodChanged())
if (shouldNotify)
this.notifyChanges();
});
}
@ -32,10 +26,12 @@ export default class Controller {
);
}
hasPaymethodChanged() {
let payMethod = this.orgData.payMethodFk != this.client.payMethodFk;
let iban = this.orgData.iban != this.client.iban;
let dueDay = this.orgData.dueDay != this.client.dueDay;
hasPaymethodChanges() {
let orgData = this.$scope.watcher.orgData;
let payMethod = orgData.payMethodFk != this.client.payMethodFk;
let iban = orgData.iban != this.client.iban;
let dueDay = orgData.dueDay != this.client.dueDay;
return payMethod || iban || dueDay;
}

View File

@ -1,4 +1,5 @@
import './index';
import {watcher} from '../../../helpers/watcherHelper';
describe('Client', () => {
describe('Component vnClientBillingData', () => {
@ -16,38 +17,24 @@ describe('Client', () => {
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope = $rootScope.$new();
$scope.watcher = {
submit: () => {
return {
then: callback => {
callback();
}
};
}
};
$scope.watcher = watcher;
controller = $componentController('vnClientBillingData', {$scope: $scope});
controller.client = {id: 101, name: 'Client name', payMethodFk: 4};
$scope.watcher.orgData = {id: 101, name: 'Client name', payMethodFk: 4};
}));
describe('client()', () => {
it(`should call setter client`, () => {
expect(controller.orgData).toEqual(controller.client);
});
});
describe('onSubmit()', () => {
it(`should call notifyChanges() if there are changes on payMethod data`, () => {
spyOn(controller, 'notifyChanges');
controller.client.payMethodFk = 5;
controller.onSubmit();
expect(controller.hasPaymethodChanged()).toBeTruthy();
expect(controller.notifyChanges).toHaveBeenCalledWith();
});
});
describe('notifyChanges()', () => {
it(`should call notifyChanges() and perform a GET query`, () => {
it(`should perform a GET query`, () => {
$httpBackend.when('GET', `/mailer/notification/payment-update/101`).respond(true);
$httpBackend.expect('GET', `/mailer/notification/payment-update/101`);
controller.notifyChanges();
@ -55,17 +42,17 @@ describe('Client', () => {
});
});
describe('hasPaymethodChanged()', () => {
it(`should call hasPaymethodChanged() and return true if there are changes on payMethod data`, () => {
describe('hasPaymethodChanges()', () => {
it(`should return true if there are changes on payMethod data`, () => {
controller.client.payMethodFk = 5;
expect(controller.hasPaymethodChanged()).toBeTruthy();
expect(controller.hasPaymethodChanges()).toBeTruthy();
});
it(`should call hasPaymethodChanged() and return false if there are no changes on payMethod data`, () => {
it(`should return false if there are no changes on payMethod data`, () => {
controller.client.payMethodFk = 4;
expect(controller.hasPaymethodChanged()).toBeFalsy();
expect(controller.hasPaymethodChanges()).toBeFalsy();
});
});
});

View File

@ -5,10 +5,11 @@
<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 orange>
<i class="material-icons pointer"
<vn-icon-button icon="lock" orange
ng-if="!classification.finished"
vn-tooltip="Close contract"
ng-click="$ctrl.closeContract(classification)">lock_outline</i>
ng-click="$ctrl.closeContract(classification)">
</vn-icon-button>
</vn-none>
<vn-one border-solid-right>
<div><vn-label translate>Since</vn-label> {{::classification.started | date:'dd/MM/yyyy'}}</div>

View File

@ -24,13 +24,13 @@
<vn-tbody>
<vn-tr ng-repeat="recovery in recoveries">
<vn-td>
<vn-icon
class="bright pointer"
icon="lock"
<vn-icon-button icon="lock"
vn-acl="administrative"
vn-acl-action="remove"
vn-tooltip="Finish that recovery period"
ng-if="!recovery.finished"
ng-click="$ctrl.setFinished(recovery)">
</vn-icon>
on-click="$ctrl.setFinished(recovery)">
</vn-icon-button>
</vn-td>
<vn-td>{{::recovery.started | date:'dd/MM/yyyy' }}</vn-td>
<vn-td>{{recovery.finished | date:'dd/MM/yyyy' }}</vn-td>
@ -49,7 +49,11 @@
</vn-pagination>
</vn-card>
</vn-vertical>
<a vn-tooltip="New recovery" ui-sref="client.card.recovery.create"
vn-bind="+" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>
<vn-float-button icon="add" fixed-bottom-right
vn-tooltip="New recovery"
vn-bind="+"
vn-acl="administrative"
vn-acl-action="remove"
ui-sref="client.card.recovery.create">
</vn-float-button>

View File

@ -24,12 +24,13 @@
<vn-tbody>
<vn-tr ng-repeat="transaction in transactions">
<vn-td style="width: 3em; text-align: center">
<vn-icon orange pointer
<vn-icon-button
icon="check"
vn-acl="administrative"
vn-tooltip="Confirm transaction"
ng-click="$ctrl.confirm(transaction)"
ng-show="::!transaction.isConfirmed"
icon="check">
</vn-icon>
ng-show="::!transaction.isConfirmed"
on-click="$ctrl.confirm(transaction)">
</vn-icon-button>
</vn-td>
<vn-td>{{::transaction.id}}</vn-td>
<vn-td number>{{::transaction.amount | currency: '€ '}}</vn-td>

View File

@ -1 +1,3 @@
<vn-icon icon="{{::$ctrl.icon}}"></vn-icon>
<button ng-click="$ctrl.onClick()">
<vn-icon icon="{{::$ctrl.icon}}">
</button>

View File

@ -23,6 +23,7 @@ ngModule.component('vnIconButton', {
template: require('./icon-button.html'),
bindings: {
icon: '@',
onClick: '&?',
enabled: '<?'
}
});

View File

@ -1,19 +1,17 @@
@import "colors";
vn-icon-button {
display: inline-block;
color: rgba($main-01, 0.7);
font-size: 18pt;
transition: color 200ms ease-in-out;
cursor: pointer;
& > vn-icon {
display: block;
font-size: inherit;
color: inherit;
margin: 0 auto;
}
&:not(.button):hover {
outline: 0;
button {
background: transparent !important;
background-color: transparent !important;
display: inline-block;
color: $main-01;
border: 0
}
button.mdl-button--colored {
color: orange
}
}

View File

@ -51,7 +51,7 @@ function vnAcl(aclService, $timeout) {
input.setAttribute("disabled", "true");
updateMaterial(input);
});
$element[0].querySelectorAll('i, vn-drop-down').forEach(element => {
$element[0].querySelectorAll('vn-drop-down').forEach(element => {
element.parentNode.removeChild(element);
});
}

View File

@ -46,10 +46,10 @@ describe('Directive acl', () => {
expect(input.attr('disabled')).toBeTruthy();
});
it('should delete any element with the tag i and vn-drop-down', () => {
let html = `<div vn-acl="administrative,client" vn-acl-action="disabled"><label><i/></label><input/></div>`;
it('should delete any element with the tag vn-drop-down', () => {
let html = `<div vn-acl="administrative,client" vn-acl-action="disabled"><vn-drop-down></vn-drop-down><input/></div>`;
compile(false, html);
expect(element.find('i').length).toBe(0);
expect(element.find('vn-drop-down').length).toBe(0);
});
});

View File

@ -1,13 +1,17 @@
module.exports.watcher = {
submit: () => {
return new Promise(accept => {
accept();
});
return {
then: callback => {
callback({data: {id: 1234}});
}
};
},
realSubmit: () => {
return new Promise(accept => {
accept();
});
return {
then: callback => {
callback({data: {id: 1234}});
}
};
},
check: () => {},
notifySaved: () => {},

View File

@ -17,12 +17,10 @@ vn-item-diary {
.balanceNegative .balance {
background-color: $main-01;
color: white;
}
.isIn .in {
background-color: $main-02;
color: white;
}
.truncate {

View File

@ -39,6 +39,7 @@ Worker: Trabajador
Available: Disponible
Create: Crear
Client card: Ficha del cliente
Shipped: F. envio
#Sections
Items: Artículos

View File

@ -15,8 +15,7 @@
"url": "/index?q",
"state": "order.index",
"component": "vn-order-index",
"description": "List",
"acl": ["developer"]
"description": "List"
},
{
"url": "/:id",

View File

@ -47,7 +47,7 @@ class Controller {
let query = `/order/api/Orders/${this.$state.params.id}/getTotal`;
this.$http.get(query).then(res => {
if (res.data) {
this.order.total = res.data.total;
this.order.total = res.data;
}
});
}

View File

@ -48,7 +48,7 @@ describe('Order', () => {
describe('getTotal()', () => {
it(`should make a query and save the data in order.total`, () => {
$httpBackend.expectGET(`/order/api/Orders/${controller.$state.params.id}/getTotal`).respond({total: '20M'});
$httpBackend.expectGET(`/order/api/Orders/${controller.$state.params.id}/getTotal`).respond('20M');
controller.getTotal();
$httpBackend.flush();

View File

@ -75,8 +75,6 @@ class Controller {
this.$http.post(`order/api/Orders/new`, params).then(res => {
this.vnApp.showSuccess(this.translate.instant('Data saved!'));
this.$state.go("order.card.catalog", {id: res.data});
}).catch(e => {
this.vnApp.showError(this.translate.instant(e.data.error.message));
});
}
}

View File

@ -12,21 +12,37 @@
<vn-tr>
<vn-th field="id" default-order="DESC">Id</vn-th>
<vn-th field="clientFk">Client</vn-th>
<vn-th field="companyFk">Company</vn-th>
<vn-th field="isConfirmed">Confirmed</vn-th>
<vn-th field="sourceApp">Created from</vn-th>
<vn-th field="created">Created</vn-th>
<vn-th field="companyFk">Company</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="order in orders" class="clickable"
ui-sref="order.card.catalog({id: {{::order.id}}})">
ui-sref="order.card.summary({id: {{::order.id}}})">
<vn-td>{{::order.id}}</vn-td>
<vn-td>
<span class="link" ng-click="$ctrl.showDescriptor($event, order.clientFk)">
{{::order.client.name}}
</span>
</vn-td>
<vn-td>
<vn-check
field="order.isConfirmed"
disabled="true">
</vn-check>
</vn-td>
<vn-td>{{::order.sourceApp}}</vn-td>
<vn-td>{{::order.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
<vn-td>{{::order.company.code}}</vn-td>
<vn-td>{{::order.created | date:'dd/MM/yyyy'}}</vn-td>
<vn-td>
<vn-icon-button
ng-click="$ctrl.preview($event, order)"
vn-tooltip="Preview"
icon="desktop_windows">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
<vn-empty-rows ng-if="model.data.length === 0" translate>
@ -39,7 +55,13 @@
scroll-selector="ui-view">
</vn-pagination>
</div>
<a ui-sref="order.create" vn-bind="+" vn-tooltip="New order" fixed-bottom-right>
<a ui-sref="order.create" vn-bind="+" vn-tooltip="New order" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>
<vn-client-descriptor-popover vn-id="descriptor"></vn-client-descriptor-popover>
<vn-client-descriptor-popover vn-id="descriptor"></vn-client-descriptor-popover>
<vn-dialog class="dialog-summary"
vn-id="order-summary-dialog">
<tpl-body>
<vn-order-summary order="$ctrl.order"></vn-order-summary>
</tpl-body>
</vn-dialog>

View File

@ -18,26 +18,6 @@ export default class Controller {
};
}
/* exprBuilder(param, value) {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {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};
}
} */
showDescriptor(event, clientFk) {
this.$scope.descriptor.clientFk = clientFk;
this.$scope.descriptor.parent = event.target;
@ -50,11 +30,11 @@ export default class Controller {
this.$scope.popover.relocate();
}
preview(event, ticket) {
preview(event, order) {
event.preventDefault();
event.stopImmediatePropagation();
this.$scope.dialogSummaryTicket.show();
this.ticketSelected = ticket;
this.$scope.orderSummaryDialog.show();
this.order = order;
}
}

View File

@ -48,10 +48,10 @@ class Controller {
}
getVAT() {
let query = `/order/api/Orders/${this.$state.params.id}/getTaxes`;
let query = `/order/api/Orders/${this.$state.params.id}/getVAT`;
this.$http.get(query).then(res => {
this.VAT = res.data.tax;
this.VAT = res.data;
});
}

View File

@ -56,9 +56,9 @@ describe('Order', () => {
});
});
describe('getTaxes()', () => {
it('should make a query to get the taxes of a given order', () => {
$httpBackend.expectGET(`/order/api/Orders/1/getTaxes`).respond({data: {tax: 3}});
describe('getVAT()', () => {
it('should make a query to get the VAT of a given order', () => {
$httpBackend.expectGET(`/order/api/Orders/1/getVAT`).respond({data: {tax: 3}});
controller.getVAT();
$httpBackend.flush();
});

View File

@ -15,4 +15,5 @@ Default order: Orden predeterminado
Ascendant name: Nombre ascendiente
Descendant name: Nombre descendiente
Ascendant price: Precio ascendiente
Descendant price: Precio descendiente
Descendant price: Precio descendiente
Created from: Creado desde

View File

@ -1,9 +1,94 @@
<vn-vertical vn-one>
<vn-card class="summary" pad-medium>
<vn-vertical margin-medium>
<vn-auto>
<h5 text-center pad-small-v class="title">Order</h5>
</vn-auto>
</vn-vertical>
</vn-card>
</vn-vertical>
<vn-card class="summary ticketSummary" pad-medium>
<vn-vertical margin-medium>
<vn-auto>
<h5 text-center pad-small-v class="title">{{$ctrl.summary.id}} - {{$ctrl.summary.client.name}} - {{$ctrl.summary.client.salesPerson.id}}</h5>
</vn-auto>
<vn-horizontal class="ticketSummary__data" pad-medium-v>
<vn-one>
<vn-label-value label="Id"
value="{{::$ctrl.summary.id}}">
</vn-label-value>
<vn-label-value label="Nickname"
value="{{::$ctrl.summary.address.nickname}}">
</vn-label-value>
<vn-label-value label="Confirmed"
value="{{::$ctrl.summary.isConfirmed}}">
</vn-label-value>
<vn-label-value label="Warehouse"
value="{{::$ctrl.summary.sourceApp}}">
</vn-label-value>
</vn-one>
<vn-one>
<vn-label-value label="Created"
value="{{::$ctrl.summary.created | date: 'dd/MM/yyyy HH:mm'}}">
</vn-label-value>
<vn-label-value label="Confirmed"
value="{{::$ctrl.summary.confirmed | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Address"
value="{{::$ctrl.formattedAddress}}">
<vn-label-value label="Phone"
value="{{::$ctrl.summary.address.phone}}">
</vn-label-value>
</vn-one>
<vn-one>
<vn-label-value label="{{'Notes'}}"
value="{{::$ctrl.summary.note}}">
</vn-label-value>
</vn-one>
<vn-one class="ticketSummary__taxes">
<section>
<p><vn-label translate>Subtotal</vn-label> {{::$ctrl.summary.subTotal | currency:' €':2}}</p>
<p><vn-label translate>VAT</vn-label> {{::$ctrl.summary.VAT | currency:' €':2}}</p>
<p><vn-label><strong>Total</strong></vn-label> <strong>{{::$ctrl.summary.total | currency:' €':2}}</strong></p>
</section>
</vn-one>
</vn-horizontal>
<vn-horizontal>
<table class="vn-grid">
<thead>
<tr>
<th></th>
<th number translate>Item</th>
<th translate>Description</th>
<th number translate>Quantity</th>
<th number translate>Price</th>
<th number translate>Amount</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in $ctrl.summary.rows track by row.id">
<td>
<vn-icon
ng-show="row.visible || row.available"
orange
icon="warning"
vn-tooltip="Visible: {{::row.visible || 0}} <br> {{::$ctrl.translate.instant('Available')}} {{::row.available || 0}}">
</vn-icon>
<vn-icon ng-show="row.reserved" icon="icon-reserva"></vn-icon>
</td>
<td number>
<span
ng-click="$ctrl.showDescriptor($event, row)"
class="link" pointer>
{{("000000"+row.itemFk).slice(-6)}}
</span>
</td>
<td><vn-fetched-tags concept="row.item.name" tags="row.item.tags"/></td>
<td number>{{::row.quantity}}</td>
<td number>{{::row.price | currency:'€':2}}</td>
<td number>{{::row.quantity * row.price | currency:'€':2}}</td>
</tr>
<tr ng-if="!$ctrl.summary.rows" class="list list-element">
<td colspan="8" style="text-align: center" translate>No results</td>
</tr>
</tbody>
</table>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-item-descriptor-popover vn-id="descriptor"
quicklinks="$ctrl.quicklinks">
</vn-item-descriptor-popover>

View File

@ -1,12 +1,57 @@
import ngModule from '../module';
import './style.scss';
class Controller {
constructor($http) {
constructor($scope, $http, $state) {
this.$scope = $scope;
this.$http = $http;
this.$state = $state;
this.order = {};
}
setSummary() {
this.$http.get(`/order/api/Orders/${this.order.id}/summary`).then(res => {
if (res && res.data) {
this.summary = res.data;
}
});
}
get formattedAddress() {
if (!this.summary) return;
let address = this.summary.address;
let province = address.province ? `(${address.province.name})` : '';
return `${address.street} - ${address.city} ${province}`;
}
$onChanges() {
if (this.order && this.order.id)
this.setSummary();
}
showDescriptor(event, item) {
this.quicklinks = {
btnThree: {
icon: 'icon-transaction',
state: `item.card.diary({
id: ${item.id},
})`,
tooltip: 'Item diary'
}
};
this.$scope.descriptor.itemFk = item.id;
this.$scope.descriptor.parent = event.target;
this.$scope.descriptor.show();
}
onDescriptorLoad() {
this.$scope.popover.relocate();
}
}
Controller.$inject = ['$http'];
Controller.$inject = ['$scope', '$http', '$state'];
ngModule.component('vnOrderSummary', {
template: require('./index.html'),

View File

@ -0,0 +1,50 @@
import './index';
describe('Order', () => {
describe('Component vnOrderSummary', () => {
let $componentController;
let controller;
let $httpBackend;
beforeEach(() => {
angular.mock.module('order');
});
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnOrderSummary');
controller.order = {id: 1};
}));
describe('getSummary()', () => {
it('should perform a GET query and define summary property', () => {
let res = {id: 1, nickname: 'Batman'};
$httpBackend.when('GET', `/order/api/Orders/1/summary`).respond(200, res);
$httpBackend.expect('GET', `/order/api/Orders/1/summary`);
controller.setSummary();
$httpBackend.flush();
expect(controller.summary).toBeDefined();
expect(controller.summary.nickname).toEqual('Batman');
});
});
describe('formattedAddress()', () => {
it('should return a full fromatted address with city and province', () => {
controller.summary = {
address: {
province: {
name: 'Gotham'
},
street: '1007 Mountain Drive',
city: 'Gotham'
}
};
expect(controller.formattedAddress).toEqual('1007 Mountain Drive - Gotham (Gotham)');
});
});
});
});

View File

@ -0,0 +1,25 @@
.ticketSummary {
.ticketSummary__data {
vn-one {
padding-right: 20px
}
vn-one:last-child {
padding-right: 0
}
}
.ticketSummary__notes {
max-width: 18em
}
.ticketSummary__taxes {
max-width: 15em;
& section {
border: 1px solid #CCC;
text-align: right;
padding: 10px
}
}
}

View File

@ -21,7 +21,7 @@ describe('Salix', () => {
describe('localBank() setter', () => {
it('should set window.localStorage.localBank and call showOk', () => {
spyOn(controller, 'showOk')
spyOn(controller, 'showOk');
controller.localBank = 4;
expect(window.localStorage.localBank).toBe('4');
@ -31,7 +31,7 @@ describe('Salix', () => {
describe('localWarehouse() setter', () => {
it('should set window.localStorage.localWarehouse and call showOk', () => {
spyOn(controller, 'showOk')
spyOn(controller, 'showOk');
controller.localWarehouse = 4;
expect(window.localStorage.localWarehouse).toBe('4');
@ -41,7 +41,7 @@ describe('Salix', () => {
describe('localCompany() setter', () => {
it('should set window.localStorage.localCompany and call showOk', () => {
spyOn(controller, 'showOk')
spyOn(controller, 'showOk');
controller.localCompany = 4;
expect(window.localStorage.localCompany).toBe('4');
@ -51,7 +51,7 @@ describe('Salix', () => {
describe('warehouseFk() setter', () => {
it('should set warehouse and call setUserConfig', () => {
spyOn(controller, 'setUserConfig')
spyOn(controller, 'setUserConfig');
controller.warehouseFk = 4;
expect(controller.warehouse).toBe(4);
@ -61,7 +61,7 @@ describe('Salix', () => {
describe('companyFk() setter', () => {
it('should set company and call setUserConfig', () => {
spyOn(controller, 'setUserConfig')
spyOn(controller, 'setUserConfig');
controller.companyFk = 4;
expect(controller.company).toBe(4);
@ -71,11 +71,11 @@ describe('Salix', () => {
describe('getUserConfig()', () => {
it('should make a query, set company and not set warehouse if its not in the response', () => {
$httpBackend.when('GET', `/api/UserConfigs/getUserConfig`).respond({response: {companyFk: 2}});
$httpBackend.when('GET', `/api/UserConfigs/getUserConfig`).respond({companyFk: 2});
$httpBackend.expect('GET', `/api/UserConfigs/getUserConfig`);
controller.getUserConfig();
$httpBackend.flush();
expect(controller.warehouse).toBeUndefined();
expect(controller.company).toEqual(2);
});
@ -89,7 +89,7 @@ describe('Salix', () => {
$httpBackend.expect('POST', `/api/UserConfigs/setUserConfig`, {companyFk: 1});
controller.setUserConfig('companyFk');
$httpBackend.flush();
expect(controller.showOk).toHaveBeenCalledWith();
});
});

View File

@ -26,11 +26,10 @@
<vn-tbody>
<vn-tr ng-repeat="expedition in expeditions">
<vn-td pad-medium-h style="width:30px;color:#FFA410;">
<i
pointer
class="material-icons"
vn-tooltip="Delete expedition"
ng-click="$ctrl.deleteExpedition(expedition)">delete</i>
<vn-icon-button icon="delete"
on-click="$ctrl.deleteExpedition(expedition)"
vn-tooltip="Delete expedition">
</vn-icon-button>
</vn-td>
<vn-td number>
<span

View File

@ -189,7 +189,7 @@ class Controller {
let sales = this.getCheckedLines();
this.$http.post(`/api/Sales/MoveToNewTicket`, {ticket: ticket, sales: sales}).then(res => {
let url = this.$state.href("ticket.card.sale", {id: res.data}, {absolute: true});
let url = this.$state.href("ticket.card.sale", {id: res.data.id}, {absolute: true});
window.open(url, '_blank');
this.$scope.transfer.hide();
this.$scope.model.refresh();

View File

@ -97,7 +97,7 @@ export default {
phoneInput: `${components.vnTextfield}[name="phone"]`,
mobileInput: `${components.vnTextfield}[name="mobile"]`,
defaultAddress: 'vn-client-address-index vn-horizontal:nth-child(2) div[name="street"]',
secondMakeDefaultStar: 'vn-client-address-index > vn-vertical > vn-card > div > vn-horizontal:nth-child(3) > vn-one > vn-horizontal > vn-none > i',
secondMakeDefaultStar: 'vn-client-address-index > vn-vertical > vn-card > div > vn-horizontal:nth-child(3) vn-icon-button[icon="star_border"]',
firstEditButton: `vn-client-address-index vn-icon-button[icon='edit']`,
secondEditButton: `vn-client-address-index vn-horizontal:nth-child(3) vn-icon-button[icon='edit']`,
activeCheckbox: `vn-check[label='Enabled'] > label > input`,
@ -291,7 +291,7 @@ export default {
},
ticketExpedition: {
expeditionButton: `vn-menu-item a[ui-sref="ticket.card.expedition"]`,
secondExpeditionRemoveButton: `vn-ticket-expedition vn-table div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(1) > i`,
secondExpeditionRemoveButton: `vn-ticket-expedition vn-table div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(1) > vn-icon-button[icon="delete"]`,
secondExpeditionText: `vn-ticket-expedition vn-table div > vn-tbody > vn-tr:nth-child(2)`
},
ticketPackages: {

View File

@ -7,7 +7,7 @@ describe('Client', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should access to the clients index by clicking the clients button', () => {

View File

@ -7,7 +7,7 @@ describe('Client', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('administrative');
});
it('should click on the Clients button of the top bar menu', () => {

View File

@ -6,7 +6,7 @@ describe('Client Edit fiscalData path', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('administrative');
});
it('should click on the Clients button of the top bar menu', () => {

View File

@ -7,7 +7,7 @@ describe('Client', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('administrative');
});
it('should click on the Clients button of the top bar menu', () => {

View File

@ -7,7 +7,7 @@ describe('Client', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should click on the Clients button of the top bar menu', () => {
@ -137,7 +137,7 @@ describe('Client', () => {
});
});
it(`should click on the active checkbox and receive an error to save it becouse it is the default address`, () => {
it(`should click on the active checkbox and receive an error to save it because it is the default address`, () => {
return nightmare
.waitToClick(selectors.clientAddresses.activeCheckbox)
.waitToClick(selectors.clientAddresses.saveButton)

View File

@ -6,7 +6,7 @@ describe('Client add address notes path', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should click on the Clients button of the top bar menu', () => {

View File

@ -7,7 +7,7 @@ describe('Client', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should click on the Clients button of the top bar menu', () => {

View File

@ -7,7 +7,7 @@ describe('Client', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should click on the Clients button of the top bar menu', () => {

View File

@ -7,7 +7,7 @@ describe('Client', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('salesAssistant');
});
it('should click on the Clients button of the top bar menu', () => {
@ -73,7 +73,7 @@ describe('Client', () => {
.getInnerText(selectors.clientCredit.firstCreditText)
.then(value => {
expect(value).toContain(999);
expect(value).toContain('developer');
expect(value).toContain('salesAssistant');
});
});
});

View File

@ -7,7 +7,7 @@ describe('Client', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should click on the Clients button of the top bar menu', () => {

View File

@ -7,7 +7,7 @@ describe('Client', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('salesPerson');
});
it('should click on the Clients button of the top bar menu', () => {

View File

@ -7,7 +7,7 @@ describe('Client', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should click on the Clients button of the top bar menu', () => {

View File

@ -7,7 +7,7 @@ describe('Item', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should access to the items index by clicking the items button', () => {

View File

@ -7,7 +7,7 @@ describe('Item', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('buyer');
});
it('should access to the items index by clicking the items button', () => {

View File

@ -7,7 +7,7 @@ describe('Item', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('buyer');
});
it('should access to the items index by clicking the items button', () => {

View File

@ -7,7 +7,7 @@ describe('Item', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('buyer');
});
it('should access to the items index by clicking the items button', () => {

View File

@ -7,7 +7,7 @@ describe('Item', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('buyer');
});
it('should access to the items index by clicking the items button', () => {

View File

@ -7,7 +7,7 @@ describe('Item', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('buyer');
});
it('should access to the items index by clicking the items button', () => {

View File

@ -7,7 +7,7 @@ describe('Item', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('buyer');
});
it('should access to the items index by clicking the items button', () => {

View File

@ -6,7 +6,7 @@ describe('Item', () => {
describe('Create path', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('buyer');
});
it('should access to the items index by clicking the items button', () => {

View File

@ -7,7 +7,7 @@ describe('Ticket', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should access to the tickets index by clicking the tickets button', () => {

View File

@ -7,7 +7,7 @@ describe('Ticket', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('production');
});
it('should access to the tickets index by clicking the tickets button', () => {

View File

@ -6,7 +6,7 @@ describe('Ticket List sale path', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should click on the Tickets button of the top bar menu', () => {

View File

@ -6,7 +6,7 @@ describe('Ticket Create packages path', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should click on the Tickets button of the top bar menu', () => {

View File

@ -7,7 +7,7 @@ describe('Ticket', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('production');
});
it('should click on the Tickets button of the top bar menu', () => {

View File

@ -7,7 +7,7 @@ describe('Ticket', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should click on the Tickets button of the top bar menu', () => {

View File

@ -265,7 +265,7 @@ describe('Ticket Edit sale path', () => {
.waitToClick(selectors.ticketSales.firstSaleCheckbox)
.waitToClick(selectors.ticketSales.moreMenuButton)
.waitToClick(selectors.ticketSales.moreMenuCreateClaim)
.waitForLogin('Developer')
.waitForLogin('salesPerson')
.waitToClick(selectors.globalItems.applicationsMenuButton)
.wait(selectors.globalItems.applicationsMenuVisible)
.waitToClick(selectors.globalItems.claimsButton)

View File

@ -6,7 +6,7 @@ describe('create client path', () => {
beforeAll(() => {
return nightmare
.waitForLogin('developer');
.waitForLogin('employee');
});
it('should access to the clients index by clicking the clients button', () => {

View File

@ -48,7 +48,7 @@ xdescribe('Auth routes', () => {
describe('when the user exists and the password is correct', () => {
it('should login and return the token', done => {
req.body.user = 'developer';
req.body.user = 'employee';
req.body.password = 'nightmare';
res.json = response => {
expect(response.token).toBeDefined();
@ -58,7 +58,7 @@ xdescribe('Auth routes', () => {
});
it('should define the url to continue upon login', done => {
req.body.user = 'developer';
req.body.user = 'employee';
req.body.password = 'nightmare';
req.body.location = 'http://localhost:5000/auth/?apiKey=salix&continue="continueURL"';
res.json = response => {
@ -69,7 +69,7 @@ xdescribe('Auth routes', () => {
});
it('should define the loginUrl upon login', done => {
req.body.user = 'developer';
req.body.user = 'employee';
req.body.password = 'nightmare';
req.body.location = 'http://localhost:5000/auth/?apiKey=salix';
res.json = response => {

View File

@ -19,12 +19,12 @@ module.exports = Self => {
}
});
async function addSalesToTicket(salesToRefund, newRefundTicket, transaction) {
async function addSalesToTicket(salesToRefund, ticketFk, transaction) {
let formatedSales = [];
salesToRefund.forEach(sale => {
let formatedSale = {
itemFk: sale.sale().itemFk,
ticketFk: newRefundTicket,
ticketFk: ticketFk,
concept: sale.sale().concept,
quantity: -Math.abs(sale.quantity),
price: sale.sale().price,
@ -113,14 +113,30 @@ module.exports = Self => {
try {
let newRefundTicket = await models.Ticket.new(params, {transaction: transaction});
let observation = {description: `Reclama ticket: ${claim.ticketFk}`, ticketFk: newRefundTicket, observationTypeFk: obsevationType.id};
let observation = {
description: `Reclama ticket: ${claim.ticketFk}`,
ticketFk: newRefundTicket.id,
observationTypeFk: obsevationType.id
};
await saveObservation(observation, {transaction: transaction});
await models.TicketTracking.create({ticketFk: newRefundTicket, stateFk: state.id, workerFk: worker.id}, {transaction: transaction});
await models.TicketTracking.create({
ticketFk: newRefundTicket.id,
stateFk: state.id,
workerFk: worker.id
}, {transaction: transaction});
let salesToRefund = await models.ClaimBeginning.find(salesFilter);
let createdSales = await addSalesToTicket(salesToRefund, newRefundTicket, {transaction: transaction});
let createdSales = await addSalesToTicket(salesToRefund, newRefundTicket.id, {transaction: transaction});
insertIntoClaimEnd(createdSales, id, worker.id, {transaction: transaction});
await Self.rawSql('CALL vn.ticketCalculateClon(?, ?)', [newRefundTicket, claim.ticketFk], {transaction: transaction});
await Self.rawSql('CALL vn.ticketCalculateClon(?, ?)', [
newRefundTicket.id, claim.ticketFk
], {transaction: transaction});
await transaction.commit();
return newRefundTicket;
} catch (e) {
await transaction.rollback();

View File

@ -1,16 +1,16 @@
const app = require(`${servicesDir}/claim/server/server`);
describe('claimBeginning', () => {
let ticketId;
let ticket;
let refundTicketObservations;
let refundTicketSales;
let salesInsertedInClaimEnd;
afterAll(async() => {
let promises = [];
promises.push(app.models.Ticket.destroyById(ticketId));
promises.push(app.models.Ticket.destroyById(ticket.id));
promises.push(app.models.Ticket.rawSql(`DELETE FROM vn.orderTicket WHERE ticketFk ='${ticketId}';`));
promises.push(app.models.Ticket.rawSql(`DELETE FROM vn.orderTicket WHERE ticketFk ='${ticket.id}';`));
await Promise.all(promises);
});
@ -19,11 +19,13 @@ describe('claimBeginning', () => {
it('should create a new ticket with negative sales, save an observation, update the state and insert the negative sales into claimEnd', async() => {
let ctxOfSalesAssistant = {req: {accessToken: {userId: 21}}};
let claimId = 1;
ticketId = await app.models.ClaimBeginning.importToNewRefundTicket(ctxOfSalesAssistant, claimId);
await app.models.Ticket.findById(ticketId);
refundTicketSales = await app.models.Sale.find({where: {ticketFk: ticketId}});
refundTicketObservations = await app.models.TicketObservation.find({where: {ticketFk: ticketId}});
let refundTicketState = await app.models.TicketState.findById(ticketId);
ticket = await app.models.ClaimBeginning.importToNewRefundTicket(ctxOfSalesAssistant, claimId);
await app.models.Ticket.findById(ticket.id);
refundTicketSales = await app.models.Sale.find({where: {ticketFk: ticket.id}});
refundTicketObservations = await app.models.TicketObservation.find({where: {ticketFk: ticket.id}});
let refundTicketState = await app.models.TicketState.findById(ticket.id);
salesInsertedInClaimEnd = await app.models.ClaimEnd.find({where: {claimFk: claimId}});
expect(refundTicketSales.length).toEqual(2);

View File

@ -133,7 +133,7 @@ module.exports = Self => {
}
async function createTicket(params, transaction) {
return await Self.app.models.Ticket.new({
let ticket = await Self.app.models.Ticket.new({
shipped: new Date(),
landed: new Date(),
clientFk: params.clientFk,
@ -142,6 +142,8 @@ module.exports = Self => {
addressFk: params.addressFk,
userId: params.userId
}, {transaction: transaction});
return ticket.id;
}
async function sendMessage(ctx, params, transaction) {

View File

@ -36,7 +36,7 @@ module.exports = Self => {
let notModifiable = ['responsibility', 'isChargedToMana'];
let changedFields = diff(oldClaim, params);
let changedFieldsPicked = pick(changedFields, notModifiable);
let statesViables = ['Gestionado', 'Pendiente'];
let statesViables = ['Gestionado', 'Pendiente', 'Anulado'];
let oldState = await models.ClaimState.findOne({where: {id: oldClaim.claimStateFk}});
let newState = await models.ClaimState.findOne({where: {id: params.claimStateFk}});
let canChangeState = statesViables.includes(oldState.description)

View File

@ -0,0 +1,32 @@
{
"name": "Defaulter",
"description": "defaulters client",
"base": "VnModel",
"options": {
"mysql": {
"table": "defaulter",
"database": "vn"
}
},
"properties": {
"created": {
"type": "Date"
},
"amount": {
"type": "Number"
},
"defaulterSinced": {
"type": "Number"
},
"hasChanged": {
"type": "Number"
}
},
"relations": {
"client": {
"type": "belongsTo",
"model": "Client",
"foreignKey": "clientFk"
}
}
}

View File

@ -52,5 +52,8 @@
},
"TpvResponse": {
"dataSource": "hedera"
},
"Defaulter": {
"dataSource": "vn"
}
}

View File

@ -3,24 +3,30 @@ export MYSQL_PWD=root
if [ -d /data/mysql ]; then
cp -R /data/mysql /var/lib
echo "Restored database to default state"
echo "[INFO] -> Restored database to default state"
else
# Dump structure
for file in dump/*-*.sql; do
echo "Imported $file"
mysql -u root -fc < $file
done
echo "[INFO] -> Imported ./dump/truncateAll.sql"
mysql -u root -f < ./dump/truncateAll.sql
echo "[INFO] -> Imported ./dump/structure.sql"
mysql -u root -f < ./dump/structure.sql
echo "[INFO] -> Imported ./dump/mysqlPlugins.sql"
mysql -u root -f < ./dump/mysqlPlugins.sql
# Import changes
for file in changes/*/*.sql; do
echo "Imported $file"
echo "[INFO] -> Imported ./$file"
mysql -u root -fc < $file
done
# Import fixtures
echo "Imported fixtures.sql"
mysql -u root -f < dump/fixtures.sql
echo "[INFO] -> Imported ./dump/dumpedFixtures.sql"
mysql -u root -f < ./dump/dumpedFixtures.sql
echo "[INFO] -> Imported ./dump/fixtures.sql"
mysql -u root -f < ./dump/fixtures.sql
# Copy dumpted data to volume
cp -R /var/lib/mysql /data
echo "[INFO] -> Dumped database"
fi

View File

@ -5,7 +5,6 @@ DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticketCalculateClon`(IN vTicketNew INT, vTicketOld INT)
BEGIN
/*
* @vTicketNew id del nuevo ticket clonado
* @vTicketOld id ticket original, a partir del qual se clonara el nuevo
@ -41,7 +40,7 @@ BEGIN
SELECT vWarehouse warehouseFk,NULL available,s.itemFk, bu.buyFk
FROM sale s
LEFT JOIN tmp.buyUltimate bu ON bu.itemFk = s.itemFk
WHERE s.ticketFk = vTicketNew GROUP BY s.itemFk;
WHERE s.ticketFk = vTicketOld GROUP BY s.itemFk;
CALL ticketComponentCalculate(vAddress,vAgencyMode);

View File

@ -6,9 +6,9 @@ VALUES
(107, 'ItemNiche', '*', 'WRITE', 'ALLOW', 'ROLE', 'marketingBoss'),
(108, 'ItemPlacement', '*', 'WRITE', 'ALLOW', 'ROLE', 'marketingBoss'),
(109, 'UserConfig', '*', '*', 'ALLOW', 'ROLE', 'employee'),
(110, 'Bank', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
(110, 'Bank', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
(111, 'ClientLog', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
(112, 'Defaulter', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
UPDATE salix.ACL
SET model='ItemTag', property='*', accessType='WRITE', permission='ALLOW', principalType='ROLE', principalId='marketingBoss'
WHERE id=52;

View File

@ -0,0 +1,14 @@
USE `vn`;
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `vn`.`defaulter` AS
SELECT
`d`.`client` AS `clientFk`,
`d`.`date` AS `created`,
`d`.`amount` AS `amount`,
`d`.`defaulterSince` AS `defaulterSinced`,
`d`.`hasChanged` AS `hasChanged`
FROM
`bi`.`defaulters` `d`;

View File

@ -207,35 +207,35 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`)
INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `isDefaultAddress`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`)
VALUES
(101, 'address 01', 'Somewhere in Thailand', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(102, 'address 02', 'Somewhere in Poland', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(103, 'address 03', 'Somewhere in Japan', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(104, 'address 04', 'Somewhere in Spain', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(105, 'address 05', 'Somewhere in Potugal', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(106, 'address 06', 'Somewhere in UK', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(107, 'address 07', 'Somewhere in Valencia', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(108, 'address 08', 'Somewhere in Silla', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(109, 'address 09', 'Somewhere in London', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(110, 'address 10', 'Somewhere in Algemesi', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(111, 'address 11', 'Somewhere in Carlet', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(112, 'address 12', 'Somewhere in Campanar', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(113, 'address 13', 'Somewhere in Malilla', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(114, 'address 14', 'Somewhere in France', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(115, 'address 15', 'Somewhere in Birmingham', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(116, 'address 16', 'Somewhere in Scotland', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(117, 'address 17', 'Somewhere in nowhere', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(118, 'address 18', 'Somewhere over the rainbow', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(119, 'address 19', 'Somewhere in Alberic', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(120, 'address 20', 'Somewhere in Montortal', 'Silla', 46460, 1, NULL, NULL, 1, 0, 109, 2, NULL, NULL, 0),
(121, 'address 21', 'the bat cave', 'Silla', 46460, 1, NULL, NULL, 1, 0, 101, 2, NULL, NULL, 0),
(122, 'address 22', 'NY roofs', 'Silla', 46460, 1, NULL, NULL, 1, 0, 102, 2, NULL, NULL, 0),
(123, 'address 23', 'The phone box', 'Silla', 46460, 1, NULL, NULL, 1, 0, 103, 2, NULL, NULL, 0),
(124, 'address 24', 'Stark tower', 'Silla', 46460, 1, NULL, NULL, 1, 0, 104, 2, NULL, NULL, 0),
(125, 'address 25', 'The plastic cell', 'Silla', 46460, 1, NULL, NULL, 1, 0, 105, 2, NULL, NULL, 0),
(126, 'address 26', 'Many places', 'Silla', 46460, 1, NULL, NULL, 1, 0, 106, 2, NULL, NULL, 0),
(127, 'address 27', 'Your pocket', 'Silla', 46460, 1, NULL, NULL, 1, 0, 107, 2, NULL, NULL, 0),
(128, 'address 28', 'Cerebro', 'Silla', 46460, 1, NULL, NULL, 1, 0, 108, 2, NULL, NULL, 0),
(129, 'address 29', 'Luke Cages Bar', 'Silla', 46460, 1, NULL, NULL, 1, 0, 110, 2, NULL, NULL, 0);
(101, 'address 01', 'Somewhere in Thailand', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(102, 'address 02', 'Somewhere in Poland', 'Silla', 46460, 1, 3333333333, 444444444, 1, 0, 109, 2, NULL, NULL, 0),
(103, 'address 03', 'Somewhere in Japan', 'Silla', 46460, 1, 3333333333, 444444444, 1, 0, 109, 2, NULL, NULL, 0),
(104, 'address 04', 'Somewhere in Spain', 'Silla', 46460, 1, 3333333333, 444444444, 1, 0, 109, 2, NULL, NULL, 0),
(105, 'address 05', 'Somewhere in Potugal', 'Silla', 46460, 1, 5555555555, 666666666, 1, 0, 109, 2, NULL, NULL, 0),
(106, 'address 06', 'Somewhere in UK', 'Silla', 46460, 1, 5555555555, 666666666, 1, 0, 109, 2, NULL, NULL, 0),
(107, 'address 07', 'Somewhere in Valencia', 'Silla', 46460, 1, 5555555555, 666666666, 1, 0, 109, 2, NULL, NULL, 0),
(108, 'address 08', 'Somewhere in Silla', 'Silla', 46460, 1, 5555555555, 666666666, 1, 0, 109, 2, NULL, NULL, 0),
(109, 'address 09', 'Somewhere in London', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(110, 'address 10', 'Somewhere in Algemesi', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(111, 'address 11', 'Somewhere in Carlet', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(112, 'address 12', 'Somewhere in Campanar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(113, 'address 13', 'Somewhere in Malilla', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(114, 'address 14', 'Somewhere in France', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(115, 'address 15', 'Somewhere in Birmingham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(116, 'address 16', 'Somewhere in Scotland', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(117, 'address 17', 'Somewhere in nowhere', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(118, 'address 18', 'Somewhere over the rainbow', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(119, 'address 19', 'Somewhere in Alberic', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(120, 'address 20', 'Somewhere in Montortal', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(121, 'address 21', 'the bat cave', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 101, 2, NULL, NULL, 0),
(122, 'address 22', 'NY roofs', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 102, 2, NULL, NULL, 0),
(123, 'address 23', 'The phone box', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 103, 2, NULL, NULL, 0),
(124, 'address 24', 'Stark tower', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 104, 2, NULL, NULL, 0),
(125, 'address 25', 'The plastic cell', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 105, 2, NULL, NULL, 0),
(126, 'address 26', 'Many places', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 106, 2, NULL, NULL, 0),
(127, 'address 27', 'Your pocket', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 107, 2, NULL, NULL, 0),
(128, 'address 28', 'Cerebro', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 108, 2, NULL, NULL, 0),
(129, 'address 29', 'Luke Cages Bar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 110, 2, NULL, NULL, 0);
INSERT INTO `vn`.`clientCredit`(`id`, `clientFk`, `workerFk`, `amount`, `created`)
VALUES
@ -529,12 +529,12 @@ INSERT INTO `vn`.`ticketPackaging`(`id`, `ticketFk`, `packagingFk`, `quantity`,
INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `price`, `discount`, `reserved`, `isPicked`, `created`)
VALUES
( 1, 1, 1 , 'Gem of Time', 5, 9.10, 0, 0, 0, CURDATE()),
( 2, 2, 1 , 'Gem of Mind', 10, 1.07, 0, 0, 0, CURDATE()),
( 3, 1, 1 , 'Gem of Time', 2, 9.10, 0, 0, 0, CURDATE()),
( 4, 4, 1 , 'Mark I' , 20, 3.06, 0, 0, 0, CURDATE()),
( 5, 1, 2 , 'Gem of Time', 10, 9.10, 0, 0, 0, CURDATE()),
( 6, 1, 3 , 'Gem of Time', 15, 6.50, 0, 0, 0, CURDATE()),
( 1, 1, 1 , 'Gem of Time', 5, 9.10, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
( 2, 2, 1 , 'Gem of Mind', 10, 1.07, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
( 3, 1, 1 , 'Gem of Time', 2, 9.10, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
( 4, 4, 1 , 'Mark I' , 20, 3.06, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
( 5, 1, 2 , 'Gem of Time', 10, 9.10, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -10 DAY)),
( 6, 1, 3 , 'Gem of Time', 15, 6.50, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -5 DAY)),
( 7, 2, 11, 'Gem of Mind', 15, 1.30, 0, 0, 0, CURDATE()),
( 8, 4, 11, 'Mark I' , 10, 3.26, 0, 0, 0, CURDATE()),
( 9, 1, 16, 'Gem of Time', 5, 9.10, 0, 0, 0, CURDATE()),
@ -837,9 +837,79 @@ INSERT INTO `hedera`.`order`(`id`, `date_send`, `customer_id`, `delivery_method_
INSERT INTO `hedera`.`orderRow`(`id`, `orderFk`, `itemFk`, `warehouseFk`, `shipment`, `amount`, `price`, `rate`, `created`, `saleFk`)
VALUES
( 1, 1, 1, 1 , NULL , 9, 1.5 , 1, CURDATE(), 1),
( 2, 2, 2, 1 , NULL , 5, 1.23, 2, CURDATE(), 2),
( 3, 3, 3, 2 , CURDATE(), 3, 0.50, 3, CURDATE(), 3);
( 1, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 5, 9.10, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 1),
( 2, 1, 2, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 10, 1.07, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 2),
( 3, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 2, 9.10, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 3),
( 4, 1, 4, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 20, 3.06, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 4),
( 5, 2, 1, 1, DATE_ADD(CURDATE(), INTERVAL -10 DAY), 10, 9.10, 0, DATE_ADD(CURDATE(), INTERVAL -10 DAY), 5),
( 6, 3, 1, 2, DATE_ADD(CURDATE(), INTERVAL -5 DAY), 15, 6.50, 0, DATE_ADD(CURDATE(), INTERVAL -5 DAY), 6),
( 7, 11, 2, 1, CURDATE(), 15, 1.30, 0, CURDATE(), 7),
( 8, 11, 4, 1, CURDATE(), 10, 3.26, 0, CURDATE(), 8),
( 9, 16, 1, 1, CURDATE(), 5, 9.10, 0, CURDATE(), 9),
( 10, 16, 2, 1, CURDATE(), 10, 1.07, 0, CURDATE(), 10),
( 11, 16, 1, 1, CURDATE(), 2, 9.10, 0, CURDATE(), 11),
( 12, 16, 4, 1, CURDATE(), 20, 3.06, 0, CURDATE(), 12);
INSERT INTO `hedera`.`orderRowComponent`(`rowFk`, `componentFk`, `price`)
VALUES
( 1, 15, 0.58),
( 1, 23, 6.5),
( 1, 28, 20.72),
( 1, 29, -18.72),
( 1, 39, 0.02),
( 2, 15, 0.058),
( 2, 21, 0.002),
( 2, 28, 5.6),
( 2, 29, -4.6),
( 2, 39, 0.01),
( 3, 15, 0.58),
( 3, 23, 6.5),
( 3, 28, 20.72),
( 3, 29, -18.72),
( 3, 39, 0.02),
( 4, 15, 0.051),
( 4, 21, -0.001),
( 4, 28, 20.72),
( 4, 29, -19.72),
( 4, 37, 2),
( 4, 39, 0.01),
( 5, 15, 0.58),
( 5, 23, 6.5),
( 5, 28, 20.72),
( 5, 29, -18.72),
( 5, 39, 0.02),
( 6, 23, 6.5),
( 7, 15, 0.29),
( 7, 28, 5.6),
( 7, 29, -4.6),
( 7, 39, 0.01),
( 8, 15, 0.254),
( 8, 21, -0.004),
( 8, 28, 20.72),
( 8, 29, -19.72),
( 8, 37, 2),
( 8, 39, 0.01),
( 9, 15, 0.58),
( 9, 23, 6.5),
( 9, 28, 20.72),
( 9, 29, -18.72),
( 9, 39, 0.02),
( 10, 15, 0.058),
( 10, 21, 0.002),
( 10, 28, 5.6),
( 10, 29, -4.6),
( 10, 39, 0.01),
( 11, 15, 0.58),
( 11, 23, 6.5),
( 11, 28, 20.72),
( 11, 29, -18.72),
( 11, 39, 0.02),
( 12, 15, 0.051),
( 12, 22, -0.001),
( 12, 28, 20.72),
( 12, 29, -19.72),
( 12, 37, 2),
( 12, 39, 0.01);
INSERT INTO `vn`.`clientContact`(`id`, `clientFk`, `name`, `phone`)
VALUES

View File

@ -52,5 +52,8 @@
"Warehouse cannot be blank": "El almacén no puede quedar en blanco",
"Agency cannot be blank": "La agencia no puede quedar en blanco",
"You don't have enough privileges to do that": "No tienes permisos para para hacer esto",
"This address doesn't exist": "This address doesn't exist"
"This address doesn't exist": "Este consignatario no existe",
"The sales of this ticket can't be modified": "Los movimientos de este tiquet no pueden ser modificadas",
"You can't create an order for a inactive client": "You can't create an order for a inactive client",
"You can't create an order for a client that doesn't has tax data verified": "You can't create an order for a client that doesn't has tax data verified"
}

View File

@ -51,14 +51,26 @@ module.exports = Self => {
userId: userId
};
let newTicket = await model.Ticket.new(newTicketParams);
let transaction = await Self.beginTransaction({});
try {
let newTicket = await model.Ticket.new(newTicketParams, {transaction: transaction});
let promises = [];
for (let i = 0; i < params.sales.length; i++) {
promises.push(model.Sale.update({id: params.sales[i].id}, {ticketFk: newTicket}));
let selectedSalesId = [];
params.sales.forEach(sale => {
selectedSalesId.push(sale.id);
});
await model.Sale.updateAll(
{id: {inq: selectedSalesId}},
{ticketFk: newTicket.id},
{transaction: transaction});
await transaction.commit();
return newTicket;
} catch (e) {
await transaction.rollback();
throw e;
}
await Promise.all(promises);
return newTicket;
};
};

View File

@ -1,6 +1,6 @@
const app = require(`${servicesDir}/ticket/server/server`);
describe('ticket listSaleTracking()', () => {
xdescribe('ticket listSaleTracking()', () => {
it('should throw an error if the ticket is not editable and the user isnt production', async() => {
let ctx = {req: {accessToken: {userId: 110}}};
let params = {ticketFk: 2, stateFk: 3};

View File

@ -22,7 +22,12 @@ module.exports = Self => {
});
Self.new = async(params, transaction) => {
let existsAddress = await Self.app.models.Address.findOne({where: {id: params.addressFk, clientFk: params.clientFk}});
let existsAddress = await Self.app.models.Address.findOne({
where: {
id: params.addressFk,
clientFk: params.clientFk}
});
if (!existsAddress)
throw new UserError(`This address doesn't exist`);
@ -40,6 +45,8 @@ module.exports = Self => {
params.userId
], transaction);
return result[1][0].newTicketId;
return await Self.findOne({
where: {id: result[1][0].newTicketId}
}, transaction);
};
};

View File

@ -1,11 +1,11 @@
const app = require(`${servicesDir}/ticket/server/server`);
describe('ticket new()', () => {
let ticketId;
let ticket;
let today = new Date();
afterAll(async() => {
await app.models.Ticket.destroyById(ticketId);
await app.models.Ticket.destroyById(ticket.id);
});
it('should throw an error if the address doesnt exist', async() => {
@ -33,10 +33,10 @@ describe('ticket new()', () => {
landed: today
};
ticketId = await app.models.Ticket.new(params);
ticket = await app.models.Ticket.new(params);
let newestTicketIdInFixtures = 21;
expect(ticketId).toBeGreaterThan(newestTicketIdInFixtures);
expect(ticket.id).toBeGreaterThan(newestTicketIdInFixtures);
});
});

View File

@ -187,8 +187,8 @@ module.exports = function(Self) {
});
};
Self.rawStmt = function(stmt) {
return this.rawSql(stmt.sql, stmt.params);
Self.rawStmt = function(stmt, options = {}) {
return this.rawSql(stmt.sql, stmt.params, options);
};
Self.escapeName = function(name) {

View File

@ -23,7 +23,7 @@ module.exports = Self => {
let query = `CALL hedera.orderGetTax(?);
SELECT * FROM tmp.orderTax;`;
let res = await Self.rawSql(query, [orderFk]);
let [taxes] = res[1];
let taxes = res[1];
return taxes;
};

View File

@ -23,6 +23,6 @@ module.exports = Self => {
let query = `SELECT hedera.orderGetTotal(?) total;`;
let [total] = await Self.rawSql(query, [orderFk]);
return total;
return total.total;
};
};

View File

@ -0,0 +1,31 @@
module.exports = Self => {
Self.remoteMethod('getVAT', {
description: 'Returns order total VAT',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'order id',
http: {source: 'path'}
}],
returns: {
type: 'number',
root: true
},
http: {
path: `/:id/getVAT`,
verb: 'GET'
}
});
Self.getVAT = async orderId => {
let totalTax = 0.00;
let taxes = await Self.app.models.Order.getTaxes(orderId);
taxes.forEach(tax => {
totalTax += tax.tax;
});
return Math.round(totalTax * 100) / 100;
};
};

View File

@ -20,8 +20,11 @@ module.exports = Self => {
});
Self.new = async params => {
let clientFkByAddress = await Self.app.models.Address.findOne({where: {id: params.addressFk}, fields: 'clientFk'});
let clientFk = clientFkByAddress.clientFk;
let address = await Self.app.models.Address.findOne({
where: {id: params.addressFk},
fields: 'clientFk'
});
let clientFk = address.clientFk;
let client = await Self.app.models.Client.findOne({
where: {id: clientFk},

View File

@ -4,18 +4,19 @@ describe('order getTaxes()', () => {
it('should call the getTaxes method and return undefined if its called with a string', async() => {
let result = await app.models.Order.getTaxes('pepinillos');
expect(result).toEqual(undefined);
expect(result.length).toEqual(0);
});
it('should call the getTaxes method and return undefined if its called with unknown id', async() => {
let result = await app.models.Order.getTaxes(99999999999999999999999);
expect(result).toEqual(undefined);
expect(result.length).toEqual(0);
});
it('should call the getTaxes method and return the taxes if its called with a known id', async() => {
let result = await app.models.Order.getTaxes(1);
expect(result.tax).toEqual(0.95);
expect(result[0].tax).toEqual(9.49);
expect(result.length).toEqual(1);
});
});

View File

@ -4,7 +4,7 @@ describe('order getTotalVolume()', () => {
it('should return the total', async() => {
let result = await app.models.Order.getTotalVolume(1);
expect(result.totalVolume).toEqual(0.072);
expect(result.totalVolume).toEqual(0.078);
expect(result.totalBoxes).toBeFalsy();
});
});

View File

@ -0,0 +1,17 @@
const app = require(`${servicesDir}/order/server/server`);
describe('order getVAT()', () => {
it('should call the getVAT method and return the response', async() => {
await app.models.Order.getVAT(1)
.then(response => {
expect(response).toEqual(9.49);
});
});
it(`should call the getVAT method and return zero if doesn't have lines`, async() => {
await app.models.Order.getVAT(13)
.then(response => {
expect(response).toEqual(0);
});
});
});

View File

@ -4,6 +4,6 @@ describe('order getVolumes()', () => {
it('should return the volumes of a given order id', async() => {
let [result] = await app.models.Order.getVolumes(1);
expect(result.volume).toEqual(0.072);
expect(result.volume).toEqual(0.04);
});
});

View File

@ -0,0 +1,36 @@
const app = require(`${servicesDir}/order/server/server`);
describe('order summary()', () => {
it('should return a summary object containing data from 1 order', async() => {
let result = await app.models.Order.summary(1);
expect(result.id).toEqual(1);
expect(result.clientFk).toEqual(101);
});
it('should return a summary object containing sales from 1 order', async() => {
let result = await app.models.Order.summary(1);
expect(result.rows.length).toEqual(3);
});
it('should return a summary object containing subTotal for 1 order', async() => {
let result = await app.models.Order.summary(1);
expect(Math.round(result.subTotal * 100) / 100).toEqual(135.60);
});
it('should return a summary object containing VAT for 1 order', async() => {
let result = await app.models.Order.summary(1);
expect(Math.round(result.VAT * 100) / 100).toEqual(9.49);
});
it('should return a summary object containing total for 1 order', async() => {
let result = await app.models.Order.summary(1);
let total = result.subTotal + result.VAT;
let expectedTotal = Math.round(total * 100) / 100;
expect(result.total).toEqual(expectedTotal);
});
});

View File

@ -0,0 +1,87 @@
module.exports = Self => {
Self.remoteMethod('summary', {
description: 'Returns a summary for a given order id',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'order id',
http: {source: 'path'}
}],
returns: {
type: [this.modelName],
root: true
},
http: {
path: `/:id/summary`,
verb: 'GET'
}
});
Self.summary = async orderId => {
let models = Self.app.models;
let summary = await getOrderData(Self, orderId);
summary.subTotal = getSubTotal(summary.rows);
summary.VAT = await models.Order.getVAT(orderId);
summary.total = await models.Order.getTotal(orderId);
return summary;
};
async function getOrderData(Self, orderId) {
let filter = {
include: [
{relation: 'agencyMode', scope: {fields: ['name']}},
{
relation: 'client',
scope: {
fields: ['salesPersonFk', 'name'],
include: {
relation: 'salesPerson',
fields: ['firstName', 'name']
}
}
},
{
relation: 'address',
scope: {
fields: ['street', 'city', 'provinceFk', 'phone', 'nickname'],
include: {
relation: 'province',
scope: {
fields: ['name']
}
}
}
},
{
relation: 'rows',
scope: {
include: {
relation: 'item',
scope: {
include: {
relation: 'tags'
}
}
}
}
}
],
where: {id: orderId}
};
return await Self.findOne(filter);
}
function getSubTotal(rows) {
let subTotal = 0.00;
rows().forEach(row => {
subTotal += row.quantity * row.price;
});
return subTotal;
}
};

View File

@ -6,4 +6,6 @@ module.exports = Self => {
require('../methods/order/isEditable')(Self);
require('../methods/order/getTotal')(Self);
require('../methods/order/catalogFilter')(Self);
require('../methods/order/summary')(Self);
require('../methods/order/getVAT')(Self);
};

View File

@ -1,6 +1,6 @@
const app = require(`${servicesDir}/ticket/server/server`);
describe('ticket listSaleTracking()', () => {
xdescribe('ticket listSaleTracking()', () => {
it('should call the listSaleTracking method and return the response', async() => {
let filter = {where: {ticketFk: 1}};
let result = await app.models.SaleTracking.listSaleTracking(filter);