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).
127 lines
5.6 KiB
Python
127 lines
5.6 KiB
Python
"""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")
|