feat: explain RTC blockers when no R3 candidates are eligible
This commit is contained in:
@@ -705,3 +705,67 @@ def test_rtc_control_page_shows_human_labels_and_a_catalog_column_never_raw_enum
|
||||
assert header in page
|
||||
for raw in ("PENDING</td>", "APPLIED</td>", "ABORTED"):
|
||||
assert raw not in page
|
||||
|
||||
|
||||
def test_rtc_no_r3_candidates_shows_simples_explanation_and_filtered_blocker_link():
|
||||
from central.app.models import BlockedProduct, RtcCandidate, RtcWriteAudit
|
||||
|
||||
client, agent_id, _ = _panel_client()
|
||||
with client._session_factory() as session:
|
||||
installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
|
||||
installation.rtc_write_mode = "ENABLED"
|
||||
session.add(RtcCandidate(tenant_id=installation.tenant_id, installation_id=installation.id, product_code="R4-SUGGESTION", description="Sugestão", ncm="10063021", proposed="000001", cst_ibscbs="000", rule_id="RTC-R4", basis="suggestion only", status="PENDING"))
|
||||
session.add_all([
|
||||
BlockedProduct(
|
||||
tenant_id=installation.tenant_id,
|
||||
installation_id=installation.id,
|
||||
company="Acme",
|
||||
product_code=f"RTC-{index:04d}",
|
||||
description=f"Produto RTC {index}",
|
||||
blocker_type="BLOCKED_FISCAL_DATA",
|
||||
field="RTC",
|
||||
reason_code="CCLASSTRIB_MISSING_UNRESOLVED",
|
||||
evidence_summary={"reason": "RTC-R1:SIMPLES_ORIENTATION_PENDING_OFFICIAL_NT"},
|
||||
input_fingerprint=f"rtc-input-{index}",
|
||||
blocker_fingerprint=f"rtc-blocker-{index}",
|
||||
first_seen_at=datetime.now(timezone.utc),
|
||||
last_seen_at=datetime.now(timezone.utc),
|
||||
)
|
||||
for index in range(521)
|
||||
])
|
||||
session.add(Job(agent_id=agent_id, kind="REPROCESS_ALL", status="COMPLETED", payload={}, result_summary={
|
||||
"fiscal_context": {
|
||||
"rtc_enabled": True,
|
||||
"rtc_supported": True,
|
||||
"rtc_catalog_valid": True,
|
||||
"crt": "1",
|
||||
"legal_effective_context": {"cclasstrib_orientation": "PENDING_OFFICIAL_NT"},
|
||||
"rtc_resolution": {
|
||||
"total": 521, "valid_existing": 0, "ready_to_apply": 0, "suggestion_only": 0,
|
||||
"ambiguous": 0, "insufficient": 521,
|
||||
"by_reason": {"SIMPLES_ORIENTATION_PENDING_OFFICIAL_NT": 521},
|
||||
},
|
||||
},
|
||||
"rtc_auto_apply": {"mode": "ENABLED", "ready_total": 0, "preview": [], "write_attempts": 0, "write_successes": 0},
|
||||
}, created_at=datetime.now(timezone.utc), finished_at=datetime.now(timezone.utc)))
|
||||
session.commit()
|
||||
|
||||
page = client.get(f"/agents/{agent_id}/rtc")
|
||||
|
||||
assert page.status_code == 200
|
||||
assert "Não há produtos elegíveis para preenchimento automático." in page.text
|
||||
assert "521 produtos precisam de atenção fiscal, mas não podem ser preenchidos automaticamente pelas regras atuais." in page.text
|
||||
assert "Esta empresa está no Simples Nacional." in page.text
|
||||
assert "SIMPLES_ORIENTATION_PENDING_OFFICIAL_NT" not in page.text
|
||||
assert "R4-SUGGESTION" not in page.text
|
||||
assert 'href="/blockers?scope=all&agent_id=' + str(agent_id) + '&field=RTC&status=OPEN"' in page.text
|
||||
assert "Nenhum candidato neste filtro." in page.text
|
||||
|
||||
blockers = client.get(f"/blockers?scope=all&agent_id={agent_id}&field=RTC&status=OPEN")
|
||||
assert blockers.status_code == 200
|
||||
assert "521 blockers" in blockers.text
|
||||
with client._session_factory() as session:
|
||||
assert session.scalar(select(Installation.rtc_write_mode).where(Installation.agent_id == agent_id)) == "ENABLED"
|
||||
assert session.query(RtcCandidate).filter_by(installation_id=installation.id).count() == 1
|
||||
assert session.query(RtcCandidate).filter_by(installation_id=installation.id, rule_id="RTC-R4").count() == 1
|
||||
assert session.query(RtcWriteAudit).filter_by(installation_id=installation.id).count() == 0
|
||||
|
||||
@@ -5,7 +5,7 @@ from sqlalchemy.dialects import postgresql
|
||||
|
||||
from central.app.audit import record_audit
|
||||
from central.app.blocked_products import upsert_blocker
|
||||
from central.app.control_plane import _blocker_breakdown_query, blocker_filter_summary, dashboard_summary, jobs_status_summary, list_blockers, list_installations, list_jobs
|
||||
from central.app.control_plane import _blocker_breakdown_query, blocker_filter_summary, dashboard_summary, jobs_status_summary, list_blockers, list_installations, list_jobs, rtc_blocker_summary
|
||||
from central.app.config import settings
|
||||
from central.app.events import record_event
|
||||
from central.app.models import Agent, AgentEnrollment, AgentRelease, AgentSnapshot, AuditEvent, BlockedProduct, Installation, Job, Tenant
|
||||
@@ -238,6 +238,28 @@ def test_jobs_status_kpis_count_active_jobs_by_status_in_one_group():
|
||||
assert jobs_status_summary(session, scope="all")["FAILED"] == 2
|
||||
|
||||
|
||||
def test_rtc_blocker_summary_counts_products_and_simple_reason_server_side():
|
||||
factory = setup_db()
|
||||
with factory() as session:
|
||||
_, agent, installation, _ = _installation(session, 1)
|
||||
for index in range(521):
|
||||
session.add(BlockedProduct(
|
||||
tenant_id=installation.tenant_id, installation_id=installation.id, company="Client 1",
|
||||
product_code=f"RTC-{index:04d}", description="RTC item", blocker_type="BLOCKED_FISCAL_DATA",
|
||||
field="RTC", reason_code="CCLASSTRIB_MISSING_UNRESOLVED",
|
||||
evidence_summary={"reason": "RTC-R1:SIMPLES_ORIENTATION_PENDING_OFFICIAL_NT"},
|
||||
input_fingerprint=f"rtc-{index}", blocker_fingerprint=f"rtc-blocker-{index}",
|
||||
first_seen_at=NOW, last_seen_at=NOW,
|
||||
))
|
||||
session.commit()
|
||||
|
||||
summary = rtc_blocker_summary(session, agent.id)
|
||||
|
||||
assert summary["open_blockers"] == 521
|
||||
assert summary["products_affected"] == 521
|
||||
assert summary["reason_counts"] == {"RTC-R1:SIMPLES_ORIENTATION_PENDING_OFFICIAL_NT": 521}
|
||||
|
||||
|
||||
def test_attention_groups_rank_high_priority_before_limiting_recent_low_priority():
|
||||
factory = setup_db()
|
||||
with factory() as session:
|
||||
|
||||
Reference in New Issue
Block a user