Merge pull request '2449 - Ticket basic show only available zones on autocomplete' (#382) from 2449-ticket_basicData into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #382
Reviewed-by: Bernat Exposito <bernat@verdnatura.es>
This commit is contained in:
Joan Sanchez 2020-09-23 13:14:13 +00:00
commit a2e7d57c36
14 changed files with 272 additions and 41 deletions

View File

@ -50,7 +50,7 @@ describe('Ticket Edit basic data path', () => {
}); });
it(`should edit the ticket agency then check there are no zones for it`, async() => { it(`should edit the ticket agency then check there are no zones for it`, async() => {
await page.autocompleteSearch(selectors.ticketBasicData.agency, 'Entanglement'); await page.autocompleteSearch(selectors.ticketBasicData.agency, 'inhouse pickup');
await page.waitFor(1000); await page.waitFor(1000);
let emptyZone = await page let emptyZone = await page
.expectPropertyValue(selectors.ticketBasicData.zone, 'value', ''); .expectPropertyValue(selectors.ticketBasicData.zone, 'value', '');
@ -59,6 +59,7 @@ describe('Ticket Edit basic data path', () => {
}); });
it(`should edit the ticket zone then check the agency is for the new zone`, async() => { it(`should edit the ticket zone then check the agency is for the new zone`, async() => {
await page.clearInput(selectors.ticketBasicData.agency);
await page.autocompleteSearch(selectors.ticketBasicData.zone, 'Zone expensive A'); await page.autocompleteSearch(selectors.ticketBasicData.zone, 'Zone expensive A');
let zone = await page let zone = await page
.waitToGetProperty(selectors.ticketBasicData.agency, 'value'); .waitToGetProperty(selectors.ticketBasicData.agency, 'value');

View File

@ -248,7 +248,8 @@ export default class Autocomplete extends Field {
'where', 'where',
'order', 'order',
'limit', 'limit',
'searchFunction' 'searchFunction',
'whereFunction'
]); ]);
} }
@ -290,6 +291,7 @@ ngModule.vnComponent('vnAutocomplete', {
limit: '<?', limit: '<?',
translateFields: '<?', translateFields: '<?',
searchFunction: '&?', searchFunction: '&?',
whereFunction: '&?',
fetchFunction: '<?' fetchFunction: '<?'
}, },
transclude: { transclude: {

View File

@ -409,6 +409,9 @@ export default class DropDown extends Popover {
? null ? null
: this.searchFunction({$search: this._search}); : this.searchFunction({$search: this._search});
if (this.whereFunction)
this.where = this.whereFunction();
Object.assign(filter, { Object.assign(filter, {
fields: this.getFields(), fields: this.getFields(),
include: this.include, include: this.include,

View File

@ -7,8 +7,10 @@
<form name="form"> <form name="form">
<vn-card class="vn-w-md vn-pa-lg"> <vn-card class="vn-w-md vn-pa-lg">
<vn-horizontal> <vn-horizontal>
<vn-autocomplete vn-one <vn-autocomplete
vn-one
vn-id="client" vn-id="client"
required="true"
url="Clients" url="Clients"
label="Client" label="Client"
show-field="name" show-field="name"
@ -18,7 +20,9 @@
initial-data="$ctrl.clientId" initial-data="$ctrl.clientId"
order="id"> order="id">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete vn-one <vn-autocomplete
vn-one
required="true"
data="$ctrl.addresses" data="$ctrl.addresses"
label="Address" label="Address"
show-field="nickname" show-field="nickname"
@ -41,6 +45,7 @@
</append> </append>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete vn-one <vn-autocomplete vn-one
required="true"
url="Warehouses" url="Warehouses"
label="Warehouse" label="Warehouse"
show-field="name" show-field="name"
@ -49,8 +54,9 @@
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-autocomplete
vn-one vn-one
required="true"
url="Companies" url="Companies"
label="Company" label="Company"
show-field="code" show-field="code"
@ -60,20 +66,24 @@
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one vn-one
url="AgencyModes/isActive" vn-id="agencyMode"
url="AgencyModes/byWarehouse"
label="Agency" label="Agency"
show-field="name" show-field="name"
value-field="id" value-field="id"
ng-model="$ctrl.agencyModeId"> ng-model="$ctrl.agencyModeId"
where-function="$ctrl.agencyModeWhere()">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one vn-one
data="zones" required="true"
url="Zones/includingExpired"
label="Zone" label="Zone"
show-field="name" show-field="name"
value-field="id" value-field="id"
ng-model="$ctrl.zoneId" ng-model="$ctrl.zoneId"
vn-acl="productionBoss"> vn-acl="productionBoss"
where-function="$ctrl.zoneWhere()">
<tpl-item> <tpl-item>
<span>{{::name}} - Max. {{::hour | date: 'HH:mm'}} h.</span> <span>{{::name}} - Max. {{::hour | date: 'HH:mm'}} h.</span>
</tpl-item> </tpl-item>
@ -82,16 +92,19 @@
<vn-horizontal> <vn-horizontal>
<vn-date-picker <vn-date-picker
vn-one vn-one
required="true"
label="Shipped" label="Shipped"
ng-model="$ctrl.shipped"> ng-model="$ctrl.shipped">
</vn-date-picker> </vn-date-picker>
<vn-input-time <vn-input-time
vn-one vn-one
required="true"
label="Shipped hour" label="Shipped hour"
ng-model="$ctrl.shipped"> ng-model="$ctrl.shipped">
</vn-input-time> </vn-input-time>
<vn-date-picker <vn-date-picker
vn-one vn-one
required="true"
label="Landed" label="Landed"
ng-model="$ctrl.landed"> ng-model="$ctrl.landed">
</vn-date-picker> </vn-date-picker>

View File

@ -56,12 +56,8 @@ class Controller extends Component {
set warehouseId(value) { set warehouseId(value) {
if (value != this.ticket.warehouseFk) { if (value != this.ticket.warehouseFk) {
this.ticket.warehouseFk = value; this.ticket.warehouseFk = value;
this.getShipped({ this.ticket.agencyModeFk = null;
landed: this.ticket.landed, this.ticket.zoneFk = null;
addressFk: this.ticket.addressFk,
agencyModeFk: this.ticket.agencyModeFk,
warehouseFk: value
});
} }
} }
@ -75,8 +71,7 @@ class Controller extends Component {
shipped: value, shipped: value,
addressFk: this.ticket.addressFk, addressFk: this.ticket.addressFk,
agencyModeFk: this.ticket.agencyModeFk, agencyModeFk: this.ticket.agencyModeFk,
warehouseFk: this.ticket.warehouseFk, warehouseFk: this.ticket.warehouseFk
showExpiredZones: false
}); });
} }
@ -101,12 +96,17 @@ class Controller extends Component {
set agencyModeId(value) { set agencyModeId(value) {
if (value != this.ticket.agencyModeFk) { if (value != this.ticket.agencyModeFk) {
this.ticket.agencyModeFk = value; this.ticket.agencyModeFk = value;
if (!value) return;
const agencyMode = this.$.agencyMode.selection;
this.ticket.warehouseFk = agencyMode.warehouseFk;
this.getLanded({ this.getLanded({
shipped: this.ticket.shipped, shipped: this.ticket.shipped,
addressFk: this.ticket.addressFk, addressFk: this.ticket.addressFk,
agencyModeFk: value, agencyModeFk: value,
warehouseFk: this.ticket.warehouseFk, warehouseFk: this.ticket.warehouseFk
showExpiredZones: false
}); });
} }
} }
@ -241,6 +241,27 @@ class Controller extends Component {
|| !this.ticket.companyFk || !this.ticket.shipped || !this.ticket.landed || !this.ticket.companyFk || !this.ticket.shipped || !this.ticket.landed
|| !this.ticket.zoneFk; || !this.ticket.zoneFk;
} }
zoneWhere() {
if (this.ticket.agencyModeFk) {
return {
shipped: this.ticket.shipped,
addressFk: this.ticket.addressFk,
agencyModeFk: this.ticket.agencyModeFk,
warehouseFk: this.ticket.warehouseFk
};
}
return {};
}
agencyModeWhere() {
if (this.warehouseId) {
return {
warehouseFk: this.warehouseId
};
}
return {};
}
} }
ngModule.vnComponent('vnTicketBasicDataStepOne', { ngModule.vnComponent('vnTicketBasicDataStepOne', {

View File

@ -94,16 +94,7 @@ describe('Ticket', () => {
jest.spyOn(controller, 'getShipped'); jest.spyOn(controller, 'getShipped');
controller.ticket.warehouseId = 1; controller.ticket.warehouseId = 1;
controller.warehouseId = 2; controller.warehouseId = 2;
const landed = new Date();
const expectedResult = {
landed: landed,
addressFk: 121,
agencyModeFk: 7,
warehouseFk: 2
};
controller.landed = landed;
expect(controller.getShipped).toHaveBeenCalledWith(expectedResult);
expect(controller.ticket.warehouseFk).toEqual(2); expect(controller.ticket.warehouseFk).toEqual(2);
}); });
}); });
@ -125,7 +116,6 @@ describe('Ticket', () => {
shipped: shipped, shipped: shipped,
addressFk: 121, addressFk: 121,
agencyModeFk: 7, agencyModeFk: 7,
showExpiredZones: false,
warehouseFk: 1 warehouseFk: 1
}; };
controller.shipped = shipped; controller.shipped = shipped;
@ -170,14 +160,14 @@ describe('Ticket', () => {
describe('agencyModeId() setter', () => { describe('agencyModeId() setter', () => {
it('should set agencyModeId property and call getLanded() method', () => { it('should set agencyModeId property and call getLanded() method', () => {
jest.spyOn(controller, 'getLanded'); jest.spyOn(controller, 'getLanded');
controller.$.agencyMode = {selection: {warehouseFk: 1}};
const shipped = new Date(); const shipped = new Date();
const agencyModeId = 8; const agencyModeId = 8;
const expectedResult = { const expectedResult = {
shipped: shipped, shipped: shipped,
addressFk: 121, addressFk: 121,
agencyModeFk: agencyModeId, agencyModeFk: agencyModeId,
warehouseFk: 1, warehouseFk: 1
showExpiredZones: false,
}; };
controller.ticket.shipped = shipped; controller.ticket.shipped = shipped;
controller.agencyModeId = 8; controller.agencyModeId = 8;
@ -363,5 +353,31 @@ describe('Ticket', () => {
expect(controller.landed).toEqual(landed); expect(controller.landed).toEqual(landed);
}); });
}); });
describe('zoneWhere() getter', () => {
it('should return an object containing filter properties', async() => {
const shipped = new Date();
controller.ticket.shipped = shipped;
const expectedResult = {
addressFk: 121,
agencyModeFk: 7,
shipped: shipped,
warehouseFk: 1
};
const result = controller.zoneWhere();
expect(result).toEqual(expect.objectContaining(expectedResult));
});
});
describe('agencyModeWhere() getter', () => {
it('should return an object containing the warehouseFk property', async() => {
const expectedResult = {warehouseFk: 1};
const result = controller.agencyModeWhere();
expect(result).toEqual(expect.objectContaining(expectedResult));
});
});
}); });
}); });

