#232 components section plus unit tests

This commit is contained in:
Carlos Jimenez 2018-04-13 16:03:43 +02:00
parent 946aa7d8e8
commit dafb554792
18 changed files with 395 additions and 52 deletions

View File

@ -1,17 +1,25 @@
.vn-grid { .vn-grid {
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
td, th{
& > thead,
& > tbody,
& > tfoot {
tr {
td, th {
text-align: left; text-align: left;
padding: 10px; padding: 10px;
&[number]{
text-align: right;
}
}
}
} }
& > thead, & > tbody { & > thead, & > tbody {
border-bottom: 3px solid #9D9D9D; border-bottom: 3px solid #9D9D9D;
} }
& > tbody > tr{ & > tbody > tr {
border-bottom: 1px solid #9D9D9D; border-bottom: 1px solid #9D9D9D;
} }
td[number], th[number]{
text-align: right;
}
} }

View File

@ -182,6 +182,18 @@
"description": "Sale checked", "description": "Sale checked",
"icon": "assignment" "icon": "assignment"
} }
},
{
"url" : "/components",
"state": "ticket.card.components",
"component": "vn-ticket-components",
"params": {
"ticket": "$ctrl.ticket"
},
"menu": {
"description": "Components",
"icon": "icon-components"
}
} }
] ]
} }

View File

@ -1,6 +1,6 @@
<vn-main-block> <vn-main-block>
<vn-horizontal> <vn-horizontal>
<vn-auto margin-medium-v class="left-block"> <vn-auto class="left-block">
<vn-ticket-descriptor ticket="$ctrl.ticket"></vn-ticket-descriptor> <vn-ticket-descriptor ticket="$ctrl.ticket"></vn-ticket-descriptor>
<vn-left-menu></vn-left-menu> <vn-left-menu></vn-left-menu>
</vn-auto> </vn-auto>

View File

@ -0,0 +1,67 @@
<mg-ajax path="/ticket/api/Sales/saleComponentFilter" options="vnIndex" actions="$ctrl.sales = index.model.instances"></mg-ajax>
<vn-vertical>
<vn-card pad-large>
<vn-vertical>
<vn-title>Components</vn-title>
<table class="vn-grid">
<thead>
<tr>
<th number translate>Item</th>
<th translate>Description</th>
<th number translate>Quantity</th>
<th translate>Serie</th>
<th translate>Components</th>
<th number translate>Import</th>
<th number translate>Total import</th>
</tr>
</thead>
<tfoot>
<tr>
<td number colspan="7">
<span translate>Base</span> {{::$ctrl.base() | currency:'€':3}}
</td>
</tr>
<tr>
<td number colspan="7">
<span translate>Margin</span> {{::$ctrl.profitMargin() | currency:'€':3}}
</td>
</tr>
<tr>
<td number colspan="7">
<span translate>Total</span> {{::$ctrl.total() | currency:'€':3}}
</td>
</tr>
</tfoot>
<tbody ng-repeat="sale in $ctrl.sales track by sale.id">
<tr>
<td rowspan="{{
::sale.components.length + 1
}}" number>{{::sale.itemFk}}</td>
<td rowspan="{{
::sale.components.length + 1
}}">{{::sale.item.name}}</td>
<td rowspan="{{
::sale.components.length + 1
}}" number>{{::sale.quantity}}</td>
</tr>
<tr
ng-repeat="component in sale.components track by component.componentFk">
<td ng-class="::{
first: $index == 0,last: $index == sale.components.length - 1
}">{{::component.componentRate.componentType.type}}</td>
<td ng-class="::{
first: $index == 0,last: $index == sale.components.length - 1
}">{{::component.componentRate.name}}</td>
<td ng-class="::{
first: $index == 0,last: $index == sale.components.length - 1
}" number>{{::component.value | currency:'€':3}}</td>
<td ng-class="::{
first: $index == 0,last: $index == sale.components.length - 1
}" number>{{::sale.quantity * component.value | currency:'€':3}}</td>
</tr>
</tbody>
</table>
</vn-vertical>
<vn-paging margin-large-top vn-one index="index" total="index.model.count"></vn-paging>
</vn-card>
</vn-vertical>

View File

@ -0,0 +1,45 @@
import ngModule from '../module';
import './style.scss';
class Controller {
total() {
let sum;
if (this.sales) {
sum = 0;
for (let sale of this.sales)
for (let component of sale.components)
sum += sale.quantity * component.value;
}
return sum;
}
base() {
let sum;
if (this.sales) {
sum = 0;
for (let sale of this.sales)
for (let component of sale.components)
if (component.componentRate.name == 'valor de compra')
sum += sale.quantity * component.value;
}
return sum;
}
profitMargin() {
let sum;
if (this.sales) {
sum = 0;
for (let sale of this.sales)
for (let component of sale.components)
if (component.componentRate.name != 'valor de compra')
sum += sale.quantity * component.value;
}
return sum;
}
}
ngModule.component('vnTicketComponents', {
template: require('./component.html'),
controller: Controller,
bindings: {
ticket: '<'
}
});

