Spaces:
Sleeping
Sleeping
Commit ·
6153aab
1
Parent(s): 2dd49f5
feat: add invoice creation logic with SQLAlchemy
Browse files- api/invoices.py +55 -0
- core/database.py +21 -0
- main.py +17 -23
- models/invoice.py +29 -0
api/invoices.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, Depends, HTTPException
|
| 2 |
+
from sqlalchemy.orm import Session
|
| 3 |
+
from core.database import get_db
|
| 4 |
+
from models.invoice import Invoice, InvoiceItem
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
from typing import List
|
| 7 |
+
|
| 8 |
+
router = APIRouter(prefix="/invoices", tags=["Invoices"])
|
| 9 |
+
|
| 10 |
+
# Pydantic Schemas for validation
|
| 11 |
+
class ItemCreate(BaseModel):
|
| 12 |
+
description: str
|
| 13 |
+
quantity: int
|
| 14 |
+
price_per_unit: float
|
| 15 |
+
|
| 16 |
+
class InvoiceCreate(BaseModel):
|
| 17 |
+
invoice_no: str
|
| 18 |
+
doctor_name: str
|
| 19 |
+
clinic_name: str
|
| 20 |
+
patient_name: str
|
| 21 |
+
shade: str
|
| 22 |
+
received_amount: float
|
| 23 |
+
items: List[ItemCreate]
|
| 24 |
+
|
| 25 |
+
@router.post("/")
|
| 26 |
+
def create_invoice(data: InvoiceCreate, db: Session = Depends(get_db)):
|
| 27 |
+
# Calculate totals
|
| 28 |
+
total = sum(item.quantity * item.price_per_unit for item in data.items)
|
| 29 |
+
|
| 30 |
+
new_invoice = Invoice(
|
| 31 |
+
invoice_no=data.invoice_no,
|
| 32 |
+
doctor_name=data.doctor_name,
|
| 33 |
+
clinic_name=data.clinic_name,
|
| 34 |
+
patient_name=data.patient_name,
|
| 35 |
+
shade=data.shade,
|
| 36 |
+
total_amount=total,
|
| 37 |
+
received_amount=data.received_amount,
|
| 38 |
+
remaining_balance=total - data.received_amount
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
db.add(new_invoice)
|
| 42 |
+
db.flush() # Gets the ID without committing yet
|
| 43 |
+
|
| 44 |
+
for item in data.items:
|
| 45 |
+
db_item = InvoiceItem(
|
| 46 |
+
invoice_id=new_invoice.id,
|
| 47 |
+
description=item.description,
|
| 48 |
+
quantity=item.quantity,
|
| 49 |
+
price_per_unit=item.price_per_unit,
|
| 50 |
+
total_price=item.quantity * item.price_per_unit
|
| 51 |
+
)
|
| 52 |
+
db.add(db_item)
|
| 53 |
+
|
| 54 |
+
db.commit()
|
| 55 |
+
return {"status": "success", "invoice_id": new_invoice.id}
|
core/database.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from sqlalchemy import create_engine
|
| 3 |
+
from sqlalchemy.ext.declarative import declarative_base
|
| 4 |
+
from sqlalchemy.orm import sessionmaker
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
DATABASE_URL = os.getenv("DATABASE_URL")
|
| 10 |
+
|
| 11 |
+
engine = create_engine(DATABASE_URL)
|
| 12 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 13 |
+
Base = declarative_base()
|
| 14 |
+
|
| 15 |
+
# Dependency to get DB session
|
| 16 |
+
def get_db():
|
| 17 |
+
db = SessionLocal()
|
| 18 |
+
try:
|
| 19 |
+
yield db
|
| 20 |
+
finally:
|
| 21 |
+
db.close()
|
main.py
CHANGED
|
@@ -1,29 +1,23 @@
|
|
| 1 |
-
import os
|
| 2 |
from fastapi import FastAPI
|
| 3 |
-
from
|
| 4 |
-
from
|
| 5 |
-
from
|
| 6 |
-
from fastapi.responses import RedirectResponse
|
| 7 |
|
| 8 |
-
#
|
| 9 |
-
|
| 10 |
|
| 11 |
-
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
-
|
|
|
|
| 18 |
|
| 19 |
-
@app.get("/"
|
| 20 |
-
def
|
| 21 |
-
|
| 22 |
-
return RedirectResponse(url="/docs")
|
| 23 |
-
|
| 24 |
-
@app.get("/health")
|
| 25 |
-
def health_check():
|
| 26 |
-
# This checks if the URL was loaded correctly from the secrets
|
| 27 |
-
if DATABASE_URL:
|
| 28 |
-
return {"status": "Database URL is loaded"}
|
| 29 |
-
return {"status": "Error: DATABASE_URL not found"}
|
|
|
|
|
|
|
| 1 |
from fastapi import FastAPI
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from core.database import engine, Base
|
| 4 |
+
from api.invoices import router as invoice_router
|
|
|
|
| 5 |
|
| 6 |
+
# Create tables in Neon
|
| 7 |
+
Base.metadata.create_all(bind=engine)
|
| 8 |
|
| 9 |
+
app = FastAPI(title="SmiloCAD Invoice API")
|
| 10 |
|
| 11 |
+
app.add_middleware(
|
| 12 |
+
CORSMiddleware,
|
| 13 |
+
allow_origins=["*"],
|
| 14 |
+
allow_methods=["*"],
|
| 15 |
+
allow_headers=["*"],
|
| 16 |
+
)
|
| 17 |
|
| 18 |
+
# Include the routes from the api folder
|
| 19 |
+
app.include_router(invoice_router)
|
| 20 |
|
| 21 |
+
@app.get("/")
|
| 22 |
+
def home():
|
| 23 |
+
return {"message": "Welcome to SmiloCAD API"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
models/invoice.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey
|
| 2 |
+
from sqlalchemy.orm import relationship
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
from core.database import Base # We will create this next
|
| 5 |
+
|
| 6 |
+
class Invoice(Base):
|
| 7 |
+
__tablename__ = "invoices"
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 9 |
+
invoice_no = Column(String, unique=True, index=True)
|
| 10 |
+
doctor_name = Column(String)
|
| 11 |
+
clinic_name = Column(String)
|
| 12 |
+
patient_name = Column(String)
|
| 13 |
+
shade = Column(String)
|
| 14 |
+
total_amount = Column(Float)
|
| 15 |
+
received_amount = Column(Float)
|
| 16 |
+
remaining_balance = Column(Float)
|
| 17 |
+
|
| 18 |
+
items = relationship("InvoiceItem", back_populates="invoice", cascade="all, delete-orphan")
|
| 19 |
+
|
| 20 |
+
class InvoiceItem(Base):
|
| 21 |
+
__tablename__ = "invoice_items"
|
| 22 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 23 |
+
invoice_id = Column(Integer, ForeignKey("invoices.id"))
|
| 24 |
+
description = Column(String)
|
| 25 |
+
quantity = Column(Integer)
|
| 26 |
+
price_per_unit = Column(Float)
|
| 27 |
+
total_price = Column(Float)
|
| 28 |
+
|
| 29 |
+
invoice = relationship("Invoice", back_populates="items")
|