lambda-erp 0.8.27__tar.gz → 0.8.29__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/PKG-INFO +1 -1
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/chat.py +31 -25
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/mcp.py +5 -28
- lambda_erp-0.8.29/api/tool_permissions.py +52 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/pyproject.toml +1 -1
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/.gitignore +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/LICENSE +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/README.md +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/__init__.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/attachments.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/auth.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/bootstrap.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/demo_limits.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/deps.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/errors.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/list_values.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/main.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/oauth.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/pdf.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/providers.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/remarks_md.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/__init__.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/accounting.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/actions.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/admin.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/analytics.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/availability.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/bank_reconciliation.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/bank_statements.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/chat_api.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/documents.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/masters.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/proposals.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/reports.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/routers/setup.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/services.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/templates/document.html +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/api/templates/proposal.html +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/docs/agents/README.md +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/frontend/LICENSE +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/frontend/README.md +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/frontend/src/api/client.ts +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/__init__.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/__init__.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/bank_account.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/bank_reconciliation.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/bank_statement_import.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/bank_transaction.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/budget.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/camt.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/chart_of_accounts.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/general_ledger.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/journal_entry.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/payment_entry.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/pos_invoice.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/purchase_invoice.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/revaluation.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/sales_invoice.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/setup/__init__.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/setup/engine.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/setup/pack.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/setup/packs/ch.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/setup/packs/generic.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/setup/profiles.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/setup/spine.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/accounting/subscription.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/assets/__init__.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/assets/asset.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/assets/reservation.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/buying/__init__.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/buying/purchase_order.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/controllers/__init__.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/controllers/currency.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/controllers/defaults.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/controllers/pricing_rule.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/controllers/taxes_and_totals.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/database.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/exceptions.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/hooks.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/model.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/selling/__init__.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/selling/proposal.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/selling/quotation.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/selling/sales_order.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/simulation.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/stock/__init__.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/stock/delivery_note.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/stock/purchase_receipt.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/stock/stock_entry.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/stock/stock_ledger.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/lambda_erp/utils.py +0 -0
- {lambda_erp-0.8.27 → lambda_erp-0.8.29}/terraform/README.md +0 -0
|
@@ -27,6 +27,7 @@ from fastapi import APIRouter, Depends as _Depends, HTTPException, WebSocket, We
|
|
|
27
27
|
from openai import OpenAI
|
|
28
28
|
|
|
29
29
|
from api import services
|
|
30
|
+
from api.tool_permissions import tool_allowed, tool_permission_error
|
|
30
31
|
from api.demo_limits import (
|
|
31
32
|
demo_call_reserve_usd,
|
|
32
33
|
demo_max_completion_tokens,
|
|
@@ -899,17 +900,16 @@ TOOLS = [
|
|
|
899
900
|
"type": "function",
|
|
900
901
|
"function": {
|
|
901
902
|
"name": "reconcile_bank_transaction",
|
|
902
|
-
"description": "Reconcile imported bank transactions after explicit user confirmation. mode=invoice_payment creates and submits a Payment Entry allocated to one or more invoices; mode=existing_voucher links an already-submitted Payment Entry or Journal Entry with an exactly matching bank leg
|
|
903
|
+
"description": "Reconcile imported bank transactions after explicit user confirmation. Always pass bank_transactions: use a one-item list for an individual reconciliation and the complete list returned by an existing_voucher_group suggestion for a grouped reconciliation. mode=invoice_payment creates and submits a Payment Entry allocated to one or more invoices; mode=existing_voucher links an already-submitted Payment Entry or Journal Entry with an exactly matching bank leg, atomically for a group; mode=journal creates and submits a two-sided Journal Entry against a non-bank, non-AR/AP account. invoice_payment and journal accept exactly one bank transaction. This posts or links real accounting data. Never call in the same turn as the first proposal; confirmed must reflect a later explicit confirmation.",
|
|
903
904
|
"parameters": {
|
|
904
905
|
"type": "object",
|
|
905
906
|
"properties": {
|
|
906
|
-
"bank_transaction": {"type": "string"},
|
|
907
907
|
"bank_transactions": {
|
|
908
908
|
"type": "array",
|
|
909
909
|
"items": {"type": "string"},
|
|
910
|
-
"minItems":
|
|
910
|
+
"minItems": 1,
|
|
911
911
|
"maxItems": 100,
|
|
912
|
-
"description": "
|
|
912
|
+
"description": "Bank Transaction names to reconcile: exactly one for an individual match/payment/journal, or the complete exact group returned by the suggestion.",
|
|
913
913
|
},
|
|
914
914
|
"mode": {"type": "string", "enum": ["invoice_payment", "existing_voucher", "journal"]},
|
|
915
915
|
"allocations": {
|
|
@@ -931,7 +931,7 @@ TOOLS = [
|
|
|
931
931
|
"remarks": {"type": "string"},
|
|
932
932
|
"confirmed": {"type": "boolean"},
|
|
933
933
|
},
|
|
934
|
-
"required": ["
|
|
934
|
+
"required": ["bank_transactions", "mode", "confirmed"],
|
|
935
935
|
},
|
|
936
936
|
},
|
|
937
937
|
},
|
|
@@ -1439,10 +1439,12 @@ def build_tools(user_info: dict | None = None):
|
|
|
1439
1439
|
role = user_info.get("role")
|
|
1440
1440
|
action_tools = [
|
|
1441
1441
|
tool for tool in action_tools
|
|
1442
|
-
if
|
|
1442
|
+
if tool_allowed(tool["function"]["name"], role)
|
|
1443
1443
|
]
|
|
1444
|
-
|
|
1445
|
-
|
|
1444
|
+
hidden_tools = {
|
|
1445
|
+
tool["function"]["name"] for tool in TOOLS
|
|
1446
|
+
if not tool_allowed(tool["function"]["name"], role)
|
|
1447
|
+
}
|
|
1446
1448
|
if not extra_docs and not extra_masters and not action_tools and not hidden_tools:
|
|
1447
1449
|
return TOOLS
|
|
1448
1450
|
tools = copy.deepcopy(TOOLS)
|
|
@@ -2083,35 +2085,32 @@ def _handle_reconcile_bank_transaction(args, user_info: dict | None = None):
|
|
|
2083
2085
|
)
|
|
2084
2086
|
}
|
|
2085
2087
|
from lambda_erp.accounting.bank_reconciliation import (
|
|
2086
|
-
reconcile_with_existing_voucher,
|
|
2087
2088
|
reconcile_with_existing_voucher_group,
|
|
2088
2089
|
reconcile_with_journal,
|
|
2089
2090
|
reconcile_with_payment,
|
|
2090
2091
|
)
|
|
2091
2092
|
user = (user_info or {}).get("name")
|
|
2092
2093
|
mode = args.get("mode")
|
|
2094
|
+
bank_transactions = args.get("bank_transactions")
|
|
2095
|
+
if not isinstance(bank_transactions, list) or not bank_transactions:
|
|
2096
|
+
return {"error": "bank_transactions must contain at least one Bank Transaction name"}
|
|
2097
|
+
if mode in {"invoice_payment", "journal"} and len(bank_transactions) != 1:
|
|
2098
|
+
return {"error": f"mode={mode} requires exactly one bank transaction"}
|
|
2099
|
+
bank_transaction = bank_transactions[0]
|
|
2093
2100
|
try:
|
|
2094
2101
|
if mode == "invoice_payment":
|
|
2095
2102
|
return reconcile_with_payment(
|
|
2096
|
-
|
|
2103
|
+
bank_transaction, args.get("allocations") or [],
|
|
2097
2104
|
conversion_rate=args.get("conversion_rate"), user=user, confirmed=True,
|
|
2098
2105
|
)
|
|
2099
2106
|
if mode == "existing_voucher":
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
if args.get("bank_transaction") not in group:
|
|
2103
|
-
return {"error": "bank_transactions must include bank_transaction"}
|
|
2104
|
-
return reconcile_with_existing_voucher_group(
|
|
2105
|
-
group, args.get("voucher_type"), args.get("voucher_no"),
|
|
2106
|
-
user=user, confirmed=True,
|
|
2107
|
-
)
|
|
2108
|
-
return reconcile_with_existing_voucher(
|
|
2109
|
-
args.get("bank_transaction"), args.get("voucher_type"), args.get("voucher_no"),
|
|
2107
|
+
return reconcile_with_existing_voucher_group(
|
|
2108
|
+
bank_transactions, args.get("voucher_type"), args.get("voucher_no"),
|
|
2110
2109
|
user=user, confirmed=True,
|
|
2111
2110
|
)
|
|
2112
2111
|
if mode == "journal":
|
|
2113
2112
|
return reconcile_with_journal(
|
|
2114
|
-
|
|
2113
|
+
bank_transaction, args.get("counterparty_account"),
|
|
2115
2114
|
conversion_rate=args.get("conversion_rate"), remarks=args.get("remarks"),
|
|
2116
2115
|
user=user, confirmed=True,
|
|
2117
2116
|
)
|
|
@@ -2573,8 +2572,9 @@ one transaction or every transaction in an exact `existing_voucher_group`,
|
|
|
2573
2572
|
voucher changes the GL and may change invoice outstanding amounts. Ask for explicit confirmation in
|
|
2574
2573
|
a later user turn, then call `reconcile_bank_transaction` with `confirmed=true`. Clearly disclose any
|
|
2575
2574
|
unallocated on-account remainder. Disclose all group members and their total before asking for
|
|
2576
|
-
confirmation
|
|
2577
|
-
|
|
2575
|
+
confirmation. Every reconciliation call uses `bank_transactions`: pass a one-item list for an
|
|
2576
|
+
individual reconciliation; for a group, copy the complete list from the suggestion and never add or
|
|
2577
|
+
omit IDs.
|
|
2578
2578
|
Never match a Bank Transaction directly to an invoice because that
|
|
2579
2579
|
would omit the cash posting. For reversals, first explain whether the voucher was created by the
|
|
2580
2580
|
reconciliation (it will be cancelled and reversed) or merely linked (it will only be unlinked), and
|
|
@@ -3559,7 +3559,8 @@ async def run_thinking_loop(
|
|
|
3559
3559
|
# If GPT is delegating report code-gen to the Anthropic specialist,
|
|
3560
3560
|
# surface the handoff in the UI.
|
|
3561
3561
|
will_delegate_to_code_specialist = (
|
|
3562
|
-
fn_name
|
|
3562
|
+
tool_allowed(fn_name, user_role)
|
|
3563
|
+
and fn_name in ("create_custom_analytics_report", "update_custom_analytics_report")
|
|
3563
3564
|
and not fn_args.get("transform_js")
|
|
3564
3565
|
and not fn_args.get("data_requests")
|
|
3565
3566
|
)
|
|
@@ -3572,7 +3573,12 @@ async def run_thinking_loop(
|
|
|
3572
3573
|
})
|
|
3573
3574
|
|
|
3574
3575
|
handler = tool_handlers.get(fn_name)
|
|
3575
|
-
|
|
3576
|
+
# The model's tool list and prompt are not authorization controls.
|
|
3577
|
+
# Check the caller at execution even for invented or replayed calls.
|
|
3578
|
+
if not tool_allowed(fn_name, user_role):
|
|
3579
|
+
result = tool_permission_error(fn_name, user_role)
|
|
3580
|
+
success = False
|
|
3581
|
+
elif not handler:
|
|
3576
3582
|
result = {"error": f"Unknown tool: {fn_name}"}
|
|
3577
3583
|
success = False
|
|
3578
3584
|
else:
|
|
@@ -25,6 +25,7 @@ from api.auth import get_current_user
|
|
|
25
25
|
from api import services
|
|
26
26
|
from api import chat as chat_mod
|
|
27
27
|
from api.chat import TOOL_HANDLERS, build_tools
|
|
28
|
+
from api.tool_permissions import tool_allowed
|
|
28
29
|
|
|
29
30
|
router = APIRouter(tags=["mcp"])
|
|
30
31
|
|
|
@@ -42,37 +43,10 @@ _EXCLUDE = {
|
|
|
42
43
|
"plan_company_setup",
|
|
43
44
|
"apply_company_setup",
|
|
44
45
|
}
|
|
45
|
-
# Mirror the REST permission model: reads are viewer+, writes are manager+,
|
|
46
|
-
# delete_master is admin-only (the handler also re-checks).
|
|
47
|
-
_WRITE = {
|
|
48
|
-
"create_document", "update_document", "batch_update_documents",
|
|
49
|
-
"submit_document", "cancel_document",
|
|
50
|
-
"discard_document", "convert_document", "create_master", "update_master",
|
|
51
|
-
"reconcile_bank_transaction", "undo_bank_reconciliation",
|
|
52
|
-
}
|
|
53
|
-
_ADMIN = {"delete_master"}
|
|
54
|
-
_MANAGER_ONLY = {
|
|
55
|
-
"list_bank_reconciliation_queue", "suggest_bank_reconciliation",
|
|
56
|
-
"reconcile_bank_transaction", "undo_bank_reconciliation",
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _can_write(role) -> bool:
|
|
61
|
-
return role in ("manager", "admin", "public_manager")
|
|
62
46
|
|
|
63
47
|
|
|
64
48
|
def _allowed(name: str, role) -> bool:
|
|
65
|
-
|
|
66
|
-
return False
|
|
67
|
-
if name in services.REGISTERED_ACTIONS:
|
|
68
|
-
return services.registered_action_allowed(name, role)
|
|
69
|
-
if name in _ADMIN:
|
|
70
|
-
return role == "admin"
|
|
71
|
-
if name in _MANAGER_ONLY:
|
|
72
|
-
return role in ("manager", "admin")
|
|
73
|
-
if name in _WRITE:
|
|
74
|
-
return _can_write(role)
|
|
75
|
-
return True
|
|
49
|
+
return name not in _EXCLUDE and tool_allowed(name, role)
|
|
76
50
|
|
|
77
51
|
|
|
78
52
|
def _require_caller(request: Request) -> dict:
|
|
@@ -99,6 +73,9 @@ def _tools(role) -> list:
|
|
|
99
73
|
|
|
100
74
|
|
|
101
75
|
def _call(name: str, args: dict, user: dict):
|
|
76
|
+
# Preserve MCP's unknown-tool error without dispatching an unclassified tool.
|
|
77
|
+
if name not in TOOL_HANDLERS and name != "delete_master" and name not in services.REGISTERED_ACTIONS:
|
|
78
|
+
raise KeyError(name)
|
|
102
79
|
role = user.get("role")
|
|
103
80
|
if not _allowed(name, role):
|
|
104
81
|
return {"error": f"'{name}' is not available to a {role or 'viewer'} key."}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Shared execution policy for ERP tools exposed through chat and MCP.
|
|
2
|
+
|
|
3
|
+
Every built-in tool must be classified here. Unknown tools and unknown callers
|
|
4
|
+
are denied; registered extension actions carry their own explicit policy.
|
|
5
|
+
Session ownership and other resource checks still belong to their handlers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from api import services
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_READERS = frozenset({"viewer", "manager", "admin", "public_manager"})
|
|
12
|
+
_DOCUMENT_WRITERS = frozenset({"manager", "admin", "public_manager"})
|
|
13
|
+
_MANAGERS = frozenset({"manager", "admin"})
|
|
14
|
+
_ADMINS = frozenset({"admin"})
|
|
15
|
+
|
|
16
|
+
TOOL_ROLES = {
|
|
17
|
+
**dict.fromkeys((
|
|
18
|
+
"list_documents", "get_document_fields", "get_document",
|
|
19
|
+
"get_master_fields", "search_masters", "get_report", "get_current_time",
|
|
20
|
+
"retrieve_chat_history", "list_chat_attachments", "retrieve_chat_attachment",
|
|
21
|
+
"preview_bank_statement_attachments", "query_dataset",
|
|
22
|
+
# Personal report drafts follow the viewer-accessible reporting API.
|
|
23
|
+
# Their handlers enforce ownership; these do not post ERP transactions.
|
|
24
|
+
"create_custom_analytics_report", "get_custom_analytics_report",
|
|
25
|
+
"update_custom_analytics_report",
|
|
26
|
+
), _READERS),
|
|
27
|
+
**dict.fromkeys((
|
|
28
|
+
"create_document", "update_document", "batch_update_documents",
|
|
29
|
+
"submit_document", "cancel_document", "discard_document", "convert_document",
|
|
30
|
+
), _DOCUMENT_WRITERS),
|
|
31
|
+
**dict.fromkeys((
|
|
32
|
+
"create_master", "update_master", "revalue_currencies",
|
|
33
|
+
"import_bank_statement_attachments", "list_bank_reconciliation_queue",
|
|
34
|
+
"suggest_bank_reconciliation", "reconcile_bank_transaction",
|
|
35
|
+
"undo_bank_reconciliation",
|
|
36
|
+
), _MANAGERS),
|
|
37
|
+
**dict.fromkeys((
|
|
38
|
+
"delete_master", "plan_company_setup", "apply_company_setup",
|
|
39
|
+
), _ADMINS),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def tool_allowed(name: str, role: str | None) -> bool:
|
|
44
|
+
if role not in _READERS:
|
|
45
|
+
return False
|
|
46
|
+
if name in TOOL_ROLES:
|
|
47
|
+
return role in TOOL_ROLES[name]
|
|
48
|
+
return services.registered_action_allowed(name, role)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def tool_permission_error(name: str, role: str | None) -> dict:
|
|
52
|
+
return {"error": f"Tool '{name}' is not available to role '{role or 'unauthenticated'}'."}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|