Merge pull request '2245-ticket_component' (#369) from 2245-ticket_component into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #369
Reviewed-by: Carlos Jimenez <carlosjr@verdnatura.es>
This commit is contained in:
Carlos Jimenez Ruiz 2020-09-10 08:52:23 +00:00
commit a9c1f97b72
19 changed files with 335 additions and 54 deletions

View File

@ -540,6 +540,32 @@ INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agen
(6, NULL, 57, CURDATE(), 5, 7, 'sixth route', 1.7, 60, CURDATE(), CURDATE(), 3),
(7, NULL, 57, CURDATE(), 6, 8, 'seventh route', 0, 70, CURDATE(), CURDATE(), 5);
INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`,`zonePrice`, `zoneBonus`, `created`)
VALUES
(1 , 3, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 101, 'Bat cave', 121, 'T1111111', 0, 1, 5, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(2 , 1, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T1111111', 0, 1, 5, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(3 , 1, 7, 1, 6, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T2222222', 0, 3, 5, 1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
(4 , 3, 2, 1, 2, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T3333333', 0, 9, 5, 1, DATE_ADD(CURDATE(), INTERVAL -3 MONTH)),
(5 , 3, 3, 3, 3, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T4444444', 0, 10, 5, 1, DATE_ADD(CURDATE(), INTERVAL -4 MONTH)),
(6 , 1, 3, 3, 3, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 101, 'Mountain Drive Gotham', 1, 'A1111111', 0, 10, 5, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(7 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'Mountain Drive Gotham', 1, NULL, 0, 3, 5, 1, CURDATE()),
(8 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'Bat cave', 121, NULL, 0, 3, 5, 1, CURDATE()),
(9 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Stark tower', 124, NULL, 0, 3, 5, 1, CURDATE()),
(10, 1, 1, 5, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'Ingram Street', 2, NULL, 0, 1, 5, 1, CURDATE()),
(11, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'NY roofs', 122, NULL, 0, 3, 5, 1, CURDATE()),
(12, 1, 1, 1, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 1, 5, 1, CURDATE()),
(13, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 3, 5, 1, CURDATE()),
(14, 1, 2, 1, NULL, CURDATE(), CURDATE(), 104, 'Malibu Point', 4, NULL, 0, 9, 5, 1, CURDATE()),
(15, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 105, 'Plastic Cell', 125, NULL, 0, 3, 5, 1, CURDATE()),
(16, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, 5, 1, CURDATE()),
(17, 1, 7, 2, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, 5, 1, CURDATE()),
(18, 1, 4, 4, 4, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 108, 'Cerebro', 128, NULL, 0, 12, 5, 1, CURDATE()),
(19, 1, 5, 5, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 1, NULL, 5, 1, CURDATE()),
(20, 1, 5, 5, 3, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 0, 13, 5, 1, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(21, NULL, 5, 5, 5, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Holland', 102, NULL, 0, 13, 5, 1, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(22, NULL, 5, 5, 5, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Japan', 103, NULL, 0, 13, 5, 1, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(23, NULL, 8, 1, 7, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'address 21', 121, NULL, 0, 5, 5, 1, CURDATE()),
(24 ,NULL, 8, 1, 7, CURDATE(), CURDATE(), 101, 'Bruce Wayne', 1, NULL, 0, 5, 5, 1, CURDATE());
INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `created`)
VALUES
(1 , 3, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 101, 'Bat cave', 121, 'T1111111', 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
@ -786,6 +812,10 @@ INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `d
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 1, 4751000000, 0, NULL, 0, 88, 2, NULL);
INSERT INTO `vn`.`expeditionBoxVol`(`boxFk`, `m3`, `ratio`)
VALUES
(71,0.141,1);
INSERT INTO `vn`.`packaging`(`id`, `volume`, `width`, `height`, `depth`, `isPackageReturnable`, `created`, `itemFk`, `price`)
VALUES
(1, 0.00, 10, 10, 0, 1, CURDATE(), 6, 1.50),

View File