View File

@ -0,0 +1,38 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('byWarehouse', {
description: 'Returns a list of agencies from a warehouse',
accepts: [{
arg: 'filter',
type: 'Object',
description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string`
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/byWarehouse`,
verb: 'GET'
}
});
Self.byWarehouse = async filter => {
const conn = Self.dataSource.connector;
const where = {isActive: true};
filter = mergeFilters(filter, {where});
let stmt = new ParameterizedSQL(
`SELECT id, name, warehouseFk
FROM (
SELECT DISTINCT am.id, am.name, am.isActive, zw.warehouseFk
FROM zoneWarehouse zw
JOIN zone z ON z.id = zw.zoneFk
JOIN agencyMode am ON am.id = z.agencyModeFk) am`);
stmt.merge(conn.makeSuffix(filter));
return conn.executeStmt(stmt);
};
};

View File

@ -0,0 +1,21 @@
const app = require('vn-loopback/server/server');
describe('AgencyMode byWarehhouse()', () => {
const warehouseId = 1;
it('should return all the agencies', async() => {
const where = {};
const agencies = await app.models.AgencyMode.byWarehouse({where});
expect(agencies.length).toBeGreaterThan(10);
});
it('should return only the agencies for a warehouse', async() => {
const where = {warehouseFk: warehouseId};
const agencies = await app.models.AgencyMode.byWarehouse({where});
const validWarehouse = agencies.every(agency => agency.warehouseFk = warehouseId);
expect(agencies.length).toEqual(6);
expect(validWarehouse).toBeTruthy();
});
});

