Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2142_e2e_entry_descriptor
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Carlos Jimenez Ruiz 2020-03-02 13:16:50 +01:00
commit 43d57511e0
18 changed files with 305 additions and 34 deletions

View File

@ -784,6 +784,21 @@ export default {
ticketOne: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(1)',
ticketTwo: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(2)'
},
travelBasicDada: {
reference: 'vn-travel-basic-data vn-textfield[ng-model="$ctrl.travel.ref"]',
agency: 'vn-travel-basic-data vn-autocomplete[ng-model="$ctrl.travel.agencyModeFk"]',
shippingDate: 'vn-travel-basic-data vn-date-picker[ng-model="$ctrl.travel.shipped"]',
deliveryDate: 'vn-travel-basic-data vn-date-picker[ng-model="$ctrl.travel.landed"]',
outputWarehouse: 'vn-travel-basic-data vn-autocomplete[ng-model="$ctrl.travel.warehouseOutFk"]',
inputWarehouse: 'vn-travel-basic-data vn-autocomplete[ng-model="$ctrl.travel.warehouseInFk"]',
delivered: 'vn-travel-basic-data vn-check[ng-model="$ctrl.travel.isDelivered"]',
received: 'vn-travel-basic-data vn-check[ng-model="$ctrl.travel.isReceived"]',
save: 'vn-travel-basic-data vn-submit[label="Save"]',
undoChanges: 'vn-travel-basic-data vn-button[label="Undo changes"]'
},
travelLog: {
firstLogFirstTD: 'vn-travel-log vn-tbody > vn-tr > vn-td:nth-child(1) > div'
},
travelThermograph: {
add: 'vn-travel-thermograph-index vn-float-button[icon="add"]',
thermographID: 'vn-travel-thermograph-create vn-autocomplete[ng-model="$ctrl.dms.thermographId"]',

View File

@ -16,6 +16,12 @@ describe('Client Add notes path', () => {
await browser.close();
});
it(`should reach the notes index`, async() => {
let url = await page.expectURL('/note');
expect(url).toBe(true);
});
it(`should click on the add note button`, async() => {
await page.waitToClick(selectors.clientNotes.addNoteFloatButton);
let url = await page.expectURL('/note/create');

View File

@ -0,0 +1,101 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Travel basic data path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'travel');
await page.accessToSearchResult('3');
await page.accessToSection('travel.card.basicData');
});
afterAll(async() => {
await browser.close();
});
it('should reach the thermograph section', async() => {
const result = await page.expectURL('/basic-data');
expect(result).toBe(true);
});
it('should set a wrong delivery date then receive an error on submit', async() => {
await page.datePicker(selectors.travelBasicDada.deliveryDate, -1, null);
await page.waitToClick(selectors.travelBasicDada.save);
const result = await page.waitForLastSnackbar();
expect(result).toEqual('Landing cannot be lesser than shipment');
});
it('should undo the changes', async() => {
await page.waitToClick(selectors.travelBasicDada.undoChanges);
await page.waitToClick(selectors.travelBasicDada.save);
const result = await page.waitForLastSnackbar();
expect(result).toEqual('No changes to save');
});
it('should now edit the whole form then save', async() => {
await page.clearInput(selectors.travelBasicDada.reference);
await page.write(selectors.travelBasicDada.reference, 'new reference!');
await page.autocompleteSearch(selectors.travelBasicDada.agency, 'Entanglement');
await page.autocompleteSearch(selectors.travelBasicDada.outputWarehouse, 'Warehouse Three');
await page.autocompleteSearch(selectors.travelBasicDada.inputWarehouse, 'Warehouse Four');
await page.waitToClick(selectors.travelBasicDada.delivered);
await page.waitToClick(selectors.travelBasicDada.received);
await page.waitToClick(selectors.travelBasicDada.save);
const result = await page.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
});
it('should reload the section and check the reference was saved', async() => {
await page.reloadSection('travel.card.basicData');
const result = await page.waitToGetProperty(selectors.travelBasicDada.reference, 'value');
expect(result).toEqual('new reference!');
});
it('should check the agency was saved', async() => {
const result = await page.waitToGetProperty(selectors.travelBasicDada.agency, 'value');
expect(result).toEqual('Entanglement');
});
it('should check the output warehouse date was saved', async() => {
const result = await page.waitToGetProperty(selectors.travelBasicDada.outputWarehouse, 'value');
expect(result).toEqual('Warehouse Three');
});
it('should check the input warehouse date was saved', async() => {
const result = await page.waitToGetProperty(selectors.travelBasicDada.inputWarehouse, 'value');
expect(result).toEqual('Warehouse Four');
});
it(`should check the delivered checkbox was saved even tho it doesn't make sense`, async() => {
await page.waitForClassPresent(selectors.travelBasicDada.delivered, 'checked');
});
it(`should check the received checkbox was saved even tho it doesn't make sense`, async() => {
await page.waitForClassPresent(selectors.travelBasicDada.received, 'checked');
});
it('should navigate to the travel logs', async() => {
await page.accessToSection('travel.card.log');
const result = await page.expectURL('/log');
expect(result).toBe(true);
});
it('should check the 1st log contains details from the changes made', async() => {
const result = await page.waitToGetProperty(selectors.travelLog.firstLogFirstTD, 'innerText');
expect(result).toContain('new reference!');
});
});

