Analyzing Unscheduled Orders
                    This sample describes how to analyze the reason, why an order remains unscheduled by the xTour service.
                    Benefits
                    
                        - Users learn how to use and integrate the operations of xTour that analyze unscheduled orders.
Prerequisites
                    Please ensure following prerequisites are fulfilled before you start with the use case:
                    
                        - Installed and licensed PTV xTour service
- License for as many  as the plan should contain
Concepts
                    
                    Programming Guide
                    
                This example provides information on how to analyze unscheduled orders to find out why they remained unplanned or if the orders are unplannable.
			
                    
				After passing a PlanToursRequest to the planTours operation of the xTour service, a number of orders may remain unplanned.
                As preparation for the analysis of the unscheduled orders, the plan must be stored using the field storeRequest in the PlanToursRequest.
                The analysis itself depends on whether the orders are unplanned or unplannable.
			
                    Analyze unplanned orders
                    
                For an unplanned order whose id is in orderIdsNotPlanned, but not in orderIdsNotPlannable, we can simply send an InsertionPositionsForOrdersQuery.
                The field orderIds contains the id of the order to analyze.
                Note that only one order can be analyzed at a time.
                If you leave targetVehicleIds empty, then all possible insertion positions for all vehicles are proposed.
                As we expect all insertion positions to be violated, returnViolatedTours has to be set to true.
            
                    
                The resulting proposals contain MoveOrdersActions and AddTripActions.
                Each proposal only contains the changed tour.
                The corresponding TourViolationReport contains the violations occurring when inserting the order at the given position.
                We can use this report to find out what prevented the order from being scheduled.
                As the order is unplanned but not unplannable, an additional vehicle would always be a feasible way to schedule the order.
            
                    
                The following sample contains a simple way of aggregating the violations in the proposals to find out whether the planning horizon or the opening intervals are the reason for an order being unplanned.
                You can modify the initial plan by changing useShortPlanningHorizon:
                
