diff --git a/modules/supplier/back/methods/supplier/consumption.js b/modules/supplier/back/methods/supplier/consumption.js new file mode 100644 index 000000000..e8e22625f --- /dev/null +++ b/modules/supplier/back/methods/supplier/consumption.js @@ -0,0 +1,168 @@ + +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +const buildFilter = require('vn-loopback/util/filter').buildFilter; +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethodCtx('consumption', { + description: 'Find all instances of the model matched by filter from the data source.', + accessType: 'READ', + accepts: [ + { + arg: 'filter', + type: 'Object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string' + }, { + arg: 'search', + type: 'String', + description: `If it's and integer searchs by id, otherwise it searchs by name` + }, { + arg: 'itemId', + type: 'Number', + description: 'Item id' + }, { + arg: 'categoryId', + type: 'Number', + description: 'Category id' + }, { + arg: 'typeId', + type: 'Number', + description: 'Item type id', + }, { + arg: 'buyerId', + type: 'Number', + description: 'Buyer id' + }, { + arg: 'from', + type: 'Date', + description: `The from date filter` + }, { + arg: 'to', + type: 'Date', + description: `The to date filter` + } + ], + returns: { + type: ['Object'], + root: true + }, + http: { + path: `/consumption`, + verb: 'GET' + } + }); + + Self.consumption = async(ctx, filter) => { + const conn = Self.dataSource.connector; + const where = buildFilter(ctx.args, (param, value) => { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? {'i.id': value} + : {'i.name': {like: `%${value}%`}}; + case 'itemId': + return {'i.id': value}; + case 'description': + return {'i.description': {like: `%${value}%`}}; + case 'categoryId': + return {'it.categoryFk': value}; + case 'typeId': + return {'it.id': value}; + case 'buyerId': + return {'it.workerFk': value}; + case 'from': + return {'t.shipped': {gte: value}}; + case 'to': + return {'t.shipped': {lte: value}}; + } + }); + + filter = mergeFilters(filter, {where}); + let stmts = []; + let stmt; + + stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.entry'); + + stmt = new ParameterizedSQL( + `CREATE TEMPORARY TABLE tmp.entry + (INDEX (id)) + ENGINE = MEMORY + SELECT + e.id, + e.ref, + e.supplierFk, + t.shipped + FROM vn.entry e + JOIN vn.travel t ON t.id = e.travelFk + JOIN buy b ON b.id = b.entryFk + JOIN item i ON i.id = b.itemFk + JOIN itemType it ON it.id = i.typeFk`); + stmt.merge(conn.makeWhere(filter.where)); + stmt.merge(conn.makeGroupBy('e.id')); + stmt.merge(conn.makeLimit(filter)); + stmts.push(stmt); + + const entriesIndex = stmts.push('SELECT * FROM tmp.entry') - 1; + stmt = new ParameterizedSQL( + `SELECT + b.id AS buyId, + b.itemFk, + b.entryFk, + b.quantity, + CAST(b.buyingValue AS DECIMAL(10,2)) AS price, + CAST(SUM(b.buyingValue*b.quantity)AS DECIMAL(10,2)) AS total, + i.id, + i.description, + i.name AS itemName, + i.subName, + i.size AS itemSize, + i.typeFk AS itemTypeFk, + i.tag5, + i.value5, + i.tag6, + i.value6, + i.tag7, + i.value7, + i.tag8, + i.value8, + i.tag9, + i.value9, + i.tag10, + i.value10, + it.id, + it.workerFk, + it.categoryFk, + it.code AS itemTypeCode + FROM buy b + JOIN tmp.entry e ON e.id = b.entryFk + JOIN item i ON i.id = b.itemFk + JOIN itemType it ON it.id = i.typeFk` + ); + stmt.merge('WHERE b.quantity > 0'); + stmt.merge(conn.makeGroupBy('b.id')); + stmt.merge(conn.makeOrderBy(filter.order)); + const buysIndex = stmts.push(stmt) - 1; + stmts.push(`DROP TEMPORARY TABLE tmp.entry`); + const sql = ParameterizedSQL.join(stmts, ';'); + + const result = await conn.executeStmt(sql); + + const entries = result[entriesIndex]; + const buys = result[buysIndex]; + const entriesMap = new Map(); + + for (let entry of entries) + entriesMap.set(entry.id, entry); + + for (let buy of buys) { + const entry = entriesMap.get(buy.entryFk); + + if (entry) { + if (!entry.buys) entry.buys = []; + + entry.buys.push(buy); + } + } + return entries; + }; +}; diff --git a/modules/supplier/back/methods/supplier/specs/consumption.spec.js b/modules/supplier/back/methods/supplier/specs/consumption.spec.js new file mode 100644 index 000000000..0e8f3afcc --- /dev/null +++ b/modules/supplier/back/methods/supplier/specs/consumption.spec.js @@ -0,0 +1,36 @@ +const app = require('vn-loopback/server/server'); + +describe('supplier consumption() filter', () => { + it('should return a list of entries from the supplier 2', async() => { + const ctx = {req: {accessToken: {userId: 9}}, args: {}}; + const filter = { + where: { + supplierFk: 2 + }, + order: 'itemTypeFk, itemName, itemSize' + }; + const result = await app.models.Supplier.consumption(ctx, filter); + + expect(result.length).toEqual(6); + }); + + it('should return a list of entries from the item id 1 and supplier 1', async() => { + const ctx = {req: {accessToken: {userId: 9}}, + args: { + itemId: 1 + } + }; + const filter = { + where: { + supplierFk: 1 + }, + order: 'itemTypeFk, itemName, itemSize' + }; + const result = await app.models.Supplier.consumption(ctx, filter); + + const expectedItemId = 1; + const firstRowBuys = result[0].buys[0]; + + expect(firstRowBuys.itemFk).toEqual(expectedItemId); + }); +}); diff --git a/modules/supplier/back/models/supplier.js b/modules/supplier/back/models/supplier.js index 73fd9d854..a95df9f20 100644 --- a/modules/supplier/back/models/supplier.js +++ b/modules/supplier/back/models/supplier.js @@ -5,6 +5,7 @@ module.exports = Self => { require('../methods/supplier/filter')(Self); require('../methods/supplier/getSummary')(Self); require('../methods/supplier/updateFiscalData')(Self); + require('../methods/supplier/consumption')(Self); Self.validatesPresenceOf('name', { message: 'The social name cannot be empty' diff --git a/modules/supplier/front/consumption-search-panel/index.html b/modules/supplier/front/consumption-search-panel/index.html new file mode 100644 index 000000000..e957c891b --- /dev/null +++ b/modules/supplier/front/consumption-search-panel/index.html @@ -0,0 +1,68 @@ +
+
+ + + + + + + + + {{nickname}} + + + + + +
{{name}}
+
+ {{category.name}} +
+
+
+ + +
+ + + + + + + + + +
+
diff --git a/modules/supplier/front/consumption-search-panel/index.js b/modules/supplier/front/consumption-search-panel/index.js new file mode 100644 index 000000000..f6c63c55c --- /dev/null +++ b/modules/supplier/front/consumption-search-panel/index.js @@ -0,0 +1,7 @@ +import ngModule from '../module'; +import SearchPanel from 'core/components/searchbar/search-panel'; + +ngModule.vnComponent('vnSupplierConsumptionSearchPanel', { + template: require('./index.html'), + controller: SearchPanel +}); diff --git a/modules/supplier/front/consumption-search-panel/locale/es.yml b/modules/supplier/front/consumption-search-panel/locale/es.yml new file mode 100644 index 000000000..f136283f8 --- /dev/null +++ b/modules/supplier/front/consumption-search-panel/locale/es.yml @@ -0,0 +1,7 @@ +Item id: Id artículo +From: Desde +To: Hasta +Campaign: Campaña +allSaints: Día de todos los Santos +valentinesDay: Día de San Valentín +mothersDay: Día de la madre \ No newline at end of file diff --git a/modules/supplier/front/consumption/index.html b/modules/supplier/front/consumption/index.html new file mode 100644 index 000000000..a0df60ea9 --- /dev/null +++ b/modules/supplier/front/consumption/index.html @@ -0,0 +1,84 @@ + + + + + + + + +
+ + + + + + +
+ + + + Entry + {{::entry.id}} + Date + {{::entry.shipped | date: 'dd/MM/yyyy'}} + Reference + {{::entry.ref}} + + + + + + + {{::buy.itemName}} + + + + + + + {{::buy.quantity | dashIfEmpty}} + {{::buy.price | dashIfEmpty}} + {{::buy.total | dashIfEmpty}} + + + + + + + + + + + + +
+
+ + diff --git a/modules/supplier/front/consumption/index.js b/modules/supplier/front/consumption/index.js new file mode 100644 index 000000000..9e1357a9f --- /dev/null +++ b/modules/supplier/front/consumption/index.js @@ -0,0 +1,65 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +class Controller extends Section { + constructor($element, $, vnReport, vnEmail) { + super($element, $); + this.vnReport = vnReport; + this.vnEmail = vnEmail; + + this.setDefaultFilter(); + } + + setDefaultFilter() { + const minDate = new Date(); + minDate.setHours(0, 0, 0, 0); + minDate.setMonth(minDate.getMonth() - 2); + + const maxDate = new Date(); + maxDate.setHours(23, 59, 59, 59); + + this.filterParams = { + from: minDate, + to: maxDate + }; + } + + get reportParams() { + const userParams = this.$.model.userParams; + return Object.assign({ + authorization: this.vnToken.token, + recipientId: this.supplier.id + }, userParams); + } + + showReport() { + this.vnReport.show('supplier-campaign-metrics', this.reportParams); + } + + sendEmail() { + this.vnEmail.send('supplier-campaign-metrics', this.reportParams); + } + + getTotal(entry) { + if (entry.buys) { + let total = 0; + for (let buy of entry.buys) + total += buy.total; + + return total; + } + } +} + +Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail']; + +ngModule.vnComponent('vnSupplierConsumption', { + template: require('./index.html'), + controller: Controller, + bindings: { + supplier: '<' + }, + require: { + card: '^vnSupplierCard' + } +}); diff --git a/modules/supplier/front/consumption/index.spec.js b/modules/supplier/front/consumption/index.spec.js new file mode 100644 index 000000000..35eb6935c --- /dev/null +++ b/modules/supplier/front/consumption/index.spec.js @@ -0,0 +1,72 @@ +import './index.js'; +import crudModel from 'core/mocks/crud-model'; + +describe('Supplier', () => { + describe('Component vnSupplierConsumption', () => { + let $scope; + let controller; + let $httpParamSerializer; + let $httpBackend; + + beforeEach(ngModule('supplier')); + + beforeEach(inject(($componentController, $rootScope, _$httpParamSerializer_, _$httpBackend_) => { + $scope = $rootScope.$new(); + $httpParamSerializer = _$httpParamSerializer_; + $httpBackend = _$httpBackend_; + const $element = angular.element(' { + it('should call the window.open function', () => { + jest.spyOn(window, 'open').mockReturnThis(); + + const now = new Date(); + controller.$.model.userParams = { + from: now, + to: now + }; + + controller.showReport(); + + const expectedParams = { + recipientId: 2, + from: now, + to: now + }; + const serializedParams = $httpParamSerializer(expectedParams); + const path = `api/report/supplier-campaign-metrics?${serializedParams}`; + + expect(window.open).toHaveBeenCalledWith(path); + }); + }); + + describe('sendEmail()', () => { + it('should make a GET query sending the report', () => { + const now = new Date(); + controller.$.model.userParams = { + from: now, + to: now + }; + const expectedParams = { + recipientId: 2, + from: now, + to: now + }; + + const serializedParams = $httpParamSerializer(expectedParams); + const path = `email/supplier-campaign-metrics?${serializedParams}`; + + $httpBackend.expect('GET', path).respond({}); + controller.sendEmail(); + $httpBackend.flush(); + }); + }); + }); +}); + diff --git a/modules/supplier/front/consumption/locale/es.yml b/modules/supplier/front/consumption/locale/es.yml new file mode 100644 index 000000000..dd052696c --- /dev/null +++ b/modules/supplier/front/consumption/locale/es.yml @@ -0,0 +1,2 @@ + +Total entry: Total entrada diff --git a/modules/supplier/front/index.js b/modules/supplier/front/index.js index 22f402a0c..9a9334c41 100644 --- a/modules/supplier/front/index.js +++ b/modules/supplier/front/index.js @@ -10,4 +10,6 @@ import './basic-data'; import './fiscal-data'; import './contact'; import './log'; +import './consumption'; +import './consumption-search-panel'; import './billing-data'; diff --git a/modules/supplier/front/routes.json b/modules/supplier/front/routes.json index 47f742a70..54d203c8c 100644 --- a/modules/supplier/front/routes.json +++ b/modules/supplier/front/routes.json @@ -13,7 +13,8 @@ {"state": "supplier.card.fiscalData", "icon": "account_balance"}, {"state": "supplier.card.billingData", "icon": "icon-payment"}, {"state": "supplier.card.contact", "icon": "contact_phone"}, - {"state": "supplier.card.log", "icon": "history"} + {"state": "supplier.card.log", "icon": "history"}, + {"state": "supplier.card.consumption", "icon": "show_chart"} ] }, "routes": [ @@ -77,6 +78,15 @@ "supplier": "$ctrl.supplier" } }, + { + "url": "/consumption?q", + "state": "supplier.card.consumption", + "component": "vn-supplier-consumption", + "description": "Consumption", + "params": { + "supplier": "$ctrl.supplier" + } + }, { "url": "/billing-data", "state": "supplier.card.billingData", diff --git a/print/templates/email/supplier-campaign-metrics/assets/css/import.js b/print/templates/email/supplier-campaign-metrics/assets/css/import.js new file mode 100644 index 000000000..b44d6bd37 --- /dev/null +++ b/print/templates/email/supplier-campaign-metrics/assets/css/import.js @@ -0,0 +1,8 @@ +const Stylesheet = require(`${appPath}/core/stylesheet`); + +module.exports = new Stylesheet([ + `${appPath}/common/css/spacing.css`, + `${appPath}/common/css/misc.css`, + `${appPath}/common/css/layout.css`, + `${appPath}/common/css/email.css`]) + .mergeStyles(); diff --git a/print/templates/email/supplier-campaign-metrics/attachments.json b/print/templates/email/supplier-campaign-metrics/attachments.json new file mode 100644 index 000000000..4eacb54db --- /dev/null +++ b/print/templates/email/supplier-campaign-metrics/attachments.json @@ -0,0 +1,6 @@ +[ + { + "filename": "supplier-campaign-metrics.pdf", + "component": "supplier-campaign-metrics" + } +] \ No newline at end of file diff --git a/print/templates/email/supplier-campaign-metrics/locale/es.yml b/print/templates/email/supplier-campaign-metrics/locale/es.yml new file mode 100644 index 000000000..d1c1182a2 --- /dev/null +++ b/print/templates/email/supplier-campaign-metrics/locale/es.yml @@ -0,0 +1,8 @@ +subject: Informe de consumo +title: Informe de consumo +dear: Estimado cliente +description: Tal y como nos ha solicitado nos complace + relacionarle a continuación el consumo que nos consta en su cuenta para las + fechas comprendidas entre {0} y {1}. + Espero le sea de utilidad para preparar su pedido.

+ Al mismo tiempo aprovecho la ocasión para saludarle cordialmente. diff --git a/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.html b/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.html new file mode 100644 index 000000000..9d7014f34 --- /dev/null +++ b/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.html @@ -0,0 +1,46 @@ + + + + + + {{ $t('subject') }} + + + + + + + + +
+ +
+
+
+ +
+
+ +
+
+ +
+
+

{{ $t('title') }}

+

{{$t('dear')}},

+

+
+
+ +
+
+ +
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.js b/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.js new file mode 100755 index 000000000..20113d8ea --- /dev/null +++ b/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.js @@ -0,0 +1,33 @@ +const Component = require(`${appPath}/core/component`); +const emailHeader = new Component('email-header'); +const emailFooter = new Component('email-footer'); + +module.exports = { + name: 'supplier-campaign-metrics', + created() { + this.filters = this.$options.filters; + }, + computed: { + minDate: function() { + return this.filters.date(this.from, '%d-%m-%Y'); + }, + maxDate: function() { + return this.filters.date(this.to, '%d-%m-%Y'); + } + }, + components: { + 'email-header': emailHeader.build(), + 'email-footer': emailFooter.build() + }, + props: { + recipientId: { + required: true + }, + from: { + required: true + }, + to: { + required: true + } + } +}; diff --git a/print/templates/reports/supplier-campaign-metrics/assets/css/import.js b/print/templates/reports/supplier-campaign-metrics/assets/css/import.js new file mode 100644 index 000000000..fd8796c2b --- /dev/null +++ b/print/templates/reports/supplier-campaign-metrics/assets/css/import.js @@ -0,0 +1,9 @@ +const Stylesheet = require(`${appPath}/core/stylesheet`); + +module.exports = new Stylesheet([ + `${appPath}/common/css/spacing.css`, + `${appPath}/common/css/misc.css`, + `${appPath}/common/css/layout.css`, + `${appPath}/common/css/report.css`, + `${__dirname}/style.css`]) + .mergeStyles(); diff --git a/print/templates/reports/supplier-campaign-metrics/assets/css/style.css b/print/templates/reports/supplier-campaign-metrics/assets/css/style.css new file mode 100644 index 000000000..32caeb43c --- /dev/null +++ b/print/templates/reports/supplier-campaign-metrics/assets/css/style.css @@ -0,0 +1,20 @@ +.column-oriented { + margin-top: 0px; +} + +.bottom-line > tr { + border-bottom: 1px solid #ccc; +} + +.bottom-line tr:nth-last-child() { + border-bottom: none; +} + +h2 { + font-weight: 100; + color: #555; +} + +.description strong { + text-transform: uppercase; +} \ No newline at end of file diff --git a/print/templates/reports/supplier-campaign-metrics/locale/es.yml b/print/templates/reports/supplier-campaign-metrics/locale/es.yml new file mode 100644 index 000000000..31c1e17dd --- /dev/null +++ b/print/templates/reports/supplier-campaign-metrics/locale/es.yml @@ -0,0 +1,13 @@ +title: Consumo +Supplier: Proveedor +supplierData: Datos del proveedor +dated: Fecha +From: Desde +To: Hasta +supplier: Proveedor {0} +reference: Referencia +Quantity: Cantidad +entry: Entrada +itemName: Artículo +price: Precio +total: Total \ No newline at end of file diff --git a/print/templates/reports/supplier-campaign-metrics/sql/buys.sql b/print/templates/reports/supplier-campaign-metrics/sql/buys.sql new file mode 100644 index 000000000..a094ac205 --- /dev/null +++ b/print/templates/reports/supplier-campaign-metrics/sql/buys.sql @@ -0,0 +1,33 @@ +SELECT + b.id AS buyId, + b.itemFk, + b.entryFk, + CAST(b.buyingValue AS DECIMAL(10,2)) AS price, + b.quantity, + i.id, + i.description, + i.name AS itemName, + i.subName, + i.size AS itemSize, + i.typeFk AS itemTypeFk, + i.tag5, + i.value5, + i.tag6, + i.value6, + i.tag7, + i.value7, + i.tag8, + i.value8, + i.tag9, + i.value9, + i.tag10, + i.value10, + it.id, + it.workerFk, + it.categoryFk, + it.code AS itemTypeCode + FROM buy b + JOIN item i ON i.id = b.itemFk + JOIN itemType it ON it.id = i.typeFk + WHERE b.entryFk IN(:entriesId) AND b.quantity > 0 + ORDER BY i.typeFk , i.name \ No newline at end of file diff --git a/print/templates/reports/supplier-campaign-metrics/sql/entries.sql b/print/templates/reports/supplier-campaign-metrics/sql/entries.sql new file mode 100644 index 000000000..aa458dda0 --- /dev/null +++ b/print/templates/reports/supplier-campaign-metrics/sql/entries.sql @@ -0,0 +1,8 @@ +SELECT + e.id, + e.ref, + e.supplierFk, + t.shipped + FROM vn.entry e + JOIN vn.travel t ON t.id = e.travelFk + WHERE e.supplierFk = ? AND DATE(t.shipped) BETWEEN ? AND ? diff --git a/print/templates/reports/supplier-campaign-metrics/sql/supplier.sql b/print/templates/reports/supplier-campaign-metrics/sql/supplier.sql new file mode 100644 index 000000000..0c2fa12ed --- /dev/null +++ b/print/templates/reports/supplier-campaign-metrics/sql/supplier.sql @@ -0,0 +1,12 @@ +SELECT + s.street, + s.city, + s.postcode, + s.id, + s.name AS supplierName, + p.name AS province, + co.country + FROM supplier s + JOIN province p ON s.provinceFk = p.id + JOIN country co ON s.countryFk = co.id + WHERE s.id = ? diff --git a/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.html b/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.html new file mode 100644 index 000000000..1303f2266 --- /dev/null +++ b/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.html @@ -0,0 +1,105 @@ + + + + + + + + + +
+ + + +
+
+
+
+

{{$t('title')}}

+
+ + + + + + + + + + + + + + + +
{{$t('Supplier')}}{{supplier.id}}
{{$t('From')}}{{from | date('%d-%m-%Y')}}
{{$t('To')}}{{to | date('%d-%m-%Y')}}
+
+
+
+
+
{{$t('supplierData')}}
+
+

{{supplier.supplierName}}

+
+ {{supplier.street}} +
+
+ {{supplier.postcode}}, {{supplier.city}} ({{supplier.province}}) +
+
+ {{supplier.country}} +
+
+
+
+
+
+

+ {{$t('entry')}} {{entry.id}} + {{$t('dated')}} {{entry.shipped | date('%d-%m-%Y')}} + {{$t('reference')}} {{entry.ref}} +

+ + + + + + + + + + + + + + + + + + + + +
{{$t('itemName')}}{{$t('Quantity')}}{{$t('price')}}{{$t('total')}}
{{buy.itemName}}{{buy.quantity}}{{buy.price | currency('EUR', $i18n.locale)}}{{buy.quantity * buy.price | currency('EUR', $i18n.locale)}}
+ + {{buy.tag5}} {{buy.value5}} + + + {{buy.tag6}} {{buy.value6}} + + + {{buy.tag7}} {{buy.value7}} + +
+ +
+
+
+ + + +
+ + \ No newline at end of file diff --git a/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.js b/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.js new file mode 100755 index 000000000..8a0a378a2 --- /dev/null +++ b/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.js @@ -0,0 +1,61 @@ +const Component = require(`${appPath}/core/component`); +const reportHeader = new Component('report-header'); +const reportFooter = new Component('report-footer'); + +module.exports = { + name: 'supplier-campaign-metrics', + async serverPrefetch() { + this.supplier = await this.fetchSupplier(this.recipientId); + let entries = await this.fetchEntries(this.recipientId, this.from, this.to); + + const entriesId = []; + + for (let entry of entries) + entriesId.push(entry.id); + + const buys = await this.fetchBuys(entriesId); + + const entriesMap = new Map(); + for (let entry of entries) + entriesMap.set(entry.id, entry); + + for (let buy of buys) { + const entry = entriesMap.get(buy.entryFk); + if (entry) { + if (!entry.buys) entry.buys = []; + + entry.buys.push(buy); + } + } + + this.entries = entries; + if (!this.supplier) + throw new Error('Something went wrong'); + }, + methods: { + fetchSupplier(supplierId) { + return this.findOneFromDef('supplier', [supplierId]); + }, + fetchEntries(supplierId, from, to) { + return this.rawSqlFromDef('entries', [supplierId, from, to]); + }, + fetchBuys(entriesId) { + return this.rawSqlFromDef('buys', {entriesId}); + } + }, + components: { + 'report-header': reportHeader.build(), + 'report-footer': reportFooter.build() + }, + props: { + recipientId: { + required: true + }, + from: { + required: true + }, + to: { + required: true + } + } +};