Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into dev
gitea/salix/dev This commit has test failures Details

This commit is contained in:
Juan Ferrer 2019-02-21 10:10:48 +01:00
commit 02569aeea4
31 changed files with 427 additions and 129 deletions

View File

@ -163,6 +163,10 @@ export default {
firstRiskLineBalance: 'vn-client-risk-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(8)'
},
webPayment: {
confirmFirstPaymentButton: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon-button[icon="done_all"]',
firstPaymentConfirmed: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon[icon="check"]'
},
itemsIndex: {
goBackToModuleIndexButton: `vn-ticket-descriptor a[href="#!/ticket/index"]`,
createItemButton: `${components.vnFloatButton}`,
@ -269,7 +273,7 @@ export default {
sale: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr',
firstSaleItemId: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > span',
popoverDiaryButton: 'vn-ticket-summary vn-item-descriptor-popover vn-item-descriptor vn-icon[icon="icon-transaction"]',
firstSaleQuantity: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4)',
firstSaleQuantity: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3)',
firstSaleDiscount: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6)'
},
ticketsIndex: {
@ -337,18 +341,18 @@ export default {
firstSaleQuantity: `vn-textfield[model="sale.quantity"]:nth-child(1) input`,
firstSaleQuantityClearInput: `vn-textfield[model="sale.quantity"] div.suffix > i`,
firstSaleID: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(4) > span',
firstSalePrice: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(7)',
firstSalePrice: 'vn-ticket-sale:nth-child(1) vn-tr:nth-child(1) > vn-td:nth-child(7) > span',
firstSalePriceInput: 'vn-ticket-sale:nth-child(1) > vn-vertical > vn-popover.edit.dialog-summary.ng-isolate-scope.vn-popover.shown > div > div.content > div > vn-textfield',
firstSaleDiscount: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(8)',
firstSaleDiscount: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(8) > span',
firstSaleDiscountInput: 'vn-ticket-sale:nth-child(1) vn-ticket-sale-edit-discount > div > vn-textfield > div > div > div.infix > input.ng-not-empty',
firstSaleImport: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(9)',
firstSaleReservedIcon: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td:nth-child(2) > vn-icon:nth-child(3)',
firstSaleColour: `vn-ticket-sale vn-tr:nth-child(1) vn-td:nth-child(5) section:nth-child(1)`,
firstSaleLength: `vn-ticket-sale vn-tr:nth-child(1) vn-td:nth-child(5) section:nth-child(3)`,
firstSaleColour: `vn-ticket-sale vn-tr:nth-child(1) vn-td:nth-child(6) section:nth-child(1)`,
firstSaleLength: `vn-ticket-sale vn-tr:nth-child(1) vn-td:nth-child(6) section:nth-child(3)`,
firstSaleCheckbox: `vn-ticket-sale vn-tr:nth-child(1) vn-check[field="sale.checked"] md-checkbox`,
secondSaleClaimIcon: 'vn-ticket-sale > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(2) > a > vn-icon',
secondSaleColour: `vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(5) section:nth-child(5)`,
secondSalePrice: `vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(7)`,
secondSaleColour: `vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(6) section:nth-child(5)`,
secondSalePrice: `vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(7) > span`,
secondSaleDiscount: `vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(8)`,
secondSaleImport: `vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(9)`,
secondSaleText: `vn-table div > vn-tbody > vn-tr:nth-child(2)`,

View File

@ -0,0 +1,40 @@
import selectors from '../../helpers/selectors.js';
import createNightmare from '../../helpers/nightmare';
// #860 E2E client/client/src/web-payment/index.js missing fixtures for another client
xdescribe('Client web Payment', () => {
const nightmare = createNightmare();
describe('as employee', () => {
beforeAll(() => {
nightmare
.loginAndModule('employee', 'client')
.accessToSearchResult('Bruce Wayne')
.accessToSection('client.card.webPayment');
});
it('should not be able to confirm payments', async() => {
let exists = await nightmare
.exists(selectors.webPayment.confirmFirstPaymentButton);
expect(exists).toBeFalsy();
});
});
describe('as administrative', () => {
beforeAll(() => {
nightmare
.loginAndModule('administrative', 'client')
.accessToSearchResult('Bruce Wayne')
.accessToSection('client.card.webPayment');
});
it('should be able to confirm payments', async() => {
let exists = await nightmare
.waitToClick(selectors.webPayment.confirmFirstPaymentButton)
.exists(selectors.webPayment.firstPaymentConfirmed);
expect(exists).toBeTruthy();
});
});
});

View File

@ -0,0 +1,61 @@
import createNightmare from '../../helpers/nightmare';
import config from '../../helpers/config.js';
describe('Login path', () => {
const nightmare = createNightmare();
it('should receive an error when the username is incorrect', async() => {
const username = 'nobody';
const password = 'nightmare';
const result = await nightmare
.goto(`${config.url}/#!/login`)
.wait(`vn-login input[name=user]`)
.write(`vn-login input[name=user]`, username)
.write(`vn-login input[name=password]`, password)
.click(`vn-login input[type=submit]`)
.waitForLastSnackbar();
expect(result.length).toBeGreaterThan(0);
});
it('should receive an error when the username is blank', async() => {
const password = 'nightmare';
const result = await nightmare
.clearInput(`vn-login input[name=user]`)
.write(`vn-login input[name=password]`, password)
.click(`vn-login input[type=submit]`)
.waitForLastSnackbar();
expect(result.length).toBeGreaterThan(0);
});
it('should receive an error when the password is incorrect', async() => {
const username = 'employee';
const password = 'badpassword';
const result = await nightmare
.write(`vn-login input[name=user]`, username)
.write(`vn-login input[name=password]`, password)
.click(`vn-login input[type=submit]`)
.waitForLastSnackbar();
expect(result.length).toBeGreaterThan(0);
});
it('should log in', async() => {
const username = 'employee';
const password = 'nightmare';
const url = await nightmare
.write(`vn-login input[name=user]`, username)
.write(`vn-login input[name=password]`, password)
.click(`vn-login input[type=submit]`)
.wait('#logout')
.parsedUrl();
expect(url.hash).toEqual('#!/');
});
});

View File

@ -538,16 +538,16 @@ xdescribe('Ticket Edit sale path', () => {
describe('when state is preparation and loged as Production', () => {
it(`should not be able to edit the sale price`, async() => {
const result = await nightmare
.waitToClick(selectors.ticketSales.firstSalePrice)
.exists(selectors.ticketSales.firstSalePriceInput);
.wait(selectors.ticketSales.firstSaleID)
.exists(selectors.ticketSales.firstSalePrice);
expect(result).toBeFalsy();
});
it(`should not be able to edit the sale discount`, async() => {
const result = await nightmare
.waitToClick(selectors.ticketSales.firstSaleDiscount)
.exists(selectors.ticketSales.firstSaleDiscountInput);
.waitToClick(selectors.ticketSales.firstSaleID)
.exists(selectors.ticketSales.firstSaleDiscount);
expect(result).toBeFalsy();
});

View File

@ -22,12 +22,12 @@ module.exports = function(Self) {
oldInstance = await fkToValue(oldInstanceFk, ctx);
if (ctx.where && !ctx.currentInstance) {
let fields = Object.keys(ctx.data);
ctx.oldInstances = await Self.modelBuilder.models[ctx.Model.definition.name].find({where: ctx.where, fields: fields});
ctx.oldInstances = await ctx.Model.app.models[ctx.Model.definition.name].find({where: ctx.where, fields: fields});
}
}
if (ctx.isNewInstance) {
if (ctx.isNewInstance)
newInstance = await fkToValue(ctx.instance.__data, ctx);
}
ctx.hookState.oldInstance = oldInstance;
ctx.hookState.newInstance = newInstance;
});
@ -36,7 +36,7 @@ module.exports = function(Self) {
if (ctx.where) {
let affectedModel = ctx.Model.definition.name;
let definition = ctx.Model.definition;
let deletedInstances = await Self.modelBuilder.models[affectedModel].find({where: ctx.where});
let deletedInstances = await ctx.Model.app.models[affectedModel].find({where: ctx.where});
let relation = definition.settings.log.relation;
if (relation) {
@ -81,12 +81,11 @@ module.exports = function(Self) {
};
let transaction = {};
if (ctx.options && ctx.options.transaction) {
if (ctx.options && ctx.options.transaction)
transaction = ctx.options.transaction;
}
let logModel = definition.settings.log.model;
await Self.modelBuilder.models[logModel].create(logRecord, transaction);
await ctx.Model.app.models[logModel].create(logRecord, transaction);
});
}
@ -147,25 +146,23 @@ module.exports = function(Self) {
if (changedModelValue && (!ctx.instance || !ctx.instance[changedModelValue])) {
var where = [];
changedModelId = [];
let changedInstances = await Self.modelBuilder.models[definition.name].find({where: ctx.where, fields: ['id', changedModelValue]});
let changedInstances = await ctx.Model.app.models[definition.name].find({where: ctx.where, fields: ['id', changedModelValue]});
changedInstances.forEach(element => {
where.push(element[changedModelValue]);
changedModelId.push(element.id);
});
} else if (ctx.hookState.oldInstance) {
} else if (ctx.hookState.oldInstance)
where = ctx.instance[changedModelValue];
}
// Set oldInstance, newInstance, userFk and action
let oldInstance = {};
if (ctx.hookState.oldInstance) {
if (ctx.hookState.oldInstance)
Object.assign(oldInstance, ctx.hookState.oldInstance);
}
let newInstance = {};
if (ctx.hookState.newInstance) {
if (ctx.hookState.newInstance)
Object.assign(newInstance, ctx.hookState.newInstance);
}
let userFk;
if (loopBackContext)
@ -189,17 +186,16 @@ module.exports = function(Self) {
let logModel = definition.settings.log.model;
let transaction = {};
if (ctx.options && ctx.options.transaction) {
if (ctx.options && ctx.options.transaction)
transaction = ctx.options.transaction;
}
await Self.modelBuilder.models[logModel].create(logsToSave, transaction);
await ctx.Model.app.models[logModel].create(logsToSave, transaction);
}
// this function retuns all the instances changed in case this is an updateAll
function setLogsToSave(changedInstances, changedInstancesIds, logRecord, ctx) {
let promises = [];
if (changedInstances && typeof changedInstances == "object") {
if (changedInstances && typeof changedInstances == 'object') {
for (let i = 0; i < changedInstances.length; i++) {
logRecord.changedModelId = changedInstancesIds[i];
logRecord.changedModelValue = changedInstances[i];
@ -207,9 +203,9 @@ module.exports = function(Self) {
logRecord.oldInstance = ctx.oldInstances[i];
promises.push(JSON.parse(JSON.stringify(logRecord)));
}
} else {
} else
return logRecord;
}
return promises;
}
@ -217,11 +213,11 @@ module.exports = function(Self) {
let oldInstance = ctx.hookState.oldInstance;
let newInstance = ctx.hookState.newInstance;
if (oldInstance && newInstance) {
if (oldInstance && newInstance)
return 'update';
} else if (!oldInstance && newInstance) {
else if (!oldInstance && newInstance)
return 'insert';
}
return 'delete';
}
};

View File

@ -67,5 +67,6 @@
"You can't create a ticket for a inactive client": "No puedes crear un ticket para un cliente inactivo",
"Tag value cannot be blank": "El valor del tag no puede quedar en blanco",
"ORDER_EMPTY": "Cesta vacía",
"You don't have enough privileges to do that": "No tienes permisos para cambiar esto"
"You don't have enough privileges to do that": "No tienes permisos para cambiar esto",
"You can't create a ticket for a client that has a debt": "No puedes crear un ticket para un client con deuda"
}

View File

@ -40,7 +40,7 @@
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th number>Id</vn-th>
<vn-th number>Item</vn-th>
<vn-th>Landed</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th number>Claimed</vn-th>
@ -107,7 +107,8 @@
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th number>Id</vn-th>
<vn-th number>Item</vn-th>
<vn-th number>Ticket</vn-th>
<vn-th>Destination</vn-th>
<vn-th number>Landed</vn-th>
<vn-th number>Quantity</vn-th>
@ -126,7 +127,13 @@
{{action.sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td number>{{action.sale.id}}</vn-td>
<vn-td number>
<span
ng-click="$ctrl.showTicketDescriptor($event, action.sale.ticket.id)"
class="link">
{{action.sale.ticket.id | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>{{action.claimBeggining.description}}</vn-td>
<vn-td number>{{action.sale.ticket.landed | dateTime: 'dd/MM/yyyy'}}</vn-td>
<vn-td number>{{action.sale.quantity}}</vn-td>
@ -151,3 +158,6 @@
vn-id="workerDescriptor"
user-id="$ctrl.selectedWorker">
</vn-worker-descriptor-popover>
<vn-ticket-descriptor-popover
vn-id="ticketDescriptor">
</vn-ticket-descriptor-popover>

View File

@ -33,12 +33,16 @@ class Controller {
}
showWorkerDescriptor(event, userId) {
event.preventDefault();
event.stopImmediatePropagation();
this.selectedWorker = userId;
this.$.workerDescriptor.parent = event.target;
this.$.workerDescriptor.show();
}
showTicketDescriptor(event, ticketId) {
this.$.ticketDescriptor.ticketFk = ticketId;
this.$.ticketDescriptor.parent = event.target;
this.$.ticketDescriptor.show();
}
}
Controller.$inject = ['$scope', '$http'];

View File

@ -1,12 +1,12 @@
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() => {
let id = 101;
let result = await app.models.Client.getCard(id);
expect(result.id).toEqual(101);
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() => {
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() => {

View File

@ -97,7 +97,7 @@
</a>
</vn-tbody>
</vn-table>
<vn-card margin-medium-v>
</vn-card>
<vn-pagination model="model"></vn-pagination>
</div>
<a ui-sref="item.create" vn-tooltip="New item" vn-bind="+" fixed-bottom-right>

View File

@ -25,23 +25,17 @@ module.exports = Self => {
Self.filter = async filter => {
const stmt = new ParameterizedSQL(
`SELECT
e.id,
e.ticketFk,
e.isBox,
i1.name namePackage,
e.counter,
e.checked,
i2.name nameBox,
e.itemFk,
u.nickname userNickname,
u.id userId,
e.created
e.id, e.ticketFk, e.isBox,
i1.name namePackage, e.counter,
e.checked, i2.name nameBox,
e.itemFk, u.nickname userNickname,
u.id userId, e.created, e.externalId
FROM
vn.expedition e
LEFT JOIN vn.item i2 ON i2.id = e.itemFk
INNER JOIN vn.item i1 ON i1.id = e.isBox
LEFT JOIN vn.worker w ON w.id = e.workerFk
JOIN account.user u ON u.id = w.id
JOIN account.user u ON u.id = w.userFk
`);
stmt.merge(Self.buildSuffix(filter, 'e'));

View File

@ -3,21 +3,21 @@ const app = require('vn-loopback/server/server');
describe('ticket deleted()', () => {
let ticket;
beforeAll(async () => {
beforeAll(async() => {
let originalTicket = await app.models.Ticket.findOne({where: {id: 16}});
originalTicket.id = null;
ticket = await app.models.Ticket.create(originalTicket);
});
afterAll(async () => {
afterAll(async() => {
await app.models.Ticket.destroyById(ticket.id);
});
it('should make sure the ticket is not deleted yet', async () => {
it('should make sure the ticket is not deleted yet', async() => {
expect(ticket.isDeleted).toEqual(false);
});
it('should set a ticket to deleted and log the change on TicketState table', async () => {
it('should set a ticket to deleted and log the change on TicketState table', async() => {
let ctx = {req: {accessToken: {userId: 9}}};
let params = {id: ticket.id};
await app.models.Ticket.deleted(ctx, params);
@ -29,7 +29,7 @@ describe('ticket deleted()', () => {
expect(changedState.stateFk).toEqual(17);
});
it('should throw an error if the given ticket has a claim', async () => {
it('should throw an error if the given ticket has a claim', async() => {
let ctx = {req: {accessToken: {userId: 9}}};
let params = {id: 16};
let error;

View File

@ -4,6 +4,6 @@ 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.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() => {
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() => {

View File

@ -4,7 +4,7 @@ describe('ticket getVAT()', () => {
it('should call the getVAT method and return the response', async() => {
await app.models.Ticket.getVAT(1)
.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);
});
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);
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() => {
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() => {
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;
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 summaryObj = await getTicketData(Self, ticketFk);
summaryObj.sales = await getSales(models.Sale, ticketFk);
summaryObj.subTotal = getSubTotal(summaryObj.sales);
summaryObj.VAT = await models.Ticket.getVAT(ticketFk);
summaryObj.total = await models.Ticket.getTotal(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',
@ -135,27 +135,18 @@ module.exports = Self => {
relation: 'user'
}
}
},
{
}, {
relation: 'atender',
scope: {
include: {
relation: 'user'
}
}
}, {
relation: 'sale'
}
]
};
return await Self.app.models.TicketRequest.find(filter);
}
function getSubTotal(sales) {
let subTotal = 0.00;
sales.forEach(sale => {
subTotal += sale.quantity * sale.price;
});
return subTotal;
}
};

View File

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

View File

@ -1,6 +1,9 @@
{
"name": "Ticket",
"base": "VnModel",
"base": "Loggable",
"log": {
"model":"TicketLog"
},
"options": {
"mysql": {
"table": "ticket"

View File

@ -13,6 +13,8 @@
<vn-thead>
<vn-tr>
<vn-th></vn-th>
<vn-th field="itemFk" number>Expedition</vn-th>
<vn-th field="itemFk" number>Envialia</vn-th>
<vn-th field="itemFk" number>Item</vn-th>
<vn-th field="name">Name</vn-th>
<vn-th field="isBox">Package type</vn-th>
@ -30,6 +32,8 @@
vn-tooltip="Delete expedition">
</vn-icon-button>
</vn-td>
<vn-td number>{{expedition.id | zeroFill:6}}</vn-td>
<vn-td number>{{expedition.externalId | zeroFill:6}}</vn-td>
<vn-td number>
<span
ng-class="{link: expedition.itemFk}"
@ -41,7 +45,6 @@
<vn-td>{{::expedition.nameBox}}</vn-td>
<vn-td number>{{::expedition.counter}}</vn-td>
<vn-td number>{{::expedition.checked}}</vn-td>
<vn-td>{{::expedition.userNickname}}</vn-td>
<vn-td expand>
<span
class="link"

View File

@ -54,7 +54,7 @@
<vn-td number>
<span
ng-show="::request.saleFk"
ng-click="$ctrl.showItemDescriptor($event, request.sale)"
ng-click="$ctrl.showItemDescriptor($event, request.sale.itemFk)"
class="link">
{{request.saleFk | zeroFill:6}}
</span>

View File

@ -44,18 +44,18 @@ class Controller {
});
}
showItemDescriptor(event, sale) {
showItemDescriptor(event, itemFk) {
this.quicklinks = {
btnThree: {
icon: 'icon-transaction',
state: `item.card.diary({
id: ${sale.itemFk},
id: ${itemFk},
ticketFk: ${this.$stateParams.id}
})`,
tooltip: 'Item diary'
}
};
this.$.itemDescriptor.itemFk = sale.itemFk;
this.$.itemDescriptor.itemFk = itemFk;
this.$.itemDescriptor.parent = event.target;
this.$.itemDescriptor.show();
}

View File

@ -46,7 +46,7 @@
</vn-button>
</vn-tool-bar>
<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><strong>Total</strong></vn-label> <strong>{{$ctrl.total | currency: 'EUR':2}}</strong></p>
</vn-one>
@ -62,8 +62,8 @@
<vn-th shrink></vn-th>
<vn-th shrink></vn-th>
<vn-th number>Id</vn-th>
<vn-th>Quantity</vn-th>
<vn-th>Item</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th number>Price</vn-th>
<vn-th number>Disc</vn-th>
<vn-th number>Amount</vn-th>
@ -107,13 +107,6 @@
{{::sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
item="::sale.tags"
title="::sale.concept">
</vn-fetched-tags>
</vn-td>
<vn-td ng-if="!$ctrl.isEditable" number>{{sale.quantity}}</vn-td>
<vn-td ng-if="$ctrl.isEditable" number>
<vn-textfield
@ -122,22 +115,29 @@
type="text">
</vn-textfield>
</vn-td>
<vn-td number
ng-if="$ctrl.isEditable"
class="link"
ng-click="$ctrl.showEditPricePopover($event, sale)"
vn-tooltip="Edit price">
<vn-td expand>
<vn-fetched-tags
max-length="6"
item="::sale.tags"
title="::sale.concept">
</vn-fetched-tags>
</vn-td>
<vn-td number ng-if="$ctrl.isEditable">
<span class="link"
vn-tooltip="Edit price"
ng-click="$ctrl.showEditPricePopover($event, sale)">
{{sale.price | currency: 'EUR':2}}
</span>
</vn-td>
<vn-td number ng-if="!$ctrl.isEditable">
{{sale.price | currency: 'EUR':2}}
</vn-td>
<vn-td number
ng-if="$ctrl.isEditable"
class="link"
ng-click="$ctrl.showEditPopover($event, sale)"
vn-tooltip="Edit discount">
<vn-td number ng-if="$ctrl.isEditable">
<span class="link"
vn-tooltip="Edit discount"
ng-click="$ctrl.showEditPopover($event, sale)">
{{sale.discount}} %
</span>
</vn-td>
<vn-td number
ng-if="!$ctrl.isEditable">
@ -158,7 +158,7 @@
<!-- Edit Price Popover -->
<vn-popover
class="edit dialog-summary"
vn-id="editPricePopover"
vn-id="edit-price-popover"
on-open="$ctrl.getManaSalespersonMana()">
<vn-horizontal pad-medium class="header">
<h5>MANÁ: {{$ctrl.mana | currency: 'EUR':0}}</h5>
@ -185,7 +185,7 @@
<!-- Edit Popover -->
<vn-popover
class="edit dialog-summary"
vn-id="editPopover"
vn-id="edit-popover"
on-open="$ctrl.getManaSalespersonMana()">
<vn-ticket-sale-edit-discount
mana="$ctrl.mana"

View File

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

View File

@ -41,6 +41,7 @@ describe('Ticket', () => {
$httpBackend = _$httpBackend_;
$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/subtotal`).respond(200, 227.5);
$element = $compile('<vn-ticket-sale ticket="ticket"></vn-ticket-sale>')($scope);
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', () => {
expect(controller.subTotal).toEqual(227.5);
expect(controller.subtotal).toEqual(227.5);
expect(controller.VAT).toEqual(10.5);
expect(controller.total).toEqual(238);
});

View File

@ -46,8 +46,8 @@
</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 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>
</vn-one>
<vn-auto name="sales">
@ -57,8 +57,8 @@
<vn-tr>
<vn-th shrink></vn-th>
<vn-th number>Item</vn-th>
<vn-th>Description</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th>Description</vn-th>
<vn-th number>Price</vn-th>
<vn-th number>Discount</vn-th>
<vn-th number>Amount</vn-th>
@ -89,8 +89,8 @@
{{sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand><vn-fetched-tags max-length="6" item="sale.item" title="sale.concept"/></vn-td>
<vn-td number>{{::sale.quantity}}</vn-td>
<vn-td expand><vn-fetched-tags max-length="6" item="sale.item" title="sale.concept"/></vn-td>
<vn-td number>{{::sale.price | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::sale.discount}} %</vn-td>
<vn-td number>{{::sale.quantity * sale.price | currency: 'EUR':2}}</vn-td>
@ -123,8 +123,8 @@
<vn-thead>
<vn-tr>
<vn-th number>Id</vn-th>
<vn-th>Description</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th>Description</vn-th>
<vn-th number>Price</vn-th>
<vn-th>Tax class</vn-th>
</vn-tr>
@ -132,8 +132,8 @@
<vn-tbody>
<vn-tr ng-repeat="service in $ctrl.summary.services">
<vn-td number>{{::service.id}}</vn-td>
<vn-td expand>{{::service.description}}</vn-td>
<vn-td number>{{::service.quantity}}</vn-td>
<vn-td expand>{{::service.description}}</vn-td>
<vn-td number>{{::service.price}}</vn-td>
<vn-td>{{::service.taxClass.description}}</vn-td>
</vn-tr>
@ -151,7 +151,7 @@
<vn-th>Atender</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th number>Price</vn-th>
<vn-th number>Sale id</vn-th>
<vn-th number>Item</vn-th>
<vn-th number>Ok</vn-th>
</vn-tr>
</vn-thead>
@ -166,9 +166,9 @@
<vn-td number>
<span
ng-show="::request.saleFk"
ng-click="$ctrl.showDescriptor($event, request.saleFk)"
ng-click="$ctrl.showDescriptor($event, request.sale.itemFk)"
class="link">
{{request.saleFk | zeroFill:6}}
{{request.sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td number>

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 ;