2824-ticket_totalWith_and_without_VAT #571

Merged
carlosjr merged 4 commits from 2824-ticket_totalWith_and_without_VAT into dev 2021-03-11 16:08:48 +00:00
22 changed files with 65 additions and 248 deletions

View File

@ -2242,4 +2242,9 @@ INSERT INTO `vn`.`duaInvoiceIn`(`id`, `duaFk`, `invoiceInFk`)
(7, 7, 7),
(8, 8, 8),
(9, 9, 9),
(10, 10, 10);
(10, 10, 10);
INSERT INTO `vn`.`ticketRecalc`(`ticketFk`)
SELECT `id` FROM `vn`.`ticket`;
CALL `vn`.`ticket_doRecalc`();

View File

@ -37,11 +37,11 @@ module.exports = Self => {
}, {
arg: 'active',
type: 'Boolean',
description: 'Whether the the item is or not active',
description: 'Whether the item is or not active',
}, {
arg: 'visible',
type: 'Boolean',
description: 'Whether the the item is or not visible',
description: 'Whether the item is or not visible',
}, {
arg: 'typeFk',
type: 'Integer',

View File

@ -1,13 +1,13 @@
module.exports = function(Self) {
Self.remoteMethodCtx('canBeInvoiced', {
description: 'Change property isEqualizated in all client addresses',
description: 'Whether the ticket can or not be invoiced',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'string',
type: 'number',
required: true,
description: 'Client id',
description: 'The ticket id',
http: {source: 'path'}
}
],
@ -23,8 +23,9 @@ module.exports = function(Self) {
});
Self.canBeInvoiced = async id => {
let ticket = await Self.app.models.Ticket.findById(id, {fields: ['id', 'refFk', 'shipped']});
let ticketTotal = await Self.app.models.Ticket.getTotal(id);
let ticket = await Self.app.models.Ticket.findById(id, {
fields: ['id', 'refFk', 'shipped', 'totalWithVat']
});
let query = `SELECT vn.hasSomeNegativeBase(?) AS hasSomeNegativeBase`;
let [result] = await Self.rawSql(query, [id]);
@ -33,7 +34,7 @@ module.exports = function(Self) {
let today = new Date();
let shipped = new Date(ticket.shipped);
if (ticket.refFk || ticketTotal == 0 || shipped.getTime() > today.getTime() || hasSomeNegativeBase)
if (ticket.refFk || ticket.totalWithVat == 0 || shipped.getTime() > today.getTime() || hasSomeNegativeBase)
return false;
return true;

View File

@ -203,6 +203,8 @@ module.exports = Self => {
t.routeFk,
t.warehouseFk,
t.clientFk,
t.totalWithoutVat,
t.totalWithVat,
io.id AS invoiceOutId,
a.provinceFk,
p.name AS province,
@ -246,6 +248,7 @@ module.exports = Self => {
stmts.push(stmt);
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.ticketGetProblems');
stmts.push(`
CREATE TEMPORARY TABLE tmp.ticketGetProblems
(INDEX (ticketFk))
@ -255,23 +258,15 @@ module.exports = Self => {
LEFT JOIN alertLevel al ON al.alertLevel = f.alertLevel
WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)
AND f.shipped >= CURDATE()`);
stmts.push('CALL ticketGetProblems()');
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.ticket');
stmts.push(`
CREATE TEMPORARY TABLE tmp.ticket
(INDEX (ticketFk)) ENGINE = MEMORY
SELECT id ticketFk FROM tmp.filter`);
stmts.push('CALL ticketGetTotal()');
stmts.push('CALL ticketGetProblems()');
stmt = new ParameterizedSQL(`
SELECT
f.*,
tt.total,
tp.*
FROM tmp.filter f
LEFT JOIN tmp.ticketProblems tp ON tp.ticketFk = f.id
LEFT JOIN tmp.ticketTotal tt ON tt.ticketFk = f.id`);
LEFT JOIN tmp.ticketProblems tp ON tp.ticketFk = f.id`);
if (args.problems != undefined && (!args.from && !args.to))
throw new UserError('Choose a date range or days forward');

View File

@ -1,28 +0,0 @@
module.exports = Self => {
Self.remoteMethod('getTaxes', {
description: 'Returns ticket taxes',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'ticket id',
http: {source: 'path'}
}],
returns: {
type: 'number',
root: true
},
http: {
path: `/:id/getTaxes`,
verb: 'GET'
}
});
Self.getTaxes = async ticketFk => {
let query = `CALL vn.ticketGetTaxAdd(?)`;
let [taxes] = await Self.rawSql(query, [ticketFk]);
return taxes;
};
};

View File

@ -1,28 +0,0 @@
module.exports = Self => {
Self.remoteMethod('getTotal', {
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/getTotal`,
verb: 'GET'
}
});
Self.getTotal = async ticketFk => {
let query = `SELECT vn.ticketGetTotal(?) AS amount`;
let [total] = await Self.rawSql(query, [ticketFk]);
return total.amount ? total.amount : 0.00;
};
};

View File

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

View File

@ -1,9 +0,0 @@
const app = require('vn-loopback/server/server');
describe('ticket getTaxes()', () => {
it('should return the tax of a given ticket', async() => {
let result = await app.models.Ticket.getTaxes(1);
expect(result[0].tax).toEqual(7.1);
});
});

View File

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

View File

@ -1,10 +0,0 @@
const app = require('vn-loopback/server/server');
describe('ticket getVAT()', () => {
it('should return the ticket VAT', async() => {
await app.models.Ticket.getVAT(1)
.then(response => {
expect(response).toEqual(84.64);
});
});
});

View File

@ -1,15 +0,0 @@
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(809.23);
});
it(`should return 0 if the ticket doesn't have lines`, async() => {
let result = await app.models.Ticket.subtotal(21);
expect(result).toEqual(0.00);
});
});

