Corin1998 commited on
Commit
f359272
·
verified ·
1 Parent(s): b256e0a

Upload main.py

Browse files
Files changed (1) hide show
  1. main.py +55 -21
main.py CHANGED
@@ -17,6 +17,7 @@ from fastapi.staticfiles import StaticFiles
17
  from pydantic import BaseModel, Field, model_validator
18
  from sqlmodel import Field as SQLField, Session, SQLModel, create_engine, select, Relationship
19
  from sqlalchemy import func
 
20
 
21
  # --------------------------
22
  # Auth
@@ -64,6 +65,7 @@ engine = create_engine(
64
  echo=False,
65
  connect_args={"check_same_thread": False} if DB_URL.startswith("sqlite") else {}
66
  )
 
67
 
68
  def get_session():
69
  with Session(engine) as session:
@@ -122,7 +124,7 @@ class Invoice(SQLModel, table=True):
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,7 +161,7 @@ class CreateInvoiceIn(BaseModel):
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):
@@ -214,7 +216,7 @@ class WizardInvoiceIn(BaseModel):
214
  return v
215
 
216
  # --------------------------
217
- # Totals
218
  # --------------------------
219
  class MoneyBreakdown(BaseModel):
220
  subtotal: float
@@ -251,7 +253,7 @@ app.add_middleware(
251
  allow_methods=["*"], allow_headers=["*"],
252
  )
253
 
254
- # Swagger の Authorize を出す(X-API-Key
255
  def custom_openapi():
256
  if app.openapi_schema:
257
  return app.openapi_schema
@@ -285,8 +287,20 @@ app.mount("/app", StaticFiles(directory="static", html=True), name="app")
285
  # -------- Customers --------
286
  @app.post("/customers", dependencies=[Depends(require_api_key)])
