206a800762
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>
217 lines
9.7 KiB
Python
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,
|
|
}
|