@ -521,11 +521,11 @@ export default {
zone: 'vn-autocomplete[ng-model="$ctrl.zoneId"]',
nextStepButton: 'vn-step-control .buttons > section:last-child vn-button',
finalizeButton: 'vn-step-control .buttons > section:last-child button[type=submit]',
stepTwoTotalPriceDif: 'vn-ticket-basic-data-step-two vn-tfoot > vn-tr > :nth-child(6)',
stepTwoTotalPriceDif: 'vn-ticket-basic-data-step-two > vn-side-menu div:nth-child(4)',
chargesReason: 'vn-ticket-basic-data-step-two div:nth-child(3) > vn-radio',
},
ticketComponents: {
base: 'vn-ticket-components [name="base-sum"]'
base: 'vn-ticket-components > vn-side-menu div:nth-child(1) > div:nth-child(2)'
},
ticketRequests: {
addRequestButton: 'vn-ticket-request-index > a > vn-float-button > button',

View File

@ -24,7 +24,6 @@ describe('Ticket List components path', () => {
await page.waitPropertyLength(selectors.ticketComponents.base, 'innerText', minLength);
const base = await page.waitToGetProperty(selectors.ticketComponents.base, 'innerText');
expect(base).toContain('Base');
expect(base.length).toBeGreaterThan(minLength);
});

View File

@ -1,6 +1,7 @@
@import "variables";
.vn-radio {
& > .btn {
border-radius: 50%;
@ -25,4 +26,8 @@
&.disabled.checked > .btn > .mark {
background-color: $color-font-secondary;
}
> div {
text-overflow: ellipsis;
overflow: hidden;
}
}

View File

@ -2,6 +2,6 @@
<div class="focus-mark"></div>
<div class="mark"></div>
</div>
<span translate>
<div translate>
{{::$ctrl.label}}
</span>
</div>

View File

@ -0,0 +1,26 @@
module.exports = Self => {
Self.remoteMethod('freightCost', {
description: 'Returns the freight cost 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/freightCost`,
verb: 'GET'
}
});
Self.freightCost = async ticketFk => {
const [freightCost] = await Self.rawSql(`SELECT vn.ticket_getFreightCost(?) total`, [ticketFk]);
return freightCost.total;
};
};

View File

@ -0,0 +1,53 @@
module.exports = Self => {
Self.remoteMethod('getComponentsSum', {
description: 'Returns a list of component and their sum from 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/getComponentsSum`,
verb: 'GET'
}
});
Self.getComponentsSum = async id => {
const models = Self.app.models;
let salesComponents = [];
let componentsSum = [];
let sales = await models.Sale.find({where: {ticketFk: id}});
let components = await models.Component.find();
if (sales.length > 0) {
for (let sale of sales) {
let myComponents = await models.SaleComponent.find({where: {saleFk: sale.id}});
salesComponents = salesComponents.concat(myComponents);
}
salesComponents.reduce((acumulator, currentValue) => {
if (!acumulator[currentValue.componentFk]) {
let defaultValue = 0;
let tarjetComponent = components.find(component => component.id === currentValue.componentFk);
acumulator[currentValue.componentFk] = {
componentFk: currentValue.componentFk,
value: defaultValue,
name: tarjetComponent.name
};
componentsSum.push(acumulator[currentValue.componentFk]);
}
acumulator[currentValue.componentFk].value += currentValue.value;
return acumulator;
});
}
return componentsSum;
};
};

View File

@ -0,0 +1,17 @@
const app = require('vn-loopback/server/server');
describe('ticket freightCost()', () => {
it('should return the freight cost of a given ticket', async() => {
let ticketId = 7;
let freightCost = await app.models.Ticket.freightCost(ticketId);
expect(freightCost).toBe(4);
});
it('should return null if the ticket does not exist', async() => {
let ticketId = 99;
let freightCost = await app.models.Ticket.freightCost(ticketId);
expect(freightCost).toBeNull();
});
});

View File

@ -0,0 +1,20 @@
const app = require('vn-loopback/server/server');
describe('ticket getComponentsSum()', () => {
it('should get the list of component for the ticket sales', async() => {
const ticketId = 7;
const components = await app.models.Ticket.getComponentsSum(ticketId);
const length = components.length;
const anyComponent = components[Math.floor(Math.random() * Math.floor(length))];
expect(components.length).toBeGreaterThan(0);
expect(anyComponent.componentFk).toBeDefined();
});
it('should return 0 if the given ticket does not have sales', async() => {
const ticketWithoutSales = 21;
const components = await app.models.Ticket.getComponentsSum(ticketWithoutSales);
expect(components.length).toEqual(0);
});
});

View File

