Changes
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Joan Sanchez 2021-02-12 12:19:42 +01:00
parent c270ec6207
commit e7c1dc2210
22 changed files with 63 additions and 598 deletions

View File

@ -236,9 +236,9 @@ export default class Contextmenu {
* value to the clipboard
*/
copyValue() {
console.log(this.field);
const cell = angular.element(this.cell);
if (navigator && navigator.clipboard)
navigator.clipboard.writeText(this.fieldValue);
navigator.clipboard.writeText(cell.text());
}
}

View File

@ -43,6 +43,7 @@ Workers: Trabajadores
Routes: Rutas
Locator: Localizador
Invoices out: Facturas emitidas
Invoices in: Fact. recibidas
Entries: Entradas
Users: Usuarios
Suppliers: Proveedores

View File

@ -6,6 +6,7 @@ import './modules/zone/front/module.js';
import './modules/claim/front/module.js';
import './modules/client/front/module.js';
import './modules/invoiceOut/front/module.js';
import './modules/invoiceIn/front/module.js';
import './modules/item/front/module.js';
import './modules/order/front/module.js';
import './modules/route/front/module.js';

View File

@ -1,59 +0,0 @@
const fs = require('fs-extra');
module.exports = Self => {
Self.remoteMethod('download', {
description: 'Download an invoice PDF',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'String',
description: 'The invoice id',
http: {source: 'path'}
}
],
returns: [
{
arg: 'body',
type: 'file',
root: true
}, {
arg: 'Content-Type',
type: 'String',
http: {target: 'header'}
}, {
arg: 'Content-Disposition',
type: 'String',
http: {target: 'header'}
}
],
http: {
path: `/:id/download`,
verb: 'GET'
}
});
Self.download = async function(id) {
let file;
let env = process.env.NODE_ENV;
let [invoice] = await Self.rawSql(`SELECT invoiceOut_getPath(?) path`, [id]);
if (env && env != 'development') {
file = {
path: `/var/lib/salix/pdfs/${invoice.path}`,
contentType: 'application/pdf',
name: `${id}.pdf`
};
} else {
file = {
path: `${process.cwd()}/README.md`,
contentType: 'text/plain',
name: `README.md`
};
}
await fs.access(file.path);
let stream = fs.createReadStream(file.path);
return [stream, file.contentType, `filename="${file.name}"`];
};
};

View File

@ -140,7 +140,6 @@ module.exports = Self => {
let itemsIndex = stmts.push(stmt) - 1;
let sql = ParameterizedSQL.join(stmts, ';');
console.log(sql.sql);
let result = await conn.executeStmt(sql);
return itemsIndex === 0 ? result : result[itemsIndex];
};

View File

@ -1,32 +0,0 @@
const app = require('vn-loopback/server/server');
describe('invoiceOut book()', () => {
const invoiceOutId = 5;
it('should check that invoice out booked is untainted', async() => {
const invoiceOut = await app.models.InvoiceOut.findById(invoiceOutId);
expect(invoiceOut.booked).toBeDefined();
expect(invoiceOut.hasPdf).toBeTruthy();
});
it('should update the booked property', async() => {
const originalInvoiceOut = await app.models.InvoiceOut.findById(invoiceOutId);
const bookedDate = originalInvoiceOut.booked;
const invoiceOutRef = originalInvoiceOut.ref;
await app.models.InvoiceOut.book(invoiceOutRef);
const updatedInvoiceOut = await app.models.InvoiceOut.findById(invoiceOutId);
expect(updatedInvoiceOut.booked).not.toEqual(bookedDate);
expect(updatedInvoiceOut.hasPdf).toBeFalsy();
// restores
await updatedInvoiceOut.updateAttributes({
booked: originalInvoiceOut.booked,
hasPdf: originalInvoiceOut.hasPdf,
amount: originalInvoiceOut.amount
});
});
});

View File

