fixes #5184 InvoiceIn crear seccion invoiceIn serial #1414

Merged
joan merged 6 commits from 5184-invoiceIn-serial into dev 2023-03-30 05:18:45 +00:00
19 changed files with 377 additions and 3 deletions

View File

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

View File

@ -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

View File

@ -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)',

View File

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

View File

@ -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(
alexandre marked this conversation as resolved
Review

si fas un subselect pero fora no fas res mes, posiblemente no el necesites, fes directament la consulta

si fas un subselect pero fora no fas res mes, posiblemente no el necesites, fes directament la consulta
`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;
};
};

View File

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

View File

@ -17,6 +17,9 @@
},
"retentionName": {
"type": "string"
},
"daysAgo": {
"type": "number"
}
},
"relations": {

View File

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

View File

@ -13,3 +13,5 @@ import './dueDay';
import './intrastat';
import './create';
import './log';
import './serial';
import './serial-search-panel';

View File

@ -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

View File

@ -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 @@
]
}
]
}
}

View File

@ -0,0 +1,27 @@
<vn-side-menu side="right">
<vn-horizontal class="input">
<vn-input-number
label="Days ago"
ng-model="$ctrl.filter.daysAgo"
vn-focus
ng-keydown="$ctrl.onKeyPress($event)"
min="0">
</vn-input-number>
</vn-horizontal>
<vn-horizontal class="input">
<vn-textfield
label="Serial"
ng-model="$ctrl.filter.serial"
ng-keydown="$ctrl.onKeyPress($event)">
</vn-textfield>
</vn-horizontal>
<div class="chips">
<vn-chip
ng-if="$ctrl.filter.serial"
removable="true"
on-remove="$ctrl.removeItemFilter('serial')"
class="colored">
<span>{{$ctrl.$t('Serial')}}: {{$ctrl.filter.serial}}</span>
</vn-chip>
</div>
</vn-side-menu>

View File

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

View File

@ -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('<vn-invoice-in-serial-search-panel></vn-invoice-in-serial-search-panel>');
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);
});
});
});
});

View File

@ -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;
}
}

View File

@ -0,0 +1,40 @@
<vn-crud-model
vn-id="model"
url="InvoiceIns/getSerial"
limit="20">
</vn-crud-model>
<vn-portal slot="topbar">
</vn-portal>
<vn-invoice-in-serial-search-panel
model="model">
</vn-invoice-in-serial-search-panel>
<vn-data-viewer
model="model"
class="vn-w-lg">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="serial">Serial</vn-th>
<vn-th field="pending">Pending</vn-th>
<vn-th field="total">Total</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="invoiceIn in model.data">
<vn-td>{{::invoiceIn.serial}}</vn-td>
<vn-td>{{::invoiceIn.pending}}</vn-td>
<vn-td>{{::invoiceIn.total}}</vn-td>
<vn-td shrink>
<vn-icon-button
vn-click-stop="$ctrl.goToIndex(model.userParams.daysAgo, invoiceIn.serial)"
vn-tooltip="Go to InvoiceIn"
icon="icon-invoice-in">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>

View File

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

View File

@ -0,0 +1,3 @@
Serial: Serie
Pending: Pendientes
Go to InvoiceIn: Ir al listado de facturas recibidas