c086f64363
- Add 7 core API endpoints: users, transactions, partners, products, inventory, payments, credit - Implement role-based authentication (admin/write/read-only access) - Add comprehensive database models with proper relationships - Include full test coverage for all endpoints and business logic - Set up Alembic migrations and Docker configuration - Configure FastAPI app with CORS and database integration
270 lines
9.5 KiB
Python
270 lines
9.5 KiB
Python
"""
|
|
Models module.
|
|
|
|
This module contains Pydantic and SQLModel classes for database table mapping,
|
|
API request/response validation, and serialization.
|
|
|
|
The models include:
|
|
- User
|
|
- Partner
|
|
- Product
|
|
- Transaction and its details
|
|
- Payment
|
|
- Credit account
|
|
- Inventory
|
|
"""
|
|
|
|
from sqlmodel import SQLModel, Field
|
|
from datetime import datetime, date
|
|
from sqlalchemy import Column, String, CheckConstraint, DateTime, func, Enum as SQLEnum
|
|
from typing import Optional
|
|
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.
|
|
"""
|
|
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
username: str = Field(nullable=False,unique=True, max_length=100)
|
|
role: UserRole = Field(nullable=False, max_length= 10, default=UserRole.READ_ONLY)
|
|
password_hash: str = Field(nullable=False)
|
|
|
|
|
|
class Partner(SQLModel, table=True):
|
|
"""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, max_length=10, default=PartnerType.CLIENT)
|
|
phone_number: str = Field(max_length=10, nullable=True)
|
|
|
|
|
|
class Product(SQLModel, table=True):
|
|
"""Products table mapping, API request/response validation, and serialization.
|
|
|
|
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.
|
|
"""
|
|
|
|
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),
|
|
server_default=func.now(),
|
|
server_onupdate=func.now())
|
|
)
|
|
|
|
|
|
class Transaction(SQLModel, table=True):
|
|
"""Transaction table mapping, API request/response validation, and serialization.
|
|
|
|
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,
|
|
default=TransactionType.SALE
|
|
)
|
|
)
|
|
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())
|
|
)
|
|
updated_on: datetime = Field(
|
|
default=None,
|
|
sa_column=Column(
|
|
DateTime(timezone=True),
|
|
onupdate=func.now(),
|
|
server_default=func.now()
|
|
)
|
|
)
|
|
|
|
|
|
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.
|
|
"""
|
|
|
|
__tablename__: str = "transaction_details"
|
|
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
partner_id: int = Field(nullable=False, foreign_key="partner.id")
|
|
product_id: int = Field(nullable=False, foreign_key="product.id")
|
|
qty: int = Field(nullable=False)
|
|
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: str = Field(
|
|
sa_column=Column(
|
|
String(10),
|
|
CheckConstraint("payment_method IN ('momo', 'bank', 'cash')"),
|
|
nullable=False,
|
|
default="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)
|