Fix distance calculation and display in Admin, Dashboards, and Notifications

This commit is contained in:
Flatlogic Bot 2026-01-31 09:58:55 +00:00
parent bc518a1fbf
commit d43de11987
6 changed files with 71 additions and 23 deletions

View File

@ -191,6 +191,29 @@ class Parcel(models.Model):
def save(self, *args, **kwargs):
if not self.tracking_number:
self.tracking_number = str(uuid.uuid4().hex[:10]).upper()
# Calculate Distance and Price if Lat/Lng provided and price is 0 (or always update to ensure accuracy)
# We only recalculate if status is pending or if it's a new object to avoid changing history for completed trips
is_new = self.pk is None
if (is_new or self.status == 'pending') and self.pickup_lat and self.pickup_lng and self.delivery_lat and self.delivery_lng:
# Local import to avoid circular dependency
from .pricing import calculate_haversine_distance, get_pricing_breakdown
# Calculate Distance
dist = calculate_haversine_distance(
self.pickup_lat, self.pickup_lng,
self.delivery_lat, self.delivery_lng
)
self.distance_km = dist
# Calculate Price
breakdown = get_pricing_breakdown(self.distance_km, self.weight)
if not breakdown.get('error'):
self.price = breakdown['price']
self.platform_fee = breakdown['platform_fee']
self.platform_fee_percentage = breakdown['platform_fee_percentage']
self.driver_amount = breakdown['driver_amount']
super().save(*args, **kwargs)
def __str__(self):

View File