287
  def create_customer(payload: Customer, session: Session = Depends(get_session)):
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(
@@ -299,23 +313,43 @@ def list_customers(
299
  count_stmt = select(func.count(Customer.id))
300
 
301
  if q:
302
- like = f"%{q}%"
303
- cond = (
304
- func.lower(Customer.name).like(func.lower(like)) |
305
- func.lower(Customer.email).like(func.lower(like)) |
306
- func.lower(Customer.phone).like(func.lower(like))
307
- )
 
 
 
 
 
 
 
308
  base = base.where(cond)
309
  count_stmt = count_stmt.where(cond)
310
 
311
  total = session.exec(count_stmt).scalar() or 0
312
  rows = session.exec(base.offset(offset).limit(limit)).all()
313
  return {"data": rows, "pagination": {"total": total, "limit": limit, "offset": offset}}
 
314
  # -------- Products --------
315
  @app.post("/products", dependencies=[Depends(require_api_key)])
316
  def create_product(payload: Product, session: Session = Depends(get_session)):
317
- session.add(payload); session.commit(); session.refresh(payload)
318
- return payload
 
 
 
 
 
 
 
 
 
 
 
 
319
 
320
  @app.get("/products", dependencies=[Depends(require_api_key)])
321
  def list_products(
@@ -369,21 +403,21 @@ def create_invoice(payload: CreateInvoiceIn, session: Session = Depends(get_sess
369
  inv = Invoice(customer_id=payload.customer_id, due_date=payload.due_date, notes=payload.notes)
370
  session.add(inv); session.commit(); session.refresh(inv)
371
 
372
- # 見積→請求コピー
373
  if payload.quote_id:
374
  q = session.get(Quote, payload.quote_id)
375
  if not q:
376
  raise HTTPException(404, "Quote not found to copy")
377
  q_items = session.exec(select(QuoteItem).where(QuoteItem.quote_id == payload.quote_id)).all()
378
  for it in q_items:
379
- session.add(InvoiceItem(
380
  invoice_id=inv.id,
381
  product_id=it.product_id,
382
  description=it.description,
383
  quantity=it.quantity,
384
  unit_price=it.unit_price,
385
  tax_rate=it.tax_rate,
386
- ))
 
387
  session.commit()
388
 
389
  session.refresh(inv)
@@ -514,10 +548,10 @@ def wizard_create_invoice(payload: WizardInvoiceIn, session: Session = Depends(g
514
  except Exception as e:
515
  raise HTTPException(400, f"Wizard failed: {e}")
516
 
517
- # ---- ルート(UI を /app に任せつつ、/ は API の案内) ----
518
- @app.get("/")
519
- def root():
520
- return {"ok": True, "app": "mini-invoice-saas", "docs": "/docs", "ui": "/app/"}
521
 
522
  # -------- ChatGPT router(最後に組み込む) --------
523
  from openai_integration import router as ai_router
 
17
  from pydantic import BaseModel, Field, model_validator
18
  from sqlmodel import Field as SQLField, Session, SQLModel, create_engine, select, Relationship
19
  from sqlalchemy import func
20
+ from sqlalchemy.exc import SQLAlchemyError
21
 
22
  # --------------------------
23
  # Auth
 
65
  echo=False,
66
  connect_args={"check_same_thread": False} if DB_URL.startswith("sqlite") else {}
67
  )
68
+ IS_SQLITE = DB_URL.startswith("sqlite")
69
 
70
  def get_session():
71
  with Session(engine) as session:
 
124
  due_date: Optional[date] = None
125
  notes: Optional[str] = None
126
 
127
+ # 入金関連(任意)
128
  paid_at: Optional[datetime] = None
129
  paid_amount: Optional[float] = None
130
  payment_method: Optional[str] = None
 
161
  customer_id: int
162
  due_date: Optional[date] = None
163
  notes: Optional[str] = None
164
+ # 見積→請求のコピー用(任意)
165
  quote_id: Optional[int] = None
166
 
167
  class InvoiceItemIn(BaseModel):
 
216
  return v
217
 
218
  # --------------------------
219
+ # Totals(堅牢化)
220
  # --------------------------
221
  class MoneyBreakdown(BaseModel):
222
  subtotal: float
 
253
  allow_methods=["*"], allow_headers=["*"],
254
  )
255
 
256
+ # Swagger の AuthorizeX-API-Key)を出す
257
  def custom_openapi():
258
  if app.openapi_schema:
259
  return app.openapi_schema
 
287
  # -------- Customers --------
288
  @app.post("/customers", dependencies=[Depends(require_api_key)])
289
  def create_customer(payload: Customer, session: Session = Depends(get_session)):
290
+ try:
291
+ if not (payload.name and str(payload.name).strip()):
292
+ raise HTTPException(422, "name は必須です")
293
+ session.add(payload)
294
+ session.commit()
295
+ session.refresh(payload)
296
+ return payload
297
+ except HTTPException:
298
+ raise
299
+ except SQLAlchemyError as e:
300
+ session.rollback()
301
+ raise HTTPException(400, f"DBエラー: {e.__class__.__name__}")
302
+ except Exception as e:
303
+ raise HTTPException(400, f"不正なリクエスト: {e}")
304
 
305
  @app.get("/customers", dependencies=[Depends(require_api_key)])
306
  def list_customers(
 
313
  count_stmt = select(func.count(Customer.id))
314
 
315
  if q:
316
+ pattern = f"%{q}%"
317
+ if IS_SQLITE:
318
+ cond = (
319
+ Customer.name.like(pattern) |
320
+ Customer.email.like(pattern) |
321
+ Customer.phone.like(pattern)
322
+ )
323
+ else:
324
+ cond = (
325
+ Customer.name.ilike(pattern) |
326
+ Customer.email.ilike(pattern) |
327
+ Customer.phone.ilike(pattern)
328
+ )
329
  base = base.where(cond)
330
  count_stmt = count_stmt.where(cond)
331
 
332
  total = session.exec(count_stmt).scalar() or 0
333
  rows = session.exec(base.offset(offset).limit(limit)).all()
334
  return {"data": rows, "pagination": {"total": total, "limit": limit, "offset": offset}}
335
+
336
  # -------- Products --------
337
  @app.post("/products", dependencies=[Depends(require_api_key)])
338
  def create_product(payload: Product, session: Session = Depends(get_session)):
339
+ try:
340
+ if not (payload.name and str(payload.name).strip()):
341
+ raise HTTPException(422, "name は必須です")
342
+ session.add(payload)
343
+ session.commit()
344
+ session.refresh(payload)
345
+ return payload
346
+ except HTTPException:
347
+ raise
348
+ except SQLAlchemyError as e:
349
+ session.rollback()
350
+ raise HTTPException(400, f"DBエラー: {e.__class__.__name__}")
351
+ except Exception as e:
352
+ raise HTTPException(400, f"不正なリクエスト: {e}")
353
 
354
  @app.get("/products", dependencies=[Depends(require_api_key)])
355
  def list_products(
 
403
  inv = Invoice(customer_id=payload.customer_id, due_date=payload.due_date, notes=payload.notes)
404
  session.add(inv); session.commit(); session.refresh(inv)
405
 
 
406
  if payload.quote_id:
407
  q = session.get(Quote, payload.quote_id)
408
  if not q:
409
  raise HTTPException(404, "Quote not found to copy")
410
  q_items = session.exec(select(QuoteItem).where(QuoteItem.quote_id == payload.quote_id)).all()
411
  for it in q_items:
412
+ new_item = InvoiceItem(
413
  invoice_id=inv.id,
414
  product_id=it.product_id,
415
  description=it.description,
416
  quantity=it.quantity,
417
  unit_price=it.unit_price,
418
  tax_rate=it.tax_rate,
419
+ )
420
+ session.add(new_item)
421
  session.commit()
422
 
423
  session.refresh(inv)
 
548
  except Exception as e:
549
  raise HTTPException(400, f"Wizard failed: {e}")
550
 
551
+ # ---- ルート(UI ----
552
+ @app.get("/", response_class=FileResponse)
553
+ def root_ui():
554
+ return FileResponse("static/app.html")
555
 
556
  # -------- ChatGPT router(最後に組み込む) --------
557
  from openai_integration import router as ai_router