Merge pull request '2931-entry_summary_pagination' (#650) from 2931-entry_summary_pagination into dev
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
Reviewed-on: #650 Reviewed-by: Joan Sanchez <joan@verdnatura.es>
This commit is contained in:
commit
bbac4483cd
|
@ -1,14 +1,22 @@
|
||||||
|
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('getBuys', {
|
Self.remoteMethod('getBuys', {
|
||||||
description: 'Returns buys for one entry',
|
description: 'Returns buys for one entry',
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
accepts: {
|
accepts: [{
|
||||||
arg: 'id',
|
arg: 'id',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The entry id',
|
description: 'The entry id',
|
||||||
http: {source: 'path'}
|
http: {source: 'path'}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
arg: 'filter',
|
||||||
|
type: 'object',
|
||||||
|
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string'
|
||||||
|
}
|
||||||
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['Object'],
|
type: ['Object'],
|
||||||
root: true
|
root: true
|
||||||
|
@ -19,14 +27,14 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.getBuys = async(id, options) => {
|
Self.getBuys = async(id, filter, options) => {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
let myOptions = {};
|
let myOptions = {};
|
||||||
|
|
||||||
if (typeof options == 'object')
|
if (typeof options == 'object')
|
||||||
Object.assign(myOptions, options);
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
const filter = {
|
let defaultFilter = {
|
||||||
where: {entryFk: id},
|
where: {entryFk: id},
|
||||||
fields: [
|
fields: [
|
||||||
'id',
|
'id',
|
||||||
|
@ -74,6 +82,8 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return models.Buy.find(filter, myOptions);
|
defaultFilter = mergeFilters(defaultFilter, filter);
|
||||||
|
|
||||||
|
return models.Buy.find(defaultFilter, myOptions);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
<vn-crud-model
|
||||||
|
vn-id="buysModel"
|
||||||
|
url="Entries/{{$ctrl.$params.id}}/getBuys"
|
||||||
|
limit="5"
|
||||||
|
data="buys"
|
||||||
|
auto-load="true">
|
||||||
|
</vn-crud-model>
|
||||||
<vn-card class="summary">
|
<vn-card class="summary">
|
||||||
<h5>
|
<h5>
|
||||||
<a ng-if="::$ctrl.entryData.id"
|
<a ng-if="::$ctrl.entryData.id"
|
||||||
|
@ -95,7 +102,7 @@
|
||||||
<th translate center expand field="price3">Packing price</th>
|
<th translate center expand field="price3">Packing price</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody ng-repeat="line in $ctrl.buys">
|
<tbody ng-repeat="line in buys">
|
||||||
<tr>
|
<tr>
|
||||||
<td center title="{{::line.quantity}}">{{::line.quantity}}</td>
|
<td center title="{{::line.quantity}}">{{::line.quantity}}</td>
|
||||||
<td center title="{{::line.stickers | dashIfEmpty}}">{{::line.stickers | dashIfEmpty}}</td>
|
<td center title="{{::line.stickers | dashIfEmpty}}">{{::line.stickers | dashIfEmpty}}</td>
|
||||||
|
@ -156,6 +163,10 @@
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<vn-pagination
|
||||||
|
model="buysModel"
|
||||||
|
class="vn-pt-xs">
|
||||||
|
</vn-pagination>
|
||||||
</vn-auto>
|
</vn-auto>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
|
|
|
@ -10,10 +10,8 @@ class Controller extends Summary {
|
||||||
set entry(value) {
|
set entry(value) {
|
||||||
this._entry = value;
|
this._entry = value;
|
||||||
|
|
||||||
if (value && value.id) {
|
if (value && value.id)
|
||||||
this.getEntryData();
|
this.getEntryData();
|
||||||
this.getBuys();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntryData() {
|
getEntryData() {
|
||||||
|
@ -21,12 +19,6 @@ class Controller extends Summary {
|
||||||
this.entryData = response.data;
|
this.entryData = response.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getBuys() {
|
|
||||||
return this.$http.get(`Entries/${this.entry.id}/getBuys`).then(response => {
|
|
||||||
this.buys = response.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.vnComponent('vnEntrySummary', {
|
ngModule.vnComponent('vnEntrySummary', {
|
||||||
|
|
|
@ -46,20 +46,4 @@ describe('component vnEntrySummary', () => {
|
||||||
expect(controller.entryData).toEqual('I am the entryData');
|
expect(controller.entryData).toEqual('I am the entryData');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getBuys()', () => {
|
|
||||||
it('should perform a get asking for the buys of an entry', () => {
|
|
||||||
controller._entry = {id: 999};
|
|
||||||
|
|
||||||
const thatQuery = `Entries/${controller._entry.id}/getEntry`;
|
|
||||||
const query = `Entries/${controller._entry.id}/getBuys`;
|
|
||||||
|
|
||||||
$httpBackend.whenGET(thatQuery).respond('My Entries');
|
|
||||||
$httpBackend.expectGET(query).respond('Some buys');
|
|
||||||
controller.getBuys();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.buys).toEqual('Some buys');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('getTickets', {
|
||||||
|
description: 'Returns tickets for one invoiceOut',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
description: 'The invoiceOut id',
|
||||||
|
http: {source: 'path'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'filter',
|
||||||
|
type: 'object',
|
||||||
|
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: ['object'],
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:id/getTickets`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.getTickets = async(id, filter, options) => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
let myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const invoiceOut = await models.InvoiceOut.findById(id, {fields: 'ref'}, myOptions);
|
||||||
|
|
||||||
|
let defaultFilter = {
|
||||||
|
where: {refFk: invoiceOut.ref},
|
||||||
|
fields: [
|
||||||
|
'id',
|
||||||
|
'nickname',
|
||||||
|
'shipped',
|
||||||
|
'totalWithVat',
|
||||||
|
'clientFk'
|
||||||
|
]
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultFilter = mergeFilters(defaultFilter, filter);
|
||||||
|
|
||||||
|
return models.Ticket.find(defaultFilter, myOptions);
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('entry getTickets()', () => {
|
||||||
|
const invoiceOutId = 4;
|
||||||
|
it('should get the ticket of an invoiceOut', async() => {
|
||||||
|
const result = await app.models.InvoiceOut.getTickets(invoiceOutId);
|
||||||
|
|
||||||
|
expect(result.length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -7,14 +7,6 @@ describe('invoiceOut summary()', () => {
|
||||||
expect(result.invoiceOut.id).toEqual(1);
|
expect(result.invoiceOut.id).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should return a summary object containing data from it's tickets`, async() => {
|
|
||||||
const summary = await app.models.InvoiceOut.summary(1);
|
|
||||||
const tickets = summary.invoiceOut.tickets();
|
|
||||||
|
|
||||||
expect(summary.invoiceOut.ref).toEqual('T1111111');
|
|
||||||
expect(tickets.length).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should return a summary object containing it's supplier country`, async() => {
|
it(`should return a summary object containing it's supplier country`, async() => {
|
||||||
const summary = await app.models.InvoiceOut.summary(1);
|
const summary = await app.models.InvoiceOut.summary(1);
|
||||||
const supplier = summary.invoiceOut.supplier();
|
const supplier = summary.invoiceOut.supplier();
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('summary', {
|
Self.remoteMethod('summary', {
|
||||||
description: 'The invoiceOut summary',
|
description: 'The invoiceOut summary',
|
||||||
|
@ -22,7 +20,6 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.summary = async id => {
|
Self.summary = async id => {
|
||||||
const conn = Self.dataSource.connector;
|
|
||||||
let summary = {};
|
let summary = {};
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
|
@ -57,54 +54,20 @@ module.exports = Self => {
|
||||||
scope: {
|
scope: {
|
||||||
fields: ['id', 'socialName']
|
fields: ['id', 'socialName']
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
relation: 'tickets'
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
summary.invoiceOut = await Self.app.models.InvoiceOut.findOne(filter);
|
summary.invoiceOut = await Self.app.models.InvoiceOut.findOne(filter);
|
||||||
|
|
||||||
let stmts = [];
|
const invoiceOutTaxes = await Self.rawSql(`
|
||||||
|
|
||||||
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.ticket');
|
|
||||||
|
|
||||||
stmt = new ParameterizedSQL(`
|
|
||||||
CREATE TEMPORARY TABLE tmp.ticket
|
|
||||||
(INDEX (ticketFk)) ENGINE = MEMORY
|
|
||||||
SELECT id ticketFk FROM vn.ticket WHERE refFk=?`, [summary.invoiceOut.ref]);
|
|
||||||
stmts.push(stmt);
|
|
||||||
|
|
||||||
stmts.push('CALL ticketGetTotal()');
|
|
||||||
|
|
||||||
let ticketTotalsIndex = stmts.push('SELECT * FROM tmp.ticketTotal') - 1;
|
|
||||||
|
|
||||||
stmt = new ParameterizedSQL(`
|
|
||||||
SELECT iot.* , pgc.*, IF(pe.equFk IS NULL, taxableBase, 0) AS Base, pgc.rate / 100 as vatPercent
|
SELECT iot.* , pgc.*, IF(pe.equFk IS NULL, taxableBase, 0) AS Base, pgc.rate / 100 as vatPercent
|
||||||
FROM vn.invoiceOutTax iot
|
FROM vn.invoiceOutTax iot
|
||||||
JOIN vn.pgc ON pgc.code = iot.pgcFk
|
JOIN vn.pgc ON pgc.code = iot.pgcFk
|
||||||
LEFT JOIN vn.pgcEqu pe ON pe.equFk = pgc.code
|
LEFT JOIN vn.pgcEqu pe ON pe.equFk = pgc.code
|
||||||
WHERE invoiceOutFk = ?`, [summary.invoiceOut.id]);
|
WHERE invoiceOutFk = ?`, [summary.invoiceOut.id]);
|
||||||
let invoiceOutTaxesIndex = stmts.push(stmt) - 1;
|
|
||||||
|
|
||||||
stmts.push(
|
summary.invoiceOut.taxesBreakdown = invoiceOutTaxes;
|
||||||
`DROP TEMPORARY TABLE
|
|
||||||
tmp.ticket,
|
|
||||||
tmp.ticketTotal`);
|
|
||||||
|
|
||||||
let sql = ParameterizedSQL.join(stmts, ';');
|
|
||||||
let result = await conn.executeStmt(sql);
|
|
||||||
|
|
||||||
totalMap = {};
|
|
||||||
for (ticketTotal of result[ticketTotalsIndex])
|
|
||||||
totalMap[ticketTotal.ticketFk] = ticketTotal.total;
|
|
||||||
|
|
||||||
summary.invoiceOut.tickets().forEach(ticket => {
|
|
||||||
ticket.total = totalMap[ticket.id];
|
|
||||||
});
|
|
||||||
|
|
||||||
summary.invoiceOut.taxesBreakdown = result[invoiceOutTaxesIndex];
|
|
||||||
|
|
||||||
return summary;
|
return summary;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
require('../methods/invoiceOut/filter')(Self);
|
require('../methods/invoiceOut/filter')(Self);
|
||||||
require('../methods/invoiceOut/summary')(Self);
|
require('../methods/invoiceOut/summary')(Self);
|
||||||
|
require('../methods/invoiceOut/getTickets')(Self);
|
||||||
require('../methods/invoiceOut/download')(Self);
|
require('../methods/invoiceOut/download')(Self);
|
||||||
require('../methods/invoiceOut/delete')(Self);
|
require('../methods/invoiceOut/delete')(Self);
|
||||||
require('../methods/invoiceOut/book')(Self);
|
require('../methods/invoiceOut/book')(Self);
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
<vn-crud-model
|
||||||
|
vn-id="ticketsModel"
|
||||||
|
url="InvoiceOuts/{{$ctrl.$params.id}}/getTickets"
|
||||||
|
limit="10"
|
||||||
|
data="tickets"
|
||||||
|
auto-load="true">
|
||||||
|
</vn-crud-model>
|
||||||
<vn-card class="summary">
|
<vn-card class="summary">
|
||||||
<h5>
|
<h5>
|
||||||
<a ng-if="::$ctrl.summary.invoiceOut.id"
|
<a ng-if="::$ctrl.summary.invoiceOut.id"
|
||||||
|
@ -59,7 +66,7 @@
|
||||||
</vn-tr>
|
</vn-tr>
|
||||||
</vn-thead>
|
</vn-thead>
|
||||||
<vn-tbody>
|
<vn-tbody>
|
||||||
<vn-tr ng-repeat="ticket in $ctrl.summary.invoiceOut.tickets">
|
<vn-tr ng-repeat="ticket in tickets">
|
||||||
<vn-td number>
|
<vn-td number>
|
||||||
<span
|
<span
|
||||||
ng-click="ticketDescriptor.show($event, ticket.id)"
|
ng-click="ticketDescriptor.show($event, ticket.id)"
|
||||||
|
@ -75,10 +82,14 @@
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td expand>{{ticket.shipped | date: 'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
<vn-td expand>{{ticket.shipped | date: 'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
||||||
<vn-td number>{{ticket.total | currency: 'EUR': 2}}</vn-td>
|
<vn-td number>{{ticket.totalWithVat | currency: 'EUR': 2}}</vn-td>
|
||||||
</vn-tr>
|
</vn-tr>
|
||||||
</vn-tbody>
|
</vn-tbody>
|
||||||
</vn-table>
|
</vn-table>
|
||||||
|
<vn-pagination
|
||||||
|
model="ticketsModel"
|
||||||
|
class="vn-pt-xs">
|
||||||
|
</vn-pagination>
|
||||||
</vn-auto>
|
</vn-auto>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
|
|
|
@ -12,79 +12,98 @@ module.exports = Self => {
|
||||||
arg: 'ctx',
|
arg: 'ctx',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
http: {source: 'context'}
|
http: {source: 'context'}
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'filter',
|
arg: 'filter',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string`
|
description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'search',
|
arg: 'search',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: `If it's and number searchs by id, otherwise it searchs by nickname`
|
description: `If it's and number searchs by id, otherwise it searchs by nickname`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'from',
|
arg: 'from',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
description: `The from date filter`
|
description: `The from date filter`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'to',
|
arg: 'to',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
description: `The to date filter`
|
description: `The to date filter`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'nickname',
|
arg: 'nickname',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: `The nickname filter`
|
description: `The nickname filter`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'id',
|
arg: 'id',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: `The ticket id filter`
|
description: `The ticket id filter`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'clientFk',
|
arg: 'clientFk',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: `The client id filter`
|
description: `The client id filter`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'agencyModeFk',
|
arg: 'agencyModeFk',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: `The agency mode id filter`
|
description: `The agency mode id filter`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'warehouseFk',
|
arg: 'warehouseFk',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: `The warehouse id filter`
|
description: `The warehouse id filter`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'salesPersonFk',
|
arg: 'salesPersonFk',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: `The salesperson id filter`
|
description: `The salesperson id filter`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'provinceFk',
|
arg: 'provinceFk',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: `The province id filter`
|
description: `The province id filter`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'stateFk',
|
arg: 'stateFk',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: `The state id filter`
|
description: `The state id filter`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'myTeam',
|
arg: 'myTeam',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: `Whether to show only tickets for the current logged user team (For now it shows only the current user tickets)`
|
description: `Whether to show only tickets for the current logged user team (For now it shows only the current user tickets)`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'problems',
|
arg: 'problems',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: `Whether to show only tickets with problems`
|
description: `Whether to show only tickets with problems`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'pending',
|
arg: 'pending',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: `Whether to show only tickets with state 'Pending'`
|
description: `Whether to show only tickets with state 'Pending'`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'mine',
|
arg: 'mine',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: `Whether to show only tickets for the current logged user`
|
description: `Whether to show only tickets for the current logged user`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'orderFk',
|
arg: 'orderFk',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: `The order id filter`
|
description: `The order id filter`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'refFk',
|
arg: 'refFk',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: `The invoice reference filter`
|
description: `The invoice reference filter`
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'alertLevel',
|
arg: 'alertLevel',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: `The alert level of the tickets`
|
description: `The alert level of the tickets`
|
||||||
|
|
Loading…
Reference in New Issue