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

This commit is contained in:
Gerard 2018-04-23 11:17:38 +02:00
commit d8dc1f742c
20 changed files with 354 additions and 42 deletions

View File

@ -41,5 +41,11 @@
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>
<vn-submit label="Save"></vn-submit> <vn-submit label="Save"></vn-submit>
<button
class="mdl-button mdl-button--raised mdl-button--colored"
translate
ui-sref="clientCard.addresses.list"
>Cancel
</button>
</vn-button-bar> </vn-button-bar>
</form> </form>

View File

@ -60,6 +60,12 @@
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>
<vn-submit label="Create"></vn-submit> <vn-submit label="Create"></vn-submit>
<button
class="mdl-button mdl-button--raised mdl-button--colored"
translate
ui-sref="clients"
>Cancel
</button>
</vn-button-bar> </vn-button-bar>
</div> </div>
</form> </form>

View File

@ -0,0 +1,32 @@
import './credit-insurance-list';
describe('Client', () => {
describe('Component vnClientCreditInsuranceList', () => {
let $componentController;
let controller;
let $httpBackend;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => {
$componentController = _$componentController_;
let $state = {params: {classificationId: 1}};
$httpBackend = _$httpBackend_;
controller = $componentController('vnClientCreditInsuranceList', {$state: $state});
}));
it('should perform a query to GET credit the credit classification', () => {
let res = [{finished: 'some value'}];
let query = '/client/api/CreditClassifications?filter=%7B%22fields%22%3A%5B%22finished%22%5D%2C%22where%22%3A%7B%22id%22%3A1%7D%7D';
$httpBackend.whenGET(query).respond(res);
$httpBackend.expectGET(query);
controller.$onInit();
$httpBackend.flush();
expect(controller.isClosed).toBe(true);
});
});
});

View File

@ -44,6 +44,12 @@
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>
<vn-submit label="Create"></vn-submit> <vn-submit label="Create"></vn-submit>
<button
class="mdl-button mdl-button--raised mdl-button--colored"
translate
ui-sref="item.index"
>Cancel
</button>
</vn-button-bar> </vn-button-bar>
</div> </div>
</form> </form>

View File