View File

@ -0,0 +1,92 @@
import './component.js';
describe('ticket', () => {
describe('Component vnTicketComponents', () => {
let $componentController;
let controller;
beforeEach(() => {
angular.mock.module('ticket');
});
beforeEach(angular.mock.inject(_$componentController_ => {
$componentController = _$componentController_;
controller = $componentController('vnTicketComponents');
}));
describe('total()', () => {
it('should return the sum from all componenets in each sale', () => {
controller.sales = [{
components: [
{componentRate: {name: 'valor de compra'}, value: 5},
{componentRate: {name: 'reparto'}, value: 5},
{componentRate: {name: 'recobro'}, value: 5}
],
quantity: 1
},
{
components: [
{componentRate: {name: 'valor de compra'}, value: 1},
{componentRate: {name: 'reparto'}, value: 1},
{componentRate: {name: 'recobro'}, value: 1}
],
quantity: 5
}
];
let result = controller.total();
expect(result).toEqual(30);
});
});
describe('base()', () => {
it(`should return the sum from all 'valor de compra' componenets in each sale`, () => {
controller.sales = [{
components: [
{componentRate: {name: 'valor de compra'}, value: 5},
{componentRate: {name: 'reparto'}, value: 5},
{componentRate: {name: 'recobro'}, value: 5}
],
quantity: 1
},
{
components: [
{componentRate: {name: 'valor de compra'}, value: 1},
{componentRate: {name: 'reparto'}, value: 1},
{componentRate: {name: 'recobro'}, value: 1}
],
quantity: 5
}
];
let result = controller.base();
expect(result).toEqual(10);
});
});
describe('profitMargin()', () => {
it(`should return the sum from all componenets but 'valor de compra' in each sale`, () => {
controller.sales = [{
components: [
{componentRate: {name: 'valor de compra'}, value: 5},
{componentRate: {name: 'reparto'}, value: 5},
{componentRate: {name: 'recobro'}, value: 5}
],
quantity: 1
},
{
components: [
{componentRate: {name: 'valor de compra'}, value: 1},
{componentRate: {name: 'reparto'}, value: 1},
{componentRate: {name: 'recobro'}, value: 1}
],
quantity: 5
}
];
let result = controller.profitMargin();
expect(result).toEqual(20);
});
});
});
});

View File

@ -0,0 +1,27 @@
vn-ticket-components .vn-grid {
tbody:not(:last-child) {
border-bottom: none;
}
tfoot tr:first-child td {
padding-top: 10px !important;
}
tr {
td {
padding-top: .1em !important;
padding-bottom: .1em !important;
}
td.first {
padding-top: 10px !important;
}
td.last {
padding-bottom: 10px !important;
}
}
tr:not(:first-child):not(:last-child), {
border-bottom: none;
}
}

View File

@ -6,6 +6,7 @@ Basic data: Datos básicos
Checked: Comprobado Checked: Comprobado
Client: Cliente Client: Cliente
Company: Empresa Company: Empresa
Components: Componentes
Counter: Contador Counter: Contador
Created : Añadido Created : Añadido
Date : Fecha Date : Fecha
@ -15,13 +16,15 @@ Description: Descripción
Discount: Descuento Discount: Descuento
Employee : Empleado Employee : Empleado
Expedition: Expedición Expedition: Expedición
Import: Importe
Is checked: Comprobado Is checked: Comprobado
Item: Articulo Item: Articulo
Landing: Llegada Landing: Llegada
Landed: F. entrega Landed: F. entrega
Name: Nombre Margin: Margen
m³ per unit: m³ por unidad m³ per unit: m³ por unidad
m³ per quantity: m³ por cantidad m³ per quantity: m³ por cantidad
Name: Nombre
Notes: Notas Notes: Notas
New price: Nuevo precio New price: Nuevo precio
New : Nuevo New : Nuevo
@ -40,6 +43,7 @@ Some fields are invalid: Algunos campos no son válidos
State: Estado State: Estado
The observation type must be unique: El tipo de observación debe ser único The observation type must be unique: El tipo de observación debe ser único
Tickets: Tickets Tickets: Tickets
Total import: Importe total
Tracking: Revisión Tracking: Revisión
Volume: Volumen Volume: Volumen
Warehouse: Almacén Warehouse: Almacén

