Files
TMS/transportmanager/server/utils/pdf.py

204 lines
7.7 KiB
Python

from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.platypus import Table, TableStyle, Paragraph, SimpleDocTemplate, Spacer, Image
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet
import io
import os
import logging
# --- helpers ---------------------------------------------------------------
def _resolve_logo_path(logo_path: str | None) -> str | None:
"""Return a readable logo path or None. Tries multiple fallbacks.
This avoids crashes when a caller passes a path that exists only in the
client container (e.g., '/client/assets/...') while we're running in the
server container on Fly.
"""
here = os.path.dirname(__file__)
project_root = os.path.abspath(os.path.join(here, "..", ".."))
candidates = []
# 1) if caller provided a path, try it as-is
if logo_path:
candidates.append(logo_path)
# also try without a leading slash inside our image tree
stripped = logo_path.lstrip("/\\")
candidates.append(os.path.join(project_root, stripped))
# 2) Known locations in this repo layout
candidates.append(os.path.join(project_root, "client", "assets", "images", "truck_logo_black.png"))
candidates.append(os.path.join(project_root, "assets", "images", "truck_logo_black.png"))
# 3) Allow override via env
env_path = os.getenv("DEFAULT_LOGO_PATH")
if env_path:
candidates.insert(0, env_path)
for p in candidates:
try:
if p and os.path.isfile(p):
return p
except Exception:
# some paths may be malformed on certain platforms
continue
return None
def generate_order_pdf(order, user_data, transporter_data, logo_path, save_to_disk=True):
#print(order)
#print(f'user data: {user_data}')
if 'address' in user_data:
address = user_data.get("address")
if address:
address = address.replace(" %",", ")
else:
address = ''
else:
address=''
buffer = io.BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4)
elements = []
styles = getSampleStyleSheet()
# Resolve logo path robustly across local/dev and Fly
logo_path = _resolve_logo_path(logo_path)
# Prepare texts
user_text = f"""<b>{user_data.get("name", "")}</b><br/>
{user_data.get("register_number", "")}<br/>
{user_data.get("vat", "")}<br/>
{address}<br/>
{""}<br/>
{user_data.get("contact_name", "")}<br/>
{user_data.get("phone", "")}<br/>
{user_data.get("email", "")}
"""
transporter_text = f"""<b>{transporter_data.get("name", "")}</b><br/>
{transporter_data.get("contact_person", "")}<br/>
{transporter_data.get("phone", "")}<br/>
{transporter_data.get("email", "")}
"""
# Logo (centered), tolerate missing file
logo = None
if logo_path:
try:
logo = Image(logo_path, width=120, mask='auto', height=60)
logo.hAlign = 'CENTER'
except Exception as e:
logging.warning("PDF: failed to load logo at %s: %s", logo_path, e)
logo = None
# Top section: transporter - logo - user
top_section = Table([
[
Paragraph(transporter_text, styles['Normal']),
logo,
Paragraph(user_text, styles['Normal'])
]
], colWidths=[200, 150, 200])
top_section.setStyle(TableStyle([
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("ALIGN", (0, 0), (0, 0), "LEFT"),
("ALIGN", (1, 0), (1, 0), "CENTER"),
("ALIGN", (2, 0), (2, 0), "RIGHT"),
("LEFTPADDING", (0, 0), (0, 0), 20),
]))
elements.append(top_section)
elements.append(Spacer(1, 12))
# Order number and current date (centered, vertically stacked)
from datetime import datetime
header_info = Table([
[Paragraph(f"<b>Loading Order</b>: {order['order_number']}", styles["Normal"])],
[Paragraph(f"Date: {datetime.now().strftime('%d/%m/%Y')}", styles["Normal"])]
], colWidths=[450])
header_info.setStyle(TableStyle([
("ALIGN", (0, 0), (-1, -1), "CENTER")
]))
elements.append(header_info)
elements.append(Spacer(1, 12))
# Order Summary Table
elements.append(Paragraph("Summary", styles['Heading3']))
summary_data = [
["Details", Paragraph("Values", styles["Normal"])],
["Truck Reg. No.", Paragraph(str(order["track_reg_number"]), styles["Normal"])],
["Trailer Reg. No.", Paragraph(str(order["trailer_reg_number"]), styles["Normal"])],
["Product Description", Paragraph(str(order["products_description"]), styles["Normal"])],
["LDM", Paragraph(str(order["ldb_quantity"]), styles["Normal"])],
["KG", Paragraph(str(order["kg_quantity"]), styles["Normal"])],
["Price", Paragraph(str(order["paid_price"]), styles["Normal"])],
]
summary_table = Table(summary_data, colWidths=[150, 350])
summary_table.setStyle(TableStyle([
("GRID", (0, 0), (-1, -1), 0.5, colors.black),
("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("WORDWRAP", (0, 0), (-1, -1), "CJK"),
("ALIGN", (0, 0), (-1, -1), "LEFT"),
]))
elements.append(summary_table)
elements.append(Spacer(1, 24))
# Loading Points
elements.append(Paragraph("Loading Sequence", styles['Heading3']))
loading_data = [[Paragraph("Address", styles["Normal"]), Paragraph("Date & Hour", styles["Normal"]), Paragraph("Instructions", styles["Normal"])]]
for l in order["loading_addresses"]:
loading_data.append([
Paragraph(f"{l["loading_address_name"]}: {l["loading_address"]}", styles["Normal"]),
Paragraph(f"{l['loading_date']} {l['loading_hour']}", styles["Normal"]),
Paragraph(str(l["loading_informatins"]), styles["Normal"])
])
loading_table = Table(loading_data, colWidths=[200, 100, 200])
loading_table.setStyle(TableStyle([
("GRID", (0, 0), (-1, -1), 0.5, colors.black),
("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("WORDWRAP", (0, 0), (-1, -1), "CJK"),
("ALIGN", (0, 0), (-1, -1), "LEFT"),
]))
elements.append(loading_table)
elements.append(Spacer(1, 24))
# Unloading Points
elements.append(Paragraph("Unloading Sequence", styles['Heading3']))
unloading_data = [[Paragraph("Address", styles["Normal"]), Paragraph("Date & Hour", styles["Normal"]), Paragraph("Instructions", styles["Normal"])]]
for u in order["unloading_addresses"]:
unloading_data.append([
Paragraph(f"{u["unloading_address_name"]}: {u["unloading_address"]}", styles["Normal"]),
Paragraph(f"{u['unloading_date']} {u['unloading_hour']}", styles["Normal"]),
Paragraph(str(u["unloading_informatins"]), styles["Normal"])
])
unloading_table = Table(unloading_data, colWidths=[200, 100, 200])
unloading_table.setStyle(TableStyle([
("GRID", (0, 0), (-1, -1), 0.5, colors.black),
("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("WORDWRAP", (0, 0), (-1, -1), "CJK"),
("ALIGN", (0, 0), (-1, -1), "LEFT"),
]))
elements.append(unloading_table)
elements.append(Spacer(1, 24))
elements.append(Paragraph("Terms and Conditions", styles["Heading3"]),)
elements.append(
Paragraph(str(order["terms"]), styles["Normal"])
)
doc.build(elements)
buffer.seek(0)
user_id = order['user_id']
if user_data['user_role'] == 'company_user':
user_id = user_data['company_id']
if save_to_disk:
save_path=f"generated_pdfs/order_{user_id}_{order['order_number']}.pdf"
with open(save_path, "wb") as f:
f.write(buffer.getvalue())
return buffer