feat: fiscal_series, fiscal_certificates, fiscal_documents models + migrations
Ports the three tables that change owner per the design spec (decision #2): FiscalSeries (was tenants.FiscalDocumentSeries), FiscalCertificate, FiscalDocument (both from fiscal.models) — organization_id/branch_id replaced by product_id (FK products) + tenant_ref/branch_ref (opaque strings) per the porte table. sale_id/service_order_id dropped (no Sale concept here). document_model is now a plain String(2) + Python enum, never a Postgres enum (Global Constraints: zero enum PG — the auto's own version of this column was a real PG enum, a documented debt not repeated here). Constraints preserved: UNIQUE chave_acesso, partial-unique cert-vivo-per-(product_id, branch_ref) (product-scoped in addition to the auto's branch_id, since branch_ref is an opaque string two different products could coincidentally share), UNIQUE (product_id, tenant_ref, branch_ref, document_model, serie) on the series. documents.service.allocate_fiscal_number ported verbatim (mechanism + contract): SELECT ... FOR UPDATE + populate_existing=True, no-commit contract (caller commits together with the FiscalDocument insert, Task 5). The next_number regression guard is deliberately deferred to Task 4's PATCH /v1/series endpoint (needs FiscalDocument, which now exists). AST guard (tests/shared/test_for_update_populate_existing.py) ported and adapted to scan src/fiscal_svc/, plus two new self-tests proving the detection logic itself in both directions (flags a missing fix, does not false-positive on a correctly fixed multi-line chain) — the ported guard alone only proves "currently green", not "actually detects". 33 tests green via `make k8s-test` (real-migration round trips + unique constraint violations, N=10 concurrency, identity-map staleness repro, tenancy-scoping not-found across product/tenant_ref/branch_ref).
This commit is contained in:
+1
-1
@@ -13,7 +13,7 @@ from fiscal_svc.core.db import Base
|
||||
# Uncomment as modules gain SQLAlchemy models, so autogenerate can see them
|
||||
# (mirrors auto/backend/alembic/env.py's own convention):
|
||||
from fiscal_svc.tenancy import models as tenancy_models # noqa: F401
|
||||
# from fiscal_svc.documents import models as documents_models # noqa: F401
|
||||
from fiscal_svc.documents import models as documents_models # noqa: F401
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
"""fiscal_series + fiscal_certificates + fiscal_documents (Task 3): the
|
||||
three tables that "change owner" per the design spec (decision #2) --
|
||||
ported from the auto's `fiscal_document_series` (tenants), `fiscal_
|
||||
certificates`/`fiscal_documents` (fiscal), with product_id/tenant_ref/
|
||||
branch_ref replacing organization_id/branch_id (porte table).
|
||||
|
||||
Revision ID: 523235d06bd5
|
||||
Revises: fd0ffe65c993
|
||||
Create Date: 2026-07-22 00:00:00.000000
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "523235d06bd5"
|
||||
down_revision: Union[str, Sequence[str], None] = "fd0ffe65c993"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"fiscal_series",
|
||||
sa.Column("id", sa.UUID(), nullable=False),
|
||||
sa.Column("product_id", sa.UUID(), nullable=False),
|
||||
sa.Column("tenant_ref", sa.String(length=64), nullable=False),
|
||||
sa.Column("branch_ref", sa.String(length=64), nullable=False),
|
||||
sa.Column("document_model", sa.String(length=2), nullable=False),
|
||||
sa.Column("serie", sa.Integer(), nullable=False),
|
||||
sa.Column("next_number", sa.Integer(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False
|
||||
),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(["product_id"], ["products.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint(
|
||||
"product_id", "tenant_ref", "branch_ref", "document_model", "serie",
|
||||
name="uq_fiscal_series_product_tenant_branch_model_serie",
|
||||
),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"fiscal_certificates",
|
||||
sa.Column("id", sa.UUID(), nullable=False),
|
||||
sa.Column("product_id", sa.UUID(), nullable=False),
|
||||
sa.Column("tenant_ref", sa.String(length=64), nullable=False),
|
||||
sa.Column("branch_ref", sa.String(length=64), nullable=False),
|
||||
sa.Column("cnpj", sa.String(length=14), nullable=False),
|
||||
sa.Column("pfx_encrypted", sa.LargeBinary(), nullable=False),
|
||||
sa.Column("password_encrypted", sa.LargeBinary(), nullable=False),
|
||||
sa.Column("subject_cn", sa.String(length=255), nullable=False),
|
||||
sa.Column("cnpj_certificado", sa.String(length=14), nullable=False),
|
||||
sa.Column("not_valid_before", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("not_valid_after", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False
|
||||
),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(["product_id"], ["products.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_fiscal_certificates_product_branch_live",
|
||||
"fiscal_certificates",
|
||||
["product_id", "branch_ref"],
|
||||
unique=True,
|
||||
postgresql_where=sa.text("deleted_at IS NULL"),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"fiscal_documents",
|
||||
sa.Column("id", sa.UUID(), nullable=False),
|
||||
sa.Column("product_id", sa.UUID(), nullable=False),
|
||||
sa.Column("tenant_ref", sa.String(length=64), nullable=False),
|
||||
sa.Column("branch_ref", sa.String(length=64), nullable=False),
|
||||
sa.Column("series_id", sa.UUID(), nullable=False),
|
||||
sa.Column("document_model", sa.String(length=2), nullable=False),
|
||||
sa.Column("serie", sa.Integer(), nullable=False),
|
||||
sa.Column("numero", sa.Integer(), nullable=False),
|
||||
sa.Column("chave_acesso", sa.String(length=44), nullable=False),
|
||||
sa.Column("codigo_numerico", sa.String(length=8), nullable=False),
|
||||
sa.Column("status", sa.String(length=20), nullable=False),
|
||||
sa.Column("ambiente", sa.String(length=12), nullable=False),
|
||||
sa.Column("xml_assinado", sa.Text(), nullable=False),
|
||||
sa.Column("rejeicao_codigo", sa.String(length=10), nullable=True),
|
||||
sa.Column("rejeicao_motivo", sa.String(length=500), nullable=True),
|
||||
sa.Column("protocolo", sa.String(length=20), nullable=True),
|
||||
sa.Column("autorizada_em", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False
|
||||
),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(["product_id"], ["products.id"]),
|
||||
sa.ForeignKeyConstraint(["series_id"], ["fiscal_series.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("chave_acesso", name="uq_fiscal_documents_chave_acesso"),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_fiscal_documents_product_tenant_branch_status",
|
||||
"fiscal_documents",
|
||||
["product_id", "tenant_ref", "branch_ref", "status"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_fiscal_documents_product_tenant_branch_status", table_name="fiscal_documents")
|
||||
op.drop_table("fiscal_documents")
|
||||
|
||||
op.drop_index("ix_fiscal_certificates_product_branch_live", table_name="fiscal_certificates")
|
||||
op.drop_table("fiscal_certificates")
|
||||
|
||||
op.drop_table("fiscal_series")
|
||||
Reference in New Issue
Block a user