View File

@ -18,3 +18,4 @@ import './tracking/index';
import './tracking/edit/edit'; import './tracking/edit/edit';
import './fetched-tags/fetched-tags'; import './fetched-tags/fetched-tags';
import './sale-checked/sale-checked'; import './sale-checked/sale-checked';
import './component/component';

View File

@ -19,9 +19,9 @@
<td number>{{::sale.itemFk}}</td> <td number>{{::sale.itemFk}}</td>
<td><vn-fetched-tags sale="sale"/></td> <td><vn-fetched-tags sale="sale"/></td>
<td number>{{::sale.quantity}}</td> <td number>{{::sale.quantity}}</td>
<td number>{{::sale.volume.m3_uni}}</td> <td number>{{::sale.volume.m3_uni | number:3}}</td>
<td number>{{::sale.volume.volumeTimesQuantity}}</td> <td number>{{::sale.volume.volumeTimesQuantity | number:3}}</td>
<td number>{{::sale.volume.m3_total}}</td> <td number>{{::sale.volume.m3_total | number:3}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -10,28 +10,28 @@ module.exports = Self => {
limit: params.size, limit: params.size,
order: params.order || 'concept ASC', order: params.order || 'concept ASC',
include: [{ include: [{
relation: "item", relation: 'item',
scope: { scope: {
include: { include: {
relation: "itemTag", relation: 'itemTag',
scope: { scope: {
fields: ["tagFk", "value"], fields: ['tagFk', 'value'],
include: { include: {
relation: "tag", relation: 'tag',
scope: { scope: {
fields: ["name"] fields: ['name']
} }
}, },
limit: 6 limit: 6
} }
}, },
fields: ["itemFk", "name"] fields: ['itemFk', 'name']
} }
}, },
{ {
relation: "isChecked", relation: 'isChecked',
scope: { scope: {
fields: ["isChecked"] fields: ['isChecked']
} }
}] }]
}; };

View File

@ -0,0 +1,52 @@
module.exports = Self => {
Self.installMethod('saleComponentFilter', filterParams);
function filterParams(params) {
return {
where: {
ticketFk: params.ticketFk
},
skip: (params.page - 1) * params.size,
limit: params.size,
order: params.order || 'concept ASC',
include: [{
relation: "item",
scope: {
include: {
relation: "itemTag",
scope: {
fields: ["tagFk", "value"],
include: {
relation: "tag",
scope: {
fields: ["name"]
}
},
limit: 6
}
},
fields: ["itemFk", "name"]
}
},
{
relation: "components",
scope: {
fields: ["componentFk", "value"],
include: {
relation: "componentRate",
scope: {
fields: ["componentTypeRate", "name"],
include: {
relation: "componentType",
scope: {
fields: ["type"]
}
}
}
}
}
}
]
};
}
};

View File

@ -28,6 +28,13 @@
"type": "Number" "type": "Number"
} }
}, },
"relations": {
"componentType": {
"type": "belongsTo",
"model": "ComponentTypeRate",
"foreignKey": "componentTypeRate"
}
},
"acls": [ "acls": [
{ {
"accessType": "READ", "accessType": "READ",

View File

@ -0,0 +1,19 @@
{
"name": "ComponentTypeRate",
"base": "VnModel",
"options": {
"mysql": {
"table": "componentTypeRate"
}
},
"properties": {
"id": {
"id": true,
"type": "Number",
"description": "Identifier"
},
"type": {
"type": "String"
}
}
}

View File

@ -1,3 +1,4 @@
module.exports = function(Self) { module.exports = function(Self) {
require('../methods/sale/filter.js')(Self); require('../methods/sale/filter.js')(Self);
require('../methods/sale/saleComponentFilter.js')(Self);
}; };

View File

@ -51,6 +51,11 @@
"type": "hasOne", "type": "hasOne",
"model": "SaleChecked", "model": "SaleChecked",
"foreignKey": "saleFk" "foreignKey": "saleFk"
},
"components": {
"type": "hasMany",
"model": "SaleComponent",
"foreignKey": "saleFk"
} }
} }
} }

View File

@ -22,7 +22,7 @@
}, },
"componentRate": { "componentRate": {
"type": "belongsTo", "type": "belongsTo",
"model": "componentRate", "model": "ComponentRate",
"foreignKey": "componentFk" "foreignKey": "componentFk"
} }
} }

View File

@ -23,6 +23,9 @@
"ComponentRate": { "ComponentRate": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ComponentTypeRate": {
"dataSource": "vn"
},
"SaleComponent": { "SaleComponent": {
"dataSource": "vn" "dataSource": "vn"
}, },