@ -29,7 +29,8 @@ export default {
email: `${components.vnTextfield}[name="email"]`, email: `${components.vnTextfield}[name="email"]`,
salesPersonInput: `vn-autocomplete[field="$ctrl.client.salesPersonFk"] input`, salesPersonInput: `vn-autocomplete[field="$ctrl.client.salesPersonFk"] input`,
salesBruceBannerOption: `vn-autocomplete[field="$ctrl.client.salesPersonFk"] vn-drop-down ul > li:nth-child(1)`, salesBruceBannerOption: `vn-autocomplete[field="$ctrl.client.salesPersonFk"] vn-drop-down ul > li:nth-child(1)`,
createButton: `${components.vnSubmit}` createButton: `${components.vnSubmit}`,
cancelButton: `button[href="#!/clients"]`
}, },
clientBasicData: { clientBasicData: {
basicDataButton: `${components.vnMenuItem}[ui-sref="clientCard.basicData"]`, basicDataButton: `${components.vnMenuItem}[ui-sref="clientCard.basicData"]`,
@ -110,7 +111,8 @@ export default {
thirdObservationTypeSelectOptionThree: `${components.vnAutocomplete}[field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(3)`, thirdObservationTypeSelectOptionThree: `${components.vnAutocomplete}[field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(3)`,
thirdObservationDescriptionInput: `vn-horizontal:nth-child(5) > vn-textfield[label="Description"] > div > input`, thirdObservationDescriptionInput: `vn-horizontal:nth-child(5) > vn-textfield[label="Description"] > div > input`,
addObservationButton: `${components.vnIcon}[icon="add_circle"]`, addObservationButton: `${components.vnIcon}[icon="add_circle"]`,
saveButton: `${components.vnSubmit}` saveButton: `${components.vnSubmit}`,
cancelButton: `button[ui-sref="clientCard.addresses.list"]`
}, },
clientWebAccess: { clientWebAccess: {
webAccessButton: `${components.vnMenuItem}[ui-sref="clientCard.webAccess"]`, webAccessButton: `${components.vnMenuItem}[ui-sref="clientCard.webAccess"]`,
@ -168,7 +170,8 @@ export default {
intrastatSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(2)`, intrastatSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(2)`,
originSelect: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] input`, originSelect: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] input`,
originSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`, originSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`,
createButton: `${components.vnSubmit}` createButton: `${components.vnSubmit}`,
cancelButton: `button[ui-sref="item.index"]`
}, },
itemBasicData: { itemBasicData: {

View File

@ -42,6 +42,26 @@ describe('Client', () => {
}); });
}); });
it('should return to the client index by clicking the cancel button', () => {
return nightmare
.click(selectors.createClientView.cancelButton)
.wait(selectors.clientsIndex.createClientButton)
.parsedUrl()
.then(url => {
expect(url.hash).toEqual('#!/clients');
});
});
it('should now access to the create client view by clicking the create-client floating button', () => {
return nightmare
.click(selectors.clientsIndex.createClientButton)
.wait(selectors.createClientView.createButton)
.parsedUrl()
.then(url => {
expect(url.hash).toEqual('#!/create');
});
});
it('should receive an error when clicking the create button having all the form fields empty', () => { it('should receive an error when clicking the create button having all the form fields empty', () => {
return nightmare return nightmare
.click(selectors.createClientView.createButton) .click(selectors.createClientView.createButton)

View File

@ -56,6 +56,26 @@ describe('Client', () => {
}); });
}); });
it(`should return to the addreses section by clicking the cancel button`, () => {
return nightmare
.waitToClick(selectors.clientAddresses.cancelButton)
.waitForURL('addresses/list')
.url()
.then(url => {
expect(url).toContain('addresses/list');
});
});
it(`should now click on the add new address button to access to the new address form`, () => {
return nightmare
.waitToClick(selectors.clientAddresses.createAddress)
.waitForURL('addresses/create')
.url()
.then(url => {
expect(url).toContain('addresses/create');
});
});
it('should receive an error after clicking save button as consignee, street and town fields are empty', () => { it('should receive an error after clicking save button as consignee, street and town fields are empty', () => {
return nightmare return nightmare
.waitToClick(selectors.clientAddresses.defaultCheckboxInput) .waitToClick(selectors.clientAddresses.defaultCheckboxInput)

View File

@ -41,6 +41,26 @@ describe('Item', () => {
}); });
}); });
it('should return to the item index by clickig the cancel button', () => {
return nightmare
.click(selectors.itemCreateView.cancelButton)
.wait(selectors.itemsIndex.createItemButton)
.parsedUrl()
.then(url => {
expect(url.hash).toEqual('#!/item/list');
});
});
it('should now access to the create item view by clicking the create floating button', () => {
return nightmare
.click(selectors.itemsIndex.createItemButton)
.wait(selectors.itemCreateView.createButton)
.parsedUrl()
.then(url => {
expect(url.hash).toEqual('#!/item/create');
});
});
it('should create the Infinity Gauntlet item', () => { it('should create the Infinity Gauntlet item', () => {
return nightmare return nightmare
.type(selectors.itemCreateView.name, 'Infinity Gauntlet') .type(selectors.itemCreateView.name, 'Infinity Gauntlet')

View File

@ -1,11 +1,17 @@
module.exports = function(Self) { module.exports = function(Self) {
Self.remoteMethod('createWithInsurance', { Self.remoteMethod('createWithInsurance', {
description: 'Creates both classification and one insurance', description: 'Creates both classification and one insurance',
accepts: { accepts: [{
arg: 'data', arg: 'data',
type: 'object', type: 'object',
http: {source: 'body'} http: {source: 'body'}
}, }, {
arg: 'context',
type: 'object',
http: function(ctx) {
return ctx;
}
}],
returns: { returns: {
root: true, root: true,
type: 'boolean' type: 'boolean'
@ -16,21 +22,23 @@ module.exports = function(Self) {
} }
}); });
Self.createWithInsurance = async data => { Self.createWithInsurance = async (data, ctx) => {
let transaction = await Self.beginTransaction({}); let transaction = await Self.beginTransaction({});
try { try {
let classificationSchema = {client: data.clientFk, started: data.started}; let classificationSchema = {client: data.clientFk, started: data.started};
let newClassification = await Self.create(classificationSchema, {transaction}); let newClassification = await Self.create(classificationSchema, {transaction});
let CreditInsurance = Self.app.models.CreditInsurance;
let insuranceSchema = { let insuranceSchema = {
creditClassification: newClassification.id, creditClassification: newClassification.id,
credit: data.credit, credit: data.credit,
grade: data.grade grade: data.grade
}; };
Self.app.models.CreditInsurance.create(insuranceSchema, {transaction}); let newCreditInsurance = await CreditInsurance.create(insuranceSchema, {transaction});
await transaction.commit(); await transaction.commit();
await CreditInsurance.messageSend(newCreditInsurance, ctx.req.accessToken);
return newClassification; return newClassification;
} catch (e) { } catch (e) {
transaction.rollback(); transaction.rollback();

View File

@ -28,7 +28,7 @@
} }
}, },
"relations": { "relations": {
"client": { "customer": {
"type": "belongsTo", "type": "belongsTo",
"model": "Client", "model": "Client",
"foreignKey": "client" "foreignKey": "client"

View File

@ -19,4 +19,51 @@ module.exports = function(Self) {
message: 'The grade must be an integer greater than or equal to zero', message: 'The grade must be an integer greater than or equal to zero',
allowNull: true allowNull: true
}); });
Self.messageSend = async function(data, accessToken) {
let filter = {
include: {
relation: 'classification',
scope: {
fields: ['client'],
include: {
relation: 'customer',
scope: {
fields: ['name', 'salesPersonFk'],
include: {
relation: 'salesPerson',
scope: {
fields: 'userFk',
include: {
relation: 'user',
scope: {
fields: ['name']
}
}
}
}
}
}
}
}
};
let ctx = {req: {accessToken: accessToken}};
let insurance = await Self.findById(data.id, filter);
let customer = insurance.classification().customer();
let salesPerson = customer.salesPerson().user().name;
let grade = data.grade ? `(Grado ${data.grade})` : '(Sin grado)';
let message = {
message: `He cambiado el crédito asegurado del cliente "${customer.name}" a ${data.credit}${grade}`
};
Self.app.models.Message.send(salesPerson, message, ctx);
};
// Update from transaction misses ctx accessToken.
// Fixed passing accessToken from method messageSend()
Self.observe('after save', async function(ctx) {
if (ctx.options.accessToken)
await Self.messageSend(ctx.instance, ctx.options.accessToken);
});
}; };

View File

@ -0,0 +1,40 @@
module.exports = Self => {
Self.remoteMethod('send', {
description: 'Send message to user',
accessType: 'WRITE',
accepts: [{
arg: 'recipient',
type: 'string',
required: true,
description: 'The user/alias name',
http: {source: 'path'}
}, {
arg: 'data',
type: 'object',
required: true,
description: 'Message data',
http: {source: 'body'}
}, {
arg: 'context',
type: 'object',
http: function(ctx) {
return ctx;
}
}],
returns: {
type: 'boolean',
root: true
},
http: {
path: `/:recipient/send`,
verb: 'post'
}
});
Self.send = async (recipient, data, ctx) => {
let query = `SELECT vn.messageSendWithUser(?, ?, ?) AS sent`;
let [result] = await Self.rawSql(query, [ctx.req.accessToken.userId, recipient, data.message]);
return result;
};
};

View File

@ -0,0 +1,12 @@
const app = require(`${servicesDir}/client/server/server`);
describe('message send()', () => {
it('should call the send method and return the response', done => {
let ctx = {req: {accessToken: {userId: 1}}};
app.models.Message.send('salesPerson', {message: 'I changed something'}, ctx)
.then(response => {
expect(response.sent).toEqual(1);
done();
});
});
});

View File

@ -10,36 +10,36 @@ module.exports = Self => {
limit: params.size, limit: params.size,
order: params.order || 'concept ASC', order: params.order || 'concept ASC',
include: [{ include: [{
relation: "item", relation: 'item',
scope: { scope: {
include: { include: {
relation: "itemTag", relation: 'itemTag',
scope: { scope: {
fields: ["tagFk", "value"], fields: ['tagFk', 'value'],
include: { include: {
relation: "tag", relation: 'tag',
scope: { scope: {
fields: ["name"] fields: ['name']
} }
}, },
limit: 6 limit: 6
} }
}, },
fields: ["itemFk", "name"] fields: ['itemFk', 'name']
} }
}, },
{ {
relation: "components", relation: 'components',
scope: { scope: {
fields: ["componentFk", "value"], fields: ['componentFk', 'value'],
include: { include: {
relation: "componentRate", relation: 'componentRate',
scope: { scope: {
fields: ["componentTypeRate", "name"], fields: ['componentTypeRate', 'name'],
include: { include: {
relation: "componentType", relation: 'componentType',
scope: { scope: {
fields: ["type"] fields: ['type']
} }
} }
} }

View File

@ -18,28 +18,28 @@ module.exports = function(Self) {
verb: 'post' verb: 'post'
} }
}); });
Model[methodName] = async crudStruct => { Model[methodName] = async crudObject => {
let promises = []; let promises = [];
let tx = await Model.beginTransaction({}); let transaction = await Model.beginTransaction({});
let options = {transaction: tx}; let options = {transaction: transaction};
try { try {
if (crudStruct.delete && crudStruct.delete.length) { if (crudObject.delete && crudObject.delete.length) {
promises.push(Model.destroyAll({id: {inq: crudStruct.delete}}, options)); promises.push(Model.destroyAll({id: {inq: crudObject.delete}}, options));
} }
if (crudStruct.create.length) { if (crudObject.create.length) {
promises.push(Model.create(crudStruct.create, options)); promises.push(Model.create(crudObject.create, options));
} }
if (crudStruct.update.length) { if (crudObject.update.length) {
crudStruct.update.forEach(toUpdate => { crudObject.update.forEach(toUpdate => {
promises.push(Model.upsert(toUpdate, options)); promises.push(Model.upsert(toUpdate, options));
}); });
} }
await Promise.all(promises); await Promise.all(promises);
await tx.commit(); await transaction.commit();
} catch (e) { } catch (error) {
await tx.rollback(); await transaction.rollback();
throw Array.isArray(e) ? e[0] : e; throw Array.isArray(error) ? error[0] : error;
} }
}; };
}; };

View File

@ -1,17 +1,60 @@
// const catchErrors = require('../../../../../../services/utils/jasmineHelpers').catchErrors;
const app = require('../../../../../item/server/server'); const app = require('../../../../../item/server/server');
describe('Model installCrudModel()', () => { describe('Model installCrudModel()', () => {
it('all models extends installCrudModel propertie', () => { it('should extend installCrudModel properties to any model passed', () => {
let someModel = app.models.Item; let exampleModel = app.models.ItemBarcode;
expect(someModel.installCrudModel).toBeDefined(); expect(exampleModel.installCrudModel).toBeDefined();
}); });
it('installCrudModel() create a new remothed method', () => { describe('installCrudModel()', () => {
let someModel = app.models.Item; it('should create a new remothed method', () => {
someModel.installCrudModel('someCrudMethod'); let exampleModel = app.models.ItemBarcode;
exampleModel.installCrudModel('crudItemBarcodes');
expect(someModel.someCrudMethod).toBeDefined(); expect(exampleModel.crudItemBarcodes).toBeDefined();
});
});
describe('ItemBarcode crudMethod()', () => {
let createdId;
it('should create a new barcode', async() => {
crudObject = {
create: [{code: '500', itemFk: '1'}],
update: [],
delete: []
};
await app.models.ItemBarcode.crudItemBarcodes(crudObject);
let result = await app.models.ItemBarcode.find({where: {itemFk: 1}});
createdId = result[3].id;
expect(result[3].code).toEqual('500');
expect(result.length).toEqual(4);
});
it('should update a barcode', async() => {
crudObject = {
create: [],
update: [{id: createdId, code: '501', itemFk: 1}],
delete: []
};
await app.models.ItemBarcode.crudItemBarcodes(crudObject);
let result = await app.models.ItemBarcode.find({where: {itemFk: 1}});
expect(result[3].code).toEqual('501');
expect(result.length).toEqual(4);
});
it('should delete a barcode', async() => {
crudObject = {
create: [],
update: [],
delete: [createdId]
};
await app.models.ItemBarcode.crudItemBarcodes(crudObject);
let result = await app.models.ItemBarcode.find({where: {itemFk: 1}});
expect(result.length).toEqual(3);
});
}); });
}); });

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/message/send.js')(Self);
}

View File

@ -0,0 +1,39 @@
{
"name": "Message",
"base": "VnModel",
"options": {
"mysql": {
"table": "message"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"sender": {
"type": "String",
"required": true
},
"recipient": {
"type": "String",
"required": true
},
"message": {
"type": "String"
}
},
"relations": {
"remitter": {
"type": "belongsTo",
"model": "User",
"foreignKey": "sender"
},
"receptor": {
"type": "belongsTo",
"model": "User",
"foreignKey": "recipient"
}
}
}

View File

@ -20,6 +20,10 @@
"name": { "name": {
"type": "string", "type": "string",
"required": true "required": true
},
"userFk": {
"type" : "Number",
"required": true
} }
}, },
"relations": { "relations": {

View File

@ -101,5 +101,8 @@
}, },
"Producer": { "Producer": {
"dataSource": "vn" "dataSource": "vn"
},
"Message": {
"dataSource": "vn"
} }
} }