Upload main.py
Browse files
main.py
CHANGED
|
@@ -122,6 +122,7 @@ class Invoice(SQLModel, table=True):
|
|
| 122 |
due_date: Optional[date] = None
|
| 123 |
notes: Optional[str] = None
|
| 124 |
|
|
|
|
| 125 |
paid_at: Optional[datetime] = None
|
| 126 |
paid_amount: Optional[float] = None
|
| 127 |
payment_method: Optional[str] = None
|
|
@@ -158,6 +159,7 @@ class CreateInvoiceIn(BaseModel):
|
|
| 158 |
customer_id: int
|
| 159 |
due_date: Optional[date] = None
|
| 160 |
notes: Optional[str] = None
|
|
|
|
| 161 |
quote_id: Optional[int] = None
|
| 162 |
|
| 163 |
class InvoiceItemIn(BaseModel):
|
|
@@ -286,7 +288,6 @@ def create_customer(payload: Customer, session: Session = Depends(get_session)):
|
|
| 286 |
session.add(payload); session.commit(); session.refresh(payload)
|
| 287 |
return payload
|
| 288 |
|
| 289 |
-
# ---- Customers 一覧APIの置き換え ----
|
| 290 |
@app.get("/customers", dependencies=[Depends(require_api_key)])
|
| 291 |
def list_customers(
|
| 292 |
q: Optional[str] = Query(default=None, description="free text search (name/email/phone)"),
|
|
@@ -294,25 +295,30 @@ def list_customers(
|
|
| 294 |
offset: int = Query(0, ge=0),
|
| 295 |
session: Session = Depends(get_session),
|
| 296 |
):
|
| 297 |
-
|
|
|
|
|
|
|
|
|
|
| 298 |
if q:
|
| 299 |
like = f"%{q}%"
|
| 300 |
-
|
| 301 |
(Customer.name.ilike(like)) |
|
| 302 |
(Customer.email.ilike(like)) |
|
| 303 |
(Customer.phone.ilike(like))
|
| 304 |
)
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
|
|
|
|
|
|
| 308 |
return {"data": rows, "pagination": {"total": total, "limit": limit, "offset": offset}}
|
|
|
|
| 309 |
# -------- Products --------
|
| 310 |
@app.post("/products", dependencies=[Depends(require_api_key)])
|
| 311 |
def create_product(payload: Product, session: Session = Depends(get_session)):
|
| 312 |
session.add(payload); session.commit(); session.refresh(payload)
|
| 313 |
return payload
|
| 314 |
|
| 315 |
-
# ---- Products 一覧APIの total も同様に修正 ----
|
| 316 |
@app.get("/products", dependencies=[Depends(require_api_key)])
|
| 317 |
def list_products(
|
| 318 |
limit: int = Query(50, ge=1, le=200),
|
|
@@ -320,7 +326,7 @@ def list_products(
|
|
| 320 |
session: Session = Depends(get_session),
|
| 321 |
):
|
| 322 |
base = select(Product)
|
| 323 |
-
total = session.exec(select(func.count(
|
| 324 |
rows = session.exec(base.offset(offset).limit(limit)).all()
|
| 325 |
return {"data": rows, "pagination": {"total": total, "limit": limit, "offset": offset}}
|
| 326 |
|
|
@@ -365,21 +371,21 @@ def create_invoice(payload: CreateInvoiceIn, session: Session = Depends(get_sess
|
|
| 365 |
inv = Invoice(customer_id=payload.customer_id, due_date=payload.due_date, notes=payload.notes)
|
| 366 |
session.add(inv); session.commit(); session.refresh(inv)
|
| 367 |
|
|
|
|
| 368 |
if payload.quote_id:
|
| 369 |
q = session.get(Quote, payload.quote_id)
|
| 370 |
if not q:
|
| 371 |
raise HTTPException(404, "Quote not found to copy")
|
| 372 |
q_items = session.exec(select(QuoteItem).where(QuoteItem.quote_id == payload.quote_id)).all()
|
| 373 |
for it in q_items:
|
| 374 |
-
|
| 375 |
invoice_id=inv.id,
|
| 376 |
product_id=it.product_id,
|
| 377 |
description=it.description,
|
| 378 |
quantity=it.quantity,
|
| 379 |
unit_price=it.unit_price,
|
| 380 |
tax_rate=it.tax_rate,
|
| 381 |
-
)
|
| 382 |
-
session.add(new_item)
|
| 383 |
session.commit()
|
| 384 |
|
| 385 |
session.refresh(inv)
|
|
@@ -510,10 +516,10 @@ def wizard_create_invoice(payload: WizardInvoiceIn, session: Session = Depends(g
|
|
| 510 |
except Exception as e:
|
| 511 |
raise HTTPException(400, f"Wizard failed: {e}")
|
| 512 |
|
| 513 |
-
# ---- ルート(UI
|
| 514 |
-
@app.get("/"
|
| 515 |
-
def
|
| 516 |
-
return
|
| 517 |
|
| 518 |
# -------- ChatGPT router(最後に組み込む) --------
|
| 519 |
from openai_integration import router as ai_router
|
|
|
|
| 122 |
due_date: Optional[date] = None
|
| 123 |
notes: Optional[str] = None
|
| 124 |
|
| 125 |
+
# 入金関連
|
| 126 |
paid_at: Optional[datetime] = None
|
| 127 |
paid_amount: Optional[float] = None
|
| 128 |
payment_method: Optional[str] = None
|
|
|
|
| 159 |
customer_id: int
|
| 160 |
due_date: Optional[date] = None
|
| 161 |
notes: Optional[str] = None
|
| 162 |
+
# 見積→請求コピー用
|
| 163 |
quote_id: Optional[int] = None
|
| 164 |
|
| 165 |
class InvoiceItemIn(BaseModel):
|
|
|
|
| 288 |
session.add(payload); session.commit(); session.refresh(payload)
|
| 289 |
return payload
|
| 290 |
|
|
|
|
| 291 |
@app.get("/customers", dependencies=[Depends(require_api_key)])
|
| 292 |
def list_customers(
|
| 293 |
q: Optional[str] = Query(default=None, description="free text search (name/email/phone)"),
|
|
|
|
| 295 |
offset: int = Query(0, ge=0),
|
| 296 |
session: Session = Depends(get_session),
|
| 297 |
):
|
| 298 |
+
# 件数とデータ取得を素直に分ける(環境依存の例外を回避)
|
| 299 |
+
base = select(Customer)
|
| 300 |
+
count_stmt = select(func.count(Customer.id))
|
| 301 |
+
|
| 302 |
if q:
|
| 303 |
like = f"%{q}%"
|
| 304 |
+
cond = (
|
| 305 |
(Customer.name.ilike(like)) |
|
| 306 |
(Customer.email.ilike(like)) |
|
| 307 |
(Customer.phone.ilike(like))
|
| 308 |
)
|
| 309 |
+
base = base.where(cond)
|
| 310 |
+
count_stmt = count_stmt.where(cond)
|
| 311 |
+
|
| 312 |
+
total = session.exec(count_stmt).scalar() or 0
|
| 313 |
+
rows = session.exec(base.offset(offset).limit(limit)).all()
|
| 314 |
return {"data": rows, "pagination": {"total": total, "limit": limit, "offset": offset}}
|
| 315 |
+
|
| 316 |
# -------- Products --------
|
| 317 |
@app.post("/products", dependencies=[Depends(require_api_key)])
|
| 318 |
def create_product(payload: Product, session: Session = Depends(get_session)):
|
| 319 |
session.add(payload); session.commit(); session.refresh(payload)
|
| 320 |
return payload
|
| 321 |
|
|
|
|
| 322 |
@app.get("/products", dependencies=[Depends(require_api_key)])
|
| 323 |
def list_products(
|
| 324 |
limit: int = Query(50, ge=1, le=200),
|
|
|
|
| 326 |
session: Session = Depends(get_session),
|
| 327 |
):
|
| 328 |
base = select(Product)
|
| 329 |
+
total = session.exec(select(func.count(Product.id))).scalar() or 0
|
| 330 |
rows = session.exec(base.offset(offset).limit(limit)).all()
|
| 331 |
return {"data": rows, "pagination": {"total": total, "limit": limit, "offset": offset}}
|
| 332 |
|
|
|
|
| 371 |
inv = Invoice(customer_id=payload.customer_id, due_date=payload.due_date, notes=payload.notes)
|
| 372 |
session.add(inv); session.commit(); session.refresh(inv)
|
| 373 |
|
| 374 |
+
# 見積→請求コピー
|
| 375 |
if payload.quote_id:
|
| 376 |
q = session.get(Quote, payload.quote_id)
|
| 377 |
if not q:
|
| 378 |
raise HTTPException(404, "Quote not found to copy")
|
| 379 |
q_items = session.exec(select(QuoteItem).where(QuoteItem.quote_id == payload.quote_id)).all()
|
| 380 |
for it in q_items:
|
| 381 |
+
session.add(InvoiceItem(
|
| 382 |
invoice_id=inv.id,
|
| 383 |
product_id=it.product_id,
|
| 384 |
description=it.description,
|
| 385 |
quantity=it.quantity,
|
| 386 |
unit_price=it.unit_price,
|
| 387 |
tax_rate=it.tax_rate,
|
| 388 |
+
))
|
|
|
|
| 389 |
session.commit()
|
| 390 |
|
| 391 |
session.refresh(inv)
|
|
|
|
| 516 |
except Exception as e:
|
| 517 |
raise HTTPException(400, f"Wizard failed: {e}")
|
| 518 |
|
| 519 |
+
# ---- ルート(UI を /app に任せつつ、/ は API の案内) ----
|
| 520 |
+
@app.get("/")
|
| 521 |
+
def root():
|
| 522 |
+
return {"ok": True, "app": "mini-invoice-saas", "docs": "/docs", "ui": "/app/"}
|
| 523 |
|
| 524 |
# -------- ChatGPT router(最後に組み込む) --------
|
| 525 |
from openai_integration import router as ai_router
|