@josephyan/qingflow-cli 0.2.0-beta.74 → 0.2.0-beta.76
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.
- package/README.md +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/builder_facade/models.py +121 -0
- package/src/qingflow_mcp/builder_facade/service.py +297 -66
- package/src/qingflow_mcp/public_surface.py +230 -0
- package/src/qingflow_mcp/response_trim.py +14 -248
- package/src/qingflow_mcp/server_app_user.py +4 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +35 -5
- package/src/qingflow_mcp/tools/approval_tools.py +0 -16
- package/src/qingflow_mcp/tools/record_tools.py +298 -25
- package/src/qingflow_mcp/tools/task_context_tools.py +130 -7
|
@@ -57,6 +57,7 @@ from .models import (
|
|
|
57
57
|
PortalListResponse,
|
|
58
58
|
PortalReadSummaryResponse,
|
|
59
59
|
PortalSectionPatch,
|
|
60
|
+
PublicDepartmentScopeMode,
|
|
60
61
|
PublicFieldType,
|
|
61
62
|
PublicRelationMode,
|
|
62
63
|
PublicChartType,
|
|
@@ -1599,29 +1600,34 @@ class AiBuilderFacade:
|
|
|
1599
1600
|
transport_error=api_error,
|
|
1600
1601
|
),
|
|
1601
1602
|
)
|
|
1602
|
-
return finalize(response)
|
|
1603
|
+
return finalize(self._append_publish_result(profile=profile, app_key=app_key, publish=True, response=response))
|
|
1603
1604
|
button = _normalize_custom_button_detail(readback.get("result") if isinstance(readback.get("result"), dict) else {})
|
|
1604
1605
|
return finalize(
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1606
|
+
self._append_publish_result(
|
|
1607
|
+
profile=profile,
|
|
1608
|
+
app_key=app_key,
|
|
1609
|
+
publish=True,
|
|
1610
|
+
response={
|
|
1611
|
+
"status": "success",
|
|
1612
|
+
"error_code": None,
|
|
1613
|
+
"recoverable": False,
|
|
1614
|
+
"message": "created custom button",
|
|
1615
|
+
"normalized_args": normalized_args,
|
|
1616
|
+
"missing_fields": [],
|
|
1617
|
+
"allowed_values": {},
|
|
1618
|
+
"details": {},
|
|
1619
|
+
"request_id": None,
|
|
1620
|
+
"suggested_next_call": None,
|
|
1621
|
+
"noop": False,
|
|
1622
|
+
"warnings": [],
|
|
1623
|
+
"verification": {"custom_button_verified": True},
|
|
1624
|
+
"verified": True,
|
|
1625
|
+
"app_key": app_key,
|
|
1626
|
+
"button_id": button_id,
|
|
1627
|
+
"edit_version_no": edit_version_no,
|
|
1628
|
+
"button": button,
|
|
1629
|
+
},
|
|
1630
|
+
)
|
|
1625
1631
|
)
|
|
1626
1632
|
|
|
1627
1633
|
def app_custom_button_update(
|
|
@@ -1707,29 +1713,34 @@ class AiBuilderFacade:
|
|
|
1707
1713
|
transport_error=api_error,
|
|
1708
1714
|
),
|
|
1709
1715
|
)
|
|
1710
|
-
return finalize(response)
|
|
1716
|
+
return finalize(self._append_publish_result(profile=profile, app_key=app_key, publish=True, response=response))
|
|
1711
1717
|
button = _normalize_custom_button_detail(readback.get("result") if isinstance(readback.get("result"), dict) else {})
|
|
1712
1718
|
return finalize(
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1719
|
+
self._append_publish_result(
|
|
1720
|
+
profile=profile,
|
|
1721
|
+
app_key=app_key,
|
|
1722
|
+
publish=True,
|
|
1723
|
+
response={
|
|
1724
|
+
"status": "success",
|
|
1725
|
+
"error_code": None,
|
|
1726
|
+
"recoverable": False,
|
|
1727
|
+
"message": "updated custom button",
|
|
1728
|
+
"normalized_args": normalized_args,
|
|
1729
|
+
"missing_fields": [],
|
|
1730
|
+
"allowed_values": {},
|
|
1731
|
+
"details": {},
|
|
1732
|
+
"request_id": None,
|
|
1733
|
+
"suggested_next_call": None,
|
|
1734
|
+
"noop": False,
|
|
1735
|
+
"warnings": [],
|
|
1736
|
+
"verification": {"custom_button_verified": True},
|
|
1737
|
+
"verified": True,
|
|
1738
|
+
"app_key": app_key,
|
|
1739
|
+
"button_id": button_id,
|
|
1740
|
+
"edit_version_no": edit_version_no,
|
|
1741
|
+
"button": button,
|
|
1742
|
+
},
|
|
1743
|
+
)
|
|
1733
1744
|
)
|
|
1734
1745
|
|
|
1735
1746
|
def app_custom_button_delete(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
|
|
@@ -1748,27 +1759,42 @@ class AiBuilderFacade:
|
|
|
1748
1759
|
def finalize(response: JSONObject) -> JSONObject:
|
|
1749
1760
|
return _apply_permission_outcomes(response, *permission_outcomes)
|
|
1750
1761
|
|
|
1762
|
+
edit_version_no, edit_context_error = self._ensure_app_edit_context(
|
|
1763
|
+
profile=profile,
|
|
1764
|
+
app_key=app_key,
|
|
1765
|
+
normalized_args=normalized_args,
|
|
1766
|
+
failure_code="CUSTOM_BUTTON_DELETE_FAILED",
|
|
1767
|
+
)
|
|
1768
|
+
if edit_context_error is not None:
|
|
1769
|
+
return finalize(edit_context_error)
|
|
1770
|
+
|
|
1751
1771
|
self.buttons.custom_button_delete(profile=profile, app_key=app_key, button_id=button_id)
|
|
1752
1772
|
return finalize(
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1773
|
+
self._append_publish_result(
|
|
1774
|
+
profile=profile,
|
|
1775
|
+
app_key=app_key,
|
|
1776
|
+
publish=True,
|
|
1777
|
+
response={
|
|
1778
|
+
"status": "success",
|
|
1779
|
+
"error_code": None,
|
|
1780
|
+
"recoverable": False,
|
|
1781
|
+
"message": "deleted custom button",
|
|
1782
|
+
"normalized_args": normalized_args,
|
|
1783
|
+
"missing_fields": [],
|
|
1784
|
+
"allowed_values": {},
|
|
1785
|
+
"details": {},
|
|
1786
|
+
"request_id": None,
|
|
1787
|
+
"suggested_next_call": None,
|
|
1788
|
+
"noop": False,
|
|
1789
|
+
"warnings": [],
|
|
1790
|
+
"verification": {"custom_button_deleted": True},
|
|
1791
|
+
"verified": True,
|
|
1792
|
+
"app_key": app_key,
|
|
1793
|
+
"button_id": button_id,
|
|
1794
|
+
"edit_version_no": edit_version_no,
|
|
1795
|
+
"deleted": True,
|
|
1796
|
+
},
|
|
1797
|
+
)
|
|
1772
1798
|
)
|
|
1773
1799
|
|
|
1774
1800
|
def _resolve_app_matches_in_visible_apps(
|
|
@@ -3593,6 +3619,7 @@ class AiBuilderFacade:
|
|
|
3593
3619
|
existing_field = next((field for field in current_fields if str(field.get("name") or "") == patch.name), None)
|
|
3594
3620
|
if existing_field is not None:
|
|
3595
3621
|
if _field_matches_patch(existing_field, patch):
|
|
3622
|
+
_merge_existing_field_with_patch(existing_field, patch)
|
|
3596
3623
|
continue
|
|
3597
3624
|
return _failed(
|
|
3598
3625
|
"DUPLICATE_FIELD",
|
|
@@ -8145,6 +8172,133 @@ def _normalize_relation_mode(value: Any) -> str:
|
|
|
8145
8172
|
return _relation_mode_from_optional_data_num(value)
|
|
8146
8173
|
|
|
8147
8174
|
|
|
8175
|
+
def _normalize_department_scope_mode(value: Any) -> str | None:
|
|
8176
|
+
if value is None:
|
|
8177
|
+
return None
|
|
8178
|
+
if isinstance(value, int):
|
|
8179
|
+
if value == 1:
|
|
8180
|
+
return PublicDepartmentScopeMode.all.value
|
|
8181
|
+
if value == 2:
|
|
8182
|
+
return PublicDepartmentScopeMode.custom.value
|
|
8183
|
+
return None
|
|
8184
|
+
normalized = str(value).strip().lower()
|
|
8185
|
+
if normalized in {"all", "workspace_all", "workspace-all", "default", "default_all", "default-all", "1"}:
|
|
8186
|
+
return PublicDepartmentScopeMode.all.value
|
|
8187
|
+
if normalized in {"custom", "explicit", "selected", "2"}:
|
|
8188
|
+
return PublicDepartmentScopeMode.custom.value
|
|
8189
|
+
return normalized or None
|
|
8190
|
+
|
|
8191
|
+
|
|
8192
|
+
def _normalize_department_scope(value: Any) -> dict[str, Any] | None:
|
|
8193
|
+
if not isinstance(value, dict):
|
|
8194
|
+
return None
|
|
8195
|
+
raw_departments = value.get("departments", value.get("depart", value.get("departs")))
|
|
8196
|
+
departments: list[dict[str, Any]] = []
|
|
8197
|
+
if isinstance(raw_departments, list):
|
|
8198
|
+
for item in raw_departments:
|
|
8199
|
+
if isinstance(item, dict):
|
|
8200
|
+
dept_id = _coerce_positive_int(item.get("dept_id", item.get("deptId", item.get("id"))))
|
|
8201
|
+
dept_name = str(
|
|
8202
|
+
item.get("dept_name", item.get("deptName", item.get("name", item.get("value")))) or ""
|
|
8203
|
+
).strip() or None
|
|
8204
|
+
else:
|
|
8205
|
+
dept_id = _coerce_positive_int(item)
|
|
8206
|
+
dept_name = None
|
|
8207
|
+
if dept_id is None and dept_name is None:
|
|
8208
|
+
continue
|
|
8209
|
+
entry: dict[str, Any] = {}
|
|
8210
|
+
if dept_id is not None:
|
|
8211
|
+
entry["dept_id"] = dept_id
|
|
8212
|
+
if dept_name is not None:
|
|
8213
|
+
entry["dept_name"] = dept_name
|
|
8214
|
+
departments.append(entry)
|
|
8215
|
+
normalized_mode = _normalize_department_scope_mode(value.get("mode"))
|
|
8216
|
+
if normalized_mode is None:
|
|
8217
|
+
normalized_mode = PublicDepartmentScopeMode.custom.value if departments else PublicDepartmentScopeMode.all.value
|
|
8218
|
+
return {
|
|
8219
|
+
"mode": normalized_mode,
|
|
8220
|
+
"departments": departments,
|
|
8221
|
+
"include_sub_departs": (
|
|
8222
|
+
None
|
|
8223
|
+
if value.get("include_sub_departs", value.get("includeSubDeparts")) is None
|
|
8224
|
+
else bool(value.get("include_sub_departs", value.get("includeSubDeparts")))
|
|
8225
|
+
),
|
|
8226
|
+
}
|
|
8227
|
+
|
|
8228
|
+
|
|
8229
|
+
def _normalize_department_scope_from_question(question: dict[str, Any]) -> dict[str, Any] | None:
|
|
8230
|
+
scope_type = _coerce_positive_int(question.get("deptSelectScopeType"))
|
|
8231
|
+
scope = question.get("deptSelectScope") if isinstance(question.get("deptSelectScope"), dict) else {}
|
|
8232
|
+
if not isinstance(scope, dict):
|
|
8233
|
+
scope = {}
|
|
8234
|
+
if isinstance(scope.get("dynamic"), list) and scope.get("dynamic"):
|
|
8235
|
+
return None
|
|
8236
|
+
if isinstance(scope.get("externalDepartList"), list) and scope.get("externalDepartList"):
|
|
8237
|
+
return None
|
|
8238
|
+
if scope_type == 1:
|
|
8239
|
+
return {
|
|
8240
|
+
"mode": PublicDepartmentScopeMode.all.value,
|
|
8241
|
+
"departments": [],
|
|
8242
|
+
"include_sub_departs": None
|
|
8243
|
+
if scope.get("includeSubDeparts") is None
|
|
8244
|
+
else bool(scope.get("includeSubDeparts")),
|
|
8245
|
+
}
|
|
8246
|
+
if scope_type == 2:
|
|
8247
|
+
departments: list[dict[str, Any]] = []
|
|
8248
|
+
for item in cast(list[Any], scope.get("depart") or []):
|
|
8249
|
+
if not isinstance(item, dict):
|
|
8250
|
+
continue
|
|
8251
|
+
dept_id = _coerce_positive_int(item.get("deptId", item.get("id")))
|
|
8252
|
+
dept_name = str(item.get("deptName", item.get("name", item.get("value"))) or "").strip() or None
|
|
8253
|
+
if dept_id is None and dept_name is None:
|
|
8254
|
+
continue
|
|
8255
|
+
entry: dict[str, Any] = {}
|
|
8256
|
+
if dept_id is not None:
|
|
8257
|
+
entry["dept_id"] = dept_id
|
|
8258
|
+
if dept_name is not None:
|
|
8259
|
+
entry["dept_name"] = dept_name
|
|
8260
|
+
departments.append(entry)
|
|
8261
|
+
return {
|
|
8262
|
+
"mode": PublicDepartmentScopeMode.custom.value,
|
|
8263
|
+
"departments": departments,
|
|
8264
|
+
"include_sub_departs": None
|
|
8265
|
+
if scope.get("includeSubDeparts") is None
|
|
8266
|
+
else bool(scope.get("includeSubDeparts")),
|
|
8267
|
+
}
|
|
8268
|
+
return None
|
|
8269
|
+
|
|
8270
|
+
|
|
8271
|
+
def _serialize_department_scope_for_question(value: Any) -> tuple[int, dict[str, Any]]:
|
|
8272
|
+
normalized = _normalize_department_scope(value)
|
|
8273
|
+
if normalized is None or normalized.get("mode") == PublicDepartmentScopeMode.all.value:
|
|
8274
|
+
scope: dict[str, Any] = {"depart": [], "dynamic": []}
|
|
8275
|
+
if normalized is not None and normalized.get("include_sub_departs") is not None:
|
|
8276
|
+
scope["includeSubDeparts"] = bool(normalized.get("include_sub_departs"))
|
|
8277
|
+
return 1, scope
|
|
8278
|
+
departments = []
|
|
8279
|
+
for item in cast(list[Any], normalized.get("departments") or []):
|
|
8280
|
+
if not isinstance(item, dict):
|
|
8281
|
+
continue
|
|
8282
|
+
dept_id = _coerce_positive_int(item.get("dept_id"))
|
|
8283
|
+
dept_name = str(item.get("dept_name") or "").strip() or None
|
|
8284
|
+
if dept_id is None and dept_name is None:
|
|
8285
|
+
continue
|
|
8286
|
+
entry: dict[str, Any] = {}
|
|
8287
|
+
if dept_id is not None:
|
|
8288
|
+
entry["deptId"] = dept_id
|
|
8289
|
+
if dept_name is not None:
|
|
8290
|
+
entry["deptName"] = dept_name
|
|
8291
|
+
departments.append(entry)
|
|
8292
|
+
scope = {"depart": departments, "dynamic": []}
|
|
8293
|
+
if normalized.get("include_sub_departs") is not None:
|
|
8294
|
+
scope["includeSubDeparts"] = bool(normalized.get("include_sub_departs"))
|
|
8295
|
+
return 2, scope
|
|
8296
|
+
|
|
8297
|
+
|
|
8298
|
+
def _department_scope_equal(left: Any, right: Any) -> bool:
|
|
8299
|
+
return _normalize_department_scope(left) == _normalize_department_scope(right)
|
|
8300
|
+
|
|
8301
|
+
|
|
8148
8302
|
def _is_relation_target_metadata_read_restricted_api_error(error: QingflowApiError) -> bool:
|
|
8149
8303
|
return error.backend_code in {40002, 40027, 40161}
|
|
8150
8304
|
|
|
@@ -8311,8 +8465,10 @@ def _hydrate_relation_field_configs(
|
|
|
8311
8465
|
field["config"] = config
|
|
8312
8466
|
display_selector = field.get("display_field") if isinstance(field.get("display_field"), dict) else None
|
|
8313
8467
|
visible_selector_payloads = [item for item in cast(list[Any], field.get("visible_fields") or []) if isinstance(item, dict)]
|
|
8314
|
-
if display_selector is None
|
|
8315
|
-
|
|
8468
|
+
if display_selector is None:
|
|
8469
|
+
raise ValueError("relation field requires display_field")
|
|
8470
|
+
if not visible_selector_payloads:
|
|
8471
|
+
raise ValueError("relation field requires visible_fields")
|
|
8316
8472
|
target_fields = target_field_cache.get(target_app_key)
|
|
8317
8473
|
if target_fields is None:
|
|
8318
8474
|
try:
|
|
@@ -8349,7 +8505,31 @@ def _hydrate_relation_field_configs(
|
|
|
8349
8505
|
)
|
|
8350
8506
|
continue
|
|
8351
8507
|
if not target_fields:
|
|
8352
|
-
|
|
8508
|
+
display_field = _normalize_relation_target_stub(
|
|
8509
|
+
display_selector,
|
|
8510
|
+
fallback_name=str((display_selector or {}).get("name") or "").strip() or None,
|
|
8511
|
+
)
|
|
8512
|
+
visible_fields = [
|
|
8513
|
+
_normalize_relation_target_stub(item, fallback_name=str(item.get("name") or "").strip() or None)
|
|
8514
|
+
for item in visible_selector_payloads
|
|
8515
|
+
]
|
|
8516
|
+
_apply_relation_target_selection(
|
|
8517
|
+
field=field,
|
|
8518
|
+
config=config,
|
|
8519
|
+
display_field=display_field,
|
|
8520
|
+
visible_fields=visible_fields,
|
|
8521
|
+
)
|
|
8522
|
+
degraded_entries.append(
|
|
8523
|
+
{
|
|
8524
|
+
"field_name": field.get("name"),
|
|
8525
|
+
"target_app_key": target_app_key,
|
|
8526
|
+
"display_field": deepcopy(field.get("display_field") or {}),
|
|
8527
|
+
"visible_fields": deepcopy(field.get("visible_fields") or []),
|
|
8528
|
+
"relation_mode": field.get("relation_mode"),
|
|
8529
|
+
"transport_error": None,
|
|
8530
|
+
}
|
|
8531
|
+
)
|
|
8532
|
+
continue
|
|
8353
8533
|
display_field = _resolve_relation_target_field(
|
|
8354
8534
|
target_fields=target_fields,
|
|
8355
8535
|
selector_payload=display_selector,
|
|
@@ -8454,6 +8634,10 @@ def _parse_field(question: dict[str, Any], *, field_id_hint: str | None = None)
|
|
|
8454
8634
|
}
|
|
8455
8635
|
field["visible_fields"] = visible_fields
|
|
8456
8636
|
field["field_name_show"] = bool(reference.get("fieldNameShow", True))
|
|
8637
|
+
if field_type == FieldType.department:
|
|
8638
|
+
department_scope = _normalize_department_scope_from_question(question)
|
|
8639
|
+
if department_scope is not None:
|
|
8640
|
+
field["department_scope"] = department_scope
|
|
8457
8641
|
if field_type == FieldType.code_block:
|
|
8458
8642
|
code_block_config = question.get("codeBlockConfig") if isinstance(question.get("codeBlockConfig"), dict) else {}
|
|
8459
8643
|
field["code_block_config"] = {
|
|
@@ -9075,6 +9259,8 @@ def _compact_public_field_read(*, field: dict[str, Any], layout: dict[str, Any])
|
|
|
9075
9259
|
payload["relation_mode"] = _normalize_relation_mode(field.get("relation_mode"))
|
|
9076
9260
|
payload["display_field"] = deepcopy(field.get("display_field"))
|
|
9077
9261
|
payload["visible_fields"] = deepcopy(field.get("visible_fields") or [])
|
|
9262
|
+
if field.get("type") == FieldType.department.value and field.get("department_scope") is not None:
|
|
9263
|
+
payload["department_scope"] = deepcopy(field.get("department_scope"))
|
|
9078
9264
|
if field.get("type") == FieldType.code_block.value:
|
|
9079
9265
|
payload["code_block_config"] = deepcopy(field.get("code_block_config") or {})
|
|
9080
9266
|
if field.get("auto_trigger") is not None:
|
|
@@ -9155,6 +9341,7 @@ def _field_patch_to_internal(patch: FieldPatch) -> dict[str, Any]:
|
|
|
9155
9341
|
"display_field": patch.display_field.model_dump(mode="json", exclude_none=True) if patch.display_field is not None else None,
|
|
9156
9342
|
"visible_fields": [selector.model_dump(mode="json", exclude_none=True) for selector in patch.visible_fields],
|
|
9157
9343
|
"relation_mode": patch.relation_mode.value if patch.relation_mode is not None else None,
|
|
9344
|
+
"department_scope": patch.department_scope.model_dump(mode="json", exclude_none=True) if patch.department_scope is not None else None,
|
|
9158
9345
|
"remote_lookup_config": remote_lookup_config,
|
|
9159
9346
|
"_explicit_remote_lookup_config": remote_lookup_config is not None,
|
|
9160
9347
|
"q_linker_binding": q_linker_binding,
|
|
@@ -9173,6 +9360,23 @@ def _field_patch_to_internal(patch: FieldPatch) -> dict[str, Any]:
|
|
|
9173
9360
|
|
|
9174
9361
|
|
|
9175
9362
|
def _field_matches_patch(field: dict[str, Any], patch: FieldPatch) -> bool:
|
|
9363
|
+
field_display_selector = field.get("display_field")
|
|
9364
|
+
patch_display_selector = patch.display_field.model_dump(mode="json", exclude_none=True) if patch.display_field is not None else None
|
|
9365
|
+
field_visible_selectors = field.get("visible_fields")
|
|
9366
|
+
patch_visible_selectors = [selector.model_dump(mode="json", exclude_none=True) for selector in patch.visible_fields]
|
|
9367
|
+
if (
|
|
9368
|
+
str(field.get("type") or "") == FieldType.relation.value
|
|
9369
|
+
and patch.type == PublicFieldType.relation
|
|
9370
|
+
and (field.get("target_app_key") or None) == patch.target_app_key
|
|
9371
|
+
and field_display_selector is None
|
|
9372
|
+
and not list(field_visible_selectors or [])
|
|
9373
|
+
):
|
|
9374
|
+
relation_selector_match = True
|
|
9375
|
+
else:
|
|
9376
|
+
relation_selector_match = (
|
|
9377
|
+
_field_selector_payload_equal(field_display_selector, patch_display_selector)
|
|
9378
|
+
and _field_selector_list_equal(field_visible_selectors, patch_visible_selectors)
|
|
9379
|
+
)
|
|
9176
9380
|
return (
|
|
9177
9381
|
str(field.get("name") or "") == patch.name
|
|
9178
9382
|
and str(field.get("type") or "") == patch.type.value
|
|
@@ -9180,9 +9384,12 @@ def _field_matches_patch(field: dict[str, Any], patch: FieldPatch) -> bool:
|
|
|
9180
9384
|
and (field.get("description") or None) == patch.description
|
|
9181
9385
|
and list(field.get("options") or []) == list(patch.options)
|
|
9182
9386
|
and (field.get("target_app_key") or None) == patch.target_app_key
|
|
9183
|
-
and
|
|
9184
|
-
and _field_selector_list_equal(field.get("visible_fields"), [selector.model_dump(mode="json", exclude_none=True) for selector in patch.visible_fields])
|
|
9387
|
+
and relation_selector_match
|
|
9185
9388
|
and _normalize_relation_mode(field.get("relation_mode")) == _normalize_relation_mode(patch.relation_mode.value if patch.relation_mode is not None else None)
|
|
9389
|
+
and (
|
|
9390
|
+
patch.department_scope is None
|
|
9391
|
+
or _department_scope_equal(field.get("department_scope"), patch.department_scope.model_dump(mode="json", exclude_none=True))
|
|
9392
|
+
)
|
|
9186
9393
|
and _remote_lookup_config_equal(field.get("remote_lookup_config"), patch.remote_lookup_config.model_dump(mode="json", exclude_none=True) if patch.remote_lookup_config is not None else None)
|
|
9187
9394
|
and _q_linker_binding_equal(field.get("q_linker_binding"), patch.q_linker_binding.model_dump(mode="json", exclude_none=True) if patch.q_linker_binding is not None else None)
|
|
9188
9395
|
and _code_block_config_equal(field.get("code_block_config"), patch.code_block_config.model_dump(mode="json", exclude_none=True) if patch.code_block_config is not None else None)
|
|
@@ -9194,6 +9401,24 @@ def _field_matches_patch(field: dict[str, Any], patch: FieldPatch) -> bool:
|
|
|
9194
9401
|
)
|
|
9195
9402
|
|
|
9196
9403
|
|
|
9404
|
+
def _merge_existing_field_with_patch(field: dict[str, Any], patch: FieldPatch) -> None:
|
|
9405
|
+
if str(field.get("type") or "") == FieldType.relation.value and patch.type == PublicFieldType.relation:
|
|
9406
|
+
if not str(field.get("target_app_key") or "").strip() and patch.target_app_key:
|
|
9407
|
+
field["target_app_key"] = patch.target_app_key
|
|
9408
|
+
if not isinstance(field.get("display_field"), dict) and patch.display_field is not None:
|
|
9409
|
+
field["display_field"] = patch.display_field.model_dump(mode="json", exclude_none=True)
|
|
9410
|
+
if not list(field.get("visible_fields") or []) and patch.visible_fields:
|
|
9411
|
+
field["visible_fields"] = [
|
|
9412
|
+
selector.model_dump(mode="json", exclude_none=True)
|
|
9413
|
+
for selector in patch.visible_fields
|
|
9414
|
+
]
|
|
9415
|
+
if field.get("relation_mode") is None and patch.relation_mode is not None:
|
|
9416
|
+
field["relation_mode"] = patch.relation_mode.value
|
|
9417
|
+
if str(field.get("type") or "") == FieldType.department.value and patch.type == PublicFieldType.department:
|
|
9418
|
+
if field.get("department_scope") is None and patch.department_scope is not None:
|
|
9419
|
+
field["department_scope"] = patch.department_scope.model_dump(mode="json", exclude_none=True)
|
|
9420
|
+
|
|
9421
|
+
|
|
9197
9422
|
def _field_selector_payload_equal(left: Any, right: Any) -> bool:
|
|
9198
9423
|
if left is None and right is None:
|
|
9199
9424
|
return True
|
|
@@ -9374,6 +9599,8 @@ def _apply_field_mutation(field: dict[str, Any], mutation: Any) -> None:
|
|
|
9374
9599
|
field["visible_fields"] = list(payload["visible_fields"])
|
|
9375
9600
|
if "relation_mode" in payload:
|
|
9376
9601
|
field["relation_mode"] = payload["relation_mode"]
|
|
9602
|
+
if "department_scope" in payload:
|
|
9603
|
+
field["department_scope"] = payload["department_scope"]
|
|
9377
9604
|
if "remote_lookup_config" in payload:
|
|
9378
9605
|
field["remote_lookup_config"] = payload["remote_lookup_config"]
|
|
9379
9606
|
field["config"] = deepcopy(payload["remote_lookup_config"])
|
|
@@ -10884,6 +11111,10 @@ def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]
|
|
|
10884
11111
|
reference["referQueId"] = field.get("target_field_que_id")
|
|
10885
11112
|
reference["optionalDataNum"] = _relation_mode_to_optional_data_num(field.get("relation_mode"))
|
|
10886
11113
|
question["referenceConfig"] = reference
|
|
11114
|
+
if field.get("type") == FieldType.department.value:
|
|
11115
|
+
scope_type, scope_payload = _serialize_department_scope_for_question(field.get("department_scope"))
|
|
11116
|
+
question["deptSelectScopeType"] = scope_type
|
|
11117
|
+
question["deptSelectScope"] = scope_payload
|
|
10887
11118
|
if field.get("type") == FieldType.code_block.value:
|
|
10888
11119
|
code_block_config = _normalize_code_block_config(field.get("code_block_config") or field.get("config") or {}) or {
|
|
10889
11120
|
"config_mode": 1,
|