@josephyan/qingflow-app-user-mcp 1.1.24 → 1.1.26
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 +7 -7
- package/docs/local-agent-install.md +6 -57
- package/entry_point.py +1 -1
- package/npm/bin/qingflow-app-user-mcp.mjs +33 -2
- package/npm/lib/runtime.mjs +101 -21
- package/npm/scripts/postinstall.mjs +10 -1
- package/package.json +2 -3
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +277 -0
- package/skills/qingflow-app-builder/agents/openai.yaml +4 -0
- package/skills/qingflow-app-builder/references/build-complete-system.md +428 -0
- package/skills/qingflow-app-builder/references/build-single-app.md +530 -0
- package/skills/qingflow-app-builder/references/create-app.md +149 -0
- package/skills/qingflow-app-builder/references/environments.md +63 -0
- package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +123 -0
- package/skills/qingflow-app-builder/references/gotchas.md +108 -0
- package/skills/qingflow-app-builder/references/match-rules.md +114 -0
- package/skills/qingflow-app-builder/references/public-surface-sync.md +75 -0
- package/skills/qingflow-app-builder/references/solution-playbooks.md +52 -0
- package/skills/qingflow-app-builder/references/tool-selection.md +107 -0
- package/skills/qingflow-app-builder/references/update-flow.md +186 -0
- package/skills/qingflow-app-builder/references/update-layout.md +68 -0
- package/skills/qingflow-app-builder/references/update-schema.md +72 -0
- package/skills/qingflow-app-builder/references/update-views.md +291 -0
- package/skills/qingflow-app-builder-code-integrations/SKILL.md +137 -0
- package/skills/qingflow-app-builder-code-integrations/agents/openai.yaml +4 -0
- package/skills/qingflow-app-builder-code-integrations/references/code-block.md +66 -0
- package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +77 -0
- package/skills/qingflow-app-user/SKILL.md +14 -17
- package/skills/qingflow-app-user/references/data-gotchas.md +2 -2
- package/skills/qingflow-app-user/references/public-surface-sync.md +3 -3
- package/skills/qingflow-app-user/references/record-patterns.md +5 -5
- package/skills/qingflow-app-user/references/workflow-usage.md +5 -4
- package/skills/qingflow-mcp-setup/SKILL.md +9 -13
- package/skills/qingflow-mcp-setup/references/claude-desktop.md +1 -1
- package/skills/qingflow-mcp-setup/references/environments.md +1 -2
- package/skills/qingflow-mcp-setup/references/generic-stdio.md +5 -6
- package/skills/qingflow-record-analysis/SKILL.md +8 -9
- package/skills/qingflow-record-delete/SKILL.md +3 -7
- package/skills/qingflow-record-import/SKILL.md +2 -35
- package/skills/qingflow-record-insert/SKILL.md +6 -78
- package/skills/qingflow-record-update/SKILL.md +24 -39
- package/skills/qingflow-task-ops/SKILL.md +27 -30
- package/skills/qingflow-task-ops/references/environments.md +1 -0
- package/skills/qingflow-task-ops/references/workflow-usage.md +6 -4
- package/skills/qingflow-workflow-builder/SKILL.md +96 -0
- package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
- package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
- package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
- package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
- package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
- package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
- package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
- package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
- package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
- package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
- package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
- package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
- package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
- package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/__main__.py +2 -6
- package/src/qingflow_mcp/builder_facade/models.py +108 -485
- package/src/qingflow_mcp/builder_facade/service.py +1686 -6674
- package/src/qingflow_mcp/cli/commands/builder.py +364 -322
- package/src/qingflow_mcp/cli/commands/chart.py +1 -1
- package/src/qingflow_mcp/cli/commands/common.py +3 -12
- package/src/qingflow_mcp/cli/commands/exports.py +2 -2
- package/src/qingflow_mcp/cli/commands/imports.py +3 -3
- package/src/qingflow_mcp/cli/commands/portal.py +2 -2
- package/src/qingflow_mcp/cli/commands/record.py +27 -101
- package/src/qingflow_mcp/cli/commands/task.py +47 -28
- package/src/qingflow_mcp/cli/commands/view.py +1 -1
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/cli/formatters.py +16 -784
- package/src/qingflow_mcp/cli/main.py +33 -117
- package/src/qingflow_mcp/errors.py +2 -43
- package/src/qingflow_mcp/public_surface.py +17 -26
- package/src/qingflow_mcp/response_trim.py +17 -81
- package/src/qingflow_mcp/server.py +12 -14
- package/src/qingflow_mcp/server_app_builder.py +36 -79
- package/src/qingflow_mcp/server_app_user.py +16 -22
- package/src/qingflow_mcp/session_store.py +7 -11
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/executor.py +18 -245
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1396 -3100
- package/src/qingflow_mcp/tools/app_tools.py +43 -184
- package/src/qingflow_mcp/tools/approval_tools.py +35 -197
- package/src/qingflow_mcp/tools/auth_tools.py +16 -92
- package/src/qingflow_mcp/tools/code_block_tools.py +40 -298
- package/src/qingflow_mcp/tools/custom_button_tools.py +10 -64
- package/src/qingflow_mcp/tools/directory_tools.py +72 -236
- package/src/qingflow_mcp/tools/export_tools.py +34 -244
- package/src/qingflow_mcp/tools/feedback_tools.py +0 -9
- package/src/qingflow_mcp/tools/file_tools.py +3 -9
- package/src/qingflow_mcp/tools/import_tools.py +49 -336
- package/src/qingflow_mcp/tools/navigation_tools.py +12 -91
- package/src/qingflow_mcp/tools/package_tools.py +6 -118
- package/src/qingflow_mcp/tools/portal_tools.py +3 -39
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +7 -116
- package/src/qingflow_mcp/tools/record_tools.py +360 -1145
- package/src/qingflow_mcp/tools/resource_read_tools.py +39 -188
- package/src/qingflow_mcp/tools/role_tools.py +9 -80
- package/src/qingflow_mcp/tools/solution_tools.py +45 -59
- package/src/qingflow_mcp/tools/task_context_tools.py +158 -662
- package/src/qingflow_mcp/tools/task_tools.py +29 -113
- package/src/qingflow_mcp/tools/view_tools.py +3 -106
- package/src/qingflow_mcp/tools/workflow_tools.py +4 -48
- package/src/qingflow_mcp/tools/workspace_tools.py +3 -71
- package/npm/bin/qingflow-skills.mjs +0 -5
- package/skills/qingflow-record-analysis/manifest.yaml +0 -10
- package/skills/qingflow-record-insert/manifest.yaml +0 -6
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
|
@@ -14,7 +14,7 @@ from datetime import UTC, datetime, timedelta
|
|
|
14
14
|
from decimal import Decimal, InvalidOperation
|
|
15
15
|
from io import BytesIO
|
|
16
16
|
from pathlib import Path
|
|
17
|
-
from typing import Any,
|
|
17
|
+
from typing import Any, cast
|
|
18
18
|
from urllib.parse import parse_qs, unquote, urlsplit
|
|
19
19
|
from uuid import uuid4
|
|
20
20
|
from xml.etree import ElementTree
|
|
@@ -22,7 +22,7 @@ from xml.etree import ElementTree
|
|
|
22
22
|
from mcp.server.fastmcp import FastMCP
|
|
23
23
|
|
|
24
24
|
from ..config import DEFAULT_PROFILE, DEFAULT_RECORD_LIST_TYPE, DEFAULT_USER_AGENT, get_mcp_home
|
|
25
|
-
from ..errors import QingflowApiError,
|
|
25
|
+
from ..errors import QingflowApiError, raise_tool_error
|
|
26
26
|
from ..id_utils import normalize_positive_id_int, stringify_backend_id
|
|
27
27
|
from ..json_types import JSONObject, JSONScalar, JSONValue
|
|
28
28
|
from ..list_type_labels import (
|
|
@@ -75,22 +75,6 @@ MAX_SUMMARY_PREVIEW_COLUMN_LIMIT = 6
|
|
|
75
75
|
VERIFY_TASK_FALLBACK_PAGE_SIZE = 50
|
|
76
76
|
VERIFY_TASK_FALLBACK_MAX_PAGES = 3
|
|
77
77
|
BACKEND_LIST_SEARCH_FIELD_LIMIT = 10
|
|
78
|
-
RECORD_WRITE_SYSTEM_FIELD_NAMES = {
|
|
79
|
-
"数据ID",
|
|
80
|
-
"编号",
|
|
81
|
-
"申请人",
|
|
82
|
-
"申请时间",
|
|
83
|
-
"创建人",
|
|
84
|
-
"创建时间",
|
|
85
|
-
"提交人",
|
|
86
|
-
"提交时间",
|
|
87
|
-
"更新时间",
|
|
88
|
-
"更新人",
|
|
89
|
-
"当前流程状态",
|
|
90
|
-
"当前处理人",
|
|
91
|
-
"当前处理节点",
|
|
92
|
-
"流程标题",
|
|
93
|
-
}
|
|
94
78
|
LOOKUP_RESOLUTION_MIN_SCORE = 0.92
|
|
95
79
|
LOOKUP_RESOLUTION_MIN_MARGIN = 0.08
|
|
96
80
|
LOOKUP_CONFIRMATION_CANDIDATE_LIMIT = 5
|
|
@@ -147,27 +131,6 @@ SCHEMA_LINKAGE_REFERENCE_SOURCE_MESSAGE = "updating this field may auto-fill or
|
|
|
147
131
|
SCHEMA_LINKAGE_REFERENCE_TARGET_MESSAGE = "this field is usually filled from an upstream reference selection or default matching logic"
|
|
148
132
|
SCHEMA_LINKAGE_REFERENCE_BOTH_MESSAGE = "this field participates in reference-driven auto-fill logic"
|
|
149
133
|
SCHEMA_LINKAGE_FORMULA_MESSAGE = "this field is usually derived by formula or default auto-fill logic"
|
|
150
|
-
OPTIONAL_SCHEMA_PERMISSION_CODES = {40002, 40027, 404}
|
|
151
|
-
RECORD_PERMISSION_DENIED_CODES = {40002, 40027}
|
|
152
|
-
SYSTEM_VIEW_LIST_TYPES = {int(list_type) for _view_id, list_type, _name in SYSTEM_VIEW_DEFINITIONS}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def _is_optional_schema_permission_error(error: QingflowApiError) -> bool:
|
|
156
|
-
if is_auth_like_error(error):
|
|
157
|
-
return False
|
|
158
|
-
return backend_code_int(error) in OPTIONAL_SCHEMA_PERMISSION_CODES or error.http_status == 404
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def _is_record_permission_denied_error(error: QingflowApiError) -> bool:
|
|
162
|
-
if is_auth_like_error(error):
|
|
163
|
-
return False
|
|
164
|
-
return backend_code_int(error) in RECORD_PERMISSION_DENIED_CODES
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def _is_optional_record_auxiliary_lookup_error(error: QingflowApiError) -> bool:
|
|
168
|
-
if is_auth_like_error(error):
|
|
169
|
-
return False
|
|
170
|
-
return backend_code_int(error) in {40002, 40027, 404} or error.http_status == 404
|
|
171
134
|
|
|
172
135
|
|
|
173
136
|
@dataclass(slots=True)
|
|
@@ -252,13 +215,6 @@ class AccessibleViewRoute:
|
|
|
252
215
|
view_type: str | None = None
|
|
253
216
|
|
|
254
217
|
|
|
255
|
-
def _prefer_custom_update_routes(routes: list[AccessibleViewRoute]) -> list[AccessibleViewRoute]:
|
|
256
|
-
return [
|
|
257
|
-
*[route for route in routes if route.kind == "custom"],
|
|
258
|
-
*[route for route in routes if route.kind != "custom"],
|
|
259
|
-
]
|
|
260
|
-
|
|
261
|
-
|
|
262
218
|
@dataclass(slots=True)
|
|
263
219
|
class RecordContextRouteProbe:
|
|
264
220
|
route: AccessibleViewRoute
|
|
@@ -366,12 +322,11 @@ class RecordTools(ToolBase):
|
|
|
366
322
|
"""注册当前工具到 MCP 服务。"""
|
|
367
323
|
@mcp.tool()
|
|
368
324
|
def record_insert_schema_get(
|
|
369
|
-
profile: str = DEFAULT_PROFILE,
|
|
370
325
|
app_key: str = "",
|
|
371
326
|
output_profile: str = "normal",
|
|
372
327
|
) -> JSONObject:
|
|
373
328
|
return self.record_insert_schema_get_public(
|
|
374
|
-
profile=
|
|
329
|
+
profile=DEFAULT_PROFILE,
|
|
375
330
|
app_key=app_key,
|
|
376
331
|
output_profile=output_profile,
|
|
377
332
|
)
|
|
@@ -454,7 +409,6 @@ class RecordTools(ToolBase):
|
|
|
454
409
|
where: list[JSONObject] | None = None,
|
|
455
410
|
order_by: list[JSONObject] | None = None,
|
|
456
411
|
page: int = 1,
|
|
457
|
-
page_size: int = DEFAULT_RECORD_LIST_RETURN_LIMIT,
|
|
458
412
|
view_id: str | None = None,
|
|
459
413
|
output_profile: str = "normal",
|
|
460
414
|
) -> JSONObject:
|
|
@@ -467,7 +421,6 @@ class RecordTools(ToolBase):
|
|
|
467
421
|
where=where or [],
|
|
468
422
|
order_by=order_by or [],
|
|
469
423
|
page=page,
|
|
470
|
-
page_size=page_size,
|
|
471
424
|
view_id=view_id,
|
|
472
425
|
list_type=None,
|
|
473
426
|
view_key=None,
|
|
@@ -483,7 +436,6 @@ class RecordTools(ToolBase):
|
|
|
483
436
|
)
|
|
484
437
|
)
|
|
485
438
|
def record_access(
|
|
486
|
-
profile: str = DEFAULT_PROFILE,
|
|
487
439
|
app_key: str = "",
|
|
488
440
|
view_id: str = "",
|
|
489
441
|
columns: list[JSONObject | int] | None = None,
|
|
@@ -491,7 +443,7 @@ class RecordTools(ToolBase):
|
|
|
491
443
|
order_by: list[JSONObject] | None = None,
|
|
492
444
|
) -> JSONObject:
|
|
493
445
|
return self.record_access(
|
|
494
|
-
profile=
|
|
446
|
+
profile=DEFAULT_PROFILE,
|
|
495
447
|
app_key=app_key,
|
|
496
448
|
view_id=view_id,
|
|
497
449
|
columns=columns or [],
|
|
@@ -506,6 +458,7 @@ class RecordTools(ToolBase):
|
|
|
506
458
|
record_id: str = "",
|
|
507
459
|
columns: list[JSONObject | int] | None = None,
|
|
508
460
|
view_id: str | None = None,
|
|
461
|
+
workflow_node_id: int | None = None,
|
|
509
462
|
output_profile: str = "detail_context",
|
|
510
463
|
) -> JSONObject:
|
|
511
464
|
return self.record_get_public(
|
|
@@ -514,11 +467,11 @@ class RecordTools(ToolBase):
|
|
|
514
467
|
record_id=record_id,
|
|
515
468
|
columns=columns or [],
|
|
516
469
|
view_id=view_id,
|
|
517
|
-
workflow_node_id=
|
|
470
|
+
workflow_node_id=workflow_node_id,
|
|
518
471
|
output_profile=output_profile,
|
|
519
472
|
)
|
|
520
473
|
|
|
521
|
-
@mcp.tool(description="Read all visible data logs and workflow logs for one Qingflow record into local JSONL files.
|
|
474
|
+
@mcp.tool(description="Read all visible data logs and workflow logs for one Qingflow record into local JSONL files. This tool hides pagination and returns file paths plus completeness metadata.")
|
|
522
475
|
def record_logs_get(
|
|
523
476
|
profile: str = DEFAULT_PROFILE,
|
|
524
477
|
app_key: str = "",
|
|
@@ -534,13 +487,12 @@ class RecordTools(ToolBase):
|
|
|
534
487
|
|
|
535
488
|
@mcp.tool()
|
|
536
489
|
def record_browse_schema_get(
|
|
537
|
-
profile: str = DEFAULT_PROFILE,
|
|
538
490
|
app_key: str = "",
|
|
539
491
|
view_id: str = "",
|
|
540
492
|
output_profile: str = "normal",
|
|
541
493
|
) -> JSONObject:
|
|
542
494
|
return self.record_browse_schema_get_public(
|
|
543
|
-
profile=
|
|
495
|
+
profile=DEFAULT_PROFILE,
|
|
544
496
|
app_key=app_key,
|
|
545
497
|
view_id=view_id,
|
|
546
498
|
output_profile=output_profile,
|
|
@@ -548,17 +500,14 @@ class RecordTools(ToolBase):
|
|
|
548
500
|
|
|
549
501
|
@mcp.tool()
|
|
550
502
|
def record_update_schema_get(
|
|
551
|
-
profile: str = DEFAULT_PROFILE,
|
|
552
503
|
app_key: str = "",
|
|
553
504
|
record_id: str = "",
|
|
554
|
-
view_id: str | None = None,
|
|
555
505
|
output_profile: str = "normal",
|
|
556
506
|
) -> JSONObject:
|
|
557
507
|
return self.record_update_schema_get_public(
|
|
558
|
-
profile=
|
|
508
|
+
profile=DEFAULT_PROFILE,
|
|
559
509
|
app_key=app_key,
|
|
560
510
|
record_id=record_id,
|
|
561
|
-
view_id=view_id,
|
|
562
511
|
output_profile=output_profile,
|
|
563
512
|
)
|
|
564
513
|
|
|
@@ -571,14 +520,13 @@ class RecordTools(ToolBase):
|
|
|
571
520
|
)
|
|
572
521
|
)
|
|
573
522
|
def record_insert(
|
|
574
|
-
profile: str = DEFAULT_PROFILE,
|
|
575
523
|
app_key: str = "",
|
|
576
524
|
items: list[JSONObject] | None = None,
|
|
577
525
|
verify_write: bool = True,
|
|
578
526
|
output_profile: str = "normal",
|
|
579
527
|
) -> JSONObject:
|
|
580
528
|
return self.record_insert_public(
|
|
581
|
-
profile=
|
|
529
|
+
profile=DEFAULT_PROFILE,
|
|
582
530
|
app_key=app_key,
|
|
583
531
|
items=items,
|
|
584
532
|
verify_write=verify_write,
|
|
@@ -589,29 +537,25 @@ class RecordTools(ToolBase):
|
|
|
589
537
|
description=(
|
|
590
538
|
"Update one Qingflow record using a field map. "
|
|
591
539
|
"For simple field changes, call this tool directly after the target record is clear. "
|
|
592
|
-
"Pass view_id when the frontend detail view is known; the tool will try that view first. "
|
|
593
540
|
"It first tries the data-manager direct route, then falls back to the frontend custom-view edit route when available. "
|
|
594
541
|
"Use record_update_schema_get for diagnostics or complex field-scope inspection."
|
|
595
542
|
)
|
|
596
543
|
)
|
|
597
544
|
def record_update(
|
|
598
|
-
profile: str = DEFAULT_PROFILE,
|
|
599
545
|
app_key: str = "",
|
|
600
546
|
record_id: str | None = None,
|
|
601
547
|
fields: JSONObject | None = None,
|
|
602
548
|
items: list[JSONObject] | None = None,
|
|
603
|
-
view_id: str | None = None,
|
|
604
549
|
dry_run: bool = False,
|
|
605
550
|
verify_write: bool = True,
|
|
606
551
|
output_profile: str = "normal",
|
|
607
552
|
) -> JSONObject:
|
|
608
553
|
return self.record_update_public(
|
|
609
|
-
profile=
|
|
554
|
+
profile=DEFAULT_PROFILE,
|
|
610
555
|
app_key=app_key,
|
|
611
556
|
record_id=record_id,
|
|
612
557
|
fields=fields,
|
|
613
558
|
items=items,
|
|
614
|
-
view_id=view_id,
|
|
615
559
|
dry_run=dry_run,
|
|
616
560
|
verify_write=verify_write,
|
|
617
561
|
output_profile=output_profile,
|
|
@@ -620,23 +564,20 @@ class RecordTools(ToolBase):
|
|
|
620
564
|
@mcp.tool(
|
|
621
565
|
description=(
|
|
622
566
|
"Delete Qingflow records by record_id or record_ids. "
|
|
623
|
-
"
|
|
567
|
+
"This tool does not accept view selectors; resolve target record ids first, then delete by id."
|
|
624
568
|
)
|
|
625
569
|
)
|
|
626
570
|
def record_delete(
|
|
627
|
-
profile: str = DEFAULT_PROFILE,
|
|
628
571
|
app_key: str = "",
|
|
629
572
|
record_id: str | None = None,
|
|
630
573
|
record_ids: list[str] | None = None,
|
|
631
|
-
view_id: str | None = None,
|
|
632
574
|
output_profile: str = "normal",
|
|
633
575
|
) -> JSONObject:
|
|
634
576
|
return self.record_delete_public(
|
|
635
|
-
profile=
|
|
577
|
+
profile=DEFAULT_PROFILE,
|
|
636
578
|
app_key=app_key,
|
|
637
579
|
record_id=record_id,
|
|
638
580
|
record_ids=record_ids or [],
|
|
639
|
-
view_id=view_id,
|
|
640
581
|
output_profile=output_profile,
|
|
641
582
|
)
|
|
642
583
|
|
|
@@ -908,7 +849,6 @@ class RecordTools(ToolBase):
|
|
|
908
849
|
profile: str = DEFAULT_PROFILE,
|
|
909
850
|
app_key: str,
|
|
910
851
|
record_id: Any,
|
|
911
|
-
view_id: str | None = None,
|
|
912
852
|
output_profile: str = "normal",
|
|
913
853
|
) -> JSONObject:
|
|
914
854
|
"""执行记录相关逻辑。"""
|
|
@@ -920,44 +860,21 @@ class RecordTools(ToolBase):
|
|
|
920
860
|
def runner(session_profile, context):
|
|
921
861
|
request_route = self._request_route_payload(context)
|
|
922
862
|
self._clear_record_schema_caches(profile=profile, app_key=app_key, clear_view_caches=True)
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
)
|
|
938
|
-
app_index = _merge_field_indexes(app_index, linked_hidden_index)
|
|
939
|
-
linkage_payloads_by_field_id = _build_static_schema_linkage_payloads(
|
|
940
|
-
index=app_index,
|
|
941
|
-
question_relations=question_relations,
|
|
942
|
-
)
|
|
943
|
-
preferred_view_id = _normalize_optional_text(view_id)
|
|
863
|
+
app_schema = self._get_form_schema(profile, context, app_key, force_refresh=True)
|
|
864
|
+
question_relations = _collect_question_relations(app_schema)
|
|
865
|
+
app_index = _build_applicant_top_level_field_index(app_schema)
|
|
866
|
+
linked_field_ids = _collect_linked_required_field_ids(question_relations)
|
|
867
|
+
linked_field_ids.update(_collect_option_linked_field_ids(app_index))
|
|
868
|
+
linked_hidden_index = _build_applicant_hidden_linked_top_level_field_index(
|
|
869
|
+
app_schema,
|
|
870
|
+
linked_field_ids=linked_field_ids,
|
|
871
|
+
)
|
|
872
|
+
app_index = _merge_field_indexes(app_index, linked_hidden_index)
|
|
873
|
+
linkage_payloads_by_field_id = _build_static_schema_linkage_payloads(
|
|
874
|
+
index=app_index,
|
|
875
|
+
question_relations=question_relations,
|
|
876
|
+
)
|
|
944
877
|
candidate_routes = self._candidate_update_views(profile, context, app_key)
|
|
945
|
-
if preferred_view_id:
|
|
946
|
-
preferred_route = next(
|
|
947
|
-
(
|
|
948
|
-
route
|
|
949
|
-
for route in candidate_routes
|
|
950
|
-
if route.view_id == preferred_view_id
|
|
951
|
-
),
|
|
952
|
-
None,
|
|
953
|
-
)
|
|
954
|
-
if preferred_route is None:
|
|
955
|
-
raise_tool_error(
|
|
956
|
-
QingflowApiError.config_error(
|
|
957
|
-
f"view_id '{preferred_view_id}' is not an accessible update candidate"
|
|
958
|
-
)
|
|
959
|
-
)
|
|
960
|
-
candidate_routes = [preferred_route]
|
|
961
878
|
probes = self._probe_candidate_record_contexts(
|
|
962
879
|
context,
|
|
963
880
|
app_key=app_key,
|
|
@@ -1059,7 +976,6 @@ class RecordTools(ToolBase):
|
|
|
1059
976
|
output_profile=normalized_output_profile,
|
|
1060
977
|
view_probe_summary=probe_summary,
|
|
1061
978
|
ambiguous_fields=[],
|
|
1062
|
-
preferred_view_id=preferred_view_id,
|
|
1063
979
|
)
|
|
1064
980
|
|
|
1065
981
|
ambiguous_field_ids: set[int] = set()
|
|
@@ -1106,7 +1022,6 @@ class RecordTools(ToolBase):
|
|
|
1106
1022
|
output_profile=normalized_output_profile,
|
|
1107
1023
|
view_probe_summary=probe_summary,
|
|
1108
1024
|
ambiguous_fields=ambiguous_fields,
|
|
1109
|
-
preferred_view_id=preferred_view_id,
|
|
1110
1025
|
)
|
|
1111
1026
|
|
|
1112
1027
|
response: JSONObject = {
|
|
@@ -1131,8 +1046,6 @@ class RecordTools(ToolBase):
|
|
|
1131
1046
|
"message": "record_update will try data-manager direct edit first, then a matching custom-view edit route, then a unique current-user todo save-only route when the target fields are editable on that workflow node.",
|
|
1132
1047
|
},
|
|
1133
1048
|
}
|
|
1134
|
-
if preferred_view_id:
|
|
1135
|
-
response["preferred_view_id"] = preferred_view_id
|
|
1136
1049
|
if normalized_output_profile == "verbose":
|
|
1137
1050
|
response["view_probe_summary"] = probe_summary
|
|
1138
1051
|
response["record_context_probe"] = probe_summary
|
|
@@ -1193,7 +1106,6 @@ class RecordTools(ToolBase):
|
|
|
1193
1106
|
output_profile: str,
|
|
1194
1107
|
view_probe_summary: list[JSONObject],
|
|
1195
1108
|
ambiguous_fields: list[JSONObject],
|
|
1196
|
-
preferred_view_id: str | None = None,
|
|
1197
1109
|
) -> JSONObject:
|
|
1198
1110
|
"""执行内部辅助逻辑。"""
|
|
1199
1111
|
response: JSONObject = {
|
|
@@ -1211,8 +1123,6 @@ class RecordTools(ToolBase):
|
|
|
1211
1123
|
"payload_template": {},
|
|
1212
1124
|
"recommended_next_actions": recommended_next_actions,
|
|
1213
1125
|
}
|
|
1214
|
-
if preferred_view_id:
|
|
1215
|
-
response["preferred_view_id"] = preferred_view_id
|
|
1216
1126
|
if output_profile == "verbose":
|
|
1217
1127
|
response["view_probe_summary"] = view_probe_summary
|
|
1218
1128
|
response["ambiguous_fields"] = ambiguous_fields
|
|
@@ -1530,58 +1440,24 @@ class RecordTools(ToolBase):
|
|
|
1530
1440
|
)
|
|
1531
1441
|
warnings: list[JSONObject] = []
|
|
1532
1442
|
scope_source = "static_applicant_scope"
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
context,
|
|
1538
|
-
app_key=app_key,
|
|
1539
|
-
record_id=record_id_int,
|
|
1540
|
-
workflow_node_id=workflow_node_id,
|
|
1541
|
-
fields=normalized_fields,
|
|
1542
|
-
)
|
|
1543
|
-
items = self._resolve_member_candidates_backend(context, field, keyword=keyword, state=state)
|
|
1544
|
-
scope_source = "backend_runtime_scope"
|
|
1545
|
-
else:
|
|
1546
|
-
items: list[JSONObject] | None = None
|
|
1547
|
-
if self._member_candidate_static_preview_should_use_backend(field):
|
|
1548
|
-
state = self._build_candidate_lookup_state(
|
|
1549
|
-
profile,
|
|
1550
|
-
context,
|
|
1551
|
-
app_key=app_key,
|
|
1552
|
-
record_id=None,
|
|
1553
|
-
workflow_node_id=None,
|
|
1554
|
-
fields={},
|
|
1555
|
-
)
|
|
1556
|
-
items = self._resolve_member_candidates_backend(context, field, keyword=keyword, state=state)
|
|
1557
|
-
scope_source = "backend_applicant_scope"
|
|
1558
|
-
if items is None:
|
|
1559
|
-
items = self._resolve_member_candidates(context, field, keyword=keyword)
|
|
1560
|
-
warnings.append(
|
|
1561
|
-
{
|
|
1562
|
-
"code": "CANDIDATE_SCOPE_PREVIEW_ONLY",
|
|
1563
|
-
"message": "candidate scope is a static applicant-node preview; pass record_id/workflow_node_id/fields to match runtime write scope",
|
|
1564
|
-
}
|
|
1565
|
-
)
|
|
1566
|
-
except (RecordInputError, QingflowApiError) as error:
|
|
1567
|
-
record_error = (
|
|
1568
|
-
error
|
|
1569
|
-
if isinstance(error, RecordInputError)
|
|
1570
|
-
else self._candidate_lookup_error(kind="member", field=field, value=keyword, error=error)
|
|
1571
|
-
)
|
|
1572
|
-
return self._candidate_lookup_failed_response(
|
|
1573
|
-
profile=profile,
|
|
1574
|
-
session_profile=session_profile,
|
|
1575
|
-
context=context,
|
|
1576
|
-
kind="member",
|
|
1577
|
-
error=record_error,
|
|
1578
|
-
field=field,
|
|
1443
|
+
if runtime_lookup:
|
|
1444
|
+
state = self._build_candidate_lookup_state(
|
|
1445
|
+
profile,
|
|
1446
|
+
context,
|
|
1579
1447
|
app_key=app_key,
|
|
1580
|
-
|
|
1448
|
+
record_id=record_id_int,
|
|
1581
1449
|
workflow_node_id=workflow_node_id,
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1450
|
+
fields=normalized_fields,
|
|
1451
|
+
)
|
|
1452
|
+
items = self._resolve_member_candidates_backend(context, field, keyword=keyword, state=state)
|
|
1453
|
+
scope_source = "backend_runtime_scope"
|
|
1454
|
+
else:
|
|
1455
|
+
items = self._resolve_member_candidates(context, field, keyword=keyword)
|
|
1456
|
+
warnings.append(
|
|
1457
|
+
{
|
|
1458
|
+
"code": "CANDIDATE_SCOPE_PREVIEW_ONLY",
|
|
1459
|
+
"message": "candidate scope is a static applicant-node preview; pass record_id/workflow_node_id/fields to match runtime write scope",
|
|
1460
|
+
}
|
|
1585
1461
|
)
|
|
1586
1462
|
total = len(items)
|
|
1587
1463
|
start = (page_num - 1) * page_size
|
|
@@ -1674,75 +1550,41 @@ class RecordTools(ToolBase):
|
|
|
1674
1550
|
)
|
|
1675
1551
|
warnings: list[JSONObject] = []
|
|
1676
1552
|
scope_source = "static_applicant_scope"
|
|
1677
|
-
|
|
1678
|
-
|
|
1553
|
+
if runtime_lookup:
|
|
1554
|
+
state = self._build_candidate_lookup_state(
|
|
1555
|
+
profile,
|
|
1556
|
+
context,
|
|
1557
|
+
app_key=app_key,
|
|
1558
|
+
record_id=record_id_int,
|
|
1559
|
+
workflow_node_id=workflow_node_id,
|
|
1560
|
+
fields=normalized_fields,
|
|
1561
|
+
)
|
|
1562
|
+
items = self._resolve_department_candidates_backend(context, field, keyword=keyword, state=state)
|
|
1563
|
+
scope_source = "backend_runtime_scope"
|
|
1564
|
+
else:
|
|
1565
|
+
items = self._resolve_department_candidates(context, field, keyword=keyword)
|
|
1566
|
+
scope = field.dept_select_scope if isinstance(field.dept_select_scope, dict) else {}
|
|
1567
|
+
if (
|
|
1568
|
+
not items
|
|
1569
|
+
and field.dept_select_scope_type == 2
|
|
1570
|
+
and not _scope_has_dynamic_or_external(scope)
|
|
1571
|
+
and not list(scope.get("depart") or [])
|
|
1572
|
+
):
|
|
1679
1573
|
state = self._build_candidate_lookup_state(
|
|
1680
1574
|
profile,
|
|
1681
1575
|
context,
|
|
1682
1576
|
app_key=app_key,
|
|
1683
|
-
record_id=
|
|
1684
|
-
workflow_node_id=
|
|
1685
|
-
fields=
|
|
1577
|
+
record_id=None,
|
|
1578
|
+
workflow_node_id=None,
|
|
1579
|
+
fields={},
|
|
1686
1580
|
)
|
|
1687
1581
|
items = self._resolve_department_candidates_backend(context, field, keyword=keyword, state=state)
|
|
1688
1582
|
scope_source = "backend_runtime_scope"
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
context,
|
|
1695
|
-
app_key=app_key,
|
|
1696
|
-
record_id=None,
|
|
1697
|
-
workflow_node_id=None,
|
|
1698
|
-
fields={},
|
|
1699
|
-
)
|
|
1700
|
-
items = self._resolve_department_candidates_backend(context, field, keyword=keyword, state=state)
|
|
1701
|
-
scope_source = "backend_applicant_scope"
|
|
1702
|
-
if items is None:
|
|
1703
|
-
items = self._resolve_department_candidates(context, field, keyword=keyword)
|
|
1704
|
-
scope = field.dept_select_scope if isinstance(field.dept_select_scope, dict) else {}
|
|
1705
|
-
if (
|
|
1706
|
-
not items
|
|
1707
|
-
and field.dept_select_scope_type == 2
|
|
1708
|
-
and not _scope_has_dynamic_or_external(scope)
|
|
1709
|
-
and not list(scope.get("depart") or [])
|
|
1710
|
-
):
|
|
1711
|
-
state = self._build_candidate_lookup_state(
|
|
1712
|
-
profile,
|
|
1713
|
-
context,
|
|
1714
|
-
app_key=app_key,
|
|
1715
|
-
record_id=None,
|
|
1716
|
-
workflow_node_id=None,
|
|
1717
|
-
fields={},
|
|
1718
|
-
)
|
|
1719
|
-
items = self._resolve_department_candidates_backend(context, field, keyword=keyword, state=state)
|
|
1720
|
-
scope_source = "backend_applicant_scope"
|
|
1721
|
-
warnings.append(
|
|
1722
|
-
{
|
|
1723
|
-
"code": "CANDIDATE_SCOPE_PREVIEW_ONLY",
|
|
1724
|
-
"message": "candidate scope is a static applicant-node preview; pass record_id/workflow_node_id/fields to match runtime write scope",
|
|
1725
|
-
}
|
|
1726
|
-
)
|
|
1727
|
-
except (RecordInputError, QingflowApiError) as error:
|
|
1728
|
-
record_error = (
|
|
1729
|
-
error
|
|
1730
|
-
if isinstance(error, RecordInputError)
|
|
1731
|
-
else self._candidate_lookup_error(kind="department", field=field, value=keyword, error=error)
|
|
1732
|
-
)
|
|
1733
|
-
return self._candidate_lookup_failed_response(
|
|
1734
|
-
profile=profile,
|
|
1735
|
-
session_profile=session_profile,
|
|
1736
|
-
context=context,
|
|
1737
|
-
kind="department",
|
|
1738
|
-
error=record_error,
|
|
1739
|
-
field=field,
|
|
1740
|
-
app_key=app_key,
|
|
1741
|
-
record_id_text=record_id_text,
|
|
1742
|
-
workflow_node_id=workflow_node_id,
|
|
1743
|
-
fields_present=bool(normalized_fields),
|
|
1744
|
-
keyword=keyword,
|
|
1745
|
-
scope_source=scope_source,
|
|
1583
|
+
warnings.append(
|
|
1584
|
+
{
|
|
1585
|
+
"code": "CANDIDATE_SCOPE_PREVIEW_ONLY",
|
|
1586
|
+
"message": "candidate scope is a static applicant-node preview; pass record_id/workflow_node_id/fields to match runtime write scope",
|
|
1587
|
+
}
|
|
1746
1588
|
)
|
|
1747
1589
|
total = len(items)
|
|
1748
1590
|
start = (page_num - 1) * page_size
|
|
@@ -1804,21 +1646,6 @@ class RecordTools(ToolBase):
|
|
|
1804
1646
|
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
1805
1647
|
if limit <= 0:
|
|
1806
1648
|
raise_tool_error(QingflowApiError.config_error("limit must be positive"))
|
|
1807
|
-
if not (
|
|
1808
|
-
_normalize_optional_text(view_id)
|
|
1809
|
-
or list_type is not None
|
|
1810
|
-
or _normalize_optional_text(view_key)
|
|
1811
|
-
or _normalize_optional_text(view_name)
|
|
1812
|
-
):
|
|
1813
|
-
raise_tool_error(
|
|
1814
|
-
QingflowApiError.config_error(
|
|
1815
|
-
"record_analyze requires view_id. Call app_get first and pass accessible_views[].view_id.",
|
|
1816
|
-
details={
|
|
1817
|
-
"error_code": "RECORD_ANALYZE_VIEW_REQUIRED",
|
|
1818
|
-
"fix_hint": "Call app_get first and choose one accessible_views[].view_id, then retry record_analyze with view_id.",
|
|
1819
|
-
},
|
|
1820
|
-
)
|
|
1821
|
-
)
|
|
1822
1649
|
legacy_warnings = _detect_analyze_legacy_warnings(
|
|
1823
1650
|
dimensions=dimensions,
|
|
1824
1651
|
metrics=metrics,
|
|
@@ -1835,7 +1662,7 @@ class RecordTools(ToolBase):
|
|
|
1835
1662
|
list_type=list_type,
|
|
1836
1663
|
view_key=view_key,
|
|
1837
1664
|
view_name=view_name,
|
|
1838
|
-
allow_default=
|
|
1665
|
+
allow_default=True,
|
|
1839
1666
|
)
|
|
1840
1667
|
if not _view_type_supports_analysis(resolved_view.view_type):
|
|
1841
1668
|
raise_tool_error(
|
|
@@ -1898,7 +1725,6 @@ class RecordTools(ToolBase):
|
|
|
1898
1725
|
order_by: list[JSONObject],
|
|
1899
1726
|
limit: int = DEFAULT_RECORD_LIST_RETURN_LIMIT,
|
|
1900
1727
|
page: int = 1,
|
|
1901
|
-
page_size: int = DEFAULT_RECORD_LIST_RETURN_LIMIT,
|
|
1902
1728
|
view_id: str | None = None,
|
|
1903
1729
|
list_type: int | None = None,
|
|
1904
1730
|
view_key: str | None = None,
|
|
@@ -1917,23 +1743,6 @@ class RecordTools(ToolBase):
|
|
|
1917
1743
|
raise_tool_error(QingflowApiError.config_error("limit must be positive"))
|
|
1918
1744
|
if page <= 0:
|
|
1919
1745
|
raise_tool_error(QingflowApiError.config_error("page must be positive"))
|
|
1920
|
-
if page_size <= 0:
|
|
1921
|
-
raise_tool_error(QingflowApiError.config_error("page_size must be positive"))
|
|
1922
|
-
if not (
|
|
1923
|
-
_normalize_optional_text(view_id)
|
|
1924
|
-
or list_type is not None
|
|
1925
|
-
or _normalize_optional_text(view_key)
|
|
1926
|
-
or _normalize_optional_text(view_name)
|
|
1927
|
-
):
|
|
1928
|
-
raise_tool_error(
|
|
1929
|
-
QingflowApiError.config_error(
|
|
1930
|
-
"record_list requires view_id. Call app_get first and pass accessible_views[].view_id.",
|
|
1931
|
-
details={
|
|
1932
|
-
"error_code": "RECORD_LIST_VIEW_REQUIRED",
|
|
1933
|
-
"fix_hint": "Call app_get first and choose one accessible_views[].view_id, then retry record_list with view_id.",
|
|
1934
|
-
},
|
|
1935
|
-
)
|
|
1936
|
-
)
|
|
1937
1746
|
view_route, compatibility_warnings = self._resolve_accessible_view_route_for_public(
|
|
1938
1747
|
profile=profile,
|
|
1939
1748
|
app_key=app_key,
|
|
@@ -1941,7 +1750,7 @@ class RecordTools(ToolBase):
|
|
|
1941
1750
|
list_type=list_type,
|
|
1942
1751
|
view_key=view_key,
|
|
1943
1752
|
view_name=view_name,
|
|
1944
|
-
allow_default=
|
|
1753
|
+
allow_default=True,
|
|
1945
1754
|
)
|
|
1946
1755
|
if not _view_type_supports_analysis(view_route.view_type):
|
|
1947
1756
|
raise_tool_error(
|
|
@@ -1991,12 +1800,12 @@ class RecordTools(ToolBase):
|
|
|
1991
1800
|
app_key=app_key,
|
|
1992
1801
|
view_route=view_route,
|
|
1993
1802
|
page_num=page,
|
|
1994
|
-
page_size=
|
|
1803
|
+
page_size=DEFAULT_LIST_PAGE_SIZE,
|
|
1995
1804
|
query_key=normalized_query,
|
|
1996
1805
|
search_que_ids=resolved_query_fields or None,
|
|
1997
1806
|
match_rules=match_rules,
|
|
1998
1807
|
sort_rules=sort_rules,
|
|
1999
|
-
max_rows=
|
|
1808
|
+
max_rows=limit,
|
|
2000
1809
|
selected_fields=selected_fields,
|
|
2001
1810
|
output_profile="verbose" if normalized_output_profile in {"verbose", "normalized"} else DEFAULT_OUTPUT_PROFILE,
|
|
2002
1811
|
)
|
|
@@ -2415,7 +2224,6 @@ class RecordTools(ToolBase):
|
|
|
2415
2224
|
requested_output_profile = _normalize_record_get_detail_output_profile(output_profile)
|
|
2416
2225
|
record_id_int = normalize_positive_id_int(record_id, field_name="record_id")
|
|
2417
2226
|
normalized_columns = _normalize_public_column_selectors(columns)
|
|
2418
|
-
explicit_view_id = _normalize_optional_text(view_id)
|
|
2419
2227
|
|
|
2420
2228
|
def runner(session_profile, context):
|
|
2421
2229
|
resolved_view, compatibility_warnings = self._resolve_accessible_view_route(
|
|
@@ -2441,45 +2249,17 @@ class RecordTools(ToolBase):
|
|
|
2441
2249
|
"code": "OUTPUT_PROFILE_DEPRECATED_FOR_DETAIL_CONTEXT",
|
|
2442
2250
|
"message": f"output_profile={requested_output_profile!r} is deprecated for record_get; detail_context is always returned.",
|
|
2443
2251
|
})
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
)
|
|
2456
|
-
|
|
2457
|
-
try:
|
|
2458
|
-
return get_detail_for_route(resolved_view, warnings)
|
|
2459
|
-
except QingflowApiError as exc:
|
|
2460
|
-
if explicit_view_id is not None:
|
|
2461
|
-
raise
|
|
2462
|
-
if not self._is_record_context_route_miss(exc):
|
|
2463
|
-
raise
|
|
2464
|
-
fallback_warnings = list(warnings)
|
|
2465
|
-
fallback_warnings.append(
|
|
2466
|
-
{
|
|
2467
|
-
"code": "DEFAULT_DETAIL_ROUTE_DENIED",
|
|
2468
|
-
"message": "record_get default system:all route was not readable; trying accessible views that match the frontend route model.",
|
|
2469
|
-
"backend_code": exc.backend_code,
|
|
2470
|
-
}
|
|
2471
|
-
)
|
|
2472
|
-
last_error = exc
|
|
2473
|
-
for candidate in self._candidate_update_views(profile, context, app_key):
|
|
2474
|
-
if candidate.view_id == resolved_view.view_id:
|
|
2475
|
-
continue
|
|
2476
|
-
try:
|
|
2477
|
-
return get_detail_for_route(candidate, fallback_warnings)
|
|
2478
|
-
except QingflowApiError as candidate_exc:
|
|
2479
|
-
if not self._is_record_context_route_miss(candidate_exc):
|
|
2480
|
-
raise
|
|
2481
|
-
last_error = candidate_exc
|
|
2482
|
-
raise last_error
|
|
2252
|
+
return self._record_get_detail_context(
|
|
2253
|
+
profile=profile,
|
|
2254
|
+
session_profile=session_profile,
|
|
2255
|
+
context=context,
|
|
2256
|
+
app_key=app_key,
|
|
2257
|
+
record_id_int=record_id_int,
|
|
2258
|
+
resolved_view=resolved_view,
|
|
2259
|
+
requested_focus_field_ids=normalized_columns,
|
|
2260
|
+
workflow_node_id=workflow_node_id,
|
|
2261
|
+
warnings=warnings,
|
|
2262
|
+
)
|
|
2483
2263
|
|
|
2484
2264
|
return self._run_record_tool(profile, runner)
|
|
2485
2265
|
|
|
@@ -2494,16 +2274,6 @@ class RecordTools(ToolBase):
|
|
|
2494
2274
|
) -> JSONObject:
|
|
2495
2275
|
"""读取单条记录可见的全量数据日志和流程日志,写入本地 JSONL。"""
|
|
2496
2276
|
record_id_int = normalize_positive_id_int(record_id, field_name="record_id")
|
|
2497
|
-
if not _normalize_optional_text(view_id):
|
|
2498
|
-
raise_tool_error(
|
|
2499
|
-
QingflowApiError.config_error(
|
|
2500
|
-
"record_logs_get requires view_id. Call app_get first and pass accessible_views[].view_id.",
|
|
2501
|
-
details={
|
|
2502
|
-
"error_code": "RECORD_LOGS_VIEW_REQUIRED",
|
|
2503
|
-
"fix_hint": "Call app_get first and choose one accessible_views[].view_id, then retry record_logs_get with view_id.",
|
|
2504
|
-
},
|
|
2505
|
-
)
|
|
2506
|
-
)
|
|
2507
2277
|
|
|
2508
2278
|
def runner(session_profile, context):
|
|
2509
2279
|
resolved_view, compatibility_warnings = self._resolve_accessible_view_route(
|
|
@@ -2514,45 +2284,21 @@ class RecordTools(ToolBase):
|
|
|
2514
2284
|
list_type=None,
|
|
2515
2285
|
view_key=None,
|
|
2516
2286
|
view_name=None,
|
|
2517
|
-
allow_default=
|
|
2287
|
+
allow_default=True,
|
|
2518
2288
|
)
|
|
2519
2289
|
warnings: list[JSONObject] = []
|
|
2520
2290
|
warnings.extend(compatibility_warnings)
|
|
2521
2291
|
warnings.extend(_view_filter_trust_warnings(resolved_view))
|
|
2522
2292
|
unavailable_context: list[JSONObject] = []
|
|
2523
2293
|
|
|
2524
|
-
schema
|
|
2525
|
-
try:
|
|
2526
|
-
schema = self._record_get_detail_schema(profile, context, app_key, resolved_view, force_refresh=False)
|
|
2527
|
-
except QingflowApiError as exc:
|
|
2528
|
-
if not _is_optional_schema_permission_error(exc):
|
|
2529
|
-
raise
|
|
2530
|
-
unavailable_context.append(
|
|
2531
|
-
_record_detail_unavailable_context(
|
|
2532
|
-
"detail_schema",
|
|
2533
|
-
"记录日志字段结构辅助信息获取失败,已尝试使用详情主数据中的字段信息继续。",
|
|
2534
|
-
exc,
|
|
2535
|
-
)
|
|
2536
|
-
)
|
|
2294
|
+
schema = self._record_get_detail_schema(profile, context, app_key, resolved_view, force_refresh=False)
|
|
2537
2295
|
index = _build_top_level_field_index(schema)
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
)
|
|
2545
|
-
except QingflowApiError as exc:
|
|
2546
|
-
if not _is_optional_schema_permission_error(exc):
|
|
2547
|
-
raise
|
|
2548
|
-
audit_info = {}
|
|
2549
|
-
unavailable_context.append(
|
|
2550
|
-
_record_detail_unavailable_context(
|
|
2551
|
-
"audit_info",
|
|
2552
|
-
"记录审批节点辅助信息获取失败,已继续读取详情主数据和日志。",
|
|
2553
|
-
exc,
|
|
2554
|
-
)
|
|
2555
|
-
)
|
|
2296
|
+
audit_info = self._record_get_audit_info(
|
|
2297
|
+
context,
|
|
2298
|
+
app_key=app_key,
|
|
2299
|
+
record_id=record_id_int,
|
|
2300
|
+
resolved_view=resolved_view,
|
|
2301
|
+
)
|
|
2556
2302
|
audit_context = _record_detail_audit_context(audit_info, workflow_node_id=None)
|
|
2557
2303
|
detail_result, used_list_type, used_role = self._record_get_apply_detail(
|
|
2558
2304
|
context,
|
|
@@ -2562,17 +2308,6 @@ class RecordTools(ToolBase):
|
|
|
2562
2308
|
audit_node_id=cast(int | None, audit_context.get("audit_node_id")),
|
|
2563
2309
|
)
|
|
2564
2310
|
answer_list = _record_detail_answers(detail_result)
|
|
2565
|
-
if not index.by_id:
|
|
2566
|
-
answer_index = _build_answer_backed_field_index(cast(list[JSONObject], answer_list))
|
|
2567
|
-
if answer_index.by_id:
|
|
2568
|
-
index = answer_index
|
|
2569
|
-
unavailable_context.append(
|
|
2570
|
-
{
|
|
2571
|
-
"section": "detail_schema",
|
|
2572
|
-
"message": "字段结构由详情 answers 回退构造;候选范围、只读状态、选项和联动信息可能不完整。",
|
|
2573
|
-
"category": "partial_context",
|
|
2574
|
-
}
|
|
2575
|
-
)
|
|
2576
2311
|
selected_fields = list(index.by_id.values())
|
|
2577
2312
|
fields = [
|
|
2578
2313
|
_record_detail_field_payload(field, _find_answer_for_field(cast(list[JSONValue], answer_list), field), focus_id_set=set())
|
|
@@ -2666,41 +2401,14 @@ class RecordTools(ToolBase):
|
|
|
2666
2401
|
warnings: list[JSONObject],
|
|
2667
2402
|
) -> JSONObject:
|
|
2668
2403
|
"""执行内部辅助逻辑。"""
|
|
2669
|
-
|
|
2670
|
-
schema: JSONObject = {}
|
|
2671
|
-
schema_available = True
|
|
2672
|
-
try:
|
|
2673
|
-
schema = self._record_get_detail_schema(profile, context, app_key, resolved_view, force_refresh=False)
|
|
2674
|
-
except QingflowApiError as exc:
|
|
2675
|
-
if not _is_optional_schema_permission_error(exc):
|
|
2676
|
-
raise
|
|
2677
|
-
schema_available = False
|
|
2678
|
-
unavailable_context.append(
|
|
2679
|
-
_record_detail_unavailable_context(
|
|
2680
|
-
"detail_schema",
|
|
2681
|
-
"记录详情字段结构辅助信息获取失败,已尝试使用详情主数据中的字段信息继续。",
|
|
2682
|
-
exc,
|
|
2683
|
-
)
|
|
2684
|
-
)
|
|
2404
|
+
schema = self._record_get_detail_schema(profile, context, app_key, resolved_view, force_refresh=False)
|
|
2685
2405
|
index = _build_top_level_field_index(schema)
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
)
|
|
2693
|
-
except QingflowApiError as exc:
|
|
2694
|
-
if not _is_optional_schema_permission_error(exc):
|
|
2695
|
-
raise
|
|
2696
|
-
audit_info = {}
|
|
2697
|
-
unavailable_context.append(
|
|
2698
|
-
_record_detail_unavailable_context(
|
|
2699
|
-
"audit_info",
|
|
2700
|
-
"记录审批节点辅助信息获取失败,已继续读取详情主数据。",
|
|
2701
|
-
exc,
|
|
2702
|
-
)
|
|
2703
|
-
)
|
|
2406
|
+
audit_info = self._record_get_audit_info(
|
|
2407
|
+
context,
|
|
2408
|
+
app_key=app_key,
|
|
2409
|
+
record_id=record_id_int,
|
|
2410
|
+
resolved_view=resolved_view,
|
|
2411
|
+
)
|
|
2704
2412
|
audit_context = _record_detail_audit_context(audit_info, workflow_node_id=workflow_node_id)
|
|
2705
2413
|
detail_result, used_list_type, used_role = self._record_get_apply_detail(
|
|
2706
2414
|
context,
|
|
@@ -2710,24 +2418,13 @@ class RecordTools(ToolBase):
|
|
|
2710
2418
|
audit_node_id=cast(int | None, audit_context.get("audit_node_id")),
|
|
2711
2419
|
)
|
|
2712
2420
|
answer_list = _record_detail_answers(detail_result)
|
|
2713
|
-
if not index.by_id:
|
|
2714
|
-
answer_index = _build_answer_backed_field_index(cast(list[JSONObject], answer_list))
|
|
2715
|
-
if answer_index.by_id:
|
|
2716
|
-
index = answer_index
|
|
2717
|
-
unavailable_context.append(
|
|
2718
|
-
{
|
|
2719
|
-
"section": "detail_schema",
|
|
2720
|
-
"message": "字段结构由详情 answers 回退构造;候选范围、只读状态、选项和联动信息可能不完整。",
|
|
2721
|
-
"category": "partial_context",
|
|
2722
|
-
}
|
|
2723
|
-
)
|
|
2724
2421
|
selected_fields = list(index.by_id.values())
|
|
2725
2422
|
row = _build_flat_row(cast(list[JSONValue], answer_list), selected_fields, apply_id=record_id_int)
|
|
2726
2423
|
normalized_record, _normalized_ambiguous_fields = _build_normalized_row_from_answers(
|
|
2727
2424
|
cast(list[JSONValue], answer_list),
|
|
2728
2425
|
selected_fields,
|
|
2729
2426
|
)
|
|
2730
|
-
if
|
|
2427
|
+
if self._record_get_needs_schema_refresh(
|
|
2731
2428
|
answer_list=cast(list[JSONValue], answer_list),
|
|
2732
2429
|
selected_fields=selected_fields,
|
|
2733
2430
|
record=row,
|
|
@@ -2743,6 +2440,7 @@ class RecordTools(ToolBase):
|
|
|
2743
2440
|
index = _build_top_level_field_index(schema)
|
|
2744
2441
|
selected_fields = list(index.by_id.values())
|
|
2745
2442
|
|
|
2443
|
+
unavailable_context: list[JSONObject] = []
|
|
2746
2444
|
dynamic_reference_answers, dynamic_reference_unavailable = self._record_get_dynamic_reference_answers(
|
|
2747
2445
|
context,
|
|
2748
2446
|
app_key=app_key,
|
|
@@ -2901,20 +2599,7 @@ class RecordTools(ToolBase):
|
|
|
2901
2599
|
) -> JSONObject:
|
|
2902
2600
|
"""执行内部辅助逻辑。"""
|
|
2903
2601
|
if resolved_view.kind == "custom" and resolved_view.view_selection is not None:
|
|
2904
|
-
return self.
|
|
2905
|
-
profile,
|
|
2906
|
-
context,
|
|
2907
|
-
resolved_view.view_selection.view_key,
|
|
2908
|
-
force_refresh=force_refresh,
|
|
2909
|
-
)
|
|
2910
|
-
if resolved_view.kind == "system" and resolved_view.list_type is not None:
|
|
2911
|
-
return self._get_system_browse_schema(
|
|
2912
|
-
profile,
|
|
2913
|
-
context,
|
|
2914
|
-
app_key,
|
|
2915
|
-
list_type=resolved_view.list_type,
|
|
2916
|
-
force_refresh=force_refresh,
|
|
2917
|
-
)
|
|
2602
|
+
return self._get_view_form_schema(profile, context, resolved_view.view_selection.view_key, force_refresh=force_refresh)
|
|
2918
2603
|
return self._get_form_schema(profile, context, app_key, force_refresh=force_refresh)
|
|
2919
2604
|
|
|
2920
2605
|
def _record_get_audit_info(
|
|
@@ -2975,7 +2660,7 @@ class RecordTools(ToolBase):
|
|
|
2975
2660
|
)
|
|
2976
2661
|
return result if isinstance(result, dict) else {"value": result}, list_type, role
|
|
2977
2662
|
except QingflowApiError as exc:
|
|
2978
|
-
if resolved_view.list_type is not None or
|
|
2663
|
+
if resolved_view.list_type is not None or exc.backend_code != 40002:
|
|
2979
2664
|
raise
|
|
2980
2665
|
last_error: QingflowApiError = exc
|
|
2981
2666
|
for fallback_list_type in (14, 1, 2, 12):
|
|
@@ -2993,7 +2678,7 @@ class RecordTools(ToolBase):
|
|
|
2993
2678
|
return result if isinstance(result, dict) else {"value": result}, fallback_list_type, role
|
|
2994
2679
|
except QingflowApiError as fallback_exc:
|
|
2995
2680
|
last_error = fallback_exc
|
|
2996
|
-
if
|
|
2681
|
+
if fallback_exc.backend_code == 40002:
|
|
2997
2682
|
continue
|
|
2998
2683
|
raise
|
|
2999
2684
|
raise last_error
|
|
@@ -3091,8 +2776,6 @@ class RecordTools(ToolBase):
|
|
|
3091
2776
|
if target_app_key == app_key and str(target_record_id) == str(source_record_id):
|
|
3092
2777
|
reference_payload["self_reference"] = True
|
|
3093
2778
|
except QingflowApiError as exc:
|
|
3094
|
-
if is_auth_like_error(exc):
|
|
3095
|
-
raise
|
|
3096
2779
|
unavailable = _record_detail_unavailable_context(
|
|
3097
2780
|
"reference_detail",
|
|
3098
2781
|
f"引用字段「{field.que_title}」的目标记录详情获取失败。",
|
|
@@ -3190,8 +2873,6 @@ class RecordTools(ToolBase):
|
|
|
3190
2873
|
json_body=body,
|
|
3191
2874
|
)
|
|
3192
2875
|
except QingflowApiError as exc:
|
|
3193
|
-
if is_auth_like_error(exc):
|
|
3194
|
-
raise
|
|
3195
2876
|
unavailable = _record_detail_unavailable_context(
|
|
3196
2877
|
"reference_runtime_match",
|
|
3197
2878
|
"动态引用字段匹配数据获取失败。",
|
|
@@ -3246,8 +2927,6 @@ class RecordTools(ToolBase):
|
|
|
3246
2927
|
},
|
|
3247
2928
|
)
|
|
3248
2929
|
except QingflowApiError as exc:
|
|
3249
|
-
if is_auth_like_error(exc):
|
|
3250
|
-
raise
|
|
3251
2930
|
unavailable_context.append(_record_detail_unavailable_context("log_visibility", "侧边栏日志可见性获取失败。", exc))
|
|
3252
2931
|
return {"status": "unavailable", "channel": channel, "data_log_visible": None, "workflow_log_visible": None}
|
|
3253
2932
|
payload = visibility if isinstance(visibility, dict) else {}
|
|
@@ -3301,8 +2980,6 @@ class RecordTools(ToolBase):
|
|
|
3301
2980
|
source="data_logs",
|
|
3302
2981
|
)
|
|
3303
2982
|
except QingflowApiError as exc:
|
|
3304
|
-
if is_auth_like_error(exc):
|
|
3305
|
-
raise
|
|
3306
2983
|
unavailable_context.append(_record_detail_unavailable_context("data_logs", "最近数据日志获取失败。", exc))
|
|
3307
2984
|
return _record_detail_log_unavailable_payload("data_logs", "fetch_unavailable")
|
|
3308
2985
|
|
|
@@ -3356,8 +3033,6 @@ class RecordTools(ToolBase):
|
|
|
3356
3033
|
source="workflow_logs",
|
|
3357
3034
|
)
|
|
3358
3035
|
except QingflowApiError as exc:
|
|
3359
|
-
if is_auth_like_error(exc):
|
|
3360
|
-
raise
|
|
3361
3036
|
unavailable_context.append(_record_detail_unavailable_context("workflow_logs", "流程日志本次获取失败。", exc))
|
|
3362
3037
|
return _record_detail_log_unavailable_payload("workflow_logs", "fetch_unavailable")
|
|
3363
3038
|
|
|
@@ -3401,8 +3076,6 @@ class RecordTools(ToolBase):
|
|
|
3401
3076
|
deadline=deadline,
|
|
3402
3077
|
)
|
|
3403
3078
|
except QingflowApiError as exc:
|
|
3404
|
-
if is_auth_like_error(exc):
|
|
3405
|
-
raise
|
|
3406
3079
|
unavailable_context.append(_record_detail_unavailable_context("data_logs", "全量数据日志获取失败。", exc))
|
|
3407
3080
|
return _record_logs_unavailable_payload("data_logs", "fetch_unavailable")
|
|
3408
3081
|
|
|
@@ -3462,8 +3135,6 @@ class RecordTools(ToolBase):
|
|
|
3462
3135
|
deadline=deadline,
|
|
3463
3136
|
)
|
|
3464
3137
|
except QingflowApiError as exc:
|
|
3465
|
-
if is_auth_like_error(exc):
|
|
3466
|
-
raise
|
|
3467
3138
|
unavailable_context.append(_record_detail_unavailable_context("workflow_logs", "全量流程日志获取失败。", exc))
|
|
3468
3139
|
return _record_logs_unavailable_payload("workflow_logs", "fetch_unavailable")
|
|
3469
3140
|
|
|
@@ -3496,8 +3167,6 @@ class RecordTools(ToolBase):
|
|
|
3496
3167
|
params["auditNodeId"] = audit_node_id
|
|
3497
3168
|
payload = self.backend.request("GET", context, f"/app/{app_key}/asosChart", params=params)
|
|
3498
3169
|
except QingflowApiError as exc:
|
|
3499
|
-
if is_auth_like_error(exc):
|
|
3500
|
-
raise
|
|
3501
3170
|
unavailable_context.append(_record_detail_unavailable_context("associated_resources", "关联资源获取失败。", exc))
|
|
3502
3171
|
return []
|
|
3503
3172
|
return [_record_detail_associated_resource(item) for item in _record_detail_associated_resource_items(payload)]
|
|
@@ -3535,17 +3204,16 @@ class RecordTools(ToolBase):
|
|
|
3535
3204
|
refresh_source_url=refresh_source_url,
|
|
3536
3205
|
)
|
|
3537
3206
|
except Exception as exc: # defensive: media should never break the core record detail.
|
|
3538
|
-
warning: JSONObject = {
|
|
3539
|
-
"code": "MEDIA_ASSETS_UNAVAILABLE",
|
|
3540
|
-
"message": f"record_get could not collect media assets: {exc}",
|
|
3541
|
-
}
|
|
3542
|
-
if isinstance(exc, QingflowApiError):
|
|
3543
|
-
warning.update(_record_detail_error_warning_fields(exc))
|
|
3544
3207
|
return {
|
|
3545
3208
|
"status": "unavailable",
|
|
3546
3209
|
"local_dir": None,
|
|
3547
3210
|
"items": [],
|
|
3548
|
-
"warnings": [
|
|
3211
|
+
"warnings": [
|
|
3212
|
+
{
|
|
3213
|
+
"code": "MEDIA_ASSETS_UNAVAILABLE",
|
|
3214
|
+
"message": f"record_get could not collect media assets: {exc}",
|
|
3215
|
+
}
|
|
3216
|
+
],
|
|
3549
3217
|
}
|
|
3550
3218
|
|
|
3551
3219
|
def _record_get_file_assets(
|
|
@@ -3583,17 +3251,16 @@ class RecordTools(ToolBase):
|
|
|
3583
3251
|
refresh_source_url=refresh_source_url,
|
|
3584
3252
|
)
|
|
3585
3253
|
except Exception as exc: # defensive: file assets should never break the core record detail.
|
|
3586
|
-
warning = {
|
|
3587
|
-
"code": "FILE_ASSETS_UNAVAILABLE",
|
|
3588
|
-
"message": f"record_get could not collect file assets: {exc}",
|
|
3589
|
-
}
|
|
3590
|
-
if isinstance(exc, QingflowApiError):
|
|
3591
|
-
warning.update(_record_detail_error_warning_fields(exc))
|
|
3592
3254
|
return {
|
|
3593
3255
|
"status": "unavailable",
|
|
3594
3256
|
"local_dir": None,
|
|
3595
3257
|
"items": [],
|
|
3596
|
-
"warnings": [
|
|
3258
|
+
"warnings": [
|
|
3259
|
+
{
|
|
3260
|
+
"code": "FILE_ASSETS_UNAVAILABLE",
|
|
3261
|
+
"message": f"record_get could not collect file assets: {exc}",
|
|
3262
|
+
}
|
|
3263
|
+
],
|
|
3597
3264
|
}
|
|
3598
3265
|
|
|
3599
3266
|
def _record_get_refreshed_media_source_url(
|
|
@@ -3605,7 +3272,7 @@ class RecordTools(ToolBase):
|
|
|
3605
3272
|
resolved_view: AccessibleViewRoute,
|
|
3606
3273
|
audit_node_id: int | None,
|
|
3607
3274
|
candidate: JSONObject,
|
|
3608
|
-
) ->
|
|
3275
|
+
) -> str | None:
|
|
3609
3276
|
"""Refresh the detail payload once to recover an expired attachment storage signature."""
|
|
3610
3277
|
if candidate.get("source") not in {"attachment", "image_field", "subtable"}:
|
|
3611
3278
|
return None
|
|
@@ -3621,15 +3288,8 @@ class RecordTools(ToolBase):
|
|
|
3621
3288
|
resolved_view=resolved_view,
|
|
3622
3289
|
audit_node_id=audit_node_id,
|
|
3623
3290
|
)
|
|
3624
|
-
except QingflowApiError
|
|
3625
|
-
return
|
|
3626
|
-
"source_url": None,
|
|
3627
|
-
"warning": _record_detail_unavailable_context(
|
|
3628
|
-
"asset_url_refresh",
|
|
3629
|
-
"record_get could not refresh the record detail before downloading a private asset.",
|
|
3630
|
-
exc,
|
|
3631
|
-
),
|
|
3632
|
-
}
|
|
3291
|
+
except QingflowApiError:
|
|
3292
|
+
return None
|
|
3633
3293
|
for answer in _record_detail_answers(detail_result):
|
|
3634
3294
|
if not isinstance(answer, dict) or _coerce_count(answer.get("queId")) != field_id:
|
|
3635
3295
|
continue
|
|
@@ -3665,7 +3325,6 @@ class RecordTools(ToolBase):
|
|
|
3665
3325
|
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
3666
3326
|
if items is not None:
|
|
3667
3327
|
normalized_items = self._normalize_public_record_insert_batch_items(fields=fields, items=items)
|
|
3668
|
-
self._reject_record_insert_system_fields([cast(JSONObject, item["fields"]) for item in normalized_items])
|
|
3669
3328
|
return self._record_insert_public_batch(
|
|
3670
3329
|
profile=profile,
|
|
3671
3330
|
app_key=app_key,
|
|
@@ -3675,35 +3334,15 @@ class RecordTools(ToolBase):
|
|
|
3675
3334
|
)
|
|
3676
3335
|
if fields is not None and not isinstance(fields, dict):
|
|
3677
3336
|
raise_tool_error(QingflowApiError.config_error("fields must be an object map keyed by field title"))
|
|
3678
|
-
normalized_fields = cast(JSONObject, fields or {})
|
|
3679
|
-
self._reject_record_insert_system_fields([normalized_fields])
|
|
3680
3337
|
return self._record_insert_public_single(
|
|
3681
3338
|
profile=profile,
|
|
3682
3339
|
app_key=app_key,
|
|
3683
|
-
fields=
|
|
3340
|
+
fields=cast(JSONObject, fields or {}),
|
|
3684
3341
|
verify_write=verify_write,
|
|
3685
3342
|
output_profile=normalized_output_profile,
|
|
3686
3343
|
capture_exceptions=False,
|
|
3687
3344
|
)
|
|
3688
3345
|
|
|
3689
|
-
def _reject_record_insert_system_fields(self, field_maps: list[JSONObject]) -> None:
|
|
3690
|
-
for row_index, field_map in enumerate(field_maps):
|
|
3691
|
-
for field_name in field_map:
|
|
3692
|
-
normalized_name = str(field_name or "").strip()
|
|
3693
|
-
if normalized_name in RECORD_WRITE_SYSTEM_FIELD_NAMES:
|
|
3694
|
-
raise_tool_error(
|
|
3695
|
-
QingflowApiError.config_error(
|
|
3696
|
-
f"record_insert fields must not include built-in system field '{normalized_name}'",
|
|
3697
|
-
details={
|
|
3698
|
-
"error_code": "RESERVED_SYSTEM_FIELD_NAME",
|
|
3699
|
-
"row_number": row_index + 1,
|
|
3700
|
-
"field_name": normalized_name,
|
|
3701
|
-
"reserved_field_names": sorted(RECORD_WRITE_SYSTEM_FIELD_NAMES),
|
|
3702
|
-
"fix_hint": "Remove Qingflow built-in system fields from record_insert payload. They are generated by the platform and can be read after creation, not manually inserted.",
|
|
3703
|
-
},
|
|
3704
|
-
)
|
|
3705
|
-
)
|
|
3706
|
-
|
|
3707
3346
|
def _record_insert_public_single(
|
|
3708
3347
|
self,
|
|
3709
3348
|
*,
|
|
@@ -4037,11 +3676,7 @@ class RecordTools(ToolBase):
|
|
|
4037
3676
|
"field_id": field_id,
|
|
4038
3677
|
"error_code": error_code,
|
|
4039
3678
|
"message": self._record_write_semantic_error_message(error_code, error.get("message")),
|
|
4040
|
-
"next_action": self._record_write_next_action_for_error(
|
|
4041
|
-
error_code,
|
|
4042
|
-
expected_format=expected_format,
|
|
4043
|
-
field_payload=field_payload,
|
|
4044
|
-
),
|
|
3679
|
+
"next_action": self._record_write_next_action_for_error(error_code),
|
|
4045
3680
|
}
|
|
4046
3681
|
if expected_format is not None:
|
|
4047
3682
|
payload["expected_format"] = expected_format
|
|
@@ -4152,30 +3787,8 @@ class RecordTools(ToolBase):
|
|
|
4152
3787
|
return _normalize_optional_text(fallback) or "字段值格式不正确。"
|
|
4153
3788
|
return _normalize_optional_text(fallback) or "字段写入失败。"
|
|
4154
3789
|
|
|
4155
|
-
def _record_write_next_action_for_error(
|
|
4156
|
-
self,
|
|
4157
|
-
error_code: str,
|
|
4158
|
-
*,
|
|
4159
|
-
expected_format: JSONObject | None = None,
|
|
4160
|
-
field_payload: JSONObject | None = None,
|
|
4161
|
-
) -> str:
|
|
3790
|
+
def _record_write_next_action_for_error(self, error_code: str) -> str:
|
|
4162
3791
|
"""执行内部辅助逻辑。"""
|
|
4163
|
-
kind = _normalize_optional_text((expected_format or {}).get("kind"))
|
|
4164
|
-
field_title = (
|
|
4165
|
-
_normalize_optional_text((field_payload or {}).get("que_title"))
|
|
4166
|
-
or _normalize_optional_text((field_payload or {}).get("title"))
|
|
4167
|
-
or "该字段"
|
|
4168
|
-
)
|
|
4169
|
-
if kind == "member_list":
|
|
4170
|
-
return f"{field_title} 需要有效成员;用唯一姓名/邮箱/id,歧义时先调用 record_member_candidates 后只重试本行。"
|
|
4171
|
-
if kind == "department_list":
|
|
4172
|
-
return f"{field_title} 需要有效部门;用候选范围内的部门名/id/object,歧义或不确定时先调用 record_department_candidates 后只重试本行。"
|
|
4173
|
-
if kind == "relation_record":
|
|
4174
|
-
return f"{field_title} 需要关联记录;优先改用目标记录 record_id/apply_id,或使用可唯一命中的显示文本后只重试本行。"
|
|
4175
|
-
if kind == "attachment_list":
|
|
4176
|
-
return f"{field_title} 需要附件值;先用 file_upload_local 上传文件,再写返回的 value/url 后只重试本行。"
|
|
4177
|
-
if kind in {"single_select", "multi_select"}:
|
|
4178
|
-
return f"{field_title} 需要选项值;使用 schema options 中的标签或 option id,修正后只重试本行。"
|
|
4179
3792
|
if error_code == "MISSING_REQUIRED_FIELD":
|
|
4180
3793
|
return "补充该字段后只重试本行。"
|
|
4181
3794
|
if error_code in {"FIELD_NOT_FOUND", "AMBIGUOUS_FIELD"}:
|
|
@@ -4191,7 +3804,6 @@ class RecordTools(ToolBase):
|
|
|
4191
3804
|
record_id: Any | None,
|
|
4192
3805
|
fields: JSONObject | None = None,
|
|
4193
3806
|
items: list[JSONObject] | None = None,
|
|
4194
|
-
view_id: str | None = None,
|
|
4195
3807
|
dry_run: bool = False,
|
|
4196
3808
|
verify_write: bool = True,
|
|
4197
3809
|
output_profile: str = "normal",
|
|
@@ -4212,7 +3824,6 @@ class RecordTools(ToolBase):
|
|
|
4212
3824
|
profile=profile,
|
|
4213
3825
|
app_key=app_key,
|
|
4214
3826
|
items=normalized_items,
|
|
4215
|
-
view_id=view_id,
|
|
4216
3827
|
dry_run=dry_run,
|
|
4217
3828
|
verify_write=verify_write,
|
|
4218
3829
|
output_profile=normalized_output_profile,
|
|
@@ -4229,7 +3840,6 @@ class RecordTools(ToolBase):
|
|
|
4229
3840
|
app_key=app_key,
|
|
4230
3841
|
record_id=record_id_int,
|
|
4231
3842
|
fields=cast(JSONObject, fields or {}),
|
|
4232
|
-
view_id=view_id,
|
|
4233
3843
|
verify_write=verify_write,
|
|
4234
3844
|
output_profile=normalized_output_profile,
|
|
4235
3845
|
)
|
|
@@ -4241,61 +3851,17 @@ class RecordTools(ToolBase):
|
|
|
4241
3851
|
app_key: str,
|
|
4242
3852
|
record_id: int,
|
|
4243
3853
|
fields: JSONObject,
|
|
4244
|
-
view_id: str | None,
|
|
4245
3854
|
verify_write: bool,
|
|
4246
3855
|
output_profile: str,
|
|
4247
|
-
capture_exceptions: bool = False,
|
|
4248
3856
|
) -> JSONObject:
|
|
4249
3857
|
"""执行内部辅助逻辑。"""
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
view_id=view_id,
|
|
4258
|
-
verify_write=verify_write,
|
|
4259
|
-
output_profile=output_profile,
|
|
4260
|
-
write_attempted_ref=lambda value: write_state.__setitem__("attempted", value),
|
|
4261
|
-
)
|
|
4262
|
-
except (QingflowApiError, RuntimeError) as exc:
|
|
4263
|
-
if not capture_exceptions:
|
|
4264
|
-
raise
|
|
4265
|
-
return self._record_write_exception_response(
|
|
4266
|
-
exc,
|
|
4267
|
-
operation="update",
|
|
4268
|
-
profile=profile,
|
|
4269
|
-
app_key=app_key,
|
|
4270
|
-
record_id=record_id,
|
|
4271
|
-
output_profile=output_profile,
|
|
4272
|
-
human_review=True,
|
|
4273
|
-
write_executed=write_state["attempted"],
|
|
4274
|
-
)
|
|
4275
|
-
|
|
4276
|
-
def _record_update_public_single_impl(
|
|
4277
|
-
self,
|
|
4278
|
-
*,
|
|
4279
|
-
profile: str,
|
|
4280
|
-
app_key: str,
|
|
4281
|
-
record_id: int,
|
|
4282
|
-
fields: JSONObject,
|
|
4283
|
-
view_id: str | None,
|
|
4284
|
-
verify_write: bool,
|
|
4285
|
-
output_profile: str,
|
|
4286
|
-
write_attempted_ref: Callable[[bool], None],
|
|
4287
|
-
) -> JSONObject:
|
|
4288
|
-
"""执行内部辅助逻辑。"""
|
|
4289
|
-
preflight_kwargs: dict[str, Any] = {
|
|
4290
|
-
"profile": profile,
|
|
4291
|
-
"app_key": app_key,
|
|
4292
|
-
"record_id": record_id,
|
|
4293
|
-
"fields": fields,
|
|
4294
|
-
"force_refresh_form": False,
|
|
4295
|
-
}
|
|
4296
|
-
if view_id is not None:
|
|
4297
|
-
preflight_kwargs["preferred_view_id"] = view_id
|
|
4298
|
-
raw_preflight = self._preflight_record_update_with_auto_view(**preflight_kwargs)
|
|
3858
|
+
raw_preflight = self._preflight_record_update_with_auto_view(
|
|
3859
|
+
profile=profile,
|
|
3860
|
+
app_key=app_key,
|
|
3861
|
+
record_id=record_id,
|
|
3862
|
+
fields=fields,
|
|
3863
|
+
force_refresh_form=False,
|
|
3864
|
+
)
|
|
4299
3865
|
preflight_used_force_refresh = self._record_preflight_used_force_refresh(raw_preflight)
|
|
4300
3866
|
preflight_data = cast(JSONObject, raw_preflight.get("data", {}))
|
|
4301
3867
|
normalized_payload = self._record_write_normalized_payload(
|
|
@@ -4315,7 +3881,6 @@ class RecordTools(ToolBase):
|
|
|
4315
3881
|
human_review=True,
|
|
4316
3882
|
target_resource={"type": "record", "app_key": app_key, "record_id": record_id, "record_ids": []},
|
|
4317
3883
|
)
|
|
4318
|
-
write_attempted_ref(True)
|
|
4319
3884
|
route_apply, tried_routes, route_blocker = self._record_update_apply_with_auto_route(
|
|
4320
3885
|
profile=profile,
|
|
4321
3886
|
app_key=app_key,
|
|
@@ -4596,9 +4161,7 @@ class RecordTools(ToolBase):
|
|
|
4596
4161
|
)
|
|
4597
4162
|
|
|
4598
4163
|
def _record_update_route_permission_denied(self, exc: QingflowApiError) -> bool:
|
|
4599
|
-
if
|
|
4600
|
-
return False
|
|
4601
|
-
if backend_code_int(exc) in {40002, 40027, 40038, 404}:
|
|
4164
|
+
if exc.backend_code in {40002, 40027, 40038, 404}:
|
|
4602
4165
|
return True
|
|
4603
4166
|
if exc.http_status == 404:
|
|
4604
4167
|
return True
|
|
@@ -4702,8 +4265,6 @@ class RecordTools(ToolBase):
|
|
|
4702
4265
|
},
|
|
4703
4266
|
)
|
|
4704
4267
|
except QingflowApiError as exc:
|
|
4705
|
-
if not _is_optional_record_auxiliary_lookup_error(exc):
|
|
4706
|
-
raise
|
|
4707
4268
|
return unavailable(
|
|
4708
4269
|
error_code="TASK_UPDATE_ROUTE_NOT_AVAILABLE",
|
|
4709
4270
|
reason="current-user todo task list is unavailable",
|
|
@@ -4751,8 +4312,6 @@ class RecordTools(ToolBase):
|
|
|
4751
4312
|
f"/app/{app_key}/auditNode/{workflow_node_id}/editableQueIds",
|
|
4752
4313
|
)
|
|
4753
4314
|
except QingflowApiError as exc:
|
|
4754
|
-
if not _is_optional_record_auxiliary_lookup_error(exc):
|
|
4755
|
-
raise
|
|
4756
4315
|
return unavailable(
|
|
4757
4316
|
error_code="TASK_EDITABLE_FIELDS_UNAVAILABLE",
|
|
4758
4317
|
reason="workflow node editable field list is unavailable; record_update will not guess task editability",
|
|
@@ -4903,7 +4462,7 @@ class RecordTools(ToolBase):
|
|
|
4903
4462
|
raise_tool_error(QingflowApiError.config_error("view_key is required for custom view update"))
|
|
4904
4463
|
|
|
4905
4464
|
def runner(session_profile, context):
|
|
4906
|
-
index = self.
|
|
4465
|
+
index = self._get_field_index(profile, context, app_key, force_refresh=force_refresh_form) if verify_write else None
|
|
4907
4466
|
normalized_answers = [dict(item) for item in answers if isinstance(item, dict)]
|
|
4908
4467
|
self._validate_record_write(app_key, normalized_answers, apply_id=normalized_apply_id)
|
|
4909
4468
|
result = self.backend.request(
|
|
@@ -5006,7 +4565,6 @@ class RecordTools(ToolBase):
|
|
|
5006
4565
|
profile: str,
|
|
5007
4566
|
app_key: str,
|
|
5008
4567
|
items: list[JSONObject],
|
|
5009
|
-
view_id: str | None,
|
|
5010
4568
|
dry_run: bool,
|
|
5011
4569
|
verify_write: bool,
|
|
5012
4570
|
output_profile: str,
|
|
@@ -5018,7 +4576,6 @@ class RecordTools(ToolBase):
|
|
|
5018
4576
|
app_key=app_key,
|
|
5019
4577
|
record_id=cast(int, item["record_id"]),
|
|
5020
4578
|
fields=cast(JSONObject, item["fields"]),
|
|
5021
|
-
view_id=view_id,
|
|
5022
4579
|
output_profile=output_profile,
|
|
5023
4580
|
)
|
|
5024
4581
|
for item in items
|
|
@@ -5047,10 +4604,8 @@ class RecordTools(ToolBase):
|
|
|
5047
4604
|
app_key=app_key,
|
|
5048
4605
|
record_id=record_id,
|
|
5049
4606
|
fields=fields,
|
|
5050
|
-
view_id=view_id,
|
|
5051
4607
|
verify_write=verify_write,
|
|
5052
4608
|
output_profile=output_profile,
|
|
5053
|
-
capture_exceptions=True,
|
|
5054
4609
|
)
|
|
5055
4610
|
)
|
|
5056
4611
|
except (QingflowApiError, RuntimeError) as exc:
|
|
@@ -5081,20 +4636,16 @@ class RecordTools(ToolBase):
|
|
|
5081
4636
|
app_key: str,
|
|
5082
4637
|
record_id: int,
|
|
5083
4638
|
fields: JSONObject,
|
|
5084
|
-
view_id: str | None,
|
|
5085
4639
|
output_profile: str,
|
|
5086
4640
|
) -> JSONObject:
|
|
5087
4641
|
"""执行内部辅助逻辑。"""
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
if view_id is not None:
|
|
5096
|
-
preflight_kwargs["preferred_view_id"] = view_id
|
|
5097
|
-
raw_preflight = self._preflight_record_update_with_auto_view(**preflight_kwargs)
|
|
4642
|
+
raw_preflight = self._preflight_record_update_with_auto_view(
|
|
4643
|
+
profile=profile,
|
|
4644
|
+
app_key=app_key,
|
|
4645
|
+
record_id=record_id,
|
|
4646
|
+
fields=fields,
|
|
4647
|
+
force_refresh_form=False,
|
|
4648
|
+
)
|
|
5098
4649
|
preflight_data = cast(JSONObject, raw_preflight.get("data", {}))
|
|
5099
4650
|
normalized_payload = self._record_write_normalized_payload(
|
|
5100
4651
|
operation="update",
|
|
@@ -5300,9 +4851,6 @@ class RecordTools(ToolBase):
|
|
|
5300
4851
|
item: JSONObject = {
|
|
5301
4852
|
"resource": data.get("resource"),
|
|
5302
4853
|
"status": response.get("status"),
|
|
5303
|
-
"write_executed": bool(response.get("write_executed")),
|
|
5304
|
-
"safe_to_retry": bool(response.get("safe_to_retry", True)),
|
|
5305
|
-
"verification_status": response.get("verification_status", "not_requested"),
|
|
5306
4854
|
"verification": data.get("verification"),
|
|
5307
4855
|
"field_errors": cast(list[JSONObject], data.get("field_errors", [])),
|
|
5308
4856
|
"confirmation_requests": cast(list[JSONObject], data.get("confirmation_requests", [])),
|
|
@@ -5312,7 +4860,7 @@ class RecordTools(ToolBase):
|
|
|
5312
4860
|
if isinstance(update_route, dict):
|
|
5313
4861
|
item["update_route"] = update_route
|
|
5314
4862
|
tried_routes = response.get("tried_routes")
|
|
5315
|
-
if isinstance(tried_routes, list)
|
|
4863
|
+
if isinstance(tried_routes, list):
|
|
5316
4864
|
item["tried_routes"] = tried_routes
|
|
5317
4865
|
blockers = data.get("blockers")
|
|
5318
4866
|
if isinstance(blockers, list) and blockers:
|
|
@@ -5334,7 +4882,6 @@ class RecordTools(ToolBase):
|
|
|
5334
4882
|
app_key: str,
|
|
5335
4883
|
record_id: int,
|
|
5336
4884
|
fields: JSONObject,
|
|
5337
|
-
preferred_view_id: str | None = None,
|
|
5338
4885
|
force_refresh_form: bool,
|
|
5339
4886
|
) -> JSONObject:
|
|
5340
4887
|
"""执行内部辅助逻辑。"""
|
|
@@ -5342,25 +4889,6 @@ class RecordTools(ToolBase):
|
|
|
5342
4889
|
request_route = self._request_route_payload(context)
|
|
5343
4890
|
def build_once(*, effective_force_refresh: bool) -> JSONObject:
|
|
5344
4891
|
candidate_routes = self._candidate_update_views(profile, context, app_key)
|
|
5345
|
-
normalized_preferred_view_id = _normalize_optional_text(preferred_view_id)
|
|
5346
|
-
if normalized_preferred_view_id:
|
|
5347
|
-
preferred_route = next(
|
|
5348
|
-
(
|
|
5349
|
-
route
|
|
5350
|
-
for route in candidate_routes
|
|
5351
|
-
if route.view_id == normalized_preferred_view_id
|
|
5352
|
-
),
|
|
5353
|
-
None,
|
|
5354
|
-
)
|
|
5355
|
-
if preferred_route is None:
|
|
5356
|
-
raise_tool_error(
|
|
5357
|
-
QingflowApiError.config_error(
|
|
5358
|
-
f"view_id '{normalized_preferred_view_id}' is not an accessible update candidate"
|
|
5359
|
-
)
|
|
5360
|
-
)
|
|
5361
|
-
candidate_routes = [preferred_route]
|
|
5362
|
-
else:
|
|
5363
|
-
candidate_routes = _prefer_custom_update_routes(candidate_routes)
|
|
5364
4892
|
probes = self._probe_candidate_record_contexts(
|
|
5365
4893
|
context,
|
|
5366
4894
|
app_key=app_key,
|
|
@@ -5574,24 +5102,41 @@ class RecordTools(ToolBase):
|
|
|
5574
5102
|
"data": first_confirmation_plan,
|
|
5575
5103
|
}
|
|
5576
5104
|
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5105
|
+
union_plan = self._build_record_update_union_preflight(
|
|
5106
|
+
profile=profile,
|
|
5107
|
+
context=context,
|
|
5108
|
+
app_key=app_key,
|
|
5109
|
+
record_id=record_id,
|
|
5110
|
+
fields=fields,
|
|
5111
|
+
current_answers=matched_answers_for_union or [],
|
|
5112
|
+
matched_routes=matched_routes,
|
|
5113
|
+
force_refresh_form=effective_force_refresh,
|
|
5114
|
+
)
|
|
5115
|
+
if union_plan is not None:
|
|
5116
|
+
validation = union_plan.get("validation")
|
|
5117
|
+
if isinstance(validation, dict):
|
|
5118
|
+
warnings = validation.get("warnings")
|
|
5119
|
+
if not isinstance(warnings, list):
|
|
5120
|
+
warnings = []
|
|
5121
|
+
validation["warnings"] = warnings
|
|
5122
|
+
for message in fallback_warning_messages:
|
|
5123
|
+
if message not in warnings:
|
|
5124
|
+
warnings.append(message)
|
|
5125
|
+
union_plan["view_probe_summary"] = probe_summary
|
|
5126
|
+
union_plan["record_context_probe"] = probe_summary
|
|
5580
5127
|
return {
|
|
5581
5128
|
"profile": profile,
|
|
5582
5129
|
"ws_id": session_profile.selected_ws_id,
|
|
5583
5130
|
"ok": True,
|
|
5584
5131
|
"request_route": request_route,
|
|
5585
|
-
"data":
|
|
5132
|
+
"data": union_plan,
|
|
5586
5133
|
}
|
|
5587
5134
|
|
|
5588
5135
|
blocked_data = self._build_auto_view_blocked_preflight_data(
|
|
5589
5136
|
app_key=app_key,
|
|
5590
5137
|
record_id=record_id,
|
|
5591
5138
|
blockers=["NO_SINGLE_VIEW_CAN_UPDATE_ALL_FIELDS"],
|
|
5592
|
-
warnings=[
|
|
5593
|
-
"record_update requires one executable frontend route for the full payload; it does not merge writable fields across multiple views."
|
|
5594
|
-
],
|
|
5139
|
+
warnings=[],
|
|
5595
5140
|
recommended_next_actions=[
|
|
5596
5141
|
"Call record_update_schema_get first to inspect the overall writable field set for this record.",
|
|
5597
5142
|
"Reduce the update payload until all requested fields fit inside one matched accessible view.",
|
|
@@ -5638,7 +5183,6 @@ class RecordTools(ToolBase):
|
|
|
5638
5183
|
union_writable_field_ids: set[int] = set()
|
|
5639
5184
|
union_visible_question_ids: set[int] = set()
|
|
5640
5185
|
matched_view_payloads: list[JSONObject] = []
|
|
5641
|
-
union_index: FieldIndex | None = None
|
|
5642
5186
|
|
|
5643
5187
|
for candidate in matched_routes:
|
|
5644
5188
|
browse_scope = self._build_browse_write_scope(
|
|
@@ -5648,13 +5192,11 @@ class RecordTools(ToolBase):
|
|
|
5648
5192
|
candidate,
|
|
5649
5193
|
force_refresh=force_refresh_form,
|
|
5650
5194
|
)
|
|
5651
|
-
browse_index = cast(FieldIndex, browse_scope["index"])
|
|
5652
|
-
union_index = browse_index if union_index is None else _merge_field_indexes(union_index, browse_index)
|
|
5653
5195
|
union_writable_field_ids.update(cast(set[int], browse_scope["writable_field_ids"]))
|
|
5654
5196
|
union_visible_question_ids.update(cast(set[int], browse_scope["visible_question_ids"]))
|
|
5655
5197
|
matched_view_payloads.append(_accessible_view_payload(candidate))
|
|
5656
5198
|
|
|
5657
|
-
if
|
|
5199
|
+
if not union_writable_field_ids and not union_visible_question_ids:
|
|
5658
5200
|
return None
|
|
5659
5201
|
|
|
5660
5202
|
plan_data = self._build_record_write_preflight(
|
|
@@ -5671,9 +5213,10 @@ class RecordTools(ToolBase):
|
|
|
5671
5213
|
view_key=None,
|
|
5672
5214
|
view_name=None,
|
|
5673
5215
|
existing_answers_override=current_answers,
|
|
5674
|
-
field_index_override=union_index,
|
|
5675
5216
|
)
|
|
5676
5217
|
|
|
5218
|
+
schema = self._get_form_schema(profile, context, app_key, force_refresh=force_refresh_form)
|
|
5219
|
+
app_index = _build_applicant_top_level_field_index(schema)
|
|
5677
5220
|
validation = cast(JSONObject, plan_data.get("validation", {}))
|
|
5678
5221
|
invalid_fields = cast(list[JSONObject], validation.get("invalid_fields", []))
|
|
5679
5222
|
missing_required_fields = cast(list[JSONObject], validation.get("missing_required_fields", []))
|
|
@@ -5684,21 +5227,12 @@ class RecordTools(ToolBase):
|
|
|
5684
5227
|
invalid_fields.extend(
|
|
5685
5228
|
self._validate_view_scoped_subtable_answers(
|
|
5686
5229
|
normalized_answers=cast(list[JSONObject], plan_data.get("normalized_answers", [])),
|
|
5687
|
-
full_index=
|
|
5688
|
-
selector_index=
|
|
5230
|
+
full_index=app_index,
|
|
5231
|
+
selector_index=app_index,
|
|
5689
5232
|
visible_question_ids=union_visible_question_ids,
|
|
5690
5233
|
)
|
|
5691
5234
|
)
|
|
5692
5235
|
|
|
5693
|
-
readonly_or_system_fields = [
|
|
5694
|
-
item
|
|
5695
|
-
for item in readonly_or_system_fields
|
|
5696
|
-
if not (
|
|
5697
|
-
isinstance(item, dict)
|
|
5698
|
-
and (que_id := _coerce_count(item.get("que_id"))) is not None
|
|
5699
|
-
and que_id in union_writable_field_ids
|
|
5700
|
-
)
|
|
5701
|
-
]
|
|
5702
5236
|
existing_readonly_ids = {
|
|
5703
5237
|
str(_coerce_count(item.get("que_id")))
|
|
5704
5238
|
for item in readonly_or_system_fields
|
|
@@ -5862,13 +5396,7 @@ class RecordTools(ToolBase):
|
|
|
5862
5396
|
view_type=None,
|
|
5863
5397
|
)
|
|
5864
5398
|
)
|
|
5865
|
-
|
|
5866
|
-
view_items = self._get_view_list(profile, context, app_key)
|
|
5867
|
-
except QingflowApiError as exc:
|
|
5868
|
-
if not _is_record_permission_denied_error(exc):
|
|
5869
|
-
raise
|
|
5870
|
-
view_items = []
|
|
5871
|
-
for item in view_items:
|
|
5399
|
+
for item in self._get_view_list(profile, context, app_key):
|
|
5872
5400
|
if not isinstance(item, dict):
|
|
5873
5401
|
continue
|
|
5874
5402
|
view_key = _normalize_optional_text(item.get("viewKey"))
|
|
@@ -5925,9 +5453,7 @@ class RecordTools(ToolBase):
|
|
|
5925
5453
|
return payload
|
|
5926
5454
|
|
|
5927
5455
|
def _is_record_context_route_miss(self, error: QingflowApiError) -> bool:
|
|
5928
|
-
if
|
|
5929
|
-
return False
|
|
5930
|
-
if backend_code_int(error) in {40002, 40023, 40027, 40038, 404}:
|
|
5456
|
+
if error.backend_code in {40002, 40023, 40027, 40038, 404}:
|
|
5931
5457
|
return True
|
|
5932
5458
|
if error.http_status == 404:
|
|
5933
5459
|
return True
|
|
@@ -5957,12 +5483,11 @@ class RecordTools(ToolBase):
|
|
|
5957
5483
|
used_list_type = None
|
|
5958
5484
|
else:
|
|
5959
5485
|
used_list_type = resolved_view.list_type if resolved_view.list_type is not None else DEFAULT_RECORD_LIST_TYPE
|
|
5960
|
-
role = _record_detail_role_for_list_type(used_list_type)
|
|
5961
5486
|
record = self.backend.request(
|
|
5962
5487
|
"GET",
|
|
5963
5488
|
context,
|
|
5964
5489
|
f"/app/{app_key}/apply/{apply_id}",
|
|
5965
|
-
params={"role":
|
|
5490
|
+
params={"role": 1, "listType": used_list_type},
|
|
5966
5491
|
)
|
|
5967
5492
|
answers = record.get("answers") if isinstance(record, dict) else None
|
|
5968
5493
|
normalized_answers = [item for item in answers if isinstance(item, dict)] if isinstance(answers, list) else []
|
|
@@ -5992,8 +5517,6 @@ class RecordTools(ToolBase):
|
|
|
5992
5517
|
error_payload=None,
|
|
5993
5518
|
)
|
|
5994
5519
|
except QingflowApiError as exc:
|
|
5995
|
-
if not self._is_record_context_route_miss(exc):
|
|
5996
|
-
raise
|
|
5997
5520
|
return RecordContextRouteProbe(
|
|
5998
5521
|
route=resolved_view,
|
|
5999
5522
|
answer_list=None,
|
|
@@ -6065,7 +5588,7 @@ class RecordTools(ToolBase):
|
|
|
6065
5588
|
]
|
|
6066
5589
|
|
|
6067
5590
|
def _looks_like_generic_record_update_backend_failure(self, exc: QingflowApiError) -> bool:
|
|
6068
|
-
if
|
|
5591
|
+
if exc.backend_code == 500:
|
|
6069
5592
|
return True
|
|
6070
5593
|
if exc.http_status is not None and exc.http_status >= 500:
|
|
6071
5594
|
return True
|
|
@@ -6190,15 +5713,12 @@ class RecordTools(ToolBase):
|
|
|
6190
5713
|
app_key: str,
|
|
6191
5714
|
record_id: Any | None = None,
|
|
6192
5715
|
record_ids: list[Any] | None = None,
|
|
6193
|
-
view_id: str | None = None,
|
|
6194
|
-
list_type: int | None = None,
|
|
6195
5716
|
output_profile: str = "normal",
|
|
6196
5717
|
) -> JSONObject:
|
|
6197
5718
|
"""执行记录相关逻辑。"""
|
|
6198
5719
|
normalized_output_profile = self._normalize_public_output_profile(output_profile)
|
|
6199
5720
|
if not app_key:
|
|
6200
5721
|
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
6201
|
-
delete_list_type = self._resolve_record_delete_list_type(view_id=view_id, list_type=list_type)
|
|
6202
5722
|
normalized_record_ids: list[int] = []
|
|
6203
5723
|
for index, item in enumerate(record_ids or []):
|
|
6204
5724
|
normalized_record_ids.append(normalize_positive_id_int(item, field_name=f"record_ids[{index}]"))
|
|
@@ -6218,63 +5738,13 @@ class RecordTools(ToolBase):
|
|
|
6218
5738
|
"record_ids": [stringify_backend_id(item) for item in delete_ids],
|
|
6219
5739
|
"answers": [],
|
|
6220
5740
|
"submit_type": 1,
|
|
6221
|
-
"selection": {"view_id": view_id, "list_type": delete_list_type},
|
|
6222
5741
|
}
|
|
6223
5742
|
return self._record_delete_public_batch(
|
|
6224
5743
|
profile=profile,
|
|
6225
|
-
app_key=app_key,
|
|
6226
|
-
delete_ids=delete_ids,
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
output_profile=normalized_output_profile,
|
|
6230
|
-
)
|
|
6231
|
-
|
|
6232
|
-
def _resolve_record_delete_list_type(self, *, view_id: str | None, list_type: int | None) -> int:
|
|
6233
|
-
normalized_view_id = _normalize_optional_text(view_id)
|
|
6234
|
-
if normalized_view_id:
|
|
6235
|
-
if normalized_view_id.startswith("custom:"):
|
|
6236
|
-
raise_tool_error(
|
|
6237
|
-
QingflowApiError.config_error(
|
|
6238
|
-
"record_delete does not support custom view deletion; the backend delete route accepts system listType only",
|
|
6239
|
-
details={
|
|
6240
|
-
"error_code": "RECORD_DELETE_CUSTOM_VIEW_UNSUPPORTED",
|
|
6241
|
-
"view_id": normalized_view_id,
|
|
6242
|
-
"fix_hint": (
|
|
6243
|
-
"Use a system view_id from app_get.accessible_views, or resolve target record_ids with "
|
|
6244
|
-
"record list/get first and retry delete without a custom view selector."
|
|
6245
|
-
),
|
|
6246
|
-
},
|
|
6247
|
-
)
|
|
6248
|
-
)
|
|
6249
|
-
if not normalized_view_id.startswith("system:"):
|
|
6250
|
-
raise_tool_error(QingflowApiError.config_error("view_id must start with system: or custom:"))
|
|
6251
|
-
mapped_list_type = SYSTEM_VIEW_ID_TO_LIST_TYPE.get(normalized_view_id)
|
|
6252
|
-
if mapped_list_type is None:
|
|
6253
|
-
raise_tool_error(QingflowApiError.config_error(f"unsupported view_id '{normalized_view_id}'"))
|
|
6254
|
-
return mapped_list_type
|
|
6255
|
-
if list_type is not None:
|
|
6256
|
-
normalized_list_type = int(list_type)
|
|
6257
|
-
if normalized_list_type not in SYSTEM_VIEW_LIST_TYPES:
|
|
6258
|
-
raise_tool_error(
|
|
6259
|
-
QingflowApiError.config_error(
|
|
6260
|
-
"record_delete list_type must map to a supported system view",
|
|
6261
|
-
details={
|
|
6262
|
-
"error_code": "RECORD_DELETE_SYSTEM_VIEW_REQUIRED",
|
|
6263
|
-
"list_type": normalized_list_type,
|
|
6264
|
-
"supported_list_types": sorted(SYSTEM_VIEW_LIST_TYPES),
|
|
6265
|
-
"fix_hint": "Pass a system view_id from app_get.accessible_views instead of an arbitrary list_type.",
|
|
6266
|
-
},
|
|
6267
|
-
)
|
|
6268
|
-
)
|
|
6269
|
-
return normalized_list_type
|
|
6270
|
-
raise_tool_error(
|
|
6271
|
-
QingflowApiError.config_error(
|
|
6272
|
-
"record_delete requires a system view_id or list_type; deleting without frontend list context is ambiguous",
|
|
6273
|
-
details={
|
|
6274
|
-
"error_code": "RECORD_DELETE_VIEW_REQUIRED",
|
|
6275
|
-
"fix_hint": "Pass a system view_id from app_get.accessible_views, for example --view-id system:all. If the target came from a custom view, first confirm the record_id, then choose an accessible system view for deletion.",
|
|
6276
|
-
},
|
|
6277
|
-
)
|
|
5744
|
+
app_key=app_key,
|
|
5745
|
+
delete_ids=delete_ids,
|
|
5746
|
+
normalized_payload=normalized_payload,
|
|
5747
|
+
output_profile=normalized_output_profile,
|
|
6278
5748
|
)
|
|
6279
5749
|
|
|
6280
5750
|
def _record_delete_public_batch(
|
|
@@ -6283,7 +5753,6 @@ class RecordTools(ToolBase):
|
|
|
6283
5753
|
profile: str,
|
|
6284
5754
|
app_key: str,
|
|
6285
5755
|
delete_ids: list[int],
|
|
6286
|
-
list_type: int,
|
|
6287
5756
|
normalized_payload: JSONObject,
|
|
6288
5757
|
output_profile: str,
|
|
6289
5758
|
) -> JSONObject:
|
|
@@ -6293,7 +5762,7 @@ class RecordTools(ToolBase):
|
|
|
6293
5762
|
for index, delete_id in enumerate(delete_ids):
|
|
6294
5763
|
record_id_text = stringify_backend_id(delete_id)
|
|
6295
5764
|
try:
|
|
6296
|
-
raw_apply = self._record_delete_many(profile=profile, app_key=app_key, record_ids=[delete_id]
|
|
5765
|
+
raw_apply = self._record_delete_many(profile=profile, app_key=app_key, record_ids=[delete_id])
|
|
6297
5766
|
request_route = cast(JSONObject, raw_apply.get("request_route")) if isinstance(raw_apply.get("request_route"), dict) else request_route
|
|
6298
5767
|
ws_id = raw_apply.get("ws_id", ws_id)
|
|
6299
5768
|
single_payload = {
|
|
@@ -6302,7 +5771,6 @@ class RecordTools(ToolBase):
|
|
|
6302
5771
|
"record_ids": [record_id_text],
|
|
6303
5772
|
"answers": [],
|
|
6304
5773
|
"submit_type": 1,
|
|
6305
|
-
"selection": normalized_payload.get("selection"),
|
|
6306
5774
|
}
|
|
6307
5775
|
single_response = self._record_write_apply_response(
|
|
6308
5776
|
raw_apply,
|
|
@@ -6585,13 +6053,12 @@ class RecordTools(ToolBase):
|
|
|
6585
6053
|
preflight=raw_preflight,
|
|
6586
6054
|
)
|
|
6587
6055
|
|
|
6588
|
-
if
|
|
6056
|
+
if uses_view_scope:
|
|
6589
6057
|
raise_tool_error(
|
|
6590
6058
|
QingflowApiError.config_error(
|
|
6591
|
-
"delete does not
|
|
6059
|
+
"delete does not accept view selectors yet; resolve target record_ids from the selected view first, then call delete by record_id/record_ids"
|
|
6592
6060
|
)
|
|
6593
6061
|
)
|
|
6594
|
-
delete_list_type = self._resolve_record_delete_list_type(view_id=view_id, list_type=list_type)
|
|
6595
6062
|
if normalized_values or normalized_set:
|
|
6596
6063
|
raise_tool_error(QingflowApiError.config_error("delete must not include values or set"))
|
|
6597
6064
|
delete_ids = normalized_record_ids or ([record_id] if record_id is not None and record_id > 0 else [])
|
|
@@ -6603,9 +6070,8 @@ class RecordTools(ToolBase):
|
|
|
6603
6070
|
"record_ids": delete_ids,
|
|
6604
6071
|
"answers": [],
|
|
6605
6072
|
"submit_type": submit_type_value,
|
|
6606
|
-
"selection": {"view_id": view_id, "list_type": delete_list_type},
|
|
6607
6073
|
}
|
|
6608
|
-
raw_apply = self._record_delete_many(profile=profile, app_key=app_key, record_ids=delete_ids
|
|
6074
|
+
raw_apply = self._record_delete_many(profile=profile, app_key=app_key, record_ids=delete_ids)
|
|
6609
6075
|
return self._record_write_apply_response(
|
|
6610
6076
|
raw_apply,
|
|
6611
6077
|
operation="delete",
|
|
@@ -6755,9 +6221,7 @@ class RecordTools(ToolBase):
|
|
|
6755
6221
|
or _normalize_optional_text(payload.get("appName"))
|
|
6756
6222
|
or _normalize_optional_text(payload.get("appTitle"))
|
|
6757
6223
|
)
|
|
6758
|
-
except QingflowApiError
|
|
6759
|
-
if is_auth_like_error(exc):
|
|
6760
|
-
raise
|
|
6224
|
+
except QingflowApiError:
|
|
6761
6225
|
name = None
|
|
6762
6226
|
self._app_name_cache[cache_key] = name
|
|
6763
6227
|
return name
|
|
@@ -6911,9 +6375,7 @@ class RecordTools(ToolBase):
|
|
|
6911
6375
|
try:
|
|
6912
6376
|
result = self.backend.request("GET", context, "/data/baseInfo", params={"queId": field.que_id})
|
|
6913
6377
|
payload = result if isinstance(result, dict) else None
|
|
6914
|
-
except QingflowApiError
|
|
6915
|
-
if is_auth_like_error(exc):
|
|
6916
|
-
raise
|
|
6378
|
+
except QingflowApiError:
|
|
6917
6379
|
payload = None
|
|
6918
6380
|
self._relation_base_info_cache[cache_key] = payload or {}
|
|
6919
6381
|
return payload
|
|
@@ -7186,26 +6648,6 @@ class RecordTools(ToolBase):
|
|
|
7186
6648
|
or bool(fields)
|
|
7187
6649
|
)
|
|
7188
6650
|
|
|
7189
|
-
def _member_candidate_static_preview_should_use_backend(self, field: FormField) -> bool:
|
|
7190
|
-
"""Return true when the frontend field-scope endpoint is safer than directory expansion."""
|
|
7191
|
-
scope = field.member_select_scope if isinstance(field.member_select_scope, dict) else {}
|
|
7192
|
-
if field.member_select_scope_type != 2:
|
|
7193
|
-
return False
|
|
7194
|
-
return bool(
|
|
7195
|
-
_scope_has_dynamic_or_external(scope)
|
|
7196
|
-
or list(scope.get("depart") or [])
|
|
7197
|
-
or list(scope.get("role") or [])
|
|
7198
|
-
)
|
|
7199
|
-
|
|
7200
|
-
def _department_candidate_static_preview_should_use_backend(self, field: FormField) -> bool:
|
|
7201
|
-
"""Return true when static preview would otherwise need ContactAuth-only directory APIs."""
|
|
7202
|
-
scope = field.dept_select_scope if isinstance(field.dept_select_scope, dict) else {}
|
|
7203
|
-
if field.dept_select_scope_type != 2:
|
|
7204
|
-
return False
|
|
7205
|
-
if _scope_has_dynamic_or_external(scope):
|
|
7206
|
-
return True
|
|
7207
|
-
return bool(_normalize_bool(scope.get("includeSubDeparts")) or not list(scope.get("depart") or []))
|
|
7208
|
-
|
|
7209
6651
|
def _build_candidate_lookup_state(
|
|
7210
6652
|
self,
|
|
7211
6653
|
profile: str,
|
|
@@ -7224,9 +6666,7 @@ class RecordTools(ToolBase):
|
|
|
7224
6666
|
if apply_id is not None:
|
|
7225
6667
|
try:
|
|
7226
6668
|
base_answers = self._load_record_answers_for_preflight(context, app_key=app_key, apply_id=apply_id)
|
|
7227
|
-
except QingflowApiError
|
|
7228
|
-
if not _is_optional_record_auxiliary_lookup_error(exc):
|
|
7229
|
-
raise
|
|
6669
|
+
except QingflowApiError:
|
|
7230
6670
|
context_complete = False
|
|
7231
6671
|
state = LookupResolutionState(
|
|
7232
6672
|
operation="update" if apply_id is not None else "insert",
|
|
@@ -7716,16 +7156,15 @@ class RecordTools(ToolBase):
|
|
|
7716
7156
|
)
|
|
7717
7157
|
if configured_candidate is not None:
|
|
7718
7158
|
self._merge_department_candidate(merged, configured_candidate)
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
self._merge_department_candidate(merged, normalized)
|
|
7159
|
+
for dept in self._list_departments_by_scope(context, dept_id=dept_id, include_sub_departments=include_sub):
|
|
7160
|
+
normalized = _normalize_candidate_department(
|
|
7161
|
+
dept,
|
|
7162
|
+
source_kind="department",
|
|
7163
|
+
source_id=dept_id,
|
|
7164
|
+
source_value=dept_name,
|
|
7165
|
+
)
|
|
7166
|
+
if normalized is not None:
|
|
7167
|
+
self._merge_department_candidate(merged, normalized)
|
|
7729
7168
|
filtered = _filter_department_candidates(list(merged.values()), keyword)
|
|
7730
7169
|
filtered.sort(key=lambda item: (_normalize_optional_text(item.get("value")) or "", _coerce_count(item.get("id")) or 0))
|
|
7731
7170
|
return filtered
|
|
@@ -8866,10 +8305,22 @@ class RecordTools(ToolBase):
|
|
|
8866
8305
|
field_index_override: FieldIndex | None = None,
|
|
8867
8306
|
) -> JSONObject:
|
|
8868
8307
|
"""执行内部辅助逻辑。"""
|
|
8308
|
+
schema = self._get_form_schema(profile, context, app_key, force_refresh=force_refresh_form)
|
|
8309
|
+
base_index = field_index_override or _build_applicant_top_level_field_index(schema)
|
|
8310
|
+
question_relations = _collect_question_relations(schema)
|
|
8311
|
+
runtime_linked_field_ids = _collect_linked_required_field_ids(question_relations)
|
|
8312
|
+
runtime_linked_field_ids.update(_collect_option_linked_field_ids(base_index))
|
|
8313
|
+
index = base_index
|
|
8314
|
+
if operation == "create" and field_index_override is None:
|
|
8315
|
+
linked_hidden_index = _build_applicant_hidden_linked_top_level_field_index(
|
|
8316
|
+
schema,
|
|
8317
|
+
linked_field_ids=runtime_linked_field_ids,
|
|
8318
|
+
)
|
|
8319
|
+
index = _merge_field_indexes(base_index, linked_hidden_index)
|
|
8869
8320
|
normalized_fields = fields or {}
|
|
8870
8321
|
normalized_answers_input = answers or []
|
|
8871
8322
|
resolved_view: AccessibleViewRoute | None = None
|
|
8872
|
-
selector_index
|
|
8323
|
+
selector_index = index
|
|
8873
8324
|
browse_writable_field_ids: set[int] = set()
|
|
8874
8325
|
visible_question_ids: set[int] = set()
|
|
8875
8326
|
if any(item is not None for item in (view_id, list_type, view_key, view_name)):
|
|
@@ -8895,31 +8346,6 @@ class RecordTools(ToolBase):
|
|
|
8895
8346
|
visible_question_ids = cast(set[int], browse_scope["visible_question_ids"])
|
|
8896
8347
|
else:
|
|
8897
8348
|
compatibility_warnings = []
|
|
8898
|
-
if field_index_override is not None:
|
|
8899
|
-
base_index = field_index_override
|
|
8900
|
-
question_relations: list[JSONObject] = []
|
|
8901
|
-
runtime_linked_field_ids: set[int] = set()
|
|
8902
|
-
index = base_index
|
|
8903
|
-
elif operation == "update" and resolved_view is not None:
|
|
8904
|
-
base_index = cast(FieldIndex, selector_index)
|
|
8905
|
-
question_relations = []
|
|
8906
|
-
runtime_linked_field_ids = set()
|
|
8907
|
-
index = base_index
|
|
8908
|
-
else:
|
|
8909
|
-
schema = self._get_form_schema(profile, context, app_key, force_refresh=force_refresh_form)
|
|
8910
|
-
base_index = _build_applicant_top_level_field_index(schema)
|
|
8911
|
-
question_relations = _collect_question_relations(schema)
|
|
8912
|
-
runtime_linked_field_ids = _collect_linked_required_field_ids(question_relations)
|
|
8913
|
-
runtime_linked_field_ids.update(_collect_option_linked_field_ids(base_index))
|
|
8914
|
-
index = base_index
|
|
8915
|
-
if operation == "create":
|
|
8916
|
-
linked_hidden_index = _build_applicant_hidden_linked_top_level_field_index(
|
|
8917
|
-
schema,
|
|
8918
|
-
linked_field_ids=runtime_linked_field_ids,
|
|
8919
|
-
)
|
|
8920
|
-
index = _merge_field_indexes(base_index, linked_hidden_index)
|
|
8921
|
-
if selector_index is None:
|
|
8922
|
-
selector_index = index
|
|
8923
8349
|
resolved_fields = self._collect_write_plan_field_refs(fields=normalized_fields, answers=normalized_answers_input, index=selector_index)
|
|
8924
8350
|
support_matrix = _summarize_write_support(resolved_fields)
|
|
8925
8351
|
invalid_fields: list[JSONObject] = []
|
|
@@ -8963,9 +8389,7 @@ class RecordTools(ToolBase):
|
|
|
8963
8389
|
apply_id=apply_id,
|
|
8964
8390
|
)
|
|
8965
8391
|
existing_answers_loaded = True
|
|
8966
|
-
except QingflowApiError
|
|
8967
|
-
if not _is_optional_record_auxiliary_lookup_error(exc):
|
|
8968
|
-
raise
|
|
8392
|
+
except QingflowApiError:
|
|
8969
8393
|
validation_warnings.append(
|
|
8970
8394
|
"update preflight could not load the current record; required-field completeness and dynamic lookup context were not fully revalidated."
|
|
8971
8395
|
)
|
|
@@ -9554,7 +8978,7 @@ class RecordTools(ToolBase):
|
|
|
9554
8978
|
break
|
|
9555
8979
|
except QingflowApiError as exc:
|
|
9556
8980
|
last_error = exc
|
|
9557
|
-
if
|
|
8981
|
+
if exc.backend_code == 40002:
|
|
9558
8982
|
continue
|
|
9559
8983
|
raise
|
|
9560
8984
|
if result is None:
|
|
@@ -9657,21 +9081,7 @@ class RecordTools(ToolBase):
|
|
|
9657
9081
|
normalized_apply_id = self._validate_app_and_record(app_key, apply_id)
|
|
9658
9082
|
|
|
9659
9083
|
def runner(session_profile, context):
|
|
9660
|
-
|
|
9661
|
-
update_index = None
|
|
9662
|
-
if needs_index:
|
|
9663
|
-
update_index = (
|
|
9664
|
-
self._get_system_browse_field_index(
|
|
9665
|
-
profile,
|
|
9666
|
-
context,
|
|
9667
|
-
app_key,
|
|
9668
|
-
list_type=DEFAULT_RECORD_LIST_TYPE,
|
|
9669
|
-
force_refresh=force_refresh_form,
|
|
9670
|
-
)
|
|
9671
|
-
if role == 1
|
|
9672
|
-
else self._get_field_index(profile, context, app_key, force_refresh=force_refresh_form)
|
|
9673
|
-
)
|
|
9674
|
-
index = update_index if verify_write else None
|
|
9084
|
+
index = self._get_field_index(profile, context, app_key, force_refresh=force_refresh_form) if verify_write else None
|
|
9675
9085
|
normalized_answers = self._resolve_answers(
|
|
9676
9086
|
profile,
|
|
9677
9087
|
context,
|
|
@@ -9679,7 +9089,6 @@ class RecordTools(ToolBase):
|
|
|
9679
9089
|
answers=answers or [],
|
|
9680
9090
|
fields=fields or {},
|
|
9681
9091
|
force_refresh_form=force_refresh_form,
|
|
9682
|
-
field_index_override=update_index,
|
|
9683
9092
|
)
|
|
9684
9093
|
self._validate_record_write(app_key, normalized_answers, apply_id=normalized_apply_id)
|
|
9685
9094
|
try:
|
|
@@ -9733,14 +9142,13 @@ class RecordTools(ToolBase):
|
|
|
9733
9142
|
def record_delete(self, *, profile: str, app_key: str, apply_id: int, list_type: int) -> JSONObject:
|
|
9734
9143
|
"""执行记录相关逻辑。"""
|
|
9735
9144
|
normalized_apply_id = self._validate_app_and_record(app_key, apply_id)
|
|
9736
|
-
delete_list_type = self._resolve_record_delete_list_type(view_id=None, list_type=list_type)
|
|
9737
9145
|
|
|
9738
9146
|
def runner(session_profile, context):
|
|
9739
9147
|
result = self.backend.request(
|
|
9740
9148
|
"DELETE",
|
|
9741
9149
|
context,
|
|
9742
9150
|
f"/app/{app_key}/apply",
|
|
9743
|
-
json_body={"type":
|
|
9151
|
+
json_body={"type": list_type, "applyIds": [normalized_apply_id]},
|
|
9744
9152
|
)
|
|
9745
9153
|
return self._attach_human_review_notice(
|
|
9746
9154
|
{
|
|
@@ -9749,7 +9157,6 @@ class RecordTools(ToolBase):
|
|
|
9749
9157
|
"request_route": self._request_route_payload(context),
|
|
9750
9158
|
"app_key": app_key,
|
|
9751
9159
|
"apply_id": normalized_apply_id,
|
|
9752
|
-
"list_type": delete_list_type,
|
|
9753
9160
|
"result": result,
|
|
9754
9161
|
},
|
|
9755
9162
|
operation="delete",
|
|
@@ -9794,7 +9201,7 @@ class RecordTools(ToolBase):
|
|
|
9794
9201
|
"GET",
|
|
9795
9202
|
context,
|
|
9796
9203
|
f"/app/{app_key}/apply/{apply_id}",
|
|
9797
|
-
params={"role":
|
|
9204
|
+
params={"role": 1, "listType": list_type},
|
|
9798
9205
|
)
|
|
9799
9206
|
answers = result.get("answers") if isinstance(result, dict) else None
|
|
9800
9207
|
answer_list = answers if isinstance(answers, list) else []
|
|
@@ -10153,7 +9560,7 @@ class RecordTools(ToolBase):
|
|
|
10153
9560
|
used_list_type: int | None = None
|
|
10154
9561
|
if view_selection is not None:
|
|
10155
9562
|
fallback_list_types = [view_route.list_type if view_route.list_type is not None else DEFAULT_RECORD_LIST_TYPE]
|
|
10156
|
-
elif view_route.list_type is not None:
|
|
9563
|
+
elif view_route.list_type is not None and view_route.list_type != DEFAULT_RECORD_LIST_TYPE:
|
|
10157
9564
|
fallback_list_types = [view_route.list_type]
|
|
10158
9565
|
else:
|
|
10159
9566
|
fallback_list_types = [DEFAULT_RECORD_LIST_TYPE, 14, 1, 2, 12]
|
|
@@ -10384,7 +9791,7 @@ class RecordTools(ToolBase):
|
|
|
10384
9791
|
try:
|
|
10385
9792
|
payload = self.backend.request("GET", context, f"/view/{view_key}/viewConfig")
|
|
10386
9793
|
except QingflowApiError as exc:
|
|
10387
|
-
if
|
|
9794
|
+
if exc.backend_code in {40002, 40027, 404} or exc.http_status == 404:
|
|
10388
9795
|
self._view_config_cache[cache_key] = None
|
|
10389
9796
|
return None
|
|
10390
9797
|
raise
|
|
@@ -10505,12 +9912,7 @@ class RecordTools(ToolBase):
|
|
|
10505
9912
|
)
|
|
10506
9913
|
normalized = _normalize_data_list_base_info_schema(payload)
|
|
10507
9914
|
if not isinstance(normalized.get("formQues"), list) or not normalized.get("formQues"):
|
|
10508
|
-
|
|
10509
|
-
return self._get_form_schema(profile, context, app_key, force_refresh=force_refresh)
|
|
10510
|
-
except QingflowApiError as exc:
|
|
10511
|
-
if not _is_optional_schema_permission_error(exc):
|
|
10512
|
-
raise
|
|
10513
|
-
return normalized
|
|
9915
|
+
return self._get_form_schema(profile, context, app_key, force_refresh=force_refresh)
|
|
10514
9916
|
self._form_cache[cache_key] = normalized
|
|
10515
9917
|
return normalized
|
|
10516
9918
|
|
|
@@ -10542,16 +9944,8 @@ class RecordTools(ToolBase):
|
|
|
10542
9944
|
cache_key = (profile, f"view:{view_key}", "browse_view_base_info", None)
|
|
10543
9945
|
if not force_refresh and cache_key in self._form_cache:
|
|
10544
9946
|
return self._form_cache[cache_key]
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
normalized = _normalize_data_list_base_info_schema(payload)
|
|
10548
|
-
form_ques = normalized.get("formQues")
|
|
10549
|
-
if not isinstance(form_ques, list) or not form_ques:
|
|
10550
|
-
normalized = self._get_view_form_schema(profile, context, view_key, force_refresh=force_refresh)
|
|
10551
|
-
except QingflowApiError as exc:
|
|
10552
|
-
if not _is_optional_schema_permission_error(exc):
|
|
10553
|
-
raise
|
|
10554
|
-
normalized = self._get_view_form_schema(profile, context, view_key, force_refresh=force_refresh)
|
|
9947
|
+
payload = self.backend.request("GET", context, f"/view/{view_key}/apply/baseInfo")
|
|
9948
|
+
normalized = _normalize_data_list_base_info_schema(payload)
|
|
10555
9949
|
self._form_cache[cache_key] = normalized
|
|
10556
9950
|
return normalized
|
|
10557
9951
|
|
|
@@ -10607,6 +10001,22 @@ class RecordTools(ToolBase):
|
|
|
10607
10001
|
force_refresh: bool,
|
|
10608
10002
|
) -> JSONObject:
|
|
10609
10003
|
"""Build the UI/table-view readable field scope from apply/baseInfo."""
|
|
10004
|
+
applicant_index: FieldIndex | None
|
|
10005
|
+
applicant_writable_field_ids: set[int]
|
|
10006
|
+
try:
|
|
10007
|
+
applicant_index = self._get_applicant_top_level_field_index(profile, context, app_key, force_refresh=force_refresh)
|
|
10008
|
+
except QingflowApiError as exc:
|
|
10009
|
+
if exc.backend_code != 40002:
|
|
10010
|
+
raise
|
|
10011
|
+
applicant_index = None
|
|
10012
|
+
applicant_writable_field_ids = set()
|
|
10013
|
+
else:
|
|
10014
|
+
applicant_writable_field_ids = {
|
|
10015
|
+
field.que_id
|
|
10016
|
+
for field in applicant_index.by_id.values()
|
|
10017
|
+
if bool(self._schema_write_hints(field)["writable"])
|
|
10018
|
+
}
|
|
10019
|
+
|
|
10610
10020
|
if resolved_view is not None and resolved_view.kind == "custom" and resolved_view.view_selection is not None:
|
|
10611
10021
|
schema = self._get_custom_view_browse_schema(
|
|
10612
10022
|
profile,
|
|
@@ -10615,16 +10025,6 @@ class RecordTools(ToolBase):
|
|
|
10615
10025
|
force_refresh=force_refresh,
|
|
10616
10026
|
)
|
|
10617
10027
|
index = _build_top_level_field_index(schema)
|
|
10618
|
-
visible_question_ids = {field.que_id for field in index.by_id.values()}
|
|
10619
|
-
return {
|
|
10620
|
-
"index": index,
|
|
10621
|
-
"writable_field_ids": {
|
|
10622
|
-
field.que_id
|
|
10623
|
-
for field in index.by_id.values()
|
|
10624
|
-
if bool(self._schema_write_hints(field)["writable"])
|
|
10625
|
-
},
|
|
10626
|
-
"visible_question_ids": visible_question_ids,
|
|
10627
|
-
}
|
|
10628
10028
|
elif resolved_view is not None and resolved_view.kind == "system" and resolved_view.list_type is not None:
|
|
10629
10029
|
schema = self._get_system_browse_base_info_schema(
|
|
10630
10030
|
profile,
|
|
@@ -10634,26 +10034,34 @@ class RecordTools(ToolBase):
|
|
|
10634
10034
|
force_refresh=force_refresh,
|
|
10635
10035
|
)
|
|
10636
10036
|
index = _build_top_level_field_index(schema)
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
field.que_id
|
|
10642
|
-
for field in index.by_id.values()
|
|
10643
|
-
if bool(self._schema_write_hints(field)["writable"])
|
|
10644
|
-
},
|
|
10645
|
-
"visible_question_ids": visible_question_ids,
|
|
10646
|
-
}
|
|
10037
|
+
else:
|
|
10038
|
+
index = applicant_index or _build_top_level_field_index(
|
|
10039
|
+
self._get_form_schema(profile, context, app_key, force_refresh=force_refresh)
|
|
10040
|
+
)
|
|
10647
10041
|
|
|
10648
|
-
applicant_index
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10042
|
+
if applicant_index is not None and index.by_id:
|
|
10043
|
+
enriched_fields = [
|
|
10044
|
+
_enrich_read_field_from_applicant(field, applicant_index.by_id.get(str(field.que_id)))
|
|
10045
|
+
for field in index.by_id.values()
|
|
10046
|
+
]
|
|
10047
|
+
index = _build_top_level_field_index({"formQues": [[_form_field_to_question(field) for field in enriched_fields]]})
|
|
10048
|
+
|
|
10049
|
+
visible_question_ids = {field.que_id for field in index.by_id.values()}
|
|
10050
|
+
if applicant_index is None:
|
|
10051
|
+
writable_field_ids = {
|
|
10653
10052
|
field.que_id
|
|
10654
|
-
for field in
|
|
10053
|
+
for field in index.by_id.values()
|
|
10655
10054
|
if bool(self._schema_write_hints(field)["writable"])
|
|
10656
|
-
}
|
|
10055
|
+
}
|
|
10056
|
+
else:
|
|
10057
|
+
writable_field_ids = {
|
|
10058
|
+
field_id
|
|
10059
|
+
for field_id in visible_question_ids
|
|
10060
|
+
if field_id in applicant_writable_field_ids
|
|
10061
|
+
}
|
|
10062
|
+
return {
|
|
10063
|
+
"index": index,
|
|
10064
|
+
"writable_field_ids": writable_field_ids,
|
|
10657
10065
|
"visible_question_ids": visible_question_ids,
|
|
10658
10066
|
}
|
|
10659
10067
|
|
|
@@ -10667,13 +10075,23 @@ class RecordTools(ToolBase):
|
|
|
10667
10075
|
force_refresh: bool,
|
|
10668
10076
|
) -> JSONObject:
|
|
10669
10077
|
"""执行内部辅助逻辑。"""
|
|
10078
|
+
applicant_index: FieldIndex | None
|
|
10079
|
+
applicant_writable_field_ids: set[int]
|
|
10080
|
+
try:
|
|
10081
|
+
applicant_index = self._get_applicant_top_level_field_index(profile, context, app_key, force_refresh=force_refresh)
|
|
10082
|
+
except QingflowApiError as exc:
|
|
10083
|
+
if exc.backend_code != 40002:
|
|
10084
|
+
raise
|
|
10085
|
+
applicant_index = None
|
|
10086
|
+
applicant_writable_field_ids = set()
|
|
10087
|
+
else:
|
|
10088
|
+
applicant_writable_field_ids = {
|
|
10089
|
+
field.que_id
|
|
10090
|
+
for field in applicant_index.by_id.values()
|
|
10091
|
+
if bool(self._schema_write_hints(field)["writable"])
|
|
10092
|
+
}
|
|
10670
10093
|
if resolved_view is not None and resolved_view.kind == "custom" and resolved_view.view_selection is not None:
|
|
10671
|
-
schema = self.
|
|
10672
|
-
profile,
|
|
10673
|
-
context,
|
|
10674
|
-
resolved_view.view_selection.view_key,
|
|
10675
|
-
force_refresh=force_refresh,
|
|
10676
|
-
)
|
|
10094
|
+
schema = self._get_view_form_schema(profile, context, resolved_view.view_selection.view_key, force_refresh=force_refresh)
|
|
10677
10095
|
index = _build_top_level_field_index(schema)
|
|
10678
10096
|
visible_question_ids = self._get_view_question_ids(profile, context, resolved_view.view_selection.view_key)
|
|
10679
10097
|
if not visible_question_ids:
|
|
@@ -10689,12 +10107,6 @@ class RecordTools(ToolBase):
|
|
|
10689
10107
|
index = _build_top_level_field_index(schema)
|
|
10690
10108
|
visible_question_ids = _question_ids_from_schema(schema)
|
|
10691
10109
|
else:
|
|
10692
|
-
applicant_index = self._get_applicant_top_level_field_index(profile, context, app_key, force_refresh=force_refresh)
|
|
10693
|
-
applicant_writable_field_ids = {
|
|
10694
|
-
field.que_id
|
|
10695
|
-
for field in applicant_index.by_id.values()
|
|
10696
|
-
if bool(self._schema_write_hints(field)["writable"])
|
|
10697
|
-
}
|
|
10698
10110
|
index = applicant_index or _build_top_level_field_index(
|
|
10699
10111
|
self._get_form_schema(profile, context, app_key, force_refresh=force_refresh)
|
|
10700
10112
|
)
|
|
@@ -10713,13 +10125,43 @@ class RecordTools(ToolBase):
|
|
|
10713
10125
|
"visible_question_ids": set(visible_question_ids),
|
|
10714
10126
|
}
|
|
10715
10127
|
|
|
10128
|
+
if applicant_index is None:
|
|
10129
|
+
return {
|
|
10130
|
+
"index": index,
|
|
10131
|
+
"writable_field_ids": {
|
|
10132
|
+
field.que_id
|
|
10133
|
+
for field in index.by_id.values()
|
|
10134
|
+
if bool(self._schema_write_hints(field)["writable"])
|
|
10135
|
+
},
|
|
10136
|
+
"visible_question_ids": visible_question_ids,
|
|
10137
|
+
}
|
|
10138
|
+
|
|
10139
|
+
augmented_fields = [
|
|
10140
|
+
_clone_form_field(applicant_index.by_id.get(str(field.que_id)) or field)
|
|
10141
|
+
for field in index.by_id.values()
|
|
10142
|
+
]
|
|
10143
|
+
augmented_field_ids = {field.que_id for field in augmented_fields}
|
|
10144
|
+
writable_field_ids = {
|
|
10145
|
+
field_id
|
|
10146
|
+
for field_id in visible_question_ids
|
|
10147
|
+
if field_id in applicant_writable_field_ids
|
|
10148
|
+
}
|
|
10149
|
+
for field in applicant_index.by_id.values():
|
|
10150
|
+
descendant_ids = _subtable_descendant_ids(field)
|
|
10151
|
+
field_visible = field.que_id in visible_question_ids
|
|
10152
|
+
descendant_visible = bool(descendant_ids and (descendant_ids & visible_question_ids))
|
|
10153
|
+
if not field_visible and not descendant_visible:
|
|
10154
|
+
continue
|
|
10155
|
+
if field.que_id not in augmented_field_ids:
|
|
10156
|
+
augmented_fields.append(_clone_form_field(field))
|
|
10157
|
+
augmented_field_ids.add(field.que_id)
|
|
10158
|
+
if descendant_visible:
|
|
10159
|
+
visible_question_ids.add(field.que_id)
|
|
10160
|
+
if field.que_id in applicant_writable_field_ids and (field_visible or descendant_visible):
|
|
10161
|
+
writable_field_ids.add(field.que_id)
|
|
10716
10162
|
return {
|
|
10717
|
-
"index":
|
|
10718
|
-
"writable_field_ids":
|
|
10719
|
-
field.que_id
|
|
10720
|
-
for field in index.by_id.values()
|
|
10721
|
-
if bool(self._schema_write_hints(field)["writable"])
|
|
10722
|
-
},
|
|
10163
|
+
"index": _build_top_level_field_index({"formQues": [[_form_field_to_question(field) for field in augmented_fields]]}),
|
|
10164
|
+
"writable_field_ids": writable_field_ids,
|
|
10723
10165
|
"visible_question_ids": visible_question_ids,
|
|
10724
10166
|
}
|
|
10725
10167
|
|
|
@@ -10784,7 +10226,7 @@ class RecordTools(ToolBase):
|
|
|
10784
10226
|
try:
|
|
10785
10227
|
payload = self.backend.request("GET", context, f"/view/{view_key}/question")
|
|
10786
10228
|
except QingflowApiError as exc:
|
|
10787
|
-
if
|
|
10229
|
+
if exc.backend_code in {40002, 40027}:
|
|
10788
10230
|
return set()
|
|
10789
10231
|
raise
|
|
10790
10232
|
if not isinstance(payload, list):
|
|
@@ -10828,7 +10270,7 @@ class RecordTools(ToolBase):
|
|
|
10828
10270
|
)
|
|
10829
10271
|
return True
|
|
10830
10272
|
except QingflowApiError as exc:
|
|
10831
|
-
if
|
|
10273
|
+
if exc.backend_code in {40002, 40027}:
|
|
10832
10274
|
return False
|
|
10833
10275
|
raise
|
|
10834
10276
|
|
|
@@ -10981,12 +10423,7 @@ class RecordTools(ToolBase):
|
|
|
10981
10423
|
requested_name = _normalize_optional_text(view_name)
|
|
10982
10424
|
if requested_key is None and requested_name is None:
|
|
10983
10425
|
return None
|
|
10984
|
-
|
|
10985
|
-
views = self._get_view_list(profile, context, app_key)
|
|
10986
|
-
except QingflowApiError as exc:
|
|
10987
|
-
if requested_key is None or not _is_record_permission_denied_error(exc):
|
|
10988
|
-
raise
|
|
10989
|
-
views = []
|
|
10426
|
+
views = self._get_view_list(profile, context, app_key)
|
|
10990
10427
|
selected: JSONObject | None = None
|
|
10991
10428
|
if requested_key is not None:
|
|
10992
10429
|
selected = next((item for item in views if _normalize_optional_text(item.get("viewKey")) == requested_key), None)
|
|
@@ -11184,11 +10621,9 @@ class RecordTools(ToolBase):
|
|
|
11184
10621
|
|
|
11185
10622
|
def _should_retry_list_type_fallback(self, error: QingflowApiError) -> bool:
|
|
11186
10623
|
"""执行内部辅助逻辑。"""
|
|
11187
|
-
if
|
|
11188
|
-
return False
|
|
11189
|
-
if backend_code_int(error) in {40002, 40027, 404}:
|
|
10624
|
+
if error.backend_code in {40002, 40027, 404}:
|
|
11190
10625
|
return True
|
|
11191
|
-
if error.http_status
|
|
10626
|
+
if error.http_status in {404, 500}:
|
|
11192
10627
|
return True
|
|
11193
10628
|
return False
|
|
11194
10629
|
|
|
@@ -11970,8 +11405,6 @@ class RecordTools(ToolBase):
|
|
|
11970
11405
|
schema: JSONObject = {}
|
|
11971
11406
|
if isinstance(raw.get("subQuestions"), list):
|
|
11972
11407
|
schema["formQues"] = [raw["subQuestions"]]
|
|
11973
|
-
elif isinstance(raw.get("subQues"), list):
|
|
11974
|
-
schema["formQues"] = [raw["subQues"]]
|
|
11975
11408
|
elif isinstance(raw.get("innerQuestions"), list):
|
|
11976
11409
|
schema["formQues"] = raw["innerQuestions"]
|
|
11977
11410
|
index = _build_field_index(schema)
|
|
@@ -12011,70 +11444,6 @@ class RecordTools(ToolBase):
|
|
|
12011
11444
|
)
|
|
12012
11445
|
)
|
|
12013
11446
|
|
|
12014
|
-
def _candidate_lookup_failed_response(
|
|
12015
|
-
self,
|
|
12016
|
-
*,
|
|
12017
|
-
profile: str,
|
|
12018
|
-
session_profile, # type: ignore[no-untyped-def]
|
|
12019
|
-
context, # type: ignore[no-untyped-def]
|
|
12020
|
-
kind: str,
|
|
12021
|
-
error: RecordInputError,
|
|
12022
|
-
field: FormField,
|
|
12023
|
-
app_key: str,
|
|
12024
|
-
record_id_text: str | None,
|
|
12025
|
-
workflow_node_id: int | None,
|
|
12026
|
-
fields_present: bool,
|
|
12027
|
-
keyword: str,
|
|
12028
|
-
scope_source: str,
|
|
12029
|
-
) -> JSONObject:
|
|
12030
|
-
"""Return a structured result when an optional field candidate lookup is unavailable."""
|
|
12031
|
-
error_payload = error.to_dict()
|
|
12032
|
-
error_details = error_payload.get("details") if isinstance(error_payload.get("details"), dict) else {}
|
|
12033
|
-
candidate_error = error_details.get("candidate_error") if isinstance(error_details.get("candidate_error"), dict) else {}
|
|
12034
|
-
warning_transport = {
|
|
12035
|
-
key: candidate_error.get(key)
|
|
12036
|
-
for key in ("backend_code", "http_status", "request_id")
|
|
12037
|
-
if candidate_error.get(key) is not None
|
|
12038
|
-
}
|
|
12039
|
-
selection: JSONObject = {
|
|
12040
|
-
"app_key": app_key,
|
|
12041
|
-
"field_id": field.que_id,
|
|
12042
|
-
"field_title": field.que_title,
|
|
12043
|
-
"record_id": record_id_text,
|
|
12044
|
-
"workflow_node_id": workflow_node_id,
|
|
12045
|
-
"fields_present": fields_present,
|
|
12046
|
-
"keyword": keyword,
|
|
12047
|
-
"permission_scope": "applicant_node",
|
|
12048
|
-
}
|
|
12049
|
-
return {
|
|
12050
|
-
"profile": profile,
|
|
12051
|
-
"ws_id": session_profile.selected_ws_id,
|
|
12052
|
-
"ok": False,
|
|
12053
|
-
"status": "failed",
|
|
12054
|
-
"error_code": error.error_code,
|
|
12055
|
-
"message": error.message,
|
|
12056
|
-
"request_route": self._request_route_payload(context),
|
|
12057
|
-
"warnings": [
|
|
12058
|
-
{
|
|
12059
|
-
"code": error.error_code,
|
|
12060
|
-
"message": error.fix_hint,
|
|
12061
|
-
"kind": kind,
|
|
12062
|
-
"field_id": field.que_id,
|
|
12063
|
-
"field_title": field.que_title,
|
|
12064
|
-
**warning_transport,
|
|
12065
|
-
}
|
|
12066
|
-
],
|
|
12067
|
-
"output_profile": "normal",
|
|
12068
|
-
"data": {
|
|
12069
|
-
"items": [],
|
|
12070
|
-
"pagination": {"returned_items": 0},
|
|
12071
|
-
"selection": selection,
|
|
12072
|
-
"scope_source": scope_source,
|
|
12073
|
-
"fix_hint": error.fix_hint,
|
|
12074
|
-
},
|
|
12075
|
-
"details": error_details,
|
|
12076
|
-
}
|
|
12077
|
-
|
|
12078
11447
|
def _request_route_payload(self, context) -> JSONObject: # type: ignore[no-untyped-def]
|
|
12079
11448
|
"""执行内部辅助逻辑。"""
|
|
12080
11449
|
describe_route = getattr(self.backend, "describe_route", None)
|
|
@@ -12248,7 +11617,7 @@ class RecordTools(ToolBase):
|
|
|
12248
11617
|
selection: JSONObject | None,
|
|
12249
11618
|
) -> None:
|
|
12250
11619
|
"""执行内部辅助逻辑。"""
|
|
12251
|
-
if
|
|
11620
|
+
if exc.backend_code != 40002:
|
|
12252
11621
|
raise exc
|
|
12253
11622
|
raise_tool_error(
|
|
12254
11623
|
QingflowApiError(
|
|
@@ -12414,7 +11783,6 @@ class RecordTools(ToolBase):
|
|
|
12414
11783
|
response_status = raw_status or "failed"
|
|
12415
11784
|
update_route = raw_apply.get("update_route") if isinstance(raw_apply.get("update_route"), dict) else None
|
|
12416
11785
|
tried_routes = raw_apply.get("tried_routes") if isinstance(raw_apply.get("tried_routes"), list) else []
|
|
12417
|
-
expose_tried_routes = output_profile == "verbose" or response_status != "success"
|
|
12418
11786
|
response: JSONObject = {
|
|
12419
11787
|
"profile": raw_apply.get("profile"),
|
|
12420
11788
|
"ws_id": raw_apply.get("ws_id"),
|
|
@@ -12427,6 +11795,7 @@ class RecordTools(ToolBase):
|
|
|
12427
11795
|
"warnings": warnings,
|
|
12428
11796
|
"output_profile": output_profile,
|
|
12429
11797
|
"update_route": update_route,
|
|
11798
|
+
"tried_routes": tried_routes,
|
|
12430
11799
|
"data": {
|
|
12431
11800
|
"action": {"operation": operation, "executed": True},
|
|
12432
11801
|
"resource": resource,
|
|
@@ -12438,11 +11807,9 @@ class RecordTools(ToolBase):
|
|
|
12438
11807
|
"resolved_fields": resolved_fields,
|
|
12439
11808
|
"human_review": self._record_write_human_review_payload(operation, enabled=human_review),
|
|
12440
11809
|
"update_route": update_route,
|
|
11810
|
+
"tried_routes": tried_routes,
|
|
12441
11811
|
},
|
|
12442
11812
|
}
|
|
12443
|
-
if expose_tried_routes:
|
|
12444
|
-
response["tried_routes"] = tried_routes
|
|
12445
|
-
response["data"]["tried_routes"] = tried_routes
|
|
12446
11813
|
if record_id is not None:
|
|
12447
11814
|
response["record_id"] = record_id
|
|
12448
11815
|
if apply_id is not None:
|
|
@@ -12622,7 +11989,7 @@ class RecordTools(ToolBase):
|
|
|
12622
11989
|
)
|
|
12623
11990
|
return errors
|
|
12624
11991
|
|
|
12625
|
-
def _record_delete_many(self, *, profile: str, app_key: str, record_ids: list[int]
|
|
11992
|
+
def _record_delete_many(self, *, profile: str, app_key: str, record_ids: list[int]) -> JSONObject:
|
|
12626
11993
|
"""执行内部辅助逻辑。"""
|
|
12627
11994
|
if not app_key:
|
|
12628
11995
|
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
@@ -12635,14 +12002,14 @@ class RecordTools(ToolBase):
|
|
|
12635
12002
|
"DELETE",
|
|
12636
12003
|
context,
|
|
12637
12004
|
f"/app/{app_key}/apply",
|
|
12638
|
-
json_body={"type":
|
|
12005
|
+
json_body={"type": DEFAULT_RECORD_LIST_TYPE, "applyIds": normalized_ids},
|
|
12639
12006
|
)
|
|
12640
12007
|
return {
|
|
12641
12008
|
"profile": profile,
|
|
12642
12009
|
"ws_id": session_profile.selected_ws_id,
|
|
12643
12010
|
"request_route": self._request_route_payload(context),
|
|
12644
12011
|
"result": result,
|
|
12645
|
-
"resource": {"type": "record", "apply_ids": normalized_ids
|
|
12012
|
+
"resource": {"type": "record", "apply_ids": normalized_ids},
|
|
12646
12013
|
"ok": True,
|
|
12647
12014
|
}
|
|
12648
12015
|
|
|
@@ -13216,30 +12583,6 @@ class RecordTools(ToolBase):
|
|
|
13216
12583
|
},
|
|
13217
12584
|
)
|
|
13218
12585
|
|
|
13219
|
-
def _candidate_lookup_error(
|
|
13220
|
-
self,
|
|
13221
|
-
*,
|
|
13222
|
-
kind: str,
|
|
13223
|
-
field: FormField,
|
|
13224
|
-
value: JSONValue,
|
|
13225
|
-
error: QingflowApiError,
|
|
13226
|
-
) -> RecordInputError:
|
|
13227
|
-
"""Build the standard candidate lookup failure without raising it."""
|
|
13228
|
-
field_kind = "member" if kind == "member" else "department"
|
|
13229
|
-
return RecordInputError(
|
|
13230
|
-
message=f"{field_kind} candidates for field '{field.que_title}' could not be loaded",
|
|
13231
|
-
error_code=f"{kind.upper()}_CANDIDATE_LOOKUP_FAILED",
|
|
13232
|
-
fix_hint=(
|
|
13233
|
-
f"Run record_{field_kind}_candidates again after the backend error is resolved, "
|
|
13234
|
-
"then choose one returned item exactly."
|
|
13235
|
-
),
|
|
13236
|
-
details={
|
|
13237
|
-
"field": _field_ref_payload(field),
|
|
13238
|
-
"received_value": value,
|
|
13239
|
-
"candidate_error": error.to_dict(),
|
|
13240
|
-
},
|
|
13241
|
-
)
|
|
13242
|
-
|
|
13243
12586
|
def _candidate_keyword_from_value(
|
|
13244
12587
|
self,
|
|
13245
12588
|
value: JSONValue,
|
|
@@ -13504,7 +12847,14 @@ class RecordTools(ToolBase):
|
|
|
13504
12847
|
|
|
13505
12848
|
def _lookup_department_detail(self, context, keyword: str, *, purpose: str) -> JSONObject: # type: ignore[no-untyped-def]
|
|
13506
12849
|
"""执行内部辅助逻辑。"""
|
|
13507
|
-
|
|
12850
|
+
payload = self.backend.request(
|
|
12851
|
+
"GET",
|
|
12852
|
+
context,
|
|
12853
|
+
"/contact/deptByPage",
|
|
12854
|
+
params={"keyword": keyword, "pageNum": 1, "pageSize": 20},
|
|
12855
|
+
)
|
|
12856
|
+
rows = payload.get("list") if isinstance(payload, dict) else None
|
|
12857
|
+
items = [item for item in rows if isinstance(item, dict)] if isinstance(rows, list) else []
|
|
13508
12858
|
normalized_keyword = keyword.strip()
|
|
13509
12859
|
exact = [
|
|
13510
12860
|
item for item in items
|
|
@@ -14029,7 +13379,6 @@ class RecordTools(ToolBase):
|
|
|
14029
13379
|
normalized_answers: list[JSONObject],
|
|
14030
13380
|
index: FieldIndex,
|
|
14031
13381
|
verify_list_type: int = DEFAULT_RECORD_LIST_TYPE,
|
|
14032
|
-
verify_role: int | None = None,
|
|
14033
13382
|
verify_view_key: str | None = None,
|
|
14034
13383
|
) -> JSONObject:
|
|
14035
13384
|
"""执行内部辅助逻辑。"""
|
|
@@ -14049,20 +13398,14 @@ class RecordTools(ToolBase):
|
|
|
14049
13398
|
f"/view/{verify_view_key}/apply/{apply_id}",
|
|
14050
13399
|
)
|
|
14051
13400
|
else:
|
|
14052
|
-
role = verify_role if verify_role is not None else 1
|
|
14053
13401
|
record = self.backend.request(
|
|
14054
13402
|
"GET",
|
|
14055
13403
|
context,
|
|
14056
13404
|
f"/app/{app_key}/apply/{apply_id}",
|
|
14057
|
-
params={"role":
|
|
13405
|
+
params={"role": 1, "listType": verify_list_type},
|
|
14058
13406
|
)
|
|
14059
13407
|
except QingflowApiError as exc:
|
|
14060
13408
|
if verify_view_key:
|
|
14061
|
-
warning: JSONObject = {
|
|
14062
|
-
"code": "WRITE_VERIFY_CUSTOM_VIEW_READBACK_FAILED",
|
|
14063
|
-
"message": "Write was sent through a custom view route, but the same view could not be re-read for field-level verification.",
|
|
14064
|
-
}
|
|
14065
|
-
warning.update(_record_detail_error_warning_fields(exc))
|
|
14066
13409
|
return {
|
|
14067
13410
|
"verified": False,
|
|
14068
13411
|
"verification_mode": "custom_view_record_detail",
|
|
@@ -14071,9 +13414,14 @@ class RecordTools(ToolBase):
|
|
|
14071
13414
|
"missing_fields": [],
|
|
14072
13415
|
"empty_fields": [],
|
|
14073
13416
|
"count_mismatches": [],
|
|
14074
|
-
"warnings": [
|
|
13417
|
+
"warnings": [{
|
|
13418
|
+
"code": "WRITE_VERIFY_CUSTOM_VIEW_READBACK_FAILED",
|
|
13419
|
+
"message": "Write was sent through a custom view route, but the same view could not be re-read for field-level verification.",
|
|
13420
|
+
"backend_code": exc.backend_code,
|
|
13421
|
+
"http_status": exc.http_status,
|
|
13422
|
+
}],
|
|
14075
13423
|
}
|
|
14076
|
-
if
|
|
13424
|
+
if exc.backend_code != 40002:
|
|
14077
13425
|
raise
|
|
14078
13426
|
return self._verify_record_write_result_via_initiated_tasks(
|
|
14079
13427
|
context,
|
|
@@ -14121,7 +13469,6 @@ class RecordTools(ToolBase):
|
|
|
14121
13469
|
or len(count_mismatches) > mismatch_before
|
|
14122
13470
|
):
|
|
14123
13471
|
continue
|
|
14124
|
-
continue
|
|
14125
13472
|
expected_value = _canonicalize_answer_value_for_compare(answer, field)
|
|
14126
13473
|
actual_value = _canonicalize_answer_value_for_compare(actual, field)
|
|
14127
13474
|
if not _canonical_value_is_empty(expected_value) and _canonical_value_is_empty(actual_value):
|
|
@@ -14368,8 +13715,6 @@ def _normalize_data_list_base_info_schema(payload: JSONValue) -> JSONObject:
|
|
|
14368
13715
|
if not isinstance(payload, dict):
|
|
14369
13716
|
return {}
|
|
14370
13717
|
que_base_infos = payload.get("queBaseInfos")
|
|
14371
|
-
if not isinstance(que_base_infos, list) and isinstance(payload.get("formQues"), list):
|
|
14372
|
-
que_base_infos = payload.get("formQues")
|
|
14373
13718
|
if not isinstance(que_base_infos, list):
|
|
14374
13719
|
return {}
|
|
14375
13720
|
return {
|
|
@@ -14756,44 +14101,6 @@ def _build_answer_backed_field_index(
|
|
|
14756
14101
|
)
|
|
14757
14102
|
|
|
14758
14103
|
|
|
14759
|
-
def _merge_subtable_parent_field(primary: FormField, extra: FormField) -> FormField:
|
|
14760
|
-
if primary.que_type not in SUBTABLE_QUE_TYPES or extra.que_type not in SUBTABLE_QUE_TYPES:
|
|
14761
|
-
return primary
|
|
14762
|
-
primary_raw = dict(primary.raw) if isinstance(primary.raw, dict) else {}
|
|
14763
|
-
extra_raw = dict(extra.raw) if isinstance(extra.raw, dict) else {}
|
|
14764
|
-
primary_subquestions = primary_raw.get("subQuestions")
|
|
14765
|
-
extra_subquestions = extra_raw.get("subQuestions")
|
|
14766
|
-
if not isinstance(primary_subquestions, list) or not isinstance(extra_subquestions, list):
|
|
14767
|
-
return primary
|
|
14768
|
-
merged_subquestions = [item for item in primary_subquestions if isinstance(item, dict)]
|
|
14769
|
-
seen_ids = {
|
|
14770
|
-
_coerce_count(item.get("queId"))
|
|
14771
|
-
for item in merged_subquestions
|
|
14772
|
-
if isinstance(item, dict) and _coerce_count(item.get("queId")) is not None
|
|
14773
|
-
}
|
|
14774
|
-
for item in extra_subquestions:
|
|
14775
|
-
if not isinstance(item, dict):
|
|
14776
|
-
continue
|
|
14777
|
-
que_id = _coerce_count(item.get("queId"))
|
|
14778
|
-
if que_id is not None and que_id in seen_ids:
|
|
14779
|
-
continue
|
|
14780
|
-
merged_subquestions.append(item)
|
|
14781
|
-
if que_id is not None:
|
|
14782
|
-
seen_ids.add(que_id)
|
|
14783
|
-
if len(merged_subquestions) == len(primary_subquestions):
|
|
14784
|
-
return primary
|
|
14785
|
-
merged_raw = dict(primary_raw)
|
|
14786
|
-
merged_raw["subQuestions"] = merged_subquestions
|
|
14787
|
-
merged_field = _clone_form_field(primary)
|
|
14788
|
-
merged_field.raw = merged_raw
|
|
14789
|
-
return merged_field
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
def _replace_field_in_lookup(index: dict[str, list[FormField]], field: FormField) -> None:
|
|
14793
|
-
for key, fields in list(index.items()):
|
|
14794
|
-
index[key] = [field if existing.que_id == field.que_id else existing for existing in fields]
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
14104
|
def _merge_field_indexes(primary: FieldIndex, extra: FieldIndex) -> FieldIndex:
|
|
14798
14105
|
by_id = dict(primary.by_id)
|
|
14799
14106
|
by_title = {key: list(value) for key, value in primary.by_title.items()}
|
|
@@ -14804,42 +14111,12 @@ def _merge_field_indexes(primary: FieldIndex, extra: FieldIndex) -> FieldIndex:
|
|
|
14804
14111
|
|
|
14805
14112
|
for field_id, field in extra.by_id.items():
|
|
14806
14113
|
if field_id in by_id:
|
|
14807
|
-
merged_field = _merge_subtable_parent_field(by_id[field_id], field)
|
|
14808
|
-
if merged_field is not by_id[field_id]:
|
|
14809
|
-
by_id[field_id] = merged_field
|
|
14810
|
-
_replace_field_in_lookup(by_title, merged_field)
|
|
14811
|
-
_replace_field_in_lookup(by_alias, merged_field)
|
|
14812
14114
|
continue
|
|
14813
14115
|
by_id[field_id] = field
|
|
14814
14116
|
by_title.setdefault(_normalize_field_lookup_key(field.que_title), []).append(field)
|
|
14815
14117
|
for alias in field.aliases:
|
|
14816
14118
|
by_alias.setdefault(_normalize_field_lookup_key(alias), []).append(field)
|
|
14817
14119
|
|
|
14818
|
-
for field_id, fields in extra.subtable_leaf_by_id.items():
|
|
14819
|
-
merged = subtable_leaf_by_id.setdefault(field_id, [])
|
|
14820
|
-
existing = {(ref.field.que_id, ref.parent_field.que_id) for ref in merged}
|
|
14821
|
-
for field in fields:
|
|
14822
|
-
key = (field.field.que_id, field.parent_field.que_id)
|
|
14823
|
-
if key not in existing:
|
|
14824
|
-
merged.append(field)
|
|
14825
|
-
existing.add(key)
|
|
14826
|
-
for title, fields in extra.subtable_leaf_by_title.items():
|
|
14827
|
-
merged = subtable_leaf_by_title.setdefault(title, [])
|
|
14828
|
-
existing = {(ref.field.que_id, ref.parent_field.que_id) for ref in merged}
|
|
14829
|
-
for field in fields:
|
|
14830
|
-
key = (field.field.que_id, field.parent_field.que_id)
|
|
14831
|
-
if key not in existing:
|
|
14832
|
-
merged.append(field)
|
|
14833
|
-
existing.add(key)
|
|
14834
|
-
for alias, fields in extra.subtable_leaf_by_alias.items():
|
|
14835
|
-
merged = subtable_leaf_by_alias.setdefault(alias, [])
|
|
14836
|
-
existing = {(ref.field.que_id, ref.parent_field.que_id) for ref in merged}
|
|
14837
|
-
for field in fields:
|
|
14838
|
-
key = (field.field.que_id, field.parent_field.que_id)
|
|
14839
|
-
if key not in existing:
|
|
14840
|
-
merged.append(field)
|
|
14841
|
-
existing.add(key)
|
|
14842
|
-
|
|
14843
14120
|
return FieldIndex(
|
|
14844
14121
|
by_id=by_id,
|
|
14845
14122
|
by_title=by_title,
|
|
@@ -16230,28 +15507,6 @@ def _record_detail_unavailable_context(section: str, message: str, exc: Qingflow
|
|
|
16230
15507
|
"message": message,
|
|
16231
15508
|
"category": exc.category,
|
|
16232
15509
|
}
|
|
16233
|
-
if is_auth_like_error(exc):
|
|
16234
|
-
payload["auth_like"] = True
|
|
16235
|
-
payload["error_code"] = "AUTH_REQUIRED"
|
|
16236
|
-
if exc.backend_code is not None:
|
|
16237
|
-
payload["backend_code"] = exc.backend_code
|
|
16238
|
-
if exc.http_status is not None:
|
|
16239
|
-
payload["http_status"] = exc.http_status
|
|
16240
|
-
request_id = getattr(exc, "request_id", None)
|
|
16241
|
-
if request_id:
|
|
16242
|
-
payload["request_id"] = request_id
|
|
16243
|
-
details = exc.details if isinstance(exc.details, dict) else {}
|
|
16244
|
-
error_code = details.get("error_code")
|
|
16245
|
-
if error_code and not payload.get("error_code"):
|
|
16246
|
-
payload["error_code"] = error_code
|
|
16247
|
-
return payload
|
|
16248
|
-
|
|
16249
|
-
|
|
16250
|
-
def _record_detail_error_warning_fields(exc: QingflowApiError) -> JSONObject:
|
|
16251
|
-
payload: JSONObject = {"category": exc.category}
|
|
16252
|
-
if is_auth_like_error(exc):
|
|
16253
|
-
payload["auth_like"] = True
|
|
16254
|
-
payload["error_code"] = "AUTH_REQUIRED"
|
|
16255
15510
|
if exc.backend_code is not None:
|
|
16256
15511
|
payload["backend_code"] = exc.backend_code
|
|
16257
15512
|
if exc.http_status is not None:
|
|
@@ -16261,35 +15516,11 @@ def _record_detail_error_warning_fields(exc: QingflowApiError) -> JSONObject:
|
|
|
16261
15516
|
payload["request_id"] = request_id
|
|
16262
15517
|
details = exc.details if isinstance(exc.details, dict) else {}
|
|
16263
15518
|
error_code = details.get("error_code")
|
|
16264
|
-
if error_code
|
|
15519
|
+
if error_code:
|
|
16265
15520
|
payload["error_code"] = error_code
|
|
16266
15521
|
return payload
|
|
16267
15522
|
|
|
16268
15523
|
|
|
16269
|
-
def _record_detail_refreshed_source_url(refresh_result: Any) -> str | None:
|
|
16270
|
-
if isinstance(refresh_result, dict):
|
|
16271
|
-
return _normalize_optional_text(refresh_result.get("source_url"))
|
|
16272
|
-
return _normalize_optional_text(refresh_result)
|
|
16273
|
-
|
|
16274
|
-
|
|
16275
|
-
def _record_detail_append_refresh_warning(
|
|
16276
|
-
warnings: list[JSONObject],
|
|
16277
|
-
refresh_result: Any,
|
|
16278
|
-
*,
|
|
16279
|
-
id_key: str,
|
|
16280
|
-
id_value: str,
|
|
16281
|
-
) -> None:
|
|
16282
|
-
if not isinstance(refresh_result, dict):
|
|
16283
|
-
return
|
|
16284
|
-
warning = refresh_result.get("warning")
|
|
16285
|
-
if not isinstance(warning, dict):
|
|
16286
|
-
return
|
|
16287
|
-
payload: JSONObject = dict(warning)
|
|
16288
|
-
payload.setdefault("code", "ASSET_STORAGE_URL_REFRESH_FAILED")
|
|
16289
|
-
payload.setdefault(id_key, id_value)
|
|
16290
|
-
warnings.append(payload)
|
|
16291
|
-
|
|
16292
|
-
|
|
16293
15524
|
_RECORD_MEDIA_IMG_SRC_RE = re.compile(r"""<img\b[^>]*\bsrc\s*=\s*["']?([^"'\s>]+)""", re.IGNORECASE)
|
|
16294
15525
|
_RECORD_MEDIA_MD_IMAGE_RE = re.compile(r"""!\[[^\]]*]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)""")
|
|
16295
15526
|
_RECORD_MEDIA_URL_RE = re.compile(r"""https?://[^\s<>"')\]]+""", re.IGNORECASE)
|
|
@@ -16444,14 +15675,7 @@ def _record_detail_media_assets_payload(
|
|
|
16444
15675
|
except QingflowApiError as exc:
|
|
16445
15676
|
blocked = exc.http_status in {401, 403}
|
|
16446
15677
|
if blocked and download_strategy != "referer_acl" and callable(refresh_source_url):
|
|
16447
|
-
|
|
16448
|
-
_record_detail_append_refresh_warning(
|
|
16449
|
-
warnings,
|
|
16450
|
-
refresh_result,
|
|
16451
|
-
id_key="asset_id",
|
|
16452
|
-
id_value=asset_id,
|
|
16453
|
-
)
|
|
16454
|
-
refreshed_url = _record_detail_refreshed_source_url(refresh_result)
|
|
15678
|
+
refreshed_url = _normalize_optional_text(refresh_source_url(candidate))
|
|
16455
15679
|
if refreshed_url and refreshed_url != source_url:
|
|
16456
15680
|
refreshed_strategy = _record_detail_media_download_strategy(refreshed_url)
|
|
16457
15681
|
try:
|
|
@@ -16493,13 +15717,14 @@ def _record_detail_media_assets_payload(
|
|
|
16493
15717
|
"readable_by_agent": False,
|
|
16494
15718
|
}
|
|
16495
15719
|
)
|
|
16496
|
-
|
|
16497
|
-
|
|
16498
|
-
|
|
16499
|
-
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
|
|
15720
|
+
warnings.append(
|
|
15721
|
+
{
|
|
15722
|
+
"code": warning_code,
|
|
15723
|
+
"asset_id": asset_id,
|
|
15724
|
+
"message": f"record_get could not download image asset {asset_id}: {exc.message}",
|
|
15725
|
+
"http_status": exc.http_status,
|
|
15726
|
+
}
|
|
15727
|
+
)
|
|
16503
15728
|
continue
|
|
16504
15729
|
|
|
16505
15730
|
if not isinstance(content, bytes):
|
|
@@ -16717,14 +15942,7 @@ def _record_detail_file_assets_payload(
|
|
|
16717
15942
|
except QingflowApiError as exc:
|
|
16718
15943
|
blocked = exc.http_status in {401, 403}
|
|
16719
15944
|
if blocked and download_strategy != "referer_acl" and callable(refresh_source_url):
|
|
16720
|
-
|
|
16721
|
-
_record_detail_append_refresh_warning(
|
|
16722
|
-
warnings,
|
|
16723
|
-
refresh_result,
|
|
16724
|
-
id_key="file_asset_id",
|
|
16725
|
-
id_value=file_asset_id,
|
|
16726
|
-
)
|
|
16727
|
-
refreshed_url = _record_detail_refreshed_source_url(refresh_result)
|
|
15945
|
+
refreshed_url = _normalize_optional_text(refresh_source_url(candidate))
|
|
16728
15946
|
if refreshed_url and refreshed_url != source_url:
|
|
16729
15947
|
refreshed_strategy = _record_detail_media_download_strategy(refreshed_url)
|
|
16730
15948
|
try:
|
|
@@ -16771,13 +15989,14 @@ def _record_detail_file_assets_payload(
|
|
|
16771
15989
|
"extraction": {"status": "failed", "text_path": None, "preview": None},
|
|
16772
15990
|
}
|
|
16773
15991
|
)
|
|
16774
|
-
|
|
16775
|
-
|
|
16776
|
-
|
|
16777
|
-
|
|
16778
|
-
|
|
16779
|
-
|
|
16780
|
-
|
|
15992
|
+
warnings.append(
|
|
15993
|
+
{
|
|
15994
|
+
"code": warning_code,
|
|
15995
|
+
"file_asset_id": file_asset_id,
|
|
15996
|
+
"message": f"record_get could not download file asset {file_asset_id}: {exc.message}",
|
|
15997
|
+
"http_status": exc.http_status,
|
|
15998
|
+
}
|
|
15999
|
+
)
|
|
16781
16000
|
continue
|
|
16782
16001
|
|
|
16783
16002
|
if not isinstance(content, bytes):
|
|
@@ -18896,14 +18115,12 @@ def _extract_sort_selector(item: JSONObject) -> JSONValue:
|
|
|
18896
18115
|
return None
|
|
18897
18116
|
|
|
18898
18117
|
|
|
18899
|
-
def _normalize_public_column_selectors(columns: list[JSONObject | int
|
|
18118
|
+
def _normalize_public_column_selectors(columns: list[JSONObject | int]) -> list[int]:
|
|
18900
18119
|
normalized: list[int] = []
|
|
18901
18120
|
for item in columns:
|
|
18902
18121
|
field_id: int | None = None
|
|
18903
18122
|
if isinstance(item, int):
|
|
18904
18123
|
field_id = item
|
|
18905
|
-
elif isinstance(item, str):
|
|
18906
|
-
field_id = _coerce_count(item)
|
|
18907
18124
|
elif isinstance(item, dict):
|
|
18908
18125
|
_ensure_allowed_record_list_keys(
|
|
18909
18126
|
item,
|
|
@@ -18915,21 +18132,19 @@ def _normalize_public_column_selectors(columns: list[JSONObject | int | str]) ->
|
|
|
18915
18132
|
if field_id is None or field_id < 0:
|
|
18916
18133
|
raise_tool_error(
|
|
18917
18134
|
QingflowApiError.config_error(
|
|
18918
|
-
"columns must be a list of field_id integers
|
|
18135
|
+
"columns must be a list of field_id integers or {field_id} objects"
|
|
18919
18136
|
)
|
|
18920
18137
|
)
|
|
18921
18138
|
normalized.append(field_id)
|
|
18922
18139
|
return normalized
|
|
18923
18140
|
|
|
18924
18141
|
|
|
18925
|
-
def _normalize_public_query_field_selectors(query_fields: list[JSONObject | int
|
|
18142
|
+
def _normalize_public_query_field_selectors(query_fields: list[JSONObject | int]) -> list[int]:
|
|
18926
18143
|
normalized: list[int] = []
|
|
18927
18144
|
for item in query_fields:
|
|
18928
18145
|
field_id: int | None = None
|
|
18929
18146
|
if isinstance(item, int):
|
|
18930
18147
|
field_id = item
|
|
18931
|
-
elif isinstance(item, str):
|
|
18932
|
-
field_id = _coerce_count(item)
|
|
18933
18148
|
elif isinstance(item, dict):
|
|
18934
18149
|
_ensure_allowed_record_list_keys(
|
|
18935
18150
|
item,
|
|
@@ -18941,7 +18156,7 @@ def _normalize_public_query_field_selectors(query_fields: list[JSONObject | int
|
|
|
18941
18156
|
if field_id is None or field_id < 0:
|
|
18942
18157
|
raise_tool_error(
|
|
18943
18158
|
QingflowApiError.config_error(
|
|
18944
|
-
"query_fields must be a list of field_id integers
|
|
18159
|
+
"query_fields must be a list of field_id integers or {field_id} objects"
|
|
18945
18160
|
)
|
|
18946
18161
|
)
|
|
18947
18162
|
normalized.append(field_id)
|