Capture-to-form signature placement so the customer's drawn or typed signature lands right on the signature rule of the actual form, not in a sidecar page. - migration 085: esign_records.signature_anchors (JSONB exact PDF coords, lower-left origin, points) + signed_document_minio_key - signature_stamper.py: signature_box() anchors; anchors_from_acroform() pulls the signature field /Rect from a real AcroForm (e.g. MCS-150 certifySignature); stamp_signature() overlays PNG (auto-trimmed so ink rests on the rule) or typed name, scaled to actual page size - state_trucking_authorization.py: renders the Limited Authorization to File PDF and returns (pdf_bytes, anchors) - esign_stamp.py: stamp_esign_document() downloads unsigned PDF, stamps, uploads _signed.pdf, sets signed_document_minio_key (idempotent) - dot_esign.py: extract certifySignature anchor for MCS-150/closeout forms so the federal perjury cert is signed on the line - state_trucking.py: authorization gate — first run emails signing link and PAUSES; resumes with client_approved after signing - job_server handle_esign_completed: stamp then re-dispatch - tests: test_signature_placement.py (custom form), and test_mcs150_signature_placement.py (official AcroForm) both assert the signature lands inside the recorded signature box (verified visually)
278 lines
9.6 KiB
Python
278 lines
9.6 KiB
Python
"""Generate the state-trucking "Limited Authorization to File" PDF.
|
|
|
|
This is the signed authorization the customer must provide before Performance
|
|
West can file state motor-carrier compliance documents on their behalf (NY HUT,
|
|
CT HUF, OR Weight-Mile, CA MCP/CARB, KY KYU, NM WDT, IRP/IFTA, etc.). See
|
|
docs/trucking-state-authorization-plan.md.
|
|
|
|
The generator draws an explicit signature line and records its exact PDF
|
|
coordinates via :func:`signature_box`, so the e-signature stamper can place the
|
|
client's signature precisely on that line (see signature_stamper.py).
|
|
|
|
Returns ``(pdf_bytes, anchors)`` where ``anchors`` is the list to persist in
|
|
``esign_records.signature_anchors``.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
from typing import Any
|
|
|
|
try: # normal import when loaded as part of the services package
|
|
from .signature_stamper import signature_box
|
|
except ImportError: # pragma: no cover - standalone/CLI use
|
|
from signature_stamper import signature_box # type: ignore
|
|
|
|
LOG = logging.getLogger("workers.services.state_trucking_authorization")
|
|
|
|
NAVY = "#0b1f3a"
|
|
SLATE = "#475569"
|
|
RULE = "#94a3b8"
|
|
|
|
_AUTH_BODY = (
|
|
"By signing below, the Customer named above authorizes Performance West Inc. "
|
|
"(\"Performance West\") to act as its limited agent to:"
|
|
)
|
|
|
|
_AUTH_GRANTS = [
|
|
"prepare, review, submit, and amend the state motor carrier compliance "
|
|
"filing(s) identified in this authorization;",
|
|
"access state agency portals or tax systems where representative, "
|
|
"practitioner, bulk-filer, or delegate access is available;",
|
|
"communicate with state agencies about the filing and receive filing status, "
|
|
"notices, confirmations, decals, credentials, and deficiency messages;",
|
|
"coordinate with the Customer's insurer or insurance agent for any required "
|
|
"insurance filings;",
|
|
"remit government fees, taxes, card processing fees, decals, permits, bonds, "
|
|
"and pass-through charges when collected from the Customer;",
|
|
"retain a copy of this signed authorization for agency audit or request "
|
|
"purposes.",
|
|
]
|
|
|
|
_AUTH_ACK = [
|
|
"The Customer remains responsible for the accuracy and completeness of the "
|
|
"information it provides.",
|
|
"Government fees, taxes, card processing fees, bonds/security, decals, "
|
|
"permits, and insurance filing costs are pass-through charges and may be "
|
|
"billed separately if not known at the time of order.",
|
|
"Some state filings require the Customer to grant portal access/delegation or "
|
|
"approve account changes; Performance West will request this where needed.",
|
|
"This authorization is limited to the filing(s) listed and may be revoked in "
|
|
"writing, except as to filings already submitted.",
|
|
]
|
|
|
|
|
|
def build_state_trucking_authorization(
|
|
*,
|
|
order_number: str,
|
|
entity_name: str,
|
|
service_name: str,
|
|
dot_number: str = "",
|
|
mc_number: str = "",
|
|
fein_last4: str = "",
|
|
base_state: str = "",
|
|
operating_states: list[str] | None = None,
|
|
signer_name: str = "",
|
|
signer_title: str = "",
|
|
) -> tuple[bytes, list[dict[str, Any]]]:
|
|
"""Render the authorization PDF and return (pdf_bytes, signature_anchors)."""
|
|
from reportlab.lib.colors import HexColor
|
|
from reportlab.lib.pagesizes import letter
|
|
from reportlab.lib.units import inch
|
|
from reportlab.pdfgen import canvas
|
|
|
|
buf = io.BytesIO()
|
|
page_w, page_h = letter # 612 x 792
|
|
c = canvas.Canvas(buf, pagesize=letter)
|
|
|
|
left = 0.85 * inch
|
|
right = page_w - 0.85 * inch
|
|
width = right - left
|
|
|
|
def wrap(text: str, font: str, size: float, max_w: float) -> list[str]:
|
|
c.setFont(font, size)
|
|
words = text.split()
|
|
lines: list[str] = []
|
|
cur = ""
|
|
for w in words:
|
|
trial = (cur + " " + w).strip()
|
|
if c.stringWidth(trial, font, size) <= max_w:
|
|
cur = trial
|
|
else:
|
|
if cur:
|
|
lines.append(cur)
|
|
cur = w
|
|
if cur:
|
|
lines.append(cur)
|
|
return lines
|
|
|
|
y = page_h - 0.9 * inch
|
|
|
|
# Header
|
|
c.setFillColor(HexColor(NAVY))
|
|
c.setFont("Helvetica-Bold", 16)
|
|
c.drawString(left, y, "Performance West Inc.")
|
|
y -= 18
|
|
c.setFont("Helvetica", 9.5)
|
|
c.setFillColor(HexColor(SLATE))
|
|
c.drawString(left, y, "DOT / State Motor Carrier Compliance Division | (888) 411-0383 | performancewest.net")
|
|
y -= 8
|
|
c.setStrokeColor(HexColor(RULE))
|
|
c.line(left, y, right, y)
|
|
y -= 26
|
|
|
|
c.setFillColor(HexColor(NAVY))
|
|
c.setFont("Helvetica-Bold", 13.5)
|
|
c.drawString(left, y, "Limited Authorization to File State Motor Carrier Compliance Documents")
|
|
y -= 22
|
|
|
|
# Customer / order block
|
|
def field_row(label: str, value: str) -> None:
|
|
nonlocal y
|
|
c.setFont("Helvetica-Bold", 10)
|
|
c.setFillColor(HexColor(NAVY))
|
|
c.drawString(left, y, label)
|
|
c.setFont("Helvetica", 10)
|
|
c.setFillColor(HexColor("#1f2937"))
|
|
c.drawString(left + 1.6 * inch, y, value or "—")
|
|
y -= 15
|
|
|
|
states = ", ".join(operating_states or []) if operating_states else (base_state or "—")
|
|
field_row("Customer (Carrier):", entity_name)
|
|
field_row("USDOT #:", dot_number)
|
|
if mc_number:
|
|
field_row("MC/MX/FF #:", mc_number)
|
|
if fein_last4:
|
|
field_row("FEIN (last 4):", fein_last4)
|
|
field_row("Covered filing:", service_name)
|
|
field_row("Covered state(s):", states)
|
|
field_row("Order #:", order_number)
|
|
y -= 8
|
|
|
|
# Body intro
|
|
c.setFillColor(HexColor("#1f2937"))
|
|
for ln in wrap(_AUTH_BODY, "Helvetica", 10, width):
|
|
c.setFont("Helvetica", 10)
|
|
c.drawString(left, y, ln)
|
|
y -= 14
|
|
y -= 4
|
|
|
|
# Numbered grants
|
|
for i, grant in enumerate(_AUTH_GRANTS, 1):
|
|
bullet = f"{i}."
|
|
c.setFont("Helvetica-Bold", 10)
|
|
c.drawString(left, y, bullet)
|
|
lines = wrap(grant, "Helvetica", 10, width - 0.3 * inch)
|
|
for j, ln in enumerate(lines):
|
|
c.setFont("Helvetica", 10)
|
|
c.drawString(left + 0.3 * inch, y, ln)
|
|
y -= 13
|
|
y -= 3
|
|
|
|
y -= 6
|
|
c.setFont("Helvetica-Bold", 10.5)
|
|
c.setFillColor(HexColor(NAVY))
|
|
c.drawString(left, y, "Acknowledgements")
|
|
y -= 16
|
|
for ack in _AUTH_ACK:
|
|
c.setFillColor(HexColor("#475569"))
|
|
c.setFont("Helvetica", 9.5)
|
|
c.drawString(left, y, "\u2022")
|
|
for j, ln in enumerate(wrap(ack, "Helvetica", 9.5, width - 0.25 * inch)):
|
|
c.drawString(left + 0.25 * inch, y, ln)
|
|
y -= 12
|
|
y -= 2
|
|
|
|
# ── Signature block ──────────────────────────────────────────────
|
|
# Reserve a fixed band near the bottom so the signature line position is
|
|
# deterministic. The signature box sits ON TOP of the rule.
|
|
sig_rule_y = 1.55 * inch
|
|
sig_box_h = 42.0
|
|
sig_box_w = 3.1 * inch
|
|
sig_x = left
|
|
|
|
c.setFillColor(HexColor(NAVY))
|
|
c.setFont("Helvetica-Bold", 10.5)
|
|
c.drawString(left, sig_rule_y + sig_box_h + 14, "Authorized Signature")
|
|
|
|
# The signature rule
|
|
c.setStrokeColor(HexColor("#334155"))
|
|
c.setLineWidth(1)
|
|
c.line(sig_x, sig_rule_y, sig_x + sig_box_w, sig_rule_y)
|
|
|
|
# Date rule to the right
|
|
date_x = sig_x + sig_box_w + 0.5 * inch
|
|
date_w = right - date_x
|
|
c.line(date_x, sig_rule_y, right, sig_rule_y)
|
|
|
|
c.setFont("Helvetica", 8.5)
|
|
c.setFillColor(HexColor(SLATE))
|
|
c.drawString(sig_x, sig_rule_y - 11, "Signature")
|
|
c.drawString(date_x, sig_rule_y - 11, "Date")
|
|
|
|
# Printed name / title beneath
|
|
ny = sig_rule_y - 30
|
|
c.setFont("Helvetica", 9.5)
|
|
c.setFillColor(HexColor("#1f2937"))
|
|
c.drawString(sig_x, ny, f"Printed name: {signer_name or '________________________'}")
|
|
c.drawString(date_x, ny, f"Title: {signer_title or '____________'}")
|
|
|
|
# Footer note
|
|
c.setFont("Helvetica-Oblique", 7.5)
|
|
c.setFillColor(HexColor("#94a3b8"))
|
|
c.drawString(
|
|
left,
|
|
0.75 * inch,
|
|
f"Generated {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')} | "
|
|
f"Order {order_number} | Performance West Inc.",
|
|
)
|
|
|
|
c.save()
|
|
buf.seek(0)
|
|
pdf_bytes = buf.getvalue()
|
|
|
|
# Record the exact signature box so the stamper lands the signature on the
|
|
# rule. The box bottom == the rule's y, height extends upward.
|
|
anchors = [
|
|
signature_box(
|
|
field="signer",
|
|
page=0,
|
|
x=sig_x + 4, # small inset from the line's left end
|
|
y=sig_rule_y + 1, # rest the signature just on top of the rule
|
|
w=sig_box_w - 8,
|
|
h=sig_box_h,
|
|
page_w=page_w,
|
|
page_h=page_h,
|
|
),
|
|
signature_box(
|
|
field="date",
|
|
page=0,
|
|
x=date_x + 4,
|
|
y=sig_rule_y + 1,
|
|
w=date_w - 8,
|
|
h=20.0,
|
|
page_w=page_w,
|
|
page_h=page_h,
|
|
),
|
|
]
|
|
return pdf_bytes, anchors
|
|
|
|
|
|
if __name__ == "__main__": # quick local render for visual verification
|
|
pdf, anchors = build_state_trucking_authorization(
|
|
order_number="CO-TEST1234",
|
|
entity_name="Acme Freight LLC",
|
|
service_name="New York Highway Use Tax (HUT) Registration",
|
|
dot_number="3456789",
|
|
mc_number="MC-987654",
|
|
fein_last4="1234",
|
|
base_state="NY",
|
|
operating_states=["NY", "PA", "OH"],
|
|
signer_name="John Smith",
|
|
signer_title="Owner",
|
|
)
|
|
with open("/tmp/state_trucking_authorization.pdf", "wb") as f:
|
|
f.write(pdf)
|
|
print("wrote /tmp/state_trucking_authorization.pdf")
|
|
print("anchors:", anchors)
|