hap-cli 0.5.0__py3-none-any.whl

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.
Files changed (58) hide show
  1. hap_cli/README.md +194 -0
  2. hap_cli/README_CN.md +601 -0
  3. hap_cli/__init__.py +3 -0
  4. hap_cli/commands/__init__.py +1 -0
  5. hap_cli/commands/ai_cmd.py +224 -0
  6. hap_cli/commands/app_cmd.py +308 -0
  7. hap_cli/commands/calendar_cmd.py +138 -0
  8. hap_cli/commands/chat_cmd.py +101 -0
  9. hap_cli/commands/config_cmd.py +169 -0
  10. hap_cli/commands/contact_cmd.py +125 -0
  11. hap_cli/commands/department_cmd.py +168 -0
  12. hap_cli/commands/group_cmd.py +128 -0
  13. hap_cli/commands/instance_cmd.py +310 -0
  14. hap_cli/commands/node_cmd.py +538 -0
  15. hap_cli/commands/optionset_cmd.py +99 -0
  16. hap_cli/commands/page_cmd.py +102 -0
  17. hap_cli/commands/plugin_cmd.py +133 -0
  18. hap_cli/commands/post_cmd.py +155 -0
  19. hap_cli/commands/record_cmd.py +228 -0
  20. hap_cli/commands/role_cmd.py +221 -0
  21. hap_cli/commands/workflow_cmd.py +284 -0
  22. hap_cli/commands/worksheet_cmd.py +342 -0
  23. hap_cli/context.py +43 -0
  24. hap_cli/core/__init__.py +1 -0
  25. hap_cli/core/ai.py +133 -0
  26. hap_cli/core/app.py +307 -0
  27. hap_cli/core/auth.py +219 -0
  28. hap_cli/core/calendar_mod.py +114 -0
  29. hap_cli/core/chat.py +73 -0
  30. hap_cli/core/contact.py +85 -0
  31. hap_cli/core/department.py +131 -0
  32. hap_cli/core/flow_node.py +1001 -0
  33. hap_cli/core/group.py +99 -0
  34. hap_cli/core/instance.py +572 -0
  35. hap_cli/core/optionset.py +112 -0
  36. hap_cli/core/page.py +138 -0
  37. hap_cli/core/plugin.py +87 -0
  38. hap_cli/core/post.py +118 -0
  39. hap_cli/core/record.py +268 -0
  40. hap_cli/core/role.py +227 -0
  41. hap_cli/core/session.py +348 -0
  42. hap_cli/core/workflow.py +556 -0
  43. hap_cli/core/worksheet.py +403 -0
  44. hap_cli/hap_cli.py +105 -0
  45. hap_cli/skills/SKILL.md +383 -0
  46. hap_cli/skills/__init__.py +0 -0
  47. hap_cli/tests/__init__.py +1 -0
  48. hap_cli/tests/test_core.py +1824 -0
  49. hap_cli/tests/test_full_e2e.py +136 -0
  50. hap_cli/tests/test_integration.py +805 -0
  51. hap_cli/utils/__init__.py +1 -0
  52. hap_cli/utils/formatting.py +111 -0
  53. hap_cli/utils/options.py +10 -0
  54. hap_cli-0.5.0.dist-info/METADATA +223 -0
  55. hap_cli-0.5.0.dist-info/RECORD +58 -0
  56. hap_cli-0.5.0.dist-info/WHEEL +5 -0
  57. hap_cli-0.5.0.dist-info/entry_points.txt +2 -0
  58. hap_cli-0.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1001 @@
