Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixAPI/adapters/db/models/customer_club.py
2026-04-25 02:31:13 +03:30

229 lines
9.1 KiB
Python
Executable file

from __future__ import annotations
from datetime import datetime
from decimal import Decimal
from sqlalchemy import (
String,
Integer,
DateTime,
ForeignKey,
Numeric,
Boolean,
Text,
UniqueConstraint,
JSON,
SmallInteger,
)
from sqlalchemy.orm import Mapped, mapped_column
from adapters.db.session import Base
class CustomerClubSettings(Base):
"""تنظیمات باشگاه مشتریان به ازای هر کسب‌وکار."""
__tablename__ = "customer_club_settings"
__table_args__ = (UniqueConstraint("business_id", name="uq_customer_club_settings_business"),)
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
business_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("businesses.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
# percent_basis | points_per_currency
earn_mode: Mapped[str] = mapped_column(String(40), nullable=False, default="percent_basis")
# net | total_with_tax
amount_basis: Mapped[str] = mapped_column(String(40), nullable=False, default="net")
percent_of_basis: Mapped[Decimal | None] = mapped_column(Numeric(18, 8), nullable=True)
step_currency_amount: Mapped[Decimal | None] = mapped_column(Numeric(18, 4), nullable=True)
points_per_step: Mapped[Decimal | None] = mapped_column(Numeric(18, 8), nullable=True)
# floor | ceil | round
rounding_mode: Mapped[str] = mapped_column(String(16), nullable=False, default="floor")
max_points_per_invoice: Mapped[Decimal | None] = mapped_column(Numeric(18, 4), nullable=True)
min_basis_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False, default=Decimal("0"))
require_customer_person_type: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
currency_value_per_point: Mapped[Decimal | None] = mapped_column(
Numeric(18, 8), nullable=True, comment="مبلغ تخفیف به ازای هر امتیاز در ارز فاکتور"
)
max_redeem_points_per_invoice: Mapped[Decimal | None] = mapped_column(Numeric(18, 4), nullable=True)
points_expire_after_days: Mapped[int | None] = mapped_column(Integer, nullable=True)
# decoupled: سطح/ضریب از مانده امتیاز | rfm_based_tiers: ضریب از نمره نرمال‌شده RFM
loyalty_rfm_integration_mode: Mapped[str] = mapped_column(
String(32), nullable=False, default="decoupled"
)
rfm_analytics_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
clv_analytics_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
rfm_analysis_window_months: Mapped[int] = mapped_column(Integer, nullable=False, default=12)
rfm_monetary_basis: Mapped[str] = mapped_column(String(40), nullable=False, default="net")
rfm_scoring_method: Mapped[str] = mapped_column(String(32), nullable=False, default="quintiles")
rfm_weight_recency: Mapped[Decimal | None] = mapped_column(Numeric(18, 8), nullable=True)
rfm_weight_frequency: Mapped[Decimal | None] = mapped_column(Numeric(18, 8), nullable=True)
rfm_weight_monetary: Mapped[Decimal | None] = mapped_column(Numeric(18, 8), nullable=True)
clv_formula: Mapped[str] = mapped_column(String(40), nullable=False, default="historical_total")
clv_avg_lifespan_years: Mapped[Decimal | None] = mapped_column(Numeric(10, 4), nullable=True)
rfm_segment_labels_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
updated_at: Mapped[datetime] = mapped_column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
)
class CustomerClubBalance(Base):
"""مانده امتیاز به ازای هر شخص در کسب‌وکار."""
__tablename__ = "customer_club_balances"
__table_args__ = (UniqueConstraint("business_id", "person_id", name="uq_customer_club_balance_biz_person"),)
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
business_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("businesses.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
person_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("persons.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
balance_points: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False, default=Decimal("0"))
updated_at: Mapped[datetime] = mapped_column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
)
class CustomerClubLedger(Base):
"""دفتر تراکنش‌های امتیاز (append-only منطقی)."""
__tablename__ = "customer_club_ledger"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
business_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("businesses.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
person_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("persons.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
delta_points: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False)
balance_after: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False)
# accrual | reversal | adjustment | sync_delta
transaction_type: Mapped[str] = mapped_column(String(32), nullable=False)
reference_document_id: Mapped[int | None] = mapped_column(
Integer,
ForeignKey("documents.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by_user_id: Mapped[int | None] = mapped_column(
Integer,
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
class CustomerClubInvoiceSnapshot(Base):
"""آخرین امتیاز ثبت‌شده برای یک سند فاکتور جهت همگام‌سازی و حذف."""
__tablename__ = "customer_club_invoice_snapshots"
document_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("documents.id", ondelete="CASCADE"),
primary_key=True,
)
business_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("businesses.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
person_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("persons.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
accrued_points: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False, default=Decimal("0"))
redeemed_points: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False, default=Decimal("0"))
updated_at: Mapped[datetime] = mapped_column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
)
class CustomerClubTier(Base):
"""سطح وفاداری (ضریب امتیاز)."""
__tablename__ = "customer_club_tiers"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
business_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("businesses.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
name: Mapped[str] = mapped_column(String(120), nullable=False)
min_balance_points: Mapped[Decimal] = mapped_column(
Numeric(18, 6), nullable=False, default=Decimal("0")
)
min_rfm_normalized: Mapped[Decimal | None] = mapped_column(Numeric(10, 6), nullable=True)
earn_multiplier: Mapped[Decimal] = mapped_column(
Numeric(18, 6), nullable=False, default=Decimal("1")
)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
updated_at: Mapped[datetime] = mapped_column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
)
class CustomerClubRfmSnapshot(Base):
"""ذخیرهٔ نتیجهٔ تحلیل RFM/CLV برای هر شخص در یک کسب‌وکار."""
__tablename__ = "customer_club_rfm_snapshots"
__table_args__ = (UniqueConstraint("business_id", "person_id", name="uq_cc_rfm_snap_biz_person"),)
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
business_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("businesses.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
person_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("persons.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
recency_days: Mapped[int | None] = mapped_column(Integer, nullable=True)
frequency_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
monetary_total: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False, default=Decimal("0"))
r_score: Mapped[int | None] = mapped_column(SmallInteger, nullable=True)
f_score: Mapped[int | None] = mapped_column(SmallInteger, nullable=True)
m_score: Mapped[int | None] = mapped_column(SmallInteger, nullable=True)
rfm_cell: Mapped[str | None] = mapped_column(String(16), nullable=True)
segment_label: Mapped[str | None] = mapped_column(String(160), nullable=True)
composite_score: Mapped[Decimal | None] = mapped_column(Numeric(18, 8), nullable=True)
clv_estimate: Mapped[Decimal | None] = mapped_column(Numeric(18, 4), nullable=True)
computed_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)