fixes #5184 InvoiceIn crear seccion invoiceIn serial #1414
|
@ -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');
|
|
@ -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
|
||||
|
|
|
@ -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)',
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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
|
||||
`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;
|
||||
};
|
||||
};
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -17,6 +17,9 @@
|
|||
},
|
||||
"retentionName": {
|
||||
"type": "string"
|
||||
},
|
||||
"daysAgo": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -13,3 +13,5 @@ import './dueDay';
|
|||
import './intrastat';
|
||||
import './create';
|
||||
import './log';
|
||||
import './serial';
|
||||
import './serial-search-panel';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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: '<'
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
Serial: Serie
|
||||
Pending: Pendientes
|
||||
Go to InvoiceIn: Ir al listado de facturas recibidas
|
Loading…
Reference in New Issue
si fas un subselect pero fora no fas res mes, posiblemente no el necesites, fes directament la consulta