admin / Apex
publicBid Management and Orchestration Tool with AI Capability
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']} · 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"}, ) |