From dc44af148ddcd5c3ef0726a8985b382e192fcce2 Mon Sep 17 00:00:00 2001 From: joan Date: Tue, 11 May 2021 09:43:15 +0200 Subject: [PATCH] 2863 - Added sales monitor --- db/changes/10310-mothersDay/00-ACL.sql | 4 +- db/dump/fixtures.sql | 73 +++- front/core/components/button/style.scss | 8 + front/core/components/table/index.js | 15 +- front/core/components/table/style.scss | 38 ++- .../methods/sales-monitor/clientsFilter.js | 60 ++++ .../methods/sales-monitor/ordersFilter.js | 68 ++++ .../back/methods/sales-monitor/salesFilter.js | 319 ++++++++++++++++++ .../sales-monitor/specs/clientsFilter.spec.js | 11 + .../sales-monitor/specs/ordersFilter.spec.js | 11 + .../sales-monitor/specs/salesFilter.spec.js | 106 ++++++ modules/monitor/back/model-config.json | 3 + modules/monitor/back/models/sales-monitor.js | 5 + .../monitor/back/models/sales-monitor.json | 11 + modules/monitor/front/index.js | 4 + .../monitor/front/index/clients/index.html | 101 ++++++ modules/monitor/front/index/clients/index.js | 30 ++ modules/monitor/front/index/index.html | 159 +-------- modules/monitor/front/index/index.js | 2 +- modules/monitor/front/index/locale/es.yml | 9 +- modules/monitor/front/index/orders/index.html | 85 +++++ modules/monitor/front/index/orders/index.js | 8 + modules/monitor/front/index/orders/style.scss | 16 + .../front/index/search-panel/index.html | 138 ++++++++ .../monitor/front/index/search-panel/index.js | 59 ++++ .../front/index/search-panel/index.spec.js | 71 ++++ .../front/index/search-panel/locale/en.yml | 4 + .../front/index/search-panel/locale/es.yml | 21 ++ modules/monitor/front/index/style.scss | 20 ++ .../monitor/front/index/tickets/index.html | 206 +++++++++++ modules/monitor/front/index/tickets/index.js | 105 ++++++ .../monitor/front/index/tickets/style.scss | 19 ++ modules/monitor/front/locale/es.yml | 3 +- modules/monitor/front/routes.json | 2 +- 34 files changed, 1623 insertions(+), 171 deletions(-) create mode 100644 modules/monitor/back/methods/sales-monitor/clientsFilter.js create mode 100644 modules/monitor/back/methods/sales-monitor/ordersFilter.js create mode 100644 modules/monitor/back/methods/sales-monitor/salesFilter.js create mode 100644 modules/monitor/back/methods/sales-monitor/specs/clientsFilter.spec.js create mode 100644 modules/monitor/back/methods/sales-monitor/specs/ordersFilter.spec.js create mode 100644 modules/monitor/back/methods/sales-monitor/specs/salesFilter.spec.js create mode 100644 modules/monitor/back/models/sales-monitor.js create mode 100644 modules/monitor/back/models/sales-monitor.json create mode 100644 modules/monitor/front/index/clients/index.html create mode 100644 modules/monitor/front/index/clients/index.js create mode 100644 modules/monitor/front/index/orders/index.html create mode 100644 modules/monitor/front/index/orders/index.js create mode 100644 modules/monitor/front/index/orders/style.scss create mode 100644 modules/monitor/front/index/search-panel/index.html create mode 100644 modules/monitor/front/index/search-panel/index.js create mode 100644 modules/monitor/front/index/search-panel/index.spec.js create mode 100644 modules/monitor/front/index/search-panel/locale/en.yml create mode 100644 modules/monitor/front/index/search-panel/locale/es.yml create mode 100644 modules/monitor/front/index/style.scss create mode 100644 modules/monitor/front/index/tickets/index.html create mode 100644 modules/monitor/front/index/tickets/index.js create mode 100644 modules/monitor/front/index/tickets/style.scss diff --git a/db/changes/10310-mothersDay/00-ACL.sql b/db/changes/10310-mothersDay/00-ACL.sql index a2ae1e40f..1067c02f7 100644 --- a/db/changes/10310-mothersDay/00-ACL.sql +++ b/db/changes/10310-mothersDay/00-ACL.sql @@ -1,2 +1,4 @@ INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId) -VALUES ('SupplierAddress', '*', '*', 'ALLOW', 'ROLE', 'employee'); +VALUES + ('SupplierAddress', '*', '*', 'ALLOW', 'ROLE', 'employee'), + ('SalesMonitor', '*', '*', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index f386ad780..79ab9f763 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -277,7 +277,7 @@ INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city (106, 'DavidCharlesHaller', '53136686Q', 'Legion', 'Charles Xavier', 'City of New York, New York, USA', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1), (107, 'Hank Pym', '09854837G', 'Ant man', 'Hawk', 'Anthill, San Francisco, California', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1), (108, 'Charles Xavier', '22641921P', 'Professor X', 'Beast', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1), - (109, 'Bruce Banner', '16104829E', 'Hulk', 'Black widow', 'Somewhere in New York', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1), + (109, 'Bruce Banner', '16104829E', 'Hulk', 'Black widow', 'Somewhere in New York', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1), (110, 'Jessica Jones', '58282869H', 'Jessica Jones', 'Luke Cage', 'NYCC 2015 Poster', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1), (111, 'Missing', NULL, 'Missing man', 'Anton', 'The space, Universe far away', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, NULL, 1, 0, 1, 0, NULL, 1, 0, NULL, 0, 1), (112, 'Trash', NULL, 'Garbage man', 'Unknown name', 'New York city, Underground', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, NULL, 1, 0, 1, 0, NULL, 1, 0, NULL, 0, 1); @@ -1559,6 +1559,66 @@ INSERT INTO `hedera`.`orderRowComponent`(`rowFk`, `componentFk`, `price`) (30, 37, 2), (30, 39, 0.01); +INSERT INTO `hedera`.`visit`(`id`, `firstAgentFk`) + VALUES + (1, NULL), + (2, NULL), + (3, NULL), + (4, NULL), + (5, NULL), + (6, NULL), + (7, NULL), + (8, NULL), + (9, NULL); + +INSERT INTO `hedera`.`visitAgent`(`id`, `visitFk`) + VALUES + (1, 1), + (2, 2), + (3, 3), + (4, 4), + (5, 5), + (6, 6), + (7, 7), + (8, 8), + (9, 9); + +INSERT INTO `hedera`.`visitAccess`(`id`, `agentFk`, `stamp`) + VALUES + (1, 1, CURDATE()), + (2, 2, CURDATE()), + (3, 3, CURDATE()), + (4, 4, CURDATE()), + (5, 5, CURDATE()), + (6, 6, CURDATE()), + (7, 7, CURDATE()), + (8, 8, CURDATE()), + (9, 9, CURDATE()); + +INSERT INTO `hedera`.`visitUser`(`id`, `accessFk`, `userFk`, `stamp`) + VALUES + (1, 1, 101, CURDATE()), + (2, 2, 101, CURDATE()), + (3, 3, 101, CURDATE()), + (4, 4, 102, CURDATE()), + (5, 5, 102, CURDATE()), + (6, 6, 102, CURDATE()), + (7, 7, 103, CURDATE()), + (8, 8, 103, CURDATE()), + (9, 9, 103, CURDATE()); + +INSERT INTO `hedera`.`userSession`(`created`, `lastUpdate`, `ssid`, `data`, `userVisitFk`) + VALUES + (CURDATE(), CURDATE(), '121', 'data', 1), + (CURDATE(), CURDATE(), '122', 'data', 2), + (CURDATE(), CURDATE(), '123', 'data', 3), + (CURDATE(), CURDATE(), '124', 'data', 4), + (CURDATE(), CURDATE(), '125', 'data', 5), + (CURDATE(), CURDATE(), '126', 'data', 6), + (CURDATE(), CURDATE(), '127', 'data', 7), + (CURDATE(), CURDATE(), '128', 'data', 8), + (CURDATE(), CURDATE(), '129', 'data', 9); + INSERT INTO `vn`.`clientContact`(`id`, `clientFk`, `name`, `phone`) VALUES (1, 101, 'contact 1', 666777888), @@ -1688,11 +1748,12 @@ INSERT INTO `vn`.`receipt`(`id`, `invoiceFk`, `amountPaid`, `amountUnpaid`, `pay INSERT INTO `vn`.`workerTeam`(`id`, `team`, `workerFk`) VALUES (1, 1, 9), - (2, 1, 18), - (3, 2, 101), - (4, 2, 102), - (5, 3, 103), - (6, 3, 104); + (2, 2, 18), + (3, 2, 19), + (4, 3, 101), + (5, 3, 102), + (6, 4, 103), + (7, 4, 104); INSERT INTO `vn`.`ticketRequest`(`id`, `description`, `requesterFk`, `attenderFk`, `quantity`, `itemFk`, `price`, `isOk`, `saleFk`, `ticketFk`, `created`) VALUES diff --git a/front/core/components/button/style.scss b/front/core/components/button/style.scss index a3fc386e2..bb784c52a 100644 --- a/front/core/components/button/style.scss +++ b/front/core/components/button/style.scss @@ -123,4 +123,12 @@ } } } + &.small { + height: 24px; + font-size: .75rem; + + & > button { + padding: 0 6px; + } + } } diff --git a/front/core/components/table/index.js b/front/core/components/table/index.js index eadb10cb5..a114ea379 100644 --- a/front/core/components/table/index.js +++ b/front/core/components/table/index.js @@ -23,10 +23,21 @@ export default class Table { this.model.refresh(); } - $onChanges() { + isScrollable() { + return this.table.classList.contains('scrollable'); + } + + $postLink() { + if (this.isScrollable()) { + const childCells = this.table.querySelector('vn-tbody'); + console.log(childCells); + } + } + + /* $onChanges() { if (this.model && !this.model.data) this.applyOrder(); - } + } */ setActiveArrow() { let columns = this.table.querySelectorAll('vn-thead vn-th'); diff --git a/front/core/components/table/style.scss b/front/core/components/table/style.scss index 08106add4..258061f1b 100644 --- a/front/core/components/table/style.scss +++ b/front/core/components/table/style.scss @@ -20,7 +20,6 @@ vn-table { font-weight: normal; } & > * > vn-th[field] { - position: relative; overflow: visible; cursor: pointer } @@ -83,6 +82,11 @@ vn-table { } &[shrink-date] { width: 100px; + max-width: 100px; + } + &[shrink-datetime] { + width: 150px; + max-width: 150px; } &[expand] { max-width: 400px; @@ -182,4 +186,36 @@ vn-table { font-size: 1.375rem; text-align: center; } +} + + +vn-table.scrollable, +vn-table.scrollable > .vn-table, +.vn-table.scrollable { + border-collapse: separate; + overflow: auto; + + vn-thead, thead { + border-bottom: 0px solid transparent + } + + vn-thead th, + vn-thead vn-th, + thead vn-th, + thead th { + border-bottom: 2px solid $color-spacer; + background-color: #FFF; + position: sticky; + top: 0 + } +} + +vn-table.scrollable.sm, +.vn-table.scrollable.sm { + max-height: 300px +} + +vn-table.scrollable.lg, +.vn-table.scrollable.lg { + max-height: 700px } \ No newline at end of file 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..8c945a4c6 --- /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.makeLimit(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..c01c65433 --- /dev/null +++ b/modules/monitor/back/methods/sales-monitor/specs/clientsFilter.spec.js @@ -0,0 +1,11 @@ +const app = require('vn-loopback/server/server'); + +fdescribe('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..df2d9ef29 --- /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: 'dated 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 index 2c63c0851..3ba94c18d 100644 --- a/modules/monitor/back/model-config.json +++ b/modules/monitor/back/model-config.json @@ -1,2 +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 index cb25b3b8c..292363b1d 100644 --- a/modules/monitor/front/index.js +++ b/modules/monitor/front/index.js @@ -3,3 +3,7 @@ export * from './module'; import './main'; import './index/'; import './search-panel'; +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..807cd7845 --- /dev/null +++ b/modules/monitor/front/index/clients/index.html @@ -0,0 +1,101 @@ + + + + + Clients on website + + + + + + + + + + + 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 index d04478bbc..937464891 100644 --- a/modules/monitor/front/index/index.html +++ b/modules/monitor/front/index/index.html @@ -1,160 +1,13 @@ - - - - - - - - - - - - - Id - Salesperson - Date - Hour - Closure - Alias - Province - State - Zone - Warehouse - Total - - - - - - - - - - - - - - - - - - - - - - {{::ticket.id}} - - - {{::ticket.userName | dashIfEmpty}} - - - - - {{::ticket.shipped | date: 'dd/MM/yyyy'}} - - - {{::ticket.shipped | date: 'HH:mm'}} - {{::ticket.zoneLanding | date: 'HH:mm'}} - - - {{::ticket.nickname}} - - - {{::ticket.province}} - - - {{::ticket.refFk}} - - - {{ticket.state}} - - - - - {{::ticket.zoneName | dashIfEmpty}} - - - {{::ticket.warehouse}} - - - {{::(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}} - - - - - - - - - - - - - + + + - - - b - + + - c + \ No newline at end of file diff --git a/modules/monitor/front/index/index.js b/modules/monitor/front/index/index.js index bd138d59a..9be66d6d8 100644 --- a/modules/monitor/front/index/index.js +++ b/modules/monitor/front/index/index.js @@ -1,6 +1,6 @@ import ngModule from '../module'; import Section from 'salix/components/section'; - +import './style.scss'; export default class Controller extends Section { openSummary(supplier, event) { if (event.defaultPrevented) return; diff --git a/modules/monitor/front/index/locale/es.yml b/modules/monitor/front/index/locale/es.yml index ad8a4f0bb..b709d0922 100644 --- a/modules/monitor/front/index/locale/es.yml +++ b/modules/monitor/front/index/locale/es.yml @@ -1,5 +1,4 @@ -Payment deadline: Plazo de pago -Pay day: Dia de pago -Account: Cuenta -Pay method: Metodo de pago -Tax number: Nif \ No newline at end of file +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..d68815bd3 --- /dev/null +++ b/modules/monitor/front/index/orders/index.html @@ -0,0 +1,85 @@ + + + + + Recent order actions + + + + + + + + + + + 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 @@ +
+
+ + + + + + + + + + +
+ + + + + + Or + + + + + +
+ + + + + {{firstName}} {{name}} + + + + + + + + + + + + {{name}} + + + + + + + + + + + + + + + + + + + + +
+
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..f3a2f39ed --- /dev/null +++ b/modules/monitor/front/index/search-panel/index.spec.js @@ -0,0 +1,71 @@ +import './index'; + +describe('Ticket Component vnTicketSearchPanel', () => { + let $httpBackend; + let controller; + + beforeEach(ngModule('ticket')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnTicketSearchPanel', {$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/search-panel/locale/en.yml b/modules/monitor/front/index/search-panel/locale/en.yml new file mode 100644 index 000000000..21ba978db --- /dev/null +++ b/modules/monitor/front/index/search-panel/locale/en.yml @@ -0,0 +1,4 @@ +FREE: Free +DELIVERED: Delivered +ON_PREPARATION: On preparation +PACKED: Packed \ No newline at end of file diff --git a/modules/monitor/front/index/search-panel/locale/es.yml b/modules/monitor/front/index/search-panel/locale/es.yml new file mode 100644 index 000000000..5872609f9 --- /dev/null +++ b/modules/monitor/front/index/search-panel/locale/es.yml @@ -0,0 +1,21 @@ +Ticket id: Id ticket +Client id: Id cliente +Nickname: Alias +From: Desde +To: Hasta +Agency: Agencia +Warehouse: Almacén +Sales person: Comercial +Province: Provincia +My team: Mi equipo +Order id: Id cesta +Grouped States: Estado agrupado +Days onward: Días adelante +With problems: Con problemas +Pending: Pendiente +FREE: Libre +DELIVERED: Servido +ON_PREPARATION: En preparacion +PACKED: Encajado +Cannot choose a range of dates and days onward at the same time: No se puede selecionar un rango de fechas y días en adelante a la vez +Or: O \ No newline at end of file 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..049223ef2 --- /dev/null +++ b/modules/monitor/front/index/tickets/index.html @@ -0,0 +1,206 @@ + + + + + + + + + Tickets monitor + + + + + + + + + + + + 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/style.scss b/modules/monitor/front/index/tickets/style.scss new file mode 100644 index 000000000..b89ce8dbe --- /dev/null +++ b/modules/monitor/front/index/tickets/style.scss @@ -0,0 +1,19 @@ +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; + } +} \ No newline at end of file diff --git a/modules/monitor/front/locale/es.yml b/modules/monitor/front/locale/es.yml index abb6a9775..da3b3f623 100644 --- a/modules/monitor/front/locale/es.yml +++ b/modules/monitor/front/locale/es.yml @@ -1 +1,2 @@ -Accounts: Cuentas \ No newline at end of file +Monitors: Monitores +Sales monitor: Monitor de ventas \ No newline at end of file diff --git a/modules/monitor/front/routes.json b/modules/monitor/front/routes.json index f0c2d9e98..2b60b3572 100644 --- a/modules/monitor/front/routes.json +++ b/modules/monitor/front/routes.json @@ -2,7 +2,7 @@ "module": "monitor", "name": "Monitors", "icon" : "icon-supplier", - "dependencies": ["ticket", "item"], + "dependencies": ["ticket", "worker", "client"], "validations" : true, "menus": { "main": [