Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixAPI/adapters/db/models/business.py

305 lines
No EOL
15 KiB
Python
Executable file

from __future__ import annotations
from datetime import datetime
from enum import Enum
from sqlalchemy import String, DateTime, Integer, ForeignKey, Enum as SQLEnum, Text, Numeric, Boolean, JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship
from adapters.db.session import Base
class BusinessType(str, Enum):
"""نوع کسب و کار"""
COMPANY = "شرکت" # شرکت
SHOP = "مغازه" # مغازه
STORE = "فروشگاه" # فروشگاه
UNION = "اتحادیه" # اتحادیه
CLUB = "باشگاه" # باشگاه
INSTITUTE = "موسسه" # موسسه
INDIVIDUAL = "شخصی" # شخصی
class BusinessField(str, Enum):
"""زمینه فعالیت کسب و کار"""
MANUFACTURING = "تولیدی" # تولیدی
TRADING = "بازرگانی" # بازرگانی
SERVICE = "خدماتی" # خدماتی
OTHER = "سایر" # سایر
class Business(Base):
__tablename__ = "businesses"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
business_type: Mapped[BusinessType] = mapped_column(SQLEnum(BusinessType), nullable=False)
business_field: Mapped[BusinessField] = mapped_column(SQLEnum(BusinessField), nullable=False)
owner_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
default_currency_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("currencies.id", ondelete="RESTRICT"), nullable=True, index=True)
# فیلدهای جدید
address: Mapped[str | None] = mapped_column(Text, nullable=True)
phone: Mapped[str | None] = mapped_column(String(20), nullable=True)
mobile: Mapped[str | None] = mapped_column(String(20), nullable=True)
national_id: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True)
registration_number: Mapped[str | None] = mapped_column(String(50), nullable=True, index=True)
economic_id: Mapped[str | None] = mapped_column(String(50), nullable=True, index=True)
# شناسه فایل‌های ذخیره‌سازی شده برای لوگو و مهر (ارجاع به جدول file_storage)
logo_file_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
stamp_file_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
# فیلدهای جغرافیایی
country: Mapped[str | None] = mapped_column(String(100), nullable=True)
province: Mapped[str | None] = mapped_column(String(100), nullable=True)
city: Mapped[str | None] = mapped_column(String(100), nullable=True)
postal_code: Mapped[str | None] = mapped_column(String(20), nullable=True)
# تنظیمات اعتبار مشتریان
default_credit_limit: Mapped[float | None] = mapped_column(Numeric(14, 2), nullable=True, comment="سقف اعتبار پیشفرض اشخاص")
check_credit_enabled_by_default: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default="0", comment="بررسی اعتبار مشتریان به صورت پیشفرض")
# تنظیمات محاسبه سود فاکتور
invoice_profit_calculation_method: Mapped[str | None] = mapped_column(String(20), nullable=True, default="automatic", server_default="automatic", comment="روش محاسبه سود فاکتور: automatic, manual, disabled")
invoice_profit_calculation_basis: Mapped[str | None] = mapped_column(
String(30),
nullable=True,
default="purchase_price",
server_default="purchase_price",
comment="مبنای محاسبه سود: purchase_price, cost_price, average_cost, fifo, lifo, weighted_average, moving_weighted_average, standard_cost, actual_cost",
)
invoice_profit_include_overhead: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default="0", comment="آیا هزینه‌های سربار در محاسبه سود لحاظ شود؟")
invoice_profit_overhead_type: Mapped[str | None] = mapped_column(String(30), nullable=True, default="none", server_default="none", comment="نوع هزینه‌های سربار: none, production_overhead, all_overhead, custom_percent")
invoice_profit_overhead_percent: Mapped[float | None] = mapped_column(Numeric(5, 2), nullable=True, default=0, server_default="0", comment="درصد هزینه‌های سربار (در صورت انتخاب custom_percent)")
invoice_profit_calculation_type: Mapped[str | None] = mapped_column(String(20), nullable=True, default="gross", server_default="gross", comment="نوع محاسبه سود: gross, net, both")
invoice_profit_fifo_shortage_mode: Mapped[str] = mapped_column(
String(40),
nullable=False,
default="perpetual_mixed",
server_default="perpetual_mixed",
comment=(
"سیاست بهای کسری در مبنای FIFO/LIFO: perpetual_mixed (لایه+آخرین قیمت) | "
"average_purchase_on_shortage (میانگین خرید برای بخش بدون لایه)"
),
)
# شناسایی سود/بهای تمام‌شده قطعی (دفتر) در برابر محاسبه تحلیلی
invoice_profit_ledger_recognition_basis: Mapped[str] = mapped_column(
String(40),
nullable=False,
default="warehouse_document_posting",
server_default="warehouse_document_posting",
comment="زمان شناسایی بهای تمام‌شده قطعی: warehouse_document_posting | sales_invoice_document",
)
# به‌روزرسانی قیمت پایه کالا از فاکتور قطعی (ارز کالا = ارز پیش‌فرض کسب‌وکار)
invoice_sync_update_sales_price_enabled: Mapped[bool] = mapped_column(
Boolean, nullable=False, default=False, server_default="0",
comment="به‌روزرسانی قیمت فروش پایه از فاکتور فروش قطعی",
)
invoice_sync_update_purchase_price_enabled: Mapped[bool] = mapped_column(
Boolean, nullable=False, default=False, server_default="0",
comment="به‌روزرسانی قیمت خرید پایه از فاکتور خرید قطعی",
)
invoice_sync_sales_price_basis: Mapped[str | None] = mapped_column(
String(40), nullable=True, default="net_after_line_discount", server_default="net_after_line_discount",
comment="مبنای قیمت فروش: unit_price, net_after_line_discount, net_with_tax, cost_price",
)
invoice_sync_purchase_price_basis: Mapped[str | None] = mapped_column(
String(40), nullable=True, default="net_after_line_discount", server_default="net_after_line_discount",
comment="مبنای قیمت خرید: unit_price, net_after_line_discount, net_with_tax, cost_price",
)
# حواله انبار پس از ثبت فاکتور: none (بدون حواله)، draft (پیش‌نویس)، posted (قطعی فوری)
invoice_warehouse_release_mode: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="draft",
server_default="draft",
comment="none | draft | posted",
)
invoice_purchase_accounting_mode: Mapped[str] = mapped_column(
String(40),
nullable=False,
default="direct_inventory",
server_default="direct_inventory",
comment="direct_inventory | grni_two_step | grni_legacy",
)
# ردیف‌های انبارداری بدون انبار در فاکتور (هنگام ثبت/ویرایش با post_inventory)
invoice_missing_line_warehouse_policy: Mapped[str] = mapped_column(
String(32),
nullable=False,
default="reject",
server_default="reject",
comment="reject | use_default_warehouse",
)
invoice_default_warehouse_id: Mapped[int | None] = mapped_column(
Integer,
ForeignKey("warehouses.id", ondelete="SET NULL"),
nullable=True,
index=True,
comment="انبار پیش‌فرض برای ردیف‌های بدون انبار وقتی سیاست use_default_warehouse است",
)
invoice_default_warehouse_fill_document_header: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=True,
server_default="1",
comment="با پر کردن خودکار خطوط، انبار سطح سربرگ فاکتور هم اگر خالی بود پر شود",
)
# تخفیف کلی فاکتور (در کنار تخفیف ردیف)
invoice_global_discount_percent_basis: Mapped[str] = mapped_column(
String(64),
nullable=False,
default="subtotal_after_line_discount",
server_default="subtotal_after_line_discount",
comment=(
"مبنای درصد تخفیف کلی: subtotal_after_line_discount | gross_before_line_discount | "
"total_after_lines_including_tax"
),
)
invoice_global_discount_tax_mode: Mapped[str] = mapped_column(
String(64),
nullable=False,
default="recalculate_tax_proportional",
server_default="recalculate_tax_proportional",
comment="recalculate_tax_proportional | keep_line_taxes",
)
invoice_global_discount_max_percent: Mapped[float | None] = mapped_column(
Numeric(5, 2),
nullable=True,
comment="سقف درصد تخفیف کلی نسبت به مبنا (اختیاری)",
)
invoice_global_discount_max_amount: Mapped[float | None] = mapped_column(
Numeric(18, 2),
nullable=True,
comment="سقف مبلغ تخفیف کلی (اختیاری)",
)
# کنترل کسری هنگام قطعی کردن حواله (خروج): پیش‌فرض سخت‌گیرانه
allow_negative_inventory_for_bulk: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
server_default="0",
comment="خروج با موجودی منفی برای کالاهای فله‌ای (inventory_mode غیر unique)",
)
allow_negative_inventory_for_unique: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
server_default="0",
comment="خروج با موجودی منفی برای کالاهای یونیک",
)
warehouse_transfer_require_positive_stock: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=True,
server_default="1",
comment="انتقال بین انبار همیشه نیاز به موجودی کافی (نادیده گرفتن اجازه منفی)",
)
# کالای هزینه‌شده / کالای درآمدشده
goods_expense_income_workflow_mode: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="simple",
server_default="simple",
comment="simple | two_step",
)
goods_expense_income_auto_post_in_simple_mode: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=True,
server_default="1",
comment="در حالت simple، ثبت قطعی در صورت داشتن مجوز post",
)
goods_expense_income_default_expense_account_code: Mapped[str] = mapped_column(
String(50),
nullable=False,
default="70407",
server_default="70407",
)
goods_expense_income_default_income_account_code: Mapped[str] = mapped_column(
String(50),
nullable=False,
default="60103",
server_default="60103",
)
goods_expense_income_stock_count_mode: Mapped[str] = mapped_column(
String(32),
nullable=False,
default="goods_docs",
server_default="goods_docs",
comment="goods_docs | physical_adjustment | ask",
)
goods_expense_income_allow_manual_unit_cost: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
server_default="0",
)
goods_expense_income_require_person: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
server_default="0",
)
# سیاست تسعیر ارز روی فاکتور/اسناد (کلیدها در app.services.invoice_fx_revaluation)
fx_revaluation_policy: Mapped[dict | None] = mapped_column(JSON, nullable=True)
# پیش‌فرض‌های لینک اشتراک فاکتور (مثلاً default_online_payment_enabled / default_online_payment_gateway_id)
invoice_share_settings: Mapped[dict | None] = mapped_column(JSON, nullable=True)
# کاتالوگ عمومی (شبکهٔ انتشار کالا): نمایش شماره تماس در API عمومی
public_catalog_show_contact: Mapped[bool] = mapped_column(
Boolean,
default=False,
nullable=False,
server_default="0",
comment="نمایش تلفن/موبایل در API عمومی کاتالوگ",
)
public_catalog_show_base_sales_price: Mapped[bool] = mapped_column(
Boolean,
default=True,
nullable=False,
server_default="1",
comment="نمایش قیمت فروش پایه در API عمومی کاتالوگ",
)
display_timezone: Mapped[str | None] = mapped_column(
String(80),
nullable=True,
comment="IANA timezone for datetime display; null = use system default",
)
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)
# Soft Delete fields
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True)
deletion_requested_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
deletion_requested_by: Mapped[int | None] = mapped_column(Integer, ForeignKey("users.id"), nullable=True)
deletion_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
auto_delete_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True) # 30 days after deleted_at
# Relationships
persons: Mapped[list["Person"]] = relationship("Person", back_populates="business", cascade="all, delete-orphan")
person_groups = relationship("PersonGroup", back_populates="business", cascade="all, delete-orphan")
fiscal_years = relationship("FiscalYear", back_populates="business", cascade="all, delete-orphan")
currencies = relationship("Currency", secondary="business_currencies", back_populates="businesses")
default_currency = relationship("Currency", foreign_keys="[Business.default_currency_id]", uselist=False)
documents = relationship("Document", back_populates="business", cascade="all, delete-orphan")
accounts = relationship("Account", back_populates="business", cascade="all, delete-orphan")
projects = relationship("Project", back_populates="business", cascade="all, delete-orphan")
crm_process_definitions = relationship(
"CrmProcessDefinition",
back_populates="business",
cascade="all, delete-orphan",
)
crm_leads = relationship("Lead", back_populates="business", cascade="all, delete-orphan")
crm_deals = relationship("Deal", back_populates="business", cascade="all, delete-orphan")
crm_activities = relationship("CrmActivity", back_populates="business", cascade="all, delete-orphan")
frequent_descriptions = relationship(
"BusinessFrequentDescription",
back_populates="business",
cascade="all, delete-orphan",
)