Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixAPI/app/services/ai/ai_function_extensions.py

591 lines
24 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
ثبت functionهای تکمیلی AI (فاز گسترش دسترسی به داده‌های کسب‌وکار).
"""
from __future__ import annotations
from typing import Any, Dict, TYPE_CHECKING
from app.services.ai.ai_query_service import (
SUPPORTED_ACTIONS,
SUPPORTED_ENTITIES,
get_business_dashboard_summary,
get_current_fiscal_year_data,
get_warehouse_stock_summary,
list_fiscal_years_data,
query_business_data,
)
from app.services.ai.function_registry import AIRole, AIFunction
if TYPE_CHECKING:
from app.services.ai.function_registry import AIFunctionRegistry
from app.services.ai.ai_tool_query_params import (
COMMON_LIST_QUERY_PROPERTIES as _COMMON_LIST_PROPS,
ai_list_parameters_schema,
)
def register_extended_business_functions(registry: "AIFunctionRegistry") -> None:
create_handler = registry._create_handler # noqa: SLF001
def query_business_data_handler(args: Dict[str, Any], context: Dict[str, Any]) -> Any:
from sqlalchemy.orm import Session
db: Session = context["db"]
user_context = context["user_context"]
business_id = args.get("business_id") or context.get("business_id")
flt = args.get("filters")
if flt is None:
flt = {k: v for k, v in args.items() if k not in ("entity", "action", "record_id", "business_id", "user_id", "filters") and v is not None}
return query_business_data(
db,
int(business_id),
user_context,
entity=str(args["entity"]),
action=str(args.get("action") or "search"),
filters=flt if isinstance(flt, dict) else None,
record_id=args.get("record_id"),
)
registry.register(
AIFunction(
name="query_business_data",
description=(
"ابزار جنریک برای خواندن داده‌های کسب‌وکار. "
f"entityهای مجاز: {', '.join(sorted(SUPPORTED_ENTITIES))}. "
f"action: {', '.join(sorted(SUPPORTED_ACTIONS))}. "
"فیلتر پیشرفته: filters.filters = [{property, operator, value}] — از list_queryable_fields کمک بگیر. "
"برای get، record_id یا id در filters بدهید."
),
parameters_schema={
"type": "object",
"properties": {
"entity": {
"type": "string",
"enum": sorted(SUPPORTED_ENTITIES),
"description": "نوع موجودیت",
},
"action": {
"type": "string",
"enum": sorted(SUPPORTED_ACTIONS),
"description": "search/list برای لیست، get برای جزئیات، count برای تعداد",
},
"record_id": {
"type": "integer",
"description": "شناسه رکورد (برای action=get)",
},
"filters": {
"type": "object",
"description": (
"QueryInfo: take, skip, search, search_fields, sort_by, sort_desc, "
"from_date, to_date, document_type, person_id, "
"و filters (آرایه {property, operator, value} با عملگرهای =, >, <, *, in). "
"ابتدا list_queryable_fields(entity) را ببین."
),
},
},
"required": ["entity"],
},
handler=query_business_data_handler,
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=[],
category="query",
)
)
# --- انبار ---
registry.register(
AIFunction(
name="search_warehouse_documents",
description="جستجوی حواله انبار با QueryInfo (filters, search_fields). شناسه کسب‌وکار خودکار.",
parameters_schema={
"type": "object",
"properties": {
**_COMMON_LIST_PROPS,
"doc_type": {"type": "string", "description": "نوع حواله (اختیاری)"},
"status": {"type": "string", "description": "وضعیت (اختیاری)"},
"warehouse_id": {"type": "integer", "description": "شناسه انبار (اختیاری)"},
},
"required": [],
},
handler=create_handler(_wh_doc_search),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["warehouses.view"],
category="warehouse",
)
)
registry.register(
AIFunction(
name="get_warehouse_document_details",
description="جزئیات یک حواله انبار.",
parameters_schema={
"type": "object",
"properties": {
"warehouse_document_id": {"type": "integer", "description": "شناسه حواله"},
},
"required": ["warehouse_document_id"],
},
handler=create_handler(_wh_doc_get),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["warehouses.view"],
category="warehouse",
)
)
registry.register(
AIFunction(
name="list_warehouses",
description="لیست انبارهای کسب‌وکار.",
parameters_schema={"type": "object", "properties": {}, "required": []},
handler=create_handler(_list_warehouses),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["warehouses.view"],
category="warehouse",
)
)
registry.register(
AIFunction(
name="get_warehouse_stock_summary",
description="خلاصه موجودی انبار (گزارش stock).",
parameters_schema={
"type": "object",
"properties": {
"product_id": {"type": "integer", "description": "فیلتر کالا (اختیاری)"},
"warehouse_id": {"type": "integer", "description": "فیلتر انبار (اختیاری)"},
"as_of_date": {"type": "string", "format": "date", "description": "موجودی در تاریخ (اختیاری)"},
"include_zero": {"type": "boolean", "description": "شامل موجودی صفر"},
},
"required": [],
},
handler=create_handler(_stock_summary),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["inventory.read"],
category="warehouse",
)
)
def create_warehouse_document_handler(args: Dict[str, Any], context: Dict[str, Any]) -> Any:
from app.services.ai.ai_tool_payloads import build_create_warehouse_document_payload
from app.services.warehouse_service import (
create_manual_warehouse_document,
warehouse_document_to_dict,
)
db = context["db"]
business_id = int(args.get("business_id") or context.get("business_id"))
user_id = context["user_context"].get_user_id()
data = build_create_warehouse_document_payload(args)
wh = create_manual_warehouse_document(db, business_id, user_id, data)
return warehouse_document_to_dict(db, wh)
from app.services.ai.ai_tool_payloads import (
CREATE_WAREHOUSE_DOCUMENT_DESCRIPTION,
CREATE_WAREHOUSE_DOCUMENT_PARAMETERS_SCHEMA,
)
registry.register(
AIFunction(
name="create_warehouse_document",
description=CREATE_WAREHOUSE_DOCUMENT_DESCRIPTION,
parameters_schema=CREATE_WAREHOUSE_DOCUMENT_PARAMETERS_SCHEMA,
handler=create_handler(create_warehouse_document_handler),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["warehouses.write"],
category="warehouse",
requires_approval=True,
risk_level="high",
is_readonly=False,
)
)
# --- چک ---
registry.register(
AIFunction(
name="search_checks",
description="جستجوی چک با QueryInfo (filters, search_fields). list_queryable_fields(entity=check).",
parameters_schema={
"type": "object",
"properties": {
**_COMMON_LIST_PROPS,
"person_id": {"type": "integer", "description": "شناسه شخص (اختیاری)"},
},
"required": [],
},
handler=create_handler(_search_checks),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["checks.view"],
category="checks",
)
)
registry.register(
AIFunction(
name="get_check_details",
description="دریافت جزئیات یک چک شامل مبلغ، وضعیت، سررسید و طرف‌حساب.",
parameters_schema={
"type": "object",
"properties": {"check_id": {"type": "integer", "description": "شناسه چک"}},
"required": ["check_id"],
},
handler=create_handler(_get_check),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["checks.view"],
category="checks",
)
)
# --- انتقال / هزینه / سند ---
registry.register(
AIFunction(
name="search_transfers",
description="جستجو در اسناد انتقال وجه بین حساب‌ها.",
parameters_schema={"type": "object", "properties": dict(_COMMON_LIST_PROPS), "required": []},
handler=create_handler(_search_transfers),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["transfers.view"],
category="financial",
)
)
registry.register(
AIFunction(
name="search_expense_income",
description="جستجو در اسناد هزینه و درآمد.",
parameters_schema={
"type": "object",
"properties": {
**_COMMON_LIST_PROPS,
"document_type": {
"type": "string",
"enum": ["expense", "income"],
"description": "نوع سند (اختیاری)",
},
},
"required": [],
},
handler=create_handler(_search_expense_income),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["expenses_income.view"],
category="financial",
)
)
registry.register(
AIFunction(
name="search_documents",
description="جستجو در اسناد حسابداری (manual، receipt، payment و غیره — غیر از فاکتور تخصصی).",
parameters_schema={
"type": "object",
"properties": {
**_COMMON_LIST_PROPS,
"document_type": {"type": "string", "description": "نوع سند (اختیاری)"},
"person_id": {"type": "integer", "description": "شناسه شخص در سطر سند (اختیاری)"},
},
"required": [],
},
handler=create_handler(_search_documents),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["accounting_documents.view"],
category="accounting",
)
)
registry.register(
AIFunction(
name="get_document_details",
description="جزئیات یک سند حسابداری (شامل سطرها).",
parameters_schema={
"type": "object",
"properties": {"document_id": {"type": "integer", "description": "شناسه سند"}},
"required": ["document_id"],
},
handler=create_handler(_get_document),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["accounting_documents.view"],
category="accounting",
)
)
# --- حساب‌های نقد/بانک ---
registry.register(
AIFunction(
name="list_bank_accounts",
description="لیست حساب‌های بانکی با موجودی (سال مالی جاری اگر مشخص نشود).",
parameters_schema={
"type": "object",
"properties": {
"search": {"type": "string", "description": "جستجو (اختیاری)"},
"fiscal_year_id": {"type": "integer", "description": "سال مالی (اختیاری)"},
"take": {"type": "integer"},
"skip": {"type": "integer"},
},
"required": [],
},
handler=create_handler(_list_bank_accounts),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["bank_accounts.view"],
category="financial",
)
)
registry.register(
AIFunction(
name="list_cash_registers",
description="لیست صندوق‌ها با موجودی.",
parameters_schema={
"type": "object",
"properties": {
"search": {"type": "string"},
"fiscal_year_id": {"type": "integer"},
"take": {"type": "integer"},
"skip": {"type": "integer"},
},
"required": [],
},
handler=create_handler(_list_cash_registers),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["cash_registers.view"],
category="financial",
)
)
# --- سال مالی ---
registry.register(
AIFunction(
name="list_fiscal_years",
description="لیست سال‌های مالی کسب‌وکار.",
parameters_schema={"type": "object", "properties": {}, "required": []},
handler=create_handler(_list_fiscal_years),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["fiscal_years.view"],
category="accounting",
)
)
registry.register(
AIFunction(
name="get_current_fiscal_year",
description="سال مالی جاری (فعال) کسب‌وکار.",
parameters_schema={"type": "object", "properties": {}, "required": []},
handler=create_handler(_get_current_fy),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["fiscal_years.view"],
category="accounting",
)
)
# --- داشبورد و گردش اشخاص ---
registry.register(
AIFunction(
name="get_business_dashboard",
description="خلاصه داشبورد: اطلاعات کسب‌وکار، آمار فروش/خرید، فعالیت‌های اخیر.",
parameters_schema={"type": "object", "properties": {}, "required": []},
handler=_dashboard_handler,
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["reports.read"],
category="business",
)
)
registry.register(
AIFunction(
name="get_person_transactions",
description=(
"گزارش معین/گردش حساب یک شخص (مشتری/تامین‌کننده): "
"فاکتور خرید/فروش همراه دریافت و پرداخت. "
"با detail_level=comprehensive ریز اقلام کالا هم برمی‌گردد."
),
parameters_schema={
"type": "object",
"properties": {
**_COMMON_LIST_PROPS,
"person_id": {"type": "integer", "description": "شناسه شخص (توصیه می‌شود)"},
"document_type": {
"type": "string",
"description": "فیلتر نوع سند (مثلاً invoice_purchase, receipt, payment)",
},
"detail_level": {
"type": "string",
"enum": ["summary", "comprehensive"],
"description": "summary=مبلغ کل فاکتور؛ comprehensive=ریز اقلام خرید/فروش",
"default": "comprehensive",
},
},
"required": [],
},
handler=create_handler(_person_transactions),
allowed_roles={AIRole.USER, AIRole.BUSINESS_OWNER, AIRole.OPERATOR, AIRole.ADMIN},
required_permissions=["persons.read"],
category="persons",
)
)
# --- handler implementations (db, business_id, user_id, **kwargs) ---
def _wh_doc_search(db, business_id, user_id, **kwargs):
from app.services.workflow.actions.hesabix_query_actions import (
_search_warehouse_documents_internal,
)
from app.services.ai.ai_query_service import _build_list_query
return _search_warehouse_documents_internal(
db, business_id, _build_list_query(kwargs, entity="warehouse_document")
)
def _wh_doc_get(db, business_id, user_id, warehouse_document_id, **kwargs):
from adapters.db.models.warehouse_document import WarehouseDocument
from app.services.warehouse_service import warehouse_document_to_dict
wh = (
db.query(WarehouseDocument)
.filter(
WarehouseDocument.id == warehouse_document_id,
WarehouseDocument.business_id == business_id,
)
.first()
)
if not wh:
raise ValueError(f"حواله انبار {warehouse_document_id} یافت نشد")
return warehouse_document_to_dict(db, wh)
def _list_warehouses(db, business_id, user_id, **kwargs):
from app.services.warehouse_service import list_warehouses
return list_warehouses(db, business_id)
def _stock_summary(db, business_id, user_id, **kwargs):
return get_warehouse_stock_summary(db, business_id, kwargs)
def _search_checks(db, business_id, user_id, **kwargs):
from app.services.check_service import list_checks
from app.services.ai.ai_query_service import _build_list_query
q = _build_list_query(kwargs, entity="check")
if q.get("search") and not q.get("search_fields"):
q["search_fields"] = [
"check_number",
"sayad_code",
"bank_name",
"branch_name",
"person_name",
]
return list_checks(db, business_id, q)
def _get_check(db, business_id, user_id, check_id, **kwargs):
from app.services.check_service import get_check_by_id
from adapters.db.models.check import Check
chk = db.query(Check).filter(Check.id == check_id, Check.business_id == business_id).first()
if not chk:
raise ValueError(f"چک {check_id} یافت نشد")
return get_check_by_id(db, check_id)
def _search_transfers(db, business_id, user_id, **kwargs):
from app.services.transfer_service import list_transfers
from app.services.ai.ai_query_service import _build_list_query
q = _build_list_query(kwargs, entity="transfer")
if q.get("search") and not q.get("search_fields"):
q["search_fields"] = ["code", "description", "created_by_name"]
return list_transfers(db, business_id, q)
def _search_expense_income(db, business_id, user_id, **kwargs):
from app.services.expense_income_service import list_expense_income
from app.services.ai.ai_query_service import _build_list_query
q = _build_list_query(kwargs, entity="expense_income")
if q.get("search") and not q.get("search_fields"):
q["search_fields"] = ["code", "description", "created_by_name"]
return list_expense_income(db, business_id, q)
def _search_documents(db, business_id, user_id, **kwargs):
from app.services.document_service import list_documents
from app.services.ai.ai_query_service import _build_list_query
return list_documents(db, business_id, _build_list_query(kwargs, entity="document"))
def _get_document(db, business_id, user_id, document_id, **kwargs):
from app.services.document_service import get_document
from adapters.db.models.document import Document
doc = db.query(Document).filter(
Document.id == document_id, Document.business_id == business_id
).first()
if not doc:
raise ValueError(f"سند {document_id} یافت نشد")
data = get_document(db, document_id)
if not data:
raise ValueError(f"سند {document_id} یافت نشد")
return data
def _list_bank_accounts(db, business_id, user_id, **kwargs):
from app.services.bank_account_service import list_bank_accounts
from app.services.ai.ai_query_service import _build_list_query
q = _build_list_query(kwargs)
if q.get("search") and not q.get("search_fields"):
q["search_fields"] = ["code", "name", "branch", "account_number", "owner_name"]
return list_bank_accounts(db, business_id, q)
def _list_cash_registers(db, business_id, user_id, **kwargs):
from app.services.cash_register_service import list_cash_registers
from app.services.ai.ai_query_service import _build_list_query
q = _build_list_query(kwargs)
if q.get("search") and not q.get("search_fields"):
q["search_fields"] = ["code", "name"]
return list_cash_registers(db, business_id, q)
def _list_fiscal_years(db, business_id, user_id, **kwargs):
return list_fiscal_years_data(db, business_id)
def _get_current_fy(db, business_id, user_id, **kwargs):
return get_current_fiscal_year_data(db, business_id)
def _person_transactions(db, business_id, user_id, **kwargs):
from app.services.person_service import get_people_transactions_report
from app.services.ai.ai_query_service import _clamp_pagination, _to_int
q = _clamp_pagination(kwargs)
pid = _to_int(kwargs.get("person_id"))
person_ids = [pid] if pid is not None else None
detail_level = kwargs.get("detail_level") or "comprehensive"
return get_people_transactions_report(
db,
business_id,
fiscal_year_id=_to_int(kwargs.get("fiscal_year_id")),
currency_id=_to_int(kwargs.get("currency_id")),
date_from=kwargs.get("from_date"),
date_to=kwargs.get("to_date"),
person_ids=person_ids,
document_type=kwargs.get("document_type"),
search=kwargs.get("search"),
skip=q["skip"],
take=q["take"],
detail_level=detail_level,
)
def _dashboard_handler(args: Dict[str, Any], context: Dict[str, Any]) -> Any:
db = context["db"]
user_context = context["user_context"]
business_id = args.get("business_id") or context.get("business_id")
return get_business_dashboard_summary(db, int(business_id), user_context)