@ -30,6 +30,8 @@ module.exports = Self => {
require('../methods/ticket/deleteStowaway')(Self);
require('../methods/ticket/sendSms')(Self);
require('../methods/ticket/isLocked')(Self);
require('../methods/ticket/freightCost')(Self);
require('../methods/ticket/getComponentsSum')(Self);
Self.observe('before save', async function(ctx) {
if (ctx.isNewInstance) return;

View File

@ -3,27 +3,12 @@
data="ticketUpdateActions"
auto-load="true">
</vn-crud-model>
<vn-card ng-show="::$ctrl.totalPriceDifference" class="vn-w-lg vn-pa-md vn-mb-md">
<h6
class="text-secondary"
style="font-weight: normal;"
translate>
Charge difference to
</h6>
<div ng-repeat="action in ticketUpdateActions">
<vn-radio
ng-model="$ctrl.ticket.option"
label="{{::action.description}}"
val={{::action.id}}>
</vn-radio>
</div>
</vn-card>
<vn-card class="vn-w-lg">
<vn-table>
<vn-thead>
<vn-tr>
<vn-th number>Item</vn-th>
<vn-th style="text-align:center">Description</vn-th>
<vn-th class="align-center">Description</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th number>Price (PPU)</vn-th>
<vn-th number>New (PPU)</vn-th>
@ -51,10 +36,31 @@
<vn-td></vn-td>
<vn-td></vn-td>
<vn-td></vn-td>
<vn-td number><strong>{{$ctrl.totalPrice | currency: 'EUR': 2}}</strong></vn-td>
<vn-td number><strong>{{$ctrl.totalNewPrice | currency: 'EUR': 2}}</strong></vn-td>
<vn-td number><strong>{{$ctrl.totalPriceDifference | currency: 'EUR': 2}}</strong></vn-td>
</vn-tr>
</vn-tfoot>
</vn-table>
</vn-card>
<vn-side-menu side="right">
<div class="vn-pa-md">
<div class="totalBox align-left">
<h6 class="align-center" translate>Total</h6>
<div> <vn-label translate>Price</vn-label> {{$ctrl.totalPrice | currency: 'EUR': 2}} </div>
<div> <vn-label translate>New price</vn-label> {{$ctrl.totalNewPrice | currency: 'EUR': 2}} </div>
<div> <vn-label translate>Difference</vn-label> {{$ctrl.totalPriceDifference | currency: 'EUR': 2}} </div>
</div>
<vn-card ng-show="::$ctrl.totalPriceDifference">
<div class="totalBox align-left" >
<h6 class="align-center" translate>Charge difference to</h6>
<div ng-repeat="action in ticketUpdateActions">
<vn-radio
ng-model="$ctrl.ticket.option"
label="{{::action.description}}"
val={{::action.id}}>
</vn-radio>
</div>
</div>
</vn-card>
</div>
</vn-side-menu>

View File

@ -1,5 +1,6 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
class Controller extends Component {
$onInit() {

View File

@ -2,4 +2,7 @@ Price (PPU): Precio (Ud.)
New (PPU): Nuevo (Ud.)
Difference: Diferencia
Charge difference to: Cargar diferencia a
The ticket has been unrouted: El ticket ha sido desenrutado
The ticket has been unrouted: El ticket ha sido desenrutado
Price: Precio
New price: Nuevo precio
Price difference: Diferencia de precio

View File

@ -0,0 +1,9 @@
@import "variables";
.align-left {
text-align: left
}
.align-center {
text-align: center
}

View File

@ -7,9 +7,6 @@
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="model" class="vn-w-xl">
<vn-card class="vn-pa-lg text-right" name="base-sum">
<span translate>Base to commission</span> {{$ctrl.base() | currency: 'EUR':3}}
</vn-card>
<vn-card class="vn-mt-md">
<table class="vn-table">
<thead>
@ -24,7 +21,7 @@
</tr>
</thead>
<tbody ng-repeat="sale in components track by sale.id">
<tr style="height: initial;">
<tr class="initial">
<td rowspan="{{::sale.components.length + 1}}" number>
<span
ng-click="descriptor.show($event, sale.itemFk, sale.id)"
@ -64,6 +61,25 @@
</table>
</vn-card>
</vn-data-viewer>
<vn-side-menu side="right">
<div class="totalBox align-left">
<h6 class="align-center" translate>Total</h6>
<div> <vn-label translate>Base to commission</vn-label> {{$ctrl.base() | currency: 'EUR':2}} </div>
<div> <vn-label translate>Total without VAT</vn-label> {{$ctrl.getTotal() | currency: 'EUR': 3}} </div>
</div>
<div class="totalBox align-left">
<h6 class="align-center" translate>Components</h6>
<section ng-repeat="component in $ctrl.componentsList">
<div>
<vn-label>{{component.name}}</vn-label> {{component.value | currency: 'EUR': 3}}
</div>
</section>
</div>
<div class="totalBox align-left">
<h6 class="align-center" translate>Theorical cost</h6>
<div> <vn-label translate>Price</vn-label> {{$ctrl.theoricalCost | currency: 'EUR': 2}} </div>
</div>
</vn-side-menu>
<vn-item-descriptor-popover
vn-id="descriptor"
warehouse-fk="$ctrl.ticket.warehouseFk">

View File

@ -29,11 +29,22 @@ class Controller extends Section {
}]
};
}
get ticket() {
return this._ticket;
}
set ticket(value) {
this._ticket = value;
if (!value) return;
this.getTheoricalCost();
this.getComponentsSum();
}
base() {
let sales = this.$.model.data;
let sum = 0;
if (!sales) return;
for (let sale of sales) {
@ -42,9 +53,29 @@ class Controller extends Section {
sum += sale.quantity * saleComponent.value;
}
}
return sum;
}
getTotal() {
const sales = this.$.model.data;
let total = 0;
if (!sales) return;
for (let sale of sales) {
for (let saleComponent of sale.components)
total += sale.quantity * saleComponent.value;
}
return total;
}
getTheoricalCost() {
this.$http.get(`Tickets/${this.ticket.id}/freightCost`)
.then(res => this.theoricalCost = res.data);
}
getComponentsSum() {
this.$http.get(`Tickets/${this.ticket.id}/getComponentsSum`)
.then(res => this.componentsList = res.data);
}
}
ngModule.vnComponent('vnTicketComponents', {

View File

@ -4,76 +4,72 @@ import crudModel from 'core/mocks/crud-model';
describe('ticket', () => {
describe('Component vnTicketComponents', () => {
let controller;
let $httpBackend;
beforeEach(ngModule('ticket'));
beforeEach(inject(($componentController, $rootScope, $state) => {
beforeEach(inject(($componentController, $rootScope, $state, _$httpBackend_) => {
$state.params.id = '1';
let $scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$scope.model = crudModel;
$scope.model.data = [{
quantity: 1,
components: [
{
value: 5,
component: {
name: 'valor de compra',
componentType: {
isBase: 1
}
},
value: 5
}
},
{
value: 5,
component: {
name: 'reparto',
componentType: {
isBase: 0
}
},
value: 5
}
},
{
value: 5,
component: {
name: 'recobro',
componentType: {
isBase: 0
}
},
value: 5
}
}
],
quantity: 1
]
},
{
quantity: 5,
components: [
{
value: 1,
component: {
name: 'valor de compra',
componentType: {
isBase: 1
}
},
value: 1
}
},
{
value: 1,
component: {
name: 'reparto',
componentType: {
isBase: 0
}
},
value: 1
}
},
{
value: 1,
component: {
name: 'recobro',
componentType: {
isBase: 0
}
},
value: 1
}
},
],
quantity: 5
]
}];
const $element = angular.element('<vn-ticket-components></vn-ticket-components>');
controller = $componentController('vnTicketComponents', {$element, $scope});
@ -86,5 +82,55 @@ describe('ticket', () => {
expect(result).toEqual(10);
});
});
describe('ticket setter', () => {
it('should set the ticket data and then call getTheoricalCost() and getComponentsSum()', () => {
jest.spyOn(controller, 'getTheoricalCost');
jest.spyOn(controller, 'getComponentsSum');
controller._ticket = undefined;
controller.ticket = {
id: 7
};
expect(controller.ticket).toBeDefined();
expect(controller.getTheoricalCost).toHaveBeenCalledWith();
expect(controller.getComponentsSum).toHaveBeenCalledWith();
});
});
describe('getTotal()', () => {
it('should return the total sum of a ticket', () => {
let result = controller.getTotal();
expect(result).toEqual(30);
});
});
describe('getTheoricalCost()', () => {
it('should make a request to get the theorical cost of a ticket', () => {
controller._ticket = {
id: 7
};
$httpBackend.expect('GET', `Tickets/${controller._ticket.id}/freightCost`).respond('My freight cost');
controller.getTheoricalCost();
$httpBackend.flush();
expect(controller.theoricalCost).toBe('My freight cost');
});
});
describe('getComponentsSum()', () => {
it('should make a request to get the component list', () => {
controller._ticket = {
id: 7
};
$httpBackend.expect('GET', `Tickets/${controller._ticket.id}/getComponentsSum`).respond('My component list');
controller.getComponentsSum();
$httpBackend.flush();
expect(controller.componentsList).toBe('My component list');
});
});
});
});

View File

@ -0,0 +1,2 @@
Theorical cost: Porte teorico
Total without VAT: Total sin IVA

View File

@ -24,4 +24,19 @@ vn-ticket-components {
}
}
}
.totalBox {
max-width: none;
}
.align-left {
text-align: left
}
.align-center {
text-align: center
}
.initial {
height: initial
}
}