This repository has been archived on 2026-07-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
shira-hermes/mcp_server/tools/legal_aid_tools.py
T
chaim 206a800762 feat(billing): teach Shira the GreenInvoiceBilling extension surface
Adds two new tool categories — billing (10 tools) and legal_aid (6 tools)
— so Shira can drive the full Green Invoice flow: list unbilled activities,
manage charges (approve/cancel/calculate), build draft invoices from
activities, sync to Green Invoice, plus the legal-aid sub-flow (proforma,
mark-submitted, monthly 320 wrap-up, prepare-email, resubmit rejected).

Wires both registrars into agent_runner with allowed_toolsets gating, and
extends TOOL_RULES so Shira (a) shows draft totals before creating, (b)
treats send_invoice_to_green_invoice as irreversible and demands explicit
confirmation, and (c) never marks a legal-aid proforma as submitted
without an explicit request number from the user.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 16:32:29 +00:00

217 lines
9.7 KiB
Python

"""Legal-aid billing tools: proforma → submission → monthly 320 → email payload."""
from __future__ import annotations
import json
import re
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from mcp_server.espocrm_client import EspoCrmClient
from mcp_server.tools._helpers import ok, fail
MONTH_RE = re.compile(r"^\d{4}-\d{2}$")
def _money(amount) -> str:
try:
return f"{float(amount):,.2f}"
except (TypeError, ValueError):
return f"{amount}"
def register_legal_aid_tools(
tools: dict,
crm: "EspoCrmClient",
case_id: str | None,
user_id: str | None,
context: dict,
):
"""Register Israeli Legal-Aid (סיוע משפטי) billing tools."""
async def create_legal_aid_proforma(chargeIds: list[str]) -> str:
if not chargeIds:
return fail("צריך לפחות מזהה חיוב סיוע משפטי אחד")
try:
result = await crm.post("Invoice/action/createLegalAidProforma", {"chargeIds": chargeIds})
if not result.get("success"):
return fail(f"יצירת חשבון עסקה נכשלה: {result.get('error', 'unknown')}")
return ok(
f"חשבון עסקה (פרופורמה לסיוע משפטי) נוצר: "
f"{result.get('name', '')} (ID: {result.get('id')}, "
f"מספר חיובים: {result.get('chargesCount', len(chargeIds))}, "
f"סכום כולל: {_money(result.get('totalAmount'))}). "
f"כעת יש להגיש ידנית באתר משרד הסיוע ולאחר מכן לקרוא ל-mark_legal_aid_submitted עם מספר הבקשה שהאתר יחזיר."
)
except Exception as e:
return fail(f"שגיאה ביצירת פרופורמה: {e}")
async def mark_legal_aid_submitted(invoiceId: str, legalAidRequestNumber: str) -> str:
if not invoiceId:
return fail("צריך מזהה חשבון עסקה (פרופורמה)")
if not legalAidRequestNumber:
return fail("צריך מספר בקשה מאתר משרד הסיוע (לדוגמה: 2026-04-00123)")
try:
result = await crm.post(
f"Invoice/{invoiceId}/action/markLegalAidSubmitted",
{"legalAidRequestNumber": legalAidRequestNumber},
)
if not result.get("success"):
return fail(f"סימון הגשה נכשל: {result.get('error', 'unknown')}")
return ok(
f"פרופורמה סומנה כמוגשת. מספר בקשה: {result.get('legalAidRequestNumber')}, "
f"זמן הגשה: {result.get('legalAidSubmittedAt')}. "
f"כל החיובים המקושרים עברו לסטטוס Submitted."
)
except Exception as e:
return fail(f"שגיאה בסימון הגשה: {e}")
async def legal_aid_monthly_summary(month: str) -> str:
if not MONTH_RE.match(month or ""):
return fail("פורמט חודש חייב להיות YYYY-MM (לדוגמה 2026-04)")
try:
result = await crm.get(f"Invoice/action/legalAidMonthlyWrapSummary?month={month}")
return ok(json.dumps(result, ensure_ascii=False))
except Exception as e:
return fail(f"שגיאה בשליפת סיכום חודשי: {e}")
async def create_monthly_legal_aid_invoice(month: str) -> str:
if not MONTH_RE.match(month or ""):
return fail("פורמט חודש חייב להיות YYYY-MM (לדוגמה 2026-04)")
try:
result = await crm.post("Invoice/action/createLegalAidMonthlyTaxInvoice", {"month": month})
if not result.get("success"):
return fail(f"יצירת חשבונית חודשית נכשלה: {result.get('error', 'unknown')}")
return ok(
f"חשבונית מס חודשית (320) לחודש {month} נוצרה: "
f"{result.get('name', '')} (ID: {result.get('id')}, "
f"סכום כולל: {_money(result.get('totalAmount'))}). "
f"כעת ניתן להפיק payload לאימייל למשרד המשפטים באמצעות prepare_monthly_320_email."
)
except Exception as e:
return fail(f"שגיאה ביצירת חשבונית חודשית: {e}")
async def prepare_monthly_320_email(invoiceId: str) -> str:
if not invoiceId:
return fail("צריך מזהה חשבונית 320 חודשית")
try:
result = await crm.get(f"Invoice/{invoiceId}/action/prepareMonthly320Email")
payload = result.get("emailPayload") or result
return ok(json.dumps(payload, ensure_ascii=False))
except Exception as e:
return fail(f"שגיאה בהכנת payload לאימייל: {e}")
async def resubmit_charge(chargeId: str, newActivitySubType: str = "") -> str:
if not chargeId:
return fail("צריך מזהה חיוב")
body = {"id": chargeId}
if newActivitySubType:
body["newActivitySubType"] = newActivitySubType
try:
result = await crm.post("Charge/action/resubmit", body)
if not result.get("success"):
return fail("הגשה מחדש נכשלה")
return ok(
f"חיוב הוגש מחדש: ID חדש {result.get('newChargeId')} "
f"(מס׳ {result.get('newChargeNumber')}), "
f"סכום {_money(result.get('newChargeAmount'))}, "
f"סוג פעילות {result.get('newChargeActivitySubType')}"
)
except Exception as e:
return fail(f"שגיאה בהגשה מחדש: {e}")
tools["create_legal_aid_proforma"] = {
"description": (
"Create a legal-aid Proforma (Invoice type 400, חשבון עסקה) bundling Recorded charges. "
"All chargeIds must be legal-aid charges in 'Recorded' status. "
"After this, the secretary submits the proforma manually at the official Legal Aid site and gets a request number — "
"then call mark_legal_aid_submitted with that number."
),
"parameters": {
"type": "object",
"properties": {
"chargeIds": {"type": "array", "items": {"type": "string"}, "description": "Legal-aid charge IDs (status=Recorded)"},
},
"required": ["chargeIds"],
},
"handler": create_legal_aid_proforma,
}
tools["mark_legal_aid_submitted"] = {
"description": (
"Mark a legal-aid Proforma as submitted to the official Legal Aid website. "
"Requires the legalAidRequestNumber returned by the official site (format like '2026-04-00123'). "
"Flips all linked charges from Recorded to Submitted. Do NOT call without an explicit number from the user."
),
"parameters": {
"type": "object",
"properties": {
"invoiceId": {"type": "string", "description": "Proforma Invoice ID (documentType=400)"},
"legalAidRequestNumber": {"type": "string", "description": "Request number from official Legal Aid site"},
},
"required": ["invoiceId", "legalAidRequestNumber"],
},
"handler": mark_legal_aid_submitted,
}
tools["legal_aid_monthly_summary"] = {
"description": "Preview what a monthly 320 legal-aid tax invoice would contain for a given YYYY-MM. Read-only.",
"parameters": {
"type": "object",
"properties": {
"month": {"type": "string", "description": "Month in YYYY-MM format (e.g. 2026-04)"},
},
"required": ["month"],
},
"handler": legal_aid_monthly_summary,
}
tools["create_monthly_legal_aid_invoice"] = {
"description": (
"Create the monthly type-320 tax invoice that aggregates all Paid legal-aid charges for the given YYYY-MM. "
"Flips all aggregated charges from Paid to Billed (terminal). "
"Use legal_aid_monthly_summary first to preview before creating."
),
"parameters": {
"type": "object",
"properties": {
"month": {"type": "string", "description": "Month in YYYY-MM format (e.g. 2026-04)"},
},
"required": ["month"],
},
"handler": create_monthly_legal_aid_invoice,
}
tools["prepare_monthly_320_email"] = {
"description": (
"Build the email payload (recipient, subject, body, attachment ref) for sending a monthly 320 legal-aid invoice "
"to the Justice Ministry. Returns JSON — does NOT actually send. The secretary copy-pastes or pipes via n8n."
),
"parameters": {
"type": "object",
"properties": {
"invoiceId": {"type": "string", "description": "Monthly 320 Invoice ID"},
},
"required": ["invoiceId"],
},
"handler": prepare_monthly_320_email,
}
tools["resubmit_charge"] = {
"description": (
"Clone a Rejected legal-aid charge as a new Recorded charge, optionally with a corrected activitySubType. "
"Use after the Legal Aid office rejects a line in a payment report."
),
"parameters": {
"type": "object",
"properties": {
"chargeId": {"type": "string", "description": "ID of the Rejected charge to clone"},
"newActivitySubType": {"type": "string", "description": "Corrected activity sub-type code (optional)"},
},
"required": ["chargeId"],
},
"handler": resubmit_charge,
}