@josephyan/qingflow-app-builder-mcp 0.2.0-beta.7 → 0.2.0-beta.71
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 +5 -3
- package/docs/local-agent-install.md +21 -5
- package/npm/bin/qingflow-app-builder-mcp.mjs +1 -1
- package/npm/lib/runtime.mjs +168 -12
- package/package.json +1 -1
- package/pyproject.toml +4 -1
- package/skills/qingflow-app-builder/SKILL.md +155 -22
- package/skills/qingflow-app-builder/references/create-app.md +51 -21
- package/skills/qingflow-app-builder/references/environments.md +1 -1
- package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +123 -0
- package/skills/qingflow-app-builder/references/gotchas.md +28 -1
- package/skills/qingflow-app-builder/references/solution-playbooks.md +14 -12
- package/skills/qingflow-app-builder/references/tool-selection.md +47 -19
- package/skills/qingflow-app-builder/references/update-flow.md +112 -25
- package/skills/qingflow-app-builder/references/update-layout.md +11 -24
- package/skills/qingflow-app-builder/references/update-schema.md +1 -23
- package/skills/qingflow-app-builder/references/update-views.md +87 -21
- 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/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/backend_client.py +210 -0
- package/src/qingflow_mcp/builder_facade/models.py +1252 -3
- package/src/qingflow_mcp/builder_facade/service.py +11367 -2389
- package/src/qingflow_mcp/cli/__init__.py +1 -0
- package/src/qingflow_mcp/cli/commands/__init__.py +15 -0
- package/src/qingflow_mcp/cli/commands/app.py +40 -0
- package/src/qingflow_mcp/cli/commands/auth.py +78 -0
- package/src/qingflow_mcp/cli/commands/builder.py +515 -0
- package/src/qingflow_mcp/cli/commands/common.py +62 -0
- package/src/qingflow_mcp/cli/commands/imports.py +96 -0
- package/src/qingflow_mcp/cli/commands/record.py +304 -0
- package/src/qingflow_mcp/cli/commands/task.py +89 -0
- package/src/qingflow_mcp/cli/commands/workspace.py +33 -0
- package/src/qingflow_mcp/cli/context.py +48 -0
- package/src/qingflow_mcp/cli/formatters.py +355 -0
- package/src/qingflow_mcp/cli/json_io.py +50 -0
- package/src/qingflow_mcp/cli/main.py +149 -0
- package/src/qingflow_mcp/config.py +39 -0
- package/src/qingflow_mcp/import_store.py +121 -0
- package/src/qingflow_mcp/list_type_labels.py +24 -0
- package/src/qingflow_mcp/response_trim.py +668 -0
- package/src/qingflow_mcp/server.py +160 -18
- package/src/qingflow_mcp/server_app_builder.py +275 -68
- package/src/qingflow_mcp/server_app_user.py +219 -191
- package/src/qingflow_mcp/session_store.py +41 -1
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +43 -4
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +119 -45
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +41 -2
- package/src/qingflow_mcp/solution/executor.py +107 -11
- package/src/qingflow_mcp/solution/spec_models.py +2 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2032 -127
- package/src/qingflow_mcp/tools/app_tools.py +419 -12
- package/src/qingflow_mcp/tools/approval_tools.py +571 -72
- package/src/qingflow_mcp/tools/auth_tools.py +398 -2
- package/src/qingflow_mcp/tools/code_block_tools.py +756 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +179 -0
- package/src/qingflow_mcp/tools/directory_tools.py +203 -31
- package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
- package/src/qingflow_mcp/tools/file_tools.py +1 -0
- package/src/qingflow_mcp/tools/import_tools.py +2150 -0
- package/src/qingflow_mcp/tools/package_tools.py +18 -4
- package/src/qingflow_mcp/tools/portal_tools.py +31 -0
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +109 -7
- package/src/qingflow_mcp/tools/record_tools.py +9894 -1104
- package/src/qingflow_mcp/tools/solution_tools.py +115 -3
- package/src/qingflow_mcp/tools/task_context_tools.py +2040 -0
- package/src/qingflow_mcp/tools/task_tools.py +376 -225
- package/src/qingflow_mcp/tools/workspace_tools.py +163 -19
|
@@ -18,14 +18,19 @@ class ApprovalTools(ToolBase):
|
|
|
18
18
|
|
|
19
19
|
def register(self, mcp: FastMCP) -> None:
|
|
20
20
|
@mcp.tool()
|
|
21
|
-
def
|
|
22
|
-
|
|
21
|
+
def record_comment_write(
|
|
22
|
+
profile: str = DEFAULT_PROFILE,
|
|
23
|
+
app_key: str = "",
|
|
24
|
+
record_id: int = 0,
|
|
25
|
+
payload: dict[str, Any] | None = None,
|
|
26
|
+
) -> dict[str, Any]:
|
|
27
|
+
return self.record_comment_write(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {})
|
|
23
28
|
|
|
24
29
|
@mcp.tool()
|
|
25
30
|
def record_comment_list(
|
|
26
31
|
profile: str = DEFAULT_PROFILE,
|
|
27
32
|
app_key: str = "",
|
|
28
|
-
|
|
33
|
+
record_id: int = 0,
|
|
29
34
|
page_size: int = 20,
|
|
30
35
|
list_type: int | None = None,
|
|
31
36
|
page_num: int | None = 1,
|
|
@@ -33,26 +38,26 @@ class ApprovalTools(ToolBase):
|
|
|
33
38
|
return self.record_comment_list(
|
|
34
39
|
profile=profile,
|
|
35
40
|
app_key=app_key,
|
|
36
|
-
apply_id=
|
|
41
|
+
apply_id=record_id,
|
|
37
42
|
page_size=page_size,
|
|
38
43
|
list_type=list_type,
|
|
39
44
|
page_num=page_num,
|
|
40
45
|
)
|
|
41
46
|
|
|
42
47
|
@mcp.tool()
|
|
43
|
-
def
|
|
48
|
+
def record_comment_mentions(
|
|
44
49
|
profile: str = DEFAULT_PROFILE,
|
|
45
50
|
app_key: str = "",
|
|
46
|
-
|
|
51
|
+
record_id: int = 0,
|
|
47
52
|
page_size: int = 20,
|
|
48
53
|
page_num: int = 1,
|
|
49
54
|
list_type: int | None = None,
|
|
50
55
|
keyword: str | None = None,
|
|
51
56
|
) -> dict[str, Any]:
|
|
52
|
-
return self.
|
|
57
|
+
return self.record_comment_mentions(
|
|
53
58
|
profile=profile,
|
|
54
59
|
app_key=app_key,
|
|
55
|
-
|
|
60
|
+
record_id=record_id,
|
|
56
61
|
page_size=page_size,
|
|
57
62
|
page_num=page_num,
|
|
58
63
|
list_type=list_type,
|
|
@@ -60,84 +65,283 @@ class ApprovalTools(ToolBase):
|
|
|
60
65
|
)
|
|
61
66
|
|
|
62
67
|
@mcp.tool()
|
|
63
|
-
def record_comment_mark_read(profile: str = DEFAULT_PROFILE, app_key: str = "",
|
|
64
|
-
return self.record_comment_mark_read(profile=profile, app_key=app_key, apply_id=
|
|
68
|
+
def record_comment_mark_read(profile: str = DEFAULT_PROFILE, app_key: str = "", record_id: int = 0) -> dict[str, Any]:
|
|
69
|
+
return self.record_comment_mark_read(profile=profile, app_key=app_key, apply_id=record_id)
|
|
65
70
|
|
|
66
|
-
@mcp.tool()
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
@mcp.tool(description=self._high_risk_tool_description(operation="approve", target="workflow task"))
|
|
72
|
+
def task_approve(
|
|
73
|
+
profile: str = DEFAULT_PROFILE,
|
|
74
|
+
app_key: str = "",
|
|
75
|
+
record_id: int = 0,
|
|
76
|
+
payload: dict[str, Any] | None = None,
|
|
77
|
+
fields: dict[str, Any] | None = None,
|
|
78
|
+
) -> dict[str, Any]:
|
|
79
|
+
return self.task_approve(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})
|
|
73
80
|
|
|
74
|
-
@mcp.tool(description=self._high_risk_tool_description(operation="reject", target="workflow
|
|
75
|
-
def
|
|
76
|
-
|
|
81
|
+
@mcp.tool(description=self._high_risk_tool_description(operation="reject", target="workflow task"))
|
|
82
|
+
def task_reject(
|
|
83
|
+
profile: str = DEFAULT_PROFILE,
|
|
84
|
+
app_key: str = "",
|
|
85
|
+
record_id: int = 0,
|
|
86
|
+
payload: dict[str, Any] | None = None,
|
|
87
|
+
fields: dict[str, Any] | None = None,
|
|
88
|
+
) -> dict[str, Any]:
|
|
89
|
+
return self.task_reject(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})
|
|
77
90
|
|
|
78
91
|
@mcp.tool()
|
|
79
|
-
def
|
|
80
|
-
|
|
92
|
+
def task_rollback_candidates(
|
|
93
|
+
profile: str = DEFAULT_PROFILE,
|
|
94
|
+
app_key: str = "",
|
|
95
|
+
record_id: int = 0,
|
|
96
|
+
workflow_node_id: int = 0,
|
|
97
|
+
) -> dict[str, Any]:
|
|
98
|
+
return self.task_rollback_candidates(profile=profile, app_key=app_key, record_id=record_id, workflow_node_id=workflow_node_id)
|
|
81
99
|
|
|
82
100
|
@mcp.tool()
|
|
83
|
-
def
|
|
84
|
-
|
|
101
|
+
def task_rollback(
|
|
102
|
+
profile: str = DEFAULT_PROFILE,
|
|
103
|
+
app_key: str = "",
|
|
104
|
+
record_id: int = 0,
|
|
105
|
+
payload: dict[str, Any] | None = None,
|
|
106
|
+
fields: dict[str, Any] | None = None,
|
|
107
|
+
) -> dict[str, Any]:
|
|
108
|
+
return self.task_rollback(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})
|
|
85
109
|
|
|
86
110
|
@mcp.tool()
|
|
87
|
-
def
|
|
88
|
-
|
|
111
|
+
def task_transfer(
|
|
112
|
+
profile: str = DEFAULT_PROFILE,
|
|
113
|
+
app_key: str = "",
|
|
114
|
+
record_id: int = 0,
|
|
115
|
+
payload: dict[str, Any] | None = None,
|
|
116
|
+
fields: dict[str, Any] | None = None,
|
|
117
|
+
) -> dict[str, Any]:
|
|
118
|
+
return self.task_transfer(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})
|
|
89
119
|
|
|
90
|
-
@mcp.tool()
|
|
91
|
-
def
|
|
120
|
+
@mcp.tool(description=self._high_risk_tool_description(operation="save", target="workflow task fields without advancing the workflow"))
|
|
121
|
+
def task_save_only(
|
|
92
122
|
profile: str = DEFAULT_PROFILE,
|
|
93
123
|
app_key: str = "",
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
audit_node_id: int = 0,
|
|
98
|
-
keyword: str | None = None,
|
|
124
|
+
record_id: int = 0,
|
|
125
|
+
workflow_node_id: int = 0,
|
|
126
|
+
fields: dict[str, Any] | None = None,
|
|
99
127
|
) -> dict[str, Any]:
|
|
100
|
-
return self.
|
|
128
|
+
return self.task_save_only(
|
|
101
129
|
profile=profile,
|
|
102
130
|
app_key=app_key,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
audit_node_id=audit_node_id,
|
|
107
|
-
keyword=keyword,
|
|
131
|
+
record_id=record_id,
|
|
132
|
+
workflow_node_id=workflow_node_id,
|
|
133
|
+
fields=fields or {},
|
|
108
134
|
)
|
|
109
135
|
|
|
110
136
|
@mcp.tool()
|
|
111
|
-
def
|
|
112
|
-
return self.record_reassign_get(profile=profile, app_key=app_key, apply_id=apply_id)
|
|
113
|
-
|
|
114
|
-
@mcp.tool()
|
|
115
|
-
def record_reassign(profile: str = DEFAULT_PROFILE, app_key: str = "", apply_id: int = 0, payload: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
116
|
-
return self.record_reassign(profile=profile, app_key=app_key, apply_id=apply_id, payload=payload or {})
|
|
117
|
-
|
|
118
|
-
@mcp.tool()
|
|
119
|
-
def record_countersign_candidates(
|
|
137
|
+
def task_transfer_candidates(
|
|
120
138
|
profile: str = DEFAULT_PROFILE,
|
|
121
139
|
app_key: str = "",
|
|
122
|
-
|
|
140
|
+
record_id: int = 0,
|
|
123
141
|
page_size: int = 20,
|
|
124
142
|
page_num: int = 1,
|
|
125
|
-
|
|
126
|
-
|
|
143
|
+
workflow_node_id: int = 0,
|
|
144
|
+
keyword: str | None = None,
|
|
127
145
|
) -> dict[str, Any]:
|
|
128
|
-
return self.
|
|
146
|
+
return self.task_transfer_candidates(
|
|
129
147
|
profile=profile,
|
|
130
148
|
app_key=app_key,
|
|
131
|
-
|
|
149
|
+
record_id=record_id,
|
|
132
150
|
page_size=page_size,
|
|
133
151
|
page_num=page_num,
|
|
134
|
-
|
|
135
|
-
|
|
152
|
+
workflow_node_id=workflow_node_id,
|
|
153
|
+
keyword=keyword,
|
|
136
154
|
)
|
|
137
155
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
156
|
+
def record_comment_write(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any]) -> dict[str, Any]:
|
|
157
|
+
raw = self.record_comment_add(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
|
|
158
|
+
return self._public_action_response(
|
|
159
|
+
raw,
|
|
160
|
+
action="record_comment_write",
|
|
161
|
+
resource={"app_key": app_key, "record_id": record_id},
|
|
162
|
+
selection={},
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def record_comment_mentions(
|
|
166
|
+
self,
|
|
167
|
+
*,
|
|
168
|
+
profile: str,
|
|
169
|
+
app_key: str,
|
|
170
|
+
record_id: int,
|
|
171
|
+
page_size: int = 20,
|
|
172
|
+
page_num: int = 1,
|
|
173
|
+
list_type: int | None = None,
|
|
174
|
+
keyword: str | None = None,
|
|
175
|
+
) -> dict[str, Any]:
|
|
176
|
+
raw = self.record_comment_mention_candidates(
|
|
177
|
+
profile=profile,
|
|
178
|
+
app_key=app_key,
|
|
179
|
+
apply_id=record_id,
|
|
180
|
+
page_size=page_size,
|
|
181
|
+
page_num=page_num,
|
|
182
|
+
list_type=list_type,
|
|
183
|
+
keyword=keyword,
|
|
184
|
+
)
|
|
185
|
+
items = _approval_page_items(raw.get("page"))
|
|
186
|
+
return self._public_page_response(
|
|
187
|
+
raw,
|
|
188
|
+
items=items,
|
|
189
|
+
pagination={
|
|
190
|
+
"page": page_num,
|
|
191
|
+
"page_size": page_size,
|
|
192
|
+
"returned_items": len(items),
|
|
193
|
+
"page_amount": _approval_page_amount(raw.get("page")),
|
|
194
|
+
"reported_total": _approval_page_total(raw.get("page")),
|
|
195
|
+
},
|
|
196
|
+
selection={"app_key": app_key, "record_id": record_id, "list_type": list_type, "keyword": keyword},
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def task_approve(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
200
|
+
if fields:
|
|
201
|
+
return self._delegate_task_action(
|
|
202
|
+
profile=profile,
|
|
203
|
+
app_key=app_key,
|
|
204
|
+
record_id=record_id,
|
|
205
|
+
action="approve",
|
|
206
|
+
payload=payload,
|
|
207
|
+
fields=fields,
|
|
208
|
+
public_action="task_approve",
|
|
209
|
+
)
|
|
210
|
+
raw = self.record_approve(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
|
|
211
|
+
return self._public_action_response(
|
|
212
|
+
raw,
|
|
213
|
+
action="task_approve",
|
|
214
|
+
resource={"app_key": app_key, "record_id": record_id},
|
|
215
|
+
selection={},
|
|
216
|
+
human_review=True,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def task_reject(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
220
|
+
if fields:
|
|
221
|
+
return self._delegate_task_action(
|
|
222
|
+
profile=profile,
|
|
223
|
+
app_key=app_key,
|
|
224
|
+
record_id=record_id,
|
|
225
|
+
action="reject",
|
|
226
|
+
payload=payload,
|
|
227
|
+
fields=fields,
|
|
228
|
+
public_action="task_reject",
|
|
229
|
+
)
|
|
230
|
+
raw = self.record_reject(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
|
|
231
|
+
return self._public_action_response(
|
|
232
|
+
raw,
|
|
233
|
+
action="task_reject",
|
|
234
|
+
resource={"app_key": app_key, "record_id": record_id},
|
|
235
|
+
selection={},
|
|
236
|
+
human_review=True,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def task_rollback_candidates(self, *, profile: str, app_key: str, record_id: int, workflow_node_id: int) -> dict[str, Any]:
|
|
240
|
+
raw = self.record_rollback_candidates(profile=profile, app_key=app_key, apply_id=record_id, audit_node_id=workflow_node_id)
|
|
241
|
+
items = _approval_page_items(raw.get("result"))
|
|
242
|
+
return self._public_page_response(
|
|
243
|
+
raw,
|
|
244
|
+
items=items,
|
|
245
|
+
pagination={"returned_items": len(items)},
|
|
246
|
+
selection={"app_key": app_key, "record_id": record_id, "workflow_node_id": workflow_node_id},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
def task_rollback(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
250
|
+
if fields:
|
|
251
|
+
return self._delegate_task_action(
|
|
252
|
+
profile=profile,
|
|
253
|
+
app_key=app_key,
|
|
254
|
+
record_id=record_id,
|
|
255
|
+
action="rollback",
|
|
256
|
+
payload=payload,
|
|
257
|
+
fields=fields,
|
|
258
|
+
public_action="task_rollback",
|
|
259
|
+
)
|
|
260
|
+
raw = self.record_rollback(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
|
|
261
|
+
return self._public_action_response(
|
|
262
|
+
raw,
|
|
263
|
+
action="task_rollback",
|
|
264
|
+
resource={"app_key": app_key, "record_id": record_id},
|
|
265
|
+
selection={},
|
|
266
|
+
human_review=True,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def task_transfer(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
270
|
+
if fields:
|
|
271
|
+
return self._delegate_task_action(
|
|
272
|
+
profile=profile,
|
|
273
|
+
app_key=app_key,
|
|
274
|
+
record_id=record_id,
|
|
275
|
+
action="transfer",
|
|
276
|
+
payload=payload,
|
|
277
|
+
fields=fields,
|
|
278
|
+
public_action="task_transfer",
|
|
279
|
+
)
|
|
280
|
+
self._raise_if_self_transfer(profile=profile, payload=payload)
|
|
281
|
+
raw = self.record_transfer(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
|
|
282
|
+
return self._public_action_response(
|
|
283
|
+
raw,
|
|
284
|
+
action="task_transfer",
|
|
285
|
+
resource={"app_key": app_key, "record_id": record_id},
|
|
286
|
+
selection={},
|
|
287
|
+
human_review=True,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
def task_save_only(
|
|
291
|
+
self,
|
|
292
|
+
*,
|
|
293
|
+
profile: str,
|
|
294
|
+
app_key: str,
|
|
295
|
+
record_id: int,
|
|
296
|
+
workflow_node_id: int,
|
|
297
|
+
fields: dict[str, Any],
|
|
298
|
+
) -> dict[str, Any]:
|
|
299
|
+
return self._delegate_task_action(
|
|
300
|
+
profile=profile,
|
|
301
|
+
app_key=app_key,
|
|
302
|
+
record_id=record_id,
|
|
303
|
+
action="save_only",
|
|
304
|
+
payload={},
|
|
305
|
+
fields=fields,
|
|
306
|
+
public_action="task_save_only",
|
|
307
|
+
workflow_node_id=workflow_node_id,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
def task_transfer_candidates(
|
|
311
|
+
self,
|
|
312
|
+
*,
|
|
313
|
+
profile: str,
|
|
314
|
+
app_key: str,
|
|
315
|
+
record_id: int,
|
|
316
|
+
page_size: int = 20,
|
|
317
|
+
page_num: int = 1,
|
|
318
|
+
workflow_node_id: int = 0,
|
|
319
|
+
keyword: str | None = None,
|
|
320
|
+
) -> dict[str, Any]:
|
|
321
|
+
raw = self.record_transfer_candidates(
|
|
322
|
+
profile=profile,
|
|
323
|
+
app_key=app_key,
|
|
324
|
+
apply_id=record_id,
|
|
325
|
+
page_size=page_size,
|
|
326
|
+
page_num=page_num,
|
|
327
|
+
audit_node_id=workflow_node_id,
|
|
328
|
+
keyword=keyword,
|
|
329
|
+
)
|
|
330
|
+
original_items = _approval_page_items(raw.get("page"))
|
|
331
|
+
items = self._filter_self_transfer_candidates(profile=profile, items=original_items)
|
|
332
|
+
filtered_count = max(len(original_items) - len(items), 0)
|
|
333
|
+
return self._public_page_response(
|
|
334
|
+
raw,
|
|
335
|
+
items=items,
|
|
336
|
+
pagination={
|
|
337
|
+
"page": page_num,
|
|
338
|
+
"page_size": page_size,
|
|
339
|
+
"returned_items": len(items),
|
|
340
|
+
"page_amount": _approval_page_amount(raw.get("page")),
|
|
341
|
+
"reported_total": max(_approval_page_total(raw.get("page")) - filtered_count, 0),
|
|
342
|
+
},
|
|
343
|
+
selection={"app_key": app_key, "record_id": record_id, "workflow_node_id": workflow_node_id, "keyword": keyword},
|
|
344
|
+
)
|
|
141
345
|
|
|
142
346
|
def record_comment_add(self, *, profile: str, app_key: str, apply_id: int, payload: dict[str, Any]) -> dict[str, Any]:
|
|
143
347
|
self._require_app_and_apply(app_key, apply_id)
|
|
@@ -145,7 +349,14 @@ class ApprovalTools(ToolBase):
|
|
|
145
349
|
|
|
146
350
|
def runner(session_profile, context):
|
|
147
351
|
result = self.backend.request("POST", context, f"/app/{app_key}/apply/{apply_id}/comment", json_body=payload)
|
|
148
|
-
return {
|
|
352
|
+
return {
|
|
353
|
+
"profile": profile,
|
|
354
|
+
"ws_id": session_profile.selected_ws_id,
|
|
355
|
+
"app_key": app_key,
|
|
356
|
+
"apply_id": apply_id,
|
|
357
|
+
"result": result,
|
|
358
|
+
"request_route": self._request_route_payload(context),
|
|
359
|
+
}
|
|
149
360
|
|
|
150
361
|
return self._run(profile, runner)
|
|
151
362
|
|
|
@@ -167,9 +378,23 @@ class ApprovalTools(ToolBase):
|
|
|
167
378
|
"list_type": list_type,
|
|
168
379
|
"list_type_label": get_record_list_type_label(list_type),
|
|
169
380
|
"page": result,
|
|
381
|
+
"request_route": self._request_route_payload(context),
|
|
170
382
|
}
|
|
171
383
|
|
|
172
|
-
|
|
384
|
+
raw = self._run(profile, runner)
|
|
385
|
+
items = _approval_page_items(raw.get("page"))
|
|
386
|
+
return self._public_page_response(
|
|
387
|
+
raw,
|
|
388
|
+
items=items,
|
|
389
|
+
pagination={
|
|
390
|
+
"page": page_num,
|
|
391
|
+
"page_size": page_size,
|
|
392
|
+
"returned_items": len(items),
|
|
393
|
+
"page_amount": _approval_page_amount(raw.get("page")),
|
|
394
|
+
"reported_total": _approval_page_total(raw.get("page")),
|
|
395
|
+
},
|
|
396
|
+
selection={"app_key": app_key, "record_id": apply_id, "list_type": list_type},
|
|
397
|
+
)
|
|
173
398
|
|
|
174
399
|
def record_comment_mention_candidates(
|
|
175
400
|
self,
|
|
@@ -199,6 +424,7 @@ class ApprovalTools(ToolBase):
|
|
|
199
424
|
"list_type": list_type,
|
|
200
425
|
"list_type_label": get_record_list_type_label(list_type),
|
|
201
426
|
"page": result,
|
|
427
|
+
"request_route": self._request_route_payload(context),
|
|
202
428
|
}
|
|
203
429
|
|
|
204
430
|
return self._run(profile, runner)
|
|
@@ -208,16 +434,36 @@ class ApprovalTools(ToolBase):
|
|
|
208
434
|
|
|
209
435
|
def runner(session_profile, context):
|
|
210
436
|
result = self.backend.request("POST", context, f"/app/{app_key}/apply/{apply_id}/comment/read")
|
|
211
|
-
return {
|
|
437
|
+
return {
|
|
438
|
+
"profile": profile,
|
|
439
|
+
"ws_id": session_profile.selected_ws_id,
|
|
440
|
+
"app_key": app_key,
|
|
441
|
+
"apply_id": apply_id,
|
|
442
|
+
"result": result,
|
|
443
|
+
"request_route": self._request_route_payload(context),
|
|
444
|
+
}
|
|
212
445
|
|
|
213
|
-
|
|
446
|
+
raw = self._run(profile, runner)
|
|
447
|
+
return self._public_action_response(
|
|
448
|
+
raw,
|
|
449
|
+
action="record_comment_mark_read",
|
|
450
|
+
resource={"app_key": app_key, "record_id": apply_id},
|
|
451
|
+
selection={},
|
|
452
|
+
)
|
|
214
453
|
|
|
215
454
|
def record_comment_stats(self, *, profile: str, app_key: str, apply_id: int) -> dict[str, Any]:
|
|
216
455
|
self._require_app_and_apply(app_key, apply_id)
|
|
217
456
|
|
|
218
457
|
def runner(session_profile, context):
|
|
219
458
|
result = self.backend.request("GET", context, f"/app/{app_key}/apply/{apply_id}/comment/statistic")
|
|
220
|
-
return {
|
|
459
|
+
return {
|
|
460
|
+
"profile": profile,
|
|
461
|
+
"ws_id": session_profile.selected_ws_id,
|
|
462
|
+
"app_key": app_key,
|
|
463
|
+
"apply_id": apply_id,
|
|
464
|
+
"result": result,
|
|
465
|
+
"request_route": self._request_route_payload(context),
|
|
466
|
+
}
|
|
221
467
|
|
|
222
468
|
return self._run(profile, runner)
|
|
223
469
|
|
|
@@ -273,7 +519,14 @@ class ApprovalTools(ToolBase):
|
|
|
273
519
|
f"/app/{app_key}/apply/{apply_id}/revertNode",
|
|
274
520
|
params={"auditNodeId": audit_node_id},
|
|
275
521
|
)
|
|
276
|
-
return {
|
|
522
|
+
return {
|
|
523
|
+
"profile": profile,
|
|
524
|
+
"ws_id": session_profile.selected_ws_id,
|
|
525
|
+
"app_key": app_key,
|
|
526
|
+
"apply_id": apply_id,
|
|
527
|
+
"result": result,
|
|
528
|
+
"request_route": self._request_route_payload(context),
|
|
529
|
+
}
|
|
277
530
|
|
|
278
531
|
return self._run(profile, runner)
|
|
279
532
|
|
|
@@ -284,7 +537,14 @@ class ApprovalTools(ToolBase):
|
|
|
284
537
|
|
|
285
538
|
def runner(session_profile, context):
|
|
286
539
|
result = self.backend.request("POST", context, f"/app/{app_key}/apply/{apply_id}/rollback", json_body=body)
|
|
287
|
-
return {
|
|
540
|
+
return {
|
|
541
|
+
"profile": profile,
|
|
542
|
+
"ws_id": session_profile.selected_ws_id,
|
|
543
|
+
"app_key": app_key,
|
|
544
|
+
"apply_id": apply_id,
|
|
545
|
+
"result": result,
|
|
546
|
+
"request_route": self._request_route_payload(context),
|
|
547
|
+
}
|
|
288
548
|
|
|
289
549
|
return self._run(profile, runner)
|
|
290
550
|
|
|
@@ -295,7 +555,14 @@ class ApprovalTools(ToolBase):
|
|
|
295
555
|
|
|
296
556
|
def runner(session_profile, context):
|
|
297
557
|
result = self.backend.request("POST", context, f"/app/{app_key}/apply/{apply_id}/transfer", json_body=body)
|
|
298
|
-
return {
|
|
558
|
+
return {
|
|
559
|
+
"profile": profile,
|
|
560
|
+
"ws_id": session_profile.selected_ws_id,
|
|
561
|
+
"app_key": app_key,
|
|
562
|
+
"apply_id": apply_id,
|
|
563
|
+
"result": result,
|
|
564
|
+
"request_route": self._request_route_payload(context),
|
|
565
|
+
}
|
|
299
566
|
|
|
300
567
|
return self._run(profile, runner)
|
|
301
568
|
|
|
@@ -319,7 +586,14 @@ class ApprovalTools(ToolBase):
|
|
|
319
586
|
if keyword:
|
|
320
587
|
params["keyword"] = keyword
|
|
321
588
|
result = self.backend.request("GET", context, f"/app/{app_key}/apply/{apply_id}/transfer/member", params=params)
|
|
322
|
-
return {
|
|
589
|
+
return {
|
|
590
|
+
"profile": profile,
|
|
591
|
+
"ws_id": session_profile.selected_ws_id,
|
|
592
|
+
"app_key": app_key,
|
|
593
|
+
"apply_id": apply_id,
|
|
594
|
+
"page": result,
|
|
595
|
+
"request_route": self._request_route_payload(context),
|
|
596
|
+
}
|
|
323
597
|
|
|
324
598
|
return self._run(profile, runner)
|
|
325
599
|
|
|
@@ -328,7 +602,14 @@ class ApprovalTools(ToolBase):
|
|
|
328
602
|
|
|
329
603
|
def runner(session_profile, context):
|
|
330
604
|
result = self.backend.request("GET", context, f"/app/{app_key}/apply/{apply_id}/reassign")
|
|
331
|
-
return {
|
|
605
|
+
return {
|
|
606
|
+
"profile": profile,
|
|
607
|
+
"ws_id": session_profile.selected_ws_id,
|
|
608
|
+
"app_key": app_key,
|
|
609
|
+
"apply_id": apply_id,
|
|
610
|
+
"result": result,
|
|
611
|
+
"request_route": self._request_route_payload(context),
|
|
612
|
+
}
|
|
332
613
|
|
|
333
614
|
return self._run(profile, runner)
|
|
334
615
|
|
|
@@ -338,7 +619,14 @@ class ApprovalTools(ToolBase):
|
|
|
338
619
|
|
|
339
620
|
def runner(session_profile, context):
|
|
340
621
|
result = self.backend.request("POST", context, f"/app/{app_key}/apply/{apply_id}/reassign", json_body=body)
|
|
341
|
-
return {
|
|
622
|
+
return {
|
|
623
|
+
"profile": profile,
|
|
624
|
+
"ws_id": session_profile.selected_ws_id,
|
|
625
|
+
"app_key": app_key,
|
|
626
|
+
"apply_id": apply_id,
|
|
627
|
+
"result": result,
|
|
628
|
+
"request_route": self._request_route_payload(context),
|
|
629
|
+
}
|
|
342
630
|
|
|
343
631
|
return self._run(profile, runner)
|
|
344
632
|
|
|
@@ -362,7 +650,14 @@ class ApprovalTools(ToolBase):
|
|
|
362
650
|
if search_key:
|
|
363
651
|
params["searchKey"] = search_key
|
|
364
652
|
result = self.backend.request("GET", context, f"/app/{app_key}/apply/{apply_id}/countersign/member", params=params)
|
|
365
|
-
return {
|
|
653
|
+
return {
|
|
654
|
+
"profile": profile,
|
|
655
|
+
"ws_id": session_profile.selected_ws_id,
|
|
656
|
+
"app_key": app_key,
|
|
657
|
+
"apply_id": apply_id,
|
|
658
|
+
"page": result,
|
|
659
|
+
"request_route": self._request_route_payload(context),
|
|
660
|
+
}
|
|
366
661
|
|
|
367
662
|
return self._run(profile, runner)
|
|
368
663
|
|
|
@@ -373,10 +668,29 @@ class ApprovalTools(ToolBase):
|
|
|
373
668
|
|
|
374
669
|
def runner(session_profile, context):
|
|
375
670
|
result = self.backend.request("POST", context, f"/app/{app_key}/apply/{apply_id}/countersign", json_body=body)
|
|
376
|
-
return {
|
|
671
|
+
return {
|
|
672
|
+
"profile": profile,
|
|
673
|
+
"ws_id": session_profile.selected_ws_id,
|
|
674
|
+
"app_key": app_key,
|
|
675
|
+
"apply_id": apply_id,
|
|
676
|
+
"result": result,
|
|
677
|
+
"request_route": self._request_route_payload(context),
|
|
678
|
+
}
|
|
377
679
|
|
|
378
680
|
return self._run(profile, runner)
|
|
379
681
|
|
|
682
|
+
def _request_route_payload(self, context) -> JSONObject: # type: ignore[no-untyped-def]
|
|
683
|
+
describe_route = getattr(self.backend, "describe_route", None)
|
|
684
|
+
if callable(describe_route):
|
|
685
|
+
payload = describe_route(context)
|
|
686
|
+
if isinstance(payload, dict):
|
|
687
|
+
return payload
|
|
688
|
+
return {
|
|
689
|
+
"base_url": getattr(context, "base_url", None),
|
|
690
|
+
"qf_version": getattr(context, "qf_version", None),
|
|
691
|
+
"qf_version_source": getattr(context, "qf_version_source", None) or ("context" if getattr(context, "qf_version", None) else "unknown"),
|
|
692
|
+
}
|
|
693
|
+
|
|
380
694
|
def _require_app_and_apply(self, app_key: str, apply_id: int) -> None:
|
|
381
695
|
if not app_key:
|
|
382
696
|
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
@@ -400,9 +714,11 @@ class ApprovalTools(ToolBase):
|
|
|
400
714
|
self._normalize_alias(body, "formId", "form_id")
|
|
401
715
|
|
|
402
716
|
node_id = self._extract_node_id(body)
|
|
403
|
-
body["nodeId"] = node_id
|
|
717
|
+
body["nodeId"] = self._resolve_actionable_node_id(context, app_key, apply_id, node_id)
|
|
404
718
|
body["applyId"] = self._match_or_fill_int(body, field_name="applyId", expected_value=apply_id)
|
|
405
719
|
body["formId"] = self._resolve_form_id(profile, context, app_key, explicit_form_id=body.get("formId"))
|
|
720
|
+
if body.get("answers") is None:
|
|
721
|
+
body["answers"] = self._fetch_current_todo_answers(context, app_key, apply_id, body["nodeId"])
|
|
406
722
|
|
|
407
723
|
self._validate_approval_payload(body)
|
|
408
724
|
return body
|
|
@@ -461,6 +777,54 @@ class ApprovalTools(ToolBase):
|
|
|
461
777
|
elif alias_value is not None and payload.get(canonical_key) != alias_value:
|
|
462
778
|
raise_tool_error(QingflowApiError.config_error(f"payload.{canonical_key} and payload.{alias_key} must match when both are provided"))
|
|
463
779
|
|
|
780
|
+
def _resolve_actionable_node_id(self, context, app_key: str, apply_id: int, node_id: int) -> int: # type: ignore[no-untyped-def]
|
|
781
|
+
infos = self.backend.request(
|
|
782
|
+
"GET",
|
|
783
|
+
context,
|
|
784
|
+
f"/app/{app_key}/apply/{apply_id}/auditInfo",
|
|
785
|
+
params={"type": 1},
|
|
786
|
+
)
|
|
787
|
+
if not isinstance(infos, list) or not infos:
|
|
788
|
+
raise_tool_error(
|
|
789
|
+
QingflowApiError.config_error(
|
|
790
|
+
f"apply_id={apply_id} is not currently actionable for the logged-in user in todo list"
|
|
791
|
+
)
|
|
792
|
+
)
|
|
793
|
+
actionable_node_ids = {
|
|
794
|
+
candidate
|
|
795
|
+
for item in infos
|
|
796
|
+
if isinstance(item, dict)
|
|
797
|
+
for candidate in (item.get("auditNodeId"), item.get("nodeId"))
|
|
798
|
+
if isinstance(candidate, int) and candidate > 0
|
|
799
|
+
}
|
|
800
|
+
if node_id not in actionable_node_ids:
|
|
801
|
+
raise_tool_error(
|
|
802
|
+
QingflowApiError.config_error(
|
|
803
|
+
f"payload.nodeId={node_id} is not an actionable todo node for apply_id={apply_id}"
|
|
804
|
+
)
|
|
805
|
+
)
|
|
806
|
+
return node_id
|
|
807
|
+
|
|
808
|
+
def _fetch_current_todo_answers(self, context, app_key: str, apply_id: int, node_id: int) -> list[dict[str, Any]]: # type: ignore[no-untyped-def]
|
|
809
|
+
detail = self.backend.request(
|
|
810
|
+
"GET",
|
|
811
|
+
context,
|
|
812
|
+
f"/app/{app_key}/apply/{apply_id}",
|
|
813
|
+
params={"role": 3, "listType": 1, "auditNodeId": node_id},
|
|
814
|
+
)
|
|
815
|
+
answers = detail.get("answers") if isinstance(detail, dict) else None
|
|
816
|
+
if not isinstance(answers, list):
|
|
817
|
+
raise_tool_error(
|
|
818
|
+
QingflowApiError.config_error(
|
|
819
|
+
f"cannot resolve current answers for apply_id={apply_id} nodeId={node_id}"
|
|
820
|
+
)
|
|
821
|
+
)
|
|
822
|
+
normalized_answers: list[dict[str, Any]] = []
|
|
823
|
+
for item in answers:
|
|
824
|
+
if isinstance(item, dict):
|
|
825
|
+
normalized_answers.append(dict(item))
|
|
826
|
+
return normalized_answers
|
|
827
|
+
|
|
464
828
|
def _validate_approval_payload(self, payload: dict[str, Any]) -> None:
|
|
465
829
|
self._reject_unsupported_fields(payload)
|
|
466
830
|
if not isinstance(payload.get("formId"), int) or payload["formId"] <= 0:
|
|
@@ -469,6 +833,9 @@ class ApprovalTools(ToolBase):
|
|
|
469
833
|
raise_tool_error(QingflowApiError.config_error("payload.applyId must be a positive integer"))
|
|
470
834
|
if not isinstance(payload.get("nodeId"), int) or payload["nodeId"] <= 0:
|
|
471
835
|
raise_tool_error(QingflowApiError.config_error("payload.nodeId must be a positive integer"))
|
|
836
|
+
answers = payload.get("answers")
|
|
837
|
+
if answers is not None and not isinstance(answers, list):
|
|
838
|
+
raise_tool_error(QingflowApiError.config_error("payload.answers must be an array when provided"))
|
|
472
839
|
|
|
473
840
|
def _validate_audit_payload(self, payload: dict[str, Any], *, require_uid: bool = False) -> None:
|
|
474
841
|
self._reject_unsupported_fields(payload)
|
|
@@ -485,6 +852,108 @@ class ApprovalTools(ToolBase):
|
|
|
485
852
|
if payload.get("handSignImageUrl"):
|
|
486
853
|
raise_tool_error(QingflowApiError.not_supported("NOT_SUPPORTED_IN_V1: handSignImageUrl is not supported"))
|
|
487
854
|
|
|
855
|
+
def _extract_transfer_target_uid(self, payload: dict[str, Any]) -> int | None:
|
|
856
|
+
for key in ("uid", "target_member_id", "targetMemberId"):
|
|
857
|
+
value = payload.get(key)
|
|
858
|
+
if isinstance(value, int) and value > 0:
|
|
859
|
+
return value
|
|
860
|
+
return None
|
|
861
|
+
|
|
862
|
+
def _raise_if_self_transfer(self, *, profile: str, payload: dict[str, Any]) -> None:
|
|
863
|
+
target_uid = self._extract_transfer_target_uid(payload)
|
|
864
|
+
if target_uid is None:
|
|
865
|
+
return
|
|
866
|
+
session_profile = self.sessions.get_profile(profile)
|
|
867
|
+
if session_profile is not None and target_uid == session_profile.uid:
|
|
868
|
+
raise_tool_error(
|
|
869
|
+
QingflowApiError.config_error(
|
|
870
|
+
"task transfer does not support transferring to the current user; choose another transfer member"
|
|
871
|
+
)
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
def _filter_self_transfer_candidates(self, *, profile: str, items: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
875
|
+
session_profile = self.sessions.get_profile(profile)
|
|
876
|
+
if session_profile is None:
|
|
877
|
+
return items
|
|
878
|
+
current_uid = session_profile.uid
|
|
879
|
+
return [item for item in items if item.get("uid") != current_uid]
|
|
880
|
+
|
|
881
|
+
def _delegate_task_action(
|
|
882
|
+
self,
|
|
883
|
+
*,
|
|
884
|
+
profile: str,
|
|
885
|
+
app_key: str,
|
|
886
|
+
record_id: int,
|
|
887
|
+
action: str,
|
|
888
|
+
payload: dict[str, Any],
|
|
889
|
+
fields: dict[str, Any],
|
|
890
|
+
public_action: str,
|
|
891
|
+
workflow_node_id: int | None = None,
|
|
892
|
+
) -> dict[str, Any]:
|
|
893
|
+
from .task_context_tools import TaskContextTools
|
|
894
|
+
|
|
895
|
+
node_id = workflow_node_id
|
|
896
|
+
if node_id is None:
|
|
897
|
+
node_payload = dict(payload or {})
|
|
898
|
+
node_id = self._extract_node_id(node_payload)
|
|
899
|
+
delegated = TaskContextTools(self.sessions, self.backend).task_action_execute(
|
|
900
|
+
profile=profile,
|
|
901
|
+
app_key=app_key,
|
|
902
|
+
record_id=record_id,
|
|
903
|
+
workflow_node_id=node_id,
|
|
904
|
+
action=action,
|
|
905
|
+
payload=payload or {},
|
|
906
|
+
fields=fields or {},
|
|
907
|
+
)
|
|
908
|
+
response = dict(delegated)
|
|
909
|
+
data = response.get("data")
|
|
910
|
+
if isinstance(data, dict):
|
|
911
|
+
payload_data = dict(data)
|
|
912
|
+
payload_data["action"] = public_action
|
|
913
|
+
response["data"] = payload_data
|
|
914
|
+
return response
|
|
915
|
+
|
|
916
|
+
def _public_page_response(
|
|
917
|
+
self,
|
|
918
|
+
raw: dict[str, Any],
|
|
919
|
+
*,
|
|
920
|
+
items: list[dict[str, Any]],
|
|
921
|
+
pagination: dict[str, Any],
|
|
922
|
+
selection: dict[str, Any],
|
|
923
|
+
) -> dict[str, Any]:
|
|
924
|
+
response = dict(raw)
|
|
925
|
+
response["ok"] = bool(raw.get("ok", True))
|
|
926
|
+
response["warnings"] = []
|
|
927
|
+
response["output_profile"] = "normal"
|
|
928
|
+
response["data"] = {
|
|
929
|
+
"items": items,
|
|
930
|
+
"pagination": pagination,
|
|
931
|
+
"selection": selection,
|
|
932
|
+
}
|
|
933
|
+
return response
|
|
934
|
+
|
|
935
|
+
def _public_action_response(
|
|
936
|
+
self,
|
|
937
|
+
raw: dict[str, Any],
|
|
938
|
+
*,
|
|
939
|
+
action: str,
|
|
940
|
+
resource: dict[str, Any],
|
|
941
|
+
selection: dict[str, Any],
|
|
942
|
+
human_review: bool = False,
|
|
943
|
+
) -> dict[str, Any]:
|
|
944
|
+
response = dict(raw)
|
|
945
|
+
response["ok"] = bool(raw.get("ok", True))
|
|
946
|
+
response["warnings"] = []
|
|
947
|
+
response["output_profile"] = "normal"
|
|
948
|
+
response["data"] = {
|
|
949
|
+
"action": action,
|
|
950
|
+
"resource": resource,
|
|
951
|
+
"selection": selection,
|
|
952
|
+
"result": raw.get("result"),
|
|
953
|
+
"human_review": human_review,
|
|
954
|
+
}
|
|
955
|
+
return response
|
|
956
|
+
|
|
488
957
|
def _request_route_payload(self, context) -> JSONObject: # type: ignore[no-untyped-def]
|
|
489
958
|
describe_route = getattr(self.backend, "describe_route", None)
|
|
490
959
|
if callable(describe_route):
|
|
@@ -496,3 +965,33 @@ class ApprovalTools(ToolBase):
|
|
|
496
965
|
"qf_version": context.qf_version,
|
|
497
966
|
"qf_version_source": getattr(context, "qf_version_source", None) or ("context" if getattr(context, "qf_version", None) else "unknown"),
|
|
498
967
|
}
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
def _approval_page_items(payload: Any) -> list[dict[str, Any]]:
|
|
971
|
+
if isinstance(payload, list):
|
|
972
|
+
return [item for item in payload if isinstance(item, dict)]
|
|
973
|
+
if not isinstance(payload, dict):
|
|
974
|
+
return []
|
|
975
|
+
for key in ("list", "items", "rows", "result"):
|
|
976
|
+
value = payload.get(key)
|
|
977
|
+
if isinstance(value, list):
|
|
978
|
+
return [item for item in value if isinstance(item, dict)]
|
|
979
|
+
for container_key in ("data", "page"):
|
|
980
|
+
nested = payload.get(container_key)
|
|
981
|
+
if isinstance(nested, dict):
|
|
982
|
+
nested_items = _approval_page_items(nested)
|
|
983
|
+
if nested_items:
|
|
984
|
+
return nested_items
|
|
985
|
+
return []
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
def _approval_page_amount(payload: Any) -> Any:
|
|
989
|
+
if isinstance(payload, dict):
|
|
990
|
+
return payload.get("pageAmount", payload.get("page_amount"))
|
|
991
|
+
return None
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
def _approval_page_total(payload: Any) -> Any:
|
|
995
|
+
if isinstance(payload, dict):
|
|
996
|
+
return payload.get("total", payload.get("count"))
|
|
997
|
+
return None
|