[ADD] l10n_at_payroll_agent: PV-agent review bridge for payout plausibility

PV-agent bridge (D25/M4.1): Odoo stays the system of record; the
standalone, KB-bound knowledge service (pv-agent) reviews plausibility
and returns evidence-backed answers (mode=review, agent API v1). No
back path: the agent never calls Odoo and receives no personal data —
only the minimized, schema-bound review context.

- context builder: projects a computed draft payslip with an
  ATP_PRAMIE input (planned one-off payout) onto the review context
  (facts + computation): gross wage (hr.version.wage), applying
  collective agreement (version.kv_id) and the draft's DG/LST lines
  (SVDG_*/LST_*/FLAFDB/KOMMST/DZ/BVG/WIEN_DAG)
- client (stdlib urllib, no extra dependency): POST /v1/ask with
  X-Request-ID, bearer key from company settings (pv_agent_url /
  pv_agent_api_key, group_hr_payroll_user); neutral UserErrors,
  technical details only in the server log
- review wizard on the draft payslip: agent answer (evidence-backed),
  structured verdict (plausible / implausible / not checkable) and
  checks in the dialog
- security: wizard bound to hr_payroll.group_hr_payroll_user; agent
  service key stored on res.company, never logged; 5 tests
This commit is contained in:
2026-09-17 13:50:18 +02:00
parent ec33dfacf2
commit a848c07e96
11 changed files with 558 additions and 0 deletions
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ============================================================ -->
<!-- D25/M4.1: PV-Agent-Review-Wizard. Die Company-Felder (URL/ -->
<!-- Service-Key) werden im Company-Formular gepflegt und sind auf -->
<!-- group_hr_payroll_user eingeschränkt. Der Agent-Service ist -->
<!-- eigenständig und bleibt autoritativ für die Wissensbasis; -->
<!-- Odoo bleibt autoritativ für die Zahlen (kein Rückpfad). -->
<!-- ============================================================ -->
<record id="res_company_view_form_pv_agent" model="ir.ui.view">
<field name="name">res.company.form.pv.agent</field>
<field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='l10n_at_payroll']" position="inside">
<group string="PV-Agent (Wissensdienst)"
name="l10n_at_pv_agent"
groups="hr_payroll.group_hr_payroll_user">
<field name="pv_agent_url" placeholder="http://127.0.0.1:8080"/>
<field name="pv_agent_api_key" password="True"/>
</group>
</xpath>
</field>
</record>
<record id="pv_agent_review_wizard_form" model="ir.ui.view">
<field name="name">pv.agent.review.wizard.form</field>
<field name="model">pv.agent.review.wizard</field>
<field name="arch" type="xml">
<form string="PV-Agent: Plausibilitätsprüfung">
<group>
<group>
<field name="payslip_id"/>
<field name="question"/>
</group>
<group>
<field name="request_id" invisible="not request_id"/>
<field name="verdict" invisible="not verdict"/>
</group>
</group>
<group string="Agent-Antwort (belegt)" invisible="not answer">
<field name="answer" nolabel="1"/>
</group>
<group string="Prüfpunkte (Agent, KB-belegt)" invisible="not checks">
<field name="checks" nolabel="1"/>
</group>
<footer>
<button string="Prüfung starten" class="btn-primary"
name="action_l10n_at_pv_review" type="object"
icon="fa-search"/>
<button string="Abbrechen" class="btn-secondary"
special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_pv_agent_review_wizard" model="ir.actions.act_window">
<field name="name">PV-Agent Prüfung</field>
<field name="res_model">pv.agent.review.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem id="menu_l10n_at_payroll_agent"
name="PV-Agent Prüfung"
parent="l10n_at_hr_payroll_private.menu_l10n_at_payroll_private_root"
action="action_pv_agent_review_wizard" sequence="50"/>
</odoo>