Merge branch 'dev' of https://git.verdnatura.es/salix into dev

This commit is contained in:
Juan 2018-10-19 19:54:50 +02:00
commit 181272086e
21 changed files with 340 additions and 154 deletions

View File

@ -205,14 +205,32 @@
}
},
{
"url": "/invoice",
"state": "client.card.invoice",
"component": "vn-client-invoice",
"description": "Invoices",
"url": "/risk",
"abstract": true,
"state": "client.card.risk",
"component": "ui-view"
},
{
"url": "/index",
"state": "client.card.risk.index",
"component": "vn-client-risk-index",
"description": "Risk",
"params": {
"client": "$ctrl.client"
},
"menu": {
"icon": "icon-invoices"
}
},
{
"url": "/create",
"state": "client.card.risk.create",
"component": "vn-client-risk-create",
"description": "New payment",
"params": {
"client": "$ctrl.client"
}
},
{
"url": "/recovery",
"abstract": true,

View File

@ -1,4 +1,4 @@
<mg-ajax path="/client/api/Clients/{{patch.params.id}}" options="vnPatch"></mg-ajax>
<mg-ajax path="/client/api/Clients/{{patch.params.id}}/updateBasicData" options="vnPatch"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.client"
@ -34,7 +34,8 @@
show-field="firstName"
value-field="id"
label="Salesperson"
vn-acl="salesAssistant">
vn-acl="salesAssistant, employee"
acl-conditional-to-employee="{{!$ctrl.client.isTaxDataChecked}}">
<tpl-item>{{firstName}} {{name}}</tpl-item>
</vn-autocomplete>
<vn-autocomplete vn-one

View File

@ -19,8 +19,9 @@ import './credit/index';
import './credit/create';
import './greuge/index';
import './greuge/create';
import './risk/index';
import './risk/create';
import './mandate';
import './invoice';
import './summary';
import './recovery/index';
import './recovery/create';

View File

@ -1,42 +0,0 @@
<vn-crud-model
vn-id="model"
url="/client/api/InvoiceOuts"
filter="{}"
link="{clientFk: $ctrl.$stateParams.id}"
limit="20"
data="invoices">
</vn-crud-model>
<vn-vertical>
<vn-card pad-large>
<vn-vertical>
<vn-title>Invoices</vn-title>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="ref" default-order="DESC">Reference</vn-th>
<vn-th field="issued">Issue date</vn-th>
<vn-th field="dued">Due date</vn-th>
<vn-th field="amount">Amount</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="invoice in invoices">
<vn-td>{{::invoice.ref}}</vn-td>
<vn-td>{{::invoice.issued | date:'dd/MM/yyyy' }}</vn-td>
<vn-td>{{::invoice.dued | date:'dd/MM/yyyy' }}</vn-td>
<vn-td>{{::invoice.amount | currency:' €': 2}}</vn-td>
</vn-tr>
</vn-tbody>
<vn-empty-rows ng-if="model.data.length === 0" translate>
No results
</vn-empty-rows>
</vn-table>
</vn-vertical>
<vn-pagination
model="model"
scroll-selector="ui-view">
</vn-pagination>
</vn-card>
</vn-vertical>

View File

@ -1,14 +0,0 @@
import ngModule from '../module';
class Controller {
constructor($stateParams) {
this.$stateParams = $stateParams;
}
}
Controller.$inject = ['$stateParams'];
ngModule.component('vnClientInvoice', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,4 +0,0 @@
Reference: Referencia
Issue date: Fecha de emisión
Due date: Vencimiento
Amount: Total

View File

@ -0,0 +1,79 @@
<vn-crud-model
vn-id="model"
url="/client/api/receipts/filter"
params= "{
params: {
clientFk:$ctrl.$stateParams.id
}
}"
limit="20"
data="risks">
</vn-crud-model>
<vn-crud-model
vn-id="riskModel"
url="/client/api/ClientRisks"
filter="::$ctrl.filter"
data="riskTotal">
</vn-crud-model>
<vn-vertical>
<vn-card pad-large>
<vn-horizontal>
<vn-title vn-two>Risk</vn-title>
<div class="totalBox" ng-if="riskTotal.length">
<h6> Total por empresa</h6>
<vn-auto ng-repeat="riskByCompany in riskTotal">
<vn-label-value label={{riskByCompany.company.code}}
value="{{riskByCompany.amount | currency: ' €': 2}}">
</vn-label-value>
</vn-auto>
</div>
</vn-horizontal>
<vn-vertical>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th>Date</vn-th>
<vn-th>Creation date</vn-th>
<vn-th>Employee</vn-th>
<vn-th>Reference</vn-th>
<vn-th>Bank</vn-th>
<vn-th>Debit</vn-th>
<vn-th>Credit</vn-th>
<vn-th>Conciliated</vn-th>
<vn-th>Company</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="risk in risks">
<vn-td>{{::risk.payed | dateTime:'dd/MM/yyyy'}}</vn-td>
<vn-td>{{::risk.created | dateTime:'dd/MM/yyyy HH:mm'}}</vn-td>
<vn-td>{{::risk.firstName}} {{::risk.name}}</vn-td>
<vn-td>{{::risk.reference}}</vn-td>
<vn-td>{{::risk.bankFk}}</vn-td>
<vn-td>{{::risk.debit | currency: ' €': 2}}</vn-td>
<vn-td>{{::risk.credit | currency: ' €': 2}}</vn-td>
<vn-td>
<vn-check
field="risk.isConciliate"
disabled="true">
</vn-check>
</vn-td>
<vn-td>{{::risk.company}}</vn-td>
</vn-tr>
</vn-tbody>
<vn-empty-rows ng-if="model.data.length === 0" translate>
No results
</vn-empty-rows>
</vn-table>
</vn-vertical>
<vn-pagination
model="model"
scroll-selector="ui-view">
</vn-pagination>
</vn-card>
</vn-vertical>
<a ui-sref="client.card.risk.create" vn-tooltip="New payment"
vn-bind="+" fixed-bottom-right>
<vn-float-button vn-acl="administrative" vn-acl-action="remove" icon="add"></vn-float-button>
</a>

View File

@ -0,0 +1,25 @@
import ngModule from '../../module';
class Controller {
constructor($stateParams) {
this.$stateParams = $stateParams;
this.filter = {
include: {
relation: "company",
scope: {
fields: ["code"]
}
},
where: {
clientFk: $stateParams.id
}};
}
}
Controller.$inject = ['$stateParams'];
ngModule.component('vnClientRiskIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,6 @@
Creation date: Fecha de creación
Reference: Referencia
Bank: Caja
Debit: Debe
Conciliated: Conciliado
New payment: Añadir pago

View File

@ -190,7 +190,7 @@
value="{{$ctrl.summary.creditInsurance | currency:'€ ':2}}">
</vn-label-value>
<vn-label-value label="Balance"
value="{{$ctrl.summary.clientRisks[0].amount | currency:'€ ':2}}">
value="{{$ctrl.summary.sumRisk | currency:'€ ':2}}">
</vn-label-value>
<vn-label-value label="Balance due"
value="{{$ctrl.summary.defaulters[0].amount | currency:'€ ':2}}"

View File

@ -16,9 +16,19 @@ class Controller {
if (res.data.classifications.length)
this.grade = res.data.classifications[0].insurances[0].grade;
this.summary.sumRisk = this.sumRisk();
}
});
}
sumRisk() {
let total = 0;
this.summary.clientRisks.forEach(risk => {
total += risk.amount;
});
return total;
}
}
Controller.$inject = ['$http'];

