204 lines
7.7 KiB
Python
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
|
|
|