@ -38,13 +38,13 @@ DEFAULT_TEMPLATES = {
},
'shipment_created_shipper': {
'description': 'Sent to Shipper when they create a shipment',
'variables': '{{ name }}, {{ description }}, {{ tracking_number }}, {{ status }}',
'variables': '{{ name }}, {{ description }}, {{ tracking_number }}, {{ status }}, {{ distance }}, {{ price }}',
'subject_en': 'Shipment Request Received - {{ tracking_number }}',
'subject_ar': 'تم استلام طلب الشحنة - {{ tracking_number }}',
'email_body_en': "Hello {{ name }},\n\nYour shipment request for '{{ description }}' has been received.\nTracking Number: {{ tracking_number }}\nStatus: {{ status }}\n\nPlease proceed to payment to make it visible to drivers.",
'email_body_ar': "مرحباً {{ name }}،\n\nتم استلام طلب الشحنة '{{ description }}'.\nرقم التتبع: {{ tracking_number }}\nالحالة: {{ status }}\n\nيرجى متابعة الدفع لجعلها مرئية للسائقين.",
'whatsapp_body_en': "Hello {{ name }},\nYour shipment request for '{{ description }}' has been received.\nTracking Number: {{ tracking_number }}\nStatus: {{ status }}\nPlease proceed to payment.",
'whatsapp_body_ar': "مرحباً {{ name }}،\nتم استلام طلب الشحنة '{{ description }}'.\nرقم التتبع: {{ tracking_number }}\nالحالة: {{ status }}\nيرجى الدفع.",
'email_body_en': "Hello {{ name }},\n\nYour shipment request for '{{ description }}' has been received.\nTracking Number: {{ tracking_number }}\nDistance: {{ distance }} km\nPrice: {{ price }} OMR\nStatus: {{ status }}\n\nPlease proceed to payment to make it visible to drivers.",
'email_body_ar': "مرحباً {{ name }}،\n\nتم استلام طلب الشحنة '{{ description }}'.\nرقم التتبع: {{ tracking_number }}\nالمسافة: {{ distance }} كم\nالسعر: {{ price }} ر.ع\nالحالة: {{ status }}\n\nيرجى متابعة الدفع لجعلها مرئية للسائقين.",
'whatsapp_body_en': "Hello {{ name }}\nYour shipment request for '{{ description }}' has been received.\nTracking Number: {{ tracking_number }}\nDistance: {{ distance }} km\nPrice: {{ price }} OMR\nStatus: {{ status }}\nPlease proceed to payment.",
'whatsapp_body_ar': "مرحباً {{ name }}،\nتم استلام طلب الشحنة '{{ description }}'.\nرقم التتبع: {{ tracking_number }}\nالمسافة: {{ distance }} كم\nالسعر: {{ price }} ر.ع\nالحالة: {{ status }}\nيرجى الدفع.",
},
'payment_success_shipper': {
'description': 'Sent to Shipper after payment',
@ -88,13 +88,13 @@ DEFAULT_TEMPLATES = {
},
'driver_pickup_driver': {
'description': 'Sent to Driver upon acceptance',
'variables': '{{ tracking_number }}, {{ shipper_name }}, {{ pickup_address }}, {{ delivery_address }}, {{ price }}',
'variables': '{{ tracking_number }}, {{ shipper_name }}, {{ pickup_address }}, {{ delivery_address }}, {{ price }}, {{ distance }}',
'subject_en': 'Shipment Accepted - {{ tracking_number }}',
'subject_ar': 'تم قبول الشحنة - {{ tracking_number }}',
'email_body_en': 'You have successfully accepted Shipment {{ tracking_number }}.\nShipper: {{ shipper_name }}\nPickup: {{ pickup_address }}\nDelivery: {{ delivery_address }}\nPrice: {{ price }} OMR',
'email_body_ar': 'لقد قبلت الشحنة {{ tracking_number }} بنجاح.\nالشاحن: {{ shipper_name }}\nالاستلام: {{ pickup_address }}\nالتوصيل: {{ delivery_address }}\nالسعر: {{ price }} ر.ع',
'whatsapp_body_en': 'You have successfully accepted Shipment {{ tracking_number }}.\nShipper: {{ shipper_name }}\nPickup: {{ pickup_address }}\nDelivery: {{ delivery_address }}\nPrice/Bid: {{ price }} OMR',
'whatsapp_body_ar': 'لقد قبلت الشحنة {{ tracking_number }} بنجاح.\nالشاحن: {{ shipper_name }}\nالاستلام: {{ pickup_address }}\nالتوصيل: {{ delivery_address }}\nالسعر: {{ price }} ر.ع',
'email_body_en': 'You have successfully accepted Shipment {{ tracking_number }}.\nShipper: {{ shipper_name }}\nPickup: {{ pickup_address }}\nDelivery: {{ delivery_address }}\nDistance: {{ distance }} km\nPrice: {{ price }} OMR',
'email_body_ar': 'لقد قبلت الشحنة {{ tracking_number }} بنجاح.\nالشاحن: {{ shipper_name }}\nالاستلام: {{ pickup_address }}\nالتوصيل: {{ delivery_address }}\nالمسافة: {{ distance }} كم\nالسعر: {{ price }} ر.ع',
'whatsapp_body_en': 'You have successfully accepted Shipment {{ tracking_number }}.\nShipper: {{ shipper_name }}\nPickup: {{ pickup_address }}\nDelivery: {{ delivery_address }}\nDistance: {{ distance }} km\nPrice/Bid: {{ price }} OMR',
'whatsapp_body_ar': 'لقد قبلت الشحنة {{ tracking_number }} بنجاح.\nالشاحن: {{ shipper_name }}\nالاستلام: {{ pickup_address }}\nالتوصيل: {{ delivery_address }}\nالمسافة: {{ distance }} كم\nالسعر: {{ price }} ر.ع',
},
'shipment_status_update': {
'description': 'Sent on general status change (In Transit, Delivered)',
@ -108,13 +108,13 @@ DEFAULT_TEMPLATES = {
},
'admin_alert_driver_accept': {
'description': 'Sent to Admin when driver accepts shipment',
'variables': '{{ driver_name }}, {{ car_plate_number }}, {{ tracking_number }}, {{ shipper_name }}, {{ price }}',
'variables': '{{ driver_name }}, {{ car_plate_number }}, {{ tracking_number }}, {{ shipper_name }}, {{ price }}, {{ distance }}',
'subject_en': 'Shipment Accepted ({{ tracking_number }})',
'subject_ar': 'تم قبول الشحنة ({{ tracking_number }})',
'email_body_en': 'Driver {{ driver_name }} ({{ car_plate_number }}) accepted shipment {{ tracking_number }} from {{ shipper_name }}.\nPrice: {{ price }} OMR',
'email_body_ar': 'قام السائق {{ driver_name }} ({{ car_plate_number }}) بقبول الشحنة {{ tracking_number }} من {{ shipper_name }}.\nالسعر: {{ price }} ر.ع',
'whatsapp_body_en': 'Driver {{ driver_name }} ({{ car_plate_number }}) accepted shipment {{ tracking_number }} from {{ shipper_name }}.\nPrice: {{ price }} OMR',
'whatsapp_body_ar': 'قام السائق {{ driver_name }} ({{ car_plate_number }}) بقبول الشحنة {{ tracking_number }} من {{ shipper_name }}.\nالسعر: {{ price }} ر.ع',
'email_body_en': 'Driver {{ driver_name }} ({{ car_plate_number }}) accepted shipment {{ tracking_number }} from {{ shipper_name }}.\nDistance: {{ distance }} km\nPrice: {{ price }} OMR',
'email_body_ar': 'قام السائق {{ driver_name }} ({{ car_plate_number }}) بقبول الشحنة {{ tracking_number }} من {{ shipper_name }}.\nالمسافة: {{ distance }} كم\nالسعر: {{ price }} ر.ع',
'whatsapp_body_en': 'Driver {{ driver_name }} ({{ car_plate_number }}) accepted shipment {{ tracking_number }} from {{ shipper_name }}.\nDistance: {{ distance }} km\nPrice: {{ price }} OMR',
'whatsapp_body_ar': 'قام السائق {{ driver_name }} ({{ car_plate_number }}) بقبول الشحنة {{ tracking_number }} من {{ shipper_name }}.\nالمسافة: {{ distance }} كم\nالسعر: {{ price }} ر.ع',
},
'contact_form_admin': {
'description': 'Sent to Admin when contact form is submitted',

View File

@ -66,6 +66,7 @@
<p class="card-text mb-3 small"><strong>{% trans "Delivery" %}:</strong> {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}</p>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted small"><strong>{% trans "Distance" %}:</strong> {{ parcel.distance_km }} km</span>
<span class="text-muted small"><strong>{% trans "Weight" %}:</strong> {{ parcel.weight }} kg</span>
</div>
@ -103,6 +104,9 @@
<i class="bi bi-geo-alt-fill text-danger"></i>
<strong>{% trans "To" %}:</strong> {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}
</span>
<span class="d-flex align-items-center gap-1">
<i class="bi bi-signpost-2"></i> {{ parcel.distance_km }} km
</span>
<span class="d-flex align-items-center gap-1">
<i class="bi bi-box-seam"></i> {{ parcel.weight }} kg
</span>
@ -228,6 +232,7 @@
<th>{% trans "Tracking ID" %}</th>
<th>{% trans "From" %}</th>
<th>{% trans "To" %}</th>
<th>{% trans "Distance" %}</th>
<th>{% trans "Price" %}</th>
<th>{% trans "Status" %}</th>
</tr>
@ -239,6 +244,7 @@
<td><span class="badge bg-light text-dark">#{{ parcel.tracking_number }}</span></td>
<td>{{ parcel.pickup_governate.name }} / {{ parcel.pickup_city.name }}</td>
<td>{{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}</td>
<td>{{ parcel.distance_km }} km</td>
<td>{{ parcel.price }} OMR</td>
<td>
<span class="badge bg-success">
@ -272,6 +278,7 @@
<th>{% trans "Tracking ID" %}</th>
<th>{% trans "From" %}</th>
<th>{% trans "To" %}</th>
<th>{% trans "Distance" %}</th>
<th>{% trans "Price" %}</th>
<th>{% trans "Status" %}</th>
</tr>
@ -283,6 +290,7 @@
<td><span class="badge bg-light text-dark">#{{ parcel.tracking_number }}</span></td>
<td>{{ parcel.pickup_governate.name }} / {{ parcel.pickup_city.name }}</td>
<td>{{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}</td>
<td>{{ parcel.distance_km }} km</td>
<td>{{ parcel.price }} OMR</td>
<td>
<span class="badge bg-danger">
@ -339,4 +347,4 @@
if (listViewBtn) listViewBtn.addEventListener('click', () => setView('list'));
});
</script>
{% endblock %}
{% endblock %}

View File

@ -196,7 +196,7 @@
<table>
<thead>
<tr>
<th width="50%">
<th width="40%">
Description
<span class="ar ar-text">الوصف</span>
</th>
@ -204,6 +204,10 @@
Tracking No
<span class="ar ar-text">رقم التتبع</span>
</th>
<th>
Distance
<span class="ar ar-text">المسافة</span>
</th>
<th>
Weight
<span class="ar ar-text">الوزن</span>
@ -221,13 +225,14 @@
<small>{{ parcel.description|truncatechars:50 }}</small>
</td>
<td>{{ parcel.tracking_number }}</td>
<td>{{ parcel.distance_km }} km</td>
<td>{{ parcel.weight }} kg</td>
<td class="text-right">{{ parcel.price }} OMR / <span class="ar-text">ر.ع.</span></td>
</tr>
</tbody>
<tfoot>
<tr class="total-row">
<td colspan="3" class="text-right">
<td colspan="4" class="text-right">
TOTAL / الإجمالي
</td>
<td class="text-right">{{ parcel.price }} OMR / <span class="ar-text">ر.ع.</span></td>

View File

@ -55,7 +55,8 @@
</div>
<h5 class="card-title">{{ parcel.description|truncatechars:30 }}</h5>
<p class="card-text mb-1 small text-muted"><i class="fas fa-map-marker-alt"></i> <strong>{% trans "From" %}:</strong> {{ parcel.pickup_governate.name }} / {{ parcel.pickup_city.name }}</p>
<p class="card-text mb-3 small text-muted"><i class="fas fa-flag-checkered"></i> <strong>{% trans "To" %}:</strong> {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}</p>
<p class="card-text mb-1 small text-muted"><i class="fas fa-flag-checkered"></i> <strong>{% trans "To" %}:</strong> {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}</p>
<p class="card-text mb-3 small text-muted"><i class="fas fa-road"></i> <strong>{% trans "Distance" %}:</strong> {{ parcel.distance_km }} km</p>
<div class="d-flex justify-content-between align-items-center mb-3">
<span class="text-primary fw-bold">{{ parcel.price }} OMR</span>
@ -124,6 +125,9 @@
<i class="bi bi-geo-alt-fill text-danger"></i>
<strong>{% trans "To" %}:</strong> {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}
</span>
<span class="d-flex align-items-center gap-1">
<i class="bi bi-signpost-2"></i> {{ parcel.distance_km }} km
</span>
<span class="d-flex align-items-center gap-1">
<i class="bi bi-truck"></i>
{% if parcel.carrier %}{{ parcel.carrier.get_full_name|default:parcel.carrier.username }}{% else %}{% trans "Waiting" %}{% endif %}
@ -237,6 +241,7 @@
<th>{% trans "Tracking ID" %}</th>
<th>{% trans "Description" %}</th>
<th>{% trans "Carrier" %}</th>
<th>{% trans "Distance" %}</th>
<th>{% trans "Bid/Price" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Action" %}</th>
@ -256,6 +261,7 @@
-
{% endif %}
</td>
<td>{{ parcel.distance_km }} km</td>
<td>{{ parcel.price }} OMR</td>
<td>
<span class="badge bg-success">
@ -310,6 +316,7 @@
<th>{% trans "Tracking ID" %}</th>
<th>{% trans "Description" %}</th>
<th>{% trans "Carrier" %}</th>
<th>{% trans "Distance" %}</th>
<th>{% trans "Bid/Price" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Action" %}</th>
@ -328,6 +335,7 @@
-
{% endif %}
</td>
<td>{{ parcel.distance_km }} km</td>
<td>{{ parcel.price }} OMR</td>
<td>
<span class="badge bg-danger">

View File

@ -147,7 +147,9 @@ def notify_shipment_created(parcel):
'name': shipper_name,
'description': parcel.description,
'tracking_number': parcel.tracking_number,
'status': parcel.get_status_display()
'status': parcel.get_status_display(),
'distance': parcel.distance_km,
'price': parcel.price
}
# Render for Shipper (check user language preference? For now assume session/request unavailable so maybe default or EN,
@ -241,7 +243,8 @@ def notify_driver_assigned(parcel):
'shipper_name': shipper_name,
'pickup_address': parcel.pickup_address,
'delivery_address': parcel.delivery_address,
'price': parcel.price
'price': parcel.price,
'distance': parcel.distance_km
}
subj_d, email_d, wa_d = get_notification_content('driver_pickup_driver', context_driver)
@ -257,7 +260,8 @@ def notify_driver_assigned(parcel):
'car_plate_number': car_plate,
'tracking_number': parcel.tracking_number,
'shipper_name': shipper_name,
'price': parcel.price
'price': parcel.price,
'distance': parcel.distance_km
}
notify_admin_alert('admin_alert_driver_accept', context_admin)
@ -274,4 +278,4 @@ def notify_status_change(parcel):
if parcel.shipper.email:
send_html_email(subject=subj, message=email_msg, recipient_list=[parcel.shipper.email], title=subj)
send_whatsapp_message(parcel.receiver_phone, wa_msg)
send_whatsapp_message(parcel.receiver_phone, wa_msg)