1
+ """Workflow node management for MingDAO HAP.
2
+
3
+ Covers: add/delete/get nodes, save node config, update node name/desc,
4
+ data processing nodes (create/update/delete/search records),
5
+ code test, webhook test, AIGC test, code templates, and JSON-to-controls.
6
+ """
7
+
8
+ from typing import Any, Optional
9
+
10
+ from hap_cli.core.session import Session
11
+
12
+
13
+ # ── Node Type Constants ──────────────────────────────────────────────────
14
+
15
+ NODE_TYPE = {
16
+ "START": 0,
17
+ "BRANCH": 1,
18
+ "BRANCH_ITEM": 2,
19
+ "FILL": 3,
20
+ "APPROVAL": 4,
21
+ "CC": 5,
22
+ "ACTION": 6, # Data processing: add/edit/delete record
23
+ "SEARCH": 7, # Get single record
24
+ "WEBHOOK": 8,
25
+ "FORMULA": 9,
26
+ "MESSAGE": 10,
27
+ "EMAIL": 11,
28
+ "DELAY": 12,
29
+ "GET_MORE_RECORD": 13, # Get multiple records / batch ops
30
+ "CODE": 14,
31
+ "LINK": 15,
32
+ "SUB_PROCESS": 16,
33
+ "PUSH": 17,
34
+ "FILE": 18,
35
+ "TEMPLATE": 19,
36
+ "PBP": 20,
37
+ "JSON_PARSE": 21,
38
+ "AUTHENTICATION": 22,
39
+ "PARAMETER": 23,
40
+ "API_PACKAGE": 24,
41
+ "API": 25,
42
+ "APPROVAL_PROCESS": 26,
43
+ "NOTICE": 27,
44
+ "SNAPSHOT": 28,
45
+ "LOOP": 29,
46
+ "RETURN": 30,
47
+ "AIGC": 31,
48
+ "PLUGIN": 32,
49
+ "AGENT": 33,
50
+ "SYSTEM": 100,
51
+ "TOOLS": 101,
52
+ "FIND_SINGLE_MESSAGE": 1000,
53
+ "FIND_MORE_MESSAGE": 1001,
54
+ }
55
+
56
+ # ── Action ID Constants (for ACTION / SEARCH / GET_MORE_RECORD) ──────────
57
+
58
+ ACTION_ID = {
59
+ # ACTION node (type 6) operations
60
+ "ADD_RECORD": "1",
61
+ "EDIT_RECORD": "2",
62
+ "DELETE_RECORD": "3",
63
+ "CREATE_FILE": "4",
64
+ "CREATE_RECORD": "5",
65
+ "REFRESH_SINGLE_DATA": "6",
66
+ "REFUND": "7",
67
+ "RELATION": "20",
68
+ # FORMULA node (type 9) operations
69
+ "NUMBER_FORMULA": "100",
70
+ "DATE_FORMULA": "101",
71
+ "JAVASCRIPT": "102",
72
+ "PYTHON": "103",
73
+ "DATE_DIFF_FORMULA": "104",
74
+ "OBJECT_TOTAL": "105",
75
+ "FUNCTION_CALCULATION": "106",
76
+ "WORKSHEET_TOTAL": "107",
77
+ "CUSTOM_ACTION_TOTAL": "108",
78
+ # EMAIL node (type 11) operations
79
+ "SEND_EMAIL_SINGLE_DISPLAY": "201",
80
+ "SEND_EMAIL": "202",
81
+ "SEND_TEMPLATE_MESSAGE": "203",
82
+ # LOOP node (type 29) operations
83
+ "CONDITION_LOOP": "210",
84
+ "COUNT_LOOP": "211",
85
+ # GET_MORE_RECORD node (type 13) operations
86
+ "FROM_WORKSHEET": "400",
87
+ "FROM_RECORD": "401",
88
+ "FROM_ADD": "402",
89
+ "FROM_ARRAY": "403",
90
+ "FROM_CODE_ARRAY": "404",
91
+ "FROM_ARTIFICIAL": "405",
92
+ # SEARCH node (type 7) operations
93
+ "WORKSHEET_FIND": "406",
94
+ "BATCH_FIND": "407",
95
+ "FROM_PBP_INPUT_ARRAY": "408",
96
+ "FROM_API_ARRAY": "409",
97
+ "FROM_PBP_OUTPUT_ARRAY": "410",
98
+ "BATCH_ACTION": "411",
99
+ "BATCH_UPDATE": "412",
100
+ "BATCH_DELETE": "413",
101
+ "FROM_PLUGIN_ARRAY": "414",
102
+ "REFRESH_MULTIPLE_DATA": "415",
103
+ "RECORD_LINK_PAY": "416",
104
+ "RECORD_LINK_FIND": "420",
105
+ "RECORD_UPDATE": "421",
106
+ "RECORD_DELETE": "422",
107
+ # PBP
108
+ "PBP": "500",
109
+ "PBP_INPUT": "501",
110
+ "PBP_OUT": "502",
111
+ # JSON
112
+ "JSON_PARSE": "510",
113
+ "FROM_JSON_PARSE_ARRAY": "511",
114
+ # AUTH
115
+ "NO_AUTH": "520",
116
+ "BASIC_AUTH": "521",
117
+ "AUTH_CODE": "522",
118
+ "CREDENTIALS": "523",
119
+ "REFRESH_CREDENTIALS": "524",
120
+ # AIGC
121
+ "AIGC_TEXT": "531",
122
+ "AIGC_OBJECT": "532",
123
+ "AGENT": "533",
124
+ }
125
+
126
+ APP_TYPE = {
127
+ "SHEET": 1,
128
+ "TASK": 2,
129
+ "LOOP": 5,
130
+ "DATE": 6,
131
+ "WEBHOOK": 7,
132
+ "CUSTOM_ACTION": 8,
133
+ }
134
+
135
+
136
+ # ── Node CRUD ────────────────────────────────────────────────────────────
137
+
138
+
139
+ def add_node(
140
+ session: Session,
141
+ process_id: str,
142
+ flow_node_type: int,
143
+ name: str = "",
144
+ node_id: str = "",
145
+ select_node_id: str = "",
146
+ action_id: str = "",
147
+ ) -> dict[str, Any]:
148
+ """Add a new node to a workflow.
149
+
150
+ Args:
151
+ session: Active session
152
+ process_id: Process ID
153
+ flow_node_type: Node type (see NODE_TYPE dict)
154
+ name: Node name
155
+ node_id: Explicit node ID (auto-generated if empty)
156
+ select_node_id: Insert after this node
157
+ action_id: Action sub-type (see ACTION_ID dict)
158
+
159
+ Returns:
160
+ Created node info
161
+ """
162
+ data: dict[str, Any] = {
163
+ "processId": process_id,
164
+ "flowNodeType": flow_node_type,
165
+ }
166
+ if name:
167
+ data["name"] = name
168
+ if node_id:
169
+ data["nodeId"] = node_id
170
+ if select_node_id:
171
+ data["selectNodeId"] = select_node_id
172
+ if action_id:
173
+ data["actionId"] = action_id
174
+ return session.workflow_call("flowNode/add", data)
175
+
176
+
177
+ def delete_node(
178
+ session: Session,
179
+ process_id: str,
180
+ node_id: str,
181
+ ) -> dict[str, Any]:
182
+ """Delete a node from a workflow.
183
+
184
+ Args:
185
+ session: Active session
186
+ process_id: Process ID
187
+ node_id: Node ID to delete
188
+
189
+ Returns:
190
+ Deletion result
191
+ """
192
+ return session.workflow_call(
193
+ "flowNode/delete",
194
+ {"processId": process_id, "nodeId": node_id},
195
+ )
196
+
197
+
198
+ def get_nodes(
199
+ session: Session,
200
+ process_id: str,
201
+ instance_id: str = "",
202
+ ) -> dict[str, Any]:
203
+ """Get all nodes of a workflow.
204
+
205
+ Args:
206
+ session: Active session
207
+ process_id: Process ID
208
+ instance_id: Optional instance ID for runtime view
209
+
210
+ Returns:
211
+ Node tree/list
212
+ """
213
+ data: dict[str, Any] = {"processId": process_id}
214
+ if instance_id:
215
+ data["instanceId"] = instance_id
216
+ return session.workflow_call("flowNode/get", data)
217
+
218
+
219
+ def get_node_detail(
220
+ session: Session,
221
+ process_id: str,
222
+ node_id: str,
223
+ flow_node_type: int = 0,
224
+ select_node_id: str = "",
225
+ app_id: str = "",
226
+ instance_id: str = "",
227
+ ) -> dict[str, Any]:
228
+ """Get detailed configuration of a node.
229
+
230
+ Args:
231
+ session: Active session
232
+ process_id: Process ID
233
+ node_id: Node ID
234
+ flow_node_type: Node type
235
+ select_node_id: Context node
236
+ app_id: App ID
237
+ instance_id: Instance ID (for runtime)
238
+
239
+ Returns:
240
+ Node detail dict with full configuration
241
+ """
242
+ data: dict[str, Any] = {"processId": process_id, "nodeId": node_id}
243
+ if flow_node_type:
244
+ data["flowNodeType"] = flow_node_type
245
+ if select_node_id:
246
+ data["selectNodeId"] = select_node_id
247
+ if app_id:
248
+ data["appId"] = app_id
249
+ if instance_id:
250
+ data["instanceId"] = instance_id
251
+ return session.workflow_call("flowNode/getNodeDetail", data)
252
+
253
+
254
+ # ── Node Update ──────────────────────────────────────────────────────────
255
+
256
+
257
+ def update_node_name(
258
+ session: Session,
259
+ process_id: str,
260
+ node_id: str,
261
+ name: str,
262
+ ) -> dict[str, Any]:
263
+ """Update a node's name.
264
+
265
+ Args:
266
+ session: Active session
267
+ process_id: Process ID
268
+ node_id: Node ID
269
+ name: New name
270
+
271
+ Returns:
272
+ Update result
273
+ """
274
+ return session.workflow_call(
275
+ "flowNode/updateFlowNodeName",
276
+ {"processId": process_id, "nodeId": node_id, "name": name},
277
+ )
278
+
279
+
280
+ def update_node_desc(
281
+ session: Session,
282
+ process_id: str,
283
+ node_id: str,
284
+ desc: str = "",
285
+ alias: str = "",
286
+ ) -> dict[str, Any]:
287
+ """Update a node's description and alias.
288
+
289
+ Args:
290
+ session: Active session
291
+ process_id: Process ID
292
+ node_id: Node ID
293
+ desc: Description
294
+ alias: Node alias
295
+
296
+ Returns:
297
+ Update result
298
+ """
299
+ data: dict[str, Any] = {"processId": process_id, "nodeId": node_id}
300
+ if desc:
301
+ data["desc"] = desc
302
+ if alias:
303
+ data["alias"] = alias
304
+ return session.workflow_call("flowNode/nodeDesc", data)
305
+
306
+
307
+ def save_node(
308
+ session: Session,
309
+ process_id: str,
310
+ node_id: str,
311
+ flow_node_type: int,
312
+ config: dict[str, Any],
313
+ name: str = "",
314
+ select_node_id: str = "",
315
+ ) -> dict[str, Any]:
316
+ """Save a node's full configuration.
317
+
318
+ This is the universal node save endpoint that supports ALL node types.
319
+ The config dict varies by node type.
320
+
321
+ Args:
322
+ session: Active session
323
+ process_id: Process ID
324
+ node_id: Node ID
325
+ flow_node_type: Node type (see NODE_TYPE dict)
326
+ config: Node-specific configuration dict. Keys vary by type:
327
+ - ACTION(6): actionId, appId, appType, fields, selectNodeId,
328
+ operateCondition, sorts, executeType, filters
329
+ - SEARCH(7): actionId, appId, findFields, executeType, fields,
330
+ operateCondition, sorts, random, filters, link
331
+ - GET_MORE_RECORD(13): actionId, appId, operateCondition, fields,
332
+ selectNodeId, sorts, numberFieldValue, filters
333
+ - FILL(3): formProperties, accounts, submitBtnName, schedule
334
+ - APPROVAL(4): accounts, condition, multipleLevel
335
+ - CC(5): accounts, smsContent, templateId
336
+ - EMAIL(11): accounts, subject, content
337
+ - WEBHOOK(8): url, method, headers, body, contentType
338
+ - CODE(14): code, inputDatas, version, actionId
339
+ - FORMULA(9): actionId, fieldValue, fieldType
340
+ - DELAY(12): time, unit, executeTime
341
+ - SUB_PROCESS(16): subProcessId
342
+ - PUSH(17): pushType, content, appId, viewId, link
343
+ - FILE(18): appId, fileName, pdf
344
+ - JSON_PARSE(21): jsonStr, controls
345
+ - AIGC(31): model, prompt, systemMessage, outputs, actionId
346
+ - LOOP(29): actionId, executeType, numberFieldValue
347
+ - SNAPSHOT(28): appId
348
+ - BRANCH(1/2): operateCondition, resultTypeId
349
+ name: Node name
350
+ select_node_id: Context node
351
+
352
+ Returns:
353
+ Save result
354
+ """
355
+ data: dict[str, Any] = {
356
+ "processId": process_id,
357
+ "nodeId": node_id,
358
+ "flowNodeType": flow_node_type,
359
+ }
360
+ if name:
361
+ data["name"] = name
362
+ if select_node_id:
363
+ data["selectNodeId"] = select_node_id
364
+ data.update(config)
365
+ return session.workflow_call("flowNode/saveNode", data)
366
+
367
+
368
+ # ── Testing ──────────────────────────────────────────────────────────────
369
+
370
+
371
+ def test_code(
372
+ session: Session,
373
+ process_id: str,
374
+ node_id: str,
375
+ code: str,
376
+ input_data: Optional[list] = None,
377
+ action_id: str = "",
378
+ version: str = "",
379
+ ) -> dict[str, Any]:
380
+ """Test a code block node.
381
+
382
+ Args:
383
+ session: Active session
384
+ process_id: Process ID
385
+ node_id: Code node ID
386
+ code: Code to execute
387
+ input_data: Input parameter list [{name, value, type}]
388
+ action_id: Action ID
389
+ version: Code version
390
+
391
+ Returns:
392
+ Test execution result with output
393
+ """
394
+ data: dict[str, Any] = {
395
+ "processId": process_id,
396
+ "nodeId": node_id,
397
+ "code": code,
398
+ }
399
+ if input_data:
400
+ data["inputDatas"] = input_data
401
+ if action_id:
402
+ data["actionId"] = action_id
403
+ if version:
404
+ data["version"] = version
405
+ return session.workflow_call("flowNode/codeTest", data)
406
+
407
+
408
+ def test_webhook(
409
+ session: Session,
410
+ process_id: str,
411
+ node_id: str,
412
+ url: str,
413
+ method: str = "POST",
414
+ headers: Optional[list] = None,
415
+ body: str = "",
416
+ content_type: str = "application/json",
417
+ params: Optional[list] = None,
418
+ auth_id: str = "",
419
+ settings: Optional[dict] = None,
420
+ ) -> dict[str, Any]:
421
+ """Test a WebHook request.
422
+
423
+ Args:
424
+ session: Active session
425
+ process_id: Process ID
426
+ node_id: WebHook node ID
427
+ url: Target URL
428
+ method: HTTP method
429
+ headers: Request headers [{name, value}]
430
+ body: Request body (JSON string)
431
+ content_type: Content-Type header
432
+ params: URL parameters [{name, value}]
433
+ auth_id: Authentication config ID
434
+ settings: Additional settings
435
+
436
+ Returns:
437
+ WebHook response data
438
+ """
439
+ data: dict[str, Any] = {
440
+ "processId": process_id,
441
+ "nodeId": node_id,
442
+ "url": url,
443
+ "method": method,
444
+ "contentType": content_type,
445
+ }
446
+ if headers:
447
+ data["headers"] = headers
448
+ if body:
449
+ data["body"] = body
450
+ if params:
451
+ data["params"] = params
452
+ if auth_id:
453
+ data["authId"] = auth_id
454
+ if settings:
455
+ data["settings"] = settings
456
+ return session.workflow_call("flowNode/webHookTestRequest", data)
457
+
458
+
459
+ def test_aigc(
460
+ session: Session,
461
+ process_id: str,
462
+ node_id: str,
463
+ prompt: str,
464
+ model: str = "",
465
+ system_message: str = "",
466
+ temperature: float = 0.7,
467
+ outputs: Optional[list] = None,
468
+ action_id: str = "",
469
+ ) -> dict[str, Any]:
470
+ """Test an AI text generation node.
471
+
472
+ Args:
473
+ session: Active session
474
+ process_id: Process ID
475
+ node_id: AI node ID
476
+ prompt: User prompt
477
+ model: AI model name
478
+ system_message: System message
479
+ temperature: Temperature (0-1)
480
+ outputs: Output field definitions
481
+ action_id: Action ID
482
+
483
+ Returns:
484
+ AI generation result
485
+ """
486
+ data: dict[str, Any] = {
487
+ "processId": process_id,
488
+ "nodeId": node_id,
489
+ "prompt": prompt,
490
+ "temperature": temperature,
491
+ }
492
+ if model:
493
+ data["model"] = model
494
+ if system_message:
495
+ data["systemMessage"] = system_message
496
+ if outputs:
497
+ data["outputs"] = outputs
498
+ if action_id:
499
+ data["actionId"] = action_id
500
+ return session.workflow_call("flowNode/aigcTest", data)
501
+
502
+
503
+ def test_basic_auth(
504
+ session: Session,
505
+ process_id: str,
506
+ user_name: str,
507
+ password: str,
508
+ ) -> dict[str, Any]:
509
+ """Test basic authentication credentials.
510
+
511
+ Args:
512
+ session: Active session
513
+ process_id: Process ID
514
+ user_name: Username
515
+ password: Password
516
+
517
+ Returns:
518
+ Auth test result
519
+ """
520
+ return session.workflow_call(
521
+ "flowNode/basicAuthTest",
522
+ {"processId": process_id, "userName": user_name, "password": password},
523
+ )
524
+
525
+
526
+ # ── WebHook Data ─────────────────────────────────────────────────────────
527
+
528
+
529
+ def get_webhook_data(
530
+ session: Session,
531
+ process_id: str,
532
+ node_id: str,
533
+ select_node_id: str = "",
534
+ ) -> dict[str, Any]:
535
+ """Get received WebHook data for a node.
536
+
537
+ Args:
538
+ session: Active session
539
+ process_id: Process ID
540
+ node_id: WebHook node ID
541
+ select_node_id: Context node
542
+
543
+ Returns:
544
+ WebHook received data
545
+ """
546
+ data: dict[str, Any] = {"processId": process_id, "nodeId": node_id}
547
+ if select_node_id:
548
+ data["selectNodeId"] = select_node_id
549
+ return session.workflow_call("flowNode/getWebHookData", data)
550
+
551
+
552
+ # ── Utilities ────────────────────────────────────────────────────────────
553
+
554
+
555
+ def json_to_controls(
556
+ session: Session,
557
+ json_str: str,
558
+ ) -> dict[str, Any]:
559
+ """Convert JSON string to workflow controls.
560
+
561
+ Args:
562
+ session: Active session
563
+ json_str: JSON string to convert
564
+
565
+ Returns:
566
+ Converted controls list
567
+ """
568
+ return session.workflow_call(
569
+ "flowNode/jsonToControls",
570
+ {"json": json_str},
571
+ )
572
+
573
+
574
+ def get_node_form_property(
575
+ session: Session,
576
+ process_id: str,
577
+ node_id: str,
578
+ select_node_id: str = "",
579
+ ) -> dict[str, Any]:
580
+ """Get node form properties.
581
+
582
+ Args:
583
+ session: Active session
584
+ process_id: Process ID
585
+ node_id: Node ID
586
+ select_node_id: Context node
587
+
588
+ Returns:
589
+ Form property config
590
+ """
591
+ data: dict[str, Any] = {"processId": process_id, "nodeId": node_id}
592
+ if select_node_id:
593
+ data["selectNodeId"] = select_node_id
594
+ return session.workflow_call("flowNode/getNodeFormProperty", data)
595
+
596
+
597
+ def get_sub_process_list(
598
+ session: Session,
599
+ process_id: str,
600
+ select_node_id: str = "",
601
+ ) -> dict[str, Any]:
602
+ """Get available sub-processes.
603
+
604
+ Args:
605
+ session: Active session
606
+ process_id: Process ID
607
+ select_node_id: Context node
608
+
609
+ Returns:
610
+ Sub-process list
611
+ """
612
+ data: dict[str, Any] = {"processId": process_id}
613
+ if select_node_id:
614
+ data["selectNodeId"] = select_node_id
615
+ return session.workflow_call("flowNode/getSubProcessList", data)
616
+
617
+
618
+ # ── Code Templates ───────────────────────────────────────────────────────
619
+
620
+
621
+ def get_code_template_list(
622
+ session: Session,
623
+ keyword: str = "",
624
+ source: int = 0,
625
+ template_type: int = 0,
626
+ page_index: int = 1,
627
+ page_size: int = 50,
628
+ ) -> dict[str, Any]:
629
+ """Get code template list.
630
+
631
+ Args:
632
+ session: Active session
633
+ keyword: Search keyword
634
+ source: Template source
635
+ template_type: Template type
636
+ page_index: Page number
637
+ page_size: Items per page
638
+
639
+ Returns:
640
+ Template list
641
+ """
642
+ data: dict[str, Any] = {"pageIndex": page_index, "pageSize": page_size}
643
+ if keyword:
644
+ data["keyword"] = keyword
645
+ if source:
646
+ data["source"] = source
647
+ if template_type:
648
+ data["type"] = template_type
649
+ return session.workflow_call("flowNode/getCodeTemplateList", data)
650
+
651
+
652
+ def create_code_template(
653
+ session: Session,
654
+ name: str,
655
+ code: str,
656
+ template_type: int = 0,
657
+ source: int = 0,
658
+ input_data: Optional[list] = None,
659
+ ) -> dict[str, Any]:
660
+ """Create a code template.
661
+
662
+ Args:
663
+ session: Active session
664
+ name: Template name
665
+ code: Template code
666
+ template_type: Template type
667
+ source: Template source
668
+ input_data: Input parameters
669
+
670
+ Returns:
671
+ Created template info
672
+ """
673
+ data: dict[str, Any] = {"name": name, "code": code, "type": template_type, "source": source}
674
+ if input_data:
675
+ data["inputDatas"] = input_data
676
+ return session.workflow_call("flowNode/createCodeTemplate", data)
677
+
678
+
679
+ def update_code_template(
680
+ session: Session,
681
+ template_id: str,
682
+ name: str = "",
683
+ code: str = "",
684
+ deleted: bool = False,
685
+ input_data: Optional[list] = None,
686
+ ) -> dict[str, Any]:
687
+ """Update or delete a code template.
688
+
689
+ Args:
690
+ session: Active session
691
+ template_id: Template ID
692
+ name: New name
693
+ code: New code
694
+ deleted: Set True to delete
695
+ input_data: Updated input parameters
696
+
697
+ Returns:
698
+ Update result
699
+ """
700
+ data: dict[str, Any] = {"id": template_id}
701
+ if name:
702
+ data["name"] = name
703
+ if code:
704
+ data["code"] = code
705
+ if deleted:
706
+ data["deleted"] = True
707
+ if input_data:
708
+ data["inputDatas"] = input_data
709
+ return session.workflow_call("flowNode/updateCodeTemplate", data)
710
+
711
+
712
+ # ── App/Control Helpers ──────────────────────────────────────────────────
713
+
714
+
715
+ def get_flow_app_dtos(
716
+ session: Session,
717
+ process_id: str,
718
+ node_id: str,
719
+ select_node_id: str = "",
720
+ source_app_id: str = "",
721
+ data_source: int = 0,
722
+ enum_default: int = 0,
723
+ field_type: int = 0,
724
+ ) -> dict[str, Any]:
725
+ """Get available app/field data for a node configuration.
726
+
727
+ Args:
728
+ session: Active session
729
+ process_id: Process ID
730
+ node_id: Node ID
731
+ select_node_id: Context node
732
+ source_app_id: Source app ID
733
+ data_source: Data source type
734
+ enum_default: Default enum
735
+ field_type: Field type filter
736
+
737
+ Returns:
738
+ Available app/field data
739
+ """
740
+ data: dict[str, Any] = {"processId": process_id, "nodeId": node_id}
741
+ if select_node_id:
742
+ data["selectNodeId"] = select_node_id
743
+ if source_app_id:
744
+ data["sourceAppId"] = source_app_id
745
+ if data_source:
746
+ data["dataSource"] = data_source
747
+ if enum_default:
748
+ data["enumDefault"] = enum_default
749
+ if field_type:
750
+ data["type"] = field_type
751
+ return session.workflow_call("flowNode/getFlowAppDtos", data)
752
+
753
+
754
+ def get_start_event_deploy(
755
+ session: Session,
756
+ process_id: str,
757
+ node_id: str,
758
+ app_id: str = "",
759
+ app_type: int = 0,
760
+ control_id: str = "",
761
+ select_node_id: str = "",
762
+ ) -> dict[str, Any]:
763
+ """Get start event deployment config.
764
+
765
+ Args:
766
+ session: Active session
767
+ process_id: Process ID
768
+ node_id: Start node ID
769
+ app_id: App ID
770
+ app_type: App type
771
+ control_id: Control ID
772
+ select_node_id: Context node
773
+
774
+ Returns:
775
+ Start event configuration
776
+ """
777
+ data: dict[str, Any] = {"processId": process_id, "nodeId": node_id}
778
+ if app_id:
779
+ data["appId"] = app_id
780
+ if app_type:
781
+ data["appType"] = app_type
782
+ if control_id:
783
+ data["controlId"] = control_id
784
+ if select_node_id:
785
+ data["selectNodeId"] = select_node_id
786
+ return session.workflow_call("flowNode/getStartEventDeploy", data)
787
+
788
+
789
+ # ── Data Processing Convenience Functions ────────────────────────────────
790
+
791
+
792
+ def save_action_node(
793
+ session: Session,
794
+ process_id: str,
795
+ node_id: str,
796
+ action_id: str,
797
+ app_id: str,
798
+ fields: Optional[list] = None,
799
+ name: str = "",
800
+ app_type: int = 1,
801
+ select_node_id: str = "",
802
+ filters: Optional[list] = None,
803
+ operate_condition: Optional[list] = None,
804
+ sorts: Optional[list] = None,
805
+ execute_type: int = 0,
806
+ ) -> dict[str, Any]:
807
+ """Save configuration for a data ACTION node (type 6).
808
+
809
+ Used for: add record, edit record, delete record, create relation, etc.
810
+
811
+ Args:
812
+ session: Active session
813
+ process_id: Process ID
814
+ node_id: Node ID
815
+ action_id: Operation type:
816
+ '1'=ADD, '2'=EDIT, '3'=DELETE, '5'=CREATE_RECORD,
817
+ '6'=REFRESH_SINGLE, '20'=RELATION
818
+ app_id: Target worksheet ID
819
+ fields: Field mapping [{fieldId, type, fieldValue, fieldValueId, nodeId}]
820
+ name: Node name
821
+ app_type: App type (1=SHEET default)
822
+ select_node_id: Source data node ID
823
+ filters: Filter conditions
824
+ operate_condition: Operation conditions
825
+ sorts: Sort rules
826
+ execute_type: Execution behavior (0=default)
827
+
828
+ Returns:
829
+ Save result
830
+ """
831
+ config: dict[str, Any] = {
832
+ "actionId": action_id,
833
+ "appId": app_id,
834
+ "appType": app_type,
835
+ }
836
+ if fields:
837
+ config["fields"] = fields
838
+ if select_node_id:
839
+ config["selectNodeId"] = select_node_id
840
+ if filters:
841
+ config["filters"] = filters
842
+ if operate_condition:
843
+ config["operateCondition"] = operate_condition
844
+ if sorts:
845
+ config["sorts"] = sorts
846
+ if execute_type:
847
+ config["executeType"] = execute_type
848
+ return save_node(session, process_id, node_id, NODE_TYPE["ACTION"], config, name=name)
849
+
850
+
851
+ def save_search_node(
852
+ session: Session,
853
+ process_id: str,
854
+ node_id: str,
855
+ action_id: str,
856
+ app_id: str = "",
857
+ fields: Optional[list] = None,
858
+ name: str = "",
859
+ select_node_id: str = "",
860
+ operate_condition: Optional[list] = None,
861
+ sorts: Optional[list] = None,
862
+ random: bool = False,
863
+ execute_type: int = 0,
864
+ filters: Optional[list] = None,
865
+ ) -> dict[str, Any]:
866
+ """Save configuration for a SEARCH node (type 7) - get single record.
867
+
868
+ Args:
869
+ session: Active session
870
+ process_id: Process ID
871
+ node_id: Node ID
872
+ action_id: Search type:
873
+ '406'=WORKSHEET_FIND, '407'=BATCH_FIND,
874
+ '420'=RECORD_LINK_FIND, '421'=RECORD_UPDATE, '422'=RECORD_DELETE
875
+ app_id: Target worksheet ID (for WORKSHEET_FIND)
876
+ fields: Return field list
877
+ name: Node name
878
+ select_node_id: Source data node ID
879
+ operate_condition: Query conditions
880
+ sorts: Sort rules
881
+ random: Pick random record
882
+ execute_type: When not found: 0=abort, 1=create, 2=continue
883
+ filters: Filter groups
884
+
885
+ Returns:
886
+ Save result
887
+ """
888
+ config: dict[str, Any] = {"actionId": action_id}
889
+ if app_id:
890
+ config["appId"] = app_id
891
+ if fields:
892
+ config["fields"] = fields
893
+ if select_node_id:
894
+ config["selectNodeId"] = select_node_id
895
+ if operate_condition:
896
+ config["operateCondition"] = operate_condition
897
+ if sorts:
898
+ config["sorts"] = sorts
899
+ if random:
900
+ config["random"] = random
901
+ if execute_type:
902
+ config["executeType"] = execute_type
903
+ if filters:
904
+ config["filters"] = filters
905
+ return save_node(session, process_id, node_id, NODE_TYPE["SEARCH"], config, name=name)
906
+
907
+
908
+ def save_get_more_record_node(
909
+ session: Session,
910
+ process_id: str,
911
+ node_id: str,
912
+ action_id: str,
913
+ app_id: str = "",
914
+ fields: Optional[list] = None,
915
+ name: str = "",
916
+ select_node_id: str = "",
917
+ operate_condition: Optional[list] = None,
918
+ sorts: Optional[list] = None,
919
+ number_field_value: Optional[dict] = None,
920
+ filters: Optional[list] = None,
921
+ random: bool = False,
922
+ ) -> dict[str, Any]:
923
+ """Save configuration for a GET_MORE_RECORD node (type 13).
924
+
925
+ Args:
926
+ session: Active session
927
+ process_id: Process ID
928
+ node_id: Node ID
929
+ action_id: Operation type:
930
+ '400'=FROM_WORKSHEET, '401'=FROM_RECORD, '402'=FROM_ADD,
931
+ '403'=FROM_ARRAY, '404'=FROM_CODE_ARRAY,
932
+ '412'=BATCH_UPDATE, '413'=BATCH_DELETE, '415'=REFRESH_MULTIPLE
933
+ app_id: Target worksheet ID (for FROM_WORKSHEET, BATCH_UPDATE, BATCH_DELETE)
934
+ fields: Field mapping
935
+ name: Node name
936
+ select_node_id: Source node ID (for FROM_RECORD etc.)
937
+ operate_condition: Filter/query conditions
938
+ sorts: Sort rules
939
+ number_field_value: Record count limit config
940
+ filters: Filter groups
941
+ random: Pick random
942
+
943
+ Returns:
944
+ Save result
945
+ """
946
+ config: dict[str, Any] = {"actionId": action_id}
947
+ if app_id:
948
+ config["appId"] = app_id
949
+ if fields:
950
+ config["fields"] = fields
951
+ if select_node_id:
952
+ config["selectNodeId"] = select_node_id
953
+ if operate_condition:
954
+ config["operateCondition"] = operate_condition
955
+ if sorts:
956
+ config["sorts"] = sorts
957
+ if number_field_value:
958
+ config["numberFieldValue"] = number_field_value
959
+ if filters:
960
+ config["filters"] = filters
961
+ if random:
962
+ config["random"] = random
963
+ return save_node(session, process_id, node_id, NODE_TYPE["GET_MORE_RECORD"], config, name=name)
964
+
965
+
966
+ def get_app_template_controls(
967
+ session: Session,
968
+ process_id: str,
969
+ node_id: str,
970
+ app_id: str,
971
+ app_type: int = 1,
972
+ select_node_id: str = "",
973
+ action_id: str = "",
974
+ ) -> dict[str, Any]:
975
+ """Get available worksheet controls/fields for node configuration.
976
+
977
+ Useful for discovering field IDs before configuring ACTION/SEARCH nodes.
978
+
979
+ Args:
980
+ session: Active session
981
+ process_id: Process ID
982
+ node_id: Node ID
983
+ app_id: Worksheet ID
984
+ app_type: App type (1=SHEET)
985
+ select_node_id: Context node
986
+ action_id: Action context
987
+
988
+ Returns:
989
+ Controls/fields list for the worksheet
990
+ """
991
+ data: dict[str, Any] = {
992
+ "processId": process_id,
993
+ "nodeId": node_id,
994
+ "appId": app_id,
995
+ "appType": app_type,
996
+ }
997
+ if select_node_id:
998
+ data["selectNodeId"] = select_node_id
999
+ if action_id:
1000
+ data["actionId"] = action_id
1001
+ return session.workflow_call("flowNode/getAppTemplateControls", data)