Upload 4 files
Browse files
main.py
CHANGED
|
@@ -5,7 +5,7 @@ SQLite + SQLModel + FastAPI
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
from __future__ import annotations
|
| 8 |
-
from typing import Optional, List
|
| 9 |
from datetime import datetime, date
|
| 10 |
import os
|
| 11 |
import pathlib
|
|
@@ -137,7 +137,7 @@ class InvoiceItem(SQLModel, table=True):
|
|
| 137 |
invoice: Optional[Invoice] = Relationship(back_populates="items")
|
| 138 |
|
| 139 |
# --------------------------
|
| 140 |
-
# Totals
|
| 141 |
# --------------------------
|
| 142 |
class MoneyBreakdown(BaseModel):
|
| 143 |
subtotal: float
|
|
@@ -147,13 +147,22 @@ class MoneyBreakdown(BaseModel):
|
|
| 147 |
def round2(v: float) -> float:
|
| 148 |
return float(f"{v:.2f}")
|
| 149 |
|
| 150 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
subtotal = 0.0
|
| 152 |
tax = 0.0
|
| 153 |
for it in items:
|
| 154 |
-
|
|
|
|
|
|
|
|
|
|
| 155 |
subtotal += line
|
| 156 |
-
tax += line *
|
| 157 |
return MoneyBreakdown(subtotal=round2(subtotal), tax=round2(tax), total=round2(subtotal + tax))
|
| 158 |
|
| 159 |
# --------------------------
|
|
@@ -176,7 +185,6 @@ def custom_openapi():
|
|
| 176 |
description="Mini Invoice/Estimate SaaS API",
|
| 177 |
routes=app.routes,
|
| 178 |
)
|
| 179 |
-
# securitySchemes を追加
|
| 180 |
openapi_schema.setdefault("components", {}).setdefault("securitySchemes", {})
|
| 181 |
openapi_schema["components"]["securitySchemes"]["APIKeyHeader"] = {
|
| 182 |
"type": "apiKey",
|
|
@@ -242,7 +250,7 @@ def get_quote(quote_id: int, session: Session = Depends(get_session)):
|
|
| 242 |
if not q:
|
| 243 |
raise HTTPException(404, "Quote not found")
|
| 244 |
items = session.exec(select(QuoteItem).where(QuoteItem.quote_id == quote_id)).all()
|
| 245 |
-
totals = compute_totals(
|
| 246 |
return {"quote": q, "items": items, "totals": totals}
|
| 247 |
|
| 248 |
@app.post("/quotes/{quote_id}/items", dependencies=[Depends(require_api_key)])
|
|
@@ -267,7 +275,7 @@ def get_invoice(invoice_id: int, session: Session = Depends(get_session)):
|
|
| 267 |
if not inv:
|
| 268 |
raise HTTPException(404, "Invoice not found")
|
| 269 |
items = session.exec(select(InvoiceItem).where(InvoiceItem.invoice_id == invoice_id)).all()
|
| 270 |
-
totals = compute_totals(
|
| 271 |
return {"invoice": inv, "items": items, "totals": totals}
|
| 272 |
|
| 273 |
@app.post("/invoices/{invoice_id}/items", dependencies=[Depends(require_api_key)])
|
|
@@ -285,4 +293,3 @@ def root():
|
|
| 285 |
# -------- ChatGPT router を最後に組み込む(app 定義後) --------
|
| 286 |
from openai_integration import router as ai_router
|
| 287 |
app.include_router(ai_router, prefix="/ai", tags=["ai"])
|
| 288 |
-
|
|
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
from __future__ import annotations
|
| 8 |
+
from typing import Optional, List, Any
|
| 9 |
from datetime import datetime, date
|
| 10 |
import os
|
| 11 |
import pathlib
|
|
|
|
| 137 |
invoice: Optional[Invoice] = Relationship(back_populates="items")
|
| 138 |
|
| 139 |
# --------------------------
|
| 140 |
+
# Totals(堅牢化)
|
| 141 |
# --------------------------
|
| 142 |
class MoneyBreakdown(BaseModel):
|
| 143 |
subtotal: float
|
|
|
|
| 147 |
def round2(v: float) -> float:
|
| 148 |
return float(f"{v:.2f}")
|
| 149 |
|
| 150 |
+
def _get(v: Any, key: str, default=0.0):
|
| 151 |
+
# モデルでも dict でもOKにする
|
| 152 |
+
if isinstance(v, dict):
|
| 153 |
+
return v.get(key, default)
|
| 154 |
+
return getattr(v, key, default)
|
| 155 |
+
|
| 156 |
+
def compute_totals(items: list[Any]) -> MoneyBreakdown:
|
| 157 |
subtotal = 0.0
|
| 158 |
tax = 0.0
|
| 159 |
for it in items:
|
| 160 |
+
qty = float(_get(it, "quantity", 0) or 0)
|
| 161 |
+
unit = float(_get(it, "unit_price", 0) or 0)
|
| 162 |
+
rate = float(_get(it, "tax_rate", 0) or 0)
|
| 163 |
+
line = qty * unit
|
| 164 |
subtotal += line
|
| 165 |
+
tax += line * rate
|
| 166 |
return MoneyBreakdown(subtotal=round2(subtotal), tax=round2(tax), total=round2(subtotal + tax))
|
| 167 |
|
| 168 |
# --------------------------
|
|
|
|
| 185 |
description="Mini Invoice/Estimate SaaS API",
|
| 186 |
routes=app.routes,
|
| 187 |
)
|
|
|
|
| 188 |
openapi_schema.setdefault("components", {}).setdefault("securitySchemes", {})
|
| 189 |
openapi_schema["components"]["securitySchemes"]["APIKeyHeader"] = {
|
| 190 |
"type": "apiKey",
|
|
|
|
| 250 |
if not q:
|
| 251 |
raise HTTPException(404, "Quote not found")
|
| 252 |
items = session.exec(select(QuoteItem).where(QuoteItem.quote_id == quote_id)).all()
|
| 253 |
+
totals = compute_totals(items) # dict化せず、そのまま渡す
|
| 254 |
return {"quote": q, "items": items, "totals": totals}
|
| 255 |
|
| 256 |
@app.post("/quotes/{quote_id}/items", dependencies=[Depends(require_api_key)])
|
|
|
|
| 275 |
if not inv:
|
| 276 |
raise HTTPException(404, "Invoice not found")
|
| 277 |
items = session.exec(select(InvoiceItem).where(InvoiceItem.invoice_id == invoice_id)).all()
|
| 278 |
+
totals = compute_totals(items)
|
| 279 |
return {"invoice": inv, "items": items, "totals": totals}
|
| 280 |
|
| 281 |
@app.post("/invoices/{invoice_id}/items", dependencies=[Depends(require_api_key)])
|
|
|
|
| 293 |
# -------- ChatGPT router を最後に組み込む(app 定義後) --------
|
| 294 |
from openai_integration import router as ai_router
|
| 295 |
app.include_router(ai_router, prefix="/ai", tags=["ai"])
|
|
|