{
describe('onEditAccept()', () => {
it(`should perform a query to update columns`, () => {
+ $httpBackend.whenGET('UserConfigViews/getConfig?tableCode=latestBuys').respond([]);
+ $httpBackend.whenGET('Buys/latestBuysFilter?filter=%7B%22limit%22:20%7D').respond([
+ {entryFk: 1},
+ {entryFk: 2},
+ {entryFk: 3},
+ {entryFk: 4}
+ ]);
controller.editedColumn = {field: 'my field', newValue: 'the new value'};
let query = 'Buys/editLatestBuys';
diff --git a/modules/entry/front/routes.json b/modules/entry/front/routes.json
index 0dc1e7ea0..c9f0efffd 100644
--- a/modules/entry/front/routes.json
+++ b/modules/entry/front/routes.json
@@ -7,7 +7,7 @@
"menus": {
"main": [
{"state": "entry.index", "icon": "icon-entry"},
- {"state": "entry.latestBuys", "icon": "icon-latestBuys"}
+ {"state": "entry.latestBuys", "icon": "contact_support"}
],
"card": [
{"state": "entry.card.basicData", "icon": "settings"},
diff --git a/modules/monitor/back/methods/sales-monitor/clientsFilter.js b/modules/monitor/back/methods/sales-monitor/clientsFilter.js
new file mode 100644
index 000000000..72b1307da
--- /dev/null
+++ b/modules/monitor/back/methods/sales-monitor/clientsFilter.js
@@ -0,0 +1,60 @@
+
+const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
+
+module.exports = Self => {
+ Self.remoteMethod('clientsFilter', {
+ description: 'Find all instances of the model matched by filter from the data source.',
+ accepts: [
+ {
+ arg: 'ctx',
+ type: 'object',
+ http: {source: 'context'}
+ },
+ {
+ 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: '/clientsFilter',
+ verb: 'GET'
+ }
+ });
+
+ Self.clientsFilter = async(ctx, filter) => {
+ const userId = ctx.req.accessToken.userId;
+ const conn = Self.dataSource.connector;
+
+ const stmt = new ParameterizedSQL(`
+ SELECT
+ u.name AS salesPerson,
+ IFNULL(sc.workerSubstitute, c.salesPersonFk) AS salesPersonFk,
+ c.id AS clientFk,
+ c.name AS clientName,
+ s.lastUpdate AS dated,
+ wtc.workerFk
+ FROM hedera.userSession s
+ JOIN hedera.visitUser v ON v.id = s.userVisitFk
+ JOIN client c ON c.id = v.userFk
+ LEFT JOIN account.user u ON c.salesPersonFk = u.id
+ LEFT JOIN worker w ON c.salesPersonFk = w.id
+ LEFT JOIN sharingCart sc ON sc.workerFk = c.salesPersonFk
+ AND CURDATE() BETWEEN sc.started AND sc.ended
+ LEFT JOIN workerTeamCollegues wtc
+ ON wtc.collegueFk = IFNULL(sc.workerSubstitute, c.salesPersonFk)`);
+
+ if (!filter.where) filter.where = {};
+
+ const where = filter.where;
+ where['wtc.workerFk'] = userId;
+
+ stmt.merge(conn.makeSuffix(filter));
+
+ return conn.executeStmt(stmt);
+ };
+};
diff --git a/modules/monitor/back/methods/sales-monitor/ordersFilter.js b/modules/monitor/back/methods/sales-monitor/ordersFilter.js
new file mode 100644
index 000000000..bc9ab3bdf
--- /dev/null
+++ b/modules/monitor/back/methods/sales-monitor/ordersFilter.js
@@ -0,0 +1,68 @@
+
+const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
+
+module.exports = Self => {
+ Self.remoteMethod('ordersFilter', {
+ description: 'Find all instances of the model matched by filter from the data source.',
+ accepts: [
+ {
+ arg: 'ctx',
+ type: 'object',
+ http: {source: 'context'}
+ },
+ {
+ 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: '/ordersFilter',
+ verb: 'GET'
+ }
+ });
+
+ Self.ordersFilter = async(ctx, filter) => {
+ const userId = ctx.req.accessToken.userId;
+ const conn = Self.dataSource.connector;
+
+ const stmt = new ParameterizedSQL(`
+ SELECT
+ c.id AS clientFk,
+ c.name AS clientName,
+ a.nickname,
+ o.id,
+ o.date_make,
+ o.date_send,
+ o.customer_id,
+ COUNT(item_id) AS totalRows,
+ ROUND(SUM(amount * price)) * 1 AS import,
+ u.id AS salesPersonFk,
+ u.name AS salesPerson,
+ am.name AS agencyName
+ FROM hedera.order o
+ JOIN hedera.order_row orw ON o.id = orw.order_id
+ JOIN client c ON c.id = o.customer_id
+ JOIN address a ON a.id = o.address_id
+ JOIN agencyMode am ON am.id = o.agency_id
+ JOIN user u ON u.id = c.salesPersonFk
+ JOIN workerTeamCollegues wtc ON c.salesPersonFk = wtc.collegueFk`);
+
+ if (!filter.where) filter.where = {};
+
+ const where = filter.where;
+ where['o.confirmed'] = false;
+ where['o.date_send'] = {gt: '2001-01-01'};
+ where['wtc.workerFk'] = userId;
+
+ stmt.merge(conn.makeWhere(filter.where));
+ stmt.merge(conn.makeGroupBy('o.id'));
+ stmt.merge(conn.makePagination(filter));
+
+ return conn.executeStmt(stmt);
+ };
+};
diff --git a/modules/monitor/back/methods/sales-monitor/salesFilter.js b/modules/monitor/back/methods/sales-monitor/salesFilter.js
new file mode 100644
index 000000000..726e5bfaf
--- /dev/null
+++ b/modules/monitor/back/methods/sales-monitor/salesFilter.js
@@ -0,0 +1,319 @@
+
+const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
+const buildFilter = require('vn-loopback/util/filter').buildFilter;
+const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
+const UserError = require('vn-loopback/util/user-error');
+
+module.exports = Self => {
+ Self.remoteMethod('salesFilter', {
+ description: 'Find all instances of the model matched by filter from the data source.',
+ accepts: [
+ {
+ arg: 'ctx',
+ type: 'object',
+ http: {source: 'context'}
+ }, {
+ 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 number searchs by id, otherwise it searchs by nickname`
+ }, {
+ arg: 'from',
+ type: 'date',
+ description: `The from date filter`
+ }, {
+ arg: 'to',
+ type: 'date',
+ description: `The to date filter`
+ }, {
+ arg: 'nickname',
+ type: 'string',
+ description: `The nickname filter`
+ }, {
+ arg: 'id',
+ type: 'number',
+ description: `The ticket id filter`
+ }, {
+ arg: 'clientFk',
+ type: 'number',
+ description: `The client id filter`
+ }, {
+ arg: 'agencyModeFk',
+ type: 'number',
+ description: `The agency mode id filter`
+ }, {
+ arg: 'warehouseFk',
+ type: 'number',
+ description: `The warehouse id filter`
+ }, {
+ arg: 'salesPersonFk',
+ type: 'number',
+ description: `The salesperson id filter`
+ }, {
+ arg: 'provinceFk',
+ type: 'number',
+ description: `The province id filter`
+ }, {
+ arg: 'stateFk',
+ type: 'number',
+ description: `The state id filter`
+ }, {
+ arg: 'myTeam',
+ type: 'boolean',
+ description: `Whether to show only tickets for the current logged user team (For now it shows only the current user tickets)`
+ }, {
+ arg: 'problems',
+ type: 'boolean',
+ description: `Whether to show only tickets with problems`
+ }, {
+ arg: 'pending',
+ type: 'boolean',
+ description: `Whether to show only tickets with state 'Pending'`
+ }, {
+ arg: 'mine',
+ type: 'boolean',
+ description: `Whether to show only tickets for the current logged user`
+ }, {
+ arg: 'orderFk',
+ type: 'number',
+ description: `The order id filter`
+ }, {
+ arg: 'refFk',
+ type: 'string',
+ description: `The invoice reference filter`
+ }, {
+ arg: 'alertLevel',
+ type: 'number',
+ description: `The alert level of the tickets`
+ }
+ ],
+ returns: {
+ type: ['object'],
+ root: true
+ },
+ http: {
+ path: '/salesFilter',
+ verb: 'GET'
+ }
+ });
+
+ Self.salesFilter = async(ctx, filter) => {
+ const userId = ctx.req.accessToken.userId;
+ const conn = Self.dataSource.connector;
+ const models = Self.app.models;
+ const args = ctx.args;
+
+ // Apply filter by team
+ const teamMembersId = [];
+ if (args.myTeam != null) {
+ const worker = await models.Worker.findById(userId, {
+ include: {
+ relation: 'collegues'
+ }
+ });
+ const collegues = worker.collegues() || [];
+ collegues.forEach(collegue => {
+ teamMembersId.push(collegue.collegueFk);
+ });
+
+ if (teamMembersId.length == 0)
+ teamMembersId.push(userId);
+ }
+
+ if (ctx.args && args.to) {
+ const dateTo = args.to;
+ dateTo.setHours(23, 59, 0, 0);
+ }
+
+ const where = buildFilter(ctx.args, (param, value) => {
+ switch (param) {
+ case 'search':
+ return /^\d+$/.test(value)
+ ? {'t.id': {inq: value}}
+ : {'t.nickname': {like: `%${value}%`}};
+ case 'from':
+ return {'t.shipped': {gte: value}};
+ case 'to':
+ return {'t.shipped': {lte: value}};
+ case 'nickname':
+ return {'t.nickname': {like: `%${value}%`}};
+ case 'refFk':
+ return {'t.refFk': value};
+ case 'salesPersonFk':
+ return {'c.salesPersonFk': value};
+ case 'provinceFk':
+ return {'a.provinceFk': value};
+ case 'stateFk':
+ return {'ts.stateFk': value};
+ case 'mine':
+ case 'myTeam':
+ if (value)
+ return {'c.salesPersonFk': {inq: teamMembersId}};
+ else
+ return {'c.salesPersonFk': {nin: teamMembersId}};
+
+ case 'alertLevel':
+ return {'ts.alertLevel': value};
+ case 'pending':
+ if (value) {
+ return {and: [
+ {'st.alertLevel': 0},
+ {'st.code': {nin: [
+ 'OK',
+ 'BOARDING',
+ 'PRINTED',
+ 'PRINTED_AUTO',
+ 'PICKER_DESIGNED'
+ ]}}
+ ]};
+ } else {
+ return {and: [
+ {'st.alertLevel': {gt: 0}}
+ ]};
+ }
+ case 'id':
+ case 'clientFk':
+ case 'agencyModeFk':
+ case 'warehouseFk':
+ param = `t.${param}`;
+ return {[param]: value};
+ }
+ });
+
+ filter = mergeFilters(filter, {where});
+
+ let stmts = [];
+ let stmt;
+
+ stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.filter');
+ stmt = new ParameterizedSQL(
+ `CREATE TEMPORARY TABLE tmp.filter
+ (INDEX (id))
+ ENGINE = MEMORY
+ SELECT
+ t.id,
+ t.shipped,
+ CAST(DATE(t.shipped) AS CHAR) AS shippedDate,
+ HOUR(t.shipped) AS shippedHour,
+ t.nickname,
+ t.refFk,
+ t.routeFk,
+ t.warehouseFk,
+ t.clientFk,
+ t.totalWithoutVat,
+ t.totalWithVat,
+ io.id AS invoiceOutId,
+ a.provinceFk,
+ p.name AS province,
+ w.name AS warehouse,
+ am.name AS agencyMode,
+ am.id AS agencyModeFk,
+ st.name AS state,
+ wk.lastName AS salesPerson,
+ ts.stateFk AS stateFk,
+ ts.alertLevel AS alertLevel,
+ ts.code AS alertLevelCode,
+ u.name AS userName,
+ c.salesPersonFk,
+ z.hour AS zoneLanding,
+ HOUR(z.hour) AS zoneHour,
+ MINUTE(z.hour) AS zoneMinute,
+ z.name AS zoneName,
+ z.id AS zoneFk,
+ CAST(z.hour AS CHAR) AS hour
+ FROM ticket t
+ LEFT JOIN invoiceOut io ON t.refFk = io.ref
+ LEFT JOIN zone z ON z.id = t.zoneFk
+ LEFT JOIN address a ON a.id = t.addressFk
+ LEFT JOIN province p ON p.id = a.provinceFk
+ LEFT JOIN warehouse w ON w.id = t.warehouseFk
+ LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
+ LEFT JOIN ticketState ts ON ts.ticketFk = t.id
+ LEFT JOIN state st ON st.id = ts.stateFk
+ LEFT JOIN client c ON c.id = t.clientFk
+ LEFT JOIN worker wk ON wk.id = c.salesPersonFk
+ LEFT JOIN account.user u ON u.id = wk.userFk`);
+
+ if (args.orderFk) {
+ stmt.merge({
+ sql: `JOIN orderTicket ot ON ot.ticketFk = t.id AND ot.orderFk = ?`,
+ params: [args.orderFk]
+ });
+ }
+
+ stmt.merge(conn.makeWhere(filter.where));
+ stmts.push(stmt);
+
+ stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.ticketGetProblems');
+
+ stmts.push(`
+ CREATE TEMPORARY TABLE tmp.ticketGetProblems
+ (INDEX (ticketFk))
+ ENGINE = MEMORY
+ SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped
+ FROM tmp.filter f
+ LEFT JOIN alertLevel al ON al.alertLevel = f.alertLevel
+ WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)
+ AND f.shipped >= CURDATE()`);
+
+ stmts.push('CALL ticketGetProblems(FALSE)');
+
+ stmt = new ParameterizedSQL(`
+ SELECT
+ f.*,
+ tp.*
+ FROM tmp.filter f
+ LEFT JOIN tmp.ticketProblems tp ON tp.ticketFk = f.id`);
+
+ if (args.problems != undefined && (!args.from && !args.to))
+ throw new UserError('Choose a date range or days forward');
+
+ let condition;
+ let hasProblem;
+ let range;
+ let hasWhere;
+ switch (args.problems) {
+ case true:
+ condition = `or`;
+ hasProblem = true;
+ range = 0;
+ hasWhere = true;
+ break;
+
+ case false:
+ condition = `and`;
+ hasProblem = null;
+ range = null;
+ hasWhere = true;
+ break;
+ }
+
+ let problems = {[condition]: [
+ {'tp.isFreezed': hasProblem},
+ {'tp.risk': hasProblem},
+ {'tp.hasTicketRequest': hasProblem},
+ {'tp.isAvailable': range}
+ ]};
+
+ if (hasWhere)
+ stmt.merge(conn.makeWhere(problems));
+
+ stmt.merge(conn.makeOrderBy(filter.order));
+ stmt.merge(conn.makeLimit(filter));
+ let ticketsIndex = stmts.push(stmt) - 1;
+
+ stmts.push(
+ `DROP TEMPORARY TABLE
+ tmp.filter,
+ tmp.ticket,
+ tmp.ticketGetProblems`);
+
+ let sql = ParameterizedSQL.join(stmts, ';');
+ let result = await conn.executeStmt(sql);
+
+ return result[ticketsIndex];
+ };
+};
diff --git a/modules/monitor/back/methods/sales-monitor/specs/clientsFilter.spec.js b/modules/monitor/back/methods/sales-monitor/specs/clientsFilter.spec.js
new file mode 100644
index 000000000..c930b5dfb
--- /dev/null
+++ b/modules/monitor/back/methods/sales-monitor/specs/clientsFilter.spec.js
@@ -0,0 +1,11 @@
+const app = require('vn-loopback/server/server');
+
+describe('SalesMonitor clientsFilter()', () => {
+ it('should return the clients web activity', async() => {
+ const ctx = {req: {accessToken: {userId: 18}}, args: {}};
+ const filter = {order: 'dated DESC'};
+ const result = await app.models.SalesMonitor.clientsFilter(ctx, filter);
+
+ expect(result.length).toEqual(9);
+ });
+});
diff --git a/modules/monitor/back/methods/sales-monitor/specs/ordersFilter.spec.js b/modules/monitor/back/methods/sales-monitor/specs/ordersFilter.spec.js
new file mode 100644
index 000000000..26894f25d
--- /dev/null
+++ b/modules/monitor/back/methods/sales-monitor/specs/ordersFilter.spec.js
@@ -0,0 +1,11 @@
+const app = require('vn-loopback/server/server');
+
+describe('SalesMonitor ordersFilter()', () => {
+ it('should return the orders activity', async() => {
+ const ctx = {req: {accessToken: {userId: 18}}, args: {}};
+ const filter = {order: 'date_make DESC'};
+ const result = await app.models.SalesMonitor.ordersFilter(ctx, filter);
+
+ expect(result.length).toEqual(12);
+ });
+});
diff --git a/modules/monitor/back/methods/sales-monitor/specs/salesFilter.spec.js b/modules/monitor/back/methods/sales-monitor/specs/salesFilter.spec.js
new file mode 100644
index 000000000..64cc35bfa
--- /dev/null
+++ b/modules/monitor/back/methods/sales-monitor/specs/salesFilter.spec.js
@@ -0,0 +1,106 @@
+const app = require('vn-loopback/server/server');
+
+describe('SalesMonitor salesFilter()', () => {
+ it('should return the tickets matching the filter', async() => {
+ const ctx = {req: {accessToken: {userId: 9}}, args: {}};
+ const filter = {order: 'id DESC'};
+ const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
+
+ expect(result.length).toEqual(24);
+ });
+
+ it('should return the tickets matching the problems on true', async() => {
+ const yesterday = new Date();
+ yesterday.setHours(0, 0, 0, 0);
+ const today = new Date();
+ today.setHours(23, 59, 59, 59);
+
+ const ctx = {req: {accessToken: {userId: 9}}, args: {
+ problems: true,
+ from: yesterday,
+ to: today
+ }};
+ const filter = {};
+ const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
+
+ expect(result.length).toEqual(3);
+ });
+
+ it('should return the tickets matching the problems on false', async() => {
+ const yesterday = new Date();
+ yesterday.setDate(yesterday.getDate() - 1);
+ yesterday.setHours(0, 0, 0, 0);
+ const today = new Date();
+ today.setHours(23, 59, 59, 59);
+
+ const ctx = {req: {accessToken: {userId: 9}}, args: {
+ problems: false,
+ from: yesterday,
+ to: today
+ }};
+ const filter = {};
+ const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
+
+ expect(result.length).toEqual(7);
+ });
+
+ it('should return the tickets matching the problems on null', async() => {
+ const ctx = {req: {accessToken: {userId: 9}}, args: {problems: null}};
+ const filter = {};
+ const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
+
+ expect(result.length).toEqual(24);
+ });
+
+ it('should return the tickets matching the orderId 11', async() => {
+ const ctx = {req: {accessToken: {userId: 9}}, args: {orderFk: 11}};
+ const filter = {};
+ const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
+ const firstRow = result[0];
+
+ expect(result.length).toEqual(1);
+ expect(firstRow.id).toEqual(11);
+ });
+
+ it('should return the tickets with grouped state "Pending" and not "Ok" nor "BOARDING"', async() => {
+ const ctx = {req: {accessToken: {userId: 9}}, args: {pending: true}};
+ const filter = {};
+ const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
+
+ const length = result.length;
+ const anyResult = result[Math.floor(Math.random() * Math.floor(length))];
+
+ expect(length).toEqual(7);
+ expect(anyResult.state).toMatch(/(Libre|Arreglar)/);
+ });
+
+ it('should return the tickets that are not pending', async() => {
+ const ctx = {req: {accessToken: {userId: 9}}, args: {pending: false}};
+ const filter = {};
+ const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
+ const firstRow = result[0];
+ const secondRow = result[1];
+ const thirdRow = result[2];
+
+ expect(result.length).toEqual(12);
+ expect(firstRow.state).toEqual('Entregado');
+ expect(secondRow.state).toEqual('Entregado');
+ expect(thirdRow.state).toEqual('Entregado');
+ });
+
+ it('should return the tickets from the worker team', async() => {
+ const ctx = {req: {accessToken: {userId: 18}}, args: {myTeam: true}};
+ const filter = {};
+ const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
+
+ expect(result.length).toEqual(20);
+ });
+
+ it('should return the tickets that are not from the worker team', async() => {
+ const ctx = {req: {accessToken: {userId: 18}}, args: {myTeam: false}};
+ const filter = {};
+ const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
+
+ expect(result.length).toEqual(4);
+ });
+});
diff --git a/modules/monitor/back/model-config.json b/modules/monitor/back/model-config.json
new file mode 100644
index 000000000..3ba94c18d
--- /dev/null
+++ b/modules/monitor/back/model-config.json
@@ -0,0 +1,5 @@
+{
+ "SalesMonitor": {
+ "dataSource": "vn"
+ }
+}
diff --git a/modules/monitor/back/models/sales-monitor.js b/modules/monitor/back/models/sales-monitor.js
new file mode 100644
index 000000000..63479cb9a
--- /dev/null
+++ b/modules/monitor/back/models/sales-monitor.js
@@ -0,0 +1,5 @@
+module.exports = Self => {
+ require('../methods/sales-monitor/salesFilter')(Self);
+ require('../methods/sales-monitor/clientsFilter')(Self);
+ require('../methods/sales-monitor/ordersFilter')(Self);
+};
diff --git a/modules/monitor/back/models/sales-monitor.json b/modules/monitor/back/models/sales-monitor.json
new file mode 100644
index 000000000..eabdcf853
--- /dev/null
+++ b/modules/monitor/back/models/sales-monitor.json
@@ -0,0 +1,11 @@
+{
+ "name": "SalesMonitor",
+ "base": "VnModel",
+ "acls": [{
+ "property": "status",
+ "principalType": "ROLE",
+ "principalId": "$everyone",
+ "permission": "ALLOW"
+ }]
+}
+
\ No newline at end of file
diff --git a/modules/monitor/front/index.js b/modules/monitor/front/index.js
new file mode 100644
index 000000000..19ea06b5a
--- /dev/null
+++ b/modules/monitor/front/index.js
@@ -0,0 +1,8 @@
+export * from './module';
+
+import './main';
+import './index/';
+import './index/tickets';
+import './index/clients';
+import './index/orders';
+import './index/search-panel';
diff --git a/modules/monitor/front/index/clients/index.html b/modules/monitor/front/index/clients/index.html
new file mode 100644
index 000000000..bfa496199
--- /dev/null
+++ b/modules/monitor/front/index/clients/index.html
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+ Hour
+ Salesperson
+ Client
+
+
+
+
+
+
+ {{::visit.dated | date: 'HH:mm'}}
+
+
+
+
+ {{::visit.salesPerson | dashIfEmpty}}
+
+
+
+
+ {{::visit.clientName}}
+
+
+
+
+
+
+ No results
+
+
+
+
+
+
+
+
+
+
+
+ Filter by selection
+
+
+ Exclude selection
+
+
+ Remove filter
+
+
+ Remove all filters
+
+
+ Copy value
+
+
+
\ No newline at end of file
diff --git a/modules/monitor/front/index/clients/index.js b/modules/monitor/front/index/clients/index.js
new file mode 100644
index 000000000..ce7e2ed09
--- /dev/null
+++ b/modules/monitor/front/index/clients/index.js
@@ -0,0 +1,30 @@
+import ngModule from '../../module';
+import Section from 'salix/components/section';
+
+export default class Controller extends Section {
+ exprBuilder(param, value) {
+ switch (param) {
+ case 'dated':
+ return {'s.lastUpdate': {
+ between: this.dateRange(value)}
+ };
+ case 'clientFk':
+ case 'salesPersonFk':
+ return {[`c.${param}`]: value};
+ }
+ }
+
+ dateRange(value) {
+ const minHour = new Date(value);
+ minHour.setHours(0, 0, 0, 0);
+ const maxHour = new Date(value);
+ maxHour.setHours(23, 59, 59, 59);
+
+ return [minHour, maxHour];
+ }
+}
+
+ngModule.vnComponent('vnMonitorSalesClients', {
+ template: require('./index.html'),
+ controller: Controller
+});
diff --git a/modules/monitor/front/index/index.html b/modules/monitor/front/index/index.html
new file mode 100644
index 000000000..937464891
--- /dev/null
+++ b/modules/monitor/front/index/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/monitor/front/index/index.js b/modules/monitor/front/index/index.js
new file mode 100644
index 000000000..4807cf1c9
--- /dev/null
+++ b/modules/monitor/front/index/index.js
@@ -0,0 +1,8 @@
+import ngModule from '../module';
+import Section from 'salix/components/section';
+import './style.scss';
+
+ngModule.vnComponent('vnMonitorIndex', {
+ template: require('./index.html'),
+ controller: Section
+});
diff --git a/modules/monitor/front/index/locale/es.yml b/modules/monitor/front/index/locale/es.yml
new file mode 100644
index 000000000..b709d0922
--- /dev/null
+++ b/modules/monitor/front/index/locale/es.yml
@@ -0,0 +1,4 @@
+Tickets monitor: Monitor de tickets
+Clients on website: Clientes activos en la web
+Recent order actions: Acciones recientes en pedidos
+Search tickets: Buscar tickets
\ No newline at end of file
diff --git a/modules/monitor/front/index/orders/index.html b/modules/monitor/front/index/orders/index.html
new file mode 100644
index 000000000..9bb4d3608
--- /dev/null
+++ b/modules/monitor/front/index/orders/index.html
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+ Date
+ Client
+ Import
+
+
+
+
+
+
+ {{::order.date_send | date: 'dd/MM/yyyy'}}
+
+
+
+
+ {{::order.clientName}}
+
+
+ {{::order.import | currency: 'EUR':2}}
+
+
+
+
+ {{::order.date_make | date: 'dd/MM/yyyy HH:mm'}}
+
+
+
+
+ {{::order.agencyName | dashIfEmpty}}
+
+
+
+
+ {{::order.salesPerson | dashIfEmpty}}
+
+
+
+
+
+
+ No results
+
+
+
+
+
+
+
+
diff --git a/modules/monitor/front/index/orders/index.js b/modules/monitor/front/index/orders/index.js
new file mode 100644
index 000000000..0a4c8c04f
--- /dev/null
+++ b/modules/monitor/front/index/orders/index.js
@@ -0,0 +1,8 @@
+import ngModule from '../../module';
+import Section from 'salix/components/section';
+import './style.scss';
+
+ngModule.vnComponent('vnMonitorSalesOrders', {
+ template: require('./index.html'),
+ controller: Section
+});
diff --git a/modules/monitor/front/index/orders/style.scss b/modules/monitor/front/index/orders/style.scss
new file mode 100644
index 000000000..5f80472fe
--- /dev/null
+++ b/modules/monitor/front/index/orders/style.scss
@@ -0,0 +1,16 @@
+@import "variables";
+
+vn-monitor-sales-orders {
+ .dark-row {
+ background-color: lighten($color-marginal, 15%);
+ color: gray;
+
+ vn-td {
+ border-bottom: 2px solid $color-marginal
+ }
+ }
+
+ vn-tbody vn-tr:nth-child(3) {
+ height: inherit
+ }
+}
\ No newline at end of file
diff --git a/modules/monitor/front/index/search-panel/index.html b/modules/monitor/front/index/search-panel/index.html
new file mode 100644
index 000000000..d0b77b9de
--- /dev/null
+++ b/modules/monitor/front/index/search-panel/index.html
@@ -0,0 +1,138 @@
+
+
+
diff --git a/modules/monitor/front/index/search-panel/index.js b/modules/monitor/front/index/search-panel/index.js
new file mode 100644
index 000000000..057d555e4
--- /dev/null
+++ b/modules/monitor/front/index/search-panel/index.js
@@ -0,0 +1,59 @@
+import ngModule from '../../module';
+import SearchPanel from 'core/components/searchbar/search-panel';
+
+class Controller extends SearchPanel {
+ constructor($, $element) {
+ super($, $element);
+ this.filter = this.$.filter;
+
+ this.getGroupedStates();
+ }
+
+ getGroupedStates() {
+ let groupedStates = [];
+ this.$http.get('AlertLevels').then(res => {
+ for (let state of res.data) {
+ groupedStates.push({
+ alertLevel: state.alertLevel,
+ code: state.code,
+ name: this.$t(state.code)
+ });
+ }
+ this.groupedStates = groupedStates;
+ });
+ }
+
+ get from() {
+ return this._from;
+ }
+
+ set from(value) {
+ this._from = value;
+ this.filter.scopeDays = null;
+ }
+
+ get to() {
+ return this._to;
+ }
+
+ set to(value) {
+ this._to = value;
+ this.filter.scopeDays = null;
+ }
+
+ get scopeDays() {
+ return this._scopeDays;
+ }
+
+ set scopeDays(value) {
+ this._scopeDays = value;
+
+ this.filter.from = null;
+ this.filter.to = null;
+ }
+}
+
+ngModule.vnComponent('vnMonitorSalesSearchPanel', {
+ template: require('./index.html'),
+ controller: Controller
+});
diff --git a/modules/monitor/front/index/search-panel/index.spec.js b/modules/monitor/front/index/search-panel/index.spec.js
new file mode 100644
index 000000000..0d19fd35f
--- /dev/null
+++ b/modules/monitor/front/index/search-panel/index.spec.js
@@ -0,0 +1,71 @@
+import './index';
+
+describe('Monitor Component vnMonitorSalesSearchPanel', () => {
+ let $httpBackend;
+ let controller;
+
+ beforeEach(ngModule('monitor'));
+
+ beforeEach(inject(($componentController, _$httpBackend_) => {
+ $httpBackend = _$httpBackend_;
+ controller = $componentController('vnMonitorSalesSearchPanel', {$element: null});
+ controller.$t = () => {};
+ controller.filter = {};
+ }));
+
+ describe('getGroupedStates()', () => {
+ it('should set an array of groupedStates with the adition of a name translation', () => {
+ jest.spyOn(controller, '$t').mockReturnValue('miCodigo');
+ const data = [
+ {
+ alertLevel: 9999,
+ code: 'myCode'
+ }
+ ];
+ $httpBackend.whenGET('AlertLevels').respond(data);
+ controller.getGroupedStates();
+ $httpBackend.flush();
+
+ expect(controller.groupedStates).toEqual([{
+ alertLevel: 9999,
+ code: 'myCode',
+ name: 'miCodigo'
+ }]);
+ });
+ });
+
+ describe('from() setter', () => {
+ it('should clear the scope days when setting the from property', () => {
+ controller.filter.scopeDays = 1;
+
+ controller.from = new Date();
+
+ expect(controller.filter.scopeDays).toBeNull();
+ expect(controller.from).toBeDefined();
+ });
+ });
+
+ describe('to() setter', () => {
+ it('should clear the scope days when setting the to property', () => {
+ controller.filter.scopeDays = 1;
+
+ controller.to = new Date();
+
+ expect(controller.filter.scopeDays).toBeNull();
+ expect(controller.to).toBeDefined();
+ });
+ });
+
+ describe('scopeDays() setter', () => {
+ it('should clear the date range when setting the scopeDays property', () => {
+ controller.filter.from = new Date();
+ controller.filter.to = new Date();
+
+ controller.scopeDays = 1;
+
+ expect(controller.filter.from).toBeNull();
+ expect(controller.filter.to).toBeNull();
+ expect(controller.scopeDays).toBeDefined();
+ });
+ });
+});
diff --git a/modules/monitor/front/index/style.scss b/modules/monitor/front/index/style.scss
new file mode 100644
index 000000000..4e0d9df2f
--- /dev/null
+++ b/modules/monitor/front/index/style.scss
@@ -0,0 +1,20 @@
+@import "variables";
+@import "effects";
+
+vn-monitor-index {
+ .header {
+ padding: 12px 0;
+ color: gray;
+ font-size: 1.2rem;
+
+ & > vn-none > vn-icon {
+ @extend %clickable-light;
+ color: $color-button;
+ font-size: 1.4rem;
+ }
+ }
+ .empty-rows {
+ color: $color-font-secondary;
+ text-align: center;
+ }
+}
\ No newline at end of file
diff --git a/modules/monitor/front/index/tickets/index.html b/modules/monitor/front/index/tickets/index.html
new file mode 100644
index 000000000..8fc3e09d9
--- /dev/null
+++ b/modules/monitor/front/index/tickets/index.html
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Client
+ Salesperson
+ Date
+ Hour
+ Closure
+ Province
+ State
+ Zone
+ Total
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{::ticket.nickname}}
+
+
+
+
+ {{::ticket.userName | dashIfEmpty}}
+
+
+
+
+ {{::ticket.shipped | date: 'dd/MM/yyyy'}}
+
+
+ {{::ticket.shipped | date: 'HH:mm'}}
+ {{::ticket.zoneLanding | date: 'HH:mm'}}
+ {{::ticket.province}}
+
+
+ {{::ticket.refFk}}
+
+
+ {{ticket.state}}
+
+
+
+
+ {{::ticket.zoneName | dashIfEmpty}}
+
+
+
+
+ {{::(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Filter by selection
+
+
+ Exclude selection
+
+
+ Remove filter
+
+
+ Remove all filters
+
+
+ Copy value
+
+
+
diff --git a/modules/monitor/front/index/tickets/index.js b/modules/monitor/front/index/tickets/index.js
new file mode 100644
index 000000000..26581f1f6
--- /dev/null
+++ b/modules/monitor/front/index/tickets/index.js
@@ -0,0 +1,105 @@
+import ngModule from '../../module';
+import Section from 'salix/components/section';
+import './style.scss';
+
+export default class Controller extends Section {
+ constructor($element, $) {
+ super($element, $);
+
+ this.filterParams = this.fetchParams({
+ scopeDays: 1
+ });
+ }
+
+ fetchParams($params) {
+ if (!Object.entries($params).length)
+ $params.scopeDays = 1;
+
+ if (typeof $params.scopeDays === 'number') {
+ const from = new Date();
+ from.setHours(0, 0, 0, 0);
+
+ const to = new Date(from.getTime());
+ to.setDate(to.getDate() + $params.scopeDays);
+ to.setHours(23, 59, 59, 999);
+
+ Object.assign($params, {from, to});
+ }
+
+ return $params;
+ }
+
+ compareDate(date) {
+ let today = new Date();
+ today.setHours(0, 0, 0, 0);
+ let timeTicket = new Date(date);
+ timeTicket.setHours(0, 0, 0, 0);
+
+ let comparation = today - timeTicket;
+
+ if (comparation == 0)
+ return 'warning';
+ if (comparation < 0)
+ return 'success';
+ }
+
+ stateColor(ticket) {
+ if (ticket.alertLevelCode === 'OK')
+ return 'success';
+ else if (ticket.alertLevelCode === 'FREE')
+ return 'notice';
+ else if (ticket.alertLevel === 1)
+ return 'warning';
+ else if (ticket.alertLevel === 0)
+ return 'alert';
+ }
+
+ totalPriceColor(ticket) {
+ const total = parseInt(ticket.totalWithVat);
+ if (total > 0 && total < 50)
+ return 'warning';
+ }
+
+ exprBuilder(param, value) {
+ switch (param) {
+ case 'stateFk':
+ return {'ts.stateFk': value};
+ case 'salesPersonFk':
+ return {'c.salesPersonFk': value};
+ case 'provinceFk':
+ return {'a.provinceFk': value};
+ case 'hour':
+ return {'z.hour': value};
+ case 'shipped':
+ return {'t.shipped': {
+ between: this.dateRange(value)}
+ };
+ case 'id':
+ case 'refFk':
+ case 'zoneFk':
+ case 'nickname':
+ case 'agencyModeFk':
+ case 'warehouseFk':
+ return {[`t.${param}`]: value};
+ }
+ }
+
+ dateRange(value) {
+ const minHour = new Date(value);
+ minHour.setHours(0, 0, 0, 0);
+ const maxHour = new Date(value);
+ maxHour.setHours(23, 59, 59, 59);
+
+ return [minHour, maxHour];
+ }
+
+ preview(ticket) {
+ this.selectedTicket = ticket;
+ this.$.summary.show();
+ }
+}
+
+ngModule.vnComponent('vnMonitorSalesTickets', {
+ template: require('./index.html'),
+ controller: Controller
+});
diff --git a/modules/monitor/front/index/tickets/index.spec.js b/modules/monitor/front/index/tickets/index.spec.js
new file mode 100644
index 000000000..fc689140a
--- /dev/null
+++ b/modules/monitor/front/index/tickets/index.spec.js
@@ -0,0 +1,133 @@
+import './index.js';
+describe('Component vnMonitorSalesTickets', () => {
+ let controller;
+ let $window;
+ let tickets = [{
+ id: 1,
+ clientFk: 1,
+ checked: false,
+ totalWithVat: 10.5
+ }, {
+ id: 2,
+ clientFk: 1,
+ checked: true,
+ totalWithVat: 20.5
+ }, {
+ id: 3,
+ clientFk: 1,
+ checked: true,
+ totalWithVat: 30
+ }];
+
+ beforeEach(ngModule('monitor'));
+
+ beforeEach(inject(($componentController, _$window_) => {
+ $window = _$window_;
+ const $element = angular.element('');
+ controller = $componentController('vnMonitorSalesTickets', {$element});
+ }));
+
+ describe('fetchParams()', () => {
+ it('should return a range of dates with passed scope days', () => {
+ let params = controller.fetchParams({
+ scopeDays: 2
+ });
+ const from = new Date();
+ from.setHours(0, 0, 0, 0);
+ const to = new Date(from.getTime());
+ to.setDate(to.getDate() + params.scopeDays);
+ to.setHours(23, 59, 59, 999);
+
+ const expectedParams = {
+ from,
+ scopeDays: params.scopeDays,
+ to
+ };
+
+ expect(params).toEqual(expectedParams);
+ });
+
+ it('should return default value for scope days', () => {
+ let params = controller.fetchParams({
+ scopeDays: 1
+ });
+
+ expect(params.scopeDays).toEqual(1);
+ });
+
+ it('should return the given scope days', () => {
+ let params = controller.fetchParams({
+ scopeDays: 2
+ });
+
+ expect(params.scopeDays).toEqual(2);
+ });
+ });
+
+ describe('compareDate()', () => {
+ it('should return warning when the date is the present', () => {
+ let today = new Date();
+ let result = controller.compareDate(today);
+
+ expect(result).toEqual('warning');
+ });
+
+ it('should return sucess when the date is in the future', () => {
+ let futureDate = new Date();
+ futureDate = futureDate.setDate(futureDate.getDate() + 10);
+ let result = controller.compareDate(futureDate);
+
+ expect(result).toEqual('success');
+ });
+
+ it('should return undefined when the date is in the past', () => {
+ let pastDate = new Date();
+ pastDate = pastDate.setDate(pastDate.getDate() - 10);
+ let result = controller.compareDate(pastDate);
+
+ expect(result).toEqual(undefined);
+ });
+ });
+
+ describe('stateColor()', () => {
+ it('should return "success" when the alertLevelCode property is "OK"', () => {
+ const result = controller.stateColor({alertLevelCode: 'OK'});
+
+ expect(result).toEqual('success');
+ });
+
+ it('should return "notice" when the alertLevelCode property is "FREE"', () => {
+ const result = controller.stateColor({alertLevelCode: 'FREE'});
+
+ expect(result).toEqual('notice');
+ });
+
+ it('should return "warning" when the alertLevel property is "1', () => {
+ const result = controller.stateColor({alertLevel: 1});
+
+ expect(result).toEqual('warning');
+ });
+
+ it('should return "alert" when the alertLevel property is "0"', () => {
+ const result = controller.stateColor({alertLevel: 0});
+
+ expect(result).toEqual('alert');
+ });
+ });
+
+ describe('preview()', () => {
+ it('should show the dialog summary', () => {
+ controller.$.summary = {show: () => {}};
+ jest.spyOn(controller.$.summary, 'show');
+
+ let event = new MouseEvent('click', {
+ view: $window,
+ bubbles: true,
+ cancelable: true
+ });
+ controller.preview(event, tickets[0]);
+
+ expect(controller.$.summary.show).toHaveBeenCalledWith();
+ });
+ });
+});
diff --git a/modules/monitor/front/index/tickets/style.scss b/modules/monitor/front/index/tickets/style.scss
new file mode 100644
index 000000000..f3931e6ec
--- /dev/null
+++ b/modules/monitor/front/index/tickets/style.scss
@@ -0,0 +1,23 @@
+vn-monitor-sales-tickets {
+ @media screen and (max-width: 1440px) {
+ .expendable {
+ display: none;
+ }
+ }
+
+ vn-th.icon-field,
+ vn-th.icon-field *,
+ vn-td.icon-field,
+ vn-td.icon-field * {
+ padding: 0
+ }
+
+ vn-td.icon-field > vn-icon {
+ margin-left: 3px;
+ margin-right: 3px;
+ }
+
+ vn-table.scrollable.lg {
+ height: 736px
+ }
+}
\ No newline at end of file
diff --git a/modules/monitor/front/locale/es.yml b/modules/monitor/front/locale/es.yml
new file mode 100644
index 000000000..7d7e72f6b
--- /dev/null
+++ b/modules/monitor/front/locale/es.yml
@@ -0,0 +1 @@
+Sales monitor: Monitor de ventas
\ No newline at end of file
diff --git a/modules/monitor/front/main/index.html b/modules/monitor/front/main/index.html
new file mode 100644
index 000000000..6e04f06d0
--- /dev/null
+++ b/modules/monitor/front/main/index.html
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/modules/monitor/front/main/index.js b/modules/monitor/front/main/index.js
new file mode 100644
index 000000000..42fc159d5
--- /dev/null
+++ b/modules/monitor/front/main/index.js
@@ -0,0 +1,9 @@
+import ngModule from '../module';
+import ModuleMain from 'salix/components/module-main';
+
+export default class Monitor extends ModuleMain {}
+
+ngModule.vnComponent('vnMonitor', {
+ controller: Monitor,
+ template: require('./index.html')
+});
diff --git a/modules/monitor/front/module.js b/modules/monitor/front/module.js
new file mode 100644
index 000000000..3a3125fe7
--- /dev/null
+++ b/modules/monitor/front/module.js
@@ -0,0 +1,3 @@
+import {ng} from 'core/vendor';
+
+export default ng.module('monitor', ['salix']);
diff --git a/modules/monitor/front/routes.json b/modules/monitor/front/routes.json
new file mode 100644
index 000000000..8fc995e4c
--- /dev/null
+++ b/modules/monitor/front/routes.json
@@ -0,0 +1,29 @@
+{
+ "module": "monitor",
+ "name": "Monitors",
+ "icon" : "grid_view",
+ "dependencies": ["ticket", "worker", "client"],
+ "validations" : true,
+ "menus": {
+ "main": [
+ {"state": "monitor.index", "icon": "grid_view"}
+ ],
+ "card": []
+ },
+ "keybindings": [],
+ "routes": [
+ {
+ "url": "/monitor",
+ "state": "monitor",
+ "abstract": true,
+ "component": "vn-monitor",
+ "description": "Monitors"
+ },
+ {
+ "url": "/index?q",
+ "state": "monitor.index",
+ "component": "vn-monitor-index",
+ "description": "Sales monitor"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/modules/order/back/methods/order/catalogFilter.js b/modules/order/back/methods/order/catalogFilter.js
index b2d49054d..4502435b5 100644
--- a/modules/order/back/methods/order/catalogFilter.js
+++ b/modules/order/back/methods/order/catalogFilter.js
@@ -118,8 +118,8 @@ module.exports = Self => {
FROM tmp.ticketCalculateItem tci
JOIN vn.item i ON i.id = tci.itemFk
JOIN vn.itemType it ON it.id = i.typeFk
- JOIN vn.ink ON ink.id = i.inkFk
- JOIN vn.worker w on w.id = it.workerFk`);
+ JOIN vn.worker w on w.id = it.workerFk
+ LEFT JOIN vn.ink ON ink.id = i.inkFk`);
// Apply order by tag
if (orderBy.isTag) {
diff --git a/modules/order/back/methods/order/specs/filter.spec.js b/modules/order/back/methods/order/specs/filter.spec.js
index 1e6022ccb..1cc434cf1 100644
--- a/modules/order/back/methods/order/specs/filter.spec.js
+++ b/modules/order/back/methods/order/specs/filter.spec.js
@@ -23,11 +23,11 @@ describe('order filter()', () => {
});
it('should call the filter method with a complex advanced search', async() => {
- const filter = {where: {'o.confirmed': false, 'c.salesPersonFk': 19}};
+ const filter = {where: {'o.confirmed': false, 'c.salesPersonFk': 18}};
const result = await app.models.Order.filter(ctx, filter);
- expect(result.length).toEqual(7);
- expect(result[0].id).toEqual(16);
+ expect(result.length).toEqual(9);
+ expect(result[0].id).toEqual(7);
});
it('should return the orders matching the showEmpty on false', async() => {
diff --git a/modules/order/front/index/index.html b/modules/order/front/index/index.html
index d90b59f89..288e81226 100644
--- a/modules/order/front/index/index.html
+++ b/modules/order/front/index/index.html
@@ -14,7 +14,7 @@
Client
Confirmed
Created
- Landed
+ Landed
Hour
Agency
Total
diff --git a/modules/route/back/methods/route/getSuggestedTickets.js b/modules/route/back/methods/route/getSuggestedTickets.js
index 12bde1684..c2afd60b4 100644
--- a/modules/route/back/methods/route/getSuggestedTickets.js
+++ b/modules/route/back/methods/route/getSuggestedTickets.js
@@ -20,12 +20,6 @@ module.exports = Self => {
});
Self.getSuggestedTickets = async id => {
- const ticketsInRoute = await Self.app.models.Ticket.find({
- where: {routeFk: id},
- fields: ['id']
- });
- const idsToExclude = ticketsInRoute.map(ticket => ticket.id);
-
const route = await Self.app.models.Route.findById(id);
const zoneAgencyModes = await Self.app.models.ZoneAgencyMode.find({
@@ -37,19 +31,17 @@ module.exports = Self => {
const zoneIds = [];
for (let zoneAgencyMode of zoneAgencyModes)
zoneIds.push(zoneAgencyMode.zoneFk);
-
const minDate = new Date(route.created);
minDate.setHours(0, 0, 0, 0);
const maxDate = new Date(route.created);
maxDate.setHours(23, 59, 59, 59);
-
let tickets = await Self.app.models.Ticket.find({
where: {
agencyModeFk: route.agencyModeFk,
zoneFk: {inq: zoneIds},
- id: {nin: idsToExclude},
- created: {between: [minDate, maxDate]}
+ routeFk: null,
+ shipped: {between: [minDate, maxDate]}
},
include: [
{
diff --git a/modules/ticket/back/methods/ticket/restore.js b/modules/ticket/back/methods/ticket/restore.js
index 19429f61b..a3a79dc7c 100644
--- a/modules/ticket/back/methods/ticket/restore.js
+++ b/modules/ticket/back/methods/ticket/restore.js
@@ -21,9 +21,14 @@ module.exports = Self => {
}
});
- Self.restore = async(ctx, id) => {
+ Self.restore = async(ctx, id, options) => {
const models = Self.app.models;
const $t = ctx.req.__; // $translate
+ const myOptions = {};
+
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
const ticket = await models.Ticket.findById(id, {
include: [{
relation: 'client',
@@ -31,7 +36,7 @@ module.exports = Self => {
fields: ['id', 'salesPersonFk']
}
}]
- });
+ }, myOptions);
const now = new Date();
const maxDate = new Date(ticket.updated);
@@ -51,6 +56,16 @@ module.exports = Self => {
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
}
- return ticket.updateAttribute('isDeleted', false);
+ const fullYear = new Date().getFullYear();
+ const newShipped = ticket.shipped;
+ const newLanded = ticket.landed;
+ newShipped.setFullYear(fullYear);
+ newLanded.setFullYear(fullYear);
+
+ return ticket.updateAttributes({
+ shipped: newShipped,
+ landed: newLanded,
+ isDeleted: false
+ }, myOptions);
};
};
diff --git a/modules/ticket/back/methods/ticket/specs/filter.spec.js b/modules/ticket/back/methods/ticket/specs/filter.spec.js
index 2ca8bcf44..7798b156e 100644
--- a/modules/ticket/back/methods/ticket/specs/filter.spec.js
+++ b/modules/ticket/back/methods/ticket/specs/filter.spec.js
@@ -89,18 +89,18 @@ describe('ticket filter()', () => {
});
it('should return the tickets from the worker team', async() => {
- const ctx = {req: {accessToken: {userId: 9}}, args: {myTeam: true}};
+ const ctx = {req: {accessToken: {userId: 18}}, args: {myTeam: true}};
const filter = {};
const result = await app.models.Ticket.filter(ctx, filter);
- expect(result.length).toEqual(17);
+ expect(result.length).toEqual(20);
});
it('should return the tickets that are not from the worker team', async() => {
- const ctx = {req: {accessToken: {userId: 9}}, args: {myTeam: false}};
+ const ctx = {req: {accessToken: {userId: 18}}, args: {myTeam: false}};
const filter = {};
const result = await app.models.Ticket.filter(ctx, filter);
- expect(result.length).toEqual(7);
+ expect(result.length).toEqual(4);
});
});
diff --git a/modules/ticket/back/methods/ticket/specs/restore.spec.js b/modules/ticket/back/methods/ticket/specs/restore.spec.js
index 97cf78f91..52135fc54 100644
--- a/modules/ticket/back/methods/ticket/specs/restore.spec.js
+++ b/modules/ticket/back/methods/ticket/specs/restore.spec.js
@@ -4,6 +4,7 @@ const models = app.models;
describe('ticket restore()', () => {
const employeeUser = 110;
+ const ticketId = 18;
const activeCtx = {
accessToken: {userId: employeeUser},
headers: {
@@ -13,45 +14,30 @@ describe('ticket restore()', () => {
};
const ctx = {req: activeCtx};
- let createdTicket;
-
- beforeEach(async done => {
+ beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
- try {
- const sampleTicket = await models.Ticket.findById(11);
- sampleTicket.id = undefined;
-
- createdTicket = await models.Ticket.create(sampleTicket);
- } catch (error) {
- console.error(error);
- }
-
- done();
- });
-
- afterEach(async done => {
- try {
- await models.Ticket.destroyById(createdTicket.id);
- } catch (error) {
- console.error(error);
- }
-
- done();
});
it('should throw an error if the given ticket has past the deletion time', async() => {
let error;
+ const tx = await app.models.Ticket.beginTransaction({});
const now = new Date();
now.setHours(now.getHours() - 1);
try {
- const ticket = await models.Ticket.findById(createdTicket.id);
- await ticket.updateAttributes({isDeleted: true, updated: now});
- await app.models.Ticket.restore(ctx, createdTicket.id);
+ const options = {transaction: tx};
+ const ticket = await models.Ticket.findById(ticketId, null, options);
+ await ticket.updateAttributes({
+ isDeleted: true,
+ updated: now
+ }, options);
+ await app.models.Ticket.restore(ctx, ticketId, options);
+ await tx.rollback();
} catch (e) {
+ await tx.rollback();
error = e;
}
@@ -59,17 +45,37 @@ describe('ticket restore()', () => {
});
it('should restore the ticket making its state no longer deleted', async() => {
+ const tx = await app.models.Ticket.beginTransaction({});
const now = new Date();
- const ticketBeforeUpdate = await models.Ticket.findById(createdTicket.id);
- await ticketBeforeUpdate.updateAttributes({isDeleted: true, updated: now});
- const ticketAfterUpdate = await models.Ticket.findById(createdTicket.id);
+ try {
+ const options = {transaction: tx};
- expect(ticketAfterUpdate.isDeleted).toBeTruthy();
+ const ticketBeforeUpdate = await models.Ticket.findById(ticketId, null, options);
+ await ticketBeforeUpdate.updateAttributes({
+ isDeleted: true,
+ updated: now
+ }, options);
- await models.Ticket.restore(ctx, createdTicket.id);
- const ticketAfterRestore = await models.Ticket.findById(createdTicket.id);
+ const ticketAfterUpdate = await models.Ticket.findById(ticketId, null, options);
- expect(ticketAfterRestore.isDeleted).toBeFalsy();
+ expect(ticketAfterUpdate.isDeleted).toBeTruthy();
+
+ await models.Ticket.restore(ctx, ticketId, options);
+ const ticketAfterRestore = await models.Ticket.findById(ticketId, null, options);
+
+ const fullYear = now.getFullYear();
+ const shippedFullYear = ticketAfterRestore.shipped.getFullYear();
+ const landedFullYear = ticketAfterRestore.landed.getFullYear();
+
+ expect(ticketAfterRestore.isDeleted).toBeFalsy();
+ expect(shippedFullYear).toEqual(fullYear);
+ expect(landedFullYear).toEqual(fullYear);
+
+ await tx.rollback();
+ } catch (e) {
+ await tx.rollback();
+ throw e;
+ }
});
});
diff --git a/modules/ticket/front/request/create/index.html b/modules/ticket/front/request/create/index.html
index f7280b2de..8d25358b5 100644
--- a/modules/ticket/front/request/create/index.html
+++ b/modules/ticket/front/request/create/index.html
@@ -20,7 +20,7 @@
ng-model="$ctrl.ticketRequest.attenderFk"
url="Workers/activeWithRole"
show-field="nickname"
- where="{role: 'buyer'}"
+ where="{role: {inq: ['logistic', 'buyer']}}"
search-function="{firstName: $search}">
diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html
index d4dfe1a8a..5bba457ca 100644
--- a/modules/ticket/front/sale/index.html
+++ b/modules/ticket/front/sale/index.html
@@ -1,7 +1,8 @@
-
+ data="$ctrl.sales"
+ auto-load="true">
+ limit="20"
+ auto-load="true">
+ data="details"
+ auto-load="true">
diff --git a/print/core/components/email-footer/locale/es.yml b/print/core/components/email-footer/locale/es.yml
index 7f4b13045..1269a3e60 100644
--- a/print/core/components/email-footer/locale/es.yml
+++ b/print/core/components/email-footer/locale/es.yml
@@ -1,7 +1,7 @@
buttons:
webAcccess: Visita nuestra Web
info: Ayúdanos a mejorar
-fiscalAddress: VERDNATURA LEVANTE SL, B97367486 Avda. Espioca, 100, 46460 Silla
+fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESI
· www.verdnatura.es · clientes@verdnatura.es
disclaimer: '- AVISO - Este mensaje es privado y confidencial, y debe ser utilizado
exclusivamente por la persona destinataria del mismo. Si has recibido este mensaje