demo16
This commit is contained in:
parent
ef5454da33
commit
80485899cb
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -115,4 +115,13 @@ class BidForm(forms.ModelForm):
|
||||
# Only allow bidding with approved trucks
|
||||
self.fields['truck'].queryset = Truck.objects.filter(owner=user, is_approved=True)
|
||||
if not self.fields['truck'].queryset.exists():
|
||||
self.fields['truck'].help_text = _("You must have an approved truck to place a bid.")
|
||||
self.fields['truck'].help_text = _("You must have an approved truck to place a bid.")
|
||||
|
||||
class ShipperOfferForm(forms.Form):
|
||||
description = forms.CharField(label=_('Goods Description'), widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3}))
|
||||
weight = forms.CharField(label=_('Weight/Volume'), widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||
origin = forms.CharField(label=_('Origin'), widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||
destination = forms.CharField(label=_('Destination'), widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||
delivery_date = forms.DateField(label=_('Requested Delivery Date'), widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}))
|
||||
amount = forms.DecimalField(label=_('Offer Amount'), max_digits=10, decimal_places=2, widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}))
|
||||
comments = forms.CharField(label=_('Comments'), required=False, widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 2}))
|
||||
|
||||
@ -1,39 +1,50 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<h2 class="mb-4">{% trans "Shipment Marketplace" %}</h2>
|
||||
<h2 class="mb-4">{% trans "Truck Marketplace" %}</h2>
|
||||
<p class="text-muted mb-4">{% trans "Browse available trucks and send your shipping offers directly to truck owners." %}</p>
|
||||
|
||||
<div class="row">
|
||||
{% for shipment in shipments %}
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card shadow-sm h-100 border-0">
|
||||
{% for truck in trucks %}
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="card shadow-sm h-100 border-0 overflow-hidden">
|
||||
{% if truck.truck_picture %}
|
||||
<img src="{{ truck.truck_picture.url }}" class="card-img-top" alt="{{ truck.display_truck_type }}" style="height: 200px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="bg-light d-flex align-items-center justify-content-center" style="height: 200px;">
|
||||
<i class="fa-solid fa-truck fa-4x text-muted opacity-25"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="badge bg-primary">{% trans "Open for Bids" %}</span>
|
||||
<small class="text-muted">{{ shipment.created_at|timesince }} {% trans "ago" %}</small>
|
||||
<span class="badge bg-success">{% trans "Available" %}</span>
|
||||
<small class="text-muted">{% trans "Plate" %}: {{ truck.plate_no }}</small>
|
||||
</div>
|
||||
<h5 class="card-title">{{ shipment.origin }} <i class="fa-solid fa-arrow-right mx-2"></i> {{ shipment.destination }}</h5>
|
||||
<p class="card-text text-muted">{{ shipment.description|truncatechars:100 }}</p>
|
||||
<div class="d-flex gap-4 mb-3">
|
||||
<div>
|
||||
<small class="text-muted d-block">{% trans "Weight" %}</small>
|
||||
<strong>{{ shipment.weight }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<small class="text-muted d-block">{% trans "Delivery Date" %}</small>
|
||||
<strong>{{ shipment.delivery_date }}</strong>
|
||||
</div>
|
||||
<h5 class="card-title">{{ truck.display_truck_type }} - {{ truck.display_model }}</h5>
|
||||
<div class="mb-3">
|
||||
<span class="badge bg-light text-dark">{% trans "Year" %}: {{ truck.year }}</span>
|
||||
<span class="badge bg-light text-dark">{% trans "Capacity" %}: {{ truck.display_load_capacity }}</span>
|
||||
</div>
|
||||
<a href="{% url 'place_bid' shipment.id %}" class="btn btn-outline-primary w-100">{% trans "Place an Offer" %}</a>
|
||||
<p class="card-text">
|
||||
<small class="text-muted d-block">{% trans "Owner" %}: {{ truck.owner.username }}</small>
|
||||
</p>
|
||||
<a href="{% url 'place_bid' truck.id %}" class="btn btn-primary w-100">
|
||||
<i class="fa-solid fa-paper-plane me-2"></i>{% trans "Send Shipping Offer" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12 text-center py-5">
|
||||
<p class="lead text-muted">{% trans "No shipments available at the moment." %}</p>
|
||||
<div class="py-5">
|
||||
<i class="fa-solid fa-truck-slash fa-4x text-muted mb-3"></i>
|
||||
<p class="lead text-muted">{% trans "No approved trucks are currently available." %}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -4,54 +4,94 @@
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow">
|
||||
<div class="col-md-8 col-lg-6">
|
||||
<div class="card shadow-lg border-0">
|
||||
<div class="card-body p-5">
|
||||
<h2 class="mb-4">{% trans "Place an Offer" %}</h2>
|
||||
<div class="alert alert-info mb-4">
|
||||
<strong>{% trans "Shipment:" %}</strong> {{ shipment.origin }} to {{ shipment.destination }}<br>
|
||||
<strong>{% trans "Goods:" %}</strong> {{ shipment.description }}
|
||||
<h2 class="mb-4 text-center">{% trans "Send Shipping Offer" %}</h2>
|
||||
|
||||
<div class="card bg-light border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h6 class="text-primary mb-2">{% trans "Target Truck" %}</h6>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if truck.truck_picture %}
|
||||
<img src="{{ truck.truck_picture.url }}" class="rounded me-3" style="width: 60px; height: 60px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="bg-secondary bg-opacity-10 rounded me-3 d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
|
||||
<i class="fa-solid fa-truck text-muted"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<div class="fw-bold">{{ truck.display_truck_type }} - {{ truck.display_model }}</div>
|
||||
<small class="text-muted">{% trans "Owner" %}: {{ truck.owner.username }} | {% trans "Plate" %}: {{ truck.plate_no }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger">
|
||||
{% trans "Please correct the errors below." %}
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if form.fields.truck.queryset.exists %}
|
||||
<form method="post">
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
|
||||
<h6 class="border-bottom pb-2 mb-3">{% trans "Shipment Details" %}</h6>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{% trans "Select Truck" %}</label>
|
||||
{{ form.truck }}
|
||||
{{ form.truck.errors }}
|
||||
<label class="form-label">{{ form.description.label }}</label>
|
||||
{{ form.description }}
|
||||
{{ form.description.errors }}
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">{{ form.origin.label }}</label>
|
||||
{{ form.origin }}
|
||||
{{ form.origin.errors }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">{{ form.destination.label }}</label>
|
||||
{{ form.destination }}
|
||||
{{ form.destination.errors }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">{{ form.weight.label }}</label>
|
||||
{{ form.weight }}
|
||||
{{ form.weight.errors }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">{{ form.delivery_date.label }}</label>
|
||||
{{ form.delivery_date }}
|
||||
{{ form.delivery_date.errors }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="border-bottom pb-2 mb-3 mt-4">{% trans "Pricing & Terms" %}</h6>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{% trans "Your Offer Amount" %}</label>
|
||||
<label class="form-label">{{ form.amount.label }}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
{{ form.amount }}
|
||||
</div>
|
||||
{{ form.amount.errors }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{% trans "Comments/Conditions" %}</label>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label">{{ form.comments.label }}</label>
|
||||
{{ form.comments }}
|
||||
{{ form.comments.errors }}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100 py-3">{% trans "Submit Offer" %}</button>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary btn-lg py-3">
|
||||
<i class="fa-solid fa-paper-plane me-2"></i>{% trans "Send Offer to Truck Owner" %}
|
||||
</button>
|
||||
<a href="{% url 'marketplace' %}" class="btn btn-link text-muted">{% trans "Cancel" %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<p class="text-danger">{% trans "You must register a truck before placing a bid." %}</p>
|
||||
<a href="{% url 'truck_register' %}" class="btn btn-success">{% trans "Register Truck Now" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -14,50 +14,61 @@
|
||||
</span>
|
||||
</div>
|
||||
<hr>
|
||||
<h5>{% trans "Details" %}</h5>
|
||||
<p>{{ shipment.description }}</p>
|
||||
<h5>{% trans "Shipment Details" %}</h5>
|
||||
<p class="lead">{{ shipment.description }}</p>
|
||||
<div class="row">
|
||||
<div class="col-6 mb-3">
|
||||
<small class="text-muted d-block">{% trans "Weight" %}</small>
|
||||
<strong>{{ shipment.weight }}</strong>
|
||||
</div>
|
||||
<div class="col-6 mb-3">
|
||||
<small class="text-muted d-block">{% trans "Delivery Date" %}</small>
|
||||
<small class="text-muted d-block">{% trans "Requested Delivery Date" %}</small>
|
||||
<strong>{{ shipment.delivery_date }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if user == shipment.shipper and shipment.status == 'OPEN' %}
|
||||
<div class="card shadow-sm">
|
||||
<!-- Offers related to this shipment -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header bg-white">
|
||||
<h5 class="mb-0">{% trans "Received Bids" %}</h5>
|
||||
<h5 class="mb-0">{% trans "Offer Status" %}</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Truck Owner" %}</th>
|
||||
<th>{% trans "Truck" %}</th>
|
||||
<th>{% trans "Amount" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for bid in bids %}
|
||||
<tr>
|
||||
<td>{{ bid.truck_owner.username }}</td>
|
||||
<td>{{ bid.truck.display_truck_type }}</td>
|
||||
<td>{{ bid.truck.display_truck_type }} ({{ bid.truck.plate_no }})</td>
|
||||
<td>${{ bid.amount }}</td>
|
||||
<td>
|
||||
<a href="{% url 'accept_bid' bid.id %}" class="btn btn-sm btn-success">{% trans "Accept" %}</a>
|
||||
<span class="badge {% if bid.status == 'PENDING' %}bg-info{% elif bid.status == 'ACCEPTED' %}bg-success{% else %}bg-secondary{% endif %}">
|
||||
{{ bid.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if bid.status == 'PENDING' and user == bid.truck_owner %}
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{% url 'accept_bid' bid.id %}" class="btn btn-success">{% trans "Accept" %}</a>
|
||||
<a href="{% url 'reject_bid' bid.id %}" class="btn btn-outline-danger">{% trans "Reject" %}</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="text-muted small">{% trans "No action available" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-4 text-muted">{% trans "No bids received yet." %}</td>
|
||||
<td colspan="4" class="text-center py-4 text-muted">{% trans "No offers for this shipment." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@ -65,37 +76,49 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if shipment.status == 'IN_PROGRESS' %}
|
||||
<div class="alert alert-success d-flex align-items-center">
|
||||
<i class="fa-solid fa-truck-moving fa-2x me-3"></i>
|
||||
<div class="alert alert-success d-flex align-items-center p-4">
|
||||
<i class="fa-solid fa-truck-moving fa-3x me-4 text-success opacity-50"></i>
|
||||
<div>
|
||||
<strong>{% trans "Shipment in progress!" %}</strong><br>
|
||||
{% trans "Assigned Truck:" %} {{ shipment.assigned_truck.display_truck_type }} ({{ shipment.assigned_truck.plate_no }})
|
||||
<h5 class="alert-heading">{% trans "Shipment is IN PROGRESS" %}</h5>
|
||||
<p class="mb-0">{% trans "Assigned Truck:" %} <strong>{{ shipment.assigned_truck.display_truck_type }} ({{ shipment.assigned_truck.plate_no }})</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<a href="https://wa.me/{{ shipment.assigned_truck.owner.profile.phone_number }}" class="btn btn-success btn-lg">
|
||||
<i class="fa-brands fa-whatsapp me-2"></i> {% trans "Contact Driver on WhatsApp" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h5>{% trans "Contact Information" %}</h5>
|
||||
<p>
|
||||
<strong>{% trans "Shipper:" %}</strong> {{ shipment.shipper.username }}<br>
|
||||
{% if shipment.status == 'IN_PROGRESS' %}
|
||||
<strong>{% trans "Phone:" %}</strong> {{ shipment.shipper.profile.phone_number }}
|
||||
{% endif %}
|
||||
</p>
|
||||
<h5>{% trans "Stakeholders" %}</h5>
|
||||
<div class="mb-3">
|
||||
<small class="text-muted d-block">{% trans "Shipper" %}</small>
|
||||
<strong>{{ shipment.shipper.username }}</strong>
|
||||
</div>
|
||||
{% if shipment.assigned_truck %}
|
||||
<div class="mb-3">
|
||||
<small class="text-muted d-block">{% trans "Truck Owner" %}</small>
|
||||
<strong>{{ shipment.assigned_truck.owner.username }}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if shipment.status == 'IN_PROGRESS' %}
|
||||
<div class="d-grid gap-2">
|
||||
{% if user == shipment.shipper %}
|
||||
<a href="https://wa.me/{{ shipment.assigned_truck.owner.profile.full_phone_number }}" target="_blank" class="btn btn-success btn-lg">
|
||||
<i class="fa-brands fa-whatsapp me-2"></i> {% trans "Message Driver" %}
|
||||
</a>
|
||||
{% elif user == shipment.assigned_truck.owner %}
|
||||
<a href="https://wa.me/{{ shipment.shipper.profile.full_phone_number }}" target="_blank" class="btn btn-success btn-lg">
|
||||
<i class="fa-brands fa-whatsapp me-2"></i> {% trans "Message Shipper" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -4,58 +4,126 @@
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>{% trans "Shipper Dashboard" %}</h2>
|
||||
<a href="{% url 'post_shipment' %}" class="btn btn-success">
|
||||
<i class="fa-solid fa-plus me-2"></i> {% trans "Post New Shipment" %}
|
||||
<div>
|
||||
<h2 class="mb-1">{% trans "Shipper Dashboard" %}</h2>
|
||||
<p class="text-muted">{% trans "Manage your shipping offers and active shipments." %}</p>
|
||||
</div>
|
||||
<a href="{% url 'marketplace' %}" class="btn btn-primary btn-lg">
|
||||
<i class="fa-solid fa-search me-2"></i> {% trans "Browse Trucks" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h5 class="mb-0">{% trans "My Shipments" %}</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>{% trans "Description" %}</th>
|
||||
<th>{% trans "Route" %}</th>
|
||||
<th>{% trans "Delivery Date" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Bids" %}</th>
|
||||
<th>{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for shipment in shipments %}
|
||||
<tr>
|
||||
<td>{{ shipment.description|truncatechars:30 }}</td>
|
||||
<td>{{ shipment.origin }} <i class="fa-solid fa-arrow-right mx-1"></i> {{ shipment.destination }}</td>
|
||||
<td>{{ shipment.delivery_date }}</td>
|
||||
<td>
|
||||
<span class="badge {% if shipment.status == 'OPEN' %}bg-primary{% elif shipment.status == 'IN_PROGRESS' %}bg-warning{% else %}bg-success{% endif %}">
|
||||
{{ shipment.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ shipment.bids.count }}</td>
|
||||
<td>
|
||||
<a href="{% url 'shipment_detail' shipment.id %}" class="btn btn-sm btn-outline-primary">{% trans "View Details" %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-4">{% trans "No shipments posted yet." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Active Offers -->
|
||||
<div class="card shadow-sm border-0 mb-5">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0 text-primary">{% trans "My Shipping Offers" %}</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>{% trans "Truck" %}</th>
|
||||
<th>{% trans "Route" %}</th>
|
||||
<th>{% trans "Amount" %}</th>
|
||||
<th>{% trans "Date Sent" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for bid in bids %}
|
||||
<tr>
|
||||
<td>
|
||||
<div><strong>{{ bid.truck.display_truck_type }}</strong></div>
|
||||
<small class="text-muted">{{ bid.truck.plate_no }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ bid.shipment.origin }}</span>
|
||||
<i class="fa-solid fa-arrow-right mx-1 text-muted small"></i>
|
||||
<span>{{ bid.shipment.destination }}</span>
|
||||
</td>
|
||||
<td class="fw-bold">${{ bid.amount }}</td>
|
||||
<td>{{ bid.created_at|date:"d M Y" }}</td>
|
||||
<td>
|
||||
{% if bid.status == 'PENDING' %}
|
||||
<span class="badge bg-info">{% trans "Pending" %}</span>
|
||||
{% elif bid.status == 'ACCEPTED' %}
|
||||
<span class="badge bg-success">{% trans "Accepted" %}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-danger">{% trans "Rejected" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'shipment_detail' bid.shipment.id %}" class="btn btn-sm btn-outline-secondary">{% trans "Details" %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-5 text-muted">
|
||||
<i class="fa-solid fa-paper-plane fa-2x mb-3 opacity-25"></i>
|
||||
<p>{% trans "You haven't sent any offers yet." %}</p>
|
||||
<a href="{% url 'marketplace' %}" class="btn btn-sm btn-primary">{% trans "Find a Truck" %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active Shipments -->
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0">{% trans "Active Shipments" %}</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>{% trans "Description" %}</th>
|
||||
<th>{% trans "Route" %}</th>
|
||||
<th>{% trans "Delivery Date" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Assigned Truck" %}</th>
|
||||
<th>{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for shipment in shipments %}
|
||||
{% if shipment.status != 'OPEN' or not shipment.bids.exists %}
|
||||
<tr>
|
||||
<td>{{ shipment.description|truncatechars:30 }}</td>
|
||||
<td>{{ shipment.origin }} <i class="fa-solid fa-arrow-right mx-1 text-muted small"></i> {{ shipment.destination }}</td>
|
||||
<td>{{ shipment.delivery_date }}</td>
|
||||
<td>
|
||||
<span class="badge {% if shipment.status == 'IN_PROGRESS' %}bg-warning{% elif shipment.status == 'COMPLETED' %}bg-success{% else %}bg-secondary{% endif %}">
|
||||
{{ shipment.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if shipment.assigned_truck %}
|
||||
{{ shipment.assigned_truck.plate_no }}
|
||||
{% else %}
|
||||
<span class="text-muted italic">{% trans "None" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'shipment_detail' shipment.id %}" class="btn btn-sm btn-outline-primary">{% trans "Track" %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-4 text-muted">{% trans "No active shipments." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -7,121 +7,119 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="mb-1">{% trans "Truck Owner Dashboard" %}</h2>
|
||||
<p class="text-muted">{% trans "Manage your fleet and active bids." %}</p>
|
||||
<p class="text-muted">{% trans "Manage your fleet and incoming shipping offers." %}</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{% url 'marketplace' %}" class="btn btn-primary me-2">
|
||||
<i class="fa-solid fa-search me-2"></i> {% trans "Find Shipments" %}
|
||||
</a>
|
||||
<a href="{% url 'truck_register' %}" class="btn btn-success">
|
||||
<i class="fa-solid fa-plus me-2"></i> {% trans "Register Truck" %}
|
||||
<a href="{% url 'truck_register' %}" class="btn btn-success btn-lg">
|
||||
<i class="fa-solid fa-plus me-2"></i> {% trans "Add New Truck" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Approved Trucks Section -->
|
||||
<h4 class="mb-3">{% trans "My Approved Trucks" %}</h4>
|
||||
<div class="row mb-5">
|
||||
{% if trucks %}
|
||||
{% for truck in trucks %}
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
{% if truck.truck_picture %}
|
||||
<img src="{{ truck.truck_picture.url }}" class="card-img-top" alt="{{ truck.display_truck_type }}" style="height: 200px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="bg-light d-flex align-items-center justify-content-center" style="height: 200px;">
|
||||
<i class="fa-solid fa-truck fa-4x text-secondary"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ truck.display_truck_type }}</h5>
|
||||
<p class="card-text mb-1"><strong>{% trans "Plate No:" %}</strong> {{ truck.plate_no }}</p>
|
||||
<p class="card-text mb-1"><strong>{% trans "Model:" %}</strong> {{ truck.display_model }} ({{ truck.year }})</p>
|
||||
<p class="card-text mb-1"><strong>{% trans "Capacity:" %}</strong> {{ truck.display_load_capacity }}</p>
|
||||
{% if truck.registration_expiry_date %}
|
||||
<p class="card-text mb-1"><strong>{% trans "Expiry Date:" %}</strong> {{ truck.registration_expiry_date }}</p>
|
||||
{% endif %}
|
||||
<a href="{% url 'edit_truck' truck.id %}" class="btn btn-outline-primary btn-sm mt-3">
|
||||
<i class="fa-solid fa-edit me-1"></i> {% trans "Edit Details" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-footer bg-white border-0">
|
||||
<span class="badge bg-success w-100 py-2">{% trans "Approved" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Incoming Offers Section -->
|
||||
<div class="card shadow-sm border-0 mb-5">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0 text-primary">{% trans "Received Shipping Offers" %}</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>{% trans "Route" %}</th>
|
||||
<th>{% trans "Truck" %}</th>
|
||||
<th>{% trans "Shipper" %}</th>
|
||||
<th>{% trans "Offer Amount" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for bid in bids %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-bold">{{ bid.shipment.origin }} <i class="fa-solid fa-arrow-right mx-1 text-muted small"></i> {{ bid.shipment.destination }}</div>
|
||||
<small class="text-muted">{{ bid.shipment.delivery_date }}</small>
|
||||
</td>
|
||||
<td>{{ bid.truck.plate_no }}</td>
|
||||
<td>{{ bid.shipment.shipper.username }}</td>
|
||||
<td class="fw-bold text-success">${{ bid.amount }}</td>
|
||||
<td>
|
||||
{% if bid.status == 'PENDING' %}
|
||||
<span class="badge bg-info">{% trans "Pending" %}</span>
|
||||
{% elif bid.status == 'ACCEPTED' %}
|
||||
<span class="badge bg-success">{% trans "Accepted" %}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{% trans "Rejected" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if bid.status == 'PENDING' %}
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{% url 'accept_bid' bid.id %}" class="btn btn-success">{% trans "Accept" %}</a>
|
||||
<a href="{% url 'reject_bid' bid.id %}" class="btn btn-outline-danger">{% trans "Reject" %}</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="{% url 'shipment_detail' bid.shipment.id %}" class="btn btn-sm btn-outline-primary">{% trans "View Details" %}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-5 text-muted">
|
||||
<i class="fa-solid fa-inbox fa-2x mb-3 opacity-25"></i>
|
||||
<p>{% trans "No offers received yet." %}</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12 text-center py-4">
|
||||
<p class="text-muted">{% trans "No approved trucks yet." %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pending Trucks Section -->
|
||||
{% if pending_trucks %}
|
||||
<h4 class="mb-3">{% trans "Pending Approval" %}</h4>
|
||||
<div class="row mb-5">
|
||||
{% for truck in pending_trucks %}
|
||||
<!-- My Fleet -->
|
||||
<h4 class="mb-3">{% trans "My Fleet" %}</h4>
|
||||
<div class="row">
|
||||
{% for truck in trucks %}
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100 shadow-sm border-0 opacity-75">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
{% if truck.truck_picture %}
|
||||
<img src="{{ truck.truck_picture.url }}" class="card-img-top" alt="{{ truck.display_truck_type }}" style="height: 200px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="bg-light d-flex align-items-center justify-content-center" style="height: 200px;">
|
||||
<i class="fa-solid fa-truck fa-4x text-secondary opacity-25"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-muted">{{ truck.display_truck_type }}</h5>
|
||||
<p class="card-text mb-1"><strong>{% trans "Plate No:" %}</strong> {{ truck.plate_no }}</p>
|
||||
<p class="card-text">{% trans "Submitted on:" %} {{ truck.created_at|date }}</p>
|
||||
<a href="{% url 'edit_truck' truck.id %}" class="btn btn-outline-secondary btn-sm mt-3">
|
||||
<i class="fa-solid fa-edit me-1"></i> {% trans "Edit Details" %}
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h5 class="card-title mb-0">{{ truck.display_truck_type }}</h5>
|
||||
<span class="badge bg-success">{% trans "Approved" %}</span>
|
||||
</div>
|
||||
<p class="card-text mb-1"><small class="text-muted">{% trans "Plate No:" %}</small> <strong>{{ truck.plate_no }}</strong></p>
|
||||
<p class="card-text mb-1"><small class="text-muted">{% trans "Capacity:" %}</small> {{ truck.display_load_capacity }}</p>
|
||||
<a href="{% url 'edit_truck' truck.id %}" class="btn btn-outline-primary btn-sm mt-3 w-100">
|
||||
<i class="fa-solid fa-edit me-1"></i> {% trans "Edit Truck" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-footer bg-white border-0">
|
||||
<span class="badge bg-warning text-dark w-100 py-2">{% trans "Waiting for Admin Review" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% for truck in pending_trucks %}
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100 shadow-sm border-0 bg-light">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h5 class="card-title mb-0 text-muted">{{ truck.display_truck_type }}</h5>
|
||||
<span class="badge bg-warning text-dark">{% trans "Pending" %}</span>
|
||||
</div>
|
||||
<p class="card-text mb-1"><small class="text-muted">{% trans "Plate No:" %}</small> <strong>{{ truck.plate_no }}</strong></p>
|
||||
<p class="card-text mt-3"><small class="text-muted italic">{% trans "Waiting for admin approval..." %}</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white">
|
||||
<h5 class="mb-0">{% trans "My Active Bids" %}</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th>{% trans "Shipment" %}</th>
|
||||
<th>{% trans "Amount" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Date" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for bid in bids %}
|
||||
<tr>
|
||||
<td>{{ bid.shipment.origin }} - {{ bid.shipment.destination }}</td>
|
||||
<td>${{ bid.amount }}</td>
|
||||
<td>
|
||||
<span class="badge {% if bid.status == 'PENDING' %}bg-warning text-dark{% elif bid.status == 'ACCEPTED' %}bg-success{% else %}bg-danger{% endif %}">
|
||||
{{ bid.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ bid.created_at|date }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-4">{% trans "No bids placed yet." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -17,6 +17,7 @@ urlpatterns = [
|
||||
path("shipment/post/", views.post_shipment, name="post_shipment"),
|
||||
path("marketplace/", views.marketplace, name="marketplace"),
|
||||
path("shipment/<int:shipment_id>/", views.shipment_detail, name="shipment_detail"),
|
||||
path("shipment/<int:shipment_id>/bid/", views.place_bid, name="place_bid"),
|
||||
path("truck/<int:truck_id>/offer/", views.place_bid, name="place_bid"),
|
||||
path("bid/<int:bid_id>/accept/", views.accept_bid, name="accept_bid"),
|
||||
]
|
||||
path("bid/<int:bid_id>/reject/", views.reject_bid, name="reject_bid"),
|
||||
]
|
||||
128
core/views.py
128
core/views.py
@ -3,7 +3,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth import login, authenticate, logout
|
||||
from django.utils import timezone
|
||||
from .models import Profile, Truck, Shipment, Bid, Message, OTPCode
|
||||
from .forms import TruckForm, ShipmentForm, BidForm, UserRegistrationForm, OTPVerifyForm
|
||||
from .forms import TruckForm, ShipmentForm, BidForm, UserRegistrationForm, OTPVerifyForm, ShipperOfferForm
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import gettext as _
|
||||
from django.db.models import Q
|
||||
@ -152,15 +152,21 @@ def dashboard(request):
|
||||
profile, created = Profile.objects.get_or_create(user=request.user)
|
||||
if profile.role == 'SHIPPER':
|
||||
my_shipments = Shipment.objects.filter(shipper=request.user).order_by('-created_at')
|
||||
return render(request, 'core/shipper_dashboard.html', {'shipments': my_shipments})
|
||||
# In the new flow, Shippers place bids.
|
||||
my_bids = Bid.objects.filter(shipment__shipper=request.user).order_by('-created_at')
|
||||
return render(request, 'core/shipper_dashboard.html', {
|
||||
'shipments': my_shipments,
|
||||
'bids': my_bids
|
||||
})
|
||||
elif profile.role == 'TRUCK_OWNER':
|
||||
approved_trucks = Truck.objects.filter(owner=request.user, is_approved=True)
|
||||
pending_trucks = Truck.objects.filter(owner=request.user, is_approved=False)
|
||||
my_bids = Bid.objects.filter(truck_owner=request.user).order_by('-created_at')
|
||||
# Truck owners receive bids in the new flow
|
||||
my_received_bids = Bid.objects.filter(truck_owner=request.user).order_by('-created_at')
|
||||
return render(request, 'core/truck_owner_dashboard.html', {
|
||||
'trucks': approved_trucks,
|
||||
'pending_trucks': pending_trucks,
|
||||
'bids': my_bids
|
||||
'bids': my_received_bids
|
||||
})
|
||||
elif profile.role == 'ADMIN' or request.user.is_superuser:
|
||||
pending_trucks = Truck.objects.filter(is_approved=False).order_by('-created_at')
|
||||
@ -175,7 +181,6 @@ def dashboard(request):
|
||||
}
|
||||
return render(request, 'core/admin_dashboard.html', context)
|
||||
else:
|
||||
# Fallback for undefined roles
|
||||
return redirect('/')
|
||||
|
||||
@login_required
|
||||
@ -188,7 +193,7 @@ def truck_register(request):
|
||||
if form.is_valid():
|
||||
truck = form.save(commit=False)
|
||||
truck.owner = request.user
|
||||
truck.is_approved = False # Ensure it stays false on new reg
|
||||
truck.is_approved = False
|
||||
truck.save()
|
||||
messages.success(request, _("Truck registered successfully! It will be visible after admin approval."))
|
||||
return redirect('dashboard')
|
||||
@ -207,7 +212,7 @@ def edit_truck(request, truck_id):
|
||||
form = TruckForm(request.POST, request.FILES, instance=truck)
|
||||
if form.is_valid():
|
||||
truck = form.save(commit=False)
|
||||
truck.is_approved = False # Reset approval status on update
|
||||
truck.is_approved = False
|
||||
truck.save()
|
||||
messages.success(request, _("Truck data updated successfully! It will be reviewed by admin again."))
|
||||
return redirect('dashboard')
|
||||
@ -227,10 +232,9 @@ def approve_truck(request, truck_id):
|
||||
truck.is_approved = True
|
||||
truck.save()
|
||||
|
||||
# Notify Truck Owner via WhatsApp
|
||||
owner_phone = getattr(truck.owner.profile, 'full_phone_number', None)
|
||||
if owner_phone:
|
||||
msg = f"Your truck ({truck.plate_no}) has been approved! You can now place bids on shipments."
|
||||
msg = f"Your truck ({truck.plate_no}) has been approved! You can now receive offers for shipments."
|
||||
send_whatsapp_message(owner_phone, msg)
|
||||
|
||||
messages.success(request, _("Truck approved successfully!"))
|
||||
@ -249,6 +253,7 @@ def suspend_truck(request, truck_id):
|
||||
|
||||
@login_required
|
||||
def post_shipment(request):
|
||||
"""Note: This is now largely redundant but kept for compatibility or direct posting."""
|
||||
if request.user.profile.role != 'SHIPPER':
|
||||
return redirect('dashboard')
|
||||
|
||||
@ -269,80 +274,103 @@ def post_shipment(request):
|
||||
|
||||
@login_required
|
||||
def marketplace(request):
|
||||
if request.user.profile.role != 'TRUCK_OWNER':
|
||||
"""Shippers browse available trucks here."""
|
||||
if request.user.profile.role != 'SHIPPER':
|
||||
return redirect('dashboard')
|
||||
|
||||
shipments = Shipment.objects.filter(status='OPEN').order_by('-created_at')
|
||||
return render(request, 'core/marketplace.html', {'shipments': shipments})
|
||||
trucks = Truck.objects.filter(is_approved=True).order_by('-created_at')
|
||||
return render(request, 'core/marketplace.html', {'trucks': trucks})
|
||||
|
||||
@login_required
|
||||
def place_bid(request, shipment_id):
|
||||
shipment = get_object_or_404(Shipment, id=shipment_id)
|
||||
if request.user.profile.role != 'TRUCK_OWNER':
|
||||
return redirect('dashboard')
|
||||
|
||||
# Optional: Only allow bidding if user has at least one approved truck
|
||||
if not Truck.objects.filter(owner=request.user, is_approved=True).exists():
|
||||
messages.warning(request, _("You must have at least one approved truck to place a bid."))
|
||||
def place_bid(request, truck_id):
|
||||
"""Shipper makes an offer to a specific truck."""
|
||||
truck = get_object_or_404(Truck, id=truck_id, is_approved=True)
|
||||
if request.user.profile.role != 'SHIPPER':
|
||||
return redirect('dashboard')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = BidForm(request.POST, user=request.user)
|
||||
form = ShipperOfferForm(request.POST)
|
||||
if form.is_valid():
|
||||
bid = form.save(commit=False)
|
||||
bid.truck_owner = request.user
|
||||
bid.shipment = shipment
|
||||
bid.save()
|
||||
# Create Shipment
|
||||
shipment = Shipment.objects.create(
|
||||
shipper=request.user,
|
||||
description=form.cleaned_data['description'],
|
||||
weight=form.cleaned_data['weight'],
|
||||
origin=form.cleaned_data['origin'],
|
||||
destination=form.cleaned_data['destination'],
|
||||
delivery_date=form.cleaned_data['delivery_date'],
|
||||
status='OPEN'
|
||||
)
|
||||
# Create Bid (Offer)
|
||||
bid = Bid.objects.create(
|
||||
shipment=shipment,
|
||||
truck_owner=truck.owner,
|
||||
truck=truck,
|
||||
amount=form.cleaned_data['amount'],
|
||||
comments=form.cleaned_data.get('comments', ''),
|
||||
status='PENDING'
|
||||
)
|
||||
|
||||
# Notify Shipper via WhatsApp
|
||||
shipper_phone = getattr(shipment.shipper.profile, 'full_phone_number', None)
|
||||
if shipper_phone:
|
||||
msg = f"New bid placed on your shipment from {shipment.origin} to {shipment.destination}! Amount: {bid.amount}"
|
||||
send_whatsapp_message(shipper_phone, msg)
|
||||
# Notify Truck Owner via WhatsApp
|
||||
owner_phone = getattr(truck.owner.profile, 'full_phone_number', None)
|
||||
if owner_phone:
|
||||
msg = f"New offer received for your truck ({truck.plate_no})! Route: {shipment.origin} to {shipment.destination}. Amount: {bid.amount}"
|
||||
send_whatsapp_message(owner_phone, msg)
|
||||
|
||||
messages.success(request, _("Bid placed successfully!"))
|
||||
return redirect('marketplace')
|
||||
messages.success(request, _("Offer sent successfully!"))
|
||||
return redirect('dashboard')
|
||||
else:
|
||||
messages.error(request, _("Error placing bid. Please check the form."))
|
||||
messages.error(request, _("Error sending offer. Please check the form."))
|
||||
else:
|
||||
form = BidForm(user=request.user)
|
||||
form = ShipperOfferForm()
|
||||
|
||||
return render(request, 'core/place_bid.html', {'form': form, 'shipment': shipment})
|
||||
return render(request, 'core/place_bid.html', {'form': form, 'truck': truck})
|
||||
|
||||
@login_required
|
||||
def shipment_detail(request, shipment_id):
|
||||
shipment = get_object_or_404(Shipment, id=shipment_id)
|
||||
# Security: check if user is shipper or a truck owner who bid
|
||||
if shipment.shipper != request.user and not Bid.objects.filter(shipment=shipment, truck_owner=request.user).exists():
|
||||
if request.user.profile.role != 'ADMIN' and not request.user.is_superuser:
|
||||
return redirect('dashboard')
|
||||
# Security: check if user is shipper, truck owner of a bid, or admin
|
||||
is_involved = Bid.objects.filter(shipment=shipment, truck_owner=request.user).exists() or shipment.shipper == request.user
|
||||
if not is_involved and not (request.user.profile.role == 'ADMIN' or request.user.is_superuser):
|
||||
return redirect('dashboard')
|
||||
|
||||
bids = shipment.bids.all()
|
||||
return render(request, 'core/shipment_detail.html', {'shipment': shipment, 'bids': bids})
|
||||
|
||||
@login_required
|
||||
def accept_bid(request, bid_id):
|
||||
"""Truck owner accepts an offer from a shipper."""
|
||||
bid = get_object_or_404(Bid, id=bid_id)
|
||||
if bid.shipment.shipper != request.user:
|
||||
if bid.truck_owner != request.user:
|
||||
messages.error(request, _("You are not authorized to accept this offer."))
|
||||
return redirect('dashboard')
|
||||
|
||||
# Accept this bid
|
||||
bid.status = 'ACCEPTED'
|
||||
bid.save()
|
||||
|
||||
# Reject others
|
||||
bid.shipment.bids.exclude(id=bid_id).update(status='REJECTED')
|
||||
|
||||
# Update shipment
|
||||
bid.shipment.status = 'IN_PROGRESS'
|
||||
bid.shipment.assigned_truck = bid.truck
|
||||
bid.shipment.save()
|
||||
|
||||
# Notify Truck Owner via WhatsApp
|
||||
owner_phone = getattr(bid.truck_owner.profile, 'full_phone_number', None)
|
||||
if owner_phone:
|
||||
msg = f"Congratulations! Your bid for the shipment from {bid.shipment.origin} to {bid.shipment.destination} has been accepted."
|
||||
send_whatsapp_message(owner_phone, msg)
|
||||
# Notify Shipper via WhatsApp
|
||||
shipper_phone = getattr(bid.shipment.shipper.profile, 'full_phone_number', None)
|
||||
if shipper_phone:
|
||||
msg = f"Your offer for truck {bid.truck.plate_no} ({bid.shipment.origin} to {bid.shipment.destination}) has been accepted!"
|
||||
send_whatsapp_message(shipper_phone, msg)
|
||||
|
||||
messages.success(request, _("Bid accepted! Shipment is now in progress."))
|
||||
return redirect('shipment_detail', shipment_id=bid.shipment.id)
|
||||
messages.success(request, _("Offer accepted! Shipment is now in progress."))
|
||||
return redirect('dashboard')
|
||||
|
||||
@login_required
|
||||
def reject_bid(request, bid_id):
|
||||
"""Truck owner rejects an offer."""
|
||||
bid = get_object_or_404(Bid, id=bid_id)
|
||||
if bid.truck_owner != request.user:
|
||||
return redirect('dashboard')
|
||||
|
||||
bid.status = 'REJECTED'
|
||||
bid.save()
|
||||
messages.info(request, _("Offer rejected."))
|
||||
return redirect('dashboard')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user