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

This commit is contained in:
Javi Gallego 2018-10-11 09:15:14 +02:00
commit bf8ed25c06
36 changed files with 591 additions and 102 deletions

View File

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

View File

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

View File

@ -48,7 +48,7 @@ describe('Order', () => {
describe('getTotal()', () => { describe('getTotal()', () => {
it(`should make a query and save the data in order.total`, () => { 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(); controller.getTotal();
$httpBackend.flush(); $httpBackend.flush();

View File

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

View File

@ -20,7 +20,7 @@
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
<vn-tr ng-repeat="order in orders" class="clickable" <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>{{::order.id}}</vn-td>
<vn-td> <vn-td>
<span class="link" ng-click="$ctrl.showDescriptor($event, order.clientFk)"> <span class="link" ng-click="$ctrl.showDescriptor($event, order.clientFk)">
@ -36,6 +36,13 @@
<vn-td>{{::order.sourceApp}}</vn-td> <vn-td>{{::order.sourceApp}}</vn-td>
<vn-td>{{::order.created | date:'dd/MM/yyyy HH:mm'}}</vn-td> <vn-td>{{::order.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
<vn-td>{{::order.company.code}}</vn-td> <vn-td>{{::order.company.code}}</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-tr>
</vn-tbody> </vn-tbody>
<vn-empty-rows ng-if="model.data.length === 0" translate> <vn-empty-rows ng-if="model.data.length === 0" translate>
@ -48,7 +55,13 @@
scroll-selector="ui-view"> scroll-selector="ui-view">
</vn-pagination> </vn-pagination>
</div> </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> <vn-float-button icon="add"></vn-float-button>
</a> </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) { showDescriptor(event, clientFk) {
this.$scope.descriptor.clientFk = clientFk; this.$scope.descriptor.clientFk = clientFk;
this.$scope.descriptor.parent = event.target; this.$scope.descriptor.parent = event.target;
@ -50,11 +30,11 @@ export default class Controller {
this.$scope.popover.relocate(); this.$scope.popover.relocate();
} }
preview(event, ticket) { preview(event, order) {
event.preventDefault(); event.preventDefault();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
this.$scope.dialogSummaryTicket.show(); this.$scope.orderSummaryDialog.show();
this.ticketSelected = ticket; this.order = order;
} }
} }

View File

@ -48,10 +48,10 @@ class Controller {
} }
getVAT() { 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.$http.get(query).then(res => {
this.VAT = res.data.tax; this.VAT = res.data;
}); });
} }

View File

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

View File

@ -1,9 +1,94 @@
<vn-vertical vn-one> <vn-card class="summary ticketSummary" pad-medium>
<vn-card class="summary" pad-medium> <vn-vertical margin-medium>
<vn-vertical margin-medium> <vn-auto>
<vn-auto> <h5 text-center pad-small-v class="title">{{$ctrl.summary.id}} - {{$ctrl.summary.client.name}} - {{$ctrl.summary.client.salesPerson.id}}</h5>
<h5 text-center pad-small-v class="title">Order</h5> </vn-auto>
</vn-auto> <vn-horizontal class="ticketSummary__data" pad-medium-v>
</vn-vertical> <vn-one>
</vn-card> <vn-label-value label="Id"
</vn-vertical> 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 ngModule from '../module';
import './style.scss';
class Controller { class Controller {
constructor($http) { constructor($scope, $http, $state) {
this.$scope = $scope;
this.$http = $http; 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', { ngModule.component('vnOrderSummary', {
template: require('./index.html'), 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

@ -189,7 +189,7 @@ class Controller {
let sales = this.getCheckedLines(); let sales = this.getCheckedLines();
this.$http.post(`/api/Sales/MoveToNewTicket`, {ticket: ticket, sales: sales}).then(res => { 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'); window.open(url, '_blank');
this.$scope.transfer.hide(); this.$scope.transfer.hide();
this.$scope.model.refresh(); this.$scope.model.refresh();

View File

@ -19,12 +19,12 @@ module.exports = Self => {
} }
}); });
async function addSalesToTicket(salesToRefund, newRefundTicket, transaction) { async function addSalesToTicket(salesToRefund, ticketFk, transaction) {
let formatedSales = []; let formatedSales = [];
salesToRefund.forEach(sale => { salesToRefund.forEach(sale => {
let formatedSale = { let formatedSale = {
itemFk: sale.sale().itemFk, itemFk: sale.sale().itemFk,
ticketFk: newRefundTicket, ticketFk: ticketFk,
concept: sale.sale().concept, concept: sale.sale().concept,
quantity: -Math.abs(sale.quantity), quantity: -Math.abs(sale.quantity),
price: sale.sale().price, price: sale.sale().price,
@ -113,14 +113,30 @@ module.exports = Self => {
try { try {
let newRefundTicket = await models.Ticket.new(params, {transaction: transaction}); 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 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 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}); 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(); await transaction.commit();
return newRefundTicket; return newRefundTicket;
} catch (e) { } catch (e) {
await transaction.rollback(); await transaction.rollback();

View File

@ -1,16 +1,16 @@
const app = require(`${servicesDir}/claim/server/server`); const app = require(`${servicesDir}/claim/server/server`);
describe('claimBeginning', () => { describe('claimBeginning', () => {
let ticketId; let ticket;
let refundTicketObservations; let refundTicketObservations;
let refundTicketSales; let refundTicketSales;
let salesInsertedInClaimEnd; let salesInsertedInClaimEnd;
afterAll(async() => { afterAll(async() => {
let promises = []; 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); 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() => { 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 ctxOfSalesAssistant = {req: {accessToken: {userId: 21}}};
let claimId = 1; let claimId = 1;
ticketId = await app.models.ClaimBeginning.importToNewRefundTicket(ctxOfSalesAssistant, claimId); ticket = await app.models.ClaimBeginning.importToNewRefundTicket(ctxOfSalesAssistant, claimId);
await app.models.Ticket.findById(ticketId);
refundTicketSales = await app.models.Sale.find({where: {ticketFk: ticketId}}); await app.models.Ticket.findById(ticket.id);
refundTicketObservations = await app.models.TicketObservation.find({where: {ticketFk: ticketId}});
let refundTicketState = await app.models.TicketState.findById(ticketId); 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}}); salesInsertedInClaimEnd = await app.models.ClaimEnd.find({where: {claimFk: claimId}});
expect(refundTicketSales.length).toEqual(2); expect(refundTicketSales.length).toEqual(2);

View File

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

View File

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

View File

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

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

@ -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`) INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `price`, `discount`, `reserved`, `isPicked`, `created`)
VALUES VALUES
( 1, 1, 1 , 'Gem of Time', 5, 9.10, 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, CURDATE()), ( 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, CURDATE()), ( 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, CURDATE()), ( 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, CURDATE()), ( 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, CURDATE()), ( 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()), ( 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()), ( 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()), ( 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`) INSERT INTO `hedera`.`orderRow`(`id`, `orderFk`, `itemFk`, `warehouseFk`, `shipment`, `amount`, `price`, `rate`, `created`, `saleFk`)
VALUES VALUES
( 1, 1, 1, 1 , NULL , 9, 1.5 , 1, CURDATE(), 1), ( 1, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 5, 9.10, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 1),
( 2, 2, 2, 1 , NULL , 5, 1.23, 2, CURDATE(), 2), ( 2, 1, 2, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 10, 1.07, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 2),
( 3, 3, 3, 2 , CURDATE(), 3, 0.50, 3, CURDATE(), 3); ( 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`) INSERT INTO `vn`.`clientContact`(`id`, `clientFk`, `name`, `phone`)
VALUES VALUES

View File

@ -52,5 +52,6 @@
"Warehouse cannot be blank": "El almacén no puede quedar en blanco", "Warehouse cannot be blank": "El almacén no puede quedar en blanco",
"Agency cannot be blank": "La agencia 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", "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"
} }

View File

@ -51,14 +51,21 @@ module.exports = Self => {
userId: userId 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 = []; await model.Sale.updateAll(
for (let i = 0; i < params.sales.length; i++) { {ticketFk: params.ticket.oldTicketFk},
promises.push(model.Sale.update({id: params.sales[i].id}, {ticketFk: newTicket})); {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

@ -22,7 +22,12 @@ module.exports = Self => {
}); });
Self.new = async(params, transaction) => { 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) if (!existsAddress)
throw new UserError(`This address doesn't exist`); throw new UserError(`This address doesn't exist`);
@ -40,6 +45,8 @@ module.exports = Self => {
params.userId params.userId
], transaction); ], 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`); const app = require(`${servicesDir}/ticket/server/server`);
describe('ticket new()', () => { describe('ticket new()', () => {
let ticketId; let ticket;
let today = new Date(); let today = new Date();
afterAll(async() => { 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() => { it('should throw an error if the address doesnt exist', async() => {
@ -33,10 +33,10 @@ describe('ticket new()', () => {
landed: today landed: today
}; };
ticketId = await app.models.Ticket.new(params); ticket = await app.models.Ticket.new(params);
let newestTicketIdInFixtures = 21; 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) { Self.rawStmt = function(stmt, options = {}) {
return this.rawSql(stmt.sql, stmt.params); return this.rawSql(stmt.sql, stmt.params, options);
}; };
Self.escapeName = function(name) { Self.escapeName = function(name) {

View File

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

View File

@ -23,6 +23,6 @@ module.exports = Self => {
let query = `SELECT hedera.orderGetTotal(?) total;`; let query = `SELECT hedera.orderGetTotal(?) total;`;
let [total] = await Self.rawSql(query, [orderFk]); 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 => { Self.new = async params => {
let clientFkByAddress = await Self.app.models.Address.findOne({where: {id: params.addressFk}, fields: 'clientFk'}); let address = await Self.app.models.Address.findOne({
let clientFk = clientFkByAddress.clientFk; where: {id: params.addressFk},
fields: 'clientFk'
});
let clientFk = address.clientFk;
let client = await Self.app.models.Client.findOne({ let client = await Self.app.models.Client.findOne({
where: {id: clientFk}, 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() => { it('should call the getTaxes method and return undefined if its called with a string', async() => {
let result = await app.models.Order.getTaxes('pepinillos'); 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() => { it('should call the getTaxes method and return undefined if its called with unknown id', async() => {
let result = await app.models.Order.getTaxes(99999999999999999999999); 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() => { 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); 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() => { it('should return the total', async() => {
let result = await app.models.Order.getTotalVolume(1); let result = await app.models.Order.getTotalVolume(1);
expect(result.totalVolume).toEqual(0.072); expect(result.totalVolume).toEqual(0.078);
expect(result.totalBoxes).toBeFalsy(); 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() => { it('should return the volumes of a given order id', async() => {
let [result] = await app.models.Order.getVolumes(1); 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/isEditable')(Self);
require('../methods/order/getTotal')(Self); require('../methods/order/getTotal')(Self);
require('../methods/order/catalogFilter')(Self); require('../methods/order/catalogFilter')(Self);
require('../methods/order/summary')(Self);
require('../methods/order/getVAT')(Self);
}; };