146 lines
4.7 KiB
Python
146 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
|
|
def _metadata_error(error: Exception) -> dict[str, str]:
|
|
message = str(error)
|
|
if "403" in message or "No permissions" in message:
|
|
return {
|
|
"code": "metadata_permission_required",
|
|
"message": message,
|
|
"hint": (
|
|
"Grant the agent role read-only permission for "
|
|
"`data-modeling collections fields list`, then retry crm_schema(include_fields=true)."
|
|
),
|
|
}
|
|
return {"code": "metadata_error", "message": message, "hint": "Check NocoBase metadata API reachability and ACL."}
|
|
|
|
|
|
def _field_list(value: Any) -> list[dict[str, Any]]:
|
|
return [item for item in value or [] if isinstance(item, dict)]
|
|
|
|
|
|
def _field_names(fields: list[dict[str, Any]]) -> list[str]:
|
|
return [str(field["name"]) for field in fields if field.get("name")]
|
|
|
|
|
|
def _field_path(field: str | list[str]) -> list[str]:
|
|
if isinstance(field, str):
|
|
return [part for part in field.split(".") if part]
|
|
return [str(part) for part in field if str(part)]
|
|
|
|
|
|
def _field_text(field: dict[str, Any]) -> str:
|
|
pieces = [
|
|
str(field.get("name") or ""),
|
|
str(field.get("title") or ""),
|
|
str(field.get("interface") or ""),
|
|
str(field.get("type") or ""),
|
|
]
|
|
ui_schema = field.get("uiSchema")
|
|
if isinstance(ui_schema, dict):
|
|
pieces.append(str(ui_schema.get("title") or ""))
|
|
return " ".join(pieces).lower()
|
|
|
|
|
|
def _find_named_field(fields: list[dict[str, Any]], names: list[str]) -> str | None:
|
|
available = {str(field.get("name")) for field in fields if field.get("name")}
|
|
for name in names:
|
|
if name in available:
|
|
return name
|
|
return None
|
|
|
|
|
|
def _existing_field_names(fields: list[dict[str, Any]], names: list[str]) -> list[str]:
|
|
available = {str(field.get("name")) for field in fields if field.get("name")}
|
|
return [name for name in names if name in available]
|
|
|
|
|
|
def _configured_or_search_fields(fields: list[dict[str, Any]], preferred_names: list[str]) -> list[str]:
|
|
preferred = _existing_field_names(fields, preferred_names)
|
|
if preferred:
|
|
return preferred
|
|
return _search_fields(fields)
|
|
|
|
|
|
def _search_fields(fields: list[dict[str, Any]]) -> list[str]:
|
|
preferred_interfaces = {"input", "textarea", "email", "phone", "url"}
|
|
preferred_types = {"string", "text"}
|
|
names = [
|
|
str(field["name"])
|
|
for field in fields
|
|
if field.get("name")
|
|
and (
|
|
str(field.get("interface") or "").lower() in preferred_interfaces
|
|
or str(field.get("type") or "").lower() in preferred_types
|
|
)
|
|
]
|
|
if names:
|
|
return names[:4]
|
|
fallback = [str(field["name"]) for field in fields if field.get("name") and str(field.get("name")) != "id"]
|
|
return fallback[:2]
|
|
|
|
|
|
def _find_field(fields: list[dict[str, Any]], needles: list[str]) -> str | None:
|
|
lowered = [needle.lower() for needle in needles]
|
|
for field in fields:
|
|
name = field.get("name")
|
|
if not name:
|
|
continue
|
|
text = _field_text(field)
|
|
if any(needle in text for needle in lowered):
|
|
return str(name)
|
|
return None
|
|
|
|
|
|
def _relation_field(fields: list[dict[str, Any]], needles: list[str]) -> str | None:
|
|
lowered = [needle.lower() for needle in needles]
|
|
for field in fields:
|
|
if not field.get("name"):
|
|
continue
|
|
if str(field.get("type") or "").lower() not in {"belongsto", "belongstomany", "hasmany", "hasone"}:
|
|
continue
|
|
text = _field_text(field)
|
|
if any(needle in text for needle in lowered):
|
|
return str(field["name"])
|
|
return None
|
|
|
|
|
|
def _date_field(fields: list[dict[str, Any]]) -> str | None:
|
|
preferred = _find_field(fields, ["跟进日期", "跟进时间", "follow", "date", "time"])
|
|
if preferred:
|
|
return preferred
|
|
for field in fields:
|
|
if field.get("name") and str(field.get("interface") or "").lower() in {"date", "datetime", "createdat"}:
|
|
return str(field["name"])
|
|
if field.get("name") and str(field.get("type") or "").lower() in {"date", "datetime"}:
|
|
return str(field["name"])
|
|
return None
|
|
|
|
|
|
def _template_sort(date_field: str | None, fallback: list[str] | None) -> list[str] | None:
|
|
if date_field:
|
|
sort = [f"-{date_field}"]
|
|
if date_field != "createdAt":
|
|
sort.append("-createdAt")
|
|
return sort
|
|
return fallback
|
|
|
|
|
|
__all__ = [
|
|
"_configured_or_search_fields",
|
|
"_date_field",
|
|
"_existing_field_names",
|
|
"_field_list",
|
|
"_field_names",
|
|
"_field_path",
|
|
"_field_text",
|
|
"_find_field",
|
|
"_find_named_field",
|
|
"_metadata_error",
|
|
"_relation_field",
|
|
"_search_fields",
|
|
"_template_sort",
|
|
]
|