"""Service for fetching nomenclatures from Oracle (read-only).""" from typing import List, Optional from decimal import Decimal from sqlmodel import select from sqlalchemy.ext.asyncio import AsyncSession from app.schemas.receipt import ( PartnerOption, AccountOption, CashRegisterOption, ExpenseTypeOption, ) from app.services.expense_types import EXPENSE_TYPES from app.db.models.nomenclature import SyncedSupplier, LocalSupplier, SyncedCashRegister class NomenclatureService: """ Service for fetching nomenclatures. In Phase 1 (MVP), some nomenclatures are hardcoded. In Phase 2, these will be fetched from Oracle. """ @staticmethod async def get_partners( company_id: int, search: Optional[str] = None, session: Optional[AsyncSession] = None ) -> List[PartnerOption]: """ Get partners (suppliers/customers) for a company. Phase 1: Returns mock data. Phase 2: Returns synced data from SQLite (from Oracle sync). Phase 3: Will fetch live from Oracle. """ # If session is provided, try to get from synced SQLite data if session: # Try to get from SQLite synced data stmt = select(SyncedSupplier).where(SyncedSupplier.company_id == company_id) if search: stmt = stmt.where( (SyncedSupplier.name.ilike(f"%{search}%")) | (SyncedSupplier.fiscal_code.ilike(f"%{search}%")) ) stmt = stmt.order_by(SyncedSupplier.name) # Order alphabetically, no limit for AutoComplete result = await session.execute(stmt) suppliers = result.scalars().all() if suppliers: # Also get local suppliers local_stmt = select(LocalSupplier).where(LocalSupplier.company_id == company_id) if search: local_stmt = local_stmt.where( (LocalSupplier.name.ilike(f"%{search}%")) | (LocalSupplier.fiscal_code.ilike(f"%{search}%")) ) local_stmt = local_stmt.order_by(LocalSupplier.name) # Order alphabetically local_result = await session.execute(local_stmt) local_suppliers = local_result.scalars().all() # Combine both - no IDs needed, just text data for autocomplete partners = [] for s in suppliers: partners.append(PartnerOption( name=s.name, fiscal_code=s.fiscal_code, address=s.address, source="oracle" )) for l in local_suppliers: partners.append(PartnerOption( name=l.name, # No suffix - must match search results fiscal_code=l.fiscal_code, address=l.address, source="local" )) return partners # Fallback to mock data for Phase 1 (when no synced data) mock_partners = [ PartnerOption(name="OMV Petrom", fiscal_code="RO123456", source="mock"), PartnerOption(name="Dedeman", fiscal_code="RO789012", source="mock"), PartnerOption(name="Kaufland", fiscal_code="RO345678", source="mock"), PartnerOption(name="Emag", fiscal_code="RO901234", source="mock"), PartnerOption(name="Altex", fiscal_code="RO567890", source="mock"), ] if search: search_lower = search.lower() mock_partners = [ p for p in mock_partners if search_lower in p.name.lower() or (p.fiscal_code and search_lower in p.fiscal_code.lower()) ] return mock_partners @staticmethod async def get_accounts(company_id: int, prefix: Optional[str] = None) -> List[AccountOption]: """ Get chart of accounts for a company. Phase 1: Returns common expense/income accounts. Phase 2: Will fetch from Oracle PLAN_CONTURI. """ # Common accounts for expenses and receipts accounts = [ # Expense accounts (Class 6) AccountOption(code="6022", name="Cheltuieli cu combustibilii"), AccountOption(code="6024", name="Cheltuieli materiale pentru ambalat"), AccountOption(code="6028", name="Alte cheltuieli cu materiale consumabile"), AccountOption(code="624", name="Cheltuieli cu transportul de bunuri si personal"), AccountOption(code="626", name="Cheltuieli postale si taxe telecomunicatii"), AccountOption(code="628", name="Alte cheltuieli cu serviciile executate de terti"), # VAT AccountOption(code="4426", name="TVA deductibila"), AccountOption(code="4427", name="TVA colectata"), # Cash and Bank (Class 5) AccountOption(code="5311", name="Casa in lei"), AccountOption(code="5121", name="Conturi la banci in lei"), # Income accounts (Class 7) AccountOption(code="7588", name="Alte venituri din exploatare"), ] if prefix: accounts = [a for a in accounts if a.code.startswith(prefix)] return accounts @staticmethod async def get_cash_registers( company_id: int, session: Optional[AsyncSession] = None ) -> List[CashRegisterOption]: """ Get cash registers and bank accounts for a company. Phase 1: Returns default options. Phase 2: Returns synced data from SQLite (from Oracle sync). Phase 3: Will fetch live from Oracle NOM_CASE / NOM_BANCI. """ # If session is provided, try to get from synced SQLite data if session: stmt = select(SyncedCashRegister).where(SyncedCashRegister.company_id == company_id) result = await session.execute(stmt) registers = result.scalars().all() if registers: return [ CashRegisterOption(id=r.id, name=r.name, account_code=r.account_code) for r in registers ] # Fallback to default cash registers for Phase 1 return [ CashRegisterOption(id=1, name="Casa principala", account_code="5311"), CashRegisterOption(id=2, name="Cont BCR", account_code="5121"), CashRegisterOption(id=3, name="Cont BRD", account_code="5121"), ] @staticmethod async def get_expense_types() -> List[ExpenseTypeOption]: """ Get predefined expense types with their accounting configuration. """ return [ ExpenseTypeOption( code=et.code, name=et.name, account_code=et.account_code, has_vat=et.has_vat, vat_percent=et.vat_percent, ) for et in EXPENSE_TYPES.values() ] @staticmethod async def get_companies(username: str) -> List[dict]: """ Get companies accessible by user. Phase 1: Returns mock data. Phase 2: Will fetch from shared auth based on user permissions. """ # TODO: Integrate with shared auth to get user's companies return [ {"id": 1, "name": "SC Test SRL", "cui": "RO12345678"}, {"id": 2, "name": "SC Demo SA", "cui": "RO87654321"}, ] # ============ Phase 2 Oracle Integration Methods ============ @staticmethod async def _fetch_partners_oracle(company_id: int, search: Optional[str] = None) -> List[PartnerOption]: """ Fetch partners from Oracle NOM_PARTENERI. Will be implemented in Phase 2. """ # TODO: Implement using shared oracle_pool # Example query: # SELECT ID_PART, DEN_PART, COD_FISCAL # FROM {schema}.NOM_PARTENERI # WHERE DEN_PART LIKE :search raise NotImplementedError("Oracle integration pending - Phase 2") @staticmethod async def _fetch_accounts_oracle(company_id: int, prefix: Optional[str] = None) -> List[AccountOption]: """ Fetch chart of accounts from Oracle PLAN_CONTURI. Will be implemented in Phase 2. """ # TODO: Implement using shared oracle_pool raise NotImplementedError("Oracle integration pending - Phase 2") @staticmethod async def _fetch_cash_registers_oracle(company_id: int) -> List[CashRegisterOption]: """ Fetch cash registers from Oracle NOM_CASE / NOM_BANCI. Will be implemented in Phase 2. """ # TODO: Implement using shared oracle_pool raise NotImplementedError("Oracle integration pending - Phase 2")