""" Web UI Lambda. Serves a simple HTML dashboard for viewing purchase orders. Accessed via Lambda Function URL. """ import os from decimal import Decimal from html import escape as esc import boto3 dynamodb = boto3.resource("dynamodb") PO_TABLE = os.environ.get("PO_TABLE", "purchase-orders") def get_purchase_orders(limit=500): table = dynamodb.Table(PO_TABLE) items = [] response = table.scan() items.extend(response.get("Items", [])) while "LastEvaluatedKey" in response: response = table.scan(ExclusiveStartKey=response["LastEvaluatedKey"]) items.extend(response.get("Items", [])) items.sort(key=lambda x: x.get("processed_at", ""), reverse=True) return items[:limit] def render_badge(value, color_map): if not value: value = "unknown" color = color_map.get(value.lower(), "#9ca3af") label = esc(value.replace("_", " ").title()) return f'{label}' STATUS_COLORS = { "issued": "#3b82f6", "pending buyer action": "#f59e0b", "open": "#3b82f6", "closed": "#10b981", "cancelled": "#ef4444", "soft closed": "#6b7280", } EMAIL_TYPE_COLORS = { "new_po": "#3b82f6", "revision": "#f59e0b", "cancellation": "#ef4444", } def fmt_currency(val): if val is None: return "" if isinstance(val, Decimal): val = float(val) if isinstance(val, (int, float)): return f"${val:,.2f}" return str(val) def render_po_detail(po): po_number = esc(po.get("po_number", "")) fields = [ ("PO Number", po_number), ("Status", render_badge(po.get("po_status", ""), STATUS_COLORS)), ("Email Type", render_badge(po.get("email_type", ""), EMAIL_TYPE_COLORS)), ("Total Amount", fmt_currency(po.get("total_amount"))), ("Currency", esc(po.get("currency", "")) or None), ("Supplier", esc((po.get("supplier") or {}).get("name") or "") or None), ("Site Code", esc(po.get("site_code", "")) or None), ("State", esc(po.get("state", "")) or None), ("Trade", esc(po.get("trade", "")) or None), ("Fiscal Year", esc(po.get("fiscal_year", "")) or None), ("Coupa Category", esc(po.get("coupa_category", "")) or None), ("Submitted By", esc(po.get("submitted_by", "")) or None), ("On Behalf Of", esc(po.get("on_behalf_of", "")) or None), ("Order Date", esc(po.get("order_date", "")) or None), ("Revision Date", esc(po.get("revision_date", "")) or None), ("Payment Terms", esc(po.get("payment_terms", "")) or None), ("Requisition #", esc(po.get("requisition_number", "")) or None), ("Department", esc(po.get("department", "")) or None), ("Data Source", esc(po.get("data_source", "")) or None), ("Processed At", esc(po.get("processed_at", "")) or None), ] ship_to = po.get("ship_to") or {} if any(ship_to.values()): ship_parts = [] if ship_to.get("name"): ship_parts.append(esc(ship_to["name"])) if ship_to.get("address"): ship_parts.append(esc(ship_to["address"])) if ship_to.get("location_code"): ship_parts.append(f"Location: {esc(ship_to['location_code'])}") if ship_to.get("attn"): ship_parts.append(f"Attn: {esc(ship_to['attn'])}") fields.append(("Ship To", "
".join(ship_parts))) view_url = po.get("view_order_url") if view_url and view_url.startswith(("https://", "http://")): escaped_url = esc(view_url, quote=True) fields.append( ( "Coupa Link", f'View in Coupa', ) ) details_html = "" for label, value in fields: if value: details_html += f"""
{label}
{value}
""" # Line items line_items = po.get("line_items") or [] items_html = "" if line_items: rows = "" for item in line_items: qty = esc(str(item.get("quantity", "") or "")) unit = esc(str(item.get("unit", "") or "")) price = esc(str(item.get("price", "") or "")) rows += f""" {esc(str(item.get("description", "")))} {qty} {unit} {price} {fmt_currency(item.get("amount"))} {esc(str(item.get("need_by", "") or ""))} """ items_html = f"""

Line Items ({len(line_items)})

{rows}
Description Qty Unit Price Amount Need By
""" return f""" PO {po_number} - Sea Haven
← All Purchase Orders

Purchase Order {po_number}

{details_html}
{items_html}
""" def render_po_list(purchase_orders): rows = "" for po in purchase_orders: po_number = esc(po.get("po_number", "")) supplier = esc((po.get("supplier") or {}).get("name", "")) site_code = esc(po.get("site_code", "")) trade = esc(po.get("trade", "")) status = po.get("po_status", "") total = fmt_currency(po.get("total_amount")) processed = esc((po.get("processed_at") or "")[:16]) rows += f""" {po_number} {supplier} {site_code} {trade} {render_badge(status, STATUS_COLORS)} {total} {processed} """ return f""" Purchase Orders - Sea Haven

Purchase Orders

{len(purchase_orders)} most recent
{rows if rows else ''}
PO # Supplier Site Trade Status Amount Processed
No purchase orders yet.
""" def handler(event, context): path = event.get("rawPath", "/") qs = event.get("queryStringParameters") or {} if path == "/po" and "id" in qs: po_number = qs["id"] table = dynamodb.Table(PO_TABLE) result = table.get_item(Key={"po_number": po_number}) po = result.get("Item") if not po: return { "statusCode": 404, "headers": {"Content-Type": "text/html"}, "body": "

Purchase order not found

", } html = render_po_detail(po) else: purchase_orders = get_purchase_orders() html = render_po_list(purchase_orders) return { "statusCode": 200, "headers": {"Content-Type": "text/html"}, "body": html, }