Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2539-supplier_log

This commit is contained in:
Bernat Exposito 2020-10-29 13:40:21 +01:00
commit 792b689b58
69 changed files with 910 additions and 106 deletions

View File

@ -1,3 +1,8 @@
UPDATE `salix`.`ACL` SET `principalId` = 'deliveryBoss' WHERE (`id` = '194');
UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '97');
UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '100');
UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '103');
UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '202');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Town', '*', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Province', '*', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss');

View File

@ -0,0 +1,3 @@
UPDATE `vn`.`claimState` SET `roleFk` = '72' WHERE (`id` = '3');
UPDATE `vn`.`claimState` SET `roleFk` = '72' WHERE (`id` = '4');
UPDATE `vn`.`claimState` SET `roleFk` = '72' WHERE (`id` = '5');

File diff suppressed because one or more lines are too long

View File

@ -1213,6 +1213,11 @@ INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`
(1, 'Plants SL', 'Plants nick', 4100000001, 1, 'A11111111', 0, 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, NULL, NULL),
(2, 'Farmer King', 'The farmer', 4000020002, 1, 'B22222222', 1, 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 8),
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, 'C33333333', 0, 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, NULL, NULL);
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`retAccount`,`commission`, `created`, `postcodeFk`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`)
VALUES
(1, 'Plants SL', 'Plants nick', 4000000001, 1, '06089160W', 0, NULL, 0, CURDATE(), 1111, 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15),
(2, 'Flower King', 'The king', 4000000002, 1, 'B22222222', 0, NULL, 0, CURDATE(), 2222, 1, 'supplier address 2', 'LONDON', 2, 45671, 1, 2, 10),
(442, 'Verdnatura Levante SL', 'Verdnatura', 4000000442, 1, 'C33333333', 0, NULL, 0, CURDATE(), 3333, 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15);
INSERT INTO `cache`.`cache_calc`(`id`, `cache_id`, `cacheName`, `params`, `last_refresh`, `expires`, `created`, `connection_id`)
VALUES
@ -1534,9 +1539,9 @@ INSERT INTO `vn`.`claimState`(`id`, `code`, `description`, `roleFk`, `priority`)
VALUES
( 1, 'pending', 'Pendiente', 1, 1),
( 2, 'managed', 'Gestionado', 1, 5),
( 3, 'resolved', 'Resuelto', 21, 7),
( 4, 'canceled', 'Anulado', 1, 6),
( 5, 'disputed', 'Cuestionado', 21, 3),
( 3, 'resolved', 'Resuelto', 72, 7),
( 4, 'canceled', 'Anulado', 72, 6),
( 5, 'disputed', 'Cuestionado', 72, 3),
( 6, 'mana', 'Mana', 1, 4),
( 7, 'inProgress', 'En Curso', 1, 2);

View File

@ -901,5 +901,16 @@ export default {
newEntryTravel: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.travelFk"]',
newEntryCompany: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.companyFk"]',
saveNewEntry: 'vn-entry-create button[type="submit"]'
},
supplierSummary: {
header: 'vn-supplier-summary > vn-card > h5',
basicDataId: 'vn-supplier-summary vn-label-value[label="Id"]',
fiscalAddressTaxNumber: 'vn-supplier-summary vn-label-value[label="Tax number"]',
billingDataPayMethod: 'vn-supplier-summary vn-label-value[label="Pay method"]'
},
supplierDescriptor: {
alias: 'vn-supplier-descriptor vn-label-value[label="Alias"]',
clientButton: 'vn-supplier-descriptor vn-icon[icon="person"]',
entriesButton: 'vn-supplier-descriptor vn-icon[icon="icon-entry"]',
}
};

View File

