Merge pull request '2931-entry_summary_pagination' (#650) from 2931-entry_summary_pagination into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #650
Reviewed-by: Joan Sanchez <joan@verdnatura.es>
This commit is contained in:
Joan Sanchez 2021-06-14 10:58:48 +00:00
commit bbac4483cd
11 changed files with 146 additions and 98 deletions

View File

@ -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);
}; };
}; };

View File

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

View File

@ -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', {

View File

@ -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');
});
});
}); });

View File

@ -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);
};
};

View File

@ -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);
});
});

View File

@ -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();

View File

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

View File

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

View File

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

View File

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