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>
113 lines
3.7 KiB
Python
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)
|