Corin1998 commited on
Commit
a5eb452
·
verified ·
1 Parent(s): f3a634d

Upload 6 files

Browse files
Files changed (1) hide show
  1. main.py +28 -16
main.py CHANGED
@@ -1,4 +1,3 @@
1
- # main.py
2
  """
3
  Mini Invoice/Estimate (Quote) SaaS — single-file FastAPI app
4
  SQLite + SQLModel + FastAPI
@@ -13,8 +12,8 @@ import pathlib
13
  from fastapi import FastAPI, Depends, HTTPException, Header, Query
14
  from fastapi.middleware.cors import CORSMiddleware
15
  from fastapi.openapi.utils import get_openapi
16
- from fastapi.responses import FileResponse
17
  from fastapi.staticfiles import StaticFiles
 
18
  from pydantic import BaseModel
19
  from sqlmodel import Field as SQLField, Session, SQLModel, create_engine, select, Relationship
20
 
@@ -159,8 +158,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):
166
  description: str
@@ -246,7 +244,20 @@ def on_startup():
246
  SQLModel.metadata.create_all(engine)
247
 
248
  # ---- UI(/app) ----
249
- app.mount("/app", StaticFiles(directory="static", html=True), name="app")
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
  # -------- Customers --------
252
  @app.post("/customers", dependencies=[Depends(require_api_key)])
@@ -264,16 +275,17 @@ def list_customers(
264
  stmt = select(Customer)
265
  if q:
266
  like = f"%{q}%"
 
267
  stmt = stmt.where(
268
- (Customer.name.ilike(like)) |
269
- (Customer.email.ilike(like)) |
270
- (Customer.phone.ilike(like))
271
  )
272
  total = session.exec(stmt).count()
273
  rows = session.exec(stmt.offset(offset).limit(limit)).all()
274
  return {"data": rows, "pagination": {"total": total, "limit": limit, "offset": offset}}
275
 
276
- # -------- Products(必要ならUIから追加) --------
277
  @app.post("/products", dependencies=[Depends(require_api_key)])
278
  def create_product(payload: Product, session: Session = Depends(get_session)):
279
  session.add(payload); session.commit(); session.refresh(payload)
@@ -324,28 +336,27 @@ def add_quote_item(quote_id: int, payload: QuoteItemIn, session: Session = Depen
324
  # -------- Invoices --------
325
  @app.post("/invoices", dependencies=[Depends(require_api_key)])
326
  def create_invoice(payload: CreateInvoiceIn, session: Session = Depends(get_session)):
327
- # 見積→請求のコピーにも対応(quote_id を受け付ける)
328
  if not session.get(Customer, payload.customer_id):
329
  raise HTTPException(400, "Customer not found")
330
 
331
  inv = Invoice(customer_id=payload.customer_id, due_date=payload.due_date, notes=payload.notes)
332
  session.add(inv); session.commit(); session.refresh(inv)
333
 
 
334
  if payload.quote_id:
335
  q = session.get(Quote, payload.quote_id)
336
  if not q:
337
  raise HTTPException(404, "Quote not found to copy")
338
  q_items = session.exec(select(QuoteItem).where(QuoteItem.quote_id == payload.quote_id)).all()
339
  for it in q_items:
340
- new_item = InvoiceItem(
341
  invoice_id=inv.id,
342
  product_id=it.product_id,
343
  description=it.description,
344
  quantity=it.quantity,
345
  unit_price=it.unit_price,
346
  tax_rate=it.tax_rate,
347
- )
348
- session.add(new_item)
349
  session.commit()
350
 
351
  session.refresh(inv)
@@ -427,9 +438,10 @@ def email_invoice(invoice_id: int, payload: EmailIn, session: Session = Depends(
427
  )
428
  return {"ok": ok, "detail": detail}
429
 
430
- @app.get("/")
431
- def root():
432
- return {"ok": True, "app": "mini-invoice-saas", "docs": "/docs"}
 
433
 
434
  # -------- ChatGPT router を最後に組み込む --------
435
  from openai_integration import router as ai_router
 
 
1
  """
