Merge branch 'dev' into 5092-listado-ticketsSinFacturar
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
commit
23a15ee15c
|
@ -14,7 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
-
|
||||
|
||||
### Fixed
|
||||
-
|
||||
- (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo"
|
||||
- (General) Al pasar el ratón por encima del icono de "Borrar" en un campo, se hacía más grande afectando a la interfaz
|
||||
|
||||
## [2308.01] - 2023-03-09
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES('ClaimBeginning', 'isEditable', 'READ', 'ALLOW', 'ROLE', 'employee');
|
||||
|
||||
DELETE FROM `salix`.`ACL`
|
||||
WHERE model='Claim' AND property='isEditable';
|
||||
|
|
@ -1759,12 +1759,12 @@ INSERT INTO `vn`.`clientSample`(`id`, `clientFk`, `typeFk`, `created`, `workerFk
|
|||
INSERT INTO `vn`.`claimState`(`id`, `code`, `description`, `roleFk`, `priority`, `hasToNotify`)
|
||||
VALUES
|
||||
( 1, 'pending', 'Pendiente', 1, 1, 0),
|
||||
( 2, 'managed', 'Gestionado', 1, 5, 0),
|
||||
( 2, 'managed', 'Gestionado', 72, 5, 0),
|
||||
( 3, 'resolved', 'Resuelto', 72, 7, 0),
|
||||
( 4, 'canceled', 'Anulado', 72, 6, 1),
|
||||
( 5, 'incomplete', 'Incompleta', 72, 3, 1),
|
||||
( 6, 'mana', 'Mana', 1, 4, 0),
|
||||
( 7, 'lack', 'Faltas', 1, 2, 0);
|
||||
( 5, 'incomplete', 'Incompleta', 1, 3, 1),
|
||||
( 6, 'mana', 'Mana', 72, 4, 0),
|
||||
( 7, 'lack', 'Faltas', 72, 2, 0);
|
||||
|
||||
INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created`, `packages`, `rma`)
|
||||
VALUES
|
||||
|
@ -1828,7 +1828,12 @@ INSERT INTO vn.claimRma (`id`, `code`, `created`, `workerFk`)
|
|||
(4, '02676A049183', DEFAULT, 1107),
|
||||
(5, '01837B023653', DEFAULT, 1106);
|
||||
|
||||
|
||||
INSERT INTO `vn`.`claimLog` (`originFk`, userFk, `action`, changedModel, oldInstance, newInstance, changedModelId, `description`)
|
||||
VALUES
|
||||
(1, 18, 'update', 'Claim', '{"hasToPickUp":false}', '{"hasToPickUp":true}', 1, NULL),
|
||||
(1, 18, 'update', 'ClaimObservation', '{}', '{"claimFk":1,"text":"Waiting for customer"}', 1, NULL),
|
||||
(1, 18, 'insert', 'ClaimBeginning', '{}', '{"claimFk":1,"saleFk":1,"quantity":10}', 1, NULL),
|
||||
(1, 18, 'insert', 'ClaimDms', '{}', '{"claimFk":1,"dmsFk":1}', 1, NULL);
|
||||
|
||||
INSERT INTO `hedera`.`tpvMerchant`(`id`, `description`, `companyFk`, `bankFk`, `secretKey`)
|
||||
VALUES
|
||||
|
@ -2760,7 +2765,6 @@ INSERT INTO `vn`.`ticketLog` (`originFk`, userFk, `action`, changedModel, oldIns
|
|||
(7, 18, 'update', 'Sale', '{"price":3}', '{"price":5}', 1, NULL),
|
||||
(7, 18, 'update', NULL, NULL, NULL, NULL, "Cambio cantidad Melee weapon heavy shield 1x0.5m de '5' a '10'");
|
||||
|
||||
|
||||
INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`, `responseType`, `fromEmailId`, `replyTo`)
|
||||
VALUES
|
||||
(0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', '1,6', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all');
|
||||
|
|
|
@ -4,12 +4,17 @@ import getBrowser from '../../helpers/puppeteer';
|
|||
describe('Ticket Future path', () => {
|
||||
let browser;
|
||||
let page;
|
||||
let httpRequest;
|
||||
|
||||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('employee', 'ticket');
|
||||
await page.accessToSection('ticket.future');
|
||||
page.on('request', req => {
|
||||
if (req.url().includes(`Tickets/getTicketsFuture`))
|
||||
httpRequest = req.url();
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async() => {
|
||||
|
@ -42,114 +47,82 @@ describe('Ticket Future path', () => {
|
|||
it('should search with the required data', async() => {
|
||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||
await page.waitToClick(selectors.ticketFuture.submit);
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||
|
||||
expect(httpRequest).toBeDefined();
|
||||
});
|
||||
|
||||
it('should search with the origin IPT', async() => {
|
||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||
|
||||
await page.clearInput(selectors.ticketFuture.ipt);
|
||||
await page.clearInput(selectors.ticketFuture.futureIpt);
|
||||
await page.clearInput(selectors.ticketFuture.state);
|
||||
await page.clearInput(selectors.ticketFuture.futureState);
|
||||
|
||||
await page.autocompleteSearch(selectors.ticketFuture.ipt, 'Horizontal');
|
||||
await page.waitToClick(selectors.ticketFuture.submit);
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||
|
||||
expect(httpRequest).toContain('ipt=H');
|
||||
});
|
||||
|
||||
it('should search with the destination IPT', async() => {
|
||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||
|
||||
await page.clearInput(selectors.ticketFuture.ipt);
|
||||
await page.clearInput(selectors.ticketFuture.futureIpt);
|
||||
await page.clearInput(selectors.ticketFuture.state);
|
||||
await page.clearInput(selectors.ticketFuture.futureState);
|
||||
|
||||
await page.autocompleteSearch(selectors.ticketFuture.futureIpt, 'Horizontal');
|
||||
await page.waitToClick(selectors.ticketFuture.submit);
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||
|
||||
expect(httpRequest).toContain('futureIpt=H');
|
||||
});
|
||||
|
||||
it('should search with the origin grouped state', async() => {
|
||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||
|
||||
await page.clearInput(selectors.ticketFuture.ipt);
|
||||
await page.clearInput(selectors.ticketFuture.futureIpt);
|
||||
await page.clearInput(selectors.ticketFuture.state);
|
||||
await page.clearInput(selectors.ticketFuture.futureState);
|
||||
|
||||
await page.autocompleteSearch(selectors.ticketFuture.state, 'Free');
|
||||
await page.waitToClick(selectors.ticketFuture.submit);
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 3);
|
||||
|
||||
expect(httpRequest).toContain('state=FREE');
|
||||
});
|
||||
|
||||
it('should search with the destination grouped state', async() => {
|
||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||
|
||||
await page.clearInput(selectors.ticketFuture.ipt);
|
||||
await page.clearInput(selectors.ticketFuture.futureIpt);
|
||||
await page.clearInput(selectors.ticketFuture.state);
|
||||
await page.clearInput(selectors.ticketFuture.futureState);
|
||||
|
||||
await page.autocompleteSearch(selectors.ticketFuture.futureState, 'Free');
|
||||
await page.waitToClick(selectors.ticketFuture.submit);
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
||||
|
||||
expect(httpRequest).toContain('futureState=FREE');
|
||||
|
||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||
await page.clearInput(selectors.ticketFuture.ipt);
|
||||
await page.clearInput(selectors.ticketFuture.futureIpt);
|
||||
await page.clearInput(selectors.ticketFuture.state);
|
||||
await page.clearInput(selectors.ticketFuture.futureState);
|
||||
|
||||
await page.waitToClick(selectors.ticketFuture.submit);
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||
});
|
||||
|
||||
it('should search in smart-table with an ID Origin', async() => {
|
||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||
await page.write(selectors.ticketFuture.tableId, '13');
|
||||
await page.write(selectors.ticketFuture.tableId, '1');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 2);
|
||||
|
||||
expect(httpRequest).toContain('id');
|
||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||
await page.waitToClick(selectors.ticketFuture.submit);
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||
});
|
||||
|
||||
it('should search in smart-table with an ID Destination', async() => {
|
||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||
await page.write(selectors.ticketFuture.tableFutureId, '12');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
||||
|
||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||
await page.waitToClick(selectors.ticketFuture.submit);
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||
});
|
||||
|
||||
it('should search in smart-table with an IPT Origin', async() => {
|
||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||
await page.autocompleteSearch(selectors.ticketFuture.tableIpt, 'Vertical');
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
||||
|
||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||
await page.waitToClick(selectors.ticketFuture.submit);
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||
});
|
||||
|
||||
it('should search in smart-table with an IPT Destination', async() => {
|
||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||
await page.autocompleteSearch(selectors.ticketFuture.tableFutureIpt, 'Vertical');
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
||||
await page.autocompleteSearch(selectors.ticketFuture.tableFutureIpt, 'Horizontal');
|
||||
|
||||
expect(httpRequest).toContain('futureIpt');
|
||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||
});
|
||||
|
||||
it('should search in smart-table with an ID Destination', async() => {
|
||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||
await page.write(selectors.ticketFuture.tableFutureId, '1');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
expect(httpRequest).toContain('futureId');
|
||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||
await page.waitToClick(selectors.ticketFuture.submit);
|
||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||
});
|
||||
|
||||
it('should check the three last tickets and move to the future', async() => {
|
||||
|
|
|
@ -4,7 +4,7 @@ import getBrowser from '../../helpers/puppeteer';
|
|||
describe('Ticket Advance path', () => {
|
||||
let browser;
|
||||
let page;
|
||||
const httpRequests = [];
|
||||
let httpRequest;
|
||||
|
||||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
|
@ -13,7 +13,7 @@ describe('Ticket Advance path', () => {
|
|||
await page.accessToSection('ticket.advance');
|
||||
page.on('request', req => {
|
||||
if (req.url().includes(`Tickets/getTicketsAdvance`))
|
||||
httpRequests.push(req.url());
|
||||
httpRequest = req.url();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -49,7 +49,7 @@ describe('Ticket Advance path', () => {
|
|||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
||||
|
||||
expect(httpRequests.length).toBeGreaterThan(0);
|
||||
expect(httpRequest).toBeDefined();
|
||||
});
|
||||
|
||||
it('should search with the origin IPT', async() => {
|
||||
|
@ -57,11 +57,7 @@ describe('Ticket Advance path', () => {
|
|||
await page.autocompleteSearch(selectors.ticketAdvance.futureIpt, 'Horizontal');
|
||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
||||
|
||||
const request = httpRequests.find(req => req.includes(('futureIpt=H')));
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
httpRequests.splice(httpRequests.indexOf(request), 1);
|
||||
expect(httpRequest).toContain('futureIpt=H');
|
||||
|
||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
||||
await page.clearInput(selectors.ticketAdvance.futureIpt);
|
||||
|
@ -73,11 +69,7 @@ describe('Ticket Advance path', () => {
|
|||
await page.autocompleteSearch(selectors.ticketAdvance.ipt, 'Horizontal');
|
||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
||||
|
||||
const request = httpRequests.find(req => req.includes(('ipt=H')));
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
httpRequests.splice(httpRequests.indexOf(request), 1);
|
||||
expect(httpRequest).toContain('ipt=H');
|
||||
|
||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
||||
await page.clearInput(selectors.ticketAdvance.ipt);
|
||||
|
@ -88,11 +80,7 @@ describe('Ticket Advance path', () => {
|
|||
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
|
||||
await page.autocompleteSearch(selectors.ticketAdvance.tableFutureIpt, 'Vertical');
|
||||
|
||||
const request = httpRequests.find(req => req.includes(('futureIpt')));
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
httpRequests.splice(httpRequests.indexOf(request), 1);
|
||||
expect(httpRequest).toContain('futureIpt');
|
||||
|
||||
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
|
||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
||||
|
@ -103,11 +91,7 @@ describe('Ticket Advance path', () => {
|
|||
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
|
||||
await page.autocompleteSearch(selectors.ticketAdvance.tableIpt, 'Vertical');
|
||||
|
||||
const request = httpRequests.find(req => req.includes(('ipt')));
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
httpRequests.splice(httpRequests.indexOf(request), 1);
|
||||
expect(httpRequest).toContain('ipt');
|
||||
|
||||
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
|
||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
||||
|
|
|
@ -40,7 +40,7 @@ export default class Check extends Toggle {
|
|||
|
||||
set tripleState(value) {
|
||||
this._tripleState = value;
|
||||
this.field = this.field;
|
||||
this.field = value;
|
||||
}
|
||||
|
||||
get tripleState() {
|
||||
|
|
|
@ -45,8 +45,8 @@ describe('Component vnCheck', () => {
|
|||
});
|
||||
|
||||
it(`should set value to null and change to true when clicked`, () => {
|
||||
controller.field = null;
|
||||
controller.tripleState = true;
|
||||
controller.field = null;
|
||||
element.click();
|
||||
|
||||
expect(controller.field).toEqual(true);
|
||||
|
|
|
@ -436,6 +436,7 @@ export default class SmartTable extends Component {
|
|||
|
||||
if (filters && filters.userFilter)
|
||||
this.model.userFilter = filters.userFilter;
|
||||
|
||||
this.addFilter(field, this.$inputsScope.searchProps[field]);
|
||||
}
|
||||
|
||||
|
@ -451,7 +452,7 @@ export default class SmartTable extends Component {
|
|||
}
|
||||
|
||||
addFilter(field, value) {
|
||||
if (value == '') value = null;
|
||||
if (value === '') value = null;
|
||||
|
||||
let stateFilter = {tableQ: {}};
|
||||
if (this.$params.q) {
|
||||
|
@ -462,7 +463,7 @@ export default class SmartTable extends Component {
|
|||
}
|
||||
|
||||
const whereParams = {[field]: value};
|
||||
if (value) {
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
let where = {[field]: value};
|
||||
if (this.exprBuilder) {
|
||||
where = buildFilter(whereParams, (param, value) =>
|
||||
|
|
|
@ -150,5 +150,7 @@
|
|||
"Tickets with associated refunds": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº {{id}}",
|
||||
"It is not possible to modify tracked sales": "It is not possible to modify tracked sales",
|
||||
"It is not possible to modify sales that their articles are from Floramondo": "It is not possible to modify sales that their articles are from Floramondo",
|
||||
"It is not possible to modify cloned sales": "It is not possible to modify cloned sales"
|
||||
}
|
||||
"It is not possible to modify cloned sales": "It is not possible to modify cloned sales",
|
||||
"Valid priorities: 1,2,3": "Valid priorities: 1,2,3",
|
||||
"Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2"
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('isEditable', {
|
||||
description: 'Check if a claim is editable',
|
||||
description: 'Check if an state is editable',
|
||||
accessType: 'READ',
|
||||
accepts: [{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'the claim id',
|
||||
description: 'the state id',
|
||||
http: {source: 'path'}
|
||||
}],
|
||||
returns: {
|
||||
|
@ -21,25 +21,18 @@ module.exports = Self => {
|
|||
|
||||
Self.isEditable = async(ctx, id, options) => {
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const models = Self.app.models;
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const isClaimManager = await Self.app.models.Account.hasRole(userId, 'claimManager', myOptions);
|
||||
|
||||
const claim = await Self.app.models.Claim.findById(id, {
|
||||
fields: ['claimStateFk'],
|
||||
include: [{
|
||||
relation: 'claimState'
|
||||
}]
|
||||
}, myOptions);
|
||||
|
||||
const isClaimResolved = claim && claim.claimState().code == 'resolved';
|
||||
|
||||
if (!claim || (isClaimResolved && !isClaimManager))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
const state = await models.ClaimState.findById(id, {
|
||||
include: {
|
||||
relation: 'writeRole'
|
||||
}
|
||||
}, myOptions);
|
||||
const roleWithGrants = state && state.writeRole().name;
|
||||
return await models.Account.hasRole(userId, roleWithGrants, myOptions);
|
||||
};
|
||||
};
|
|
@ -1,16 +1,16 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('claim isEditable()', () => {
|
||||
const salesPerdonId = 18;
|
||||
describe('claimstate isEditable()', () => {
|
||||
const salesPersonId = 18;
|
||||
const claimManagerId = 72;
|
||||
it('should return false if the given claim does not exist', async() => {
|
||||
it('should return false if the given state does not exist', async() => {
|
||||
const tx = await app.models.Claim.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: claimManagerId}}};
|
||||
const result = await app.models.Claim.isEditable(ctx, 99999, options);
|
||||
const result = await app.models.ClaimState.isEditable(ctx, 9999, options);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
|
||||
|
@ -27,8 +27,8 @@ describe('claim isEditable()', () => {
|
|||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: salesPerdonId}}};
|
||||
const result = await app.models.Claim.isEditable(ctx, 4, options);
|
||||
const ctx = {req: {accessToken: {userId: salesPersonId}}};
|
||||
const result = await app.models.ClaimState.isEditable(ctx, 3, options);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
|
||||
|
@ -46,7 +46,7 @@ describe('claim isEditable()', () => {
|
|||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: claimManagerId}}};
|
||||
const result = await app.models.Claim.isEditable(ctx, 4, options);
|
||||
const result = await app.models.ClaimState.isEditable(ctx, 3, options);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
|
||||
|
@ -63,8 +63,8 @@ describe('claim isEditable()', () => {
|
|||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: salesPerdonId}}};
|
||||
const result = await app.models.Claim.isEditable(ctx, 1, options);
|
||||
const ctx = {req: {accessToken: {userId: claimManagerId}}};
|
||||
const result = await app.models.ClaimState.isEditable(ctx, 7, options);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
|
|
@ -65,7 +65,8 @@ module.exports = Self => {
|
|||
]
|
||||
};
|
||||
|
||||
promises.push(Self.app.models.Claim.find(filter, myOptions));
|
||||
const models = Self.app.models;
|
||||
promises.push(models.Claim.find(filter, myOptions));
|
||||
|
||||
// Claim detail
|
||||
filter = {
|
||||
|
@ -82,7 +83,7 @@ module.exports = Self => {
|
|||
}
|
||||
]
|
||||
};
|
||||
promises.push(Self.app.models.ClaimBeginning.find(filter, myOptions));
|
||||
promises.push(models.ClaimBeginning.find(filter, myOptions));
|
||||
|
||||
// Claim observations
|
||||
filter = {
|
||||
|
@ -96,7 +97,7 @@ module.exports = Self => {
|
|||
}
|
||||
]
|
||||
};
|
||||
promises.push(Self.app.models.ClaimObservation.find(filter, myOptions));
|
||||
promises.push(models.ClaimObservation.find(filter, myOptions));
|
||||
|
||||
// Claim developments
|
||||
filter = {
|
||||
|
@ -128,7 +129,7 @@ module.exports = Self => {
|
|||
}
|
||||
]
|
||||
};
|
||||
promises.push(Self.app.models.ClaimDevelopment.find(filter, myOptions));
|
||||
promises.push(models.ClaimDevelopment.find(filter, myOptions));
|
||||
|
||||
// Claim action
|
||||
filter = {
|
||||
|
@ -145,11 +146,11 @@ module.exports = Self => {
|
|||
{relation: 'claimBeggining'}
|
||||
]
|
||||
};
|
||||
promises.push(Self.app.models.ClaimEnd.find(filter, myOptions));
|
||||
promises.push(models.ClaimEnd.find(filter, myOptions));
|
||||
|
||||
const res = await Promise.all(promises);
|
||||
|
||||
summary.isEditable = await Self.isEditable(ctx, id, myOptions);
|
||||
summary.isEditable = await models.ClaimState.isEditable(ctx, res[0][0].claimStateFk, myOptions);
|
||||
[summary.claim] = res[0];
|
||||
summary.salesClaimed = res[1];
|
||||
summary.observations = res[2];
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
|
||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||
const buildFilter = require('vn-loopback/util/filter').buildFilter;
|
||||
const {mergeFilters, mergeWhere} = require('vn-loopback/util/filter');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('logs', {
|
||||
description: 'Find all claim logs of the claim entity matched by a filter',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The claim id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'filter',
|
||||
type: 'object',
|
||||
http: {source: 'query'}
|
||||
},
|
||||
{
|
||||
arg: 'search',
|
||||
type: 'string',
|
||||
http: {source: 'query'}
|
||||
},
|
||||
{
|
||||
arg: 'userFk',
|
||||
type: 'number',
|
||||
http: {source: 'query'}
|
||||
},
|
||||
{
|
||||
arg: 'created',
|
||||
type: 'date',
|
||||
http: {source: 'query'}
|
||||
},
|
||||
],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/:id/logs`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.logs = async(ctx, id, filter, options) => {
|
||||
const conn = Self.dataSource.connector;
|
||||
const args = ctx.args;
|
||||
const myOptions = {};
|
||||
let to;
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
let where = buildFilter(args, (param, value) => {
|
||||
switch (param) {
|
||||
case 'search':
|
||||
return {
|
||||
or: [
|
||||
{changedModel: {like: `%${value}%`}},
|
||||
{oldInstance: {like: `%${value}%`}}
|
||||
]
|
||||
};
|
||||
case 'userFk':
|
||||
return {'cl.userFk': value};
|
||||
case 'created':
|
||||
value.setHours(0, 0, 0, 0);
|
||||
to = new Date(value);
|
||||
to.setHours(23, 59, 59, 999);
|
||||
|
||||
return {creationDate: {between: [value, to]}};
|
||||
}
|
||||
});
|
||||
where = mergeWhere(where, {['cl.originFk']: id});
|
||||
filter = mergeFilters(args.filter, {where});
|
||||
|
||||
const stmts = [];
|
||||
|
||||
const stmt = new ParameterizedSQL(
|
||||
`SELECT
|
||||
cl.id,
|
||||
cl.userFk,
|
||||
u.name AS userName,
|
||||
cl.oldInstance,
|
||||
cl.newInstance,
|
||||
cl.changedModel,
|
||||
cl.action,
|
||||
cl.creationDate AS created
|
||||
FROM claimLog cl
|
||||
JOIN account.user u ON u.id = cl.userFk
|
||||
`
|
||||
);
|
||||
|
||||
stmt.merge(conn.makeSuffix(filter));
|
||||
stmts.push(stmt);
|
||||
|
||||
const sql = ParameterizedSQL.join(stmts, ';');
|
||||
const result = await conn.executeStmt(sql, myOptions);
|
||||
|
||||
const logs = [];
|
||||
for (const row of result) {
|
||||
const changes = [];
|
||||
const oldInstance = JSON.parse(row.oldInstance);
|
||||
const newInstance = JSON.parse(row.newInstance);
|
||||
const mergedProperties = [...Object.keys(oldInstance), ...Object.keys(newInstance)];
|
||||
const properties = new Set(mergedProperties);
|
||||
for (const property of properties) {
|
||||
let oldValue = oldInstance[property];
|
||||
let newValue = newInstance[property];
|
||||
|
||||
const change = {
|
||||
property: property,
|
||||
before: oldValue,
|
||||
after: newValue,
|
||||
};
|
||||
|
||||
changes.push(change);
|
||||
}
|
||||
|
||||
logs.push({
|
||||
model: row.changedModel,
|
||||
action: row.action,
|
||||
created: row.created,
|
||||
userFk: row.userFk,
|
||||
userName: row.userName,
|
||||
changes: changes,
|
||||
});
|
||||
}
|
||||
|
||||
return logs;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('claim log()', () => {
|
||||
const claimId = 1;
|
||||
const salesPersonId = 18;
|
||||
|
||||
it('should return results filtering by user id', async() => {
|
||||
const result = await app.models.Claim.logs({args: {userFk: salesPersonId}}, claimId);
|
||||
|
||||
const expectedObject = {
|
||||
model: 'Claim',
|
||||
action: 'update',
|
||||
changes: [
|
||||
{property: 'hasToPickUp', before: false, after: true}
|
||||
]
|
||||
};
|
||||
|
||||
const firstRow = result[0];
|
||||
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
expect(firstRow).toEqual(jasmine.objectContaining(expectedObject));
|
||||
});
|
||||
});
|
|
@ -2,6 +2,7 @@ const UserError = require('vn-loopback/util/user-error');
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('updateClaim', {
|
||||
description: 'Update a claim with privileges',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'ctx',
|
||||
type: 'object',
|
||||
|
@ -78,11 +79,11 @@ module.exports = Self => {
|
|||
|
||||
// Validate when claimState has been changed
|
||||
if (args.claimStateFk) {
|
||||
const canUpdate = await canChangeState(ctx, claim.claimStateFk, myOptions);
|
||||
const hasRights = await canChangeState(ctx, args.claimStateFk, myOptions);
|
||||
const canEditOldState = await models.ClaimState.isEditable(ctx, claim.claimStateFk, myOptions);
|
||||
const canEditNewState = await models.ClaimState.isEditable(ctx, args.claimStateFk, myOptions);
|
||||
const isClaimManager = await models.Account.hasRole(userId, 'claimManager', myOptions);
|
||||
|
||||
if (!canUpdate || !hasRights || changedHasToPickUp && !isClaimManager)
|
||||
if (!canEditOldState || !canEditNewState || changedHasToPickUp && !isClaimManager)
|
||||
throw new UserError(`You don't have enough privileges to change that field`);
|
||||
}
|
||||
|
||||
|
@ -113,21 +114,6 @@ module.exports = Self => {
|
|||
}
|
||||
};
|
||||
|
||||
async function canChangeState(ctx, id, options) {
|
||||
let models = Self.app.models;
|
||||
let userId = ctx.req.accessToken.userId;
|
||||
|
||||
let state = await models.ClaimState.findById(id, {
|
||||
include: {
|
||||
relation: 'writeRole'
|
||||
}
|
||||
}, options);
|
||||
let stateRole = state.writeRole().name;
|
||||
let canUpdate = await models.Account.hasRole(userId, stateRole, options);
|
||||
|
||||
return canUpdate;
|
||||
}
|
||||
|
||||
async function notifyStateChange(ctx, workerId, claim, state) {
|
||||
const models = Self.app.models;
|
||||
const origin = ctx.req.headers.origin;
|
||||
|
|
|
@ -11,7 +11,7 @@ module.exports = Self => {
|
|||
|
||||
Self.observe('before save', async ctx => {
|
||||
if (ctx.isNewInstance) return;
|
||||
await claimIsEditable(ctx);
|
||||
//await claimIsEditable(ctx);
|
||||
});
|
||||
|
||||
Self.observe('before delete', async ctx => {
|
||||
|
@ -22,8 +22,28 @@ module.exports = Self => {
|
|||
async function claimIsEditable(ctx) {
|
||||
const loopBackContext = LoopBackContext.getCurrentContext();
|
||||
const httpCtx = {req: loopBackContext.active};
|
||||
const models = Self.app.models;
|
||||
const myOptions = {};
|
||||
|
||||
if (ctx.options && ctx.options.transaction)
|
||||
myOptions.transaction = ctx.options.transaction;
|
||||
|
||||
const claimBeginning = await Self.findById(ctx.where.id);
|
||||
const isEditable = await Self.app.models.Claim.isEditable(httpCtx, claimBeginning.claimFk);
|
||||
|
||||
const filter = {
|
||||
where: {id: claimBeginning.claimFk},
|
||||
include: [
|
||||
{
|
||||
relation: 'claimState',
|
||||
scope: {
|
||||
fields: ['id', 'code', 'description']
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const [claim] = await models.Claim.find(filter, myOptions);
|
||||
const isEditable = await models.ClaimState.isEditable(httpCtx, claim.claimState().id);
|
||||
|
||||
if (!isEditable)
|
||||
throw new UserError(`The current claim can't be modified`);
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/claim-state/isEditable')(Self);
|
||||
};
|
|
@ -6,9 +6,9 @@ module.exports = Self => {
|
|||
require('../methods/claim/regularizeClaim')(Self);
|
||||
require('../methods/claim/uploadFile')(Self);
|
||||
require('../methods/claim/updateClaimAction')(Self);
|
||||
require('../methods/claim/isEditable')(Self);
|
||||
require('../methods/claim/updateClaimDestination')(Self);
|
||||
require('../methods/claim/downloadFile')(Self);
|
||||
require('../methods/claim/claimPickupPdf')(Self);
|
||||
require('../methods/claim/claimPickupEmail')(Self);
|
||||
require('../methods/claim/logs')(Self);
|
||||
};
|
||||
|
|
|
@ -100,8 +100,8 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
setClaimedQuantity(id, claimedQuantity) {
|
||||
let params = {id: id, quantity: claimedQuantity};
|
||||
let query = `ClaimBeginnings/`;
|
||||
let params = {quantity: claimedQuantity};
|
||||
let query = `ClaimBeginnings/${id}`;
|
||||
this.$http.patch(query, params).then(() => {
|
||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||
this.calculateTotals();
|
||||
|
@ -151,7 +151,7 @@ class Controller extends Section {
|
|||
isClaimEditable() {
|
||||
if (!this.claim) return;
|
||||
|
||||
this.$http.get(`Claims/${this.claim.id}/isEditable`).then(res => {
|
||||
this.$http.get(`ClaimStates/${this.claim.id}/isEditable`).then(res => {
|
||||
this.isRewritable = res.data;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('claim', () => {
|
|||
$httpBackend = _$httpBackend_;
|
||||
$httpBackend.whenGET('Claims/ClaimBeginnings').respond({});
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond(true);
|
||||
$httpBackend.whenGET(`Claims/2/isEditable`).respond(true);
|
||||
$httpBackend.whenGET(`ClaimStates/2/isEditable`).respond(true);
|
||||
const $element = angular.element('<vn-claim-detail></vn-claim-detail>');
|
||||
controller = $componentController('vnClaimDetail', {$element, $scope});
|
||||
controller.claim = {
|
||||
|
@ -89,9 +89,12 @@ describe('claim', () => {
|
|||
|
||||
describe('setClaimedQuantity(id, claimedQuantity)', () => {
|
||||
it('should make a patch and call refresh and showSuccess', () => {
|
||||
const id = 1;
|
||||
const claimedQuantity = 1;
|
||||
|
||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||
$httpBackend.expectPATCH(`ClaimBeginnings/`).respond({});
|
||||
controller.setClaimedQuantity(1, 1);
|
||||
$httpBackend.expectPATCH(`ClaimBeginnings/${id}`).respond({});
|
||||
controller.setClaimedQuantity(id, claimedQuantity);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
||||
|
|
|
@ -13,55 +13,59 @@ module.exports = Self => {
|
|||
type: 'Object',
|
||||
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
|
||||
http: {source: 'query'}
|
||||
}, {
|
||||
arg: 'tags',
|
||||
type: ['Object'],
|
||||
description: 'List of tags to filter with',
|
||||
http: {source: 'query'}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
arg: 'search',
|
||||
type: 'String',
|
||||
description: `If it's and integer searchs by id, otherwise it searchs by name`,
|
||||
http: {source: 'query'}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'Integer',
|
||||
description: 'The worker id',
|
||||
http: {source: 'query'}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
arg: 'userFk',
|
||||
type: 'Integer',
|
||||
description: 'The user id',
|
||||
http: {source: 'query'}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
arg: 'fi',
|
||||
type: 'String',
|
||||
description: 'The worker fi',
|
||||
http: {source: 'query'}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
arg: 'departmentFk',
|
||||
type: 'Integer',
|
||||
description: 'The worker department id',
|
||||
http: {source: 'query'}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
arg: 'extension',
|
||||
type: 'Integer',
|
||||
description: 'The worker extension id',
|
||||
http: {source: 'query'}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
arg: 'firstName',
|
||||
type: 'String',
|
||||
description: 'The worker firstName',
|
||||
http: {source: 'query'}
|
||||
}, {
|
||||
arg: 'name',
|
||||
},
|
||||
{
|
||||
arg: 'lastName',
|
||||
type: 'String',
|
||||
description: 'The worker name',
|
||||
description: 'The worker lastName',
|
||||
http: {source: 'query'}
|
||||
}, {
|
||||
arg: 'nickname',
|
||||
},
|
||||
{
|
||||
arg: 'userName',
|
||||
type: 'String',
|
||||
description: 'The worker nickname',
|
||||
description: 'The worker user name',
|
||||
http: {source: 'query'}
|
||||
}
|
||||
],
|
||||
|
@ -93,10 +97,10 @@ module.exports = Self => {
|
|||
return {'w.id': value};
|
||||
case 'userFk':
|
||||
return {'w.userFk': value};
|
||||
case 'name':
|
||||
return {'w.lastName': {like: `%${value}%`}};
|
||||
case 'firstName':
|
||||
return {'w.firstName': {like: `%${value}%`}};
|
||||
case 'lastName':
|
||||
return {'w.lastName': {like: `%${value}%`}};
|
||||
case 'extension':
|
||||
return {'p.extension': value};
|
||||
case 'fi':
|
||||
|
|
|
@ -16,7 +16,7 @@ describe('worker filter()', () => {
|
|||
});
|
||||
|
||||
it('should return 2 results filtering by name', async() => {
|
||||
let result = await app.models.Worker.filter({args: {filter: {}, name: 'agency'}});
|
||||
let result = await app.models.Worker.filter({args: {filter: {}, firstName: 'agency'}});
|
||||
|
||||
expect(result.length).toEqual(2);
|
||||
expect(result[0].nickname).toEqual('agencyNick');
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<vn-textfield
|
||||
vn-one
|
||||
label="Last name"
|
||||
ng-model="filter.name">
|
||||
ng-model="filter.lastName">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
|
|
Loading…
Reference in New Issue