View File

@ -1,7 +1,7 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('getLanded', { Self.remoteMethodCtx('getLanded', {
description: 'Returns the first shipped and landed possible for params', description: 'Returns the first shipped and landed possible for params',
accessType: 'READ', accessType: 'READ',
accepts: [{ accepts: [{
@ -23,11 +23,6 @@ module.exports = Self => {
arg: 'warehouseFk', arg: 'warehouseFk',
type: 'number', type: 'number',
required: true required: true
},
{
arg: 'showExpiredZones',
type: 'boolean',
required: true
}], }],
returns: { returns: {
type: 'object', type: 'object',
@ -39,7 +34,13 @@ module.exports = Self => {
} }
}); });
Self.getLanded = async(shipped, addressFk, agencyModeFk, warehouseFk, showExpiredZones) => { Self.getLanded = async(ctx, shipped, addressFk, agencyModeFk, warehouseFk) => {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models;
const isProductionBoss = await models.Account.hasRole(userId, 'productionBoss');
let showExpired = false;
if (isProductionBoss) showExpired = true;
let stmts = []; let stmts = [];
stmts.push(new ParameterizedSQL( stmts.push(new ParameterizedSQL(
`CALL vn.zone_getLanded(?, ?, ?, ?, ?)`, [ `CALL vn.zone_getLanded(?, ?, ?, ?, ?)`, [
@ -47,7 +48,7 @@ module.exports = Self => {
addressFk, addressFk,
agencyModeFk, agencyModeFk,
warehouseFk, warehouseFk,
showExpiredZones showExpired
] ]
)); ));

View File

@ -2,13 +2,13 @@ const app = require('vn-loopback/server/server');
describe('agency getLanded()', () => { describe('agency getLanded()', () => {
it('should return a landing date', async() => { it('should return a landing date', async() => {
const ctx = {req: {accessToken: {userId: 1}}};
const shipped = new Date(); const shipped = new Date();
shipped.setDate(shipped.getDate() + 1); shipped.setDate(shipped.getDate() + 1);
const addressFk = 121; const addressFk = 121;
const agencyModeFk = 7; const agencyModeFk = 7;
const warehouseFk = 1; const warehouseFk = 1;
const showExpiredZones = true; let result = await app.models.Agency.getLanded(ctx, shipped, addressFk, agencyModeFk, warehouseFk);
let result = await app.models.Agency.getLanded(shipped, addressFk, agencyModeFk, warehouseFk, showExpiredZones);
expect(result.landed).toBeDefined(); expect(result.landed).toBeDefined();
}); });

View File

@ -0,0 +1,71 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethodCtx('includingExpired', {
description: 'Returns a list of agencies from a warehouse',
accepts: [{
arg: 'filter',
type: 'Object',
description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string`
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/includingExpired`,
verb: 'GET'
}
});
Self.includingExpired = async(ctx, filter) => {
const userId = ctx.req.accessToken.userId;
const conn = Self.dataSource.connector;
const models = Self.app.models;
const where = filter.where;
const stmts = [];
let stmt;
const filterByAvailability = where.shipped && where.addressFk
&& where.agencyModeFk && where.warehouseFk;
if (filterByAvailability) {
const isProductionBoss = await models.Account.hasRole(userId, 'productionBoss');
let showExpired = false;
if (isProductionBoss) showExpired = true;
stmt = new ParameterizedSQL(`CALL vn.zone_getLanded(?, ?, ?, ?, ?)`, [
where.shipped,
where.addressFk,
where.agencyModeFk,
where.warehouseFk,
showExpired]);
stmts.push(stmt);
}
delete where.shipped;
delete where.addressFk;
delete where.warehouseFk;
stmt = new ParameterizedSQL(
`SELECT id, name, agencyModeFk
FROM vn.zone z`);
if (filterByAvailability)
stmt.merge(`JOIN tmp.zoneGetLanded zgl ON zgl.zoneFk = z.id`);
stmt.merge(conn.makeWhere(filter.where));
let index;
if (stmts.length)
index = stmts.push(stmt) - 1;
else stmts.push(stmt);
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql);
return index ? result[index] : result;
};
};

View File

@ -0,0 +1,40 @@
const app = require('vn-loopback/server/server');
describe('zone includingExpired()', () => {
const inhousePickupId = 1;
const addressId = 101;
const warehouseId = 1;
it('should return an array containing all zones', async() => {
const ctx = {req: {accessToken: {userId: 1}}};
const where = {};
const result = await app.models.Zone.includingExpired(ctx, {where});
expect(result.length).toBeGreaterThan(2);
});
it('should return an array containing zones from the agencyMode "Inhouse pickup"', async() => {
const ctx = {req: {accessToken: {userId: 1}}};
const where = {agencyModeFk: inhousePickupId};
const result = await app.models.Zone.includingExpired(ctx, {where});
const validAgency = result.every(zone => zone.agencyModeFk = inhousePickupId);
expect(result.length).toEqual(3);
expect(validAgency).toBeTruthy();
});
it('should return an array containing available zones', async() => {
const ctx = {req: {accessToken: {userId: 1}}};
const where = {
shipped: new Date(),
addressFk: addressId,
agencyModeFk: inhousePickupId,
warehouseFk: warehouseId
};
const result = await app.models.Zone.includingExpired(ctx, {where});
const firstZone = result[0];
expect(firstZone.name).toEqual('Zone pickup A');
});
});

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/agency-mode/byWarehouse')(Self);
};

View File

@ -5,6 +5,7 @@ module.exports = Self => {
require('../methods/zone/toggleIsIncluded')(Self); require('../methods/zone/toggleIsIncluded')(Self);
require('../methods/zone/getUpcomingDeliveries')(Self); require('../methods/zone/getUpcomingDeliveries')(Self);
require('../methods/zone/deleteZone')(Self); require('../methods/zone/deleteZone')(Self);
require('../methods/zone/includingExpired')(Self);
Self.validatesPresenceOf('agencyModeFk', { Self.validatesPresenceOf('agencyModeFk', {
message: `Agency cannot be blank` message: `Agency cannot be blank`