feat: emission v1 + idempotency (Task 5)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c903a9ce0e
commit
3aae8b67ef
@@ -215,3 +215,42 @@ class FiscalDocument(Base, UUIDPKMixin, TimestampMixin, SoftDeleteMixin):
|
||||
rejeicao_motivo: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
protocolo: Mapped[str | None] = mapped_column(String(20), nullable=True)
|
||||
autorizada_em: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
|
||||
class FiscalIdempotencyKey(Base, UUIDPKMixin, TimestampMixin):
|
||||
"""Task 5: backs the `Idempotency-Key` contract of `POST /v1/emissoes`
|
||||
(design spec decision #6) -- a SEPARATE table rather than a column on
|
||||
`FiscalDocument` (Task 3's table, already shipped/migrated) so this
|
||||
Task never touches that migration. `UNIQUE (product_id,
|
||||
idempotency_key)` is the SAME-transaction outbox partner of `emission.
|
||||
service.emitir_documento`'s number-allocation + document-INSERT commit
|
||||
(`documents.service.allocate_fiscal_number`'s docstring): this row is
|
||||
added to the SAME session, in the SAME commit, as the `FiscalDocument`
|
||||
it points to via `document_id` -- so a rollback (e.g. the idempotency
|
||||
race below, or a signature failure) undoes the allocated number AND the
|
||||
document row AND this key together, never just some of the three.
|
||||
|
||||
The two-layer idempotency pattern this table exists for: (1) a cheap
|
||||
pre-check SELECT before doing any real work (the fast path for a
|
||||
genuine retry); (2) this UNIQUE constraint as the source of truth for
|
||||
the RACE -- two concurrent requests carrying the SAME `Idempotency-Key`
|
||||
can both pass the pre-check (`None`) before either commits; the FIRST
|
||||
to commit wins, the SECOND's commit raises `IntegrityError` here, which
|
||||
`emission.service` catches and translates into a RE-READ of the
|
||||
winner's row (converging both callers on the SAME `FiscalDocument`,
|
||||
never emitting a duplicate NF-e for one logical request).
|
||||
|
||||
No soft-delete mixin: an idempotency key's history is permanent by
|
||||
design -- there is no "un-claim this key" operation."""
|
||||
|
||||
__tablename__ = "fiscal_idempotency_keys"
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"product_id", "idempotency_key",
|
||||
name="uq_fiscal_idempotency_key_product_key",
|
||||
),
|
||||
)
|
||||
|
||||
product_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("products.id"), nullable=False)
|
||||
idempotency_key: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
document_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("fiscal_documents.id"), nullable=False)
|
||||
|
||||
Reference in New Issue
Block a user