feat: Migrate to ultrathin monolith architecture
Consolidate 3 separate applications (reports-app, data-entry-app, telegram-bot) into a unified
architecture with single backend and frontend:
Backend Changes:
- Unified FastAPI backend at backend/ with modular structure
- Modules: reports, data_entry, telegram in backend/modules/
- Centralized config.py and main.py with all routers registered
- Single worker mode (--workers 1) for Telegram bot compatibility
- Shared Oracle connection pool and JWT authentication
- Unified requirements.txt and environment configuration
Frontend Changes:
- Single Vue.js SPA with module-based routing
- Unified frontend at src/ with modules in src/modules/{reports,data-entry}/
- Shared components and stores in src/shared/
- Error boundaries for module isolation
- Dual API proxy in Vite for module communication
Infrastructure:
- New unified startup scripts: start-prod.sh, start-test.sh, start-backend.sh
- Environment templates: .env.dev.example, .env.test.example, .env.prod.example
- Updated deployment scripts for Windows IIS
- Simplified SSH tunnel management
Documentation:
- Comprehensive CLAUDE.md with architecture overview
- Module-specific docs in docs/{data-entry,telegram}/
- Architecture decision records in docs/ARCHITECTURE-DECISIONS.md
- Deployment guides consolidated in deployment/windows/docs/
This migration reduces complexity, improves maintainability, and enables easier
deployment while maintaining all existing functionality.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
79
backend/modules/reports/models/invoice.py
Normal file
79
backend/modules/reports/models/invoice.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
Modele Pydantic pentru facturi - Compatibile cu aplicația Flask existentă
|
||||
"""
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from datetime import date
|
||||
from typing import Optional, List, Literal
|
||||
from decimal import Decimal
|
||||
|
||||
class InvoiceBase(BaseModel):
|
||||
"""Model de bază pentru factură - mapează exact pe rezultatul query-ului Flask"""
|
||||
nume: str = Field(description="Numele partenerului")
|
||||
nract: int = Field(description="Numărul actului")
|
||||
dataact: Optional[date] = Field(description="Data actului")
|
||||
datascad: Optional[date] = Field(description="Data scadentă")
|
||||
contract: Optional[str] = Field(description="Numărul contractului")
|
||||
cod_fiscal: Optional[str] = Field(description="Codul fiscal")
|
||||
reg_comert: Optional[str] = Field(description="Registrul comerțului")
|
||||
cont: Optional[str] = Field(description="Contul contabil")
|
||||
valuta: str = Field(default="RON", description="Valuta (RON, EUR, USD, etc.)")
|
||||
|
||||
class Invoice(InvoiceBase):
|
||||
"""Model complet pentru factură cu calcule financiare"""
|
||||
totctva: Decimal = Field(description="Total cu TVA", decimal_places=2)
|
||||
achitat: Decimal = Field(description="Suma achitată", decimal_places=2)
|
||||
soldfinal: Decimal = Field(description="Soldul final", decimal_places=2)
|
||||
css_class: Literal["", "invoice-paid", "invoice-overdue"] = Field(
|
||||
default="", description="Clasa CSS pentru stilizare"
|
||||
)
|
||||
|
||||
@validator('css_class', always=True)
|
||||
def determine_css_class(cls, v, values):
|
||||
"""Determină automat clasa CSS bazată pe status factură"""
|
||||
if 'soldfinal' in values and 'datascad' in values:
|
||||
sold = values['soldfinal']
|
||||
data_scad = values['datascad']
|
||||
|
||||
if sold < 1:
|
||||
return 'invoice-paid'
|
||||
elif data_scad and data_scad < date.today() and sold != 0:
|
||||
return 'invoice-overdue'
|
||||
return ''
|
||||
|
||||
class InvoiceFilter(BaseModel):
|
||||
"""Filtru pentru căutarea facturilor"""
|
||||
company: str = Field(description="Codul firmei (schema Oracle)")
|
||||
partner_type: Literal["CLIENTI", "FURNIZORI"] = Field(description="Tipul partenerului")
|
||||
luna: Optional[int] = Field(default=None, ge=1, le=12, description="Luna contabilă (1-12)")
|
||||
an: Optional[int] = Field(default=None, ge=2000, le=2100, description="Anul contabil")
|
||||
partner_name: Optional[str] = Field(description="Filtru după nume")
|
||||
cont: Optional[str] = Field(description="Filtru după cont contabil")
|
||||
only_unpaid: bool = Field(default=True, description="Doar neachitate")
|
||||
min_amount: Optional[Decimal] = Field(description="Suma minimă")
|
||||
max_amount: Optional[Decimal] = Field(description="Suma maximă")
|
||||
page: int = Field(default=1, ge=1, description="Pagina")
|
||||
page_size: int = Field(default=50, ge=1, le=10000000, description="Mărimea paginii")
|
||||
|
||||
class InvoiceListResponse(BaseModel):
|
||||
"""Răspuns pentru lista de facturi"""
|
||||
invoices: List[Invoice]
|
||||
total_count: int
|
||||
filtered_count: int
|
||||
total_amount: Decimal
|
||||
page: int
|
||||
page_size: int
|
||||
has_more: bool
|
||||
accounting_period: Optional[dict] = Field(default=None, description="Perioada contabilă (an, luna)")
|
||||
# Total sold din TOATE facturile filtrate (nu doar pagina curentă)
|
||||
total_sold_all: Decimal = Field(default=Decimal('0.00'), description="Total sold din toate facturile filtrate")
|
||||
|
||||
class InvoiceSummary(BaseModel):
|
||||
"""Rezumat pentru facturi - pentru dashboard"""
|
||||
company: str
|
||||
partner_type: str
|
||||
total_invoices: int
|
||||
total_amount: Decimal
|
||||
paid_amount: Decimal
|
||||
outstanding_amount: Decimal
|
||||
overdue_amount: Decimal
|
||||
overdue_count: int
|
||||
Reference in New Issue
Block a user