View File

@ -21,32 +21,34 @@ describe('Client', () => {
describe('$onChanges()', () => {
it('should perform a GET query and define summary property', () => {
let res = {name: 'Superman', classifications: []};
spyOn(controller, "sumRisk");
$httpBackend.when('GET', `/client/api/Clients/101/summary`).respond(200, res);
$httpBackend.expect('GET', `/client/api/Clients/101/summary`);
controller.$onChanges();
$httpBackend.flush();
expect(controller.summary).toBeDefined();
expect(controller.summary.name).toEqual('Superman');
expect(controller.grade).toBeUndefined();
});
});
it('should perform a GET query and define summary and grade property', () => {
let res = {
name: 'Superman',
classifications: [{insurances: [
{id: 1, grade: 1}
]}]
};
$httpBackend.when('GET', `/client/api/Clients/101/summary`).respond(200, res);
$httpBackend.expect('GET', `/client/api/Clients/101/summary`);
controller.$onChanges();
$httpBackend.flush();
describe('sumRisk()', () => {
it('should sum property amount of an array', () => {
controller.summary = {
clientRisks: [{
companyFk: 442,
amount: 100
},
{
companyFk: 567,
amount: 200
}]};
expect(controller.summary).toBeDefined();
expect(controller.summary.name).toEqual('Superman');
expect(controller.grade).toBeDefined();
expect(controller.grade).toEqual(1);
let result = controller.sumRisk();
expect(result).toEqual(300);
});
});
});

View File

@ -0,0 +1,74 @@
const ParameterizedSQL = require('vn-loopback/node_modules/loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('filter', {
description: 'Find all instances of the model matched by filter from the data source.',
accessType: 'READ',
accepts: [
{
arg: 'filter',
type: 'Object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
}, {
arg: 'params',
type: 'Object',
description: 'clientFk',
http: {source: 'query'}
}
],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/filter`,
verb: 'GET'
}
});
Self.filter = async (filter, params) => {
let stmt = new ParameterizedSQL(
`SELECT
r.id,
r.isConciliate,
r.payed,
c.code AS company,
r.created,
'' description,
0 AS debit,
r.amountPaid AS credit,
r.bankFk,
firstName,
name,
r.clientFk
FROM vn.receipt r
JOIN vn.worker w ON w.id = r.workerFk
JOIN vn.company c ON c.id = r.companyFk
WHERE clientFk = ?
UNION ALL
SELECT
i.id,
TRUE,
i.dued,
c.code AS company,
i.created,
CONCAT(' N/FRA ', i.ref) description,
i.amount AS debit,
0 credit,
NULL bank,
NULL firstName,
NULL name,
i.clientFk
FROM vn.invoiceOut i
JOIN vn.company c ON c.id = i.companyFk
WHERE clientFk = ?
ORDER BY payed DESC, created DESC`, [
params.clientFk,
params.clientFk
]);
stmt.merge(Self.buildPagination(filter));
return await Self.rawStmt(stmt);
};
};

View File

@ -0,0 +1,13 @@
const app = require(`${servicesDir}/client/server/server`);
describe('receipt filter()', () => {
it('should call the filter method', async() => {
let filter = {limit: 20};
let params = {
clientFk: 101
};
let result = await app.models.Receipt.filter(filter, params);
expect(result.length).toBeGreaterThan(0);
});
});

View File

@ -0,0 +1,3 @@
module.exports = function(Self) {
require('../methods/receipt/filter')(Self);
};

View File

@ -5,59 +5,54 @@
"mysql": {
"table": "receipt"
}
},
"properties": {
"id": {
"id": true,
"type": "Number",
"description": "Identifier"
},
"ref": {
"id": true,
"type": "String",
"required": true
},
"amountPaid": {
"type": "Number"
},
"amountUnpaid": {
"type": "Number"
},
"payed": {
"type": "date"
},
"worcreated": {
"type": "date"
},
"isConciliate": {
"type": "date"
}
},
"relations": {
"client": {
"type": "belongsTo",
"model": "Client",
"foreignKey": "clientFk"
},
"company": {
"type": "belongsTo",
"model": "Company",
"foreignKey": "companyFk"
},
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
},
"bank": {
"type": "belongsTo",
"model": "Bank",
"foreignKey": "bankFk"
},
"invoice": {
"type": "belongsTo",
"model": "InvoiceOut",
"foreignKey": "invoiceFk"
}
}
},
"properties": {
"id": {
"id": true,
"type": "Number",
"description": "Identifier"
},
"amountPaid": {
"type": "Number"
},
"amountUnpaid": {
"type": "Number"
},
"payed": {
"type": "date"
},
"created": {
"type": "date"
},
"isConciliate": {
"type": "date"
}
},
"relations": {
"client": {
"type": "belongsTo",
"model": "Client",
"foreignKey": "clientFk"
},
"company": {
"type": "belongsTo",
"model": "Company",
"foreignKey": "companyFk"
},
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
},
"bank": {
"type": "belongsTo",
"model": "Bank",
"foreignKey": "bankFk"
},
"invoice": {
"type": "belongsTo",
"model": "InvoiceOut",
"foreignKey": "invoiceFk"
}
}
}

View File

@ -8,7 +8,8 @@ VALUES
(109, 'UserConfig', '*', '*', 'ALLOW', 'ROLE', 'employee'),
(110, 'Bank', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
(111, 'ClientLog', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
(112, 'Defaulter', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
(112, 'Defaulter', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
(113, 'ClientRisk', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
UPDATE salix.ACL
SET model='ItemTag', property='*', accessType='WRITE', permission='ALLOW', principalType='ROLE', principalId='marketingBoss'
WHERE id=52;

View File

@ -3,18 +3,33 @@ const app = require(`${servicesDir}/client/server/server`);
describe('Client updateBasicData', () => {
afterAll(async() => {
let id = 101;
let ctx = {req: {accessToken: {userId: 1}}};
let validparams = {email: 'BruceWayne@verdnatura.es'};
await app.models.Client.updateBasicData(validparams, id);
await app.models.Client.updateBasicData(ctx, validparams, id);
});
it('should return an error if the params aint valid', async() => {
let error;
let ctx = {req: {accessToken: {userId: 1}}};
let id = 101;
let invalidparams = {invalid: 'param for update'};
await app.models.Client.updateBasicData(invalidparams, id)
await app.models.Client.updateBasicData(ctx, invalidparams, id)
.catch(e => {
error = e;
});
expect(error.toString()).toContain(`You don't have enough privileges to do that`);
});
it('should return an error if the client has isTaxDataChecked and employee try to change his salesPerson', async() => {
let error;
let ctx = {req: {accessToken: {userId: 1}}};
let id = 101;
let params = {salesPerson: 3};
await app.models.Client.updateBasicData(ctx, params, id)
.catch(e => {
error = e;
});
@ -29,8 +44,8 @@ describe('Client updateBasicData', () => {
expect(client.email).toEqual('BruceWayne@verdnatura.es');
let validparams = {email: 'myNewEmail@myDomain.es'};
let result = await app.models.Client.updateBasicData(validparams, id);
let ctx = {req: {accessToken: {userId: 1}}};
let result = await app.models.Client.updateBasicData(ctx, validparams, id);
expect(result.email).toEqual('myNewEmail@myDomain.es');
});

View File

@ -70,11 +70,9 @@ module.exports = Self => {
}
},
{
relation: 'clientsRisk',
relation: 'clientRisks',
scope: {
where: {companyFk: 442},
fields: ['amount'],
limit: 1
fields: ['amount', 'companyFk']
}
},
{

View File

@ -1,7 +1,7 @@
let UserError = require('../../helpers').UserError;
module.exports = Self => {
Self.remoteMethod('updateBasicData', {
Self.remoteMethodCtx('updateBasicData', {
description: 'Updates billing data of a client',
accessType: 'WRITE',
accepts: [{
@ -28,7 +28,9 @@ module.exports = Self => {
}
});
Self.updateBasicData = async(params, id) => {
Self.updateBasicData = async(ctx, params, id) => {
let userId = ctx.req.accessToken.userId;
let validUpdateParams = [
'contact',
'name',
@ -39,11 +41,14 @@ module.exports = Self => {
'contactChannelFk'
];
let isSalesAssistant = await Self.app.models.Account.hasRole(userId, 'salesAssistant');
let client = await Self.app.models.Client.findById(id);
for (const key in params) {
if (validUpdateParams.indexOf(key) === -1)
if (validUpdateParams.indexOf(key) === -1 || key == 'salesPersonFk' && client.isTaxDataChecked && !isSalesAssistant)
throw new UserError(`You don't have enough privileges to do that`);
}
let client = await Self.app.models.Client.findById(id);
return await client.updateAttributes(params);
};
};

View File

@ -183,7 +183,7 @@
"model": "Defaulter",
"foreignKey": "clientFk"
},
"clientsRisk": {
"clientRisks": {
"type": "hasMany",
"model": "ClientRisk",
"foreignKey": "clientFk"