Catalyst / admin/Apex 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Apex

public

Bid Management and Orchestration Tool with AI Capability

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
Apex / Synapse-Apexv2 / backend / app / routers / exports.py 4003 B · main
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import io

from docx import Document as Docx
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import StreamingResponse
from openpyxl import Workbook
from sqlalchemy.orm import Session
from xhtml2pdf import pisa

from app.core.database import get_db
from app.core.deps import get_current_user
from app.models import Bid, CostLine, CostSheet, Package, PricingScenario, User

router = APIRouter(prefix="/api/bids", tags=["exports"])


def _latest_pkg(db: Session, bid_id: int) -> Package:
    pkg = db.query(Package).filter(Package.bid_id == bid_id).order_by(Package.version.desc()).first()
    if not pkg:
        raise HTTPException(400, "Assemble the package first")
    return pkg


@router.get("/{bid_id}/export/docx")
def export_docx(bid_id: int, db: Session = Depends(get_db), _: User = Depends(get_current_user)):
    pkg = _latest_pkg(db, bid_id)
    snap = pkg.snapshot
    doc = Docx()
    doc.add_heading(snap["bid"]["title"], 0)
    doc.add_paragraph(f"Client: {snap['bid']['client']}    Reference: {snap['bid']['ref']}")
    doc.add_heading("Executive summary", level=1)
    doc.add_paragraph(snap.get("justification") or "See commercial response.")
    doc.add_heading("Responses", level=1)
    for s in snap["sections"]:
        doc.add_heading(f"{s['ref']}", level=2)
        doc.add_paragraph(s["question"]).italic = True
        doc.add_paragraph(s["response"] or "[No response]")
    buf = io.BytesIO()
    doc.save(buf)
    buf.seek(0)
    return StreamingResponse(
        buf, media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        headers={"Content-Disposition": f"attachment; filename=bid_{bid_id}_response.docx"},
    )


@router.get("/{bid_id}/export/xlsx")
def export_xlsx(bid_id: int, db: Session = Depends(get_db), _: User = Depends(get_current_user)):
    wb = Workbook()
    ws = wb.active
    ws.title = "Cost sheet"
    ws.append(["Group", "Description", "Qty", "Hours", "Rate", "Monthly", "One-off"])
    sheet = db.query(CostSheet).filter(CostSheet.bid_id == bid_id).first()
    if sheet:
        for l in db.query(CostLine).filter(CostLine.sheet_id == sheet.id).all():
            ws.append([l.group, l.description, float(l.qty), float(l.hours),
                       float(l.rate_snapshot), float(l.monthly), float(l.oneoff)])
    ws2 = wb.create_sheet("Pricing")
    ws2.append(["Scenario", "Risk %", "Mgmt %", "Margin %", "Sell price"])
    for sc in db.query(PricingScenario).filter(PricingScenario.bid_id == bid_id).all():
        ws2.append([sc.name, float(sc.risk_pct), float(sc.mgmt_pct), float(sc.margin_pct),
                    str((sc.computed or {}).get("sell_price", ""))])
    buf = io.BytesIO()
    wb.save(buf)
    buf.seek(0)
    return StreamingResponse(
        buf, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        headers={"Content-Disposition": f"attachment; filename=bid_{bid_id}_commercials.xlsx"},
    )


@router.get("/{bid_id}/export/pdf")
def export_pdf(bid_id: int, db: Session = Depends(get_db), _: User = Depends(get_current_user)):
    pkg = _latest_pkg(db, bid_id)
    snap = pkg.snapshot
    price = (snap.get("pricing") or {}).get("sell_price", "TBC")
    rows = "".join(
        f"<tr><td><b>{s['ref']}</b><br/>{s['question']}</td>"
        f"<td>{'✔ approved' if s['approved'] else 'draft'}</td></tr>"
        for s in snap["sections"]
    )
    html = f"""
    <h1>{snap['bid']['title']}</h1>
    <p>Client: {snap['bid']['client']} &middot; Ref: {snap['bid']['ref']}</p>
    <h2>Commercial summary</h2>
    <p>Total contract sell price: <b>{price}</b></p>
    <h2>Response coverage</h2>
    <table border="1" cellpadding="6" cellspacing="0" width="100%">{rows}</table>
    """
    buf = io.BytesIO()
    pisa.CreatePDF(html, dest=buf)
    buf.seek(0)
    return StreamingResponse(
        buf, media_type="application/pdf",
        headers={"Content-Disposition": f"attachment; filename=bid_{bid_id}_summary.pdf"},
    )