View File

@ -14,23 +14,15 @@ describe('ticket summary()', () => {
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 totalWithoutVat for 1 ticket', async() => {
let result = await app.models.Ticket.summary(1);
expect(Math.round(result.subtotal * 100) / 100).toEqual(809.23);
});
it('should return a summary object containing VAT for 1 ticket', async() => {
let result = await app.models.Ticket.summary(1);
expect(Math.round(result.vat * 100) / 100).toEqual(84.64);
expect(result.totalWithoutVat).toEqual(807.23);
});
it('should return a summary object containing total for 1 ticket', async() => {
let result = await app.models.Ticket.summary(1);
let total = result.subtotal + result.vat;
let expectedTotal = Math.round(total * 100) / 100;
expect(result.total).toEqual(expectedTotal);
expect(result.totalWithVat).toEqual(891.87);
});
});

View File

@ -1,39 +0,0 @@
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,6 @@ module.exports = Self => {
let models = Self.app.models;
let summaryObj = await getTicketData(Self, ticketFk);
summaryObj.sales = await getSales(models.Sale, ticketFk);
summaryObj.subtotal = await models.Ticket.subtotal(ticketFk);
summaryObj.vat = await models.Ticket.getVAT(ticketFk);
summaryObj.total = summaryObj.subtotal + summaryObj.vat;
summaryObj.packagings = await models.TicketPackaging.find({
where: {ticketFk: ticketFk},
include: [{relation: 'packaging',

View File

@ -6,16 +6,12 @@ module.exports = Self => {
require('../methods/ticket/getVolume')(Self);
require('../methods/ticket/getTotalVolume')(Self);
require('../methods/ticket/summary')(Self);
require('../methods/ticket/getTotal')(Self);
require('../methods/ticket/getTaxes')(Self);
require('../methods/ticket/subtotal')(Self);
require('../methods/ticket/priceDifference')(Self);
require('../methods/ticket/componentUpdate')(Self);
require('../methods/ticket/new')(Self);
require('../methods/ticket/isEditable')(Self);
require('../methods/ticket/setDeleted')(Self);
require('../methods/ticket/restore')(Self);
require('../methods/ticket/getVAT')(Self);
require('../methods/ticket/getSales')(Self);
require('../methods/ticket/getSalesPersonMana')(Self);
require('../methods/ticket/filter')(Self);

View File

@ -12,30 +12,30 @@
"properties": {
"id": {
"id": true,
"type": "Number",
"type": "number",
"description": "Identifier"
},
"shipped": {
"type": "Date",
"type": "date",
"required": true
},
"landed": {
"type": "Date"
"type": "date"
},
"nickname": {
"type": "String"
"type": "string"
},
"location": {
"type": "String"
"type": "string"
},
"solution": {
"type": "String"
"type": "string"
},
"packages": {
"type": "Number"
"type": "number"
},
"updated": {
"type": "Date",
"type": "date",
"mysql": {
"columnName": "created"
}
@ -44,16 +44,22 @@
"type": "boolean"
},
"priority": {
"type": "Number"
"type": "number"
},
"zoneFk": {
"type": "Number"
"type": "number"
},
"zonePrice": {
"type": "Number"
"type": "number"
},
"zoneBonus": {
"type": "Number"
"type": "number"
},
"totalWithVat": {
"type": "number"
},
"totalWithoutVat": {
"type": "number"
}
},
"relations": {

View File

@ -121,7 +121,7 @@
<vn-td>{{::ticket.warehouse}}</vn-td>
<vn-td number>
<span class="chip {{$ctrl.totalPriceColor(ticket)}}">
{{::ticket.total | currency: 'EUR': 2}}
{{::(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}}
</span>
</vn-td>
<vn-td actions>

View File

@ -49,7 +49,7 @@ export default class Controller extends Section {
throw new UserError('You cannot make a payment on account from multiple clients');
for (let ticket of checkedTickets) {
this.$.balanceCreateDialog.amountPaid += ticket.total;
this.$.balanceCreateDialog.amountPaid += ticket.totalWithVat;
this.$.balanceCreateDialog.clientFk = ticket.clientFk;
description.push(`${ticket.id}`);
}
@ -109,7 +109,7 @@ export default class Controller extends Section {
}
totalPriceColor(ticket) {
const total = parseInt(ticket.total);
const total = parseInt(ticket.totalWithVat);
if (total > 0 && total < 50)
return 'warning';
}

View File

@ -6,18 +6,18 @@ describe('Component vnTicketIndex', () => {
let tickets = [{
id: 1,
clientFk: 1,
total: 10.5,
checked: false
checked: false,
totalWithVat: 10.5
}, {
id: 2,
clientFk: 1,
total: 20.5,
checked: true
checked: true,
totalWithVat: 20.5
}, {
id: 3,
clientFk: 1,
total: 30,
checked: true
checked: true,
totalWithVat: 30
}];
beforeEach(ngModule('ticket'));
@ -76,7 +76,6 @@ describe('Component vnTicketIndex', () => {
jest.spyOn(controller.$.balanceCreateDialog, 'show').mockReturnThis();
controller.$.model = {data: tickets};
controller.$.balanceCreateDialog.amountPaid = 0;
controller.openBalanceDialog();
let description = controller.$.balanceCreateDialog.description;

View File

@ -39,8 +39,8 @@ class Controller extends Section {
getSubTotal() {
if (!this.$params.id || !this.sales) return;
this.$http.get(`Tickets/${this.$params.id}/subtotal`).then(res => {
this.subtotal = res.data || 0.0;
this.$http.get(`Tickets/${this.$params.id}`).then(res => {
this.subtotal = res.data.totalWithoutVat || 0.0;
});
}
@ -62,8 +62,8 @@ class Controller extends Section {
getVat() {
this.VAT = 0.0;
if (!this.$params.id || !this.sales) return;
this.$http.get(`Tickets/${this.$params.id}/getVAT`).then(res => {
this.VAT = res.data || 0.0;
this.$http.get(`Tickets/${this.$params.id}`).then(res => {
this.VAT = res.data.totalWithVat - res.data.totalWithoutVat || 0.0;
});
}

View File

@ -84,13 +84,13 @@ describe('Ticket', () => {
describe('getSubTotal()', () => {
it('should make an HTTP GET query and then set the subtotal property', () => {
const expectedAmount = 128;
const expectedResponse = {totalWithoutVat: 128};
$httpBackend.expect('GET', 'Tickets/1/subtotal').respond(200, expectedAmount);
$httpBackend.expect('GET', 'Tickets/1').respond(200, expectedResponse);
controller.getSubTotal();
$httpBackend.flush();
expect(controller.subtotal).toEqual(expectedAmount);
expect(controller.subtotal).toEqual(expectedResponse.totalWithoutVat);
});
});
@ -125,13 +125,15 @@ describe('Ticket', () => {
describe('getVat()', () => {
it('should make an HTTP GET query and return the ticket VAT', () => {
controller.edit = {};
const expectedAmount = 67;
const expectedResponse = {totalWithVat: 1000, totalWithoutVat: 999};
$httpBackend.expect('GET', 'Tickets/1/getVAT').respond(200, expectedAmount);
const expectedVAT = expectedResponse.totalWithVat - expectedResponse.totalWithoutVat;
$httpBackend.expect('GET', 'Tickets/1').respond(200, expectedResponse);
controller.getVat();
$httpBackend.flush();
expect(controller.VAT).toEqual(expectedAmount);
expect(controller.VAT).toEqual(expectedVAT);
});
});

View File

@ -99,9 +99,9 @@
</vn-label-value>
</vn-one>
<vn-one class="taxes">
<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><strong>Total</strong></vn-label> <strong>{{$ctrl.summary.total | currency: 'EUR':2}}</strong></p>
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.summary.totalWithoutVat | currency: 'EUR':2}}</p>
<p><vn-label translate>VAT</vn-label> {{$ctrl.summary.totalWithVat - $ctrl.summary.totalWithoutVat | currency: 'EUR':2}}</p>
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.summary.totalWithVat | currency: 'EUR':2}}</strong></p>
</vn-one>
<vn-auto name="sales">
<h4>