@ -1,38 +0,0 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('invoiceOut delete()', () => {
const invoiceOutId = 2;
let originalInvoiceOut;
let originalTicket;
const userId = 106;
const activeCtx = {
accessToken: {userId: userId},
};
it('should check that there is one ticket in the target invoiceOut', async() => {
const invoiceOut = await app.models.InvoiceOut.findById(invoiceOutId);
const tickets = await app.models.Ticket.find({where: {refFk: invoiceOut.ref}});
expect(tickets.length).toEqual(1);
expect(tickets[0].id).toEqual(3);
});
it(`should delete the target invoiceOut then check the ticket doesn't have a refFk anymore`, async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
originalInvoiceOut = await app.models.InvoiceOut.findById(invoiceOutId);
await app.models.InvoiceOut.delete(invoiceOutId);
originalTicket = await app.models.Ticket.findById(3);
const deletedInvoiceOut = await app.models.InvoiceOut.findById(invoiceOutId);
expect(deletedInvoiceOut).toBeNull();
expect(originalTicket.refFk).toBeNull();
// restores
const restoredInvoiceOut = await app.models.InvoiceOut.create(originalInvoiceOut);
await restoredInvoiceOut.updateAttribute('ref', originalInvoiceOut.ref);
await originalTicket.updateAttribute('refFk', restoredInvoiceOut.ref);
});
});

View File

@ -1,10 +0,0 @@
const app = require('vn-loopback/server/server');
describe('InvoiceOut download()', () => {
it('should return the downloaded fine name', async() => {
let result = await app.models.InvoiceOut.download(1);
expect(result[1]).toEqual('text/plain');
expect(result[2]).toEqual('filename="README.md"');
});
});

View File

