Updates:
- Restructure table models - Remove React/Next.js frontend (in favor of HTMX)
This commit is contained in:
@@ -1,87 +0,0 @@
|
||||
"""
|
||||
The Clients endpoint
|
||||
"""
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from sqlmodel import select
|
||||
from app.api.deps import SessionDep, exists
|
||||
from app.schemas.models import Client
|
||||
from app.schemas.schemas import ClientCreate, ClientUpdate
|
||||
from pydantic import ValidationError
|
||||
from typing import Sequence, List, Optional
|
||||
|
||||
router = APIRouter(prefix="/clients", tags=["clients"])
|
||||
|
||||
|
||||
@router.get("/", response_model=List[Client])
|
||||
def fetch_clients(session: SessionDep) -> Sequence[Client]:
|
||||
"""Fetch client list
|
||||
"""
|
||||
clients = session.exec(select(Client)).all()
|
||||
return clients
|
||||
|
||||
|
||||
@router.post("/", response_model=ClientCreate)
|
||||
def create_client(client_data: ClientCreate, session: SessionDep) -> Client:
|
||||
"""Create a client
|
||||
"""
|
||||
existing = exists(session, Client, tin_number=client_data.tin_number)
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="Client with this tin number already exists")
|
||||
|
||||
try:
|
||||
client = Client.model_validate(client_data)
|
||||
except ValidationError as e:
|
||||
raise HTTPException(status_code=400, detail=e.errors())
|
||||
|
||||
session.add(client)
|
||||
session.commit()
|
||||
session.refresh(client)
|
||||
return client
|
||||
|
||||
|
||||
@router.get("/{client_id}", response_model=Client)
|
||||
def get_client(client_id: int, session: SessionDep) -> Optional[Client]:
|
||||
"""Returns a client by its ID
|
||||
"""
|
||||
stmt = select(Client).where(Client.id == client_id)
|
||||
result: Optional[Client] = session.exec(stmt).first()
|
||||
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Client not found")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.patch("/{client_id}", response_model=ClientCreate)
|
||||
def update_client(
|
||||
client_id: int,
|
||||
client_data: ClientUpdate,
|
||||
session: SessionDep) -> Optional[Client]:
|
||||
"""Updates a client using its ID
|
||||
"""
|
||||
client = session.get(Client, client_id)
|
||||
if not client:
|
||||
raise HTTPException(status_code=404, detail="Client not found")
|
||||
|
||||
update_fields = client_data.model_dump(exclude_unset=True)
|
||||
|
||||
for key, value in update_fields.items():
|
||||
setattr(client, key, value)
|
||||
|
||||
session.add(client)
|
||||
session.commit()
|
||||
session.refresh(client)
|
||||
return client
|
||||
|
||||
|
||||
@router.delete("/{client_id}", status_code=204)
|
||||
def delete_client(client_id: int, session: SessionDep):
|
||||
"""Deletes a client
|
||||
"""
|
||||
client = session.get(Client, client_id)
|
||||
|
||||
if not client:
|
||||
raise HTTPException(status_code=404, detail="Client not found")
|
||||
|
||||
session.delete(client)
|
||||
session.commit()
|
||||
@@ -1,99 +0,0 @@
|
||||
"""
|
||||
The products endpoint
|
||||
"""
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from sqlmodel import select
|
||||
from app.api.deps import SessionDep, exists
|
||||
from app.schemas.models import Product
|
||||
from app.schemas.schemas import ProductCreate, ProductUpdate
|
||||
from pydantic import ValidationError
|
||||
from typing import Sequence, List, Optional
|
||||
|
||||
|
||||
router = APIRouter(prefix="/products", tags=["products"])
|
||||
|
||||
|
||||
@router.get("/", response_model=List[Product])
|
||||
def fetch_products(session: SessionDep) -> Sequence[Product]:
|
||||
"""Fetch product list
|
||||
"""
|
||||
products = session.exec(select(Product)).all()
|
||||
return products
|
||||
|
||||
|
||||
@router.post("/", response_model=ProductCreate)
|
||||
def create_product(product_data: ProductCreate, session: SessionDep) -> Product:
|
||||
"""Create a product
|
||||
"""
|
||||
name_exists = exists(session, Product, product_name=product_data.product_name)
|
||||
if name_exists:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Product with this product_name exists"
|
||||
)
|
||||
|
||||
existing = exists(session, Product, product_code=product_data.product_code)
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Product with this product_code exists"
|
||||
)
|
||||
|
||||
try:
|
||||
product = Product.model_validate(product_data)
|
||||
except ValidationError as e:
|
||||
raise HTTPException(status_code=400, detail=e.errors())
|
||||
|
||||
session.add(product)
|
||||
session.commit()
|
||||
session.refresh(product)
|
||||
return product
|
||||
|
||||
|
||||
@router.get("/{product_id}", response_model=Product)
|
||||
def get_product(product_id: int, session: SessionDep) -> Optional[Product]:
|
||||
"""Returns a product by its id
|
||||
"""
|
||||
stmt = select(Product).where(Product.id == product_id)
|
||||
result: Optional[Product] = session.exec(stmt).first()
|
||||
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Product not found")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.patch("/{product_id}",response_model=ProductUpdate)
|
||||
def update_product(
|
||||
product_id: int,
|
||||
product_data: ProductUpdate,
|
||||
session: SessionDep
|
||||
) -> Optional[Product]:
|
||||
"""Updates a product
|
||||
"""
|
||||
product = session.get(Product, product_id)
|
||||
if not product:
|
||||
raise HTTPException(status_code=404, detail="Product not found")
|
||||
|
||||
updated_fields = product_data.model_dump(exclude_unset=True)
|
||||
|
||||
for key, value in updated_fields.items():
|
||||
setattr(product, key, value)
|
||||
|
||||
session.add(product)
|
||||
session.commit()
|
||||
session.refresh(product)
|
||||
return product
|
||||
|
||||
|
||||
@router.delete("/{product_id}", status_code=204)
|
||||
def delete_product(product_id: int, session: SessionDep):
|
||||
"""Deletes a product
|
||||
"""
|
||||
product = session.get(Product, product_id)
|
||||
|
||||
if not product:
|
||||
raise HTTPException(status_code=404, detail="Product not found")
|
||||
|
||||
session.delete(product)
|
||||
session.commit()
|
||||
@@ -1,88 +0,0 @@
|
||||
"""The suppliers endpoint
|
||||
"""
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from sqlmodel import select
|
||||
from app.api.deps import SessionDep, exists
|
||||
from app.schemas.models import Supplier
|
||||
from app.schemas.schemas import SupplierCreate, SupplierUpdate
|
||||
from pydantic import ValidationError
|
||||
from typing import Sequence, List, Optional
|
||||
|
||||
|
||||
router = APIRouter(prefix="/suppliers", tags=["suppliers"])
|
||||
|
||||
|
||||
@router.get("/", response_model=List[Supplier])
|
||||
def fetch_suppliers(session: SessionDep) -> Sequence[Supplier]:
|
||||
"""Fetch supplier list
|
||||
"""
|
||||
suppliers = session.exec(select(Supplier)).all()
|
||||
return suppliers
|
||||
|
||||
|
||||
@router.post("/", response_model=SupplierCreate)
|
||||
def create_supplier(supplier_data: SupplierCreate, session: SessionDep) -> Supplier:
|
||||
"""Create a supplier
|
||||
"""
|
||||
existing = exists(session, Supplier, tin_number=supplier_data.tin_number)
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="Supplier with this tin_number already exists")
|
||||
|
||||
try:
|
||||
supplier = Supplier.model_validate(supplier_data)
|
||||
except ValidationError as e:
|
||||
raise HTTPException(status_code=400, detail=e.errors())
|
||||
|
||||
session.add(supplier)
|
||||
session.commit()
|
||||
session.refresh(supplier)
|
||||
return supplier
|
||||
|
||||
|
||||
@router.get("/{supplier_id}", response_model=Supplier)
|
||||
def get_supplier(supplier_id: int, session: SessionDep) -> Optional[Supplier]:
|
||||
"""Returns a supplier by its ID
|
||||
"""
|
||||
stmt = select(Supplier).where(Supplier.id == supplier_id)
|
||||
result: Optional[Supplier] = session.exec(stmt).first()
|
||||
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Supplier not found")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.patch("/{supplier_id}", response_model=SupplierUpdate)
|
||||
def update_supplier(
|
||||
supplier_id: int,
|
||||
supplier_data: SupplierUpdate,
|
||||
session: SessionDep
|
||||
) -> Optional[Supplier]:
|
||||
"""Updates a supplier's details
|
||||
"""
|
||||
supplier = session.get(Supplier, supplier_id)
|
||||
if not supplier:
|
||||
raise HTTPException(status_code=404, detail="Supplier not found")
|
||||
|
||||
updated_fields = supplier_data.model_dump(exclude_unset=True)
|
||||
|
||||
for key, value in updated_fields.items():
|
||||
setattr(supplier, key, value)
|
||||
|
||||
session.add(supplier)
|
||||
session.commit()
|
||||
session.refresh(supplier)
|
||||
return supplier
|
||||
|
||||
|
||||
@router.delete("/{supplier_id}", status_code=204)
|
||||
def delete_supplier(supplier_id: int, session: SessionDep):
|
||||
"""Deletes a supplier
|
||||
"""
|
||||
supplier = session.get(Supplier, supplier_id)
|
||||
|
||||
if not supplier:
|
||||
raise HTTPException(status_code=404, detail="Supplier not found")
|
||||
|
||||
session.delete(supplier)
|
||||
session.commit()
|
||||
+3
-3
@@ -7,9 +7,9 @@ NOTE:
|
||||
from app.core.config import settings
|
||||
from typing import Union
|
||||
from fastapi import FastAPI
|
||||
from app.api.clients.endpoints import router as clients_router
|
||||
from app.api.suppliers.endpoints import router as supplier_router
|
||||
from app.api.products.endpoints import router as product_router
|
||||
from backend.app.api.endpoints.clients import router as clients_router
|
||||
from backend.app.api.endpoints.suppliers import router as supplier_router
|
||||
from backend.app.api.endpoints.products import router as product_router
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
|
||||
@@ -1,26 +1,62 @@
|
||||
from sqlmodel import SQLModel
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class UserRole(str, Enum):
|
||||
"""User roles for system access.
|
||||
|
||||
Attributes:
|
||||
ADMIN (str): Administrator with full access.
|
||||
WRITE (str): User with write permissions.
|
||||
READ_ONLY (str): User with read-only permissions.
|
||||
"""
|
||||
ADMIN = "admin"
|
||||
WRITE = "write"
|
||||
READ_ONLY = "read_only"
|
||||
|
||||
|
||||
class TransactionType(str, Enum):
|
||||
"""Types of financial transactions.
|
||||
|
||||
Attributes:
|
||||
SALE (str): Sale transaction.
|
||||
PURCHASE (str): Purchase transaction.
|
||||
CREDIT (str): Credit transaction.
|
||||
"""
|
||||
SALE = "sell"
|
||||
PURCHASE = "buy"
|
||||
CREDIT = "credit"
|
||||
|
||||
|
||||
class TransactionStatus(str, Enum):
|
||||
"""Possible statuses of a transaction.
|
||||
|
||||
Attributes:
|
||||
UNPAID (str): Transaction not paid.
|
||||
PARTIALLY_PAID (str): Transaction partially paid.
|
||||
PAID (str): Transaction fully paid.
|
||||
CANCELLED (str): Transaction cancelled.
|
||||
"""
|
||||
UNPAID = "unpaid"
|
||||
PARTIALLY_PAID = "partially_paid"
|
||||
PAID = "paid"
|
||||
CANCELLED = 'cancelled'
|
||||
|
||||
|
||||
class PartnerType(str, Enum):
|
||||
"""Types of business partners.
|
||||
|
||||
Attributes:
|
||||
CLIENT (str): Client partner.
|
||||
SUPPLIER (str): Supplier partner.
|
||||
"""
|
||||
CLIENT = "client"
|
||||
SUPPLIER = "supplier"
|
||||
|
||||
class PaymentMethod(str, Enum):
|
||||
"""Payment methods available.
|
||||
|
||||
Attributes:
|
||||
MOMO (str): Mobile money.
|
||||
BANK (str): Bank transfer.
|
||||
CASH (str): Cash payment.
|
||||
"""
|
||||
MOMO = "momo"
|
||||
BANK = "bank"
|
||||
CASH = "cash"
|
||||
|
||||
+200
-48
@@ -1,56 +1,81 @@
|
||||
"""
|
||||
This module contains Pydantic/Database Models that map database tables validate
|
||||
and serialize api responses.
|
||||
Models module.
|
||||
|
||||
If the logic is identical -> SQLModel is used to do both.
|
||||
Otherwise pydantic - for api responses
|
||||
And SQLAlchemy is used for db data validation.
|
||||
This module contains Pydantic and SQLModel classes for database table mapping,
|
||||
API request/response validation, and serialization.
|
||||
|
||||
TODO:
|
||||
Mapping & validation for:
|
||||
- Clients, Suppliers, Products, payments
|
||||
|
||||
Done:
|
||||
* Table mappings
|
||||
The models include:
|
||||
- User
|
||||
- Partner
|
||||
- Product
|
||||
- Transaction and its details
|
||||
- Payment
|
||||
- Credit account
|
||||
- Inventory
|
||||
"""
|
||||
|
||||
from sqlmodel import SQLModel, Field, UniqueConstraint
|
||||
from datetime import datetime
|
||||
from datetime import datetime, date
|
||||
from sqlalchemy import Column, DateTime, func, Enum as SQLEnum
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from base import UserRole, PartnerType, TransactionType, TransactionStatus
|
||||
from base import UserRole, PartnerType, TransactionType, TransactionStatus, PaymentMethod
|
||||
|
||||
|
||||
class User(SQLModel, table=True):
|
||||
"""User table mapping, API request/response validation, and serialization.
|
||||
|
||||
Attributes:
|
||||
id (int, optional): Primary key.
|
||||
username (str): Unique user name (max 100 chars).
|
||||
role (UserRole): User role (default READ_ONLY).
|
||||
password_hash (str): Hashed password.
|
||||
"""
|
||||
User table mapping, api response validation and serialisation
|
||||
"""
|
||||
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
username: str = Field(nullable=False,unique=True, max_length=100)
|
||||
role: UserRole = Field(nullable=True, default=PartnerType.CLIENT)
|
||||
role: UserRole = Field(nullable=False, max_length= 10, default=UserRole.READ_ONLY)
|
||||
password_hash: str = Field(nullable=False)
|
||||
|
||||
|
||||
class Partner(SQLModel, table=True):
|
||||
"""Clients table mapping, api response validation and serialisation"""
|
||||
"""Partner (client or supplier) mapping, API request/response validation, and serialization.
|
||||
|
||||
Attributes:
|
||||
id (int, optional): Primary key.
|
||||
tin_number (int): Tax identification number.
|
||||
names (str): Full name.
|
||||
type (PartnerType): Partner type (CLIENT or SUPPLIER).
|
||||
phone_number (str, optional): Phone number.
|
||||
"""
|
||||
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
tin_number: int = Field(nullable=False, unique=True)
|
||||
names: str = Field(max_length=100, nullable=False)
|
||||
type: PartnerType = Field(nullable=False, default=PartnerType.CLIENT)
|
||||
type: PartnerType = Field(nullable=False, max_length=10, default=PartnerType.CLIENT)
|
||||
phone_number: str = Field(max_length=10, nullable=True)
|
||||
|
||||
|
||||
class Product(SQLModel, table=True):
|
||||
"""Products table mapping, api response validation and serialisation
|
||||
"""Products table mapping, API request/response validation, and serialization.
|
||||
|
||||
NOTE: Every time a product's purchase price changes, it should be updated
|
||||
here as well
|
||||
Every time a product's purchase price changes, update here.
|
||||
selling_price is referential: defaults but can be overridden.
|
||||
|
||||
Attributes:
|
||||
id (int, optional): Primary key.
|
||||
product_code (str): Unique product code (max 10 chars).
|
||||
product_name (str): Unique product name (max 20 chars).
|
||||
purchase_price (int): Last purchase price.
|
||||
selling_price (int): Reference selling price.
|
||||
date_modified (datetime): Last modified timestamp.
|
||||
"""
|
||||
__table_args__ = (UniqueConstraint("product_code"))
|
||||
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
product_code: str = Field(max_length=10, unique=True, nullable=False)
|
||||
product_name: str = Field(max_length=20, nullable=False, unique=True)
|
||||
purchase_price: int = Field(nullable=False)
|
||||
selling_price: int = Field(nullable=False)
|
||||
date_modified: datetime = Field(
|
||||
default=None,
|
||||
sa_column=Column(DateTime(timezone=True),
|
||||
@@ -60,18 +85,44 @@ class Product(SQLModel, table=True):
|
||||
|
||||
|
||||
class Transaction(SQLModel, table=True):
|
||||
"""
|
||||
Transaction table mapping, api response validation and serialisation
|
||||
"""Transaction table mapping, API request/response validation, and serialization.
|
||||
|
||||
Include both business events to/from suppliers and to/from clients
|
||||
Includes both business events to/from suppliers and clients.
|
||||
|
||||
Attributes:
|
||||
id (int, optional): Primary key.
|
||||
partner_id (int): Related partner ID.
|
||||
transcation_type (TransactionType): Type of transaction.
|
||||
transaction_status (TransactionStatus): Current status.
|
||||
total_amount (int): Total transaction amount.
|
||||
created_by (int): User ID who created.
|
||||
updated_by (int): User ID who last updated.
|
||||
created_on (datetime): Creation timestamp.
|
||||
updated_on (datetime): Last update timestamp.
|
||||
"""
|
||||
|
||||
__tablename__: str = "transactions"
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
partner_id: Optional[int] = Field(nullable=False, foreign_key="partner.id")
|
||||
transcation_type: TransactionType = Field(
|
||||
sa_column=Column(SQLEnum(TransactionType), nullable=False)
|
||||
sa_column=Column(
|
||||
SQLEnum(TransactionType),
|
||||
nullable=False,
|
||||
default=TransactionType.SALE
|
||||
)
|
||||
)
|
||||
transaction_status: TransactionStatus
|
||||
transaction_status: TransactionStatus = Field(
|
||||
sa_column=Column(
|
||||
SQLEnum(TransactionStatus),
|
||||
nullable=False,
|
||||
default=TransactionStatus.UNPAID
|
||||
)
|
||||
)
|
||||
total_amount: int = Field(nullable=False, default=0)
|
||||
|
||||
created_by: int = Field(nullable=False, foreign_key="user.id")
|
||||
updated_by: int = Field(nullable=False, foreign_key="user.id")
|
||||
|
||||
created_on: datetime = Field(
|
||||
default=None,
|
||||
sa_column=Column(DateTime(timezone=True), server_default=func.now())
|
||||
@@ -86,32 +137,133 @@ class Transaction(SQLModel, table=True):
|
||||
)
|
||||
|
||||
|
||||
class Transaction_items(SQLModel, table=True):
|
||||
"""
|
||||
Transaction table mapping, api response validation and serialisation
|
||||
Includes transactions details from transactions
|
||||
class Transaction_details(SQLModel, table=True):
|
||||
"""Transaction details mapping, API request/response validation, and serialization.
|
||||
|
||||
Attributes:
|
||||
id (int, optional): Primary key.
|
||||
partner_id (int): Related partner ID.
|
||||
product_id (str): Product ID.
|
||||
qty (int): Quantity.
|
||||
selling_price (int): Unit price.
|
||||
total_value (int): qty * selling_price.
|
||||
created_by (int): User ID who created.
|
||||
updated_by (int): User ID who last updated.
|
||||
created_at (datetime): Creation timestamp.
|
||||
updated_at (datetime): Last update timestamp.
|
||||
"""
|
||||
|
||||
|
||||
class Payment(SQLModel, table=True):
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
class Credit_accounts(SQLModel, table=True):
|
||||
"""Credit table mapping, api response validation and serialisation
|
||||
|
||||
Include both credit from suppliers and to clients
|
||||
"""
|
||||
__tablename__: str = "credit_accounts"
|
||||
__tablename__: str = "transaction_details"
|
||||
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
product_code: str = Field(nullable=False, foreign_key="product.product_code")
|
||||
client_id: Optional[int] = Field(nullable=True, foreign_key="client.id")
|
||||
supplier_id: Optional[int] = Field(nullable=True, foreign_key="supplier.id")
|
||||
partner_id: int = Field(nullable=False, foreign_key="partner.id")
|
||||
product_id: str = Field(nullable=False, foreign_key="product.id")
|
||||
qty: int = Field(nullable=False)
|
||||
amount: int = Field(nullable=False)
|
||||
date: datetime = Field(
|
||||
selling_price: int = Field(nullable=False)
|
||||
|
||||
# qty * selling_price
|
||||
total_value: int = Field(nullable=False, default=0) # per items
|
||||
|
||||
created_by: int = Field(nullable=False, foreign_key="user.id")
|
||||
updated_by: int = Field(nullable=False, foreign_key="user.id")
|
||||
created_at: datetime = Field(
|
||||
default=None,
|
||||
sa_column=Column(DateTime(timezone=True), server_default=func.now())
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default=None,
|
||||
sa_column=Column(DateTime(timezone=True), server_default=func.now())
|
||||
)
|
||||
|
||||
|
||||
class Payment(SQLModel, table=True):
|
||||
"""Payment table mapping, API request/response validation, and serialization.
|
||||
|
||||
Attributes:
|
||||
id (int, optional): Primary key.
|
||||
transaction_id (int): Related transaction ID.
|
||||
payment_method (PaymentMethod): Method of payment.
|
||||
paid_amount (int): Amount paid.
|
||||
payment_date (date): Date of payment.
|
||||
created_by (int): User ID who created.
|
||||
updated_by (int): User ID who last updated.
|
||||
created_at (datetime): Creation timestamp.
|
||||
updated_at (datetime): Last update timestamp.
|
||||
"""
|
||||
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
transaction_id: int = Field(nullable=False, foreign_key="transactions.id")
|
||||
payment_method: PaymentMethod = Field(
|
||||
sa_column=Column(
|
||||
SQLEnum(PaymentMethod),
|
||||
nullable=False,
|
||||
default=PaymentMethod.CASH
|
||||
)
|
||||
)
|
||||
paid_amount: int = Field(nullable=False)
|
||||
payment_date: date = Field(nullable=False)
|
||||
created_by: int = Field(nullable=False, foreign_key="user.id")
|
||||
updated_by: int = Field(nullable=False, foreign_key="user.id")
|
||||
created_at: datetime = Field(
|
||||
default=None,
|
||||
sa_column=Column(DateTime(timezone=True), server_default=func.now())
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default=None,
|
||||
sa_column=Column(DateTime(timezone=True), server_default=func.now())
|
||||
)
|
||||
|
||||
|
||||
class Credit(SQLModel, table=True):
|
||||
"""Credit account mapping, API request/response validation, and serialization.
|
||||
|
||||
Includes both supplier and client credit events.
|
||||
|
||||
Attributes:
|
||||
id (int, optional): Primary key.
|
||||
partner_id (int): Related partner ID.
|
||||
transaction_id (int): Related transaction ID.
|
||||
credit_amount (int): Credit amount.
|
||||
credit_limit (int): Credit limit.
|
||||
balance (int): Current balance.
|
||||
created_by (int): User ID who created.
|
||||
updated_by (int): User ID who last updated.
|
||||
created_at (datetime): Creation timestamp.
|
||||
updated_at (datetime): Last update timestamp.
|
||||
"""
|
||||
|
||||
__tablename__: str = "credit_accounts"
|
||||
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
partner_id: int = Field(nullable=False, unique=True, foreign_key="partner.id")
|
||||
transaction_id: int = Field(nullable=False, foreign_key="transactions.id")
|
||||
|
||||
credit_amount: int = Field(nullable=False)
|
||||
credit_limit: int = Field(nullable=False)
|
||||
balance: int = Field(nullable=False)
|
||||
|
||||
created_by: int = Field(nullable=False, foreign_key="user.id")
|
||||
updated_by: int = Field(nullable=False, foreign_key="user.id")
|
||||
created_at: datetime = Field(
|
||||
default=None,
|
||||
sa_column=Column(DateTime(timezone=True), server_default=func.now())
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default=None,
|
||||
sa_column=Column(DateTime(timezone=True), server_default=func.now())
|
||||
)
|
||||
|
||||
|
||||
class Inventory(SQLModel, table=True):
|
||||
"""Inventory mapping, API request/response validation, and serialization.
|
||||
|
||||
Attributes:
|
||||
id (int, optional): Primary key.
|
||||
product_id (int): Related product ID.
|
||||
total_qty (int): Total quantity in inventory.
|
||||
"""
|
||||
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
|
||||
product_id: int = Field(nullable=False, unique=True, foreign_key="product.id")
|
||||
total_qty: int = Field(nullable=False, default=0)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
"""
|
||||
Custom validation schema
|
||||
"""
|
||||
from sqlmodel import SQLModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import pytest
|
||||
from sqlmodel import SQLModel, Session, create_engine
|
||||
|
||||
from app.core.config import settings
|
||||
from app.api.deps import SessionDep
|
||||
from app.schemas.schemas import ClientCreate
|
||||
from app.api.clients.endpoints import create_client
|
||||
from fastapi import HTTPException
|
||||
|
||||
|
||||
# Fixture for a temporary in-memory database session
|
||||
@pytest.fixture
|
||||
def session():
|
||||
engine = create_engine(str(settings.database_uri), echo=False)
|
||||
SQLModel.metadata.create_all(engine)
|
||||
with Session(engine) as session:
|
||||
yield session
|
||||
|
||||
|
||||
def test_create_client_success(session: SessionDep):
|
||||
client_data = ClientCreate(tin_number=781410046, names="Lin", phone_number="0781410046")
|
||||
client = create_client(client_data, session)
|
||||
|
||||
assert client.id is not None
|
||||
assert client.names == "Lin"
|
||||
assert client.tin_number == 781410046
|
||||
assert client.phone_number == "0781410046"
|
||||
|
||||
|
||||
def test_create_client_duplicate_name(session):
|
||||
client_data = ClientCreate(tin_number=781410045, names="John Doe", phone_number="0781410045")
|
||||
|
||||
# Try to create another client with the same name
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
create_client(client_data, session)
|
||||
assert exc_info.value.status_code == 400
|
||||
assert "already exists" in exc_info.value.detail
|
||||
Reference in New Issue
Block a user