fix(backend): sync push error handling + validation

- apply_push now uses PRAGMA table_info() to get valid column names per
  table and filters incoming data to only known columns, preventing
  "no such column" SQLite errors from frontend-only fields like oracle_id
- Wrap each operation in try/except so one bad op doesn't abort the
  whole batch; errors are returned in the conflicts list instead of 500
- Add server_default to all NOT NULL float/int columns so raw SQL
  INSERT OR REPLACE without those fields still succeeds
- Align DB models with frontend schema.js:
  - orders: add nr_comanda, client_nume, client_telefon, nr_auto,
    marca_denumire, model_denumire, created_by
  - order_lines: add norma_id, mecanic_id, ordine
  - vehicles: add serie_sasiu, client_cod_fiscal (keep vin, client_cui
    for REST API compat)
  - catalog_*: rename nume → denumire to match frontend schema
  - catalog_norme: align fields (cod, denumire, ore_normate)
  - invoices: add serie_factura, modalitate_plata, client_cod_fiscal,
    nr_auto, total_fara_tva, tva, total_general; keep total for compat
  - appointments: add client_nume, client_telefon, data_ora, durata_minute,
    status, order_id
  - mecanici: add user_id, prenume, activ
- Fix seed.py to use denumire= instead of nome= after catalog rename
- Add 3 new sync push tests covering order insert with frontend fields,
  unknown column filtering, and order_line with norma_id/mecanic_id/ordine

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 18:45:31 +02:00
parent 9aef3d6933
commit 1e96db4d91
10 changed files with 321 additions and 58 deletions

View File

@@ -1,4 +1,4 @@
from sqlalchemy import Float, String, Text
from sqlalchemy import Float, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base, UUIDMixin, TenantMixin, TimestampMixin
@@ -6,39 +6,41 @@ from app.db.base import Base, UUIDMixin, TenantMixin, TimestampMixin
class CatalogMarca(Base, UUIDMixin, TenantMixin, TimestampMixin):
__tablename__ = "catalog_marci"
nume: Mapped[str] = mapped_column(String(100))
denumire: Mapped[str] = mapped_column(String(100))
activ: Mapped[int] = mapped_column(Integer, default=1, server_default="1")
class CatalogModel(Base, UUIDMixin, TimestampMixin):
__tablename__ = "catalog_modele"
marca_id: Mapped[str] = mapped_column(String(36), index=True)
nume: Mapped[str] = mapped_column(String(100))
denumire: Mapped[str] = mapped_column(String(100))
class CatalogAnsamblu(Base, UUIDMixin, TenantMixin, TimestampMixin):
__tablename__ = "catalog_ansamble"
nume: Mapped[str] = mapped_column(String(100))
denumire: Mapped[str] = mapped_column(String(100))
class CatalogNorma(Base, UUIDMixin, TenantMixin, TimestampMixin):
__tablename__ = "catalog_norme"
ansamblu_id: Mapped[str] = mapped_column(String(36), index=True)
descriere: Mapped[str] = mapped_column(Text)
ore: Mapped[float] = mapped_column(Float, default=0)
cod: Mapped[str | None] = mapped_column(String(50))
denumire: Mapped[str] = mapped_column(Text)
ore_normate: Mapped[float] = mapped_column(Float, default=0, server_default="0")
ansamblu_id: Mapped[str | None] = mapped_column(String(36), index=True)
class CatalogPret(Base, UUIDMixin, TenantMixin, TimestampMixin):
__tablename__ = "catalog_preturi"
denumire: Mapped[str] = mapped_column(String(200))
pret: Mapped[float] = mapped_column(Float, default=0)
pret: Mapped[float] = mapped_column(Float, default=0, server_default="0")
um: Mapped[str] = mapped_column(String(10))
class CatalogTipDeviz(Base, UUIDMixin, TenantMixin, TimestampMixin):
__tablename__ = "catalog_tipuri_deviz"
nume: Mapped[str] = mapped_column(String(100))
denumire: Mapped[str] = mapped_column(String(100))
class CatalogTipMotor(Base, UUIDMixin, TenantMixin, TimestampMixin):
__tablename__ = "catalog_tipuri_motoare"
nume: Mapped[str] = mapped_column(String(50))
denumire: Mapped[str] = mapped_column(String(50))