@ -1,109 +1,81 @@
const app = require('vn-loopback/server/server');
describe('InvoiceOut filter()', () => {
let today = new Date();
today.setHours(2, 0, 0, 0);
it('should return the invoice out matching ref', async() => {
fdescribe('InvoiceIn filter()', () => {
it('should return the invoice in matching supplier name', async() => {
let ctx = {
args: {
search: 'T4444444',
search: 'Plants SL',
}
};
let result = await app.models.InvoiceOut.filter(ctx);
expect(result.length).toEqual(1);
expect(result[0].ref).toEqual('T4444444');
});
it('should return the invoice out matching clientFk', async() => {
let ctx = {
args: {
clientFk: 102,
}
};
let result = await app.models.InvoiceOut.filter(ctx);
expect(result.length).toEqual(1);
expect(result[0].ref).toEqual('T2222222');
});
it('should return the invoice out matching hasPdf', async() => {
let ctx = {
args: {
hasPdf: true,
}
};
let result = await app.models.InvoiceOut.filter(ctx);
expect(result.length).toEqual(5);
});
it('should return the invoice out matching amount', async() => {
let ctx = {
args: {
amount: 121.36,
}
};
let result = await app.models.InvoiceOut.filter(ctx);
expect(result.length).toEqual(1);
expect(result[0].ref).toEqual('T2222222');
});
it('should return the invoice out matching min and max', async() => {
let ctx = {
args: {
min: 0,
max: 100,
}
};
let result = await app.models.InvoiceOut.filter(ctx);
let result = await app.models.InvoiceIn.filter(ctx);
expect(result.length).toEqual(3);
expect(result[0].supplierName).toEqual('Plants SL');
});
// #1428 cuadrar formato de horas
xit('should return the invoice out matching issued', async() => {
it('should return the invoice in matching supplier reference', async() => {
let ctx = {
args: {
issued: today,
supplierRef: '4233',
}
};
let result = await app.models.InvoiceOut.filter(ctx);
let result = await app.models.InvoiceIn.filter(ctx);
expect(result.length).toEqual(1);
expect(result[0].supplierRef).toEqual('4233');
});
// #1428 cuadrar formato de horas
xit('should return the invoice out matching created', async() => {
it('should return the invoice in matching the serial number', async() => {
let ctx = {
args: {
created: today,
serialNumber: '1002',
}
};
let result = await app.models.InvoiceOut.filter(ctx);
expect(result.length).toEqual(5);
});
// #1428 cuadrar formato de horas
xit('should return the invoice out matching dued', async() => {
let ctx = {
args: {
dued: today
}
};
let result = await app.models.InvoiceOut.filter(ctx);
let result = await app.models.InvoiceIn.filter(ctx);
expect(result.length).toEqual(1);
expect(result[0].serialNumber).toEqual(1002);
});
it('should return the invoice in matching the account', async() => {
let ctx = {
args: {
account: '4000020002',
}
};
let result = await app.models.InvoiceIn.filter(ctx);
expect(result.length).toEqual(4);
expect(result[0].account).toEqual('4000020002');
});
it('should return the invoice in matching the amount', async() => {
let ctx = {
args: {
amount: '64.23',
}
};
let result = await app.models.InvoiceIn.filter(ctx);
expect(result.length).toEqual(1);
expect(result[0].amount).toEqual(64.23);
});
it('should return the booked invoice in', async() => {
let ctx = {
args: {
isBooked: true,
}
};
let result = await app.models.InvoiceIn.filter(ctx);
expect(result.length).toEqual(3);
expect(result[0].isBooked).toBeTruthy();
});
});

View File

@ -1,36 +0,0 @@
const app = require('vn-loopback/server/server');
describe('invoiceOut regenerate()', () => {
const invoiceReportFk = 30;
const invoiceOutId = 1;
it('should check that the invoice has a PDF and is not in print generation queue', async() => {
const invoiceOut = await app.models.InvoiceOut.findById(invoiceOutId);
const [queue] = await app.models.InvoiceOut.rawSql(`
SELECT COUNT(*) AS total
FROM vn.printServerQueue
WHERE reportFk = ?`, [invoiceReportFk]);
expect(invoiceOut.hasPdf).toBeTruthy();
expect(queue.total).toEqual(0);
});
it(`should mark the invoice as doesn't have PDF and add it to a print queue`, async() => {
const ctx = {req: {accessToken: {userId: 5}}};
const invoiceOut = await app.models.InvoiceOut.regenerate(ctx, invoiceOutId);
const [queue] = await app.models.InvoiceOut.rawSql(`
SELECT COUNT(*) AS total
FROM vn.printServerQueue
WHERE reportFk = ?`, [invoiceReportFk]);
expect(invoiceOut.hasPdf).toBeFalsy();
expect(queue.total).toEqual(1);
// restores
const invoiceOutToRestore = await app.models.InvoiceOut.findById(invoiceOutId);
await invoiceOutToRestore.updateAttributes({hasPdf: true});
await app.models.InvoiceOut.rawSql(`
DELETE FROM vn.printServerQueue
WHERE reportFk = ?`, [invoiceReportFk]);
});
});

View File

@ -1,33 +0,0 @@
const app = require('vn-loopback/server/server');
describe('invoiceOut summary()', () => {
it('should return a summary object containing data from one invoiceOut', async() => {
const result = await app.models.InvoiceOut.summary(1);
expect(result.invoiceOut.id).toEqual(1);
});
it(`should return a summary object containing data from it's tickets`, async() => {
const summary = await app.models.InvoiceOut.summary(1);
const tickets = summary.invoiceOut.tickets();
expect(summary.invoiceOut.ref).toEqual('T1111111');
expect(tickets.length).toEqual(2);
});
it(`should return a summary object containing it's supplier country`, async() => {
const summary = await app.models.InvoiceOut.summary(1);
const supplier = summary.invoiceOut.supplier();
expect(summary.invoiceOut.ref).toEqual('T1111111');
expect(supplier.id).toEqual(442);
expect(supplier.countryFk).toEqual(1);
});
it(`should return a summary object containing idata from it's tax types`, async() => {
const summary = await app.models.InvoiceOut.summary(1);
expect(summary.invoiceOut.ref).toEqual('T1111111');
expect(summary.invoiceOut.taxesBreakdown.length).toEqual(2);
});
});

View File

@ -1,111 +0,0 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('summary', {
description: 'The invoiceOut summary',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The invoiceOut id',
http: {source: 'path'}
}],
returns: {
type: 'object',
root: true
},
http: {
path: `/:id/summary`,
verb: 'GET'
}
});
Self.summary = async id => {
const conn = Self.dataSource.connector;
let summary = {};
const filter = {
fields: [
'id',
'ref',
'issued',
'dued',
'amount',
'created',
'booked',
'clientFk',
'companyFk',
'hasPdf'
],
where: {id: id},
include: [
{
relation: 'company',
scope: {
fields: ['id', 'code']
}
},
{
relation: 'supplier',
scope: {
fields: ['id', 'countryFk']
}
},
{
relation: 'client',
scope: {
fields: ['id', 'socialName']
}
},
{
relation: 'tickets'
}
]
};
summary.invoiceOut = await Self.app.models.InvoiceOut.findOne(filter);
let stmts = [];
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.ticket');
stmt = new ParameterizedSQL(`
CREATE TEMPORARY TABLE tmp.ticket
(INDEX (ticketFk)) ENGINE = MEMORY
SELECT id ticketFk FROM vn.ticket WHERE refFk=?`, [summary.invoiceOut.ref]);
stmts.push(stmt);
stmts.push('CALL ticketGetTotal()');
let ticketTotalsIndex = stmts.push('SELECT * FROM tmp.ticketTotal') - 1;
stmt = new ParameterizedSQL(`
SELECT iot.* , pgc.*, IF(pe.equFk IS NULL, taxableBase, 0) AS Base, pgc.rate / 100 as vatPercent
FROM vn.invoiceOutTax iot
JOIN vn.pgc ON pgc.code = iot.pgcFk
LEFT JOIN vn.pgcEqu pe ON pe.equFk = pgc.code
WHERE invoiceOutFk = ?`, [summary.invoiceOut.id]);
let invoiceOutTaxesIndex = stmts.push(stmt) - 1;
stmts.push(
`DROP TEMPORARY TABLE
tmp.ticket,
tmp.ticketTotal`);
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);
totalMap = {};
for (ticketTotal of result[ticketTotalsIndex])
totalMap[ticketTotal.ticketFk] = ticketTotal.total;
summary.invoiceOut.tickets().forEach(ticket => {
ticket.total = totalMap[ticket.id];
});
summary.invoiceOut.taxesBreakdown = result[invoiceOutTaxesIndex];
return summary;
};
};

View File

@ -1,4 +1,3 @@
module.exports = Self => {
require('../methods/invoice-in/filter')(Self);
require('../methods/invoice-in/summary')(Self);
};

View File

@ -1,5 +1,5 @@
<vn-portal slot="menu">
<vn-invoice-out-descriptor invoice-out="$ctrl.invoiceOut"></vn-invoice-out-descriptor>
<vn-invoice-in-descriptor invoice-in="$ctrl.invoiceIn"></vn-invoice-out-descriptor>
<vn-left-menu source="card"></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -32,7 +32,7 @@ class Controller extends ModuleCard {
}
}
ngModule.vnComponent('vnInvoiceOutCard', {
ngModule.vnComponent('vnInvoiceInCard', {
template: require('./index.html'),
controller: Controller
});

View File

@ -2,33 +2,14 @@ import ngModule from '../module';
import Descriptor from 'salix/components/descriptor';
class Controller extends Descriptor {
get invoiceOut() {
get invoiceIn() {
return this.entity;
}
set invoiceOut(value) {
set invoiceIn(value) {
this.entity = value;
}
deleteInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.id}/delete`)
.then(() => this.$state.go('invoiceOut.index'))
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut deleted')));
}
bookInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.invoiceOut.ref}/book`)
.then(() => this.$state.reload())
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked')));
}
get filter() {
if (this.invoiceOut)
return JSON.stringify({refFk: this.invoiceOut.ref});
return null;
}
loadData() {
const filter = {
include: [
@ -37,24 +18,19 @@ class Controller extends Descriptor {
scope: {
fields: ['id', 'code']
}
}, {
relation: 'client',
scope: {
fields: ['id', 'name']
}
}
]
};
return this.getData(`InvoiceOuts/${this.id}`, {filter})
return this.getData(`InvoiceIns/${this.id}`, {filter})
.then(res => this.entity = res.data);
}
}
ngModule.vnComponent('vnInvoiceOutDescriptor', {
ngModule.vnComponent('vnInvoiceInDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
invoiceOut: '<'
invoiceIn: '<'
}
});

View File

@ -3,7 +3,6 @@ export * from './module';
import './main';
import './index/';
import './search-panel';
import './summary';
import './card';
import './descriptor';
import './descriptor-popover';

View File

@ -2,16 +2,6 @@ import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
/* preview(invoiceOut) {
this.selectedInvoiceOut = invoiceOut;
this.$.summary.show();
}
openPdf(id) {
let url = `api/InvoiceOuts/${id}/download?access_token=${this.vnToken.token}`;
window.open(url, '_blank');
} */
exprBuilder(param, value) {
switch (param) {
case 'shipped':

View File

@ -1,90 +0,0 @@
<vn-card class="summary">
<h5>
<a ng-if="::$ctrl.summary.invoiceOut.id"
vn-tooltip="Go to the Invoice Out"
ui-sref="invoiceOut.card.summary({id: {{::$ctrl.summary.invoiceOut.id}}})"
name="goToSummary">
<vn-icon-button icon="launch"></vn-icon-button>
</a>
<span>{{$ctrl.summary.invoiceOut.ref}} - {{$ctrl.summary.invoiceOut.client.socialName}}</span>
</h5>
<vn-horizontal>
<vn-one>
<vn-label-value label="Date"
value="{{$ctrl.summary.invoiceOut.issued | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Due"
value="{{$ctrl.summary.invoiceOut.dued | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Created"
value="{{$ctrl.summary.invoiceOut.created | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Booked"
value="{{$ctrl.summary.invoiceOut.booked | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Company"
value="{{$ctrl.summary.invoiceOut.company.code | dashIfEmpty}}">
</vn-label-value>
</vn-one>
<vn-two>
<h4 translate>Tax breakdown</h4>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th>Type</vn-th>
<vn-th>Taxable base</vn-th>
<vn-th>Rate</vn-th>
<vn-th>Fee</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="tax in $ctrl.summary.invoiceOut.taxesBreakdown">
<vn-td>{{tax.name}}</vn-td>
<vn-td>{{tax.taxableBase | currency: 'EUR': 2}}</vn-td>
<vn-td>{{tax.rate}}%</vn-td>
<vn-td>{{tax.vat | currency: 'EUR': 2}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-two>
<vn-auto>
<h4 translate>Ticket</h4>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th number>Ticket id</vn-th>
<vn-th>Alias</vn-th>
<vn-th expand>Shipped</vn-th>
<vn-th number>Amount</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="ticket in $ctrl.summary.invoiceOut.tickets">
<vn-td number>
<span
ng-click="ticketDescriptor.show($event, ticket.id)"
class="link">
{{ticket.id}}
</span>
</vn-td>
<vn-td>
<span
ng-click="clientDescriptor.show($event, ticket.clientFk)"
class="link">
{{ticket.nickname}}
</span>
</vn-td>
<vn-td expand>{{ticket.shipped | date: 'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
<vn-td number>{{ticket.total | currency: 'EUR': 2}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-auto>
</vn-horizontal>
</vn-card>
<vn-ticket-descriptor-popover
vn-id="ticketDescriptor">
</vn-ticket-descriptor-popover>
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>

View File

@ -1,28 +0,0 @@
import ngModule from '../module';
import Summary from 'salix/components/summary';
import './style.scss';
class Controller extends Summary {
set invoiceOut(value) {
this._invoiceOut = value;
if (value && value.id)
this.getSummary();
}
get invoiceOut() {
return this._invoiceOut;
}
getSummary() {
return this.$http.get(`InvoiceOuts/${this.invoiceOut.id}/summary`)
.then(res => this.summary = res.data);
}
}
ngModule.vnComponent('vnInvoiceOutSummary', {
template: require('./index.html'),
controller: Controller,
bindings: {
invoiceOut: '<'
}
});

View File

@ -1,29 +0,0 @@
import './index.js';
describe('InvoiceOut', () => {
describe('Component summary', () => {
let controller;
let $httpBackend;
let $scope;
beforeEach(ngModule('invoiceOut'));
beforeEach(inject(($componentController, _$httpBackend_, $rootScope) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
const $element = angular.element('<vn-invoice-out-summary></vn-invoice-out-summary>');
controller = $componentController('vnInvoiceOutSummary', {$element, $scope});
controller.invoiceOut = {id: 1};
}));
describe('getSummary()', () => {
it('should perform a query to set summary', () => {
$httpBackend.when('GET', `InvoiceOuts/1/summary`).respond(200, 'the data you are looking for');
controller.getSummary();
$httpBackend.flush();
expect(controller.summary).toEqual('the data you are looking for');
});
});
});
});

View File

@ -1,6 +0,0 @@
@import "variables";
vn-invoice-out-summary .summary {
max-width: $width-lg;
}