@josephyan/qingflow-cli 0.2.0-beta.1011 → 0.2.0-beta.1013
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/cli/commands/exports.py +16 -0
- package/src/qingflow_mcp/server.py +5 -0
- package/src/qingflow_mcp/server_app_user.py +13 -0
- package/src/qingflow_mcp/tools/export_tools.py +187 -6
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-cli@0.2.0-beta.
|
|
6
|
+
npm install @josephyan/qingflow-cli@0.2.0-beta.1013
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-cli@0.2.0-beta.
|
|
12
|
+
npx -y -p @josephyan/qingflow-cli@0.2.0-beta.1013 qingflow
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@josephyan/qingflow-cli",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.1013",
|
|
4
4
|
"description": "Human-friendly Qingflow command line interface for auth, record operations, import, tasks, and stable builder flows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/pyproject.toml
CHANGED
|
@@ -15,6 +15,8 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
15
15
|
start.add_argument("--view-id", default="system:all")
|
|
16
16
|
start.add_argument("--column", dest="columns", action="append", type=int, default=[], help="只导出这些 field_id;不传时导出当前视图全部字段")
|
|
17
17
|
start.add_argument("--columns-file", help="JSON/YAML list,内容与 --column 语义一致")
|
|
18
|
+
start.add_argument("--where-file", help="JSON/YAML list,内容与 record list 的 where DSL 一致;内部先查命中 record_id 再走原生导出")
|
|
19
|
+
start.add_argument("--order-by-file", help="JSON/YAML list,内容与 record list 的 order_by DSL 一致;内部查询和导出记录顺序保持一致")
|
|
18
20
|
start.add_argument("--record-id", dest="record_ids", action="append", default=[], help="只导出这些 record_id;不传时导出当前视图全部数据")
|
|
19
21
|
start.add_argument("--record-ids-file", help="JSON/YAML list,内容与 --record-id 语义一致")
|
|
20
22
|
start.add_argument("--include-workflow-log", action=argparse.BooleanOptionalAction, default=False, help="是否同时导出流程日志")
|
|
@@ -34,6 +36,8 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
34
36
|
direct.add_argument("--view-id", default="system:all")
|
|
35
37
|
direct.add_argument("--column", dest="columns", action="append", type=int, default=[], help="只导出这些 field_id;不传时导出当前视图全部字段")
|
|
36
38
|
direct.add_argument("--columns-file", help="JSON/YAML list,内容与 --column 语义一致")
|
|
39
|
+
direct.add_argument("--where-file", help="JSON/YAML list,内容与 record list 的 where DSL 一致;内部先查命中 record_id 再走原生导出")
|
|
40
|
+
direct.add_argument("--order-by-file", help="JSON/YAML list,内容与 record list 的 order_by DSL 一致;内部查询和导出记录顺序保持一致")
|
|
37
41
|
direct.add_argument("--record-id", dest="record_ids", action="append", default=[], help="只导出这些 record_id;不传时导出当前视图全部数据")
|
|
38
42
|
direct.add_argument("--record-ids-file", help="JSON/YAML list,内容与 --record-id 语义一致")
|
|
39
43
|
direct.add_argument("--include-workflow-log", action=argparse.BooleanOptionalAction, default=False, help="是否同时导出流程日志")
|
|
@@ -56,12 +60,22 @@ def _record_ids(args: argparse.Namespace) -> list[str | int]:
|
|
|
56
60
|
return record_ids
|
|
57
61
|
|
|
58
62
|
|
|
63
|
+
def _where(args: argparse.Namespace) -> list[dict]:
|
|
64
|
+
return load_list_arg(args.where_file, option_name="--where-file") if args.where_file else []
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _order_by(args: argparse.Namespace) -> list[dict]:
|
|
68
|
+
return load_list_arg(args.order_by_file, option_name="--order-by-file") if args.order_by_file else []
|
|
69
|
+
|
|
70
|
+
|
|
59
71
|
def _handle_start(args: argparse.Namespace, context: CliContext) -> dict:
|
|
60
72
|
return context.exports.record_export_start(
|
|
61
73
|
profile=args.profile,
|
|
62
74
|
app_key=args.app_key,
|
|
63
75
|
view_id=args.view_id,
|
|
64
76
|
columns=_columns(args),
|
|
77
|
+
where=_where(args),
|
|
78
|
+
order_by=_order_by(args),
|
|
65
79
|
record_ids=_record_ids(args),
|
|
66
80
|
include_workflow_log=args.include_workflow_log,
|
|
67
81
|
)
|
|
@@ -88,6 +102,8 @@ def _handle_direct(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
88
102
|
app_key=args.app_key,
|
|
89
103
|
view_id=args.view_id,
|
|
90
104
|
columns=_columns(args),
|
|
105
|
+
where=_where(args),
|
|
106
|
+
order_by=_order_by(args),
|
|
91
107
|
record_ids=_record_ids(args),
|
|
92
108
|
include_workflow_log=args.include_workflow_log,
|
|
93
109
|
download_to_path=args.download_to_path,
|
|
@@ -158,6 +158,11 @@ Use `record_code_block_run` when the user wants to execute a form code-block fie
|
|
|
158
158
|
- `record_export_start` / `record_export_direct` support frontend-like row selection:
|
|
159
159
|
- omit `record_ids` to export all rows in the selected view
|
|
160
160
|
- pass `record_ids` to export selected rows only
|
|
161
|
+
- `record_export_start` / `record_export_direct` also support internal query selection:
|
|
162
|
+
- pass `where` to resolve matching `record_id` values first
|
|
163
|
+
- pass `order_by` to keep the internal query and export row order aligned with `record_list`
|
|
164
|
+
- then run native export as selected rows
|
|
165
|
+
- `where/order_by` and `record_ids` are mutually exclusive
|
|
161
166
|
- `record_export_start` / `record_export_direct` also support frontend-like column selection:
|
|
162
167
|
- omit `columns` to export all current-view fields
|
|
163
168
|
- pass `columns` to export only selected fields, preserving the provided order
|
|
@@ -153,6 +153,11 @@ Use `record_code_block_run` when the user wants to execute a form code-block fie
|
|
|
153
153
|
- `record_export_start` / `record_export_direct` support frontend-like row selection:
|
|
154
154
|
- omit `record_ids` to export all rows in the selected view
|
|
155
155
|
- pass `record_ids` to export selected rows only
|
|
156
|
+
- `record_export_start` / `record_export_direct` also support internal query selection:
|
|
157
|
+
- pass `where` to resolve matching `record_id` values first
|
|
158
|
+
- pass `order_by` to keep the internal query and export row order aligned with `record_list`
|
|
159
|
+
- then run native export as selected rows
|
|
160
|
+
- `where/order_by` and `record_ids` are mutually exclusive
|
|
156
161
|
- `record_export_start` / `record_export_direct` also support frontend-like column selection:
|
|
157
162
|
- omit `columns` to export all current-view fields
|
|
158
163
|
- pass `columns` to export only selected fields, preserving the provided order
|
|
@@ -241,6 +246,8 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
241
246
|
app_key: str = "",
|
|
242
247
|
view_id: str = "system:all",
|
|
243
248
|
columns: list[dict | int] | None = None,
|
|
249
|
+
where: list[dict] | None = None,
|
|
250
|
+
order_by: list[dict] | None = None,
|
|
244
251
|
record_ids: list[str | int] | None = None,
|
|
245
252
|
include_workflow_log: bool = False,
|
|
246
253
|
) -> dict:
|
|
@@ -249,6 +256,8 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
249
256
|
app_key=app_key,
|
|
250
257
|
view_id=view_id,
|
|
251
258
|
columns=columns or [],
|
|
259
|
+
where=where or [],
|
|
260
|
+
order_by=order_by or [],
|
|
252
261
|
record_ids=record_ids or [],
|
|
253
262
|
include_workflow_log=include_workflow_log,
|
|
254
263
|
)
|
|
@@ -278,6 +287,8 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
278
287
|
app_key: str = "",
|
|
279
288
|
view_id: str = "system:all",
|
|
280
289
|
columns: list[dict | int] | None = None,
|
|
290
|
+
where: list[dict] | None = None,
|
|
291
|
+
order_by: list[dict] | None = None,
|
|
281
292
|
record_ids: list[str | int] | None = None,
|
|
282
293
|
include_workflow_log: bool = False,
|
|
283
294
|
download_to_path: str | None = None,
|
|
@@ -288,6 +299,8 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
288
299
|
app_key=app_key,
|
|
289
300
|
view_id=view_id,
|
|
290
301
|
columns=columns or [],
|
|
302
|
+
where=where or [],
|
|
303
|
+
order_by=order_by or [],
|
|
291
304
|
record_ids=record_ids or [],
|
|
292
305
|
include_workflow_log=include_workflow_log,
|
|
293
306
|
download_to_path=download_to_path,
|
|
@@ -18,6 +18,7 @@ from ..json_types import JSONObject
|
|
|
18
18
|
from .base import ToolBase, tool_cn_name
|
|
19
19
|
from .record_tools import (
|
|
20
20
|
AccessibleViewRoute,
|
|
21
|
+
DEFAULT_LIST_PAGE_SIZE,
|
|
21
22
|
LAYOUT_ONLY_QUE_TYPES,
|
|
22
23
|
RecordTools,
|
|
23
24
|
_normalize_public_column_selectors,
|
|
@@ -58,6 +59,8 @@ class ExportTools(ToolBase):
|
|
|
58
59
|
app_key: str = "",
|
|
59
60
|
view_id: str = "system:all",
|
|
60
61
|
columns: list[JSONObject | int] | None = None,
|
|
62
|
+
where: list[JSONObject] | None = None,
|
|
63
|
+
order_by: list[JSONObject] | None = None,
|
|
61
64
|
record_ids: list[str | int] | None = None,
|
|
62
65
|
include_workflow_log: bool = False,
|
|
63
66
|
) -> dict[str, Any]:
|
|
@@ -66,6 +69,8 @@ class ExportTools(ToolBase):
|
|
|
66
69
|
app_key=app_key,
|
|
67
70
|
view_id=view_id,
|
|
68
71
|
columns=columns or [],
|
|
72
|
+
where=where or [],
|
|
73
|
+
order_by=order_by or [],
|
|
69
74
|
record_ids=record_ids or [],
|
|
70
75
|
include_workflow_log=include_workflow_log,
|
|
71
76
|
)
|
|
@@ -98,6 +103,8 @@ class ExportTools(ToolBase):
|
|
|
98
103
|
app_key: str = "",
|
|
99
104
|
view_id: str = "system:all",
|
|
100
105
|
columns: list[JSONObject | int] | None = None,
|
|
106
|
+
where: list[JSONObject] | None = None,
|
|
107
|
+
order_by: list[JSONObject] | None = None,
|
|
101
108
|
record_ids: list[str | int] | None = None,
|
|
102
109
|
include_workflow_log: bool = False,
|
|
103
110
|
download_to_path: str | None = None,
|
|
@@ -108,6 +115,8 @@ class ExportTools(ToolBase):
|
|
|
108
115
|
app_key=app_key,
|
|
109
116
|
view_id=view_id,
|
|
110
117
|
columns=columns or [],
|
|
118
|
+
where=where or [],
|
|
119
|
+
order_by=order_by or [],
|
|
111
120
|
record_ids=record_ids or [],
|
|
112
121
|
include_workflow_log=include_workflow_log,
|
|
113
122
|
download_to_path=download_to_path,
|
|
@@ -122,12 +131,16 @@ class ExportTools(ToolBase):
|
|
|
122
131
|
app_key: str,
|
|
123
132
|
view_id: str = "system:all",
|
|
124
133
|
columns: list[JSONObject | int] | None = None,
|
|
134
|
+
where: list[JSONObject] | None = None,
|
|
135
|
+
order_by: list[JSONObject] | None = None,
|
|
125
136
|
record_ids: list[str | int] | None = None,
|
|
126
137
|
include_workflow_log: bool = False,
|
|
127
138
|
) -> dict[str, Any]:
|
|
128
139
|
normalized_app_key = str(app_key or "").strip()
|
|
129
140
|
normalized_view_id = str(view_id or "").strip() or "system:all"
|
|
130
141
|
normalized_columns = _normalize_export_columns(columns or [])
|
|
142
|
+
normalized_where = self._record_tools._normalize_record_list_where(where or [])
|
|
143
|
+
normalized_order_by = self._record_tools._normalize_record_list_order_by(order_by or [])
|
|
131
144
|
normalized_record_ids = _normalize_export_record_ids(record_ids or [])
|
|
132
145
|
if not normalized_app_key:
|
|
133
146
|
return self._failed_export_result(
|
|
@@ -154,11 +167,21 @@ class ExportTools(ToolBase):
|
|
|
154
167
|
resolved_view=resolved_view,
|
|
155
168
|
column_selectors=normalized_columns,
|
|
156
169
|
)
|
|
170
|
+
effective_record_ids, row_scope = self._resolve_export_record_scope(
|
|
171
|
+
profile=profile,
|
|
172
|
+
context=context,
|
|
173
|
+
app_key=normalized_app_key,
|
|
174
|
+
resolved_view=resolved_view,
|
|
175
|
+
selected_record_ids=normalized_record_ids,
|
|
176
|
+
where_filters=normalized_where,
|
|
177
|
+
order_by=normalized_order_by,
|
|
178
|
+
select_columns=cast(list[JSONObject], export_config.get("questionExportConfigList") or []),
|
|
179
|
+
)
|
|
157
180
|
result_amount = self._estimate_export_result_amount(
|
|
158
181
|
context,
|
|
159
182
|
app_key=normalized_app_key,
|
|
160
183
|
resolved_view=resolved_view,
|
|
161
|
-
selected_record_ids=
|
|
184
|
+
selected_record_ids=effective_record_ids,
|
|
162
185
|
)
|
|
163
186
|
self._validate_export_limits(
|
|
164
187
|
result_amount=result_amount,
|
|
@@ -167,7 +190,7 @@ class ExportTools(ToolBase):
|
|
|
167
190
|
)
|
|
168
191
|
filter_bean = self._build_export_filter_bean(
|
|
169
192
|
resolved_view,
|
|
170
|
-
selected_record_ids=
|
|
193
|
+
selected_record_ids=effective_record_ids,
|
|
171
194
|
include_workflow_log=include_workflow_log,
|
|
172
195
|
)
|
|
173
196
|
started_at = _utc_now().replace(microsecond=0).isoformat()
|
|
@@ -180,7 +203,6 @@ class ExportTools(ToolBase):
|
|
|
180
203
|
export_config=export_config,
|
|
181
204
|
result_amount=result_amount,
|
|
182
205
|
)
|
|
183
|
-
row_scope = "selected" if normalized_record_ids else "all"
|
|
184
206
|
column_scope = "selected" if normalized_columns else "all"
|
|
185
207
|
export_handle = uuid4().hex
|
|
186
208
|
self._job_store.put(
|
|
@@ -194,7 +216,7 @@ class ExportTools(ToolBase):
|
|
|
194
216
|
"started_at": started_at,
|
|
195
217
|
"uid": session_profile.uid,
|
|
196
218
|
"row_scope": row_scope,
|
|
197
|
-
"selected_record_count": len(
|
|
219
|
+
"selected_record_count": len(effective_record_ids),
|
|
198
220
|
"field_scope": column_scope,
|
|
199
221
|
"selected_field_count": len(cast(list[JSONObject], export_config.get("questionExportConfigList") or [])),
|
|
200
222
|
"include_workflow_log": bool(include_workflow_log),
|
|
@@ -212,7 +234,7 @@ class ExportTools(ToolBase):
|
|
|
212
234
|
"view_id": resolved_view.view_id,
|
|
213
235
|
"export_handle": export_handle,
|
|
214
236
|
"row_scope": row_scope,
|
|
215
|
-
"selected_record_count": len(
|
|
237
|
+
"selected_record_count": len(effective_record_ids),
|
|
216
238
|
"field_scope": column_scope,
|
|
217
239
|
"selected_field_count": len(cast(list[JSONObject], export_config.get("questionExportConfigList") or [])),
|
|
218
240
|
"include_workflow_log": bool(include_workflow_log),
|
|
@@ -223,7 +245,9 @@ class ExportTools(ToolBase):
|
|
|
223
245
|
"verification": {
|
|
224
246
|
"export_acknowledged": bool(socket_result.get("backend_export_id")),
|
|
225
247
|
"view_route_supported": True,
|
|
226
|
-
"row_selection_applied": bool(
|
|
248
|
+
"row_selection_applied": bool(effective_record_ids),
|
|
249
|
+
"query_filter_applied": bool(normalized_where),
|
|
250
|
+
"query_sort_applied": bool(normalized_order_by),
|
|
227
251
|
"field_selection_applied": bool(normalized_columns),
|
|
228
252
|
},
|
|
229
253
|
"request_route": self.backend.describe_route(context),
|
|
@@ -407,6 +431,8 @@ class ExportTools(ToolBase):
|
|
|
407
431
|
app_key: str,
|
|
408
432
|
view_id: str = "system:all",
|
|
409
433
|
columns: list[JSONObject | int] | None = None,
|
|
434
|
+
where: list[JSONObject] | None = None,
|
|
435
|
+
order_by: list[JSONObject] | None = None,
|
|
410
436
|
record_ids: list[str | int] | None = None,
|
|
411
437
|
include_workflow_log: bool = False,
|
|
412
438
|
download_to_path: str | None = None,
|
|
@@ -428,6 +454,8 @@ class ExportTools(ToolBase):
|
|
|
428
454
|
app_key=normalized_app_key,
|
|
429
455
|
view_id=normalized_view_id,
|
|
430
456
|
columns=columns or [],
|
|
457
|
+
where=where or [],
|
|
458
|
+
order_by=order_by or [],
|
|
431
459
|
record_ids=record_ids or [],
|
|
432
460
|
include_workflow_log=include_workflow_log,
|
|
433
461
|
)
|
|
@@ -552,6 +580,152 @@ class ExportTools(ToolBase):
|
|
|
552
580
|
extra={"app_key": normalized_app_key, "view_id": normalized_view_id},
|
|
553
581
|
)
|
|
554
582
|
|
|
583
|
+
def _resolve_export_record_scope(
|
|
584
|
+
self,
|
|
585
|
+
*,
|
|
586
|
+
profile: str,
|
|
587
|
+
context,
|
|
588
|
+
app_key: str,
|
|
589
|
+
resolved_view: AccessibleViewRoute,
|
|
590
|
+
selected_record_ids: list[int],
|
|
591
|
+
where_filters: list[JSONObject],
|
|
592
|
+
order_by: list[JSONObject],
|
|
593
|
+
select_columns: list[JSONObject],
|
|
594
|
+
) -> tuple[list[int], str]:
|
|
595
|
+
if selected_record_ids and (where_filters or order_by):
|
|
596
|
+
raise QingflowApiError(
|
|
597
|
+
category="config",
|
|
598
|
+
message="record export does not allow record_ids together with query selectors",
|
|
599
|
+
details={
|
|
600
|
+
"error_code": "EXPORT_ROW_SCOPE_CONFLICT",
|
|
601
|
+
"fix_hint": "Use record_ids for explicit selected rows, or use where/order_by for internal query selection, but not both.",
|
|
602
|
+
},
|
|
603
|
+
)
|
|
604
|
+
if selected_record_ids:
|
|
605
|
+
return selected_record_ids, "selected"
|
|
606
|
+
if not where_filters and not order_by:
|
|
607
|
+
return [], "all"
|
|
608
|
+
resolved_ids = self._collect_record_ids_from_query(
|
|
609
|
+
profile=profile,
|
|
610
|
+
context=context,
|
|
611
|
+
app_key=app_key,
|
|
612
|
+
resolved_view=resolved_view,
|
|
613
|
+
where_filters=where_filters,
|
|
614
|
+
order_by=order_by,
|
|
615
|
+
select_columns=select_columns,
|
|
616
|
+
)
|
|
617
|
+
if not resolved_ids:
|
|
618
|
+
raise QingflowApiError.config_error(
|
|
619
|
+
"record export query did not match any records",
|
|
620
|
+
details={
|
|
621
|
+
"error_code": "EXPORT_NO_MATCHED_RECORDS",
|
|
622
|
+
"view_id": resolved_view.view_id,
|
|
623
|
+
},
|
|
624
|
+
)
|
|
625
|
+
return resolved_ids, "queried"
|
|
626
|
+
|
|
627
|
+
def _collect_record_ids_from_query(
|
|
628
|
+
self,
|
|
629
|
+
*,
|
|
630
|
+
profile: str,
|
|
631
|
+
context,
|
|
632
|
+
app_key: str,
|
|
633
|
+
resolved_view: AccessibleViewRoute,
|
|
634
|
+
where_filters: list[JSONObject],
|
|
635
|
+
order_by: list[JSONObject],
|
|
636
|
+
select_columns: list[JSONObject],
|
|
637
|
+
) -> list[int]:
|
|
638
|
+
browse_scope = self._record_tools._build_browse_write_scope(
|
|
639
|
+
profile,
|
|
640
|
+
context,
|
|
641
|
+
app_key,
|
|
642
|
+
resolved_view,
|
|
643
|
+
force_refresh=False,
|
|
644
|
+
)
|
|
645
|
+
index = browse_scope["index"]
|
|
646
|
+
match_rules = self._record_tools._resolve_match_rules(context, where_filters, index)
|
|
647
|
+
query_sorts = [
|
|
648
|
+
{
|
|
649
|
+
"queId": _coerce_int(item.get("field_id")),
|
|
650
|
+
"direction": str(item.get("direction") or "asc"),
|
|
651
|
+
}
|
|
652
|
+
for item in order_by
|
|
653
|
+
if isinstance(item, dict) and _coerce_int(item.get("field_id")) is not None
|
|
654
|
+
]
|
|
655
|
+
dept_member_cache: dict[int, set[int]] = {}
|
|
656
|
+
current_page = 1
|
|
657
|
+
selected_ids: list[int] = []
|
|
658
|
+
seen: set[int] = set()
|
|
659
|
+
primary_field_ids = [
|
|
660
|
+
_coerce_int(item.get("queId"))
|
|
661
|
+
for item in select_columns
|
|
662
|
+
if isinstance(item, dict)
|
|
663
|
+
]
|
|
664
|
+
primary_search_que_ids = [item for item in primary_field_ids if item is not None][:1] or None
|
|
665
|
+
|
|
666
|
+
while True:
|
|
667
|
+
page = self._record_tools._search_page(
|
|
668
|
+
context,
|
|
669
|
+
app_key=app_key,
|
|
670
|
+
view_selection=resolved_view.view_selection,
|
|
671
|
+
page_num=current_page,
|
|
672
|
+
page_size=DEFAULT_LIST_PAGE_SIZE,
|
|
673
|
+
query_key=None,
|
|
674
|
+
match_rules=match_rules,
|
|
675
|
+
sorts=cast(list[JSONObject], query_sorts),
|
|
676
|
+
search_que_ids=primary_search_que_ids,
|
|
677
|
+
list_type=resolved_view.list_type if resolved_view.list_type is not None else DEFAULT_RECORD_LIST_TYPE,
|
|
678
|
+
)
|
|
679
|
+
raw_rows = page.get("list")
|
|
680
|
+
items = raw_rows if isinstance(raw_rows, list) else []
|
|
681
|
+
for item in items:
|
|
682
|
+
if not isinstance(item, dict):
|
|
683
|
+
continue
|
|
684
|
+
answers = item.get("answers")
|
|
685
|
+
answer_list = answers if isinstance(answers, list) else []
|
|
686
|
+
if not self._record_tools._matches_view_selection(
|
|
687
|
+
context,
|
|
688
|
+
answer_list,
|
|
689
|
+
view_selection=resolved_view.view_selection,
|
|
690
|
+
dept_member_cache=dept_member_cache,
|
|
691
|
+
):
|
|
692
|
+
continue
|
|
693
|
+
record_id = _coerce_int(item.get("applyId"))
|
|
694
|
+
if record_id is None:
|
|
695
|
+
record_id = _coerce_int(item.get("apply_id"))
|
|
696
|
+
if record_id is None:
|
|
697
|
+
record_id = _coerce_int(item.get("id"))
|
|
698
|
+
if record_id is None:
|
|
699
|
+
record_id = _coerce_int(item.get("record_id"))
|
|
700
|
+
if record_id is None or record_id in seen or record_id <= 0:
|
|
701
|
+
continue
|
|
702
|
+
seen.add(record_id)
|
|
703
|
+
selected_ids.append(record_id)
|
|
704
|
+
if len(selected_ids) > EXPORT_ROWS_LIMIT:
|
|
705
|
+
raise QingflowApiError.config_error(
|
|
706
|
+
f"record export exceeds the native row limit of {EXPORT_ROWS_LIMIT}",
|
|
707
|
+
details={
|
|
708
|
+
"error_code": "EXPORT_ROWS_LIMIT_EXCEEDED",
|
|
709
|
+
"result_amount": len(selected_ids),
|
|
710
|
+
"row_limit": EXPORT_ROWS_LIMIT,
|
|
711
|
+
},
|
|
712
|
+
)
|
|
713
|
+
if current_page == 1:
|
|
714
|
+
reported_total = _effective_total(page, page_size=DEFAULT_LIST_PAGE_SIZE)
|
|
715
|
+
if reported_total > EXPORT_ROWS_LIMIT:
|
|
716
|
+
raise QingflowApiError.config_error(
|
|
717
|
+
f"record export exceeds the native row limit of {EXPORT_ROWS_LIMIT}",
|
|
718
|
+
details={
|
|
719
|
+
"error_code": "EXPORT_ROWS_LIMIT_EXCEEDED",
|
|
720
|
+
"result_amount": reported_total,
|
|
721
|
+
"row_limit": EXPORT_ROWS_LIMIT,
|
|
722
|
+
},
|
|
723
|
+
)
|
|
724
|
+
if not _page_has_more(page, current_page=current_page, page_size=DEFAULT_LIST_PAGE_SIZE, returned_rows=len(items)):
|
|
725
|
+
break
|
|
726
|
+
current_page += 1
|
|
727
|
+
return selected_ids
|
|
728
|
+
|
|
555
729
|
def _build_export_filter_bean(
|
|
556
730
|
self,
|
|
557
731
|
resolved_view: AccessibleViewRoute,
|
|
@@ -1101,6 +1275,13 @@ def _effective_total(page: JSONObject, *, page_size: int) -> int:
|
|
|
1101
1275
|
return returned_rows
|
|
1102
1276
|
|
|
1103
1277
|
|
|
1278
|
+
def _page_has_more(page: JSONObject, *, current_page: int, page_size: int, returned_rows: int) -> bool:
|
|
1279
|
+
page_amount = _coerce_int(page.get("pageAmount"))
|
|
1280
|
+
if page_amount is not None:
|
|
1281
|
+
return current_page < page_amount
|
|
1282
|
+
return returned_rows >= page_size
|
|
1283
|
+
|
|
1284
|
+
|
|
1104
1285
|
def _resolve_download_targets(
|
|
1105
1286
|
destination_hint: str,
|
|
1106
1287
|
*,
|