2
  Mini Invoice/Estimate (Quote) SaaS — single-file FastAPI app
3
  SQLite + SQLModel + FastAPI
 
12
  from fastapi import FastAPI, Depends, HTTPException, Header, Query
13
  from fastapi.middleware.cors import CORSMiddleware
14
  from fastapi.openapi.utils import get_openapi
 
15
  from fastapi.staticfiles import StaticFiles
16
+ from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
17
  from pydantic import BaseModel
18
  from sqlmodel import Field as SQLField, Session, SQLModel, create_engine, select, Relationship
19
 
 
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):
164
  description: str
 
244
  SQLModel.metadata.create_all(engine)
245
 
246
  # ---- UI(/app) ----
247
+ STATIC_DIR = "static"
248
+ if os.path.isdir(STATIC_DIR):
249
+ app.mount("/app", StaticFiles(directory=STATIC_DIR, html=True), name="app")
250
+ else:
251
+ # フォールバック(staticが無い場合でも起動できるようにする)
252
+ @app.get("/app", response_class=HTMLResponse)
253
+ def app_inline():
254
+ return """
255
+ <!doctype html><meta charset="utf-8">
256
+ <title>Mini Invoice/Estimate SaaS</title>
257
+ <h1>Mini Invoice/Estimate SaaS</h1>
258
+ <p><code>static/app.html</code> が見つかりません。Filesで作成してから再読み込みしてください。</p>
259
+ <p>APIは <a href="/docs">/docs</a> から操作できます。</p>
260
+ """
261
 
262
  # -------- Customers --------
263
  @app.post("/customers", dependencies=[Depends(require_api_key)])
 
275
  stmt = select(Customer)
276
  if q:
277
  like = f"%{q}%"
278
+ # SQLite は ILIKE 非対応なので LIKE で十分(大文字小文字は基本無視コラテラル)
279
  stmt = stmt.where(
280
+ (Customer.name.like(like)) |
281
+ (Customer.email.like(like)) |
282
+ (Customer.phone.like(like))
283
  )
284
  total = session.exec(stmt).count()
285
  rows = session.exec(stmt.offset(offset).limit(limit)).all()
286
  return {"data": rows, "pagination": {"total": total, "limit": limit, "offset": offset}}
287
 
288
+ # -------- Products --------
289
  @app.post("/products", dependencies=[Depends(require_api_key)])
290
  def create_product(payload: Product, session: Session = Depends(get_session)):
291
  session.add(payload); session.commit(); session.refresh(payload)
 
336
  # -------- Invoices --------
337
  @app.post("/invoices", dependencies=[Depends(require_api_key)])
338
  def create_invoice(payload: CreateInvoiceIn, session: Session = Depends(get_session)):
 
339
  if not session.get(Customer, payload.customer_id):
340
  raise HTTPException(400, "Customer not found")
341
 
342
  inv = Invoice(customer_id=payload.customer_id, due_date=payload.due_date, notes=payload.notes)
343
  session.add(inv); session.commit(); session.refresh(inv)
344
 
345
+ # 見積→請求のコピー
346
  if payload.quote_id:
347
  q = session.get(Quote, payload.quote_id)
348
  if not q:
349
  raise HTTPException(404, "Quote not found to copy")
350
  q_items = session.exec(select(QuoteItem).where(QuoteItem.quote_id == payload.quote_id)).all()
351
  for it in q_items:
352
+ session.add(InvoiceItem(
353
  invoice_id=inv.id,
354
  product_id=it.product_id,
355
  description=it.description,
356
  quantity=it.quantity,
357
  unit_price=it.unit_price,
358
  tax_rate=it.tax_rate,
359
+ ))
 
360
  session.commit()
361
 
362
  session.refresh(inv)
 
438
  )
439
  return {"ok": ok, "detail": detail}
440
 
441
+ # ---- ルート:UIへリダイレクト(デモしやすさ優先)----
442
+ @app.get("/", include_in_schema=False)
443
+ def root_redirect():
444
+ return RedirectResponse("/app")
445
 
446
  # -------- ChatGPT router を最後に組み込む --------
447
  from openai_integration import router as ai_router