start working on business
This commit is contained in:
parent
f1a5bb4c41
commit
9e07470511
|
|
@ -5,5 +5,6 @@ from .user import User # noqa: F401
|
||||||
from .api_key import ApiKey # noqa: F401
|
from .api_key import ApiKey # noqa: F401
|
||||||
from .captcha import Captcha # noqa: F401
|
from .captcha import Captcha # noqa: F401
|
||||||
from .password_reset import PasswordReset # noqa: F401
|
from .password_reset import PasswordReset # noqa: F401
|
||||||
|
from .business import Business # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
55
hesabixAPI/adapters/db/models/business.py
Normal file
55
hesabixAPI/adapters/db/models/business.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from sqlalchemy import String, DateTime, Integer, ForeignKey, Enum as SQLEnum, Text
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# فیلدهای جدید
|
||||||
|
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)
|
||||||
|
|
||||||
|
# فیلدهای جغرافیایی
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
143
hesabixAPI/adapters/db/repositories/business_repo.py
Normal file
143
hesabixAPI/adapters/db/repositories/business_repo.py
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List, Optional
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from sqlalchemy import select, and_
|
||||||
|
|
||||||
|
from .base_repo import BaseRepository
|
||||||
|
from ..models.business import Business, BusinessType, BusinessField
|
||||||
|
|
||||||
|
|
||||||
|
class BusinessRepository(BaseRepository[Business]):
|
||||||
|
"""Repository برای مدیریت کسب و کارها"""
|
||||||
|
|
||||||
|
def __init__(self, db: Session) -> None:
|
||||||
|
super().__init__(db, Business)
|
||||||
|
|
||||||
|
def get_by_owner_id(self, owner_id: int) -> List[Business]:
|
||||||
|
"""دریافت تمام کسب و کارهای یک مالک"""
|
||||||
|
stmt = select(Business).where(Business.owner_id == owner_id)
|
||||||
|
return list(self.db.execute(stmt).scalars().all())
|
||||||
|
|
||||||
|
def get_by_business_type(self, business_type: BusinessType) -> List[Business]:
|
||||||
|
"""دریافت کسب و کارها بر اساس نوع"""
|
||||||
|
stmt = select(Business).where(Business.business_type == business_type)
|
||||||
|
return list(self.db.execute(stmt).scalars().all())
|
||||||
|
|
||||||
|
def get_by_business_field(self, business_field: BusinessField) -> List[Business]:
|
||||||
|
"""دریافت کسب و کارها بر اساس زمینه فعالیت"""
|
||||||
|
stmt = select(Business).where(Business.business_field == business_field)
|
||||||
|
return list(self.db.execute(stmt).scalars().all())
|
||||||
|
|
||||||
|
def get_by_owner_and_type(self, owner_id: int, business_type: BusinessType) -> List[Business]:
|
||||||
|
"""دریافت کسب و کارهای یک مالک بر اساس نوع"""
|
||||||
|
stmt = select(Business).where(
|
||||||
|
and_(
|
||||||
|
Business.owner_id == owner_id,
|
||||||
|
Business.business_type == business_type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return list(self.db.execute(stmt).scalars().all())
|
||||||
|
|
||||||
|
def search_by_name(self, name: str) -> List[Business]:
|
||||||
|
"""جستجوی کسب و کارها بر اساس نام (case-insensitive)"""
|
||||||
|
stmt = select(Business).where(Business.name.ilike(f"%{name}%"))
|
||||||
|
return list(self.db.execute(stmt).scalars().all())
|
||||||
|
|
||||||
|
def create_business(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
business_type: BusinessType,
|
||||||
|
business_field: BusinessField,
|
||||||
|
owner_id: int,
|
||||||
|
address: str | None = None,
|
||||||
|
phone: str | None = None,
|
||||||
|
mobile: str | None = None,
|
||||||
|
national_id: str | None = None,
|
||||||
|
registration_number: str | None = None,
|
||||||
|
economic_id: str | None = None,
|
||||||
|
country: str | None = None,
|
||||||
|
province: str | None = None,
|
||||||
|
city: str | None = None,
|
||||||
|
postal_code: str | None = None
|
||||||
|
) -> Business:
|
||||||
|
"""ایجاد کسب و کار جدید"""
|
||||||
|
business = Business(
|
||||||
|
name=name,
|
||||||
|
business_type=business_type,
|
||||||
|
business_field=business_field,
|
||||||
|
owner_id=owner_id,
|
||||||
|
address=address,
|
||||||
|
phone=phone,
|
||||||
|
mobile=mobile,
|
||||||
|
national_id=national_id,
|
||||||
|
registration_number=registration_number,
|
||||||
|
economic_id=economic_id,
|
||||||
|
country=country,
|
||||||
|
province=province,
|
||||||
|
city=city,
|
||||||
|
postal_code=postal_code
|
||||||
|
)
|
||||||
|
self.db.add(business)
|
||||||
|
self.db.commit()
|
||||||
|
self.db.refresh(business)
|
||||||
|
return business
|
||||||
|
|
||||||
|
def get_by_national_id(self, national_id: str) -> Business | None:
|
||||||
|
"""دریافت کسب و کار بر اساس شناسه ملی"""
|
||||||
|
stmt = select(Business).where(Business.national_id == national_id)
|
||||||
|
return self.db.execute(stmt).scalars().first()
|
||||||
|
|
||||||
|
def get_by_registration_number(self, registration_number: str) -> Business | None:
|
||||||
|
"""دریافت کسب و کار بر اساس شماره ثبت"""
|
||||||
|
stmt = select(Business).where(Business.registration_number == registration_number)
|
||||||
|
return self.db.execute(stmt).scalars().first()
|
||||||
|
|
||||||
|
def get_by_economic_id(self, economic_id: str) -> Business | None:
|
||||||
|
"""دریافت کسب و کار بر اساس شناسه اقتصادی"""
|
||||||
|
stmt = select(Business).where(Business.economic_id == economic_id)
|
||||||
|
return self.db.execute(stmt).scalars().first()
|
||||||
|
|
||||||
|
def search_by_phone(self, phone: str) -> List[Business]:
|
||||||
|
"""جستجوی کسب و کارها بر اساس شماره تلفن"""
|
||||||
|
stmt = select(Business).where(
|
||||||
|
(Business.phone == phone) | (Business.mobile == phone)
|
||||||
|
)
|
||||||
|
return list(self.db.execute(stmt).scalars().all())
|
||||||
|
|
||||||
|
def get_by_country(self, country: str) -> List[Business]:
|
||||||
|
"""دریافت کسب و کارها بر اساس کشور"""
|
||||||
|
stmt = select(Business).where(Business.country == country)
|
||||||
|
return list(self.db.execute(stmt).scalars().all())
|
||||||
|
|
||||||
|
def get_by_province(self, province: str) -> List[Business]:
|
||||||
|
"""دریافت کسب و کارها بر اساس استان"""
|
||||||
|
stmt = select(Business).where(Business.province == province)
|
||||||
|
return list(self.db.execute(stmt).scalars().all())
|
||||||
|
|
||||||
|
def get_by_city(self, city: str) -> List[Business]:
|
||||||
|
"""دریافت کسب و کارها بر اساس شهرستان"""
|
||||||
|
stmt = select(Business).where(Business.city == city)
|
||||||
|
return list(self.db.execute(stmt).scalars().all())
|
||||||
|
|
||||||
|
def get_by_postal_code(self, postal_code: str) -> List[Business]:
|
||||||
|
"""دریافت کسب و کارها بر اساس کد پستی"""
|
||||||
|
stmt = select(Business).where(Business.postal_code == postal_code)
|
||||||
|
return list(self.db.execute(stmt).scalars().all())
|
||||||
|
|
||||||
|
def get_by_location(self, country: str | None = None, province: str | None = None, city: str | None = None) -> List[Business]:
|
||||||
|
"""دریافت کسب و کارها بر اساس موقعیت جغرافیایی"""
|
||||||
|
stmt = select(Business)
|
||||||
|
conditions = []
|
||||||
|
|
||||||
|
if country:
|
||||||
|
conditions.append(Business.country == country)
|
||||||
|
if province:
|
||||||
|
conditions.append(Business.province == province)
|
||||||
|
if city:
|
||||||
|
conditions.append(Business.city == city)
|
||||||
|
|
||||||
|
if conditions:
|
||||||
|
stmt = stmt.where(and_(*conditions))
|
||||||
|
|
||||||
|
return list(self.db.execute(stmt).scalars().all())
|
||||||
|
|
@ -21,6 +21,7 @@ Requires-Dist: Babel>=2.15.0
|
||||||
Requires-Dist: jdatetime>=4.1.0
|
Requires-Dist: jdatetime>=4.1.0
|
||||||
Requires-Dist: weasyprint>=62.3
|
Requires-Dist: weasyprint>=62.3
|
||||||
Requires-Dist: jinja2>=3.1.0
|
Requires-Dist: jinja2>=3.1.0
|
||||||
|
Requires-Dist: openpyxl>=3.1.0
|
||||||
Provides-Extra: dev
|
Provides-Extra: dev
|
||||||
Requires-Dist: pytest>=8.2.0; extra == "dev"
|
Requires-Dist: pytest>=8.2.0; extra == "dev"
|
||||||
Requires-Dist: httpx>=0.27.0; extra == "dev"
|
Requires-Dist: httpx>=0.27.0; extra == "dev"
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,12 @@ app/core/smart_normalizer.py
|
||||||
app/services/api_key_service.py
|
app/services/api_key_service.py
|
||||||
app/services/auth_service.py
|
app/services/auth_service.py
|
||||||
app/services/captcha_service.py
|
app/services/captcha_service.py
|
||||||
app/services/pdf_service.py
|
|
||||||
app/services/query_service.py
|
app/services/query_service.py
|
||||||
app/services/user_context_service.py
|
app/services/pdf/__init__.py
|
||||||
|
app/services/pdf/base_pdf_service.py
|
||||||
|
app/services/pdf/modules/__init__.py
|
||||||
|
app/services/pdf/modules/marketing/__init__.py
|
||||||
|
app/services/pdf/modules/marketing/marketing_module.py
|
||||||
hesabix_api.egg-info/PKG-INFO
|
hesabix_api.egg-info/PKG-INFO
|
||||||
hesabix_api.egg-info/SOURCES.txt
|
hesabix_api.egg-info/SOURCES.txt
|
||||||
hesabix_api.egg-info/dependency_links.txt
|
hesabix_api.egg-info/dependency_links.txt
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ Babel>=2.15.0
|
||||||
jdatetime>=4.1.0
|
jdatetime>=4.1.0
|
||||||
weasyprint>=62.3
|
weasyprint>=62.3
|
||||||
jinja2>=3.1.0
|
jinja2>=3.1.0
|
||||||
|
openpyxl>=3.1.0
|
||||||
|
|
||||||
[dev]
|
[dev]
|
||||||
pytest>=8.2.0
|
pytest>=8.2.0
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "20250117_000003"
|
||||||
|
down_revision = "20250916_000002"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# Create businesses table
|
||||||
|
op.create_table(
|
||||||
|
'businesses',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('business_type', mysql.ENUM('شرکت', 'مغازه', 'فروشگاه', 'اتحادیه', 'باشگاه', 'موسسه', 'شخصی', name='businesstype'), nullable=False),
|
||||||
|
sa.Column('business_field', mysql.ENUM('تولیدی', 'بازرگانی', 'خدماتی', 'سایر', name='businessfield'), nullable=False),
|
||||||
|
sa.Column('owner_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['owner_id'], ['users.id'], ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create indexes
|
||||||
|
op.create_index('ix_businesses_name', 'businesses', ['name'])
|
||||||
|
op.create_index('ix_businesses_owner_id', 'businesses', ['owner_id'])
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# Drop indexes
|
||||||
|
op.drop_index('ix_businesses_owner_id', table_name='businesses')
|
||||||
|
op.drop_index('ix_businesses_name', table_name='businesses')
|
||||||
|
|
||||||
|
# Drop table
|
||||||
|
op.drop_table('businesses')
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "20250117_000004"
|
||||||
|
down_revision = "20250117_000003"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# Add new contact and identification fields to businesses table
|
||||||
|
op.add_column('businesses', sa.Column('address', sa.Text(), nullable=True))
|
||||||
|
op.add_column('businesses', sa.Column('phone', sa.String(length=20), nullable=True))
|
||||||
|
op.add_column('businesses', sa.Column('mobile', sa.String(length=20), nullable=True))
|
||||||
|
op.add_column('businesses', sa.Column('national_id', sa.String(length=20), nullable=True))
|
||||||
|
op.add_column('businesses', sa.Column('registration_number', sa.String(length=50), nullable=True))
|
||||||
|
op.add_column('businesses', sa.Column('economic_id', sa.String(length=50), nullable=True))
|
||||||
|
|
||||||
|
# Create indexes for the new fields
|
||||||
|
op.create_index('ix_businesses_national_id', 'businesses', ['national_id'])
|
||||||
|
op.create_index('ix_businesses_registration_number', 'businesses', ['registration_number'])
|
||||||
|
op.create_index('ix_businesses_economic_id', 'businesses', ['economic_id'])
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# Drop indexes
|
||||||
|
op.drop_index('ix_businesses_economic_id', table_name='businesses')
|
||||||
|
op.drop_index('ix_businesses_registration_number', table_name='businesses')
|
||||||
|
op.drop_index('ix_businesses_national_id', table_name='businesses')
|
||||||
|
|
||||||
|
# Drop columns
|
||||||
|
op.drop_column('businesses', 'economic_id')
|
||||||
|
op.drop_column('businesses', 'registration_number')
|
||||||
|
op.drop_column('businesses', 'national_id')
|
||||||
|
op.drop_column('businesses', 'mobile')
|
||||||
|
op.drop_column('businesses', 'phone')
|
||||||
|
op.drop_column('businesses', 'address')
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "20250117_000005"
|
||||||
|
down_revision = "20250117_000004"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# Add geographic fields to businesses table
|
||||||
|
op.add_column('businesses', sa.Column('country', sa.String(length=100), nullable=True))
|
||||||
|
op.add_column('businesses', sa.Column('province', sa.String(length=100), nullable=True))
|
||||||
|
op.add_column('businesses', sa.Column('city', sa.String(length=100), nullable=True))
|
||||||
|
op.add_column('businesses', sa.Column('postal_code', sa.String(length=20), nullable=True))
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# Drop geographic columns
|
||||||
|
op.drop_column('businesses', 'postal_code')
|
||||||
|
op.drop_column('businesses', 'city')
|
||||||
|
op.drop_column('businesses', 'province')
|
||||||
|
op.drop_column('businesses', 'country')
|
||||||
Loading…
Reference in a new issue