ticket subtotal should check service prices #1151
gitea/salix/dev This commit has test failures Details

This commit is contained in:
Joan Sanchez 2019-02-21 08:43:04 +01:00
parent 0f496b9500
commit 06be535115
16 changed files with 213 additions and 22 deletions

View File

@ -1,12 +1,12 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('Client card', () => { describe('Client get', () => {
it('should call the card() method to receive a formated card of Bruce Wayne', async() => { it('should call the card() method to receive a formated card of Bruce Wayne', async() => {
let id = 101; let id = 101;
let result = await app.models.Client.getCard(id); let result = await app.models.Client.getCard(id);
expect(result.id).toEqual(101); expect(result.id).toEqual(101);
expect(result.name).toEqual('Bruce Wayne'); expect(result.name).toEqual('Bruce Wayne');
expect(result.debt).toEqual(579.1); expect(result.debt).toEqual(595.81);
}); });
}); });

View File

@ -17,7 +17,7 @@ describe('client summary()', () => {
it('should return a summary object containing debt', async() => { it('should return a summary object containing debt', async() => {
let result = await app.models.Client.summary(101); let result = await app.models.Client.summary(101);
expect(result.debt.debt).toEqual(579.1); expect(result.debt.debt).toEqual(595.81);
}); });
it('should return a summary object containing averageInvoiced', async() => { it('should return a summary object containing averageInvoiced', async() => {

View File

@ -4,6 +4,6 @@ describe('ticket getTaxes()', () => {
it('should return the tax of a given ticket', async() => { it('should return the tax of a given ticket', async() => {
let result = await app.models.Ticket.getTaxes(1); let result = await app.models.Ticket.getTaxes(1);
expect(result[0].tax).toEqual(7.44); expect(result[0].tax).toEqual(7.64);
}); });
}); });

View File

@ -4,7 +4,7 @@ describe('ticket getTotal()', () => {
it('should return the total of a ticket', async() => { it('should return the total of a ticket', async() => {
let result = await app.models.Ticket.getTotal(1); let result = await app.models.Ticket.getTotal(1);
expect(result).toEqual(155.89); expect(result).toEqual(158.09);
}); });
it(`should return zero if the ticket doesn't have lines`, async() => { it(`should return zero if the ticket doesn't have lines`, async() => {

View File

@ -4,7 +4,7 @@ describe('ticket getVAT()', () => {
it('should call the getVAT method and return the response', async() => { it('should call the getVAT method and return the response', async() => {
await app.models.Ticket.getVAT(1) await app.models.Ticket.getVAT(1)
.then(response => { .then(response => {
expect(response).toEqual(20.29); expect(response).toEqual(20.49);
}); });
}); });

View File

@ -0,0 +1,15 @@
const app = require('vn-loopback/server/server');
describe('ticket subtotal()', () => {
it('should return the subtotal of a ticket', async() => {
let result = await app.models.Ticket.subtotal(1);
expect(result).toEqual(137.60);
});
it(`should return zero if the ticket doesn't have lines`, async() => {
let result = await app.models.Ticket.subtotal(13);
expect(result).toEqual(0.00);
});
});

View File

@ -14,21 +14,21 @@ describe('ticket summary()', () => {
expect(result.sales.length).toEqual(4); expect(result.sales.length).toEqual(4);
}); });
it('should return a summary object containing subTotal for 1 ticket', async() => { it('should return a summary object containing subtotal for 1 ticket', async() => {
let result = await app.models.Ticket.summary(1); let result = await app.models.Ticket.summary(1);
expect(Math.round(result.subTotal * 100) / 100).toEqual(135.60); expect(Math.round(result.subtotal * 100) / 100).toEqual(137.60);
}); });
it('should return a summary object containing VAT for 1 ticket', async() => { it('should return a summary object containing VAT for 1 ticket', async() => {
let result = await app.models.Ticket.summary(1); let result = await app.models.Ticket.summary(1);
expect(Math.round(result.VAT * 100) / 100).toEqual(20.29); expect(Math.round(result.vat * 100) / 100).toEqual(20.49);
}); });
it('should return a summary object containing total for 1 ticket', async() => { it('should return a summary object containing total for 1 ticket', async() => {
let result = await app.models.Ticket.summary(1); let result = await app.models.Ticket.summary(1);
let total = result.subTotal + result.VAT; let total = result.subtotal + result.vat;
let expectedTotal = Math.round(total * 100) / 100; let expectedTotal = Math.round(total * 100) / 100;
expect(result.total).toEqual(expectedTotal); expect(result.total).toEqual(expectedTotal);

View File

@ -0,0 +1,40 @@
module.exports = Self => {
Self.remoteMethod('subtotal', {
description: 'Returns the total of a ticket',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'ticket id',
http: {source: 'path'}
}],
returns: {
type: 'number',
root: true
},
http: {
path: `/:id/subtotal`,
verb: 'GET'
}
});
Self.subtotal = async ticketFk => {
const sale = Self.app.models.Sale;
const ticketSales = await sale.find({where: {ticketFk}});
const ticketService = Self.app.models.TicketService;
const ticketServices = await ticketService.find({where: {ticketFk}});
let subtotal = 0.00;
ticketSales.forEach(sale => {
subtotal += sale.price * sale.quantity * ((100 - sale.discount) / 100);
});
ticketServices.forEach(service => {
subtotal += service.price * service.quantity;
});
return Math.round(subtotal * 100) / 100;
};
};

View File

@ -23,9 +23,9 @@ module.exports = Self => {
let models = Self.app.models; let models = Self.app.models;
let summaryObj = await getTicketData(Self, ticketFk); let summaryObj = await getTicketData(Self, ticketFk);
summaryObj.sales = await getSales(models.Sale, ticketFk); summaryObj.sales = await getSales(models.Sale, ticketFk);
summaryObj.subTotal = getSubTotal(summaryObj.sales); summaryObj.subtotal = await models.Ticket.subtotal(ticketFk);
summaryObj.VAT = await models.Ticket.getVAT(ticketFk); summaryObj.vat = await models.Ticket.getVAT(ticketFk);
summaryObj.total = await models.Ticket.getTotal(ticketFk); summaryObj.total = summaryObj.subtotal + summaryObj.vat;
summaryObj.packagings = await models.TicketPackaging.find({ summaryObj.packagings = await models.TicketPackaging.find({
where: {ticketFk: ticketFk}, where: {ticketFk: ticketFk},
include: [{relation: 'packaging', include: [{relation: 'packaging',

View File

@ -5,6 +5,7 @@ module.exports = Self => {
require('../methods/ticket/summary')(Self); require('../methods/ticket/summary')(Self);
require('../methods/ticket/getTotal')(Self); require('../methods/ticket/getTotal')(Self);
require('../methods/ticket/getTaxes')(Self); require('../methods/ticket/getTaxes')(Self);
require('../methods/ticket/subtotal')(Self);
require('../methods/ticket/componentUpdate')(Self); require('../methods/ticket/componentUpdate')(Self);
require('../methods/ticket/new')(Self); require('../methods/ticket/new')(Self);
require('../methods/ticket/isEditable')(Self); require('../methods/ticket/isEditable')(Self);

View File

@ -46,7 +46,7 @@
</vn-button> </vn-button>
</vn-tool-bar> </vn-tool-bar>
<vn-one class="taxes" ng-if="$ctrl.sales.length > 0"> <vn-one class="taxes" ng-if="$ctrl.sales.length > 0">
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.subTotal | currency: 'EUR':2}}</p> <p><vn-label translate>Subtotal</vn-label> {{$ctrl.subtotal | currency: 'EUR':2}}</p>
<p><vn-label translate>VAT</vn-label> {{$ctrl.VAT | currency: 'EUR':2}}</p> <p><vn-label translate>VAT</vn-label> {{$ctrl.VAT | currency: 'EUR':2}}</p>
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.total | currency: 'EUR':2}}</strong></p> <p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.total | currency: 'EUR':2}}</strong></p>
</vn-one> </vn-one>

View File

@ -36,9 +36,10 @@ class Controller {
} }
loadSubTotal() { loadSubTotal() {
this.subTotal = 0.0; if (!this.$stateParams.id || !this.sales) return;
if (!this.sales) return; this.$http.get(`/ticket/api/Tickets/${this.$stateParams.id}/subtotal`).then(res => {
this.subTotal = this.sales.reduce((sum, sale) => sum + this.getSaleTotal(sale), 0.0); this.subtotal = res.data || 0.0;
});
} }
getSaleTotal(sale) { getSaleTotal(sale) {
@ -54,7 +55,7 @@ class Controller {
} }
get total() { get total() {
return this.subTotal + this.VAT; return this.subtotal + this.VAT;
} }
onMoreOpen() { onMoreOpen() {

View File

@ -41,6 +41,7 @@ describe('Ticket', () => {
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.whenGET(/api\/Tickets\/1\/getSales.*/).respond(sales); $httpBackend.whenGET(/api\/Tickets\/1\/getSales.*/).respond(sales);
$httpBackend.whenGET(`/ticket/api/Tickets/1/getVAT`).respond(200, 10.5); $httpBackend.whenGET(`/ticket/api/Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`/ticket/api/Tickets/1/subtotal`).respond(200, 227.5);
$element = $compile('<vn-ticket-sale ticket="ticket"></vn-ticket-sale>')($scope); $element = $compile('<vn-ticket-sale ticket="ticket"></vn-ticket-sale>')($scope);
controller = $element.controller('vnTicketSale'); controller = $element.controller('vnTicketSale');
@ -67,9 +68,9 @@ describe('Ticket', () => {
}); });
}); });
describe('total/VAT/subTotal properties', () => { describe('total/VAT/subtotal properties', () => {
it('should fill total, VAT and subTotal', () => { it('should fill total, VAT and subTotal', () => {
expect(controller.subTotal).toEqual(227.5); expect(controller.subtotal).toEqual(227.5);
expect(controller.VAT).toEqual(10.5); expect(controller.VAT).toEqual(10.5);
expect(controller.total).toEqual(238); expect(controller.total).toEqual(238);
}); });

View File

@ -46,8 +46,8 @@
</vn-label-value> </vn-label-value>
</vn-one> </vn-one>
<vn-one class="taxes"> <vn-one class="taxes">
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.summary.subTotal | currency: 'EUR':2}}</p> <p><vn-label translate>Subtotal</vn-label> {{$ctrl.summary.subtotal | currency: 'EUR':2}}</p>
<p><vn-label translate>VAT</vn-label> {{$ctrl.summary.VAT | currency: 'EUR':2}}</p> <p><vn-label translate>VAT</vn-label> {{$ctrl.summary.vat | currency: 'EUR':2}}</p>
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.summary.total | currency: 'EUR':2}}</strong></p> <p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.summary.total | currency: 'EUR':2}}</strong></p>
</vn-one> </vn-one>
<vn-auto name="sales"> <vn-auto name="sales">

View File

@ -0,0 +1,99 @@
USE `vn`;
DROP procedure IF EXISTS `ticketGetTax`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticketGetTax`()
READS SQL DATA
BEGIN
/**
* Calcula la base imponible, el IVA y el recargo de equivalencia para
* un conjunto de tickets.
*
* @table tmp.ticket(ticketFk) Identificadores de los tickets a calcular
* @return tmp.ticketAmount
* @return tmp.ticketTax Impuesto desglosado para cada ticket.
*/
DROP TEMPORARY TABLE IF EXISTS tmp.addressCompany;
CREATE TEMPORARY TABLE tmp.addressCompany
(INDEX (addressFk, companyFk))
ENGINE = MEMORY
SELECT DISTINCT t.addressFk, t.companyFk
FROM tmp.ticket tmpTicket
JOIN ticket t ON t.id = tmpTicket.ticketFk;
CALL addressTaxArea ();
/** Solo se calcula la base imponible (taxableBase) y el impuesto se calculará posteriormente
* No se debería cambiar el sistema por problemas con los decimales
*/
DROP TEMPORARY TABLE IF EXISTS tmp.ticketTax;
CREATE TEMPORARY TABLE tmp.ticketTax
(INDEX (ticketFk))
ENGINE = MEMORY
SELECT tmpTicket.ticketFk,
bp.pgcFk,
SUM(s.quantity * s.price * (100 - s.discount)/100 ) AS taxableBase,
pgc.rate,
tc.code
FROM tmp.ticket tmpTicket
JOIN sale s ON s.ticketFk = tmpTicket.ticketFk
JOIN item i ON i.id = s.itemFk
JOIN ticket t ON t.id = tmpTicket.ticketFk
JOIN supplier su ON su.id = t.companyFk
JOIN tmp.addressTaxArea ata
ON ata.addressFk = t.addressFk AND ata.companyFk = t.companyFk
JOIN itemTaxCountry itc
ON itc.itemFk = i.id AND itc.countryFk = su.countryFk
JOIN bookingPlanner bp
ON bp.countryFk = su.countryFk
AND bp.taxAreaFk = ata.areaFk
AND bp.taxClassFk = itc.taxClassFk
JOIN pgc ON pgc.code = bp.pgcFk
JOIN taxClass tc ON tc.id = bp.taxClassFk
GROUP BY tmpTicket.ticketFk, pgc.code,pgc.rate
HAVING taxableBase != 0;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketServiceTax;
CREATE TEMPORARY TABLE tmp.ticketServiceTax
(INDEX (ticketFk))
ENGINE = MEMORY
SELECT tt.ticketFk,
SUM(ts.quantity * ts.price) AS taxableBase,
pgc.rate,
tc.code
FROM tmp.ticketTax tt
JOIN ticketService ts ON ts.ticketFk = tt.ticketFk
JOIN ticket t ON t.id = tt.ticketFk
JOIN supplier su ON su.id = t.companyFk
JOIN tmp.addressTaxArea ata
ON ata.addressFk = t.addressFk AND ata.companyFk = t.companyFk
JOIN bookingPlanner bp
ON bp.countryFk = su.countryFk
AND bp.taxAreaFk = ata.areaFk
AND bp.taxClassFk = ts.taxClassFk
JOIN pgc ON pgc.code = bp.pgcFk AND pgc.rate = tt.rate
JOIN taxClass tc ON tc.id = bp.taxClassFk
GROUP BY tt.ticketFk, tt.code,tt.rate
HAVING taxableBase != 0;
UPDATE tmp.ticketTax tt
JOIN tmp.ticketServiceTax ts ON tt.ticketFk = ts.ticketFk AND tt.code = ts.code AND tt.rate = ts.rate
SET tt.taxableBase = tt.taxableBase + ts.taxableBase;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketAmount;
CREATE TEMPORARY TABLE tmp.ticketAmount
(INDEX (ticketFk))
ENGINE = MEMORY
SELECT ticketFk, taxableBase, SUM(CAST(taxableBase * rate / 100 AS DECIMAL(10, 2))) tax,code
FROM tmp.ticketTax
GROUP BY ticketFk, code;
DROP TEMPORARY TABLE IF EXISTS tmp.addressCompany;
DROP TEMPORARY TABLE IF EXISTS tmp.addressTaxArea;
END$$
DELIMITER ;

View File

@ -0,0 +1,34 @@
DROP PROCEDURE IF EXISTS vn.ticketGetTaxAdd;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`ticketGetTaxAdd`(vTicketFk INT)
BEGIN
/**
* Añade un ticket a la tabla tmp.ticket para calcular
* el IVA y el recargo de equivalencia y devuelve el resultado.
*/
DROP TEMPORARY TABLE IF EXISTS tmp.ticket;
CREATE TEMPORARY TABLE tmp.ticket
ENGINE = MEMORY
SELECT vTicketFk ticketFk;
CALL vn.ticketGetTax();
SELECT
tt.ticketFk,
CAST(tt.taxableBase AS DECIMAL(10, 2)) AS taxableBase,
CAST(tt.rate * tt.taxableBase / 100 AS DECIMAL(10, 2)) AS tax,
pgc.*,
CAST(IF(pe.equFk IS NULL, taxableBase, 0) AS DECIMAL(10, 2)) AS Base,
pgc.rate / 100 as vatPercent
FROM tmp.ticketTax tt
JOIN vn.pgc ON pgc.code = tt.pgcFk
LEFT JOIN vn.pgcEqu pe ON pe.equFk = pgc.code;
DROP TEMPORARY TABLE tmp.ticket;
DROP TEMPORARY TABLE tmp.ticketTax;
DROP TEMPORARY TABLE tmp.ticketAmount;
END$$
DELIMITER ;