merge
gitea/salix/dev This commit has test failures Details

This commit is contained in:
Carlos Jimenez Ruiz 2019-02-21 11:53:19 +01:00
commit 413d2f2855
31 changed files with 313 additions and 147 deletions

View File

@ -23,7 +23,7 @@ export default {
clientsIndex: {
searchClientInput: `${components.vnTextfield}`,
searchButton: `vn-searchbar vn-icon[icon="search"]`,
searchResult: `vn-item-client a`,
searchResult: `vn-client-index .vn-list-item`,
createClientButton: `${components.vnFloatButton}`,
othersButton: `vn-left-menu li[name="Others"] > a`
},

View File

@ -152,44 +152,46 @@ vn-tool-bar {
.vn-list {
max-width: 36em;
margin: 0 auto;
}
a.vn-list-item {
@extend %clickable;
}
.vn-list-item {
padding: $pad-medium;
border-bottom: $border-thin solid $color-spacer;
display: block;
text-decoration: none;
color: inherit;
a.vn-list-item {
@extend %clickable;
}
.vn-list-item {
border-bottom: $border-thin solid $color-spacer;
display: block;
text-decoration: none;
color: inherit;
& > vn-horizontal {
& > vn-one {
overflow: hidden;
}
& > .buttons {
align-items: center;
& > vn-horizontal {
padding: $pad-medium;
vn-icon-button {
opacity: .4;
color: $color-main;
margin-left: .5em;
transition: opacity 250ms ease-out;
padding: 0;
font-size: 2em;
& > vn-one {
overflow: hidden;
}
& > .buttons {
align-items: center;
&:hover {
opacity: 1;
vn-icon-button {
opacity: .4;
color: $color-main;
margin-left: .5em;
transition: opacity 250ms ease-out;
padding: 0;
font-size: 2em;
&:hover {
opacity: 1;
}
}
}
}
}
}
vn-empty-rows.vn-list-item {
text-align: center;
padding: 1.5em;
box-sizing: border-box;
vn-empty-rows {
display: block;
text-align: center;
padding: 1.5em;
box-sizing: border-box;
}
}
/** START - FORM ELEMENTS DISABLED **/

View File

@ -1,7 +1,7 @@
{
"module": "agency",
"name": "Agencies",
"icon" : "local_shipping",
"icon" : "icon-delivery",
"validations" : true,
"menu": [
{"state": "zone.card.basicData", "icon": "settings"},

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

@ -25,7 +25,7 @@ class Controller {
tooltip: 'Client ticket list'
},
btnTwo: {
icon: 'shopping_cart',
icon: 'icon-basket',
state: `order.create({clientFk: ${value.id}})`,
tooltip: 'New order'
}

View File

@ -17,11 +17,42 @@
</vn-searchbar>
</vn-card>
<vn-card margin-medium-v>
<vn-item-client
class="searchResult"
<a
ng-repeat="client in clients track by client.id"
client="::client">
</vn-item-client>
ui-sref="client.card.summary({ id: {{::client.id}} })"
translate-attr="{title: 'View client'}"
class="vn-list-item searchResult">
<vn-horizontal ng-click="$ctrl.onClick($event)">
<vn-one>
<h6>{{::client.name}}</h6>
<vn-label-value label="Id"
value="{{::client.id}}">
</vn-label-value>
<vn-label-value label="Phone"
value="{{::client.phone | phone}}">
</vn-label-value>
<vn-label-value label="Town/City"
value="{{::client.city}}">
</vn-label-value>
<vn-label-value label="Email"
value="{{::client.email}}">
</vn-label-value>
</vn-one>
<vn-horizontal class="buttons">
<vn-icon-button
ng-click="$ctrl.preview($event)"
vn-tooltip="Preview"
icon="desktop_windows">
</vn-icon-button>
</vn-horizontal>
</vn-horizontal>
</a>
<vn-empty-rows translate ng-if="model.data.length === 0">
No results
</vn-empty-rows>
<vn-empty-rows translate ng-if="model.data === null">
Enter a new search
</vn-empty-rows>
</vn-card>
<vn-pagination model="model"></vn-pagination>
</div>

View File

@ -1,5 +1,4 @@
import ngModule from '../module';
import './item-client';
export default class Controller {
constructor($scope, $stateParams) {
@ -33,7 +32,13 @@ export default class Controller {
}
}
openSummary(client) {
onClick(event) {
if (event.defaultPrevented)
event.stopImmediatePropagation();
}
openSummary(client, event) {
event.preventDefault();
this.clientSelected = client;
this.$.dialogSummaryClient.show();
}

View File

@ -1,29 +0,0 @@
<a
ui-sref="client.card.summary({ id: {{::$ctrl.client.id}} })"
translate-attr="{title: 'View client'}"
class="vn-list-item">
<vn-horizontal ng-click="$ctrl.onClick($event)">
<vn-one>
<h6>{{::$ctrl.client.name}}</h6>
<vn-label-value label="Id"
value="{{::$ctrl.client.id}}">
</vn-label-value>
<vn-label-value label="Phone"
value="{{::$ctrl.client.phone | phone}}">
</vn-label-value>
<vn-label-value label="Town/City"
value="{{::$ctrl.client.city}}">
</vn-label-value>
<vn-label-value label="Email"
value="{{::$ctrl.client.email}}">
</vn-label-value>
</vn-one>
<vn-horizontal class="buttons">
<vn-icon-button
ng-click="$ctrl.preview($event)"
vn-tooltip="Preview"
icon="desktop_windows">
</vn-icon-button>
</vn-horizontal>
</vn-horizontal>
</a>

View File

@ -1,24 +0,0 @@
import ngModule from '../module';
class Controller {
onClick(event) {
if (event.defaultPrevented)
event.stopImmediatePropagation();
}
preview(event) {
event.preventDefault();
this.list.openSummary(this.client);
}
}
ngModule.component('vnItemClient', {
template: require('./item-client.html'),
controller: Controller,
bindings: {
client: '<'
},
require: {
list: '^vnClientIndex'
}
});

View File

@ -1,3 +0,0 @@
vn-item-client {
display: block;
}

View File

@ -7,7 +7,7 @@
{"state": "client.card.basicData", "icon": "settings"},
{"state": "client.card.fiscalData", "icon": "account_balance"},
{"state": "client.card.billingData", "icon": "icon-payment"},
{"state": "client.card.address.index", "icon": "local_shipping"},
{"state": "client.card.address.index", "icon": "icon-delivery"},
{"state": "client.card.note.index", "icon": "insert_drive_file"},
{"state": "client.card.credit.index", "icon": "credit_card"},
{"state": "client.card.greuge.index", "icon": "work"},

View File

@ -1,7 +1,7 @@
{
"module": "item",
"name": "Items",
"icon": "inbox",
"icon": "icon-item",
"validations" : true,
"dependencies": ["worker", "client", "ticket"],
"menu": [

View File

@ -1,12 +1,12 @@
{
"module": "order",
"name": "Orders",
"icon": "shopping_cart",
"icon": "icon-basket",
"validations": true,
"dependencies": ["worker", "item", "ticket"],
"menu": [
{"state": "order.card.basicData", "icon": "settings"},
{"state": "order.card.catalog", "icon": "shopping_cart"},
{"state": "order.card.catalog", "icon": "icon-basket"},
{"state": "order.card.volume", "icon": "icon-volume"},
{"state": "order.card.line", "icon": "icon-lines"}
],

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

@ -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',
@ -149,14 +149,4 @@ module.exports = Self => {
};
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

@ -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

@ -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>

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">

View File

@ -48,6 +48,12 @@
</vn-horizontal>
</vn-horizontal>
</a>
<vn-empty-rows translate ng-if="model.data.length === 0">
No results
</vn-empty-rows>
<vn-empty-rows translate ng-if="model.data === null">
Enter a new search
</vn-empty-rows>
</vn-card>
<vn-pagination model="model"></vn-pagination>
</div>

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 ;