diff --git a/db/changes/231201/.gitkeep b/db/changes/231201/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/db/changes/231201/00-invoiceInSerial.sql b/db/changes/231201/00-invoiceInSerial.sql new file mode 100644 index 000000000..de476027c --- /dev/null +++ b/db/changes/231201/00-invoiceInSerial.sql @@ -0,0 +1,4 @@ +ALTER TABLE `vn`.`invoiceInConfig` ADD daysAgo INT UNSIGNED DEFAULT 45 COMMENT 'Días en el pasado para mostrar facturas en invoiceIn series en salix'; +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('InvoiceIn', 'getSerial', 'READ', 'ALLOW', 'ROLE', 'administrative'); diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index e9c70878f..61b7cba9e 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2490,9 +2490,9 @@ REPLACE INTO `vn`.`invoiceIn`(`id`, `serialNumber`,`serial`, `supplierFk`, `issu (9, 1009, 'R', 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1242, 1, 442, 1), (10, 1010, 'R', 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1243, 1, 442, 1); -INSERT INTO `vn`.`invoiceInConfig` (`id`, `retentionRate`, `retentionName`, `sageWithholdingFk`) +INSERT INTO `vn`.`invoiceInConfig` (`id`, `retentionRate`, `retentionName`, `sageWithholdingFk`, `daysAgo`) VALUES - (1, -2, '2% retention', 2); + (1, -2, '2% retention', 2, 45); INSERT INTO `vn`.`invoiceInDueDay`(`invoiceInFk`, `dueDated`, `bankFk`, `amount`) VALUES diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index f4c67f002..97693e71e 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -1127,6 +1127,15 @@ export default { saveButton: 'vn-invoice-in-tax vn-submit', }, + invoiceInIndex: { + topbarSearchParams: 'vn-searchbar div.search-params > span', + }, + invoiceInSerial: { + daysAgo: 'vn-invoice-in-serial-search-panel vn-input-number[ng-model="$ctrl.filter.daysAgo"]', + serial: 'vn-invoice-in-serial-search-panel vn-textfield[ng-model="$ctrl.filter.serial"]', + chip: 'vn-chip > vn-icon', + goToIndex: 'vn-invoice-in-serial vn-icon-button[icon="icon-invoice-in"]', + }, travelIndex: { anySearchResult: 'vn-travel-index vn-tbody > a', firstSearchResult: 'vn-travel-index vn-tbody > a:nth-child(1)', diff --git a/e2e/paths/09-invoice-in/05_serial.spec.js b/e2e/paths/09-invoice-in/05_serial.spec.js new file mode 100644 index 000000000..3aa94f48c --- /dev/null +++ b/e2e/paths/09-invoice-in/05_serial.spec.js @@ -0,0 +1,48 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +describe('InvoiceIn serial path', () => { + let browser; + let page; + let httpRequest; + + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('administrative', 'invoiceIn'); + await page.accessToSection('invoiceIn.serial'); + page.on('request', req => { + if (req.url().includes(`InvoiceIns/getSerial`)) + httpRequest = req.url(); + }); + }); + + afterAll(async() => { + await browser.close(); + }); + + it('should check that passes the correct params to back', async() => { + await page.overwrite(selectors.invoiceInSerial.daysAgo, '30'); + await page.keyboard.press('Enter'); + + expect(httpRequest).toContain('daysAgo=30'); + + await page.overwrite(selectors.invoiceInSerial.serial, 'R'); + await page.keyboard.press('Enter'); + + expect(httpRequest).toContain('serial=R'); + await page.click(selectors.invoiceInSerial.chip); + }); + + it('should go to index and check if the search-panel has the correct params', async() => { + await page.click(selectors.invoiceInSerial.goToIndex); + const params = await page.$$(selectors.invoiceInIndex.topbarSearchParams); + const serial = await params[0].getProperty('title'); + const isBooked = await params[1].getProperty('title'); + const from = await params[2].getProperty('title'); + + expect(await serial.jsonValue()).toContain('serial'); + expect(await isBooked.jsonValue()).toContain('not isBooked'); + expect(await from.jsonValue()).toContain('from'); + }); +}); diff --git a/modules/invoiceIn/back/methods/invoice-in/getSerial.js b/modules/invoiceIn/back/methods/invoice-in/getSerial.js new file mode 100644 index 000000000..dcc1fbc3c --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/getSerial.js @@ -0,0 +1,65 @@ +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +const buildFilter = require('vn-loopback/util/filter').buildFilter; +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethodCtx('getSerial', { + description: 'Return invoiceIn serial', + accessType: 'READ', + accepts: [{ + arg: 'filter', + type: 'object' + }, { + arg: 'daysAgo', + type: 'number', + required: true + }, { + arg: 'serial', + type: 'string' + }], + returns: { + type: 'object', + root: true + }, + http: { + path: '/getSerial', + verb: 'GET' + } + }); + + Self.getSerial = async(ctx, options) => { + const conn = Self.dataSource.connector; + const args = ctx.args; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const issued = Date.vnNew(); + const where = buildFilter(args, (param, value) => { + switch (param) { + case 'daysAgo': + issued.setDate(issued.getDate() - value); + return {'i.issued': {gte: issued}}; + case 'serial': + return {'i.serial': {like: `%${value}%`}}; + } + }); + + filter = mergeFilters(args.filter, {where}); + + const stmt = new ParameterizedSQL( + `SELECT i.serial, SUM(IF(i.isBooked, 0,1)) pending, COUNT(*) total + FROM vn.invoiceIn i` + ); + + stmt.merge(conn.makeWhere(filter.where)); + stmt.merge(`GROUP BY i.serial`); + stmt.merge(conn.makeOrderBy(filter.order)); + stmt.merge(conn.makeLimit(filter)); + + const result = await conn.executeStmt(stmt, myOptions); + + return result; + }; +}; diff --git a/modules/invoiceIn/back/methods/invoice-in/specs/getSerial.spec.js b/modules/invoiceIn/back/methods/invoice-in/specs/getSerial.spec.js new file mode 100644 index 000000000..6224ce9ac --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/specs/getSerial.spec.js @@ -0,0 +1,24 @@ +const models = require('vn-loopback/server/server').models; + +describe('invoiceIn getSerial()', () => { + it('should check that returns without serial param', async() => { + const ctx = {args: {daysAgo: 45}}; + const result = await models.InvoiceIn.getSerial(ctx); + + expect(result.length).toBeGreaterThan(0); + }); + + it('should check that returns with serial param', async() => { + const ctx = {args: {daysAgo: 45, serial: 'R'}}; + const result = await models.InvoiceIn.getSerial(ctx); + + expect(result.length).toBeGreaterThan(0); + }); + + it('should check that returns with non exist serial param', async() => { + const ctx = {args: {daysAgo: 45, serial: 'Mock serial'}}; + const result = await models.InvoiceIn.getSerial(ctx); + + expect(result.length).toEqual(0); + }); +}); diff --git a/modules/invoiceIn/back/models/invoice-in-config.json b/modules/invoiceIn/back/models/invoice-in-config.json index 5cf0ed64c..c0236e654 100644 --- a/modules/invoiceIn/back/models/invoice-in-config.json +++ b/modules/invoiceIn/back/models/invoice-in-config.json @@ -17,6 +17,9 @@ }, "retentionName": { "type": "string" + }, + "daysAgo": { + "type": "number" } }, "relations": { diff --git a/modules/invoiceIn/back/models/invoice-in.js b/modules/invoiceIn/back/models/invoice-in.js index 95ccc7b20..51905ccb8 100644 --- a/modules/invoiceIn/back/models/invoice-in.js +++ b/modules/invoiceIn/back/models/invoice-in.js @@ -6,4 +6,5 @@ module.exports = Self => { require('../methods/invoice-in/getTotals')(Self); require('../methods/invoice-in/invoiceInPdf')(Self); require('../methods/invoice-in/invoiceInEmail')(Self); + require('../methods/invoice-in/getSerial')(Self); }; diff --git a/modules/invoiceIn/front/index.js b/modules/invoiceIn/front/index.js index 7b6d6a77c..e257cfee3 100644 --- a/modules/invoiceIn/front/index.js +++ b/modules/invoiceIn/front/index.js @@ -13,3 +13,5 @@ import './dueDay'; import './intrastat'; import './create'; import './log'; +import './serial'; +import './serial-search-panel'; diff --git a/modules/invoiceIn/front/locale/es.yml b/modules/invoiceIn/front/locale/es.yml index 35b43f9f6..a2d658519 100644 --- a/modules/invoiceIn/front/locale/es.yml +++ b/modules/invoiceIn/front/locale/es.yml @@ -7,6 +7,7 @@ Foreign value: Divisa InvoiceIn: Facturas recibidas InvoiceIn cloned: Factura clonada InvoiceIn deleted: Factura eliminada +InvoiceIn Serial: Facturas por series Invoice list: Listado de facturas recibidas InvoiceIn booked: Factura contabilizada Net: Neto @@ -22,3 +23,4 @@ Total stems: Total tallos Show agricultural receipt as PDF: Ver recibo agrícola como PDF Send agricultural receipt as PDF: Enviar recibo agrícola como PDF New InvoiceIn: Nueva Factura +Days ago: Últimos días diff --git a/modules/invoiceIn/front/routes.json b/modules/invoiceIn/front/routes.json index 4867b7db9..90c4f8472 100644 --- a/modules/invoiceIn/front/routes.json +++ b/modules/invoiceIn/front/routes.json @@ -12,6 +12,10 @@ { "state": "invoiceIn.index", "icon": "icon-invoice-in" + }, + { + "state": "invoiceIn.serial", + "icon": "icon-invoice-in" } ], "card": [ @@ -54,6 +58,15 @@ "administrative" ] }, + { + "url": "/serial", + "state": "invoiceIn.serial", + "component": "vn-invoice-in-serial", + "description": "InvoiceIn Serial", + "acl": [ + "administrative" + ] + }, { "url": "/:id", "state": "invoiceIn.card", @@ -133,4 +146,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/modules/invoiceIn/front/serial-search-panel/index.html b/modules/invoiceIn/front/serial-search-panel/index.html new file mode 100644 index 000000000..0dda54852 --- /dev/null +++ b/modules/invoiceIn/front/serial-search-panel/index.html @@ -0,0 +1,27 @@ + + + + + + + + + +
+ + {{$ctrl.$t('Serial')}}: {{$ctrl.filter.serial}} + +
+
diff --git a/modules/invoiceIn/front/serial-search-panel/index.js b/modules/invoiceIn/front/serial-search-panel/index.js new file mode 100644 index 000000000..b11911ee3 --- /dev/null +++ b/modules/invoiceIn/front/serial-search-panel/index.js @@ -0,0 +1,44 @@ +import ngModule from '../module'; +import SearchPanel from 'core/components/searchbar/search-panel'; +import './style.scss'; + +class Controller extends SearchPanel { + constructor($element, $) { + super($element, $); + this.filter = {}; + const filter = { + fields: ['daysAgo'] + }; + this.$http.get('InvoiceInConfigs', {filter}).then(res => { + if (res.data) { + this.invoiceInConfig = res.data[0]; + this.addFilters(); + } + }); + } + + removeItemFilter(param) { + this.filter[param] = null; + this.addFilters(); + } + + onKeyPress($event) { + if ($event.key === 'Enter') + this.addFilters(); + } + + addFilters() { + if (!this.filter.daysAgo) + this.filter.daysAgo = this.invoiceInConfig.daysAgo; + + return this.model.addFilter({}, this.filter); + } +} + +ngModule.component('vnInvoiceInSerialSearchPanel', { + template: require('./index.html'), + controller: Controller, + bindings: { + model: '<' + } +}); diff --git a/modules/invoiceIn/front/serial-search-panel/index.spec.js b/modules/invoiceIn/front/serial-search-panel/index.spec.js new file mode 100644 index 000000000..b5228e126 --- /dev/null +++ b/modules/invoiceIn/front/serial-search-panel/index.spec.js @@ -0,0 +1,43 @@ +import './index.js'; + +describe('InvoiceIn', () => { + describe('Component serial-search-panel', () => { + let controller; + let $scope; + + beforeEach(ngModule('invoiceIn')); + + beforeEach(inject(($componentController, $rootScope) => { + $scope = $rootScope.$new(); + const $element = angular.element(''); + controller = $componentController('vnInvoiceInSerialSearchPanel', {$element, $scope}); + controller.model = { + addFilter: jest.fn(), + }; + controller.invoiceInConfig = { + daysAgo: 45, + }; + })); + + describe('addFilters()', () => { + it('should add default daysAgo if it is not already set', () => { + controller.filter = { + serial: 'R', + }; + controller.addFilters(); + + expect(controller.filter.daysAgo).toEqual(controller.invoiceInConfig.daysAgo); + }); + + it('should not add default daysAgo if it is already set', () => { + controller.filter = { + daysAgo: 1, + serial: 'R', + }; + controller.addFilters(); + + expect(controller.filter.daysAgo).toEqual(1); + }); + }); + }); +}); diff --git a/modules/invoiceIn/front/serial-search-panel/style.scss b/modules/invoiceIn/front/serial-search-panel/style.scss new file mode 100644 index 000000000..4abfcbfa2 --- /dev/null +++ b/modules/invoiceIn/front/serial-search-panel/style.scss @@ -0,0 +1,24 @@ +@import "variables"; + +vn-invoice-in-serial-search-panel vn-side-menu div { + & > .input { + padding-left: $spacing-md; + padding-right: $spacing-md; + border-color: $color-spacer; + border-bottom: $border-thin; + } + & > .horizontal { + grid-auto-flow: column; + grid-column-gap: $spacing-sm; + align-items: center; + } + & > .chips { + display: flex; + flex-wrap: wrap; + padding: $spacing-md; + overflow: hidden; + max-width: 100%; + border-color: $color-spacer; + border-top: $border-thin; + } +} diff --git a/modules/invoiceIn/front/serial/index.html b/modules/invoiceIn/front/serial/index.html new file mode 100644 index 000000000..1649ec7d7 --- /dev/null +++ b/modules/invoiceIn/front/serial/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + Serial + Pending + Total + + + + + + {{::invoiceIn.serial}} + {{::invoiceIn.pending}} + {{::invoiceIn.total}} + + + + + + + + + diff --git a/modules/invoiceIn/front/serial/index.js b/modules/invoiceIn/front/serial/index.js new file mode 100644 index 000000000..193a57492 --- /dev/null +++ b/modules/invoiceIn/front/serial/index.js @@ -0,0 +1,22 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + } + + goToIndex(daysAgo, serial) { + const issued = Date.vnNew(); + issued.setDate(issued.getDate() - daysAgo); + this.$state.go('invoiceIn.index', + {q: `{"serial": "${serial}", "isBooked": false, "from": ${issued.getTime()}}`}); + } +} + +Controller.$inject = ['$element', '$scope']; + +ngModule.vnComponent('vnInvoiceInSerial', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/invoiceIn/front/serial/locale/es.yml b/modules/invoiceIn/front/serial/locale/es.yml new file mode 100644 index 000000000..92a49cc82 --- /dev/null +++ b/modules/invoiceIn/front/serial/locale/es.yml @@ -0,0 +1,3 @@ +Serial: Serie +Pending: Pendientes +Go to InvoiceIn: Ir al listado de facturas recibidas