mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 16:56:42 +00:00
a848c07e96
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
74 lines
3.0 KiB
Python
74 lines
3.0 KiB
Python
# Part of the odoo-at-payroll project. License: LGPL-3.
|
|
"""PV-Agent-Review-Wizard (D25/M4.1 Pilot): Plausibilitätsprüfung einer
|
|
geplanten einmaligen Auszahlung.
|
|
|
|
Workflow: Draft-Lohnzettel mit ATP_PRAMIE-Eingabe rechnen → Wizard öffnen →
|
|
„Prüfung starten“ → Odoo baut den minimierten Kontext und ruft den Agenten
|
|
(mode=review) → Verdict + belegte Antwort werden im Dialog angezeigt. Odoo
|
|
bleibt autoritativ für Zahlen; der Agent korrigiert nichts stillschweigend.
|
|
"""
|
|
import uuid
|
|
|
|
from odoo import _, fields, models
|
|
|
|
REVIEW_QUESTION_DEFAULT = (
|
|
"Prüfe die geplante Auszahlung gegen die Regeln der Wissensbasis."
|
|
)
|
|
|
|
|
|
class PvAgentReviewWizard(models.TransientModel):
|
|
_name = "pv.agent.review.wizard"
|
|
_description = "PV-Agent: Plausibilitätsprüfung"
|
|
_order = "id desc"
|
|
|
|
payslip_id = fields.Many2one(
|
|
"hr.payslip", string="Lohnzettel (Draft)", required=True,
|
|
domain="[('state', '=', 'draft')]",
|
|
help="Gerechneter Draft-Lohnzettel mit einer ATP_PRAMIE-Eingabe "
|
|
"(geplante Auszahlung).")
|
|
question = fields.Text(
|
|
string="Frage an den Agenten", required=True,
|
|
default=REVIEW_QUESTION_DEFAULT)
|
|
answer = fields.Text(string="Agent-Antwort (belegt)", readonly=True)
|
|
verdict = fields.Selection(
|
|
[("plausible", "Plausibel"),
|
|
("implausible", "Nicht plausibel"),
|
|
("not_checkable", "Nicht prüfbar")],
|
|
string="Verdict", readonly=True)
|
|
checks = fields.Text(string="Prüfpunkte", readonly=True)
|
|
request_id = fields.Char(string="Request-ID", readonly=True)
|
|
|
|
def action_l10n_at_pv_review(self):
|
|
self.ensure_one()
|
|
context = self.env["pv.agent.context.builder"].l10n_at_pv_build_payout_context(
|
|
self.payslip_id)
|
|
request_id = "odoo-" + uuid.uuid4().hex[:12]
|
|
body = self.env["pv.agent.client"].l10n_at_pv_ask_review(
|
|
self.question, context, request_id)
|
|
plausibility = body.get("plausibility") or {}
|
|
self.write({
|
|
"answer": body.get("answer", ""),
|
|
"verdict": plausibility.get("verdict") or "not_checkable",
|
|
"checks": self._l10n_at_pv_render_checks(plausibility),
|
|
"request_id": body.get("request_id") or request_id,
|
|
})
|
|
return {
|
|
"type": "ir.actions.act_window",
|
|
"res_model": self._name,
|
|
"res_id": self.id,
|
|
"view_mode": "form",
|
|
"target": "new",
|
|
"name": _("PV-Agent Prüfung"),
|
|
}
|
|
|
|
@staticmethod
|
|
def _l10n_at_pv_render_checks(plausibility):
|
|
lines = []
|
|
for check in plausibility.get("checks", []):
|
|
label = {"ok": "OK", "warn": "WARN ⚠", "open": "OFFEN"}.get(
|
|
check.get("status"), check.get("status", ""))
|
|
ids = ", ".join(check.get("source_ids", []))
|
|
lines.append(
|
|
f"[{label}] {check.get('aspect', '')} — {check.get('detail', '')}"
|
|
+ (f" ({ids})" if ids else ""))
|
|
return "\n".join(lines) |