File: /var/www/vhost/disk-apps/agile-selling-wpb/resources/views/route_assistant/route_planning.blade.php
@extends('modules.head')
@section('contenido')
@include('route_assistant.modal_new_delivery')
@include('modules.modal_loading')
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">
@lang('messages.route_simulation')
</h3>
<div class="row">
<div class="col-6 col-md-6">
<select id="current_sucursal" class="form-control">
@foreach ($sucursals as $s)
@if(Auth::user()->userInfo->sucursal_id == $s->id)
<option value="{{$s->id}}" data-lat="{{$s->address->lat}}" data-lng="{{$s->address->long}}" selected="selected">{{$s->code}} - {{$s->name}}</option>
@else
<option value="{{$s->id}}" data-lat="{{$s->address->lat}}" data-lng="{{$s->address->long}}">{{$s->code}} - {{$s->name}}</option>
@endif
@endforeach
</select>
</div>
<div class="col-3 col-md-3">
<button class="btn btn-default btnBox pull-right" onclick="planningRoute()">
<i class="fa fa-map-signs"></i>
@lang('messages.simulate_route')
</button>
</div>
<div class="col-3 col-md-3">
<button class="btn btn-default btnBox pull-right" onclick="onTakeAndAssign()">
<i class="fa fa-check"></i>
@lang('messages.take_and_simulate_route')
</button>
</div>
</div>
</div>
<div class="box-body" style="height: 300px; overflow: scroll;">
<div class="row">
<div class="col-sm-6">
<table class="table table-striped table-responsive" style="max-height:100px;" id="pending_orders">
<thead>
<h4 style="display: inline;">@lang('messages.pending_orders')</h4>
<tr>
<th width="8px"><button class="btn btn-default btnBox pull-left" style="font-size: 10px;" onclick="selectAllOrders(this)">Todos</button></th>
<th>@lang('messages.code')</th>
<th>@lang('messages.address')</th>
<th style="display: none;" >Lat</th>
<th style="display: none;">Lng</th>
</tr>
</thead>
<tbody>
@foreach($orders as $o)
<tr>
<td>
<div style="margin-left: 30%;">
<input type="checkbox" id="order_check{{$loop->iteration}}">
<label for="order_check{{$loop->iteration}}"></label>
</div>
</td>
<td>{{$o->code}}</td>
<td>{{$o->address->direction}}@if($o->transferences->count() > 0 ) <strong> ({{ $o->transferences->count() }}) Transferencia</strong> @endif </td>
<td style="display: none;">{{$o->address->lat}}</td>
<td style="display: none;">{{$o->address->long}}</td>
<td style="display: none;">{{$o->transferences}}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="col-sm-6">
<table class="table table-striped table-responsive" style="max-height:100px;" id="delivery_availables">
<thead>
<tr>
<h4 style="display: inline;">
@lang('messages.dealers')
<button type="button" class="btn pull-right" id="btn-open-modal-direction" style="font-size: 12px; padding: 0px" data-toggle="modal"
data-target="#modal_new_delivery">
<i class="fa fa-plus"></i>
Repartidor externo
</button>
</h4>
<th width="8px"><button class="btn btn-default btnBox pull-left" style="font-size: 10px;" onclick="selectAllDeliveries(this)">Todos</button></th>
<th>@lang('messages.name')</th>
<th>@lang('messages.company')</th>
<th>@lang('messages.phone')</th>
<th style="display: none;" >ID</th>
</tr>
</thead>
<tbody id="tbody_delivery">
@foreach($deliveries as $d)
<tr>
<td>
<div style="margin-left: 30%; margin-top: 20px;">
<input type="checkbox" class="custom-control-input" id="delivery_check{{$loop->iteration}}">
<label class="custom-control-label" for="delivery_check{{$loop->iteration}}"></label>
</div>
</td>
<td><img src="{{ asset('img/no_image2.png') }}" style="display: inline;" width="50px" alt="Producto 1"> {{$d->first_name}}</td>
<td style="padding-top: 20px;">{{$d->userInfo->company->name}}</td>
<td style="padding-top: 20px;">{{$d->phone}}</td>
<td style="display: none;">{{$d->id}}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="maps" id="map_zone">
</div>
@endsection
<script>
function onTakeAndAssign(){
var routes = window.finalRoute,
deliveries = window.finalDeliveries;
if(!routes || !deliveries){
swal("Por favor realice al menos una simulación para continuar.", "", "error");
return;
}
swal("Desea tomar la ruta y realizar la asignación de los pedidos ?", {
buttons: {
cancel: "No.",
si: {
text: "Si, aceptar",
value: "si",
}
},
})
.then((value) => {
switch (value) {
case "si":
makeAssign(routes, deliveries);
break;
default:
swal("Se ha cancelado el proceso.");
}
});
}
function makeAssign(routes, deliveries){
swal("Se ha realizado la asignación correctamente.", "", "success");
// Ajax con las rutas y los mensajeros.
$.when(sendToServerAssign(routes, deliveries)).done(function(data, textStatus, jqXHR){
});
}
function sendToServerAssign(routes, deliveries){
var data = {
routes: routes,
deliveries: deliveries,
};
return $.ajax({
type: "POST",
data: JSON.stringify(data),
dataType: "json",
url: "/assignPlan",
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
contentType: 'application/json',
success: function(r) {
$(location).attr('href', '/route/planning');
},
error: function(err){
swal('Ha ocurrido un error por favor intente nuevamente en un momento.');
hideModal();
}
});
}
function putGmaps(currentRoute, round, markers, distanceEstimation, timeEstimation){
// Fix decimals
timeEstimation = Number((timeEstimation).toFixed(2));
distanceEstimation = Number((distanceEstimation).toFixed(2));
var a = `<div class="box">
<div class="box-body" id="map_zone">
<a class="btn btn-block btn-social btn-bitbucket" href='${currentRoute}' target='_blank'>
<i class="fa fa-flag-checkered"></i> ABRIR RUTA SUGERIDA # ${round}
</a>
<p><b>Recorrido estimado:${distanceEstimation} km</b></p>
<p><b>Tiempo estimado:${timeEstimation} min</b></p>
<div id="map_canvas${round}" style="width:100%; height:500px;"></div>
</div>
</div>`;
$('#map_zone').append(a);
initializeMap('map_canvas' + round, round, markers);
}
function initializeMap(mapId, round, markers) {
// create the maps
var myOptions = {
zoom: 11,
center: new google.maps.LatLng(parseFloat(markers[0].lat), parseFloat(markers[0].lng)),
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById(mapId), myOptions);
putMarkersOnMap(map, markers);
}
function putMarkersOnMap(map, markers){
var cont = 0,
initialPoint = null,
myLatLng = null;
markers.forEach(function(m){
cont++;
myLatLng = {lat: parseFloat(m.lat), lng: parseFloat(m.lng)};
if(m.name == "home"){
map.setCenter(myLatLng);
var marker = new google.maps.Marker({
position: myLatLng,
map: map,
title: "Home",
label: "Home"
});
}else{
var marker = new google.maps.Marker({
position: myLatLng,
map: map,
title: "" + cont,
label: "" + cont,
animation: google.maps.Animation.DROP,
});
}
});
}
function selectAllOrders(me){
var isAll = true;
if(me.innerText == "Todos"){
me.innerText = "Ninguno"
}else{
me.innerText = "Todos"
isAll = false;
}
$('#pending_orders tr').each(function(i) {
var $chkbox = $(this).find('input[type="checkbox"]');
if(isAll){
$chkbox.prop('checked', true);
}else{
$chkbox.prop('checked', false);
}
});
}
function selectAllDeliveries(me){
var isAll = true;
if(me.innerText == "Todos"){
me.innerText = "Ninguno"
}else{
me.innerText = "Todos"
isAll = false;
}
$('#delivery_availables tr').each(function(i) {
var $chkbox = $(this).find('input[type="checkbox"]');
if(isAll){
$chkbox.prop('checked', true);
}else{
$chkbox.prop('checked', false);
}
});
}
function addNewDelivery(){
var name = $('#new_name_delivery').val(),
enterprise = $('#new_enterprise').val(),
phone = $('#new_phone').val();
if(!name || !enterprise || !phone ){
swal('', 'Por favor complete todos los campos','error');
return;
}
var d = new Date();
var n = d.getTime();
var s = `<tr><td><div class="custom-control custom-checkbox">
<div style="margin-left: 30%; margin-top: 20px;">
<input type="checkbox" class="custom-control-input" id="delivery_check${n}"><label class="custom-control-label" for="delivery_check${n}"></label></div>
</div>
</td><td><img src="/img/no_image2.png" style="display: inline;" width="50px" alt="Producto 1">${name}</td>
<td style="padding-top: 20px;">${enterprise}</td>
<td style="padding-top: 20px;">${phone}</td>
<td style="display: none;">free</td>
</tr>`;
$('#tbody_delivery').append(s);
$('#modal_new_delivery').modal('hide');
}
function getLocationsAndOrders(){
var locationsAndOrders = [],
locations = [],
orders = [],
transferences = [];
$('#pending_orders tr').each(function(i) {
var $chkbox = $(this).find('input[type="checkbox"]');
if($chkbox.prop('checked')){
var code = $(this).find("td:eq(1)").text(),
address = $(this).find("td:eq(2)").text(),
lat = $(this).find("td:eq(3)").text(),
long = $(this).find("td:eq(4)").text();
locations.push({ address: code, lat: lat, lng: long });
orders.push( {code: code} );
if(JSON.parse($(this).find("td:eq(5)").text()).length > 0){
transferences[code] = JSON.parse($(this).find("td:eq(5)").text());
}
}
});
locationsAndOrders['locations'] = locations;
locationsAndOrders['orders'] = orders;
locationsAndOrders['transferences'] = transferences;
return locationsAndOrders;
}
function countKeys(obj) {
var count=0;
for(var prop in obj) {
if (obj.hasOwnProperty(prop)) {
++count;
}
}
return count;
}
function getRounds(){
var rounds = 0;
window.finalDeliveries = [];
$('#delivery_availables tr').each(function(i) {
var $chkbox = $(this).find('input[type="checkbox"]');
var id = $(this).find("td:eq(4)").text();
if($chkbox.prop('checked')){
var oDelivery = {
id: id
};
window.finalDeliveries.push(oDelivery);
rounds++;
}
});
return rounds;
}
function setRoutes(data, transferences){
var round = 1,
cont = 0,
initialPoint,
currentRoute = "",
currentOrders = [],
goAndBack = true,
isTransferenceRoute = false,
transferencesRoute = [],
distanceEpsilon = 0.5,
timeEpsilon = 5,
baseGmaps = "https://www.google.com/maps/dir/",
initialPoint = { name: 'home', lat: $('#current_sucursal').find(':selected').data('lat'), lng:$('#current_sucursal').find(':selected').data('lng'), arrival: 0, distance: 0 };
// Clear div
$('#map_zone').html("");
for(var r in data.route){
// Se verifica si la ruta contiene transferencias, si esta contiene, se debe recalcular.
if(transferences[data.route[r].name]){
transferencesRoute = transferences[data.route[r].name];
isTransferenceRoute = true;
}
// Si el punto evaluando actualmente es el inicial indica que inicia otro tramo.
if(initialPoint.name == data.route[r].name && cont != 0){
if(isTransferenceRoute){
recalculateWithTransferences(currentOrders, transferencesRoute, initialPoint);
currentRoute = "";
currentOrders = [];
transferencesRoute = [];
isTransferenceRoute = false;
continue;
}
if(goAndBack){
currentRoute = baseGmaps + currentRoute + initialPoint.lat + "," + initialPoint.lng;
currentOrders.push(initialPoint);
}else{
currentRoute = baseGmaps + currentRoute;
}
var distanceEstimation = data.route[r].distance - initialPoint.distance + distanceEpsilon,
timeEstimation = data.route[r].arrival - initialPoint.arrival + timeEpsilon;
initialPoint = data.route[r];
// Capture Route
window.finalRoute.push(currentOrders);
putGmaps(currentRoute, round, currentOrders, distanceEstimation, timeEstimation);
round++;
currentRoute = "";
currentOrders = [];
currentOrders.push(initialPoint);
continue;
}
currentOrders.push(data.route[r]);
currentRoute = currentRoute + data.route[r].lat + "," + data.route[r].lng + "/";
cont++;
}
window.maxRounds = round;
}
// Para este proceso se tiene en cuenta el "Due" para dar prioridad a las transferencias antes de realizar la entrega del pedido que la requiere.
function recalculateWithTransferences(route, transferencesRoute, initialPoint){
var due = 5;
for(var i =0; i < transferencesRoute.length; i++){
route.push({ address:transferencesRoute[i].description, lat: transferencesRoute[0].sucursal.address.lat, lng: transferencesRoute[0].sucursal.address.long, restrictions: { due: due } });
due+=5;
}
route.push(initialPoint);
$.when(ajaxRouteXL(route, 1)).done(function(data, textStatus, jqXHR){
hideModal();
setRouteTransference(data);
});
}
// Asignar una ruta de transferencia, especificando en el encabezado que es una ruta con transferencia
function setRouteTransference(data, transferences){
var round = window.maxRounds,
cont = 0,
initialPoint,
currentRoute = "",
currentOrders = [],
distanceEpsilon = 0.5,
timeEpsilon = 5,
goAndBack = true,
baseGmaps = "https://www.google.com/maps/dir/",
initialPoint = { name: 'home', lat: $('#current_sucursal').find(':selected').data('lat'), lng:$('#current_sucursal').find(':selected').data('lng'), arrival: 0, distance: 0 };
for(var r in data.route){
// Si el punto evaluando actualmente es el inicial indica que inicia otro tramo.
if(initialPoint.name == data.route[r].name && cont != 0){
if(goAndBack){
currentRoute = baseGmaps + currentRoute + initialPoint.lat + "," + initialPoint.lng;
currentOrders.push(initialPoint);
}else{
currentRoute = baseGmaps + currentRoute;
}
var distanceEstimation = data.route[r].distance - initialPoint.distance + distanceEpsilon,
timeEstimation = data.route[r].arrival - initialPoint.arrival + timeEpsilon;
initialPoint = data.route[r];
// Capture Route
window.finalRoute.push(currentOrders);
putGmaps(currentRoute, round + ' (Incluye transferencias)', currentOrders, distanceEstimation, timeEstimation);
round++;
currentRoute = "";
currentOrders = [];
currentOrders.push(initialPoint);
continue;
}
currentOrders.push(data.route[r]);
currentRoute = currentRoute + data.route[r].lat + "," + data.route[r].lng + "/";
cont++;
}
window.maxRounds = round;
}
function showModal(){
$('#modalLoader').modal('show');
$('#modalLoader').css('display', 'flex');
}
function hideModal(){
$('#modalLoader').modal('hide');
$('#modalLoader').css('display', 'none');
}
function planningRoute(){
var arrLocations = getLocationsAndOrders(),
locations = arrLocations.locations,
orders = arrLocations.orders,
transferences = arrLocations.transferences,
rounds = getRounds();
window.finalRoute = [];
if(countKeys( locations ) < 2){
swal('Por favor indique al menos 2 pedidos para realizar la planificación.', '', 'error');
return;
}
if(rounds == 0){
swal('Por favor indique al menos 1 repartidor para realizar la planificación.', '', 'error');
return;
}
// Add Start and End point
var selected_sucursal = $('#current_sucursal').find(':selected'),
home_location = { address: "home", lat: selected_sucursal.data('lat')+"", lng:selected_sucursal.data('lng')+"" };
// Start
locations.unshift(home_location);
//End
locations.push(home_location);
showModal();
$.when(ajaxRouteXL(locations, rounds)).done(function(data, textStatus, jqXHR){
setRoutes(data, transferences);
hideModal();
});
}
function ajaxRouteXL(locations, rounds){
var data = {
locations: locations,
rounds: rounds
};
return $.ajax({
type: "POST",
data: JSON.stringify(data),
dataType: "json",
url: "/calculate_route",
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
contentType: 'application/json',
error: function(err){
swal('Ha ocurrido un error por favor intente nuevamente en un momento.');
hideModal();
}
});
}
</script>
</script>
<!--Load the API from the specified URL
* The async attribute allows the browser to render the page while the API loads
* The key parameter will contain your own API key (which is not needed for this tutorial)
* The callback parameter executes the initMap() function
-->
<script async defer
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBkgxwtElGfHz5bnO9u5zypbzy9IrfflM0">
</script>