- Set useShortPlanningHorizontotrue, so that an order cannot be planned because of the planning horizon being too short.
- Set useShortPlanningHorizontofalse, so that an order cannot be planned because of an opening interval being too short.
function getPlanToursRequest() {
                    // If this is set to false, a longer planning horizon is used, but the time windows are shorter.
                    var useShortPlanningHorizon = true;  
                    var depotLocation = {
                        "$type": "OffRoadRouteLocation",
                        "offRoadCoordinate": {
                            "x": 6.1035919189453125,
                            "y": 49.61070993807422
                        }
					};
					var customerLocation1 = {
                        "$type" : "OffRoadRouteLocation",
                        "offRoadCoordinate": {
                            "x": 6.147022247314454,
                            "y": 49.59685949756711
                        }
					};
                    var customerLocation2 = {
                        "$type" : "OffRoadRouteLocation",
                        "offRoadCoordinate": {
                            "x": 6.115522384643555,
                            "y": 49.603701773184035
                        }
                    };
                    var customerLocation3 = {
                        "$type" : "OffRoadRouteLocation",
                        "offRoadCoordinate": {
                            "x": 6.156635284423828,
                            "y": 49.616048816070446
                        }
                    };
                    var locations = [
                        {
                            "$type": "DepotSite",
                            "id": "Depot",
                            "routeLocation" : depotLocation
                        },
                        {
                            "$type": "CustomerSite",
                            "id": "Customer1",
                            "routeLocation" : customerLocation1,
                            "openingIntervals": [
                                {
                                    "$type": "StartEndInterval",
                                    "start": "2022-01-01T10:00:00+01:00",
                                    "end": useShortPlanningHorizon ? "2022-01-01T18:00:00+01:00" : "2022-01-01T11:00:00+01:00"
                                }
                            ]
                        },
                        {
                            "$type": "CustomerSite",
                            "id": "Customer2",
                            "routeLocation" : customerLocation2,
                            "openingIntervals": [
                                {
                                    "$type": "StartEndInterval",
                                    "start": "2022-01-01T11:00:00+01:00",
                                    "end": useShortPlanningHorizon ? "2022-01-01T18:00:00+01:00" : "2022-01-01T12:00:00+01:00"
                                }
                            ]
				        },
				        {
				            "$type": "CustomerSite",
				            "id": "Customer3",
				            "routeLocation" : customerLocation3,
                            "openingIntervals": [
                                {
                                    "$type": "StartEndInterval",
                                    "start": "2022-01-01T12:00:00+01:00",
                                    "end": useShortPlanningHorizon ? "2022-01-01T18:00:00+01:00" : "2022-01-01T13:00:00+01:00"
                                }
                            ]
				        },
                    ];
                    var orders = [
                        {
                            "$type": "PickupDeliveryOrder",
                            "id": 1,
				            "pickupLocationId": "Depot",
                            "deliveryLocationId": "Customer1",
				            "serviceTimeForDelivery": 2 * 60 * 60, // 2 hours
                        },
                        {
                            "$type": "PickupDeliveryOrder",
				            "id": 2,
				            "pickupLocationId": "Depot",
							"deliveryLocationId": "Customer2",
							"serviceTimeForDelivery": 2 * 60 * 60, // 2 hours
                        },
                        {
                            "$type": "PickupDeliveryOrder",
				            "id": 3,
				            "pickupLocationId": "Depot",
							"deliveryLocationId": "Customer3",
							"serviceTimeForDelivery": 2 * 60 * 60, // 2 hours
                        }
                    ];
                    var planToursRequest = {
                        "locations": locations,
                        "orders": orders,
                        "fleet": {
                            "vehicles": [
                                {
                                    "ids": [ "vehicle1" ]
                                }
                            ]
                        },
                        "planToursOptions": {
                            "planningHorizon": {
                                "start": "2022-01-01T10:00:00+01:00",
                                "end": useShortPlanningHorizon ? "2022-01-01T15:00:00+01:00" : "2022-01-01T18:00:00+01:00"
                            }
                        },
                        "distanceMode": {
                            "$type": "DirectDistance"
                        },
                        "storeRequest": true
                    };
                    return planToursRequest;
                }
				
                function planToursAndAnalyzeUnplannedOrder() {
                    var planToursRequest = getPlanToursRequest();
                    xtour.planTours(
                        planToursRequest,
                        function(toursResponse, exception) {
                            analyzeUnplannedOrders(planToursRequest, toursResponse);
                        }
                    );
                }
				
				function analyzeUnplannedOrders(planToursRequest, toursResponse) {
					if (toursResponse.orderIdsNotPlanned.length > 0)
					{
                    	var unplannedOrderId = toursResponse.orderIdsNotPlanned[0];
						var proposalsQuery = {
                            "$type": "InsertionPositionsForOrdersQuery",
                            "storedRequestId": toursResponse.storedRequestId,
                            "orderIds": [ unplannedOrderId ],
                            "targetVehicleIds": []
                        };
                        var findChangeToursProposalsRequest = {
                            "proposalsQuery": proposalsQuery,
                            "proposalsOptions": {
                                "returnViolatedTours": true
                            }
                        };
                        xtour.findChangeToursProposals(findChangeToursProposalsRequest,
                            function(changeToursProposalsResponse, exception) {
                               analyzeProposals(unplannedOrderId, changeToursProposalsResponse);
                            }
                        );
					}
					else
					{
						print('All orders were scheduled.');
					}
				}
                
                function analyzeProposals(unplannedOrderId, changeToursProposalsResponse) {
                    var numberOfProposalsWithPlanningHorizonExceedance = 0;
                    var numberOfProposalsWithOpeningIntervalExceedance = 0;
                    for (var index = 0; index < changeToursProposalsResponse.proposals.length; index++) {
                        if (changeToursProposalsResponse.proposals[index].tourReports[0].violationReport.planningHorizonExceedance > 0) {
                            numberOfProposalsWithPlanningHorizonExceedance++;
                        }
                        if (changeToursProposalsResponse.proposals[index].tourReports[0].violationReport.maximumOpeningIntervalExceedance > 0) {
                            numberOfProposalsWithOpeningIntervalExceedance++;
                        }
                    }
                    if (numberOfProposalsWithPlanningHorizonExceedance > 0) {
                        print('An additional vehicle or a longer planning horizon is needed for order ' + unplannedOrderId + ' to be planned.');
                    } else if (numberOfProposalsWithOpeningIntervalExceedance > 0) {
                        print('An additional vehicle or longer opening intervals are needed for order ' + unplannedOrderId + ' to be planned.');
                    }
                }
				
				planToursAndAnalyzeUnplannedOrder();Analyze unplannable orders
                    
                Contrary to the previous part, unplannable orders cannot be served using additional vehicles.
                For a reasonable analysis, we need a plan without tours, so that we can find out the reason for the order being unplannable even if the vehicles do not serve any other order.
            
                    
                In the following sample, the request contains an order with a 20-hours service time.
                Even a completely empty vehicle cannot serve the order, because the planning horizon only has a length of 8 hours.
                After planning, the id of the unplannable order is in orderIdsNotPlannable.
                In function createSingleOrderPlan a plan is created which only contains the unplannable order.
                Function analyzeUnplannableOrders then runs an InsertionPositionsForOrdersQuery like before.
                Only one AddTripAction per vehicle is created.
                We again have a look at the TourViolationReport to find out the reason for the order to be unplannable, which in this example is the planning horizon being too short.
            
                function getPlanToursRequest() {
                    var depotLocation = {
                        "$type": "OffRoadRouteLocation",
                        "offRoadCoordinate": {
                            "x": 6.1035919189453125,
                            "y": 49.61070993807422
                        }
					};
					var customerLocation1 = {
                        "$type" : "OffRoadRouteLocation",
                        "offRoadCoordinate": {
                            "x": 6.147022247314454,
                            "y": 49.59685949756711
                        }
					};
                    var customerLocation2 = {
                        "$type" : "OffRoadRouteLocation",
                        "offRoadCoordinate": {
                            "x": 6.115522384643555,
                            "y": 49.603701773184035
                        }
                    };
                    var customerLocation3 = {
                        "$type" : "OffRoadRouteLocation",
                        "offRoadCoordinate": {
                            "x": 6.156635284423828,
                            "y": 49.616048816070446
                        }
                    };
                    var locations = [
                        {
                            "$type": "DepotSite",
                            "id": "Depot",
                            "routeLocation" : depotLocation
                        },
                        {
                            "$type": "CustomerSite",
                            "id": "Customer1",
                            "routeLocation" : customerLocation1,
                            "openingIntervals": [
                                {
                                    "$type": "StartEndInterval",
                                    "start": "2022-01-01T10:00:00+01:00",
                                    "end": "2022-01-01T18:00:00+01:00"
                                }
                            ]
                        },
                        {
                            "$type": "CustomerSite",
                            "id": "Customer2",
                            "routeLocation" : customerLocation2,
                            "openingIntervals": [
                                {
                                    "$type": "StartEndInterval",
                                    "start": "2022-01-01T10:00:00+01:00",
                                    "end": "2022-01-01T18:00:00+01:00"
                                }
                            ]
				        },
				        {
				            "$type": "CustomerSite",
				            "id": "Customer3",
				            "routeLocation" : customerLocation3,
                            "openingIntervals": [
                                {
                                    "$type": "StartEndInterval",
                                    "start": "2022-01-01T10:00:00+01:00",
                                    "end": "2022-01-01T18:00:00+01:00"
                                }
                            ]
				        },
                    ];
                    var orders = [
                        {
                            "$type": "PickupDeliveryOrder",
                            "id": 1,
				            "pickupLocationId": "Depot",
                            "deliveryLocationId": "Customer1",
				            "serviceTimeForDelivery": 2 * 60 * 60, // 2 hours
                        },
                        {
                            "$type": "PickupDeliveryOrder",
				            "id": 2,
				            "pickupLocationId": "Depot",
							"deliveryLocationId": "Customer2",
							"serviceTimeForDelivery": 2 * 60 * 60, // 2 hours
                        },
                        {
                            "$type": "PickupDeliveryOrder",
				            "id": 3,
				            "pickupLocationId": "Depot",
							"deliveryLocationId": "Customer3",
							"serviceTimeForDelivery": 20 * 60 * 60, // 20 hours!
                        }
                    ];
                    var planToursRequest = {
                        "locations": locations,
                        "orders": orders,
                        "fleet": {
                            "vehicles": [
                                {
                                    "ids": [ "vehicle1" ]
                                }
                            ]
                        },
                        "planToursOptions": {
                            "planningHorizon": {
                                "start": "2022-01-01T10:00:00+01:00",
                                "end": "2022-01-01T18:00:00+01:00"
                            }
                        },
                        "distanceMode": {
                            "$type": "DirectDistance"
                        },
                        "storeRequest": true
                    };
                    return planToursRequest;
                }
				
                function planToursAndAnalyzeUnplannableOrder() {
                    var planToursRequest = getPlanToursRequest();
                    xtour.planTours(
                        planToursRequest,
                        function(toursResponse, exception) {
                            createSingleOrderPlan(planToursRequest, toursResponse);
                        }
                    );
                }
				
				function createSingleOrderPlan(planToursRequest, toursResponse) {
					if (toursResponse.orderIdsNotPlannable.length > 0)
					{
                    	var unplannableOrderId = toursResponse.orderIdsNotPlannable[0];
                        
                        var singleOrderPlanToursRequest = planToursRequest;
                        singleOrderPlanToursRequest.orders = [
                            planToursRequest.orders.find(order => { return order.id == unplannableOrderId; })
                        ];
                        xtour.planTours(
                            singleOrderPlanToursRequest,
                            function(singleOrderToursResponse, exception) {
                                analyzeUnplannableOrders(unplannableOrderId, singleOrderPlanToursRequest, singleOrderToursResponse);
                            }
                        );
					}
					else
					{
						print('No order is unplannable.');
					}
				}
                
                function analyzeUnplannableOrders(unplannableOrderId, singleOrderPlanToursRequest, singleOrderToursResponse) {
                    var proposalsQuery = {
                        "$type": "InsertionPositionsForOrdersQuery",
                        "storedRequestId": singleOrderToursResponse.storedRequestId,
                        "orderIds": [ unplannableOrderId ],
                        "targetVehicleIds": []
                    };
                    var findChangeToursProposalsRequest = {
                        "proposalsQuery": proposalsQuery,
                        "proposalsOptions": {
                            "returnViolatedTours": true
                        }
                    };
                    xtour.findChangeToursProposals(findChangeToursProposalsRequest,
                        function(changeToursProposalsResponse, exception) {
                           analyzeProposals(unplannableOrderId, changeToursProposalsResponse);
                        }
                    );
                }
                
                function analyzeProposals(unplannableOrderId, changeToursProposalsResponse) {
                    var numberOfProposalsWithPlanningHorizonExceedance = 0;
                    for (var index = 0; index < changeToursProposalsResponse.proposals.length; index++) {
                        if (changeToursProposalsResponse.proposals[index].tourReports[0].violationReport.planningHorizonExceedance > 0) {
                            numberOfProposalsWithPlanningHorizonExceedance++;
                        }
                    }
                    if (numberOfProposalsWithPlanningHorizonExceedance > 0) {
                        print('A longer planning horizon is needed for order ' + unplannableOrderId + ' to be planned.');
                    }
                }
				
				planToursAndAnalyzeUnplannableOrder();
			
                    Limiting the resulting number of proposals of an InsertionPositionsForOrdersQuery
                    
                Running an InsertionPositionsForOrdersQuery yields all possible insertion positions for the given order.
                While the number of AddTripActions is limited by the number of vehicles and existing trips, many more MoveOrdersActions may be proposed, depending on the size of the plan.
            
                    
                Therefore, you can limit the number of MoveOrdersActions using the field maximumNumberOfMoveOrdersActions so that at most the given number of nearest insertion positions are returned.
            
                    
                        
                            
Note
                        
                        
                    We recommend not to exceed the default value of 100, as a larger number hardly increases the quality of the proposals, but increases the computation time and the response size.
                
                     
                    
                Additionally, you can limit the proposed MoveOrdersActions using maximumDistanceOfAdjacentStops to a certain area around the customer site of the given order (or in case of a depot-depot transport to an area around both depot sites).
                Only stops in this area may be used as neighboring stops for the insertion.