Merge branch 'dev' into 3649-claim_log
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Carlos Jimenez Ruiz 2022-03-03 12:00:02 +00:00
commit 63fc58b6ba
38 changed files with 607 additions and 563 deletions

View File

@ -0,0 +1,2 @@
DELETE FROM salix.ACL
WHERE model = 'ClaimEnd' AND property = 'importTicketSales';

View File

@ -1703,10 +1703,10 @@ INSERT INTO `vn`.`claimState`(`id`, `code`, `description`, `roleFk`, `priority`)
INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `observation`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created` ) INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `observation`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created` )
VALUES VALUES
(1, CURDATE(), 1, 'observation one', 1101, 18, 3, 0, CURDATE()), (1, CURDATE(), 1, 'Cu nam labores lobortis definiebas, ei aliquyam salutatus persequeris quo, cum eu nemore fierent dissentiunt. Per vero dolor id, vide democritum scribentur eu vim, pri erroribus temporibus ex.', 1101, 18, 3, 0, CURDATE()),
(2, CURDATE(), 2, 'observation two', 1101, 18, 3, 0, CURDATE()), (2, CURDATE(), 2, 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.', 1101, 18, 3, 0, CURDATE()),
(3, CURDATE(), 3, 'observation three', 1101, 18, 1, 1, CURDATE()), (3, CURDATE(), 3, 'An vim commodo dolorem volutpat, cu expetendis voluptatum usu, et mutat consul adversarium his. His natum numquam legimus an, diam fabulas mei ut. Melius fabellas sadipscing vel id. Partem diceret mandamus mea ne, has te tempor nostrud. Aeque nostro eum no.', 1101, 18, 1, 1, CURDATE()),
(4, CURDATE(), 3, 'observation four', 1104, 18, 5, 0, CURDATE()); (4, CURDATE(), 3, 'Wisi forensibus mnesarchum in cum. Per id impetus abhorreant, his no magna definiebas, inani rationibus in quo. Ut vidisse dolores est, ut quis nominavi mel. Ad pri quod apeirian concludaturque.', 1104, 18, 5, 0, CURDATE());
INSERT INTO `vn`.`claimBeginning`(`id`, `claimFk`, `saleFk`, `quantity`) INSERT INTO `vn`.`claimBeginning`(`id`, `claimFk`, `saleFk`, `quantity`)
VALUES VALUES
@ -1851,6 +1851,15 @@ INSERT INTO `postgresql`.`business_labour`(`business_id`, `notes`, `department_i
SELECT b.business_id, NULL, 23, 1, 0, 1, 1, 1, 1 SELECT b.business_id, NULL, 23, 1, 0, 1, 1, 1, 1
FROM `postgresql`.`business` `b`; FROM `postgresql`.`business` `b`;
INSERT INTO `postgresql`.`business` (`client_id`, `provider_id`, `date_start`, `date_end`, `workerBusiness`, `reasonEndFk`)
SELECT p.profile_id, 1000, CONCAT(YEAR(DATE_ADD(CURDATE(), INTERVAL -2 YEAR)), '-12-25'), CONCAT(YEAR(DATE_ADD(CURDATE(), INTERVAL -1 YEAR)), '-12-24'), CONCAT('E-46-',RPAD(CONCAT(p.profile_id,9),8,p.profile_id)), NULL
FROM `postgresql`.`profile` `p`
WHERE `p`.`profile_id` = 1109;
INSERT INTO `postgresql`.`business_labour` (`business_id`, `notes`, `department_id`, `professional_category_id`, `incentivo`, `calendar_labour_type_id`, `porhoras`, `labour_agreement_id`, `workcenter_id`)
VALUES
(1111, NULL, 23, 1, 0.0, 1, 1, 1, 1);
UPDATE `postgresql`.`business_labour` bl UPDATE `postgresql`.`business_labour` bl
JOIN `postgresql`.`business` b ON b.business_id = bl.business_id JOIN `postgresql`.`business` b ON b.business_id = bl.business_id
JOIN `postgresql`.`profile` pr ON pr.profile_id = b.client_id JOIN `postgresql`.`profile` pr ON pr.profile_id = b.client_id

View File

@ -305,12 +305,12 @@ export default {
anyCreditInsuranceLine: 'vn-client-credit-insurance-insurance-index vn-tbody > vn-tr', anyCreditInsuranceLine: 'vn-client-credit-insurance-insurance-index vn-tbody > vn-tr',
}, },
clientDefaulter: { clientDefaulter: {
anyClient: 'vn-client-defaulter-index vn-tbody > vn-tr', anyClient: 'vn-client-defaulter tbody > tr',
firstClientName: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(2) > span', firstClientName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(2) > span',
firstSalesPersonName: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(3) > span', firstSalesPersonName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(3) > span',
firstObservation: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(6) > vn-textarea[ng-model="defaulter.observation"]', firstObservation: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(6) > vn-textarea[ng-model="defaulter.observation"]',
allDefaulterCheckbox: 'vn-client-defaulter-index vn-thead vn-multi-check', allDefaulterCheckbox: 'vn-client-defaulter thead vn-multi-check',
addObservationButton: 'vn-client-defaulter-index vn-button[icon="icon-notes"]', addObservationButton: 'vn-client-defaulter vn-button[icon="icon-notes"]',
observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]', observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]',
saveButton: 'button[response="accept"]' saveButton: 'button[response="accept"]'
}, },
@ -680,13 +680,13 @@ export default {
header: 'vn-claim-summary > vn-card > h5', header: 'vn-claim-summary > vn-card > h5',
state: 'vn-claim-summary vn-label-value[label="State"] > section > span', state: 'vn-claim-summary vn-label-value[label="State"] > section > span',
observation: 'vn-claim-summary vn-textarea[ng-model="$ctrl.summary.claim.observation"]', observation: 'vn-claim-summary vn-textarea[ng-model="$ctrl.summary.claim.observation"]',
firstSaleItemId: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(4) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(1) > span', firstSaleItemId: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(1) > span',
firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img', firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img',
itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor', itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
itemDescriptorPopoverItemDiaryButton: '.vn-popover vn-item-descriptor vn-quick-link[icon="icon-transaction"] > a', itemDescriptorPopoverItemDiaryButton: '.vn-popover vn-item-descriptor vn-quick-link[icon="icon-transaction"] > a',
firstDevelopmentWorker: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > span', firstDevelopmentWorker: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(4) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > span',
firstDevelopmentWorkerGoToClientButton: '.vn-popover vn-worker-descriptor vn-quick-link[icon="person"] > a', firstDevelopmentWorkerGoToClientButton: '.vn-popover vn-worker-descriptor vn-quick-link[icon="person"] > a',
firstActionTicketId: 'vn-claim-summary > vn-card > vn-horizontal > vn-auto:nth-child(6) vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > span', firstActionTicketId: 'vn-claim-summary > vn-card > vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > span',
firstActionTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor' firstActionTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor'
}, },
claimBasicData: { claimBasicData: {
@ -722,10 +722,7 @@ export default {
}, },
claimAction: { claimAction: {
importClaimButton: 'vn-claim-action vn-button[label="Import claim"]', importClaimButton: 'vn-claim-action vn-button[label="Import claim"]',
importTicketButton: 'vn-claim-action vn-button[label="Import ticket"]', anyLine: 'vn-claim-action vn-tbody > vn-tr',
secondImportableTicket: '.vn-popover.shown .content > div > vn-table > div > vn-tbody > vn-tr:nth-child(2)',
firstLineDestination: 'vn-claim-action vn-tr:nth-child(1) vn-autocomplete[ng-model="saleClaimed.claimDestinationFk"]',
secondLineDestination: 'vn-claim-action vn-tr:nth-child(2) vn-autocomplete[ng-model="saleClaimed.claimDestinationFk"]',
firstDeleteLine: 'vn-claim-action vn-tr:nth-child(1) vn-icon-button[icon="delete"]', firstDeleteLine: 'vn-claim-action vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
isPaidWithManaCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.isChargedToMana"]' isPaidWithManaCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.isChargedToMana"]'
}, },

View File

@ -9,7 +9,7 @@ describe('Client defaulter path', () => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('insurance', 'client'); await page.loginAndModule('insurance', 'client');
await page.accessToSection('client.defaulter.index'); await page.accessToSection('client.defaulter');
}); });
afterAll(async() => { afterAll(async() => {
@ -28,8 +28,8 @@ describe('Client defaulter path', () => {
const salesPersonName = const salesPersonName =
await page.waitToGetProperty(selectors.clientDefaulter.firstSalesPersonName, 'innerText'); await page.waitToGetProperty(selectors.clientDefaulter.firstSalesPersonName, 'innerText');
expect(clientName).toEqual('Ororo Munroe'); expect(clientName).toEqual('Batman');
expect(salesPersonName).toEqual('salesPerson'); expect(salesPersonName).toEqual('salesPersonNick');
}); });
it('should first observation not changed', async() => { it('should first observation not changed', async() => {
@ -52,7 +52,7 @@ describe('Client defaulter path', () => {
it('shoul checked all defaulters', async() => { it('shoul checked all defaulters', async() => {
await page.loginAndModule('insurance', 'client'); await page.loginAndModule('insurance', 'client');
await page.accessToSection('client.defaulter.index'); await page.accessToSection('client.defaulter');
await page.waitToClick(selectors.clientDefaulter.allDefaulterCheckbox); await page.waitToClick(selectors.clientDefaulter.allDefaulterCheckbox);
}); });
@ -65,6 +65,7 @@ describe('Client defaulter path', () => {
it('should first observation changed', async() => { it('should first observation changed', async() => {
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
await page.waitForSelector(selectors.clientDefaulter.firstObservation);
const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value'); const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value');
expect(message.text).toContain('Observation saved!'); expect(message.text).toContain('Observation saved!');

View File

@ -24,22 +24,6 @@ describe('Claim action path', () => {
expect(message.text).toContain('Data saved!'); expect(message.text).toContain('Data saved!');
}); });
it('should import the second importable ticket', async() => {
await page.waitToClick(selectors.claimAction.importTicketButton);
await page.waitToClick(selectors.claimAction.secondImportableTicket);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should edit the second line destination field', async() => {
await page.waitForContentLoaded();
await page.autocompleteSearch(selectors.claimAction.secondLineDestination, 'Bueno');
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should delete the first line', async() => { it('should delete the first line', async() => {
await page.waitToClick(selectors.claimAction.firstDeleteLine); await page.waitToClick(selectors.claimAction.firstDeleteLine);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
@ -47,18 +31,11 @@ describe('Claim action path', () => {
expect(message.text).toContain('Data saved!'); expect(message.text).toContain('Data saved!');
}); });
it('should refresh the view to check the remaining line is the expected one', async() => { it('should refresh the view to check not have lines', async() => {
await page.reloadSection('claim.card.action'); await page.reloadSection('claim.card.action');
const result = await page.waitToGetProperty(selectors.claimAction.firstLineDestination, 'value'); const result = await page.countElement(selectors.claimAction.anyLine);
expect(result).toEqual('Bueno'); expect(result).toEqual(0);
});
it('should delete the current first line', async() => {
await page.waitToClick(selectors.claimAction.firstDeleteLine);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
}); });
it('should check the "is paid with mana" checkbox', async() => { it('should check the "is paid with mana" checkbox', async() => {

View File

@ -1,3 +1,4 @@
import selectors from '../../helpers/selectors.js'; import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
@ -38,7 +39,7 @@ describe('Claim summary path', () => {
it('should display the observation', async() => { it('should display the observation', async() => {
const result = await page.waitToGetProperty(selectors.claimSummary.observation, 'value'); const result = await page.waitToGetProperty(selectors.claimSummary.observation, 'value');
expect(result).toContain('observation four'); expect(result).toContain('Wisi forensibus mnesarchum in cum. Per id impetus abhorreant');
}); });
it('should display the claimed line(s)', async() => { it('should display the claimed line(s)', async() => {

View File

@ -1,61 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('importTicketSales', {
description: 'Imports lines from claimBeginning to a new ticket with specific shipped, landed dates, agency and company',
accessType: 'WRITE',
accepts: [{
arg: 'params',
type: 'object',
http: {source: 'body'}
}],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/importTicketSales`,
verb: 'POST'
}
});
Self.importTicketSales = async(ctx, params, options) => {
let models = Self.app.models;
let userId = ctx.req.accessToken.userId;
let tx;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const worker = await models.Worker.findOne({where: {userFk: userId}}, myOptions);
let ticketSales = await models.Sale.find({
where: {ticketFk: params.ticketFk}
}, myOptions);
let claimEnds = [];
ticketSales.forEach(sale => {
claimEnds.push({
saleFk: sale.id,
claimFk: params.claimFk,
workerFk: worker.id
});
});
const createdClaimEnds = await Self.create(claimEnds, myOptions);
if (tx) await tx.commit();
return createdClaimEnds;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -1,26 +0,0 @@
const app = require('vn-loopback/server/server');
describe('Claim importTicketSales()', () => {
it('should import sales to a claim actions from an specific ticket', async() => {
const ctx = {req: {accessToken: {userId: 5}}};
const tx = await app.models.Entry.beginTransaction({});
try {
const options = {transaction: tx};
const claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, {
claimFk: 1,
ticketFk: 1
}, options);
expect(claimEnds.length).toEqual(4);
expect(claimEnds[0].saleFk).toEqual(1);
expect(claimEnds[2].saleFk).toEqual(3);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -94,19 +94,14 @@ module.exports = Self => {
? {'cl.id': value} ? {'cl.id': value}
: { : {
or: [ or: [
{'c.name': {like: `%${value}%`}} {'cl.socialName': {like: `%${value}%`}}
] ]
}; };
case 'client':
return {'c.name': {like: `%${value}%`}};
case 'id': case 'id':
return {'cl.id': value};
case 'clientFk':
return {'c.id': value};
case 'claimStateFk': case 'claimStateFk':
return {'cl.claimStateFk': value}; case 'priority':
return {[`cl.${param}`]: value};
case 'salesPersonFk': case 'salesPersonFk':
return {'c.salesPersonFk': value};
case 'attenderFk': case 'attenderFk':
return {'cl.workerFk': value}; return {'cl.workerFk': value};
case 'created': case 'created':
@ -123,12 +118,23 @@ module.exports = Self => {
const stmts = []; const stmts = [];
const stmt = new ParameterizedSQL( const stmt = new ParameterizedSQL(
`SELECT cl.id, c.name, cl.clientFk, cl.workerFk, u.name AS userName, cs.description, cl.created `SELECT *
FROM claim cl FROM (
LEFT JOIN client c ON c.id = cl.clientFk SELECT
LEFT JOIN worker w ON w.id = cl.workerFk cl.id,
LEFT JOIN account.user u ON u.id = w.userFk cl.clientFk,
LEFT JOIN claimState cs ON cs.id = cl.claimStateFk` c.socialName,
cl.workerFk,
u.name AS workerName,
cs.description,
cl.created,
cs.priority,
cl.claimStateFk
FROM claim cl
LEFT JOIN client c ON c.id = cl.clientFk
LEFT JOIN worker w ON w.id = cl.workerFk
LEFT JOIN account.user u ON u.id = w.userFk
LEFT JOIN claimState cs ON cs.id = cl.claimStateFk ) cl`
); );
stmt.merge(conn.makeSuffix(filter)); stmt.merge(conn.makeSuffix(filter));

View File

@ -1,5 +1,5 @@
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('getSummary', { Self.remoteMethodCtx('getSummary', {
description: 'Return the claim summary', description: 'Return the claim summary',
accessType: 'READ', accessType: 'READ',
accepts: [{ accepts: [{
@ -19,7 +19,7 @@ module.exports = Self => {
} }
}); });
Self.getSummary = async(id, options) => { Self.getSummary = async(ctx, id, options) => {
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')
@ -135,6 +135,7 @@ module.exports = Self => {
const res = await Promise.all(promises); const res = await Promise.all(promises);
summary.isEditable = await Self.isEditable(ctx, id, myOptions);
[summary.claim] = res[0]; [summary.claim] = res[0];
summary.salesClaimed = res[1]; summary.salesClaimed = res[1];
summary.developments = res[2]; summary.developments = res[2];

View File

@ -1,6 +1,7 @@
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('regularizeClaim', { Self.remoteMethodCtx('regularizeClaim', {
description: 'Imports lines from claimBeginning to a new ticket with specific shipped, landed dates, agency and company', description: `Imports lines from claimBeginning to a new ticket
with specific shipped, landed dates, agency and company`,
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',

View File

@ -25,7 +25,7 @@ describe('claim filter()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const result = await app.models.Claim.filter({args: {filter: {}, search: 'Tony Stark'}}, null, options); const result = await app.models.Claim.filter({args: {filter: {}, search: 'Iron man'}}, null, options);
expect(result.length).toEqual(1); expect(result.length).toEqual(1);
expect(result[0].id).toEqual(4); expect(result[0].id).toEqual(4);

View File

@ -3,17 +3,24 @@ const app = require('vn-loopback/server/server');
describe('claim getSummary()', () => { describe('claim getSummary()', () => {
it('should return summary with claim, salesClaimed, developments and actions defined ', async() => { it('should return summary with claim, salesClaimed, developments and actions defined ', async() => {
const tx = await app.models.Claim.beginTransaction({}); const tx = await app.models.Claim.beginTransaction({});
const ctx = {
req: {
accessToken: {
userId: 9
}
}
};
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const result = await app.models.Claim.getSummary(1, options); const result = await app.models.Claim.getSummary(ctx, 1, options);
const keys = Object.keys(result); const keys = Object.keys(result);
expect(keys).toContain('claim'); expect(keys).toContain('claim');
expect(keys).toContain('salesClaimed'); expect(keys).toContain('salesClaimed');
expect(keys).toContain('developments'); expect(keys).toContain('developments');
expect(keys).toContain('actions'); expect(keys).toContain('actions');
expect(keys).toContain('isEditable');
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -1,9 +1,10 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('claim regularizeClaim()', () => { describe('claim regularizeClaim()', () => {
const userId = 18;
const ctx = { const ctx = {
req: { req: {
accessToken: {userId: 18}, accessToken: {userId: userId},
headers: {origin: 'http://localhost'} headers: {origin: 'http://localhost'}
} }
}; };
@ -11,8 +12,9 @@ describe('claim regularizeClaim()', () => {
return params.nickname; return params.nickname;
}; };
const chatModel = app.models.Chat; const chatModel = models.Chat;
const claimFk = 1; const claimId = 1;
const ticketId = 1;
const pendentState = 1; const pendentState = 1;
const resolvedState = 3; const resolvedState = 3;
const trashDestination = 2; const trashDestination = 2;
@ -21,27 +23,40 @@ describe('claim regularizeClaim()', () => {
let claimEnds = []; let claimEnds = [];
let trashTicket; let trashTicket;
async function importTicket(ticketId, claimId, userId, options) {
const ticketSales = await models.Sale.find({
where: {ticketFk: ticketId}
}, options);
const claimEnds = [];
for (let sale of ticketSales) {
claimEnds.push({
saleFk: sale.id,
claimFk: claimId,
workerFk: userId
});
}
return await models.ClaimEnd.create(claimEnds, options);
}
it('should send a chat message with value "Trash" and then change claim state to resolved', async() => { it('should send a chat message with value "Trash" and then change claim state to resolved', async() => {
const tx = await app.models.Claim.beginTransaction({}); const tx = await models.Claim.beginTransaction({});
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, { claimEnds = await importTicket(ticketId, claimId, userId, options);
claimFk: claimFk,
ticketFk: 1
}, options);
for (claimEnd of claimEnds) for (claimEnd of claimEnds)
await claimEnd.updateAttributes({claimDestinationFk: trashDestination}, options); await claimEnd.updateAttributes({claimDestinationFk: trashDestination}, options);
let claimBefore = await app.models.Claim.findById(claimFk, null, options); let claimBefore = await models.Claim.findById(claimId, null, options);
await app.models.Claim.regularizeClaim(ctx, claimFk, options); await models.Claim.regularizeClaim(ctx, claimId, options);
let claimAfter = await app.models.Claim.findById(claimFk, null, options); let claimAfter = await models.Claim.findById(claimId, null, options);
trashTicket = await app.models.Ticket.findOne({where: {addressFk: 12}}, options); trashTicket = await models.Ticket.findOne({where: {addressFk: 12}}, options);
expect(trashTicket.addressFk).toEqual(trashAddress); expect(trashTicket.addressFk).toEqual(trashAddress);
expect(claimBefore.claimStateFk).toEqual(pendentState); expect(claimBefore.claimStateFk).toEqual(pendentState);
@ -57,22 +72,19 @@ describe('claim regularizeClaim()', () => {
}); });
it('should send a chat message with value "Bueno" and then change claim state to resolved', async() => { it('should send a chat message with value "Bueno" and then change claim state to resolved', async() => {
const tx = await app.models.Claim.beginTransaction({}); const tx = await models.Claim.beginTransaction({});
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, { claimEnds = await importTicket(ticketId, claimId, userId, options);
claimFk: claimFk,
ticketFk: 1
}, options);
for (claimEnd of claimEnds) for (claimEnd of claimEnds)
await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options); await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options);
await app.models.Claim.regularizeClaim(ctx, claimFk, options); await models.Claim.regularizeClaim(ctx, claimId, options);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno'); expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno');
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4);
@ -85,22 +97,19 @@ describe('claim regularizeClaim()', () => {
}); });
it('should send a chat message to the salesPerson when claim isPickUp is enabled', async() => { it('should send a chat message to the salesPerson when claim isPickUp is enabled', async() => {
const tx = await app.models.Claim.beginTransaction({}); const tx = await models.Claim.beginTransaction({});
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, { claimEnds = await importTicket(ticketId, claimId, userId, options);
claimFk: claimFk,
ticketFk: 1
}, options);
for (claimEnd of claimEnds) for (claimEnd of claimEnds)
await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options); await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options);
await app.models.Claim.regularizeClaim(ctx, claimFk, options); await models.Claim.regularizeClaim(ctx, claimId, options);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno'); expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno');
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4);

View File

@ -1,3 +0,0 @@
module.exports = Self => {
require('../methods/claim-end/importTicketSales')(Self);
};

View File

@ -24,15 +24,9 @@
<vn-button <vn-button
label="Import claim" label="Import claim"
disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedStateId" disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedStateId"
vn-http-click="$ctrl.importToNewRefundTicket()"p vn-http-click="$ctrl.importToNewRefundTicket()"
translate-attr="{title: 'Imports claim details'}"> translate-attr="{title: 'Imports claim details'}">
</vn-button> </vn-button>
<vn-button
label="Import ticket"
disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedStateId"
ng-click="$ctrl.showLastTickets($event)"
translate-attr="{title: 'Imports ticket lines'}">
</vn-button>
<vn-range <vn-range
label="Responsability" label="Responsability"
min-label="Company" min-label="Company"
@ -121,38 +115,6 @@
</vn-button> </vn-button>
</vn-button-bar> </vn-button-bar>
</vn-card> </vn-card>
<vn-crud-model
vn-id="lastTicketsModel"
url="Tickets"
limit="20"
data="lastTickets" auto-load="false">
</vn-crud-model>
<!-- Transfer Popover -->
<vn-popover class="lastTicketsPopover" vn-id="lastTicketsPopover">
<div class="ticketList vn-pa-md">
<vn-table model="lastTicketsModel" auto-load="false">
<vn-thead>
<vn-tr>
<vn-th field="id" number>ID</vn-th>
<vn-th field="shipped" default-order="DESC">F. envio</vn-th>
<vn-th>Agencia</vn-th>
<vn-th>Almacen</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr
class="clickable"
ng-repeat="ticket in lastTickets"
ng-click="$ctrl.importTicketLines(ticket.id)">
<vn-td number>{{::ticket.id}}</vn-td>
<vn-td>{{::ticket.shipped | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td>{{::ticket.agencyMode.name}}</vn-td>
<vn-td>{{::ticket.warehouse.name}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</div>
</vn-popover>
<vn-item-descriptor-popover <vn-item-descriptor-popover
vn-id="item-descriptor" vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk"> warehouse-fk="$ctrl.vnConfig.warehouseFk">

View File

@ -60,36 +60,6 @@ export default class Controller extends Section {
}); });
} }
showLastTickets(event) {
let pastWeek = new Date();
pastWeek.setDate(-7);
let filter = {
include: [
{relation: 'agencyMode', fields: ['name']},
{relation: 'warehouse', fields: ['name']}
],
where: {
created: {gt: pastWeek},
clientFk: this.claim.clientFk
}
};
this.$.lastTicketsModel.filter = filter;
this.$.lastTicketsModel.refresh();
this.$.lastTicketsPopover.show(event);
}
importTicketLines(ticketFk) {
let data = {claimFk: this.$params.id, ticketFk: ticketFk};
let query = `ClaimEnds/importTicketSales`;
this.$http.post(query, data).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
this.$.lastTicketsPopover.hide();
this.$.model.refresh();
});
}
regularize() { regularize() {
const query = `Claims/${this.$params.id}/regularizeClaim`; const query = `Claims/${this.$params.id}/regularizeClaim`;
return this.$http.post(query).then(() => { return this.$http.post(query).then(() => {

View File

@ -67,35 +67,6 @@ describe('claim', () => {
}); });
}); });
describe('showLastTickets()', () => {
it('should get a list of tickets and call lastTicketsPopover show() method', () => {
jest.spyOn(controller.$.lastTicketsModel, 'refresh');
jest.spyOn(controller.$.lastTicketsPopover, 'show');
controller.showLastTickets({});
expect(controller.$.lastTicketsModel.refresh).toHaveBeenCalled();
expect(controller.$.lastTicketsPopover.show).toHaveBeenCalled();
});
});
describe('importTicketLines()', () => {
it('should perform a post query and add lines from an existent ticket', () => {
jest.spyOn(controller.$.model, 'refresh');
jest.spyOn(controller.vnApp, 'showSuccess');
jest.spyOn(controller.$.lastTicketsPopover, 'hide');
let data = {claimFk: 1, ticketFk: 1};
$httpBackend.expect('POST', `ClaimEnds/importTicketSales`, data).respond({});
controller.importTicketLines(1);
$httpBackend.flush();
expect(controller.$.model.refresh).toHaveBeenCalledWith();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.$.lastTicketsPopover.hide).toHaveBeenCalledWith();
});
});
describe('regularize()', () => { describe('regularize()', () => {
it('should perform a post query and reload the claim card', () => { it('should perform a post query and reload the claim card', () => {
jest.spyOn(controller.card, 'reload'); jest.spyOn(controller.card, 'reload');

View File

@ -3,8 +3,6 @@ Action: Actuaciones
Total claimed: Total Reclamado Total claimed: Total Reclamado
Import claim: Importar reclamacion Import claim: Importar reclamacion
Imports claim details: Importa detalles de la reclamacion Imports claim details: Importa detalles de la reclamacion
Import ticket: Importar ticket
Imports ticket lines: Importa las lineas de un ticket
Regularize: Regularizar Regularize: Regularizar
Do you want to insert greuges?: Desea insertar greuges? Do you want to insert greuges?: Desea insertar greuges?
Insert greuges on client card: Insertar greuges en la ficha del cliente Insert greuges on client card: Insertar greuges en la ficha del cliente

View File

@ -1,59 +1,74 @@
<vn-auto-search <vn-auto-search
model="model"> model="model">
</vn-auto-search> </vn-auto-search>
<vn-data-viewer <vn-card>
model="model" <smart-table
class="vn-w-lg"> model="model"
<vn-card> options="$ctrl.smartTableOptions"
<vn-table model="model"> expr-builder="$ctrl.exprBuilder(param, value)">
<vn-thead> <slot-table>
<vn-tr> <table>
<vn-th field="id" number>Id</vn-th> <thead>
<vn-th field="clientFk">Client</vn-th> <tr>
<vn-th field="created" center shrink-date>Created</vn-th> <th field="id" shrink>
<vn-th field="workerFk">Worker</vn-th> <span translate>Id</span>
<vn-th field="claimStateFk">State</vn-th> </th>
<vn-th></vn-th> <th field="clientFk">
</vn-tr> <span translate>Client</span>
</vn-thead> </th>
<vn-tbody> <th field="created" center shrink-date>
<a <span translate>Created</span>
ng-repeat="claim in model.data" </th>
class="{{::$ctrl.compareDate(ticket.shipped)}} clickable vn-tr search-result" <th field="salesPersonFk">
ui-sref="claim.card.summary({id: claim.id})"> <span translate>Worker</span>
<vn-td number>{{::claim.id}}</vn-td> </th>
<vn-td expand> <th field="claimStateFk">
<span <span translate>State</span>
vn-click-stop="clientDescriptor.show($event, claim.clientFk)" </th>
class="link"> <th></th>
{{::claim.name}} </tr>
</span> </thead>
</vn-td> <tbody>
<vn-td center shrink-date>{{::claim.created | date:'dd/MM/yyyy'}}</vn-td> <tr
<vn-td expand> ng-repeat="claim in model.data"
<span vn-anchor="::{
vn-click-stop="workerDescriptor.show($event, claim.workerFk)" state: 'claim.card.summary',
class="link" > params: {id: claim.id}
{{::claim.userName}} }">
</span> <td>{{::claim.id}}</td>
</vn-td> <td>
<vn-td> <span
<span class="chip {{::$ctrl.stateColor(claim)}}"> vn-click-stop="clientDescriptor.show($event, claim.clientFk)"
{{::claim.description}} class="link">
</span> {{::claim.socialName}}
</vn-td> </span>
<vn-td shrink> </td>
<vn-icon-button <td center shrink-date>{{::claim.created | date:'dd/MM/yyyy'}}</td>
vn-click-stop="$ctrl.preview(claim)" <td>
vn-tooltip="Preview" <span
icon="preview"> vn-click-stop="workerDescriptor.show($event, claim.workerFk)"
</vn-icon-button> class="link" >
</vn-td> {{::claim.workerName}}
</a> </span>
</vn-tbody> </td>
</vn-table> <td>
</vn-card> <span class="chip {{::$ctrl.stateColor(claim)}}">
</vn-data-viewer> {{::claim.description}}
</span>
</td>
<td shrink>
<vn-icon-button
vn-click-stop="$ctrl.preview(claim)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<vn-client-descriptor-popover <vn-client-descriptor-popover
vn-id="clientDescriptor"> vn-id="clientDescriptor">
</vn-client-descriptor-popover> </vn-client-descriptor-popover>
@ -62,6 +77,7 @@
</vn-worker-descriptor-popover> </vn-worker-descriptor-popover>
<vn-popup vn-id="summary"> <vn-popup vn-id="summary">
<vn-claim-summary <vn-claim-summary
claim="$ctrl.claimSelected"> claim="$ctrl.claimSelected"
parent-reload="$ctrl.reload()">
</vn-claim-summary> </vn-claim-summary>
</vn-popup> </vn-popup>

View File

@ -1,7 +1,69 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
export default class Controller extends Section { class Controller extends Section {
constructor($element, $) {
super($element, $);
this.smartTableOptions = {
activeButtons: {
search: true
},
columns: [
{
field: 'clientFk',
autocomplete: {
url: 'Clients',
showField: 'socialName',
valueField: 'socialName'
}
},
{
field: 'workerFk',
autocomplete: {
url: 'Workers/activeWithInheritedRole',
where: `{role: 'salesPerson'}`,
searchFunction: '{firstName: $search}',
showField: 'name',
valueField: 'id',
}
},
{
field: 'claimStateFk',
autocomplete: {
url: 'ClaimStates',
showField: 'description',
valueField: 'id',
}
},
{
field: 'created',
searchable: false
}
]
};
}
exprBuilder(param, value) {
switch (param) {
case 'clientFk':
return {['cl.socialName']: value};
case 'id':
case 'claimStateFk':
case 'priority':
return {[`cl.${param}`]: value};
case 'salesPersonFk':
case 'attenderFk':
return {'cl.workerFk': value};
case 'created':
value.setHours(0, 0, 0, 0);
to = new Date(value);
to.setHours(23, 59, 59, 999);
return {'cl.created': {between: [value, to]}};
}
}
stateColor(claim) { stateColor(claim) {
switch (claim.description) { switch (claim.description) {
case 'Pendiente': case 'Pendiente':
@ -17,6 +79,10 @@ export default class Controller extends Section {
this.claimSelected = claim; this.claimSelected = claim;
this.$.summary.show(); this.$.summary.show();
} }
reload() {
this.$.model.refresh();
}
} }
ngModule.vnComponent('vnClaimIndex', { ngModule.vnComponent('vnClaimIndex', {

View File

@ -1,9 +1,7 @@
import ngModule from '../module'; import ngModule from '../module';
import ModuleMain from 'salix/components/module-main'; import ModuleMain from 'salix/components/module-main';
export default class Claim extends ModuleMain {}
ngModule.vnComponent('vnClaim', { ngModule.vnComponent('vnClaim', {
controller: Claim, controller: ModuleMain,
template: require('./index.html') template: require('./index.html')
}); });

View File

@ -12,6 +12,15 @@
<vn-icon-button icon="launch"></vn-icon-button> <vn-icon-button icon="launch"></vn-icon-button>
</a> </a>
<span>{{::$ctrl.summary.claim.id}} - {{::$ctrl.summary.claim.client.name}}</span> <span>{{::$ctrl.summary.claim.id}} - {{::$ctrl.summary.claim.client.name}}</span>
<vn-button-menu
disabled="!$ctrl.summary.isEditable"
class="message"
label="Change state"
value-field="id"
show-field="description"
url="claimStates"
on-change="$ctrl.changeState(value)">
</vn-button-menu>
</h5> </h5>
<vn-horizontal> <vn-horizontal>
<vn-one> <vn-one>
@ -32,28 +41,14 @@
value="{{$ctrl.summary.claim.worker.user.nickname}}"> value="{{$ctrl.summary.claim.worker.user.nickname}}">
</vn-label-value> </vn-label-value>
</vn-one> </vn-one>
<vn-one> <vn-two>
<vn-textarea <vn-textarea
vn-three vn-three
disabled="true" disabled="true"
label="Observation" label="Observation"
ng-model="$ctrl.summary.claim.observation"> ng-model="$ctrl.summary.claim.observation">
</vn-textarea> </vn-textarea>
</vn-one> </vn-two>
<vn-one>
<vn-range
vn-one
disabled="true"
label="Responsability"
min-label="Company"
max-label="Sales/Client"
ng-model="$ctrl.summary.claim.responsibility"
max="5"
min="1"
step="1"
vn-acl="claimManager">
</vn-range>
</vn-one>
<vn-auto> <vn-auto>
<h4 ng-show="$ctrl.isSalesPerson"> <h4 ng-show="$ctrl.isSalesPerson">
<a <a
@ -171,6 +166,22 @@
ng-show="!$ctrl.isClaimManager"> ng-show="!$ctrl.isClaimManager">
Action Action
</h4> </h4>
<vn-horizontal>
<vn-one>
<vn-range
vn-one
disabled="true"
label="Responsability"
min-label="Company"
max-label="Sales/Client"
ng-model="$ctrl.summary.claim.responsibility"
max="5"
min="1"
step="1"
vn-acl="claimManager">
</vn-range>
</vn-one>
</vn-horizontal>
<vn-data-viewer data="::$ctrl.summary.actions"> <vn-data-viewer data="::$ctrl.summary.actions">
<vn-table> <vn-table>
<vn-thead> <vn-thead>

View File

@ -10,7 +10,25 @@ class Controller extends Summary {
$onChanges() { $onChanges() {
if (this.claim && this.claim.id) if (this.claim && this.claim.id)
this.getSummary(); this.loadData();
}
loadData() {
return this.$http.get(`Claims/${this.claim.id}/getSummary`).then(res => {
if (res && res.data)
this.summary = res.data;
});
}
reload() {
this.loadData()
.then(() => {
if (this.card)
this.card.reload();
if (this.parentReload)
this.parentReload();
});
} }
get isSalesPerson() { get isSalesPerson() {
@ -29,8 +47,10 @@ class Controller extends Summary {
this._claim = value; this._claim = value;
// Get DMS on summary load // Get DMS on summary load
if (value) if (value) {
this.$.$applyAsync(() => this.loadDms()); this.$.$applyAsync(() => this.loadDms());
this.loadData();
}
} }
loadDms() { loadDms() {
@ -40,15 +60,24 @@ class Controller extends Summary {
this.$.model.refresh(); this.$.model.refresh();
} }
getSummary() {
this.$http.get(`Claims/${this.claim.id}/getSummary`).then(response => {
this.summary = response.data;
});
}
getImagePath(dmsId) { getImagePath(dmsId) {
return this.vnFile.getPath(`/api/dms/${dmsId}/downloadFile`); return this.vnFile.getPath(`/api/dms/${dmsId}/downloadFile`);
} }
changeState(value) {
const params = {
id: this.claim.id,
claimStateFk: value
};
this.$http.patch(`Claims/updateClaim/${this.claim.id}`, params)
.then(() => {
this.reload();
})
.then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
} }
Controller.$inject = ['$element', '$scope', 'vnFile']; Controller.$inject = ['$element', '$scope', 'vnFile'];
@ -57,6 +86,11 @@ ngModule.vnComponent('vnClaimSummary', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller,
bindings: { bindings: {
claim: '<' claim: '<',
model: '<?',
parentReload: '&'
},
require: {
card: '?^vnClaimCard'
} }
}); });

View File

@ -18,23 +18,37 @@ describe('Claim', () => {
controller.$.model = crudModel; controller.$.model = crudModel;
})); }));
describe('getSummary()', () => { describe('loadData()', () => {
it('should perform a query to set summary', () => { it('should perform a query to set summary', () => {
$httpBackend.expect('GET', `Claims/1/getSummary`).respond(200, 24); $httpBackend.when('GET', `Claims/1/getSummary`).respond(200, 24);
controller.getSummary(); controller.loadData();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.summary).toEqual(24); expect(controller.summary).toEqual(24);
}); });
}); });
describe('changeState()', () => {
it('should make an HTTP post query, then call the showSuccess()', () => {
jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis();
const expectedParams = {id: 1, claimStateFk: 1};
$httpBackend.when('GET', `Claims/1/getSummary`).respond(200, 24);
$httpBackend.expect('PATCH', `Claims/updateClaim/1`, expectedParams).respond(200);
controller.changeState(1);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('$onChanges()', () => { describe('$onChanges()', () => {
it('should call getSummary when item.id is defined', () => { it('should call loadData when $onChanges is called', () => {
jest.spyOn(controller, 'getSummary'); jest.spyOn(controller, 'loadData');
controller.$onChanges(); controller.$onChanges();
expect(controller.getSummary).toHaveBeenCalledWith(); expect(controller.loadData).toHaveBeenCalledWith();
}); });
}); });
}); });

View File

@ -7,4 +7,7 @@ vn-claim-summary {
.photo .image { .photo .image {
border-radius: 3px; border-radius: 3px;
} }
vn-textarea *{
height: 80px;
}
} }

View File

@ -56,18 +56,18 @@ module.exports = Self => {
FROM ( FROM (
SELECT SELECT
DISTINCT c.id clientFk, DISTINCT c.id clientFk,
c.name clientName, c.socialName clientName,
c.salesPersonFk, c.salesPersonFk,
u.name salesPersonName, u.nickname salesPersonName,
d.amount, d.amount,
co.created, co.created,
CONCAT(DATE(co.created), ' ', co.text) observation, co.text observation,
uw.id workerFk, uw.id workerFk,
uw.name workerName, uw.nickname workerName,
c.creditInsurance, c.creditInsurance,
d.defaulterSinced d.defaulterSinced
FROM vn.defaulter d FROM vn.defaulter d
JOIN vn.client c ON c.id = d.clientFk JOIN vn.client c ON c.id = d.clientFk
LEFT JOIN vn.clientObservation co ON co.clientFk = c.id LEFT JOIN vn.clientObservation co ON co.clientFk = c.id
LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN account.user u ON u.id = c.salesPersonFk
LEFT JOIN account.user uw ON uw.id = co.workerFk LEFT JOIN account.user uw ON uw.id = co.workerFk

View File

@ -47,12 +47,12 @@ describe('defaulter filter()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'bruce'}}; const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'spider'}};
const result = await models.Defaulter.filter(ctx, null, options); const result = await models.Defaulter.filter(ctx, null, options);
const firstRow = result[0]; const firstRow = result[0];
expect(firstRow.clientName).toEqual('Bruce Wayne'); expect(firstRow.clientName).toEqual('Spider man');
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -15,17 +15,18 @@
model="model"> model="model">
</vn-searchbar> </vn-searchbar>
</vn-portal> </vn-portal>
<vn-data-viewer <vn-card>
model="model" <smart-table
class="vn-w-xl"> model="model"
<vn-card> options="$ctrl.smartTableOptions"
<vn-tool-bar> expr-builder="$ctrl.exprBuilder(param, value)">
<div class="vn-pa-md"> <slot-actions>
<div>
<div class="totalBox" style="text-align: center;"> <div class="totalBox" style="text-align: center;">
<h6 translate>Total</h6> <h6 translate>Total</h6>
<vn-label-value <vn-label-value
label="Balance due" label="Balance due"
value="{{$ctrl.balanceDueTotal}}"> value="{{$ctrl.balanceDueTotal | currency: 'EUR': 2}}">
</vn-label-value> </vn-label-value>
</div> </div>
</div> </div>
@ -38,90 +39,109 @@
icon="icon-notes"> icon="icon-notes">
</vn-button> </vn-button>
</div> </div>
</vn-tool-bar> </slot-actions>
<vn-table model="model"> <slot-table>
<vn-thead> <table>
<vn-tr> <thead>
<vn-th shrink> <tr>
<vn-multi-check <th shrink>
model="model"> <vn-multi-check
</vn-multi-check> model="model">
</vn-th> </vn-multi-check>
<vn-th field="clientName">Client</vn-th> </th>
<vn-th field="salesPersonFk">Comercial</vn-th> <th field="clientName">
<vn-th <span translate>Client</span>
field="amount" </th>
vn-tooltip="Balance due" <th field="salesPersonFk">
number> <span translate>Comercial</span>
Balance D. </th>
</vn-th> <th
<vn-th field="amount"
vn-tooltip="Worker who made the last observation" vn-tooltip="Balance due">
shrink> <span translate>Balance D.</span>
Author </th>
</vn-th> <th
<vn-th expand>Last observation</vn-th> field="workerFk"
<vn-th vn-tooltip="Worker who made the last observation">
vn-tooltip="Credit insurance" <span translate>Author</span>
number> </th>
Credit I. <th field="observation" expand>
</vn-th> <span translate>Last observation</span>
<vn-th shrink-datetime>From</vn-th> </th>
</vn-tr> <th
</vn-thead> vn-tooltip="Last observation date"
<vn-tbody> field="created"
<vn-tr ng-repeat="defaulter in defaulters"> shrink-datetime>
<vn-td shrink> <span translate>Last observation D.</span>
<vn-check </th>
ng-model="defaulter.checked" <th
vn-click-stop> vn-tooltip="Credit insurance"
</vn-check> field="creditInsurance" >
</vn-td> <span translate>Credit I.</span>
<vn-td> </th>
<span <th field="defaulterSinced">
vn-click-stop="clientDescriptor.show($event, defaulter.clientFk)" <span translate>From</span>
title ="{{::defaulter.clientName}}" </th>
class="link"> </tr>
{{::defaulter.clientName}} </thead>
</span> <tbody>
</vn-td> <tr ng-repeat="defaulter in defaulters">
<vn-td> <td shrink>
<span <vn-check
title="{{::defaulter.salesPersonName}}" ng-model="defaulter.checked"
vn-click-stop="workerDescriptor.show($event, defaulter.salesPersonFk)" vn-click-stop>
class="link" > </vn-check>
{{::defaulter.salesPersonName | dashIfEmpty}} </td>
</span> <td title="{{::defaulter.clientName}}">
</vn-td> <span
<vn-td number>{{::defaulter.amount}}</vn-td> vn-click-stop="clientDescriptor.show($event, defaulter.clientFk)"
<vn-td shrink> title ="{{::defaulter.clientName}}"
<span class="link">
title="{{::defaulter.workerName}}" {{::defaulter.clientName}}
vn-click-stop="workerDescriptor.show($event, defaulter.workerFk)" </span>
class="link" > </td>
{{::defaulter.workerName | dashIfEmpty}} <td>
</span> <span
</vn-td> title="{{::defaulter.salesPersonName}}"
<vn-td expand> vn-click-stop="workerDescriptor.show($event, defaulter.salesPersonFk)"
<vn-textarea class="link">
vn-three {{::defaulter.salesPersonName | dashIfEmpty}}
disabled="true" </span>
label="Observation" </td>
ng-model="defaulter.observation"> <td>{{::defaulter.amount | currency: 'EUR': 2}}</td>
</vn-textarea> <td>
</vn-td> <span
<vn-td number>{{::defaulter.creditInsurance}}</vn-td> title="{{::defaulter.workerName}}"
<vn-td shrink-datetime>{{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}}</vn-td> vn-click-stop="workerDescriptor.show($event, defaulter.workerFk)"
</vn-tr> class="link">
</vn-tbody> {{::defaulter.workerName | dashIfEmpty}}
</vn-table> </span>
</vn-card> </td>
</vn-data-viewer> <td expand>
<vn-textarea
vn-three
disabled="true"
ng-model="defaulter.observation">
</vn-textarea>
</td>
<td shrink-datetime>
<span class="chip {{::$ctrl.chipColor(defaulter.created)}}">
{{::defaulter.created | date: 'dd/MM/yyyy'}}
</span>
</td>
<td>{{::defaulter.creditInsurance | currency: 'EUR': 2}}</td>
<td>{{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}}</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<vn-client-descriptor-popover <vn-client-descriptor-popover
vn-id="clientDescriptor"> vn-id="client-descriptor">
</vn-client-descriptor-popover> </vn-client-descriptor-popover>
<vn-worker-descriptor-popover <vn-worker-descriptor-popover
vn-id="workerDescriptor"> vn-id="worker-descriptor">
</vn-worker-descriptor-popover> </vn-worker-descriptor-popover>
<vn-popup vn-id="dialog-summary-client"> <vn-popup vn-id="dialog-summary-client">
<vn-client-summary <vn-client-summary
@ -129,37 +149,6 @@
</vn-client-summary> </vn-client-summary>
</vn-popup> </vn-popup>
<!--Context menu-->
<vn-contextmenu vn-id="contextmenu" targets="['vn-data-viewer']" model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-menu>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.filterBySelection()">
Filter by selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.excludeSelection()">
Exclude selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.removeFilter()">
Remove filter
</vn-item>
<vn-item translate
ng-click="contextmenu.removeAllFilters()">
Remove all filters
</vn-item>
<vn-item translate
ng-if="contextmenu.isActionAllowed()"
ng-click="contextmenu.copyValue()">
Copy value
</vn-item>
</slot-menu>
</vn-contextmenu>
<!-- Dialog of add notes button --> <!-- Dialog of add notes button -->
<vn-dialog <vn-dialog
vn-id="notesDialog" vn-id="notesDialog"

View File

@ -6,17 +6,61 @@ export default class Controller extends Section {
constructor($element, $) { constructor($element, $) {
super($element, $); super($element, $);
this.defaulter = {}; this.defaulter = {};
this.smartTableOptions = {
activeButtons: {
search: true
},
columns: [
{
field: 'clientName',
autocomplete: {
url: 'Clients',
showField: 'socialName',
valueField: 'socialName'
}
},
{
field: 'salesPersonFk',
autocomplete: {
url: 'Workers/activeWithInheritedRole',
where: `{role: 'salesPerson'}`,
searchFunction: '{firstName: $search}',
showField: 'nickname',
valueField: 'id',
}
},
{
field: 'workerFk',
autocomplete: {
url: 'Workers/activeWithInheritedRole',
searchFunction: '{firstName: $search}',
showField: 'nickname',
valueField: 'id',
}
},
{
field: 'observation',
searchable: false
},
{
field: 'created',
searchable: false
},
{
field: 'defaulterSinced',
searchable: false
}
]
};
} }
get balanceDueTotal() { get balanceDueTotal() {
let balanceDueTotal = 0; let balanceDueTotal = 0;
const defaulters = this.$.model.data || [];
if (this.checked.length > 0) { for (let defaulter of defaulters)
for (let defaulter of this.checked) balanceDueTotal += defaulter.amount;
balanceDueTotal += defaulter.amount;
return balanceDueTotal;
}
return balanceDueTotal; return balanceDueTotal;
} }
@ -32,6 +76,22 @@ export default class Controller extends Section {
return checkedLines; return checkedLines;
} }
chipColor(date) {
const day = 24 * 60 * 60 * 1000;
const today = new Date();
today.setHours(0, 0, 0, 0);
const observationShipped = new Date(date);
observationShipped.setHours(0, 0, 0, 0);
const difference = today - observationShipped;
if (difference > (day * 20))
return 'alert';
if (difference > (day * 10))
return 'warning';
}
onResponse() { onResponse() {
if (!this.defaulter.observation) if (!this.defaulter.observation)
throw new UserError(`The message can't be empty`); throw new UserError(`The message can't be empty`);
@ -52,14 +112,17 @@ export default class Controller extends Section {
exprBuilder(param, value) { exprBuilder(param, value) {
switch (param) { switch (param) {
case 'creditInsurance':
case 'amount':
case 'clientName': case 'clientName':
case 'workerFk':
case 'salesPersonFk': case 'salesPersonFk':
return {[`d.${param}`]: value}; return {[`d.${param}`]: value};
} }
} }
} }
ngModule.vnComponent('vnClientDefaulterIndex', { ngModule.vnComponent('vnClientDefaulter', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller controller: Controller
}); });

View File

@ -2,7 +2,7 @@ import './index';
import crudModel from 'core/mocks/crud-model'; import crudModel from 'core/mocks/crud-model';
describe('client defaulter', () => { describe('client defaulter', () => {
describe('Component vnClientDefaulterIndex', () => { describe('Component vnClientDefaulter', () => {
let controller; let controller;
let $httpBackend; let $httpBackend;
@ -11,7 +11,7 @@ describe('client defaulter', () => {
beforeEach(inject(($componentController, _$httpBackend_) => { beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
const $element = angular.element('<vn-client-defaulter></vn-client-defaulter>'); const $element = angular.element('<vn-client-defaulter></vn-client-defaulter>');
controller = $componentController('vnClientDefaulterIndex', {$element}); controller = $componentController('vnClientDefaulter', {$element});
controller.$.model = crudModel; controller.$.model = crudModel;
controller.$.model.data = [ controller.$.model.data = [
{clientFk: 1101, amount: 125}, {clientFk: 1101, amount: 125},
@ -39,11 +39,7 @@ describe('client defaulter', () => {
describe('balanceDueTotal() getter', () => { describe('balanceDueTotal() getter', () => {
it('should return balance due total', () => { it('should return balance due total', () => {
const data = controller.$.model.data; const data = controller.$.model.data;
data[1].checked = true; const expectedAmount = data[0].amount + data[1].amount + data[2].amount;
data[2].checked = true;
const checkedRows = controller.checked;
const expectedAmount = checkedRows[0].amount + checkedRows[1].amount;
const result = controller.balanceDueTotal; const result = controller.balanceDueTotal;
@ -51,6 +47,31 @@ describe('client defaulter', () => {
}); });
}); });
describe('chipColor()', () => {
it('should return undefined when the date is the present', () => {
let today = new Date();
let result = controller.chipColor(today);
expect(result).toEqual(undefined);
});
it('should return warning when the date is 10 days in the past', () => {
let pastDate = new Date();
pastDate = pastDate.setDate(pastDate.getDate() - 11);
let result = controller.chipColor(pastDate);
expect(result).toEqual('warning');
});
it('should return alert when the date is 20 days in the past', () => {
let pastDate = new Date();
pastDate = pastDate.setDate(pastDate.getDate() - 21);
let result = controller.chipColor(pastDate);
expect(result).toEqual('alert');
});
});
describe('onResponse()', () => { describe('onResponse()', () => {
it('should return error for empty message', () => { it('should return error for empty message', () => {
let error; let error;

View File

@ -1,7 +1,9 @@
Last observation: Última observación
Add observation: Añadir observación Add observation: Añadir observación
Search client: Buscar clientes
Add observation to all selected clients: Añadir observación a {{total}} cliente(s) seleccionado(s) Add observation to all selected clients: Añadir observación a {{total}} cliente(s) seleccionado(s)
Credit I.: Crédito A.
Balance D.: Saldo V. Balance D.: Saldo V.
Credit I.: Crédito A.
Last observation: Última observación
Last observation D.: Fecha última O.
Last observation date: Fecha última observación
Search client: Buscar clientes
Worker who made the last observation: Trabajador que ha realizado la última observación Worker who made the last observation: Trabajador que ha realizado la última observación

View File

@ -8,7 +8,7 @@
"main": [ "main": [
{"state": "client.index", "icon": "person"}, {"state": "client.index", "icon": "person"},
{"state": "client.notification", "icon": "campaign"}, {"state": "client.notification", "icon": "campaign"},
{"state": "client.defaulter.index", "icon": "icon-defaulter"} {"state": "client.defaulter", "icon": "icon-defaulter"}
], ],
"card": [ "card": [
{"state": "client.card.basicData", "icon": "settings"}, {"state": "client.card.basicData", "icon": "settings"},
@ -366,13 +366,7 @@
{ {
"url": "/defaulter", "url": "/defaulter",
"state": "client.defaulter", "state": "client.defaulter",
"component": "ui-view", "component": "vn-client-defaulter",
"description": "Defaulter"
},
{
"url": "/index?q",
"state": "client.defaulter.index",
"component": "vn-client-defaulter-index",
"description": "Defaulter" "description": "Defaulter"
}, },
{ {

View File

@ -4,14 +4,24 @@ module.exports = Self => {
Self.remoteMethodCtx('absences', { Self.remoteMethodCtx('absences', {
description: 'Returns an array of absences from an specified contract', description: 'Returns an array of absences from an specified contract',
accepts: [{ accepts: [{
arg: 'businessFk', arg: 'workerFk',
type: 'number', type: 'number',
required: true, required: true,
}, },
{
arg: 'businessFk',
type: 'number',
required: false,
},
{ {
arg: 'year', arg: 'year',
type: 'date', type: 'date',
required: true, required: true,
},
{
arg: 'all',
type: 'boolean',
required: false,
}], }],
returns: [{ returns: [{
arg: 'absences', arg: 'absences',
@ -27,7 +37,7 @@ module.exports = Self => {
} }
}); });
Self.absences = async(ctx, businessFk, year, options) => { Self.absences = async(ctx, workerFk, businessFk, year, options) => {
const models = Self.app.models; const models = Self.app.models;
const started = new Date(); const started = new Date();
@ -45,7 +55,17 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const contract = await models.WorkerLabour.findOne({ let condition = {
and: [
{workerFk: workerFk},
{businessFk: businessFk}
]
};
if (businessFk)
condition.and.push({workerFk: workerFk});
const contracts = await models.WorkerLabour.find({
include: [{ include: [{
relation: 'holidays', relation: 'holidays',
scope: { scope: {
@ -82,31 +102,32 @@ module.exports = Self => {
} }
} }
}], }],
where: {businessFk} where: condition
}, myOptions); }, myOptions);
if (!contract) return; if (!contracts) return;
const isSubordinate = await models.Worker.isSubordinate(ctx, contract.workerFk, myOptions); const isSubordinate = await models.Worker.isSubordinate(ctx, workerFk, myOptions);
if (!isSubordinate) if (!isSubordinate)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
const absences = []; const absences = [];
for (let absence of contract.absences()) {
absence.dated = new Date(absence.dated);
absence.dated.setHours(0, 0, 0, 0);
absences.push(absence);
}
// Workcenter holidays
const holidays = []; const holidays = [];
const holidayList = contract.workCenter().holidays();
for (let day of holidayList) {
day.dated = new Date(day.dated);
day.dated.setHours(0, 0, 0, 0);
holidays.push(day); for (let contract of contracts) {
for (let absence of contract.absences()) {
absence.dated = new Date(absence.dated);
absence.dated.setHours(0, 0, 0, 0);
absences.push(absence);
}
for (let day of contract.workCenter().holidays()) {
day.dated = new Date(day.dated);
day.dated.setHours(0, 0, 0, 0);
holidays.push(day);
}
} }
return [absences, holidays]; return [absences, holidays];

View File

@ -3,12 +3,13 @@ const app = require('vn-loopback/server/server');
describe('Worker absences()', () => { describe('Worker absences()', () => {
it('should get the absence calendar for a full year contract', async() => { it('should get the absence calendar for a full year contract', async() => {
const ctx = {req: {accessToken: {userId: 1106}}}; const ctx = {req: {accessToken: {userId: 1106}}};
const workerId = 1106;
const businessId = 1106; const businessId = 1106;
const now = new Date(); const now = new Date();
const year = now.getFullYear(); const year = now.getFullYear();
const [absences] = await app.models.Calendar.absences(ctx, businessId, year); const [absences] = await app.models.Calendar.absences(ctx, workerId, businessId, year);
const firstType = absences[0].absenceType().name; const firstType = absences[0].absenceType().name;
const sixthType = absences[5].absenceType().name; const sixthType = absences[5].absenceType().name;
@ -35,7 +36,7 @@ describe('Worker absences()', () => {
`UPDATE postgresql.business SET date_end = ? WHERE business_id = ?`, `UPDATE postgresql.business SET date_end = ? WHERE business_id = ?`,
[null, worker.businessFk], options); [null, worker.businessFk], options);
const [absences] = await app.models.Calendar.absences(ctx, businessId, year, options); const [absences] = await app.models.Calendar.absences(ctx, worker.id, businessId, year, options);
let firstType = absences[0].absenceType().name; let firstType = absences[0].absenceType().name;
let sixthType = absences[5].absenceType().name; let sixthType = absences[5].absenceType().name;
@ -51,6 +52,8 @@ describe('Worker absences()', () => {
it('should give the same holidays as worked days since the holidays amount matches the amount of days in a year', async() => { it('should give the same holidays as worked days since the holidays amount matches the amount of days in a year', async() => {
const businessId = 1106; const businessId = 1106;
const workerId = 1106;
const userId = 1106; const userId = 1106;
const today = new Date(); const today = new Date();
@ -101,7 +104,7 @@ describe('Worker absences()', () => {
const ctx = {req: {accessToken: {userId: userId}}}; const ctx = {req: {accessToken: {userId: userId}}};
const [absences] = await app.models.Calendar.absences(ctx, businessId, currentYear); const [absences] = await app.models.Calendar.absences(ctx, workerId, businessId, currentYear);
const firstType = absences[0].absenceType().name; const firstType = absences[0].absenceType().name;
const sixthType = absences[5].absenceType().name; const sixthType = absences[5].absenceType().name;

View File

@ -282,6 +282,7 @@ class Controller extends Section {
refresh() { refresh() {
const params = { const params = {
workerFk: this.$params.id,
businessFk: this.businessId, businessFk: this.businessId,
year: this.year year: this.year
}; };

View File

@ -328,7 +328,7 @@ describe('Worker', () => {
jest.spyOn(controller, 'onData').mockReturnThis(); jest.spyOn(controller, 'onData').mockReturnThis();
const expecteResponse = [{id: 1}]; const expecteResponse = [{id: 1}];
const expectedParams = {year: year}; const expectedParams = {workerFk: controller.worker.id, year: year};
const serializedParams = $httpParamSerializer(expectedParams); const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expect('GET', `Calendars/absences?${serializedParams}`).respond(200, expecteResponse); $httpBackend.expect('GET', `Calendars/absences?${serializedParams}`).respond(200, expecteResponse);
controller.refresh(); controller.refresh();

View File

@ -32,11 +32,6 @@ class Controller extends Section {
set worker(value) { set worker(value) {
this._worker = value; this._worker = value;
if (value) {
this.getActiveContract()
.then(() => this.getAbsences());
}
} }
/** /**
@ -96,14 +91,6 @@ class Controller extends Section {
} }
} }
getActiveContract() {
return this.$http.get(`Workers/${this.worker.id}/activeContract`)
.then(res => {
if (res.data)
this.businessId = res.data.businessFk;
});
}
fetchHours() { fetchHours() {
const params = {workerFk: this.$params.id}; const params = {workerFk: this.$params.id};
const filter = { const filter = {
@ -123,11 +110,10 @@ class Controller extends Section {
} }
getAbsences() { getAbsences() {
if (!this.businessId) return;
const fullYear = this.started.getFullYear(); const fullYear = this.started.getFullYear();
let params = { let params = {
businessFk: this.businessId, workerFk: this.$params.id,
businessFk: null,
year: fullYear year: fullYear
}; };