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:
0
backend/modules/reports/models/__init__.py
Normal file
0
backend/modules/reports/models/__init__.py
Normal file
19
backend/modules/reports/models/calendar.py
Normal file
19
backend/modules/reports/models/calendar.py
Normal file
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
Calendar period models for accounting period selector
|
||||
"""
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class CalendarPeriod(BaseModel):
|
||||
"""Model for an accounting period"""
|
||||
an: int # Year
|
||||
luna: int # Month (1-12)
|
||||
display_name: str # Format: "Decembrie 2025"
|
||||
|
||||
|
||||
class CalendarPeriodsResponse(BaseModel):
|
||||
"""Response model for calendar periods list"""
|
||||
periods: List[CalendarPeriod]
|
||||
current_period: Optional[CalendarPeriod] = None # Most recent period
|
||||
total_count: int
|
||||
129
backend/modules/reports/models/dashboard.py
Normal file
129
backend/modules/reports/models/dashboard.py
Normal file
@@ -0,0 +1,129 @@
|
||||
from pydantic import BaseModel
|
||||
from decimal import Decimal
|
||||
from typing import List, Dict, Optional, Any
|
||||
|
||||
class TreasuryAccount(BaseModel):
|
||||
"""Cont de trezorerie (bancă/casă)"""
|
||||
cont: str # 5121, 5124, 5311, 5314
|
||||
nume_cont: str # "Bancă LEI", "Casă VALUTA" etc
|
||||
nume_banca: str # Numele băncii din vbalanta_parteneri.nume
|
||||
sold: Decimal
|
||||
valuta: str
|
||||
|
||||
class TrendData(BaseModel):
|
||||
"""Model pentru datele de trend - MODEL VECHI"""
|
||||
labels: List[str]
|
||||
incasari: List[Decimal]
|
||||
plati: List[Decimal]
|
||||
trezorerie: List[Decimal]
|
||||
incasari_total: Decimal
|
||||
plati_total: Decimal
|
||||
trezorerie_total: Decimal
|
||||
incasari_change: Optional[float] = None
|
||||
plati_change: Optional[float] = None
|
||||
trezorerie_change: Optional[float] = None
|
||||
|
||||
class TrendsResponse(BaseModel):
|
||||
"""Model pentru răspunsul endpoint-ului de trenduri - MODEL NOU"""
|
||||
# Current period data
|
||||
periods: List[str]
|
||||
clienti_facturat: List[float]
|
||||
clienti_incasat: List[float]
|
||||
furnizori_facturat: List[float]
|
||||
furnizori_achitat: List[float]
|
||||
clienti_sold: List[float]
|
||||
furnizori_sold: List[float]
|
||||
trezorerie_sold: Optional[List[float]] = None
|
||||
rata_incasare_clienti: List[float]
|
||||
rata_achitare_furnizori: List[float]
|
||||
|
||||
# Previous period data (for year-over-year comparison in sparklines)
|
||||
previous_periods: Optional[List[str]] = None
|
||||
clienti_facturat_prev: Optional[List[float]] = None
|
||||
clienti_incasat_prev: Optional[List[float]] = None
|
||||
furnizori_facturat_prev: Optional[List[float]] = None
|
||||
furnizori_achitat_prev: Optional[List[float]] = None
|
||||
clienti_sold_prev: Optional[List[float]] = None
|
||||
furnizori_sold_prev: Optional[List[float]] = None
|
||||
trezorerie_sold_prev: Optional[List[float]] = None
|
||||
|
||||
# Metadata and analytics
|
||||
metadata: Dict[str, Any]
|
||||
growth_rates: Optional[Dict[str, float]] = None
|
||||
|
||||
# Cache metadata (optional, for Telegram Bot)
|
||||
cache_hit: Optional[bool] = None
|
||||
response_time_ms: Optional[float] = None
|
||||
cache_source: Optional[str] = None
|
||||
|
||||
class DashboardSummary(BaseModel):
|
||||
"""Model pentru toate datele dashboard-ului"""
|
||||
# CLIENȚI - statistici existente
|
||||
clienti_total_facturat: Decimal # precdeb + debit (conturi 4111, 461)
|
||||
clienti_total_incasat: Decimal # preccred + credit (conturi 4111, 461)
|
||||
clienti_avansuri: Decimal # sold 419 (pasiv): credit - debit
|
||||
clienti_sold_total: Decimal # (facturat - incasat) - avansuri
|
||||
clienti_sold_restant: Decimal # sold cu datascad < azi
|
||||
|
||||
# CLIENȚI - NOI câmpuri pentru sold în termen
|
||||
clienti_sold_in_termen: Decimal # sold cu datascad >= azi
|
||||
|
||||
# CLIENȚI - NOI detalieri restanțe (sold cu datascad < azi)
|
||||
clienti_restant_7: Decimal # restant 1-7 zile
|
||||
clienti_restant_14: Decimal # restant 8-14 zile
|
||||
clienti_restant_30: Decimal # restant 15-30 zile
|
||||
clienti_restant_60: Decimal # restant 31-60 zile
|
||||
clienti_restant_90: Decimal # restant 61-90 zile
|
||||
clienti_restant_90plus: Decimal # restant 90+ zile
|
||||
|
||||
# CLIENȚI - NOI detalieri scadențe (sold cu datascad >= azi)
|
||||
clienti_scadent_7: Decimal # scadent în 1-7 zile
|
||||
clienti_scadent_14: Decimal # scadent în 8-14 zile
|
||||
clienti_scadent_30: Decimal # scadent în 15-30 zile
|
||||
clienti_scadent_60: Decimal # scadent în 31-60 zile
|
||||
clienti_scadent_90: Decimal # scadent în 61-90 zile
|
||||
clienti_scadent_90plus: Decimal # scadent în 90+ zile
|
||||
|
||||
# FURNIZORI - statistici existente
|
||||
furnizori_total_facturat: Decimal # preccred + credit (conturi 401, 404, 462)
|
||||
furnizori_total_achitat: Decimal # precdeb + debit (conturi 401, 404, 462)
|
||||
furnizori_avansuri: Decimal # sold 409x (activ): debit - credit
|
||||
furnizori_sold_total: Decimal # (facturat - achitat) - avansuri
|
||||
furnizori_sold_restant: Decimal # sold cu datascad < azi
|
||||
|
||||
# FURNIZORI - NOI câmpuri pentru sold în termen
|
||||
furnizori_sold_in_termen: Decimal # sold cu datascad >= azi
|
||||
|
||||
# FURNIZORI - NOI detalieri restanțe (sold cu datascad < azi)
|
||||
furnizori_restant_7: Decimal # restant 1-7 zile
|
||||
furnizori_restant_14: Decimal # restant 8-14 zile
|
||||
furnizori_restant_30: Decimal # restant 15-30 zile
|
||||
furnizori_restant_60: Decimal # restant 31-60 zile
|
||||
furnizori_restant_90: Decimal # restant 61-90 zile
|
||||
furnizori_restant_90plus: Decimal # restant 90+ zile
|
||||
|
||||
# FURNIZORI - NOI detalieri scadențe (sold cu datascad >= azi)
|
||||
furnizori_scadent_7: Decimal # scadent în 1-7 zile
|
||||
furnizori_scadent_14: Decimal # scadent în 8-14 zile
|
||||
furnizori_scadent_30: Decimal # scadent în 15-30 zile
|
||||
furnizori_scadent_60: Decimal # scadent în 31-60 zile
|
||||
furnizori_scadent_90: Decimal # scadent în 61-90 zile
|
||||
furnizori_scadent_90plus: Decimal # scadent în 90+ zile
|
||||
|
||||
# TREZORERIE - existente
|
||||
treasury_accounts: List[TreasuryAccount]
|
||||
treasury_totals_by_currency: Dict[str, Decimal]
|
||||
|
||||
# DATE SUPLIMENTARE pentru trend analysis
|
||||
clienti_facturat_luna_anterioara: Optional[Decimal] = Decimal('0')
|
||||
furnizori_facturat_luna_anterioara: Optional[Decimal] = Decimal('0')
|
||||
clienti_facturat_an_curent: Optional[Decimal] = Decimal('0')
|
||||
clienti_facturat_an_anterior: Optional[Decimal] = Decimal('0')
|
||||
furnizori_facturat_an_curent: Optional[Decimal] = Decimal('0')
|
||||
furnizori_facturat_an_anterior: Optional[Decimal] = Decimal('0')
|
||||
|
||||
# SOLDURI TVA
|
||||
tva_plata_precedent: Decimal = Decimal('0')
|
||||
tva_recuperat_precedent: Decimal = Decimal('0')
|
||||
tva_plata_curent: Decimal = Decimal('0')
|
||||
tva_recuperat_curent: Decimal = Decimal('0')
|
||||
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
|
||||
52
backend/modules/reports/models/treasury.py
Normal file
52
backend/modules/reports/models/treasury.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from pydantic import BaseModel
|
||||
from decimal import Decimal
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
|
||||
class AccountingPeriod(BaseModel):
|
||||
"""Model pentru perioada contabilă"""
|
||||
an: Optional[int] = None
|
||||
luna: Optional[int] = None
|
||||
|
||||
class BankCashRegister(BaseModel):
|
||||
"""Model pentru Registrul de Casă și Bancă"""
|
||||
nume: str
|
||||
nract: Optional[int] = None
|
||||
dataact: Optional[datetime] = None
|
||||
nume_cont_bancar: str # din vbalanta_parteneri.nume
|
||||
incasari: Decimal
|
||||
plati: Decimal
|
||||
sold: Decimal
|
||||
valuta: Optional[str] = None
|
||||
tip_registru: str # "BANCA LEI", "CASA VALUTA" etc
|
||||
explicatia: str
|
||||
|
||||
class RegisterFilter(BaseModel):
|
||||
"""Filtre pentru registrul de casă și bancă"""
|
||||
company: str
|
||||
register_type: Optional[str] = None # BANCA_LEI, BANCA_VALUTA, CASA_LEI, CASA_VALUTA sau None pentru toate
|
||||
luna: Optional[int] = None # Luna contabilă (1-12) pentru PACK_SESIUNE
|
||||
an: Optional[int] = None # Anul contabil pentru PACK_SESIUNE
|
||||
date_from: Optional[datetime] = None
|
||||
date_to: Optional[datetime] = None
|
||||
partner_name: Optional[str] = None
|
||||
bank_account: Optional[str] = None # Filter for specific bank/cash account (bancasa)
|
||||
page: int = 1
|
||||
page_size: int = 50
|
||||
|
||||
class RegisterListResponse(BaseModel):
|
||||
"""Răspuns pentru lista din registru"""
|
||||
registers: List[BankCashRegister]
|
||||
total_count: int
|
||||
filtered_count: int
|
||||
total_incasari: Decimal
|
||||
total_plati: Decimal
|
||||
page: int
|
||||
page_size: int
|
||||
has_more: bool
|
||||
accounting_period: Optional[AccountingPeriod] = None
|
||||
# Totaluri din TOATE înregistrările filtrate (nu doar pagina curentă)
|
||||
sold_precedent_all: Decimal = Decimal('0.00')
|
||||
total_incasari_all: Decimal = Decimal('0.00')
|
||||
total_plati_all: Decimal = Decimal('0.00')
|
||||
sold_final_all: Decimal = Decimal('0.00')
|
||||
102
backend/modules/reports/models/trial_balance.py
Normal file
102
backend/modules/reports/models/trial_balance.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
Pydantic models for Trial Balance (Balanță de Verificare)
|
||||
Maps to Oracle VBAL VIEW (exists in each company schema)
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List
|
||||
from decimal import Decimal
|
||||
|
||||
class TrialBalanceItem(BaseModel):
|
||||
"""
|
||||
Individual trial balance record from VBAL VIEW
|
||||
Real structure from Oracle:
|
||||
- CONT: account number
|
||||
- DENUMIRE: account description
|
||||
- PRECDEB/PRECCRED: previous balance debit/credit
|
||||
- RULDEB/RULCRED: monthly movement debit/credit
|
||||
- SOLDDEB/SOLDCRED: final balance debit/credit
|
||||
"""
|
||||
cont: str = Field(description="Număr cont contabil (CONT)")
|
||||
denumire: Optional[str] = Field(default="", description="Denumire cont (DENUMIRE)")
|
||||
sold_precedent_debit: Decimal = Field(description="Sold precedent debit (PRECDEB)", decimal_places=2)
|
||||
sold_precedent_credit: Decimal = Field(description="Sold precedent credit (PRECCRED)", decimal_places=2)
|
||||
rulaj_lunar_debit: Decimal = Field(description="Rulaj lunar debit (RULDEB)", decimal_places=2)
|
||||
rulaj_lunar_credit: Decimal = Field(description="Rulaj lunar credit (RULCRED)", decimal_places=2)
|
||||
sold_final_debit: Decimal = Field(description="Sold final debit (SOLDDEB)", decimal_places=2)
|
||||
sold_final_credit: Decimal = Field(description="Sold final credit (SOLDCRED)", decimal_places=2)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TrialBalanceFilters(BaseModel):
|
||||
"""
|
||||
Filters applied to trial balance data
|
||||
"""
|
||||
luna: int = Field(description="Luna (1-12)")
|
||||
an: int = Field(description="An")
|
||||
cont_filter: Optional[str] = Field(default=None, description="Filtru număr cont (partial match)")
|
||||
denumire_filter: Optional[str] = Field(default=None, description="Filtru denumire cont (partial match, case-insensitive)")
|
||||
|
||||
|
||||
class TrialBalancePagination(BaseModel):
|
||||
"""
|
||||
Pagination metadata
|
||||
"""
|
||||
total_items: int = Field(description="Total number of items")
|
||||
total_pages: int = Field(description="Total number of pages")
|
||||
current_page: int = Field(description="Current page number")
|
||||
page_size: int = Field(description="Items per page")
|
||||
|
||||
|
||||
class TrialBalanceTotals(BaseModel):
|
||||
"""
|
||||
Totals for all 6 columns from all filtered records (not just current page)
|
||||
"""
|
||||
total_sold_precedent_debit: Decimal = Decimal('0.00')
|
||||
total_sold_precedent_credit: Decimal = Decimal('0.00')
|
||||
total_rulaj_lunar_debit: Decimal = Decimal('0.00')
|
||||
total_rulaj_lunar_credit: Decimal = Decimal('0.00')
|
||||
total_sold_final_debit: Decimal = Decimal('0.00')
|
||||
total_sold_final_credit: Decimal = Decimal('0.00')
|
||||
|
||||
|
||||
class TrialBalanceResponse(BaseModel):
|
||||
"""
|
||||
Complete response for trial balance endpoint
|
||||
"""
|
||||
success: bool = Field(default=True, description="Request success status")
|
||||
data: dict = Field(description="Trial balance data with items, pagination, and filters")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"success": True,
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"cont": "4111",
|
||||
"dcont": "Furnizori interni",
|
||||
"sold_precedent_debit": 0.00,
|
||||
"sold_precedent_credit": 15000.00,
|
||||
"rulaj_lunar_debit": 5000.00,
|
||||
"rulaj_lunar_credit": 8000.00,
|
||||
"sold_final_debit": 0.00,
|
||||
"sold_final_credit": 18000.00
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"total_items": 150,
|
||||
"total_pages": 3,
|
||||
"current_page": 1,
|
||||
"page_size": 50
|
||||
},
|
||||
"filters_applied": {
|
||||
"luna": 11,
|
||||
"an": 2025,
|
||||
"cont_filter": None,
|
||||
"denumire_filter": "furnizori"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user