add printing labels
This commit is contained in:
parent
1e9f216ade
commit
4020492307
Binary file not shown.
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
{% load i18n static i18n_urls %}
|
||||
{% load i18n static i18n_urls core_tags %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_language_info for LANGUAGE_CODE as lang %}
|
||||
<!DOCTYPE html>
|
||||
@ -256,9 +256,16 @@
|
||||
<i class="bi bi-robot me-2 fs-5"></i>
|
||||
<h6 class="mb-0 fw-bold">MasarX AI</h6>
|
||||
</div>
|
||||
<button id="masar-chat-close" class="btn btn-sm btn-link text-white p-0">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if platform_profile.phone_number %}
|
||||
<a href="https://wa.me/{{ platform_profile.phone_number|whatsapp_url }}" target="_blank" class="btn btn-sm btn-success me-2 rounded-circle d-flex align-items-center justify-content-center" style="width: 32px; height: 32px;" title="{% trans 'Chat on WhatsApp' %}" data-bs-toggle="tooltip">
|
||||
<i class="bi bi-whatsapp"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<button id="masar-chat-close" class="btn btn-sm btn-link text-white p-0">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="masar-chat-messages" class="flex-grow-1 p-3 bg-light overflow-auto">
|
||||
<div class="d-flex mb-3 justify-content-start">
|
||||
@ -277,6 +284,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="{% static "js/chat.js" %}?v={{ deployment_timestamp }}"></script>
|
||||
<script>
|
||||
// Initialize tooltips
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
144
core/templates/core/parcel_label.html
Normal file
144
core/templates/core/parcel_label.html
Normal file
@ -0,0 +1,144 @@
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ LANGUAGE_CODE }}" dir="{{ LANGUAGE_BIDI|yesno:'rtl,ltr' }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Label {{ parcel.tracking_number }}</title>
|
||||
<style>
|
||||
@page {
|
||||
size: 100mm 150mm;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding: 5mm;
|
||||
font-size: 12px;
|
||||
}
|
||||
.container {
|
||||
border: 2px solid #000;
|
||||
height: 138mm; /* Adjusted slightly */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.header {
|
||||
border-bottom: 2px solid #000;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
height: 20mm;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
.header img {
|
||||
max-height: 15mm;
|
||||
max-width: 80mm;
|
||||
}
|
||||
.big-code {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
border-bottom: 2px solid #000;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.address-box {
|
||||
border-bottom: 1px solid #000;
|
||||
padding: 10px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.main-info {
|
||||
display: flex;
|
||||
border-bottom: 2px solid #000;
|
||||
}
|
||||
.section {
|
||||
padding: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
/* RTL support for border */
|
||||
html[dir="ltr"] .section-right { border-left: 2px solid #000; }
|
||||
html[dir="rtl"] .section-right { border-right: 2px solid #000; }
|
||||
|
||||
.qr-code {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
border-bottom: 2px solid #000;
|
||||
}
|
||||
.qr-code img {
|
||||
width: 30mm;
|
||||
height: 30mm;
|
||||
}
|
||||
.tracking-number {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-top: 5px;
|
||||
font-family: monospace;
|
||||
}
|
||||
.footer {
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
}
|
||||
h3 { margin: 0 0 5px 0; font-size: 10px; text-transform: uppercase; color: #555; }
|
||||
p { margin: 0 0 2px 0; }
|
||||
.bold { font-weight: bold; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
{% if logo_base64 %}
|
||||
<img src="data:image/jpeg;base64,{{ logo_base64 }}" alt="MasarX">
|
||||
{% else %}
|
||||
<h1>{{ platform_profile.name|default:"MASARX" }}</h1>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="big-code">
|
||||
{{ parcel.delivery_city.name|upper }}
|
||||
</div>
|
||||
|
||||
<div class="address-box">
|
||||
<h3>{% trans "TO (Receiver)" %}</h3>
|
||||
<p class="bold">{{ parcel.receiver_name }}</p>
|
||||
<p>{{ parcel.delivery_address }}</p>
|
||||
<p>{{ parcel.delivery_city.name }}, {{ parcel.delivery_governate.name }}</p>
|
||||
<p>{{ parcel.delivery_country.name }}</p>
|
||||
<p>{% trans "Tel" %}: {{ parcel.receiver_phone }}</p>
|
||||
</div>
|
||||
|
||||
<div class="address-box">
|
||||
<h3>{% trans "FROM (Sender)" %}</h3>
|
||||
<p class="bold">{{ parcel.shipper.get_full_name|default:parcel.shipper.username }}</p>
|
||||
<p>{{ parcel.pickup_address }}</p>
|
||||
<p>{{ parcel.pickup_city.name }}, {{ parcel.pickup_governate.name }}</p>
|
||||
<p>{% trans "Tel" %}: {{ parcel.shipper.profile.phone_number }}</p>
|
||||
</div>
|
||||
|
||||
<div class="main-info">
|
||||
<div class="section">
|
||||
<h3>{% trans "Date" %}</h3>
|
||||
<p class="bold">{{ parcel.created_at|date:"Y-m-d" }}</p>
|
||||
</div>
|
||||
<div class="section section-right">
|
||||
<h3>{% trans "Weight" %}</h3>
|
||||
<p class="bold">{{ parcel.weight }} KG</p>
|
||||
</div>
|
||||
<div class="section section-right">
|
||||
<h3>{% trans "Price" %}</h3>
|
||||
<p class="bold">{{ parcel.price }} OMR</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qr-code">
|
||||
<img src="data:image/png;base64,{{ qr_code }}" alt="QR Code">
|
||||
<div class="tracking-number">{{ parcel.tracking_number }}</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
{% trans "Thank you for shipping with us." %}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -63,11 +63,15 @@
|
||||
|
||||
{% if parcel.payment_status == 'pending' %}
|
||||
{% if payments_enabled %}
|
||||
<a href="{% url 'initiate_payment' parcel.id %}" class="btn btn-sm btn-outline-primary w-100 mb-3">
|
||||
<a href="{% url 'initiate_payment' parcel.id %}" class="btn btn-sm btn-outline-primary w-100 mb-2">
|
||||
<i class="fas fa-credit-card me-1"></i> {% trans "Pay Now" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<a href="{% url 'generate_parcel_label' parcel.id %}" class="btn btn-sm btn-outline-dark w-100 mb-3" target="_blank">
|
||||
<i class="fas fa-print me-1"></i> {% trans "Print Label" %}
|
||||
</a>
|
||||
|
||||
<hr>
|
||||
<p class="card-text small mb-0"><strong>{% trans "Receiver" %}:</strong> {{ parcel.receiver_name }}</p>
|
||||
@ -114,15 +118,21 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% if parcel.payment_status == 'pending' and payments_enabled %}
|
||||
<a href="{% url 'initiate_payment' parcel.id %}" class="btn btn-sm btn-outline-primary w-100 w-md-auto">
|
||||
<i class="fas fa-credit-card me-1"></i> {% trans "Pay Now" %}
|
||||
<div class="d-flex gap-2 w-100 justify-content-md-end">
|
||||
<a href="{% url 'generate_parcel_label' parcel.id %}" class="btn btn-sm btn-outline-dark" target="_blank" title="{% trans 'Print Label' %}">
|
||||
<i class="fas fa-print"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="badge {% if parcel.payment_status == 'paid' %}bg-success{% else %}bg-secondary{% endif %}">
|
||||
{{ parcel.get_payment_status_display }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if parcel.payment_status == 'pending' and payments_enabled %}
|
||||
<a href="{% url 'initiate_payment' parcel.id %}" class="btn btn-sm btn-outline-primary flex-grow-1 flex-md-grow-0">
|
||||
<i class="fas fa-credit-card me-1"></i> {% trans "Pay Now" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="badge {% if parcel.payment_status == 'paid' %}bg-success{% else %}bg-secondary{% endif %} p-2">
|
||||
{{ parcel.get_payment_status_display }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -218,6 +228,9 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'generate_parcel_label' parcel.id %}" class="btn btn-sm btn-outline-dark me-2" target="_blank" title="{% trans 'Print Label' %}">
|
||||
<i class="fas fa-print"></i>
|
||||
</a>
|
||||
{% if parcel.status == 'delivered' and parcel.carrier %}
|
||||
{% if not rating %}
|
||||
<a href="{% url 'rate_driver' parcel.id %}" class="btn btn-sm btn-outline-warning">
|
||||
|
||||
Binary file not shown.
@ -12,4 +12,11 @@ def get_rating(parcel):
|
||||
try:
|
||||
return parcel.rating
|
||||
except:
|
||||
return None
|
||||
return None
|
||||
|
||||
@register.filter
|
||||
def whatsapp_url(value):
|
||||
"""Removes non-numeric characters for WhatsApp URL."""
|
||||
if not value:
|
||||
return ""
|
||||
return "".join(filter(str.isdigit, str(value)))
|
||||
|
||||
@ -36,6 +36,7 @@ urlpatterns = [
|
||||
path('accept-parcel/<int:parcel_id>/', views.accept_parcel, name='accept_parcel'),
|
||||
path('update-status/<int:parcel_id>/', views.update_status, name='update_status'),
|
||||
path('rate-driver/<int:parcel_id>/', views.rate_driver, name='rate_driver'),
|
||||
path('parcel/<int:parcel_id>/label/', views.generate_parcel_label, name='generate_parcel_label'),
|
||||
path('initiate-payment/<int:parcel_id>/', views.initiate_payment, name='initiate_payment'),
|
||||
path('payment-success/', views.payment_success, name='payment_success'),
|
||||
path('payment-cancel/', views.payment_cancel, name='payment_cancel'),
|
||||
|
||||
@ -8,7 +8,7 @@ from .forms import UserRegistrationForm, ParcelForm, ContactForm, UserProfileFor
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import get_language
|
||||
from django.contrib import messages
|
||||
from django.http import JsonResponse
|
||||
from django.http import JsonResponse, HttpResponse
|
||||
from django.urls import reverse
|
||||
from .payment_utils import ThawaniPay
|
||||
from django.conf import settings
|
||||
@ -16,6 +16,7 @@ from django.core.mail import send_mail
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.db.models import Avg, Count
|
||||
from django.template.loader import render_to_string
|
||||
import random
|
||||
import string
|
||||
from .whatsapp_utils import (
|
||||
@ -28,6 +29,10 @@ from .whatsapp_utils import (
|
||||
from .mail import send_contact_message, send_html_email
|
||||
import json
|
||||
from ai.local_ai_api import LocalAIApi
|
||||
import weasyprint
|
||||
import qrcode
|
||||
from io import BytesIO
|
||||
import base64
|
||||
|
||||
def index(request):
|
||||
tracking_id = request.GET.get('tracking_id')
|
||||
@ -650,3 +655,53 @@ def chatbot(request):
|
||||
return JsonResponse({"success": False, "error": "Invalid JSON"})
|
||||
except Exception as e:
|
||||
return JsonResponse({"success": False, "error": str(e)})
|
||||
|
||||
@login_required
|
||||
def generate_parcel_label(request, parcel_id):
|
||||
parcel = get_object_or_404(Parcel, id=parcel_id)
|
||||
|
||||
# Security check: only shipper or carrier can print label
|
||||
if parcel.shipper != request.user and parcel.carrier != request.user:
|
||||
messages.error(request, _("You are not authorized to print this label."))
|
||||
return redirect('dashboard')
|
||||
|
||||
# Generate QR Code
|
||||
qr = qrcode.QRCode(
|
||||
version=1,
|
||||
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
||||
box_size=10,
|
||||
border=4,
|
||||
)
|
||||
qr.add_data(parcel.tracking_number)
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image(fill_color="black", back_color="white")
|
||||
|
||||
buffer = BytesIO()
|
||||
img.save(buffer, format="PNG")
|
||||
qr_image_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||||
|
||||
# Get Logo Base64
|
||||
logo_base64 = None
|
||||
platform_profile = PlatformProfile.objects.first()
|
||||
if platform_profile and platform_profile.logo:
|
||||
try:
|
||||
with open(platform_profile.logo.path, "rb") as image_file:
|
||||
logo_base64 = base64.b64encode(image_file.read()).decode()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Render Template
|
||||
html_string = render_to_string('core/parcel_label.html', {
|
||||
'parcel': parcel,
|
||||
'qr_code': qr_image_base64,
|
||||
'logo_base64': logo_base64,
|
||||
'platform_profile': platform_profile,
|
||||
})
|
||||
|
||||
# Generate PDF
|
||||
html = weasyprint.HTML(string=html_string, base_url=request.build_absolute_uri())
|
||||
pdf_file = html.write_pdf()
|
||||
|
||||
response = HttpResponse(pdf_file, content_type='application/pdf')
|
||||
response['Content-Disposition'] = f'inline; filename="label_{parcel.tracking_number}.pdf"'
|
||||
return response
|
||||
|
||||
@ -2,3 +2,5 @@ Django==5.2.7
|
||||
mysqlclient==2.2.7
|
||||
python-dotenv==1.1.1
|
||||
Pillow
|
||||
weasyprint
|
||||
qrcode
|
||||
Loading…
x
Reference in New Issue
Block a user