Files
shanxia2.0/backend/app/services/crm/facet_context.py
T
wuzilongandCursor 6a8cd90b98 feat: cascade column facet options by current list context
Make dynamic column-facet values respect other active filters (Excel-style), with backend facet context helpers, list-page wiring, slip print/sidebar fixes, and refreshed graphify graphs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-11 22:04:20 +08:00

113 lines
3.7 KiB
Python

"""CRM 列头 facet:从 column_filter_kwargs 中清除当前列自身条件。"""
from __future__ import annotations
import re
from typing import Any, Dict, Mapping, MutableMapping, Optional, Sequence
# field(prop/API) → column_filter_kwargs 中应清空的 snake 键
CRM_FACET_FIELD_KWARG_KEYS: Dict[str, Sequence[str]] = {
# customer
"customerNo": ("customer_no", "customer_no_in"),
"customerName": ("customer_name", "customer_name_in"),
"shortName": ("short_name", "short_name_in"),
# product
"productNo": ("product_no", "product_no_in"),
"productName": ("product_name", "product_name_in"),
"categoryName": ("category_name", "category_name_in"),
"brands": ("brands", "brands_in", "brand", "brand_in"),
"aliases": ("aliases", "aliases_in", "alias", "alias_in"),
"model": ("model", "model_in"),
# clue
"clueName": ("clue_name", "clue_name_in"),
"company": ("company", "company_in"),
# opportunity
"opportunityNo": ("opportunity_no", "opportunity_no_in"),
"opportunityName": ("opportunity_name", "opportunity_name_in"),
# quote / contract share customerName
"quoteNo": ("quote_no", "quote_no_in"),
"contractNo": ("contract_no", "contract_no_in"),
"contractName": ("contract_name", "contract_name_in"),
"templateNo": ("template_no", "template_no_in"),
"templateName": ("template_name", "template_name_in"),
# supplier / price letter
"supplierCode": ("supplier_code", "supplier_code_in"),
"supplierName": ("supplier_name", "supplier_name_in"),
}
_META_KEYS = frozenset(
{
"field",
"keyword",
"limit",
"page",
"pageSize",
"page_size",
"pageNum",
"page_num",
"ids",
"columns",
"sortField",
"sort_field",
"sortOrder",
"sort_order",
}
)
def _camel_to_snake(name: str) -> str:
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
def clear_facet_field_kwargs(
kwargs: Mapping[str, object],
field: str,
*,
extra_keys: Optional[Sequence[str]] = None,
) -> Dict[str, object]:
"""返回清除当前 facet 列相关键后的 kwargs 副本(值置 None)。"""
out: Dict[str, object] = dict(kwargs)
keys = list(CRM_FACET_FIELD_KWARG_KEYS.get(str(field or "").strip(), ()))
if extra_keys:
keys.extend(extra_keys)
for key in keys:
if key in out:
out[key] = None
return out
def strip_crm_facet_body_params(
params: MutableMapping[str, object], field: str
) -> Dict[str, object]:
"""从原始 camelCase body dump 中去掉本列键与元数据(兼容无 mixin 路径)。"""
from app.services.pl.facet_context import strip_simple_facet_self_params
return strip_simple_facet_self_params(params, field)
def facet_filter_kwargs_from_body(body: Any, field: str) -> Dict[str, object]:
"""从 Facet Body(含 extra=allow)提取 snake_case 列筛 kwargs,并清除当前列。"""
dumped = body.model_dump(
by_alias=False,
exclude_none=False,
exclude={"field", "keyword", "limit"},
)
out: Dict[str, object] = {}
for key, value in dumped.items():
if key in _META_KEYS or value is None or value == "":
continue
out[key] = value
extra = getattr(body, "model_extra", None) or {}
for key, value in extra.items():
if key in _META_KEYS or value is None or value == "":
continue
snake = _camel_to_snake(key) if any(c.isupper() for c in key) else key
if snake in _META_KEYS:
continue
out[snake] = value
return clear_facet_field_kwargs(out, field)