@ -14,8 +14,8 @@ describe('Claim edit basic data path', () => {
await browser.close();
});
it(`should log in as salesAssistant then reach basic data of the target claim`, async() => {
await page.loginAndModule('salesAssistant', 'claim');
it(`should log in as claimManager then reach basic data of the target claim`, async() => {
await page.loginAndModule('claimManager', 'claim');
await page.accessToSearchResult('1');
await page.accessToSection('claim.card.basicData');
});
@ -30,7 +30,7 @@ describe('Claim edit basic data path', () => {
expect(message.type).toBe('success');
});
it(`should have been redirected to the next section of claims as the role is salesAssistant`, async() => {
it(`should have been redirected to the next section of claims as the role is claimManager`, async() => {
await page.waitForState('claim.card.detail');
});

View File

@ -8,7 +8,7 @@ describe('Claim development', () => {
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('salesAssistant', 'claim');
await page.loginAndModule('claimManager', 'claim');
await page.accessToSearchResult('1');
await page.accessToSection('claim.card.development');
});
@ -31,7 +31,7 @@ describe('Claim development', () => {
expect(message.type).toBe('success');
});
it(`should redirect to the next section of claims as the role is salesAssistant`, async() => {
it(`should redirect to the next section of claims as the role is claimManager`, async() => {
await page.waitForState('claim.card.action');
});

View File

@ -8,7 +8,7 @@ describe('Claim action path', () => {
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('administrative', 'claim');
await page.loginAndModule('claimManager', 'claim');
await page.accessToSearchResult('2');
await page.accessToSection('claim.card.action');
});

View File

@ -26,8 +26,8 @@ describe('claim Descriptor path', () => {
await page.waitForSelector(selectors.claimDescriptor.moreMenuDeleteClaim, {hidden: true});
});
it(`should log in as salesAssistant and navigate to the target claim`, async() => {
await page.loginAndModule('salesAssistant', 'claim');
it(`should log in as claimManager and navigate to the target claim`, async() => {
await page.loginAndModule('claimManager', 'claim');
await page.accessToSearchResult(claimId);
await page.waitForState('claim.card.summary');
});

View File

@ -0,0 +1,84 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Supplier descriptor path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('administrative', 'supplier');
await page.accessToSearchResult('1');
});
afterAll(async() => {
await browser.close();
});
// summary
it('should reach the second entry summary section', async() => {
await page.waitForState('supplier.card.summary');
});
it(`should confirm there's data on the summary header`, async() => {
const result = await page.waitToGetProperty(selectors.supplierSummary.header, 'innerText');
expect(result).toContain('Plants SL - 1');
});
it(`should confirm there's data on the summary basic data`, async() => {
const result = await page.waitToGetProperty(selectors.supplierSummary.basicDataId, 'innerText');
expect(result).toContain('Id 1');
});
it(`should confirm there's data on the summary fiscal address`, async() => {
const result = await page.waitToGetProperty(selectors.supplierSummary.fiscalAddressTaxNumber, 'innerText');
expect(result).toContain('Tax number 06089160W');
});
it(`should confirm there's data on the summary fiscal pay method`, async() => {
const result = await page.waitToGetProperty(selectors.supplierSummary.billingDataPayMethod, 'innerText');
expect(result).toContain('Pay method PayMethod one');
});
// descriptor
it(`should confirm there's data on the descriptor`, async() => {
const result = await page.waitToGetProperty(selectors.supplierDescriptor.alias, 'innerText');
expect(result).toContain('Plants nick');
});
it(`should navigate to the supplier's client summary using the icon client button`, async() => {
await page.waitToClick(selectors.supplierDescriptor.clientButton);
await page.waitForState('client.card.summary');
});
it(`should navigate back to the supplier`, async() => {
await page.waitToClick(selectors.globalItems.homeButton);
await page.waitForState('home');
await page.selectModule('supplier');
await page.accessToSearchResult('1');
await page.waitForState('supplier.card.summary');
});
it(`should navigate to the supplier's entries`, async() => {
await page.waitToClick(selectors.supplierDescriptor.entriesButton);
await page.waitForState('entry.index');
});
it(`should navigate back to suppliers but a different one this time`, async() => {
await page.waitToClick(selectors.globalItems.homeButton);
await page.waitForState('home');
await page.selectModule('supplier');
await page.accessToSearchResult('2');
await page.waitForState('supplier.card.summary');
});
it(`should check the client button isn't present since this supplier should not be a client`, async() => {
await page.waitForSelector(selectors.supplierDescriptor.clientButton, {hidden: true});
});
});

View File

@ -162,6 +162,9 @@ function e2eSingleRun() {
`${__dirname}/e2e/paths/08*/*[sS]pec.js`,
`${__dirname}/e2e/paths/09*/*[sS]pec.js`,
`${__dirname}/e2e/paths/10*/*[sS]pec.js`,
`${__dirname}/e2e/paths/11*/*[sS]pec.js`,
`${__dirname}/e2e/paths/12*/*[sS]pec.js`,
`${__dirname}/e2e/paths/13*/*[sS]pec.js`,
`${__dirname}/e2e/paths/**/*[sS]pec.js`
];

View File

@ -73,6 +73,7 @@
"I have deleted the ticket id": "I have deleted the ticket id [{{id}}]({{{url}}})",
"I have restored the ticket id": "I have restored the ticket id [{{id}}]({{{url}}})",
"Changed this data from the ticket": "I have changed the data from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"The grade must be similar to the last one": "The grade must be similar to the last one",
"agencyModeFk": "Agency",
"clientFk": "Client",
"zoneFk": "Zone",

View File

@ -2,13 +2,13 @@ const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('claimBeginning', () => {
const salesAssistantId = 21;
const claimManagerId = 72;
let ticket;
let refundTicketSales;
let salesInsertedInClaimEnd;
const activeCtx = {
accessToken: {userId: salesAssistantId},
accessToken: {userId: claimManagerId},
};
const ctx = {req: activeCtx};

View File

@ -1,12 +1,12 @@
module.exports = Self => {
Self.remoteMethod('getSummary', {
description: 'Updates the item taxes',
description: 'Return the claim summary',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The item id',
description: 'The claim id',
http: {source: 'path'}
}],
returns: {

View File

@ -21,10 +21,8 @@ module.exports = Self => {
Self.isEditable = async(ctx, id) => {
const userId = ctx.req.accessToken.userId;
const isSalesAssistant = await Self.app.models.Account.hasRole(userId, 'salesAssistant');
let claim = await Self.app.models.Claim.findById(id, {
const isClaimManager = await Self.app.models.Account.hasRole(userId, 'claimManager');
const claim = await Self.app.models.Claim.findById(id, {
fields: ['claimStateFk'],
include: [{
relation: 'claimState'
@ -33,7 +31,7 @@ module.exports = Self => {
const isClaimResolved = claim && claim.claimState().code == 'resolved';
if (!claim || (isClaimResolved && !isSalesAssistant))
if (!claim || (isClaimResolved && !isClaimManager))
return false;
return true;

View File

@ -2,9 +2,9 @@ const app = require('vn-loopback/server/server');
describe('claim isEditable()', () => {
const salesPerdonId = 18;
const salesAssistantId = 21;
const claimManagerId = 72;
it('should return false if the given claim does not exist', async() => {
let ctx = {req: {accessToken: {userId: salesAssistantId}}};
let ctx = {req: {accessToken: {userId: claimManagerId}}};
let result = await app.models.Claim.isEditable(ctx, 99999);
expect(result).toEqual(false);
@ -17,14 +17,14 @@ describe('claim isEditable()', () => {
expect(result).toEqual(false);
});
it('should be able to edit a resolved claim for a salesAssistant', async() => {
let ctx = {req: {accessToken: {userId: salesAssistantId}}};
it('should be able to edit a resolved claim for a claimManager', async() => {
let ctx = {req: {accessToken: {userId: claimManagerId}}};
let result = await app.models.Claim.isEditable(ctx, 4);
expect(result).toEqual(true);
});
it('should be able to edit a claim for a salesAssistant', async() => {
it('should be able to edit a claim for a claimManager', async() => {
let ctx = {req: {accessToken: {userId: salesPerdonId}}};
let result = await app.models.Claim.isEditable(ctx, 1);

View File

@ -42,21 +42,21 @@ describe('Update Claim', () => {
it(`should success to update the claim within privileges `, async() => {
let newClaim = await app.models.Claim.create(originalData);
const correctState = 4;
const salesPersonId = 18;
const canceledState = 4;
const claimManagerId = 72;
const ctx = {
req: {
accessToken: {
userId: salesPersonId
userId: claimManagerId
}
},
args: {
observation: 'valid observation',
claimStateFk: correctState,
claimStateFk: canceledState,
hasToPickUp: false
}
};
await app.models.Claim.updateClaim(ctx, newClaim.id,);
await app.models.Claim.updateClaim(ctx, newClaim.id);
let updatedClaim = await app.models.Claim.findById(newClaim.id);
@ -66,15 +66,15 @@ describe('Update Claim', () => {
await app.models.Claim.destroyById(newClaim.id);
});
it('should change some sensible fields as salesAssistant', async() => {
it('should change some sensible fields as claimManager', async() => {
let newClaim = await app.models.Claim.create(originalData);
const chatModel = app.models.Chat;
spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
const salesAssistantId = 21;
const claimManagerId = 72;
const ctx = {
req: {
accessToken: {userId: salesAssistantId},
accessToken: {userId: claimManagerId},
headers: {origin: 'http://localhost'}
},
args: {

View File

@ -60,9 +60,9 @@ module.exports = Self => {
if (args.claimStateFk) {
const canUpdate = await canChangeState(ctx, claim.claimStateFk);
const hasRights = await canChangeState(ctx, args.claimStateFk);
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant');
const isClaimManager = await models.Account.hasRole(userId, 'claimManager');
if (!canUpdate || !hasRights || changedHasToPickUp && !isSalesAssistant)
if (!canUpdate || !hasRights || changedHasToPickUp && !isClaimManager)
throw new UserError(`You don't have enough privileges to change that field`);
}
delete args.ctx;

View File

@ -56,7 +56,8 @@
class="vn-mr-md"
label="Pick up"
ng-model="$ctrl.claim.hasToPickUp"
vn-acl="salesAssistant">
vn-acl="claimManager"
info="When checked will notify to the salesPerson">
</vn-check>
</vn-horizontal>
</vn-card>

View File

@ -5,7 +5,7 @@ import './style.scss';
class Controller extends Section {
onSubmit() {
this.$.watcher.submit().then(() => {
if (this.aclService.hasAny(['salesAssistant']))
if (this.aclService.hasAny(['claimManager']))
this.$state.go('claim.card.detail');
});
}

View File

@ -5,3 +5,4 @@ Responsability: Responsabilidad
Company: Empresa
Sales/Client: Comercial/Cliente
Pick up: Recoger
When checked will notify a pickup to the salesPerson: Cuando se marque enviará una notificación de recogida al comercial

View File

@ -13,7 +13,7 @@
Send Pickup order
</vn-item>
<vn-item
vn-acl="salesAssistant"
vn-acl="claimManager"
vn-acl-action="remove"
ng-click="confirmDeleteClaim.show()"
name="deleteClaim"

View File

@ -77,7 +77,7 @@ class Controller extends Section {
this.$.model.refresh();
this.vnApp.showSuccess(this.$t('Data saved!'));
if (this.aclService.hasAny(['salesAssistant']))
if (this.aclService.hasAny(['claimManager']))
this.$state.go('claim.card.development');
});
}
@ -133,7 +133,7 @@ class Controller extends Section {
showEditPopover(event, saleClaimed) {
if (this.isEditable) {
if (!this.aclService.hasAny(['salesAssistant']))
if (!this.aclService.hasAny(['claimManager']))
return this.vnApp.showError(this.$t('Insuficient permisos'));
this.saleClaimed = saleClaimed;

View File

@ -9,7 +9,7 @@ class Controller extends Section {
this.$.watcher.notifySaved();
this.$.watcher.updateOriginalData();
if (this.aclService.hasAny(['salesAssistant']))
if (this.aclService.hasAny(['claimManager']))
this.$state.go('claim.card.action');
});
}

View File

@ -70,7 +70,7 @@
"params": {
"claim": "$ctrl.claim"
},
"acl": ["salesAssistant"]
"acl": ["claimManager"]
}, {
"url": "/action",
"state": "claim.card.action",
@ -79,7 +79,7 @@
"params": {
"claim": "$ctrl.claim"
},
"acl": ["salesAssistant"]
"acl": ["claimManager"]
}, {
"url": "/photos",
"state": "claim.card.photos",

View File

@ -42,7 +42,7 @@
max="5"
min="1"
step="1"
vn-acl="salesAssistant">
vn-acl="claimManager">
</vn-range>
</vn-one>
<vn-auto>

View File

@ -51,7 +51,7 @@ describe('Client', () => {
}
};
const serializedParams = $httpParamSerializer({filter});
$httpBackend.expect('GET', `ClientRisks?${serializedParams}`,).respond([{amount: 20}]);
$httpBackend.expect('GET', `ClientRisks?${serializedParams}`).respond([{amount: 20}]);
controller.getAmountPaid();
$httpBackend.flush();
@ -65,7 +65,7 @@ describe('Client', () => {
controller.$params = {id: 101};
$httpBackend.expect('POST', `Receipts`,).respond({id: 1});
$httpBackend.expect('POST', `Receipts`).respond({id: 1});
controller.responseHandler('accept');
$httpBackend.flush();

View File

@ -7,7 +7,7 @@ export default class Client extends ModuleMain {
case 'search':
return /^\d+$/.test(value)
? {id: value}
: {name: {like: `%${value}%`}};
: {or: [{name: {like: `%${value}%`}}, {socialName: {like: `%${value}%`}}]};
case 'phone':
return {
or: [

View File

@ -55,8 +55,8 @@
</span>
</vn-td>
<vn-td number>
<span ng-class="::{link: sale.isTicket}"
ng-click="$ctrl.showTicketDescriptor($event, sale)"
<span class="link"
ng-click="$ctrl.showDescriptor($event, sale)"
name="origin">
{{::sale.origin | dashIfEmpty}}
</span>
@ -94,3 +94,7 @@
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>
<vn-entry-descriptor-popover
vn-id="entryDescriptor">
</vn-entry-descriptor-popover>

View File

@ -58,10 +58,12 @@ class Controller extends Section {
this.$anchorScroll();
}
showTicketDescriptor(event, sale) {
if (!sale.isTicket) return;
showDescriptor(event, sale) {
let descriptor = 'entryDescriptor';
if (sale.isTicket)
descriptor = 'ticketDescriptor';
this.$.ticketDescriptor.show(event.target, sale.origin);
this.$[descriptor].show(event.target, sale.origin);
}
}

View File

@ -60,6 +60,34 @@ describe('Item', () => {
expect(controller.$anchorScroll).toHaveBeenCalledWith();
});
});
describe('showDescriptor ()', () => {
it('should call to the entryDescriptor show() method', () => {
controller.$.entryDescriptor = {};
controller.$.entryDescriptor.show = jest.fn();
const $event = new Event('click');
const target = document.createElement('div');
target.dispatchEvent($event);
const data = {id: 1, origin: 1};
controller.showDescriptor($event, data);
expect(controller.$.entryDescriptor.show).toHaveBeenCalledWith($event.target, data.origin);
});
it('should call to the ticketDescriptor show() method', () => {
controller.$.ticketDescriptor = {};
controller.$.ticketDescriptor.show = jest.fn();
const $event = new Event('click');
const target = document.createElement('div');
target.dispatchEvent($event);
const data = {id: 1, origin: 1, isTicket: true};
controller.showDescriptor($event, data);
expect(controller.$.ticketDescriptor.show).toHaveBeenCalledWith($event.target, data.origin);
});
});
});
});

View File

@ -3,7 +3,7 @@
"name": "Items",
"icon": "icon-item",
"validations" : true,
"dependencies": ["worker", "client", "ticket"],
"dependencies": ["worker", "client", "ticket", "entry"],
"menus": {
"main": [
{"state": "item.index", "icon": "icon-item"},

View File

@ -18,7 +18,7 @@ describe('Component vnOrderIndex', () => {
beforeEach(ngModule('order'));
beforeEach(inject(($componentController, _$window_,) => {
beforeEach(inject(($componentController, _$window_) => {
$window = _$window_;
const $element = angular.element('<vn-order-index></vn-order-index>');
controller = $componentController('vnOrderIndex', {$element});

View File

@ -60,7 +60,11 @@ module.exports = Self => {
let where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return {'s.id': value};
return {or: [
{'s.id': value},
{'s.name': {like: `%${value}%`}},
{'s.nickname': {like: `%${value}%`}}
]};
case 'nickname':
param = `s.${param}`;
return {[param]: {like: `%${value}%`}};

View File

@ -0,0 +1,74 @@
module.exports = Self => {
Self.remoteMethod('getSummary', {
description: 'Returns the supplier summary',
accessType: 'READ',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'The supplier id',
http: {source: 'path'}
},
returns: {
type: 'object',
root: true
},
http: {
path: `/:id/getSummary`,
verb: 'GET'
}
});
Self.getSummary = async id => {
let filter = {
where: {id: id},
fields: [
'id',
'name',
'nickname',
'isOfficial',
'isActive',
'note',
'nif',
'street',
'city',
'postCode',
'provinceFk',
'countryFk',
'payMethodFk',
'payDemFk',
'payDay',
'account',
'isFarmer',
],
include: [
{
relation: 'province',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'country',
scope: {
fields: ['id', 'country', 'code']
}
},
{
relation: 'payMethod',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'payDem',
scope: {
fields: ['id', 'payDem']
}
}
]
};
let supplier = await Self.app.models.Supplier.findOne(filter);
return supplier;
};
};

View File

@ -0,0 +1,28 @@
const app = require('vn-loopback/server/server');
describe('Supplier getSummary()', () => {
it('should return a summary object containing data from one supplier', async() => {
const supplier = await app.models.Supplier.getSummary(1);
expect(supplier.id).toEqual(1);
expect(supplier.name).toEqual('Plants SL');
expect(supplier.nif).toEqual('06089160W');
expect(supplier.account).toEqual(4000000001);
expect(supplier.payDay).toEqual(15);
});
it(`should return a summary object containing it's supplier country relation`, async() => {
const supplier = await app.models.Supplier.getSummary(1);
const country = supplier.country();
expect(country.id).toEqual(1);
expect(country.code).toEqual('ES');
});
it(`should return a summary object containing it's billing data relation`, async() => {
const supplier = await app.models.Supplier.getSummary(1);
const payMethod = supplier.payMethod();
expect(payMethod.name).toEqual('PayMethod one');
});
});

View File

@ -1,5 +1,8 @@
{
"Supplier": {
"dataSource": "vn"
},
"PayDem": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,19 @@
{
"name": "PayDem",
"base": "VnModel",
"options": {
"mysql": {
"table": "payDem"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"payDem": {
"type": "Number"
}
}
}

View File

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

View File

@ -48,6 +48,12 @@
"isActive": {
"type": "Boolean"
},
"isOfficial": {
"type": "Boolean"
},
"note": {
"type": "String"
},
"street": {
"type": "String"
},
@ -66,10 +72,41 @@
"payDemFk": {
"type": "Number"
},
"payDay": {
"type": "Number"
},
"nickname": {
"type": "String"
}
},
"relations": {
"payMethod": {
"type": "belongsTo",
"model": "PayMethod",
"foreignKey": "payMethodFk"
},
"payDem": {
"type": "belongsTo",
"model": "PayDem",
"foreignKey": "payDemFk"
},
"province": {
"type": "belongsTo",
"model": "Province",
"foreignKey": "provinceFk"
},
"country": {
"type": "belongsTo",
"model": "Country",
"foreignKey": "countryFk"
},
"client": {
"type": "belongsTo",
"model": "Client",
"foreignKey": "nif",
"primaryKey": "fi"
}
},
"acls": [
{
"accessType": "READ",

View File

@ -0,0 +1,5 @@
<vn-portal slot="menu">
<vn-supplier-descriptor supplier="$ctrl.supplier"></vn-entry-descriptor>
<vn-left-menu source="card"></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -0,0 +1,48 @@
import ngModule from '../module';
import ModuleCard from 'salix/components/module-card';
class Controller extends ModuleCard {
reload() {
let filter = {
include: [
{
relation: 'province',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'country',
scope: {
fields: ['id', 'name', 'code']
}
},
{
relation: 'payMethod',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'payDem',
scope: {
fields: ['id', 'payDem']
}
},
{
relation: 'client',
scope: {
fields: ['id', 'fi']
}
}
]
};
this.$http.get(`Suppliers/${this.$params.id}`, {filter})
.then(response => this.supplier = response.data);
}
}
ngModule.vnComponent('vnSupplierCard', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,57 @@
<vn-descriptor-content
module="supplier"
description="$ctrl.supplier.name">
<slot-body>
<div class="attributes">
<vn-label-value label="Tax number"
value="{{$ctrl.supplier.nif}}">
</vn-label-value>
<vn-label-value label="Alias"
value="{{$ctrl.supplier.nickname}}">
</vn-label-value>
<vn-label-value label="Pay method"
value="{{$ctrl.supplier.payMethod.name}}">
</vn-label-value>
<vn-label-value label="Payment deadline"
value="{{$ctrl.supplier.payDem.payDem}}">
</vn-label-value>
<vn-label-value label="Pay day"
value="{{$ctrl.supplier.payDay}}">
</vn-label-value>
<vn-label-value label="Account"
value="{{$ctrl.supplier.account}}">
</vn-label-value>
</div>
<div class="icons">
<vn-icon
vn-tooltip="Inactive supplier"
icon="icon-disabled"
ng-class="{bright: $ctrl.supplier.isActive == false}">
</vn-icon>
<vn-icon
vn-tooltip="Official supplier"
icon="icon-net"
ng-class="{bright: $ctrl.supplier.isOfficial == false}">
</vn-icon>
</div>
<div class="quicklinks">
<div ng-transclude="btnOne">
<vn-quick-link
tooltip="All entries with current supplier"
state="['entry.index', {q: $ctrl.entryFilter}]"
icon="icon-entry">
</vn-quick-link>
</div>
<div ng-transclude="btnTwo">
<vn-quick-link
ng-if="$ctrl.supplier.client.fi"
tooltip="Go to client"
state="['client.card.summary', {id: $ctrl.supplier.client.id}]"
icon="person">
</vn-quick-link>
</div>
<div ng-transclude="btnThree">
</div>
</div>
</slot-body>
</vn-descriptor-content>

View File

@ -0,0 +1,79 @@
import ngModule from '../module';
import Descriptor from 'salix/components/descriptor';
class Controller extends Descriptor {
get supplier() {
return this.entity;
}
set supplier(value) {
this.entity = value;
}
get entryFilter() {
if (!this.supplier) return null;
const date = new Date();
date.setHours(0, 0, 0, 0);
const from = new Date(date.getTime());
from.setDate(from.getDate() - 10);
const to = new Date(date.getTime());
to.setDate(to.getDate() + 10);
return JSON.stringify({
supplierFk: this.supplier.id,
from,
to
});
}
loadData() {
const filter = {
fields: [
'id',
'name',
'nickname',
'nif',
'payMethodFk',
'payDemFk',
'payDay',
'isActive',
'isOfficial',
'account'
],
include: [
{
relation: 'payMethod',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'payDem',
scope: {
fields: ['id', 'payDem']
}
},
{
relation: 'client',
scope: {
fields: ['id', 'fi']
}
}
]
};
return this.getData(`Suppliers/${this.supplier.id}`, {filter})
.then(res => this.supplier = res.data);
}
}
ngModule.vnComponent('vnSupplierDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
supplier: '<'
}
});

View File

@ -0,0 +1,64 @@
import './index.js';
describe('Supplier Component vnSupplierDescriptor', () => {
let $httpBackend;
let controller;
let $httpParamSerializer;
const supplier = {id: 1};
beforeEach(ngModule('supplier'));
beforeEach(inject(($componentController, _$httpBackend_, _$httpParamSerializer_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnSupplierDescriptor', {$element: null}, {supplier});
}));
describe('loadData()', () => {
it('should perform ask for the supplier', () => {
const filter = {
fields: [
'id',
'name',
'nickname',
'nif',
'payMethodFk',
'payDemFk',
'payDay',
'isActive',
'isOfficial',
'account'
],
include: [
{
relation: 'payMethod',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'payDem',
scope: {
fields: ['id', 'payDem']
}
},
{
relation: 'client',
scope: {
fields: ['id', 'fi']
}
}
]
};
const serializedParams = $httpParamSerializer({filter});
let query = `Suppliers/${controller.supplier.id}?${serializedParams}`;
jest.spyOn(controller, 'getData');
$httpBackend.expect('GET', query).respond({id: 1});
controller.loadData();
$httpBackend.flush();
expect(controller.getData).toHaveBeenCalledTimes(1);
});
});
});

View File

@ -0,0 +1,5 @@
Tax number: NIF / CIF
All entries with current supplier: Todas las entradas con el proveedor actual
Go to client: Ir al cliente
Official supplier: Proveedor oficial
Inactive supplier: Proveedor inactivo

View File

@ -4,3 +4,6 @@ import './main';
import './index/';
import './search-panel';
import './log';
import './summary';
import './card';
import './descriptor';

View File

@ -8,7 +8,7 @@
<vn-searchbar
vn-focus
panel="vn-supplier-search-panel"
info="Search suppliers by id"
info="Search suppliers by id, name or alias"
model="model">
</vn-searchbar>
</vn-portal>

View File

@ -2,6 +2,7 @@
"module": "supplier",
"name": "Suppliers",
"icon" : "icon-supplier",
"dependencies": ["client", "item"],
"validations" : true,
"menus": {
"main": [
@ -27,6 +28,18 @@
"state": "supplier.card.log",
"component": "vn-supplier-log",
"description": "Log"
"url": "/:id",
"state": "supplier.card",
"abstract": true,
"component": "vn-supplier-card"
}, {
"url": "/summary",
"state": "supplier.card.summary",
"component": "vn-supplier-summary",
"description": "Summary",
"params": {
"supplier": "$ctrl.supplier"
}
}
]
}

View File

@ -5,7 +5,7 @@
vn-one
label="General search"
ng-model="filter.search"
info="Search suppliers by id"
info="Search suppliers by id, name or alias"
vn-focus>
</vn-textfield>
</vn-horizontal>

View File

@ -1,3 +1,4 @@
Province: Provincia
Country: País
Tax number: Nif
Tax number: NIF / CIF
Search suppliers by id, name or alias: Busca proveedores por id, nombre o alias

View File

@ -0,0 +1,74 @@
<vn-card class="summary">
<h5>{{$ctrl.summary.name}} - {{$ctrl.summary.id}}</h5>
<vn-horizontal>
<vn-one>
<h4 translate>Basic data</h4>
<vn-label-value label="Id"
value="{{$ctrl.summary.id}}">
</vn-label-value>
<vn-label-value label="Alias"
value="{{$ctrl.summary.nickname}}">
</vn-label-value>
<vn-check
label="Is official"
ng-model="$ctrl.summary.isOfficial"
disabled="true">
</vn-check>
<vn-check
label="Is active"
ng-model="$ctrl.summary.isActive"
disabled="true">
</vn-check>
<vn-label-value label="Notes"
value="{{$ctrl.summary.note}}">
</vn-label-value>
</vn-one>
<vn-one>
<h4 translate>Fiscal address</h4>
<vn-label-value label="Social name"
value="{{$ctrl.summary.name}}">
</vn-label-value>
<vn-label-value label="Tax number"
value="{{$ctrl.summary.nif}}">
</vn-label-value>
<vn-label-value label="Street" ellipsize="false"
value="{{$ctrl.summary.street}}">
</vn-label-value>
<vn-label-value label="City"
value="{{$ctrl.summary.city}}">
</vn-label-value>
<vn-label-value label="Postcode"
value="{{$ctrl.summary.postCode}}">
</vn-label-value>
<vn-label-value label="Province"
value="{{$ctrl.summary.province.name}}">
</vn-label-value>
<vn-label-value label="Country"
value="{{$ctrl.summary.country.country}}">
</vn-label-value>
</vn-one>
<vn-one>
<h4 translate>Billing data</h4>
<vn-label-value label="Pay method"
value="{{$ctrl.summary.payMethod.name}}">
</vn-label-value>
<vn-label-value label="Payment deadline"
value="{{$ctrl.summary.payDem.payDem}}">
</vn-label-value>
<vn-label-value label="Pay day"
value="{{$ctrl.summary.payDay}}">
</vn-label-value>
<vn-label-value label="Account"
value="{{$ctrl.summary.account}}">
</vn-label-value>
<vn-check
label="Is Farmer"
ng-model="$ctrl.summary.isFarmer"
disabled="true">
</vn-check>
</vn-one>
</vn-horizontal>
</vn-card>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>

View File

@ -0,0 +1,26 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
$onChanges() {
if (!this.supplier)
return;
this.getSummary();
}
getSummary() {
return this.$http.get(`Suppliers/${this.supplier.id}/getSummary`).then(response => {
this.summary = response.data;
});
}
}
ngModule.vnComponent('vnSupplierSummary', {
template: require('./index.html'),
controller: Controller,
bindings: {
supplier: '<'
}
});

View File

@ -0,0 +1,32 @@
import './index';
describe('Supplier', () => {
describe('Component vnSupplierSummary', () => {
let controller;
let $httpBackend;
let $scope;
beforeEach(ngModule('supplier'));
beforeEach(inject(($componentController, _$httpBackend_, $rootScope) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
const $element = angular.element('<vn-supplier-summary></vn-supplier-summary>');
controller = $componentController('vnSupplierSummary', {$element, $scope});
}));
describe('getSummary()', () => {
it('should perform a get asking for the supplier data', () => {
controller.supplier = {id: 1};
const query = `Suppliers/${controller.supplier.id}/getSummary`;
$httpBackend.expectGET(query).respond({id: 1});
controller.getSummary();
$httpBackend.flush();
expect(controller.summary).toEqual({id: 1});
});
});
});
});

View File

@ -0,0 +1,5 @@
Is official: Es oficial
Country: País
Tax number: NIF / CIF
Search suppliers by id, name or alias: Busca proveedores por id, nombre o alias
Is Farmer: Es agrícola

View File

@ -0,0 +1,7 @@
@import "variables";
vn-client-summary {
.alert span {
color: $color-alert
}
}

View File

@ -48,6 +48,12 @@
},
"zoneFk": {
"type": "Number"
},
"zonePrice": {
"type": "Number"
},
"zoneBonus": {
"type": "Number"
}
},
"relations": {

View File

@ -15,6 +15,9 @@ class Controller extends ModuleCard {
relation: 'warehouse',
scope: {fields: ['name']}
},
{
relation: 'zone',
},
{
relation: 'invoiceOut',
scope: {fields: ['id']}

View File

@ -75,12 +75,36 @@
</div>
</section>
</div>
<div class="totalBox align-left">
<h6 class="align-center" translate>Zone breakdown</h6>
<div> <vn-label translate>Price</vn-label> {{$ctrl.ticket.zonePrice | currency: 'EUR': 2}} </div>
<div> <vn-label translate>Bonus</vn-label> {{$ctrl.ticket.zoneBonus | currency: 'EUR': 2}} </div>
<div> <vn-label translate>Zone</vn-label>
<span
title="{{$ctrl.ticket.zone.name}}"
vn-click-stop="zoneDescriptor.show($event, $ctrl.ticket.zone.id)"
class="link">
{{$ctrl.ticket.zone.name | dashIfEmpty}}
</span>
</div>
<div ng-show="$ctrl.ticket.zone.isVolumetric">
<vn-label translate>Volume</vn-label> {{$ctrl.ticketVolume}}
</div>
<div ng-show="!$ctrl.ticket.zone.isVolumetric">
<vn-label translate>Packages</vn-label> {{$ctrl.ticket.packages}}
</div>
</div>
<div class="totalBox align-left">
<h6 class="align-center" translate>Theorical cost</h6>
<div> <vn-label translate>Price</vn-label> {{$ctrl.theoricalCost | currency: 'EUR': 2}} </div>
<div class="total"> <vn-label translate>Price total</vn-label> {{$ctrl.theoricalCost | currency: 'EUR': 2}} </div>
</div>
</vn-side-menu>
<vn-item-descriptor-popover
vn-id="descriptor"
warehouse-fk="$ctrl.ticket.warehouseFk">
</vn-item-descriptor-popover>
<vn-zone-descriptor-popover
vn-id="zoneDescriptor">
</vn-zone-descriptor-popover>

View File

@ -37,9 +37,10 @@ class Controller extends Section {
this._ticket = value;
if (!value) return;
this.getTheoricalCost();
this.getComponentsSum();
if (this.ticket.zone.isVolumetric)
this.getTicketVolume();
}
base() {
@ -76,6 +77,13 @@ class Controller extends Section {
this.$http.get(`Tickets/${this.ticket.id}/getComponentsSum`)
.then(res => this.componentsList = res.data);
}
getTicketVolume() {
if (!this.ticket) return;
this.$http.get(`Tickets/${this.ticket.id}/getVolume`)
.then(res => this.ticketVolume = res.data[0].volume);
}
}
ngModule.vnComponent('vnTicketComponents', {

View File

@ -89,7 +89,10 @@ describe('ticket', () => {
jest.spyOn(controller, 'getComponentsSum');
controller._ticket = undefined;
controller.ticket = {
id: 7
id: 7,
zone: {
isVolumetric: false
}
};
expect(controller.ticket).toBeDefined();

View File

@ -1,2 +1,6 @@
Theorical cost: Porte teorico
Total without VAT: Total sin IVA
Bonus: Bonificación
Price: Precio
Price total: Precio total
Zone breakdown: Desglose zona

View File

@ -14,6 +14,7 @@
</vn-multi-check>
</vn-th>
<vn-th></vn-th>
<vn-th></vn-th>
<vn-th field="id" number>Id</vn-th>
<vn-th field="salesPersonFk" class="expendable">Salesperson</vn-th>
<vn-th field="shipped">Date</vn-th>
@ -40,27 +41,36 @@
</vn-td>
<vn-td shrink>
<vn-icon
ng-show="ticket.hasTicketRequest"
ng-show="ticket.isTaxDataChecked"
translate-attr="{title: 'Verified data'}"
class="bright"
icon="check">
</vn-icon>
</vn-td>
<vn-td shrink>
<vn-icon
ng-show="ticket.hasTicketRequest"
translate-attr="{title: 'Purchase request'}"
class="bright"
vn-tooltip="Purchase request"
icon="icon-100">
</vn-icon>
<vn-icon
ng-show="ticket.isAvailable === 0"
translate-attr="{title: 'Not available'}"
class="bright"
vn-tooltip="Not available"
icon="icon-unavailable">
</vn-icon>
<vn-icon
ng-show="ticket.isFreezed"
translate-attr="{title: 'Client frozen'}"
class="bright"
vn-tooltip="Client frozen"
icon="icon-frozen">
</vn-icon>
<vn-icon
ng-show="ticket.risk"
title="{{::$ctrl.$t('Risk')}}: {{ticket.risk}}"
class="bright"
vn-tooltip="{{::$ctrl.$t('Risk')}}: {{ticket.risk}}"
icon="icon-risk">
</vn-icon>
</vn-td>

View File

@ -28,8 +28,12 @@
<vn-label-value label="Agency"
value="{{$ctrl.summary.agencyMode.name}}">
</vn-label-value>
<vn-label-value label="Zone"
value="{{$ctrl.summary.zone.name}}">
<vn-label-value label="Zone">
<span
ng-click="zoneDescriptor.show($event, $ctrl.summary.zoneFk)"
class="link">
{{$ctrl.summary.zone.name}}
</span>
</vn-label-value>
<vn-label-value label="Warehouse"
value="{{$ctrl.summary.warehouse.name}}">
@ -247,3 +251,6 @@
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-zone-descriptor-popover
vn-id="zoneDescriptor">
</vn-zone-descriptor-popover>

View File

@ -3,7 +3,7 @@
"name": "Travels",
"icon": "local_airport",
"validations": true,
"dependencies": ["worker"],
"dependencies": ["worker", "entry"],
"menus": {
"main": [
{"state": "travel.index", "icon": "local_airport"}

View File

@ -41,7 +41,7 @@
value="{{$ctrl.travelData.ref}}">
</vn-label-value>
<vn-label-value
label="m3"
label="m³"
value="{{$ctrl.travelData.m3}}">
</vn-label-value>
<vn-label-value
@ -63,7 +63,7 @@
<vn-th shrink>Package</vn-th>
<vn-th shrink>CC</vn-th>
<vn-th shrink>Pallet</vn-th>
<vn-th shrink>m3</vn-th>
<vn-th shrink>m³</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
@ -75,7 +75,12 @@
disabled="true">
</vn-check>
</vn-td>
<vn-td shrink>{{entry.id}} </vn-td>
<vn-td shrink>
<span class="link"
vn-click-stop="entryDescriptor.show($event, entry.id)">
{{entry.id}}
</span>
</vn-td>
<vn-td expand>{{entry.supplierName}}</vn-td>
<vn-td shrink>{{entry.ref}}</vn-td>
<vn-td shrink>{{entry.hb}}</vn-td>
@ -142,3 +147,6 @@
</vn-auto>
</vn-horizontal>
</vn-card>
<vn-entry-descriptor-popover
vn-id="entryDescriptor">
</vn-entry-descriptor-popover>

View File

@ -9,7 +9,7 @@ Received: Recibida
Agency: Agencia
Entries: Entradas
Confirmed: Confirmada
Entry Id: Entrada Id
Entry Id: Id entrada
Supplier: Proveedor
Pallet: Pallet
Freight: Porte

View File

@ -73,7 +73,7 @@
value="{{::row.bonus | currency:'EUR':2}}">
</vn-label-value>
<vn-label-value
label="Max m3"
label="Max m³"
value="{{::row.m3Max}}">
</vn-label-value>
</vn-item-section>
@ -166,7 +166,7 @@
</vn-input-number>
</vn-horizontal>
<vn-input-number
label="Max m3"
label="Max m³"
ng-model="$ctrl.selected.m3Max"
min="0"
step="0.01">