View File

@ -16,7 +16,7 @@ describe('Client isValidClient', () => {
});
it('should call the isValidClient() method with an unexistant id and receive false', async() => {
let id = 999999;
let id = 999;
let result = await app.models.Client.isValidClient(id);
expect(result).toBeFalsy();

View File

@ -34,7 +34,7 @@ describe('ticket-request confirm()', () => {
expect(error.message).toEqual(`That item doesn't exists`);
});
it(`should throw an error if the item is not available`, async() => {
it('should throw an error if the item is not available', async() => {
const requestId = 5;
const itemId = 4;
const quantity = 99999;

View File

@ -67,6 +67,10 @@ module.exports = Self => {
arg: 'problems',
type: 'Boolean',
description: `Whether to show only tickets with problems`
}, {
arg: 'pending',
type: 'Boolean',
description: `Whether to show only tickets with state 'Pending'`
}, {
arg: 'mine',
type: 'Boolean',
@ -130,7 +134,7 @@ module.exports = Self => {
dateTo.setHours(23, 59, 0, 0);
}
let where = buildFilter(ctx.args, (param, value) => {
const where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
@ -155,6 +159,17 @@ module.exports = Self => {
return {'c.salesPersonFk': {inq: teamIds}};
case 'alertLevel':
return {'ts.alertLevel': value};
case 'pending':
if (value) {
return {and: [
{'st.alertLevel': 0},
{'st.code': {neq: 'OK'}}
]};
} else {
return {and: [
{'st.alertLevel': {gt: 0}}
]};
}
case 'id':
case 'clientFk':
case 'agencyModeFk':
@ -244,7 +259,6 @@ module.exports = Self => {
LEFT JOIN tmp.ticketProblems tp ON tp.ticketFk = f.id
LEFT JOIN tmp.ticketTotal tt ON tt.ticketFk = f.id`);
let condition;
let hasProblem;
let range;

View File

@ -32,7 +32,7 @@ module.exports = Self => {
let alertLevel = state ? state.alertLevel : null;
let ticket = await Self.app.models.Ticket.findById(id, {
fields: ['isDeleted', 'clientFk', 'refFk'],
fields: ['clientFk'],
include: [{
relation: 'client',
scope: {
@ -42,13 +42,13 @@ module.exports = Self => {
}
}]
});
const isLocked = await Self.app.models.Ticket.isLocked(id);
const isDeleted = ticket && ticket.isDeleted;
const isOnDelivery = (alertLevel && alertLevel > 0);
const alertLevelGreaterThanZero = (alertLevel && alertLevel > 0);
const isNormalClient = ticket && ticket.client().type().code == 'normal';
const isInvoiced = ticket && ticket.refFk;
const validAlertAndRoleNormalClient = (alertLevelGreaterThanZero && isNormalClient && !isValidRole);
if (!ticket || isInvoiced || isDeleted || (isOnDelivery && isNormalClient && !isValidRole))
if (!ticket || validAlertAndRoleNormalClient || isLocked)
return false;
return true;

View File

@ -0,0 +1,35 @@
module.exports = Self => {
Self.remoteMethod('isLocked', {
description: 'Check if a ticket is invoiced or deleted',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'the ticket id',
http: {source: 'path'}
}],
returns: {
type: 'boolean',
root: true
},
http: {
path: `/:id/isLocked`,
verb: 'get'
}
});
Self.isLocked = async id => {
const ticket = await Self.app.models.Ticket.findById(id, {
fields: ['isDeleted', 'refFk']
});
const isDeleted = ticket && ticket.isDeleted;
const isInvoiced = ticket && ticket.refFk;
if (!ticket || isInvoiced || isDeleted)
return true;
return false;
};
};

View File

@ -41,6 +41,34 @@ describe('ticket filter()', () => {
const firstRow = result[0];
expect(result.length).toEqual(1);
expect(firstRow.ticketFk).toEqual(11);
expect(firstRow.id).toEqual(11);
});
it('should return the tickets with grouped state "Pending" and not "Ok"', async() => {
const ctx = {req: {accessToken: {userId: 9}}, args: {pending: true}};
const filter = {};
const result = await app.models.Ticket.filter(ctx, filter);
const firstRow = result[0];
const secondRow = result[1];
const thirdRow = result[2];
expect(result.length).toEqual(3);
expect(firstRow.state).toEqual('Arreglar');
expect(secondRow.state).toEqual('Arreglar');
expect(thirdRow.state).toEqual('Arreglar');
});
it('should return the tickets that are not pending', async() => {
const ctx = {req: {accessToken: {userId: 9}}, args: {pending: false}};
const filter = {};
const result = await app.models.Ticket.filter(ctx, filter);
const firstRow = result[0];
const secondRow = result[1];
const thirdRow = result[2];
expect(result.length).toEqual(13);
expect(firstRow.state).toEqual('Entregado');
expect(secondRow.state).toEqual('Entregado');
expect(thirdRow.state).toEqual('Entregado');
});
});

View File

@ -1,13 +1,6 @@
const app = require('vn-loopback/server/server');
describe('ticket isEditable()', () => {
it('should return false if the given ticket is not editable', async() => {
let ctx = {req: {accessToken: {userId: 9}}};
let result = await app.models.Ticket.isEditable(ctx, 2);
expect(result).toEqual(false);
});
it('should return false if the given ticket does not exist', async() => {
let ctx = {req: {accessToken: {userId: 9}}};
let result = await app.models.Ticket.isEditable(ctx, 99999);
@ -15,37 +8,46 @@ describe('ticket isEditable()', () => {
expect(result).toEqual(false);
});
it('should return false if the given ticket isDeleted', async() => {
it(`should return false if the given ticket isn't invoiced but isDeleted`, async() => {
let ctx = {req: {accessToken: {userId: 9}}};
let result = await app.models.Ticket.isEditable(ctx, 19);
let deletedTicket = await app.models.Ticket.findOne({
where: {
invoiceOut: null,
isDeleted: true
},
fields: ['id']
});
let result = await app.models.Ticket.isEditable(ctx, deletedTicket.id);
expect(result).toEqual(false);
});
it('should return true if the given ticket is editable', async() => {
let ctx = {req: {accessToken: {userId: 9}}};
let result = await app.models.Ticket.isEditable(ctx, 16);
expect(result).toEqual(true);
});
it('should be able to edit a deleted or invoiced ticket if the role is salesAssistant', async() => {
it('should not be able to edit a deleted or invoiced ticket even for salesAssistant', async() => {
let ctx = {req: {accessToken: {userId: 21}}};
let result = await app.models.Ticket.isEditable(ctx, 8);
let result = await app.models.Ticket.isEditable(ctx, 19);
expect(result).toEqual(true);
expect(result).toEqual(false);
});
it('should be able to edit a deleted or invoiced ticket if the role is productionBoss', async() => {
it('should not be able to edit a deleted or invoiced ticket even for productionBoss', async() => {
let ctx = {req: {accessToken: {userId: 50}}};
let result = await app.models.Ticket.isEditable(ctx, 8);
let result = await app.models.Ticket.isEditable(ctx, 19);
expect(result).toEqual(true);
expect(result).toEqual(false);
});
it('should not be able to edit a deleted or invoiced ticket if the role is salesPerson', async() => {
it('should not be able to edit a deleted or invoiced ticket even for salesPerson', async() => {
let ctx = {req: {accessToken: {userId: 18}}};
let result = await app.models.Ticket.isEditable(ctx, 8);
let result = await app.models.Ticket.isEditable(ctx, 19);
expect(result).toEqual(false);
});

View File

@ -0,0 +1,34 @@
const app = require('vn-loopback/server/server');
describe('ticket isLocked()', () => {
it('should return true if the given ticket does not exist', async() => {
let result = await app.models.Ticket.isLocked(99999);
expect(result).toEqual(true);
});
it('should return true if the given ticket is invoiced', async() => {
let invoicedTicket = await app.models.Ticket.findOne({
where: {invoiceOut: {neq: null}},
fields: ['id']
});
let result = await app.models.Ticket.isLocked(invoicedTicket.id);
expect(result).toEqual(true);
});
it(`should return true if the given ticket isn't invoiced but deleted`, async() => {
let deletedTicket = await app.models.Ticket.findOne({
where: {
invoiceOut: null,
isDeleted: true
},
fields: ['id']
});
let result = await app.models.Ticket.isLocked(deletedTicket.id);
expect(result).toEqual(true);
});
});

View File

@ -35,6 +35,7 @@ module.exports = Self => {
});
Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models;
const tx = await Self.beginTransaction({});
@ -68,8 +69,14 @@ module.exports = Self => {
if (!allFromSameTicket)
throw new UserError('All sales must belong to the same ticket');
const isEditable = await models.Ticket.isEditable(ctx, id);
if (!isEditable)
const isLocked = await models.Ticket.isLocked(id);
const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson');
const state = await Self.app.models.TicketState.findOne({
where: {ticketFk: id}
});
const alertLevel = state ? state.alertLevel : null;
if (isLocked || (!isSalesPerson && alertLevel > 0 ))
throw new UserError(`The sales of this ticket can't be modified`);
const ticket = await models.Ticket.findById(id, {

View File

@ -29,6 +29,7 @@ module.exports = Self => {
require('../methods/ticket/recalculateComponents')(Self);
require('../methods/ticket/deleteStowaway')(Self);
require('../methods/ticket/sendSms')(Self);
require('../methods/ticket/isLocked')(Self);
Self.observe('before save', async function(ctx) {
if (ctx.isNewInstance) return;

View File

@ -165,8 +165,8 @@
</span>
</vn-td>
<vn-td number>
<span ng-class="{'link': $ctrl.isEditable}"
title="{{$ctrl.isEditable ? 'Edit discount' : ''}}"
<span ng-class="{'link': !$ctrl.isLocked}"
title="{{!$ctrl.isLocked ? 'Edit discount' : ''}}"
ng-click="$ctrl.showEditDiscountPopover($event, sale)">
{{(sale.discount / 100) | percentage}}
</span>

View File

@ -46,6 +46,7 @@ class Controller {
set ticket(value) {
this._ticket = value;
this.isTicketEditable();
this.isTicketLocked();
}
get sales() {
@ -354,7 +355,7 @@ class Controller {
}
showEditDiscountPopover(event, sale) {
if (!this.isEditable) return;
if (this.isLocked) return;
this.sale = sale;
this.edit = [{
@ -540,6 +541,12 @@ class Controller {
});
}
isTicketLocked() {
this.$http.get(`Tickets/${this.$state.params.id}/isLocked`).then(res => {
this.isLocked = res.data;
});
}
hasOneSaleSelected() {
if (this.totalCheckedLines() === 1)
return true;

View File

@ -69,6 +69,7 @@ describe('Ticket', () => {
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
$httpBackend.when('POST', `Claims/createFromSales`, {claim: claim, sales: sales}).respond(claim);
$httpBackend.expect('POST', `Claims/createFromSales`).respond(claim);
controller.createClaim();
@ -98,6 +99,7 @@ describe('Ticket', () => {
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
let result = controller.checkedLines();
$httpBackend.flush();
@ -116,6 +118,7 @@ describe('Ticket', () => {
$httpBackend.expectGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.expectGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
controller.onStateOkClick();
$httpBackend.flush();
@ -129,6 +132,7 @@ describe('Ticket', () => {
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
controller.onStateChange(3);
$httpBackend.flush();
});
@ -142,6 +146,7 @@ describe('Ticket', () => {
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
controller.onRemoveLinesClick('accept');
$httpBackend.flush();
@ -183,6 +188,7 @@ describe('Ticket', () => {
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
controller.unmarkAsReserved(false);
$httpBackend.flush();
});
@ -213,6 +219,7 @@ describe('Ticket', () => {
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
controller.updateQuantity(sale);
$httpBackend.flush();
@ -232,6 +239,7 @@ describe('Ticket', () => {
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
controller.updateConcept(sale);
$httpBackend.flush();
@ -262,6 +270,7 @@ describe('Ticket', () => {
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
controller.addSale(newSale);
$httpBackend.flush();
@ -287,6 +296,7 @@ describe('Ticket', () => {
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
controller.transferSales(13);
$httpBackend.flush();
@ -305,6 +315,7 @@ describe('Ticket', () => {
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
controller.setTransferParams();
$httpBackend.flush();
@ -330,6 +341,7 @@ describe('Ticket', () => {
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
controller.newOrderFromTicket();
$httpBackend.flush();
@ -353,6 +365,7 @@ describe('Ticket', () => {
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
controller.calculateSalePrice();
$httpBackend.flush();

View File

@ -113,10 +113,16 @@
</vn-check>
<vn-check
vn-one
label="Problems"
label="With problems"
ng-model="filter.problems"
triple-state="true">
</vn-check>
<vn-check
vn-one
label="Pending"
ng-model="filter.pending"
triple-state="true">
</vn-check>
</vn-horizontal>
<vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit>

View File

@ -10,4 +10,6 @@ Province: Provincia
My team: Mi equipo
Order id: Id pedido
Grouped States: Estado agrupado
Days onward: Días adelante
Days onward: Días adelante
With problems: Con problemas
Pending: Pendientes