feat: refs #7882 Osrm service
gitea/salix/pipeline/pr-dev There was a failure building this commit
Details
gitea/salix/pipeline/pr-dev There was a failure building this commit
Details
This commit is contained in:
parent
cba5d88c5e
commit
c8ec94bed9
|
@ -9,9 +9,13 @@ module.exports = Self => {
|
|||
arg: 'addressIds',
|
||||
type: 'array',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'firstAddressId',
|
||||
type: 'number',
|
||||
required: false
|
||||
}],
|
||||
returns: {
|
||||
type: 'string',
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
|
@ -20,21 +24,22 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.optimize = async addressIds => {
|
||||
Self.optimize = async(addressIds, firstAddressId) => {
|
||||
const models = Self.app.models;
|
||||
try {
|
||||
const osrmConfig = await models.OsrmConfig.findOne();
|
||||
if (!osrmConfig) throw new UserError(`OSRM service is not configured`);
|
||||
|
||||
let coords = [];
|
||||
|
||||
const address = await models.Address.findById(32308); // Aquí irá el address asociada a la zona
|
||||
if (address.latitude && address.longitude) {
|
||||
coords.push({
|
||||
addressId: address.id,
|
||||
latitude: address.latitude.toFixed(6),
|
||||
longitude: address.longitude.toFixed(6)
|
||||
});
|
||||
if (firstAddressId) {
|
||||
const firstAddress = await models.Address.findById(firstAddressId);
|
||||
if (firstAddress.latitude && firstAddress.longitude) {
|
||||
coords.push({
|
||||
addressId: firstAddress.id,
|
||||
latitude: firstAddress.latitude.toFixed(6),
|
||||
longitude: firstAddress.longitude.toFixed(6)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const addressId of addressIds) {
|
||||
|
@ -47,6 +52,9 @@ module.exports = Self => {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!coords.length) throw new UserError('No address has coordinates');
|
||||
|
||||
const concatCoords = coords
|
||||
.map(coord => `${coord.longitude},${coord.latitude}`)
|
||||
.join(';');
|
||||
|
|
|
@ -1,7 +1,20 @@
|
|||
CREATE TABLE `vn`.`osrmConfig` (
|
||||
`id` int(10) unsigned NOT NULL,
|
||||
`url` varchar(100) NOT NULL COMMENT 'Dirección base de la API',
|
||||
`tolerance` decimal(6,6) NOT NULL DEFAULT 0 COMMENT 'Tolerancia entre las coordenadas enviadas y las retornadas',
|
||||
PRIMARY KEY (`id`),
|
||||
CONSTRAINT `osrmConfig_check` CHECK (`id` = 1)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
`id` int(10) unsigned NOT NULL,
|
||||
`url` varchar(100) NOT NULL COMMENT 'Dirección base de la API',
|
||||
`tolerance` decimal(6,6) NOT NULL DEFAULT 0 COMMENT 'Tolerancia entre las coordenadas enviadas y las retornadas',
|
||||
PRIMARY KEY (`id`),
|
||||
CONSTRAINT `osrmConfig_check` CHECK (`id` = 1)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
-- Para que no de error al añadir la FK de zone
|
||||
UPDATE vn.zone
|
||||
SET price = 0.1
|
||||
WHERE price = 0;
|
||||
|
||||
ALTER TABLE vn.`zone`
|
||||
ADD addressFk int(11) DEFAULT NULL COMMENT 'Punto de distribución de donde salen para repartir',
|
||||
ADD CONSTRAINT zone_address_FK FOREIGN KEY (addressFk) REFERENCES vn.address(id) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE vn.zoneConfig
|
||||
ADD defaultAddressFk int(11) DEFAULT NULL NULL COMMENT 'Punto de distribución por defecto',
|
||||
ADD CONSTRAINT zoneConfig_address_FK FOREIGN KEY (defaultAddressFk) REFERENCES vn.address(id) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
|
|
@ -393,5 +393,6 @@
|
|||
"There are tickets to be invoiced": "Hay tickets para esta zona, borralos primero",
|
||||
"No trips found because input coordinates are not connected": "No se encontraron rutas porque las coordenadas de entrada no están conectadas",
|
||||
"This request is not supported": "Esta solicitud no es compatible",
|
||||
"Invalid options or too many coordinates": "Opciones invalidas o demasiadas coordenadas"
|
||||
"Invalid options or too many coordinates": "Opciones invalidas o demasiadas coordenadas",
|
||||
"No address has coordinates": "Ninguna dirección tiene coordenadas"
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('optimizePriority', {
|
||||
description: 'Updates the ticket priority of tickets without priority',
|
||||
accepts: {
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'Route id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: '/:id/optimizePriority',
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.optimizePriority = async(id, options) => {
|
||||
const models = Self.app.models;
|
||||
const myOptions = {};
|
||||
let tx;
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const ticketsIds = await models.Ticket.find({
|
||||
where: {routeFk: id}
|
||||
}, myOptions);
|
||||
|
||||
let ticketAddress = [];
|
||||
for (const ticketId of ticketsIds) {
|
||||
ticketAddress.push({
|
||||
ticketId: ticketId.id,
|
||||
addressId: ticketId.addressFk,
|
||||
zoneId: ticketId.zoneFk,
|
||||
priority: ticketId.priority
|
||||
});
|
||||
}
|
||||
|
||||
// Igualamos los priority del mismo addressId
|
||||
const addressPriorityMap = ticketAddress.reduce((acc, {addressId, priority}) => {
|
||||
if (priority !== null) {
|
||||
acc[addressId] = acc[addressId] === undefined
|
||||
? priority
|
||||
: Math.max(acc[addressId], priority);
|
||||
}
|
||||
return acc;
|
||||
});
|
||||
ticketAddress.forEach(item => {
|
||||
const maxPriority = addressPriorityMap[item.addressId];
|
||||
if (maxPriority) item.priority = maxPriority;
|
||||
});
|
||||
|
||||
// Añadimos las direcciones a optimizar
|
||||
let addressIds = [];
|
||||
ticketAddress.forEach(h => {
|
||||
if (!addressIds.includes(h.addressId) && !h.priority)
|
||||
addressIds.push(h.addressId);
|
||||
});
|
||||
if (!addressIds.length) throw new UserError('All tickets have a route order');
|
||||
|
||||
// Obtenemos el zoneId más frecuente
|
||||
const zoneFrequency = ticketAddress.reduce((acc, {zoneId}) => {
|
||||
if (zoneId != null) acc[zoneId] = (acc[zoneId] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
const [mostFrequentZoneId] = Object.entries(zoneFrequency)
|
||||
.reduce((maxEntry, entry) => entry[1] > maxEntry[1] ? entry : maxEntry, [null, 0]);
|
||||
const zone = await models.Zone.findById(mostFrequentZoneId, myOptions);
|
||||
let firstAddress = zone.addressFk;
|
||||
if (!firstAddress) firstAddress = (await models.ZoneConfig.findOne()).defaultAddressFk;
|
||||
|
||||
// Revisamos las coincidencias y actualizamos la prioridad en el array
|
||||
const addressPositions = await models.OsrmConfig.optimize(addressIds, firstAddress, myOptions);
|
||||
const maxPosition = Math.max(...ticketAddress.map(g => g.priority));
|
||||
await Promise.all(ticketAddress.map(async i => {
|
||||
const foundPosition = addressPositions.coords.find(item => item.addressId === i.addressId);
|
||||
if (foundPosition) i.priority = foundPosition.position + (maxPosition + 1);
|
||||
}));
|
||||
|
||||
// Suavizado de prioridad para que no hayan escalones
|
||||
const allPriorities = ticketAddress
|
||||
.map(item => item.priority)
|
||||
.filter(p => p !== null);
|
||||
const uniquePriorities = [...new Set(allPriorities)].sort((a, b) => a - b);
|
||||
const priorityMap = {};
|
||||
uniquePriorities.forEach((p, index) => {
|
||||
priorityMap[p] = index + 1;
|
||||
});
|
||||
ticketAddress.forEach(item => {
|
||||
if (item.priority !== null) item.priority = priorityMap[item.priority];
|
||||
});
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
// Realizamos el update en la base de datos
|
||||
try {
|
||||
await Promise.all(ticketAddress.map(async y => {
|
||||
if (y.priority) {
|
||||
const ticket = await models.Ticket.findById(y.ticketId);
|
||||
await ticket.updateAttribute('priority', y.priority, myOptions);
|
||||
}
|
||||
}));
|
||||
if (tx) await tx.commit();
|
||||
return;
|
||||
} catch (err) {
|
||||
if (tx) await tx.rollback();
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('optimizeStops', {
|
||||
description: 'Updates the ticket priority of tickets without priority',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'routeFk',
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: '/optimizeStops',
|
||||
verb: 'post'
|
||||
}
|
||||
});
|
||||
|
||||
Self.optimizeStops = async(routeFk, options) => {
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
|
@ -16,5 +16,5 @@ module.exports = Self => {
|
|||
require('../methods/route/downloadZip')(Self);
|
||||
require('../methods/route/getExpeditionSummary')(Self);
|
||||
require('../methods/route/getByWorker')(Self);
|
||||
require('../methods/route/optimizeStops')(Self);
|
||||
require('../methods/route/optimizePriority')(Self);
|
||||
};
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
"ZoneClosure": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ZoneConfig": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ZoneEvent": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "ZoneConfig",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "zoneConfig"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"scope": {
|
||||
"type": "number"
|
||||
},
|
||||
"forwardDays": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"address": {
|
||||
"type": "belongsTo",
|
||||
"model": "Address",
|
||||
"foreignKey": "defaultAddressFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"name": "Zone",
|
||||
"base": "VnModel",
|
||||
"mixins": {
|
||||
"Loggable": true
|
||||
},
|
||||
"mixins": {
|
||||
"Loggable": true
|
||||
},
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "zone"
|
||||
|
@ -48,30 +48,35 @@
|
|||
}
|
||||
},
|
||||
"relations": {
|
||||
"agencyMode": {
|
||||
"type": "belongsTo",
|
||||
"model": "AgencyMode",
|
||||
"foreignKey": "agencyModeFk"
|
||||
"agencyMode": {
|
||||
"type": "belongsTo",
|
||||
"model": "AgencyMode",
|
||||
"foreignKey": "agencyModeFk"
|
||||
},
|
||||
"events": {
|
||||
"type": "hasMany",
|
||||
"model": "ZoneEvent",
|
||||
"foreignKey": "zoneFk"
|
||||
},
|
||||
"type": "hasMany",
|
||||
"model": "ZoneEvent",
|
||||
"foreignKey": "zoneFk"
|
||||
},
|
||||
"exclusions": {
|
||||
"type": "hasMany",
|
||||
"model": "ZoneExclusion",
|
||||
"type": "hasMany",
|
||||
"model": "ZoneExclusion",
|
||||
"foreignKey": "zoneFk"
|
||||
},
|
||||
"warehouses": {
|
||||
"type": "hasMany",
|
||||
"model": "ZoneWarehouse",
|
||||
"foreignKey": "zoneFk"
|
||||
},
|
||||
},
|
||||
"closures": {
|
||||
"type": "hasMany",
|
||||
"model": "ZoneClosure",
|
||||
"foreignKey": "zoneFk"
|
||||
}
|
||||
}
|
||||
},
|
||||
"address": {
|
||||
"type": "belongsTo",
|
||||
"model": "Address",
|
||||
"foreignKey": "addressFk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue