diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql
index 13ac2bcb3..e48a20ec6 100644
--- a/db/dump/fixtures.sql
+++ b/db/dump/fixtures.sql
@@ -664,12 +664,12 @@ INSERT INTO `vn`.`itemCategory`(`id`, `name`, `display`, `color`, `icon`, `code`
INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `life`,`workerFk`, `isPackaging`)
VALUES
- (1, 'CRI', 'Crisantemo', 2, 31, 5, 0),
- (2, 'ITG', 'Anthurium', 1, 31, 5, 0),
- (3, 'WPN', 'Paniculata', 2, 31, 5, 0),
- (4, 'PRT', 'Delivery ports', 3, NULL, 5, 1),
- (5, 'CON', 'Container', 3, NULL, 5, 1),
- (6, 'ALS', 'Alstroemeria', 1, 31, 5, 0);
+ (1, 'CRI', 'Crisantemo', 2, 31, 35, 0),
+ (2, 'ITG', 'Anthurium', 1, 31, 35, 0),
+ (3, 'WPN', 'Paniculata', 2, 31, 35, 0),
+ (4, 'PRT', 'Delivery ports', 3, NULL, 35, 1),
+ (5, 'CON', 'Container', 3, NULL, 35, 1),
+ (6, 'ALS', 'Alstroemeria', 1, 31, 35, 0);
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`)
VALUES
diff --git a/modules/client/back/methods/client/consumption.js b/modules/client/back/methods/client/consumption.js
new file mode 100644
index 000000000..cb5b39c29
--- /dev/null
+++ b/modules/client/back/methods/client/consumption.js
@@ -0,0 +1,122 @@
+
+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: 'itemFk',
+ type: 'Integer',
+ description: 'Item id'
+ }, {
+ arg: 'categoryFk',
+ type: 'Integer',
+ description: 'Category id'
+ }, {
+ arg: 'typeFk',
+ type: 'Integer',
+ description: 'Item type id',
+ }, {
+ arg: 'buyerFk',
+ type: 'Integer',
+ description: 'Buyer id'
+ }, {
+ arg: 'from',
+ type: 'Date',
+ description: `The from date filter`
+ }, {
+ arg: 'to',
+ type: 'Date',
+ description: `The to date filter`
+ }, {
+ arg: 'grouped',
+ type: 'Boolean',
+ description: 'Group by item'
+ }
+ ],
+ returns: {
+ type: ['Object'],
+ root: true
+ },
+ http: {
+ path: `/consumption`,
+ verb: 'GET'
+ }
+ });
+
+ Self.consumption = async(ctx, filter) => {
+ const conn = Self.dataSource.connector;
+ const args = ctx.args;
+ 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};
+ }
+ });
+ filter = mergeFilters(filter, {where});
+
+ let stmt = new ParameterizedSQL('SELECT');
+ if (args.grouped)
+ stmt.merge(`SUM(s.quantity) AS quantity,`);
+ else
+ stmt.merge(`s.quantity,`);
+
+ stmt.merge(`s.itemFk,
+ s.concept,
+ s.ticketFk,
+ t.shipped,
+ i.name AS itemName,
+ i.size AS itemSize,
+ i.typeFk AS itemTypeFk,
+ i.subName,
+ i.tag5,
+ i.value5,
+ i.tag6,
+ i.value6,
+ i.tag7,
+ i.value7,
+ i.tag8,
+ i.value8,
+ i.tag9,
+ i.value9,
+ i.tag10,
+ i.value10
+ FROM sale s
+ JOIN ticket t ON t.id = s.ticketFk
+ JOIN item i ON i.id = s.itemFk
+ JOIN itemType it ON it.id = i.typeFk`, [args.grouped]);
+
+ stmt.merge(conn.makeWhere(filter.where));
+
+ if (args.grouped)
+ stmt.merge(`GROUP BY s.itemFk`);
+
+ stmt.merge(conn.makePagination(filter));
+
+ return conn.executeStmt(stmt);
+ };
+};
diff --git a/modules/client/back/methods/client/specs/consumption.spec.js b/modules/client/back/methods/client/specs/consumption.spec.js
new file mode 100644
index 000000000..e7a42a8da
--- /dev/null
+++ b/modules/client/back/methods/client/specs/consumption.spec.js
@@ -0,0 +1,40 @@
+const app = require('vn-loopback/server/server');
+
+describe('client consumption() filter', () => {
+ it('should return a list of buyed items by ticket', async() => {
+ const ctx = {req: {accessToken: {userId: 9}}, args: {}};
+ const filter = {
+ where: {
+ clientFk: 101
+ },
+ order: 'itemTypeFk, itemName, itemSize'
+ };
+ const result = await app.models.Client.consumption(ctx, filter);
+
+ expect(result.length).toEqual(10);
+ });
+
+ it('should return a list of tickets grouped by item', async() => {
+ const ctx = {req: {accessToken: {userId: 9}},
+ args: {
+ grouped: true
+ }
+ };
+ const filter = {
+ where: {
+ clientFk: 101
+ },
+ order: 'itemTypeFk, itemName, itemSize'
+ };
+ const result = await app.models.Client.consumption(ctx, filter);
+
+ const firstRow = result[0];
+ const secondRow = result[1];
+ const thirdRow = result[2];
+
+ expect(result.length).toEqual(3);
+ expect(firstRow.quantity).toEqual(10);
+ expect(secondRow.quantity).toEqual(15);
+ expect(thirdRow.quantity).toEqual(20);
+ });
+});
diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js
index cf3ded2f8..2e92d70a2 100644
--- a/modules/client/back/models/client.js
+++ b/modules/client/back/models/client.js
@@ -2,7 +2,6 @@ let request = require('request-promise-native');
let UserError = require('vn-loopback/util/user-error');
let getFinalState = require('vn-loopback/util/hook').getFinalState;
let isMultiple = require('vn-loopback/util/hook').isMultiple;
-const httpParamSerializer = require('vn-loopback/util/http').httpParamSerializer;
const LoopBackContext = require('loopback-context');
module.exports = Self => {
@@ -27,6 +26,7 @@ module.exports = Self => {
require('../methods/client/sendSms')(Self);
require('../methods/client/createAddress')(Self);
require('../methods/client/updateAddress')(Self);
+ require('../methods/client/consumption')(Self);
// Validations
diff --git a/modules/client/front/consumption-search-panel/index.html b/modules/client/front/consumption-search-panel/index.html
new file mode 100644
index 000000000..e957c891b
--- /dev/null
+++ b/modules/client/front/consumption-search-panel/index.html
@@ -0,0 +1,68 @@
+
diff --git a/modules/client/front/consumption-search-panel/index.js b/modules/client/front/consumption-search-panel/index.js
new file mode 100644
index 000000000..9b9354a93
--- /dev/null
+++ b/modules/client/front/consumption-search-panel/index.js
@@ -0,0 +1,7 @@
+import ngModule from '../module';
+import SearchPanel from 'core/components/searchbar/search-panel';
+
+ngModule.component('vnConsumptionSearchPanel', {
+ template: require('./index.html'),
+ controller: SearchPanel
+});
diff --git a/modules/client/front/consumption-search-panel/locale/es.yml b/modules/client/front/consumption-search-panel/locale/es.yml
new file mode 100644
index 000000000..8d5276194
--- /dev/null
+++ b/modules/client/front/consumption-search-panel/locale/es.yml
@@ -0,0 +1,4 @@
+Ink: Tinta
+Origin: Origen
+Producer: Productor
+For me: Para mi
\ No newline at end of file
diff --git a/modules/client/front/consumption/index.html b/modules/client/front/consumption/index.html
index 272c58a7d..8ea65ecae 100644
--- a/modules/client/front/consumption/index.html
+++ b/modules/client/front/consumption/index.html
@@ -1,89 +1,91 @@
-
+ order="itemTypeFk, itemName, itemSize">
-
+
+
+
+
+
-
-
-
-
-
+
- Date
- Id
- State
- Reference
- Client
- In
- Out
- Balance
+ Item
+ Ticket
+ Fecha
+ Description
+ Quantity
-
-
- {{::sale.shipped | date:'dd/MM/yyyy' }}
+ ng-repeat="sale in sales">
+
+
+ {{::sale.itemFk}}
-
- {{::sale.origin | dashIfEmpty}}
-
-
- {{::sale.stateName | dashIfEmpty}}
- {{::sale.reference | dashIfEmpty}}
-
-
- {{::sale.name | dashIfEmpty}}
-
-
- {{::sale.name | dashIfEmpty}}
+ {{::sale.ticketFk}}
- {{::sale.in | dashIfEmpty}}
- {{::sale.out | dashIfEmpty}}
-
-
- {{::sale.balance | dashIfEmpty}}
-
+ {{::sale.shipped | date: 'dd/MM/yyyy'}}
+
+
+
+ {{::sale.quantity | dashIfEmpty}}
-
-
+
+
+
-
-
+
+
diff --git a/modules/client/front/consumption/index.js b/modules/client/front/consumption/index.js
index c9be2ceb4..6b6bec89f 100644
--- a/modules/client/front/consumption/index.js
+++ b/modules/client/front/consumption/index.js
@@ -5,6 +5,31 @@ import './style.scss';
class Controller extends Section {
constructor($element, $) {
super($element, $);
+ this.filter = {
+ where: {
+ isPackaging: false
+ }
+ };
+
+ 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.client.id
+ }, userParams);
}
showTicketDescriptor(event, sale) {
@@ -12,6 +37,24 @@ class Controller extends Section {
this.$.ticketDescriptor.show(event.target, sale.origin);
}
+
+ showReport() {
+ const params = this.reportParams;
+ const serializedParams = this.$httpParamSerializer(params);
+ window.open(`api/report/campaign-metrics?${serializedParams}`);
+ }
+
+ sendEmail() {
+ const params = this.reportParams;
+ this.$http.get(`email/campaign-metrics`, {params})
+ .then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
+ }
+
+ changeGrouped(value) {
+ const model = this.$.model;
+
+ model.addFilter({}, {grouped: value});
+ }
}
Controller.$inject = ['$element', '$scope'];
@@ -21,5 +64,8 @@ ngModule.component('vnClientConsumption', {
controller: Controller,
bindings: {
client: '<'
+ },
+ require: {
+ card: '^vnClientCard'
}
});
diff --git a/modules/client/front/consumption/index.spec.js b/modules/client/front/consumption/index.spec.js
index b6e50e5c5..d76bc1e76 100644
--- a/modules/client/front/consumption/index.spec.js
+++ b/modules/client/front/consumption/index.spec.js
@@ -1,63 +1,70 @@
import './index.js';
import crudModel from 'core/mocks/crud-model';
-describe('Item', () => {
- describe('Component vnItemDiary', () => {
+describe('Client', () => {
+ describe('Component vnClientConsumption', () => {
let $scope;
let controller;
+ let $httpParamSerializer;
+ let $httpBackend;
- beforeEach(ngModule('item'));
+ beforeEach(ngModule('client'));
- beforeEach(angular.mock.inject(($componentController, $rootScope) => {
+ beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpParamSerializer_, _$httpBackend_) => {
$scope = $rootScope.$new();
- const $element = angular.element('');
- controller = $componentController('vnItemDiary', {$element, $scope});
+ $httpParamSerializer = _$httpParamSerializer_;
+ $httpBackend = _$httpBackend_;
+ const $element = angular.element(' {
- it('should set warehouseFk property based on itemType warehouseFk', () => {
- jest.spyOn(controller.$, '$applyAsync');
- controller.item = {id: 1, itemType: {warehouseFk: 1}};
+ describe('showReport()', () => {
+ it('should call the window.open function', () => {
+ jest.spyOn(window, 'open').mockReturnThis();
- expect(controller.$.$applyAsync).toHaveBeenCalledWith(jasmine.any(Function));
- $scope.$apply();
+ const now = new Date();
+ controller.$.model.userParams = {
+ from: now,
+ to: now
+ };
- expect(controller.warehouseFk).toEqual(1);
- expect(controller.item.id).toEqual(1);
- });
+ controller.showReport();
- it(`should set warehouseFk property based on url query warehouseFk`, () => {
- jest.spyOn(controller.$, '$applyAsync');
- controller.$params.warehouseFk = 4;
- controller.item = {id: 1, itemType: {warehouseFk: 1}};
+ const expectedParams = {
+ recipientId: 101,
+ from: now,
+ to: now
+ };
+ const serializedParams = $httpParamSerializer(expectedParams);
+ const path = `api/report/campaign-metrics?${serializedParams}`;
- expect(controller.$.$applyAsync).toHaveBeenCalledWith(jasmine.any(Function));
- $scope.$apply();
-
- expect(controller.warehouseFk).toEqual(4);
- expect(controller.item.id).toEqual(1);
+ expect(window.open).toHaveBeenCalledWith(path);
});
});
- describe('scrollToLine ()', () => {
- it('should assign $location then call anchorScroll using controller value', () => {
- jest.spyOn(controller, '$anchorScroll');
- controller.lineFk = 1;
- controller.scrollToLine('invalidValue');
+ 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: 101,
+ from: now,
+ to: now
+ };
- expect(controller.$location.hash()).toEqual(`vnItemDiary-${1}`);
- expect(controller.$anchorScroll).toHaveBeenCalledWith();
- });
+ const serializedParams = $httpParamSerializer(expectedParams);
+ const path = `email/campaign-metrics?${serializedParams}`;
- it('should assign $location then call anchorScroll using received value', () => {
- jest.spyOn(controller, '$anchorScroll');
- controller.lineFk = undefined;
- controller.scrollToLine(1);
-
- expect(controller.$location.hash()).toEqual(`vnItemDiary-${1}`);
- expect(controller.$anchorScroll).toHaveBeenCalledWith();
+ $httpBackend.expect('GET', path).respond({});
+ controller.sendEmail();
+ $httpBackend.flush();
});
});
});
diff --git a/modules/client/front/consumption/locale/es.yml b/modules/client/front/consumption/locale/es.yml
index e00816d78..adf0f060c 100644
--- a/modules/client/front/consumption/locale/es.yml
+++ b/modules/client/front/consumption/locale/es.yml
@@ -1,4 +1,6 @@
-In: Entrada
-Out: Salida
-Visible quantity: Cantidad visible
-Ticket/Entry: Ticket/Entrada
\ No newline at end of file
+Group by item: Agrupar por artículo
+Open as PDF: Abrir como PDF
+Send to email: Enviar por email
+Search by item id or name: Buscar por id de artículo o nombre
+The consumption report will be sent: Se enviará el informe de consumo
+Please, confirm: Por favor, confirma
diff --git a/modules/client/front/index.js b/modules/client/front/index.js
index ff004c028..7df6f7b10 100644
--- a/modules/client/front/index.js
+++ b/modules/client/front/index.js
@@ -41,3 +41,4 @@ import './dms/index';
import './dms/create';
import './dms/edit';
import './consumption';
+import './consumption-search-panel';
diff --git a/modules/client/front/locale/es.yml b/modules/client/front/locale/es.yml
index a20acf6cb..e332a0229 100644
--- a/modules/client/front/locale/es.yml
+++ b/modules/client/front/locale/es.yml
@@ -56,4 +56,5 @@ Requested credits: Créditos solicitados
Contacts: Contactos
Samples: Plantillas
Send sample: Enviar plantilla
-Log: Historial
\ No newline at end of file
+Log: Historial
+Consumption: Consumo
\ No newline at end of file
diff --git a/modules/client/front/routes.json b/modules/client/front/routes.json
index 20f7841e7..2a5f63bb2 100644
--- a/modules/client/front/routes.json
+++ b/modules/client/front/routes.json
@@ -18,17 +18,17 @@
{"state": "client.card.greuge.index", "icon": "work"},
{"state": "client.card.balance.index", "icon": "icon-invoices"},
{"state": "client.card.recovery.index", "icon": "icon-recovery"},
- {"state": "client.card.consumption", "icon": "show_chart"},
+ {"state": "client.card.webAccess", "icon": "cloud"},
{"state": "client.card.log", "icon": "history"},
{
"description": "Others",
"icon": "more",
"childs": [
- {"state": "client.card.webAccess", "icon": "cloud"},
+ {"state": "client.card.sample.index", "icon": "mail"},
+ {"state": "client.card.consumption", "icon": "show_chart"},
{"state": "client.card.mandate", "icon": "pan_tool"},
{"state": "client.card.creditInsurance.index", "icon": "icon-solunion"},
{"state": "client.card.contact", "icon": "contact_phone"},
- {"state": "client.card.sample.index", "icon": "mail"},
{"state": "client.card.webPayment", "icon": "icon-onlinepayment"},
{"state": "client.card.dms.index", "icon": "cloud_upload"}
]
diff --git a/print/core/components/email-footer/assets/css/style.css b/print/core/components/email-footer/assets/css/style.css
index 4bc22fdfd..29620a64e 100644
--- a/print/core/components/email-footer/assets/css/style.css
+++ b/print/core/components/email-footer/assets/css/style.css
@@ -46,6 +46,7 @@
}
.privacy {
+ text-align: center;
padding: 20px 0;
font-size: 10px;
font-weight: 100
diff --git a/print/core/components/email-header/assets/css/style.css b/print/core/components/email-header/assets/css/style.css
index 4db5e2b2e..e6451ca5a 100644
--- a/print/core/components/email-header/assets/css/style.css
+++ b/print/core/components/email-header/assets/css/style.css
@@ -1,5 +1,10 @@
+header {
+ text-align: center
+}
+
header .logo {
- margin-bottom: 15px;
+ margin-top: 25px;
+ margin-bottom: 25px
}
header .logo img {
diff --git a/print/core/filters/date.js b/print/core/filters/date.js
index 0988eda75..5d1bc0de5 100644
--- a/print/core/filters/date.js
+++ b/print/core/filters/date.js
@@ -2,5 +2,6 @@ const Vue = require('vue');
const strftime = require('strftime');
Vue.filter('date', function(value, specifiers = '%d-%m-%Y') {
+ if (!(value instanceof Date)) value = new Date(value);
return strftime(specifiers, value);
});
diff --git a/print/templates/email/campaign-metrics/attachments.json b/print/templates/email/campaign-metrics/attachments.json
index 3f6a93bb5..d836d6040 100644
--- a/print/templates/email/campaign-metrics/attachments.json
+++ b/print/templates/email/campaign-metrics/attachments.json
@@ -1,6 +1,6 @@
[
{
- "filename": "campaing-metrics",
+ "filename": "campaign-metrics.pdf",
"component": "campaign-metrics"
}
]
\ No newline at end of file
diff --git a/print/templates/email/campaign-metrics/campaign-metrics.html b/print/templates/email/campaign-metrics/campaign-metrics.html
index 4ba95adb9..9d7014f34 100644
--- a/print/templates/email/campaign-metrics/campaign-metrics.html
+++ b/print/templates/email/campaign-metrics/campaign-metrics.html
@@ -25,7 +25,7 @@
{{ $t('title') }}
{{$t('dear')}},
-
{{$t('description')}}
+
diff --git a/print/templates/email/campaign-metrics/campaign-metrics.js b/print/templates/email/campaign-metrics/campaign-metrics.js
index 51d2ebb44..0ace0fc25 100755
--- a/print/templates/email/campaign-metrics/campaign-metrics.js
+++ b/print/templates/email/campaign-metrics/campaign-metrics.js
@@ -4,7 +4,17 @@ const emailFooter = new Component('email-footer');
module.exports = {
name: '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()
diff --git a/print/templates/email/campaign-metrics/locale/es.yml b/print/templates/email/campaign-metrics/locale/es.yml
index e662ca614..fc7aec432 100644
--- a/print/templates/email/campaign-metrics/locale/es.yml
+++ b/print/templates/email/campaign-metrics/locale/es.yml
@@ -1,7 +1,8 @@
-subject: Informe consumo campaña
-title: Informe consumo campaña
+subject: Informe de consumo
+title: Informe de consumo
dear: Estimado cliente
-description: Con motivo de esta próxima campaña, me complace
+description: Tal y como nos ha solicitado nos complace
relacionarle a continuación el consumo que nos consta en su cuenta para las
- mismas fechas del año pasado. Espero le sea de utilidad para preparar su pedido.
+ fechas comprendidas entre {0} y {1}.
+ Espero le sea de utilidad para preparar sus pedido.
Al mismo tiempo aprovecho la ocasión para saludarle cordialmente.
diff --git a/print/templates/reports/campaign-metrics/campaign-metrics.js b/print/templates/reports/campaign-metrics/campaign-metrics.js
index ef1d735de..420c1ffb1 100755
--- a/print/templates/reports/campaign-metrics/campaign-metrics.js
+++ b/print/templates/reports/campaign-metrics/campaign-metrics.js
@@ -6,9 +6,6 @@ const reportFooter = new Component('report-footer');
module.exports = {
name: 'campaign-metrics',
async serverPrefetch() {
- this.to = new Date(this.to);
- this.from = new Date(this.from);
-
this.client = await this.fetchClient(this.recipientId);
this.sales = await this.fetchSales(this.recipientId, this.from, this.to);
@@ -54,7 +51,7 @@ module.exports = {
t.clientFk = ? AND it.isPackaging = FALSE
AND DATE(t.shipped) BETWEEN ? AND ?
GROUP BY s.itemFk
- ORDER BY i.typeFk , i.name , i.size`, [clientId, from, to]);
+ ORDER BY i.typeFk , i.name`, [clientId, from, to]);
},
},
components: {
@@ -66,12 +63,10 @@ module.exports = {
required: true
},
from: {
- required: true,
- type: Date
+ required: true
},
to: {
- required: true,
- type: Date
+ required: true
}
}
};
diff --git a/print/templates/reports/campaign-metrics/locale/es.yml b/print/templates/reports/campaign-metrics/locale/es.yml
index df0c91971..8a4cc4637 100644
--- a/print/templates/reports/campaign-metrics/locale/es.yml
+++ b/print/templates/reports/campaign-metrics/locale/es.yml
@@ -1,4 +1,4 @@
-title: Consumo de campaña
+title: Consumo
Client: Cliente
clientData: Datos del cliente
dated: Fecha