Files
2026-06-20 15:01:44 +05:30

321 lines
23 KiB
Python

from __future__ import annotations
from datetime import date, datetime, timezone
from decimal import Decimal
from sqlalchemy import Boolean, Date, DateTime, ForeignKey, Integer, Numeric, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.db.common import CommonBase
class BillingSettings(CommonBase):
__tablename__ = "billing_settings"
__table_args__ = (
UniqueConstraint("tenant_id", "branch_id", name="uq_billing_settings_tenant_branch"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int | None] = mapped_column(ForeignKey("branches.id", ondelete="SET NULL"), nullable=True, index=True)
invoice_prefix: Mapped[str] = mapped_column(String(40), nullable=False, default="INV")
next_invoice_no: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
padding: Mapped[int] = mapped_column(Integer, nullable=False, default=4)
default_gst_rate: Mapped[Decimal] = mapped_column(Numeric(5, 2), nullable=False, default=Decimal("18.00"))
default_tax_type: Mapped[str] = mapped_column(String(20), nullable=False, default="CGST_SGST")
legal_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
gstin: Mapped[str | None] = mapped_column(String(20), nullable=True)
pan: Mapped[str | None] = mapped_column(String(10), nullable=True)
state_code: Mapped[str | None] = mapped_column(String(2), nullable=True)
billing_address: Mapped[str | None] = mapped_column(Text, nullable=True)
contact_email: Mapped[str | None] = mapped_column(String(255), nullable=True)
contact_mobile: Mapped[str | None] = mapped_column(String(50), nullable=True)
website_url: Mapped[str | None] = mapped_column(String(255), nullable=True)
invoice_title: Mapped[str | None] = mapped_column(String(80), nullable=True)
invoice_number_format: Mapped[str | None] = mapped_column(String(120), nullable=True)
default_due_days: Mapped[int] = mapped_column(Integer, nullable=False, default=15)
default_sac_code: Mapped[str | None] = mapped_column(String(20), nullable=True)
bank_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
bank_account_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
bank_account_number: Mapped[str | None] = mapped_column(String(50), nullable=True)
bank_ifsc: Mapped[str | None] = mapped_column(String(20), nullable=True)
upi_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
bank_details: Mapped[str | None] = mapped_column(Text, nullable=True)
payumoney_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
payumoney_mode: Mapped[str] = mapped_column(String(20), nullable=False, default="TEST")
payumoney_merchant_key: Mapped[str | None] = mapped_column(String(120), nullable=True)
payumoney_merchant_salt: Mapped[str | None] = mapped_column(String(200), nullable=True)
payumoney_merchant_id: Mapped[str | None] = mapped_column(String(120), nullable=True)
payumoney_product_info: Mapped[str | None] = mapped_column(String(200), nullable=True)
cashfree_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
cashfree_mode: Mapped[str] = mapped_column(String(20), nullable=False, default="TEST")
cashfree_client_id: Mapped[str | None] = mapped_column(String(180), nullable=True)
cashfree_client_secret: Mapped[str | None] = mapped_column(String(240), nullable=True)
cashfree_api_version: Mapped[str] = mapped_column(String(20), nullable=False, default="2023-08-01")
cashfree_order_note: Mapped[str | None] = mapped_column(String(250), nullable=True)
authorised_signatory_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
declaration: Mapped[str | None] = mapped_column(Text, nullable=True)
terms: Mapped[str | None] = mapped_column(Text, nullable=True)
footer_note: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
class BillingInvoiceGenerationBatch(CommonBase):
__tablename__ = "billing_invoice_generation_batches"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int | None] = mapped_column(ForeignKey("branches.id", ondelete="SET NULL"), nullable=True, index=True)
billing_period_from: Mapped[date] = mapped_column(Date, nullable=False, index=True)
billing_period_to: Mapped[date] = mapped_column(Date, nullable=False, index=True)
financial_year: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True)
frequency: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True)
status: Mapped[str] = mapped_column(String(20), nullable=False, default="DRAFT_CREATED", index=True)
selected_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
created_invoice_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
skipped_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
error_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
remarks: Mapped[str | None] = mapped_column(Text, nullable=True)
generated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
generated_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
invoices = relationship("BillingInvoice", back_populates="generation_batch")
class BillingInvoice(CommonBase):
__tablename__ = "billing_invoices"
__table_args__ = (
UniqueConstraint("tenant_id", "invoice_no", name="uq_billing_invoices_tenant_invoice_no"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int | None] = mapped_column(ForeignKey("branches.id", ondelete="SET NULL"), nullable=True, index=True)
client_id: Mapped[int] = mapped_column(ForeignKey("clients.id", ondelete="RESTRICT"), nullable=False, index=True)
engagement_id: Mapped[int | None] = mapped_column(ForeignKey("client_service_subscriptions.id", ondelete="SET NULL"), nullable=True, index=True)
generation_batch_id: Mapped[int | None] = mapped_column(ForeignKey("billing_invoice_generation_batches.id", ondelete="SET NULL"), nullable=True, index=True)
invoice_no: Mapped[str] = mapped_column(String(60), nullable=False, index=True)
invoice_date: Mapped[date] = mapped_column(Date, nullable=False, default=date.today, index=True)
due_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True)
billing_period_from: Mapped[date | None] = mapped_column(Date, nullable=True)
billing_period_to: Mapped[date | None] = mapped_column(Date, nullable=True)
financial_year: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True)
invoice_title: Mapped[str | None] = mapped_column(String(80), nullable=True)
place_of_supply: Mapped[str | None] = mapped_column(String(120), nullable=True)
reverse_charge: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
client_legal_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
client_trade_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
client_gstin: Mapped[str | None] = mapped_column(String(20), nullable=True)
client_pan: Mapped[str | None] = mapped_column(String(20), nullable=True)
client_billing_address: Mapped[str | None] = mapped_column(Text, nullable=True)
client_state: Mapped[str | None] = mapped_column(String(100), nullable=True)
client_state_code: Mapped[str | None] = mapped_column(String(2), nullable=True)
client_email: Mapped[str | None] = mapped_column(String(255), nullable=True)
client_mobile: Mapped[str | None] = mapped_column(String(50), nullable=True)
tax_type: Mapped[str] = mapped_column(String(20), nullable=False, default="CGST_SGST")
subtotal: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
discount_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
taxable_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
cgst_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
sgst_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
igst_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
round_off: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
total_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
amount_received: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
tds_deducted: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
bank_charges: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
balance_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
amount_in_words: Mapped[str | None] = mapped_column(String(500), nullable=True)
status: Mapped[str] = mapped_column(String(20), nullable=False, default="DRAFT", index=True)
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
terms: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
approved_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
posted_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
client = relationship("Client")
engagement = relationship("ClientServiceSubscription", foreign_keys=[engagement_id])
generation_batch = relationship("BillingInvoiceGenerationBatch", back_populates="invoices")
lines = relationship("BillingInvoiceLine", back_populates="invoice", cascade="all, delete-orphan", order_by="BillingInvoiceLine.sort_order.asc()")
payments = relationship("BillingPayment", back_populates="invoice", cascade="all, delete-orphan", order_by="BillingPayment.payment_date.desc(), BillingPayment.id.desc()")
online_transactions = relationship("BillingOnlinePaymentTransaction", back_populates="invoice", cascade="all, delete-orphan", order_by="BillingOnlinePaymentTransaction.created_at_utc.desc(), BillingOnlinePaymentTransaction.id.desc()")
class BillingPayment(CommonBase):
__tablename__ = "billing_payments"
__table_args__ = (
UniqueConstraint("tenant_id", "receipt_no", name="uq_billing_payments_tenant_receipt_no"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int | None] = mapped_column(ForeignKey("branches.id", ondelete="SET NULL"), nullable=True, index=True)
invoice_id: Mapped[int] = mapped_column(ForeignKey("billing_invoices.id", ondelete="CASCADE"), nullable=False, index=True)
client_id: Mapped[int] = mapped_column(ForeignKey("clients.id", ondelete="RESTRICT"), nullable=False, index=True)
receipt_no: Mapped[str] = mapped_column(String(60), nullable=False, index=True)
receipt_date: Mapped[date] = mapped_column(Date, nullable=False, default=date.today, index=True)
payment_date: Mapped[date] = mapped_column(Date, nullable=False, default=date.today, index=True)
financial_year: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True)
amount_received: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
tds_deducted: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
bank_charges: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
mode: Mapped[str] = mapped_column(String(30), nullable=False, default="BANK")
reference_no: Mapped[str | None] = mapped_column(String(120), nullable=True)
payment_gateway: Mapped[str | None] = mapped_column(String(50), nullable=True)
gateway_transaction_id: Mapped[str | None] = mapped_column(String(120), nullable=True)
remarks: Mapped[str | None] = mapped_column(Text, nullable=True)
status: Mapped[str] = mapped_column(String(20), nullable=False, default="RECEIVED", index=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
invoice = relationship("BillingInvoice", back_populates="payments")
client = relationship("Client")
class BillingOnlinePaymentTransaction(CommonBase):
__tablename__ = "billing_online_payment_transactions"
__table_args__ = (
UniqueConstraint("tenant_id", "txnid", name="uq_billing_online_payment_tenant_txnid"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int | None] = mapped_column(ForeignKey("branches.id", ondelete="SET NULL"), nullable=True, index=True)
invoice_id: Mapped[int] = mapped_column(ForeignKey("billing_invoices.id", ondelete="CASCADE"), nullable=False, index=True)
client_id: Mapped[int] = mapped_column(ForeignKey("clients.id", ondelete="RESTRICT"), nullable=False, index=True)
provider: Mapped[str] = mapped_column(String(40), nullable=False, default="PAYUMONEY", index=True)
mode: Mapped[str] = mapped_column(String(20), nullable=False, default="TEST")
txnid: Mapped[str] = mapped_column(String(80), nullable=False, index=True)
amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
productinfo: Mapped[str | None] = mapped_column(String(250), nullable=True)
firstname: Mapped[str | None] = mapped_column(String(120), nullable=True)
email: Mapped[str | None] = mapped_column(String(255), nullable=True)
phone: Mapped[str | None] = mapped_column(String(50), nullable=True)
payu_payment_id: Mapped[str | None] = mapped_column(String(120), nullable=True, index=True)
cashfree_order_id: Mapped[str | None] = mapped_column(String(120), nullable=True, index=True)
cashfree_cf_order_id: Mapped[str | None] = mapped_column(String(120), nullable=True, index=True)
cashfree_payment_session_id: Mapped[str | None] = mapped_column(String(500), nullable=True)
cashfree_payment_id: Mapped[str | None] = mapped_column(String(120), nullable=True, index=True)
webhook_event_id: Mapped[str | None] = mapped_column(String(120), nullable=True, index=True)
bank_ref_num: Mapped[str | None] = mapped_column(String(120), nullable=True)
mihpayid: Mapped[str | None] = mapped_column(String(120), nullable=True)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="INITIATED", index=True)
gateway_status: Mapped[str | None] = mapped_column(String(80), nullable=True)
response_hash: Mapped[str | None] = mapped_column(String(200), nullable=True)
raw_response: Mapped[str | None] = mapped_column(Text, nullable=True)
receipt_payment_id: Mapped[int | None] = mapped_column(ForeignKey("billing_payments.id", ondelete="SET NULL"), nullable=True, index=True)
created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
completed_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
invoice = relationship("BillingInvoice", back_populates="online_transactions")
client = relationship("Client")
receipt_payment = relationship("BillingPayment", foreign_keys=[receipt_payment_id])
class BillingInvoiceLine(CommonBase):
__tablename__ = "billing_invoice_lines"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
invoice_id: Mapped[int] = mapped_column(ForeignKey("billing_invoices.id", ondelete="CASCADE"), nullable=False, index=True)
service_id: Mapped[int | None] = mapped_column(ForeignKey("service_catalogues.id", ondelete="SET NULL"), nullable=True, index=True)
engagement_id: Mapped[int | None] = mapped_column(ForeignKey("client_service_subscriptions.id", ondelete="SET NULL"), nullable=True, index=True)
fee_group_id: Mapped[int | None] = mapped_column(ForeignKey("billing_fee_groups.id", ondelete="SET NULL"), nullable=True, index=True)
description: Mapped[str] = mapped_column(String(500), nullable=False)
sac_code: Mapped[str | None] = mapped_column(String(20), nullable=True)
billing_period_from: Mapped[date | None] = mapped_column(Date, nullable=True)
billing_period_to: Mapped[date | None] = mapped_column(Date, nullable=True)
quantity: Mapped[Decimal] = mapped_column(Numeric(12, 2), nullable=False, default=Decimal("1.00"))
rate: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
discount_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
taxable_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
gst_rate: Mapped[Decimal] = mapped_column(Numeric(5, 2), nullable=False, default=Decimal("18.00"))
cgst_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
sgst_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
igst_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
line_total: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
invoice = relationship("BillingInvoice", back_populates="lines")
service = relationship("ServiceCatalogue")
class BillingFeeGroup(CommonBase):
__tablename__ = "billing_fee_groups"
__table_args__ = (
UniqueConstraint("tenant_id", "group_code", name="uq_billing_fee_groups_tenant_code"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int | None] = mapped_column(ForeignKey("branches.id", ondelete="SET NULL"), nullable=True, index=True)
client_id: Mapped[int] = mapped_column(ForeignKey("clients.id", ondelete="CASCADE"), nullable=False, index=True)
partner_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
group_code: Mapped[str] = mapped_column(String(80), nullable=False, index=True)
group_name: Mapped[str] = mapped_column(String(200), nullable=False)
billing_mode: Mapped[str] = mapped_column(String(20), nullable=False, default="PACKAGE")
frequency: Mapped[str] = mapped_column(String(20), nullable=False, default="Monthly")
fee_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
gst_rate: Mapped[Decimal] = mapped_column(Numeric(5, 2), nullable=False, default=Decimal("18.00"))
tax_type: Mapped[str] = mapped_column(String(20), nullable=False, default="CGST_SGST")
effective_from: Mapped[date | None] = mapped_column(Date, nullable=True)
effective_to: Mapped[date | None] = mapped_column(Date, nullable=True)
auto_generate: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, index=True)
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
updated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
client = relationship("Client")
services = relationship("BillingFeeGroupService", back_populates="fee_group", cascade="all, delete-orphan", order_by="BillingFeeGroupService.sort_order.asc()")
class BillingFeeGroupService(CommonBase):
__tablename__ = "billing_fee_group_services"
__table_args__ = (
UniqueConstraint("fee_group_id", "service_id", name="uq_billing_fee_group_services_group_service"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
fee_group_id: Mapped[int] = mapped_column(ForeignKey("billing_fee_groups.id", ondelete="CASCADE"), nullable=False, index=True)
service_id: Mapped[int] = mapped_column(ForeignKey("service_catalogues.id", ondelete="RESTRICT"), nullable=False, index=True)
line_description: Mapped[str | None] = mapped_column(String(500), nullable=True)
allocation_type: Mapped[str] = mapped_column(String(20), nullable=False, default="Included")
line_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, default=Decimal("0.00"))
percentage: Mapped[Decimal | None] = mapped_column(Numeric(5, 2), nullable=True)
sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
fee_group = relationship("BillingFeeGroup", back_populates="services")
service = relationship("ServiceCatalogue")