Initial commit: fixtures, hooks, doctype events, API, and JS
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import json
|
||||
import re
|
||||
import frappe
|
||||
|
||||
@frappe.whitelist()
|
||||
def optimize_routes(pickup_date=None):
|
||||
"""Optimize routes for all trucks on a given date."""
|
||||
if not pickup_date:
|
||||
pickup_date = frappe.utils.today()
|
||||
|
||||
trucks = frappe.get_list("Truck Profile",
|
||||
filters={"active": 1},
|
||||
fields=["name", "truck_name", "total_slots", "weight_capacity"]
|
||||
)
|
||||
|
||||
pickups = frappe.get_list("Scheduled Pickup",
|
||||
filters={
|
||||
"pickup_date": pickup_date,
|
||||
"shipment_type": "Truck",
|
||||
"truck_profile": ["is", "not set"]
|
||||
},
|
||||
fields=["name", "customer_number", "company_name", "estimated_items",
|
||||
"estimated_weight", "gaylord_count", "gaylord_sizes", "slots_needed",
|
||||
"latitude", "longitude"],
|
||||
order_by="stop_order asc"
|
||||
)
|
||||
|
||||
for pickup in pickups:
|
||||
if not pickup.slots_needed and pickup.gaylord_sizes:
|
||||
pickup.slots_needed = _calculate_slots(pickup.gaylord_sizes)
|
||||
elif not pickup.slots_needed:
|
||||
pickup.slots_needed = pickup.gaylord_count or 1
|
||||
|
||||
pickups.sort(key=lambda x: x.slots_needed or 0, reverse=True)
|
||||
|
||||
routes = {}
|
||||
for truck in trucks:
|
||||
routes[truck.name] = {
|
||||
"truck": truck,
|
||||
"pickups": [],
|
||||
"used_slots": 0,
|
||||
"used_weight": 0
|
||||
}
|
||||
|
||||
unassigned = []
|
||||
|
||||
for pickup in pickups:
|
||||
assigned = False
|
||||
for truck_name, route in routes.items():
|
||||
truck = route["truck"]
|
||||
slots = pickup.slots_needed or 0
|
||||
weight = float(pickup.estimated_weight or 0)
|
||||
|
||||
if route["used_slots"] + slots <= truck.total_slots:
|
||||
if truck.weight_capacity and route["used_weight"] + weight <= truck.weight_capacity:
|
||||
route["pickups"].append(pickup)
|
||||
route["used_slots"] += slots
|
||||
route["used_weight"] += weight
|
||||
assigned = True
|
||||
frappe.db.set_value("Scheduled Pickup", pickup.name, "truck_profile", truck_name)
|
||||
break
|
||||
elif not truck.weight_capacity:
|
||||
route["pickups"].append(pickup)
|
||||
route["used_slots"] += slots
|
||||
route["used_weight"] += weight
|
||||
assigned = True
|
||||
frappe.db.set_value("Scheduled Pickup", pickup.name, "truck_profile", truck_name)
|
||||
break
|
||||
if not assigned:
|
||||
unassigned.append(pickup)
|
||||
|
||||
for truck_name, route in routes.items():
|
||||
if route["pickups"]:
|
||||
route["pickups"].sort(key=lambda p: (float(p.latitude or 0), float(p.longitude or 0)))
|
||||
for i, pickup in enumerate(route["pickups"], 1):
|
||||
frappe.db.set_value("Scheduled Pickup", pickup.name, "stop_order", i)
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"date": pickup_date,
|
||||
"trucks_assigned": len([r for r in routes.values() if r["pickups"]]),
|
||||
"total_pickups": len(pickups),
|
||||
"unassigned": len(unassigned),
|
||||
"routes": {
|
||||
truck_name: {
|
||||
"truck_name": route["truck"].truck_name,
|
||||
"slots_used": route["used_slots"],
|
||||
"slots_total": route["truck"].total_slots,
|
||||
"weight_used": route["used_weight"],
|
||||
"weight_capacity": route["truck"].weight_capacity,
|
||||
"stops": len(route["pickups"]),
|
||||
"pickups": [{"name": p.name, "company": p.company_name, "slots": p.slots_needed} for p in route["pickups"]]
|
||||
}
|
||||
for truck_name, route in routes.items() if route["pickups"]
|
||||
}
|
||||
}
|
||||
|
||||
def _calculate_slots(gaylord_sizes_text):
|
||||
if not gaylord_sizes_text:
|
||||
return 0
|
||||
size_map = {"small": 1, "medium": 2, "large": 3}
|
||||
total = 0
|
||||
matches = re.findall(r'(\d+)\s*(\w+)', gaylord_sizes_text.lower())
|
||||
for count, size in matches:
|
||||
slots = size_map.get(size, 1)
|
||||
total += int(count) * slots
|
||||
return total or 1
|
||||
Reference in New Issue
Block a user