fix(emission): validate Σ vPag against the document total (vNF)
IMPORTANT (F2 review): the service promises to validate structural coherence (sums, required leiaute fields) per design spec decision #3, but the one sum that can genuinely diverge -- pagamento.valor vs the document total -- was unchecked. The lib documents SEFAZ rejects this with 'Valor do Pagamento difere do total' (sowai_fiscal.xml_builder). Adds PagamentoTotalMismatchError, computed in the completeness block (before allocate_fiscal_number) by reusing sowai_fiscal.xml_builder. soma_itens_quantizados -- the SAME function the lib's own _build_total uses for vNF/vProd, avoiding a second source of truth for the same total. Decimal-exact comparison (never float), both sides quantized to cents the same way _build_pag does. Router maps it to 409 pagamento_total_diverge. Test: tests/emission/test_emissao.py:: test_pagamento_divergente_do_total_dos_itens_e_409_e_nao_queima_numero -- pagamento.valor != Σ itens -> 409 pagamento_total_diverge, no fiscal number consumed. The existing golden-backed happy-path tests (already balanced) continue to prove the matching-totals case still succeeds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
72bb089222
commit
0b64602716
@@ -15,6 +15,7 @@ import importlib.resources
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -475,6 +476,45 @@ async def test_dois_tenants_do_mesmo_produto_reusando_branch_ref_tem_certificado
|
||||
assert emit_b.status_code == 201, emit_b.text
|
||||
|
||||
|
||||
# --- FIX 3 (F2 review): Σ vPag vs vNF ---------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pagamento_divergente_do_total_dos_itens_e_409_e_nao_queima_numero(db_session):
|
||||
"""A SEFAZ rejeita `pagamento.valor != vNF` com "Valor do Pagamento
|
||||
difere do total" (`sowai_fiscal.xml_builder` module docstring) -- este
|
||||
serviço promete validar "coerência estrutural (somas)" (spec decisão
|
||||
#3) antes de queimar um número, não só deixar a rejeição acontecer do
|
||||
lado de lá depois de já ter um `FiscalDocument` ASSINADO persistido."""
|
||||
product, key = await _product_and_key(db_session)
|
||||
payload, raw = _payload_from_golden("caso_padrao_intra")
|
||||
valor_correto = Decimal(str(payload["pagamento"]["valor"]))
|
||||
payload["pagamento"]["valor"] = str(valor_correto + Decimal("10.00"))
|
||||
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
await _setup_certificate_and_series(
|
||||
db_session, client, key, tenant_ref="t1", branch_ref="b1",
|
||||
serie=raw["serie"], next_number=raw["numero"],
|
||||
)
|
||||
|
||||
response = await client.post(
|
||||
"/v1/emissoes", json=payload,
|
||||
headers={**_headers(key), "Idempotency-Key": f"idem-{uuid.uuid4().hex}"},
|
||||
)
|
||||
|
||||
assert response.status_code == 409, response.text
|
||||
assert response.json()["detail"]["code"] == "pagamento_total_diverge"
|
||||
|
||||
result = await db_session.execute(
|
||||
select(FiscalSeries).where(FiscalSeries.product_id == product.id, FiscalSeries.tenant_ref == "t1")
|
||||
)
|
||||
series = result.scalar_one()
|
||||
assert series.next_number == raw["numero"], (
|
||||
"o número NÃO pode ter sido queimado por um pagamento divergente do total dos itens"
|
||||
)
|
||||
|
||||
|
||||
# --- prova do outbox --------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user