feat: implement route assignment management and sequencing module with interactive data visualization and editing capabilities

This commit is contained in:
2026-08-31 17:56:33 -04:00
parent cd5c57c5ee
commit 9f42175afc
29 changed files with 458 additions and 218 deletions
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { TableModule } from 'primeng/table';
import { RouteNoAssignedResponse } from '../../../core/models/route.model';
import { RouteState } from '../../../core/route-state/route';
import { RouteState, UnassignedAssignment } from '../../../core/route-state/route';
import { Subscription } from 'rxjs';
import { OrsService } from '../../../core/services/ors.service';
@@ -21,7 +21,7 @@ export class NoAssignedRouteComponent implements OnDestroy {
cols: { field: string; header: string }[] = [];
private subs: Subscription[] = [];
assignmentUser: Record<string, string> = {};
assignmentDay: Record<string, string> = {};
assignmentDays: Record<string, string[]> = {};
assigningId: number | null = null;
actionError: string | null = null;
@@ -62,11 +62,18 @@ export class NoAssignedRouteComponent implements OnDestroy {
return this.routesState.getDiasUnicos();
}
toggleDay(pdvId: number, day: string, checked: boolean): void {
const key = String(pdvId);
const days = new Set(this.assignmentDays[key] ?? []);
checked ? days.add(day) : days.delete(day);
this.assignmentDays[key] = Array.from(days);
}
async assignRoute(route: RouteNoAssignedResponse): Promise<void> {
const userId = this.assignmentUser[String(route.pdv_id)];
const day = this.assignmentDay[String(route.pdv_id)];
if (!userId || !day) {
this.actionError = 'Selecciona usuario y día antes de asignar.';
const days = this.assignmentDays[String(route.pdv_id)] ?? [];
if (!userId || !days.length) {
this.actionError = 'Selecciona usuario y al menos un día antes de asignar.';
return;
}
const user = this.routesState.getAssignedSnapshot().find(item => String(item.json.usuario_id) === userId);
@@ -76,13 +83,22 @@ export class NoAssignedRouteComponent implements OnDestroy {
try {
const origin = [Number(user.json.longitud_usuario), Number(user.json.latitud_usuario)];
const destination = [Number(route.longitud), Number(route.latitud)];
const result = await this.orsService.processPairs([{ pdv_id: route.pdv_id, origin, destination }], 1, true);
const geo = (result[0] as { geojson?: { features?: { properties?: { summary?: { distance: number; duration: number } } }[] } }).geojson;
const summary = geo?.features?.[0]?.properties?.summary;
if (!summary) throw new Error('ORS no devolvió una ruta vial');
this.routesState.addAssignedFromUnassigned(route, userId, day, summary.distance / 1000, summary.duration / 3600);
const assignments: UnassignedAssignment[] = [];
for (const day of days) {
const result = await this.orsService.processPairs([{ pdv_id: route.pdv_id, origin, destination }], 1, true);
const geo = (result[0] as { geojson?: { features?: { properties?: { summary?: { distance: number; duration: number } } }[] } }).geojson;
const summary = geo?.features?.[0]?.properties?.summary;
if (!summary) throw new Error('ORS no devolvió una ruta vial');
const capacity = this.routesState.canAssignToDay(userId, day, route.minimo_horas_semana, undefined, summary.duration / 3600);
if (!capacity.allowed) {
this.actionError = `${day}: excede la capacidad diaria (${capacity.totalHours.toFixed(1)} h de ${capacity.capacityHours.toFixed(1)} h).`;
return;
}
assignments.push({ userId, day, distance: summary.distance / 1000, duration: summary.duration / 3600 });
}
this.routesState.addAssignedFromUnassignedMany(route, assignments);
delete this.assignmentUser[String(route.pdv_id)];
delete this.assignmentDay[String(route.pdv_id)];
delete this.assignmentDays[String(route.pdv_id)];
} catch {
this.actionError = `No se pudo calcular la ruta vial para ${route.pdv_nombre}.`;
} finally {