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", "url": "/risk",
"state": "client.card.invoice", "abstract": true,
"component": "vn-client-invoice", "state": "client.card.risk",
"description": "Invoices", "component": "ui-view"
},
{
"url": "/index",
"state": "client.card.risk.index",
"component": "vn-client-risk-index",
"description": "Risk",
"params": {
"client": "$ctrl.client"
},
"menu": { "menu": {
"icon": "icon-invoices" "icon": "icon-invoices"
} }
}, },
{
"url": "/create",
"state": "client.card.risk.create",
"component": "vn-client-risk-create",
"description": "New payment",
"params": {
"client": "$ctrl.client"
}
},
{ {
"url": "/recovery", "url": "/recovery",
"abstract": true, "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-watcher
vn-id="watcher" vn-id="watcher"
data="$ctrl.client" data="$ctrl.client"
@ -34,7 +34,8 @@
show-field="firstName" show-field="firstName"
value-field="id" value-field="id"
label="Salesperson" label="Salesperson"
vn-acl="salesAssistant"> vn-acl="salesAssistant, employee"
acl-conditional-to-employee="{{!$ctrl.client.isTaxDataChecked}}">
<tpl-item>{{firstName}} {{name}}</tpl-item> <tpl-item>{{firstName}} {{name}}</tpl-item>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete vn-one <vn-autocomplete vn-one

View File

@ -19,8 +19,9 @@ import './credit/index';
import './credit/create'; import './credit/create';
import './greuge/index'; import './greuge/index';
import './greuge/create'; import './greuge/create';
import './risk/index';
import './risk/create';
import './mandate'; import './mandate';
import './invoice';
import './summary'; import './summary';
import './recovery/index'; import './recovery/index';
import './recovery/create'; 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}}"> value="{{$ctrl.summary.creditInsurance | currency:'€ ':2}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Balance" <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>
<vn-label-value label="Balance due" <vn-label-value label="Balance due"
value="{{$ctrl.summary.defaulters[0].amount | currency:'€ ':2}}" value="{{$ctrl.summary.defaulters[0].amount | currency:'€ ':2}}"

View File

@ -16,9 +16,19 @@ class Controller {
if (res.data.classifications.length) if (res.data.classifications.length)
this.grade = res.data.classifications[0].insurances[0].grade; 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']; Controller.$inject = ['$http'];

View File

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

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": { "mysql": {
"table": "receipt" "table": "receipt"
} }
}, },
"properties": { "properties": {
"id": { "id": {
"id": true, "id": true,
"type": "Number", "type": "Number",
"description": "Identifier" "description": "Identifier"
}, },
"ref": { "amountPaid": {
"id": true, "type": "Number"
"type": "String", },
"required": true "amountUnpaid": {
}, "type": "Number"
"amountPaid": { },
"type": "Number" "payed": {
}, "type": "date"
"amountUnpaid": { },
"type": "Number" "created": {
}, "type": "date"
"payed": { },
"type": "date" "isConciliate": {
}, "type": "date"
"worcreated": { }
"type": "date" },
}, "relations": {
"isConciliate": { "client": {
"type": "date" "type": "belongsTo",
} "model": "Client",
}, "foreignKey": "clientFk"
"relations": { },
"client": { "company": {
"type": "belongsTo", "type": "belongsTo",
"model": "Client", "model": "Company",
"foreignKey": "clientFk" "foreignKey": "companyFk"
}, },
"company": { "worker": {
"type": "belongsTo", "type": "belongsTo",
"model": "Company", "model": "Worker",
"foreignKey": "companyFk" "foreignKey": "workerFk"
}, },
"worker": { "bank": {
"type": "belongsTo", "type": "belongsTo",
"model": "Worker", "model": "Bank",
"foreignKey": "workerFk" "foreignKey": "bankFk"
}, },
"bank": { "invoice": {
"type": "belongsTo", "type": "belongsTo",
"model": "Bank", "model": "InvoiceOut",
"foreignKey": "bankFk" "foreignKey": "invoiceFk"
}, }
"invoice": { }
"type": "belongsTo",
"model": "InvoiceOut",
"foreignKey": "invoiceFk"
}
}
} }

View File

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

View File

@ -3,18 +3,33 @@ const app = require(`${servicesDir}/client/server/server`);
describe('Client updateBasicData', () => { describe('Client updateBasicData', () => {
afterAll(async() => { afterAll(async() => {
let id = 101; let id = 101;
let ctx = {req: {accessToken: {userId: 1}}};
let validparams = {email: 'BruceWayne@verdnatura.es'}; 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() => { it('should return an error if the params aint valid', async() => {
let error; let error;
let ctx = {req: {accessToken: {userId: 1}}};
let id = 101; let id = 101;
let invalidparams = {invalid: 'param for update'}; 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 => { .catch(e => {
error = e; error = e;
}); });
@ -29,8 +44,8 @@ describe('Client updateBasicData', () => {
expect(client.email).toEqual('BruceWayne@verdnatura.es'); expect(client.email).toEqual('BruceWayne@verdnatura.es');
let validparams = {email: 'myNewEmail@myDomain.es'}; let validparams = {email: 'myNewEmail@myDomain.es'};
let ctx = {req: {accessToken: {userId: 1}}};
let result = await app.models.Client.updateBasicData(validparams, id); let result = await app.models.Client.updateBasicData(ctx, validparams, id);
expect(result.email).toEqual('myNewEmail@myDomain.es'); expect(result.email).toEqual('myNewEmail@myDomain.es');
}); });

View File

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

View File

@ -1,7 +1,7 @@
let UserError = require('../../helpers').UserError; let UserError = require('../../helpers').UserError;
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('updateBasicData', { Self.remoteMethodCtx('updateBasicData', {
description: 'Updates billing data of a client', description: 'Updates billing data of a client',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ 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 = [ let validUpdateParams = [
'contact', 'contact',
'name', 'name',
@ -39,11 +41,14 @@ module.exports = Self => {
'contactChannelFk' '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) { 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`); 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); return await client.updateAttributes(params);
}; };
}; };

View File

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