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