@qingflow-tech/qingflow-app-builder-mcp 1.0.9 → 1.0.10

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.
@@ -3757,13 +3757,44 @@ class RecordTools(ToolBase):
3757
3757
  """执行内部辅助逻辑。"""
3758
3758
  summary = self._record_update_batch_summary(responses)
3759
3759
  batch_items = [self._record_update_batch_item_from_response(response, output_profile=output_profile) for response in responses]
3760
+ public_items = [self._record_update_public_batch_item(item, index=index) for index, item in enumerate(batch_items)]
3760
3761
  status, ok, message = self._record_update_batch_envelope_status(summary=summary, dry_run=dry_run)
3761
3762
  first_response = responses[0] if responses else {}
3763
+ applied_count = int(summary.get("applied_count") or 0)
3764
+ ready_count = int(summary.get("ready_count") or 0)
3765
+ verified_count = int(summary.get("verified_count") or 0)
3766
+ field_level_verified_count = int(summary.get("field_level_verified_count") or 0)
3767
+ confirmation_count = int(summary.get("confirmation_count") or 0)
3768
+ blocked_count = int(summary.get("blocked_count") or 0)
3769
+ failed_count = int(summary.get("failed_count") or 0)
3770
+ write_executed = applied_count > 0
3771
+ verification_status = "not_requested"
3772
+ if write_executed:
3773
+ verification_status = "verified" if verified_count == applied_count else "failed"
3774
+ updated_record_ids = [
3775
+ str(item.get("record_id"))
3776
+ for item in public_items
3777
+ if item.get("record_id") not in (None, "") and str(item.get("status") or "").lower() == "success"
3778
+ ]
3762
3779
  return {
3763
3780
  "profile": first_response.get("profile", profile),
3764
3781
  "ws_id": first_response.get("ws_id"),
3765
3782
  "ok": ok,
3766
3783
  "status": status,
3784
+ "mode": "batch",
3785
+ "dry_run": dry_run,
3786
+ "app_key": app_key,
3787
+ "total": int(summary.get("total") or 0),
3788
+ "succeeded": ready_count if dry_run else applied_count,
3789
+ "failed": blocked_count + failed_count,
3790
+ "needs_confirmation": confirmation_count,
3791
+ "updated_record_ids": updated_record_ids,
3792
+ "write_executed": write_executed,
3793
+ "safe_to_retry": not write_executed,
3794
+ "verification_status": verification_status,
3795
+ "field_level_verified_count": field_level_verified_count,
3796
+ "summary": summary,
3797
+ "items": public_items,
3767
3798
  "request_route": first_response.get("request_route"),
3768
3799
  "warnings": [],
3769
3800
  "output_profile": output_profile,
@@ -3777,6 +3808,31 @@ class RecordTools(ToolBase):
3777
3808
  "message": message,
3778
3809
  }
3779
3810
 
3811
+ def _record_update_public_batch_item(self, item: JSONObject, *, index: int) -> JSONObject:
3812
+ """执行内部辅助逻辑。"""
3813
+ public = dict(item)
3814
+ public.setdefault("index", index)
3815
+ public.setdefault("row_number", index + 1)
3816
+ resource = public.get("resource")
3817
+ if isinstance(resource, dict):
3818
+ record_id = resource.get("record_id")
3819
+ apply_id = resource.get("apply_id")
3820
+ if record_id not in (None, ""):
3821
+ public["record_id"] = str(record_id)
3822
+ if apply_id not in (None, ""):
3823
+ public["apply_id"] = str(apply_id)
3824
+ status = str(public.get("status") or "").lower()
3825
+ verification = public.get("verification")
3826
+ if isinstance(verification, dict):
3827
+ if bool(verification.get("verified")):
3828
+ public.setdefault("verification_status", "verified")
3829
+ elif status == "success":
3830
+ public.setdefault("verification_status", "failed")
3831
+ public.setdefault("write_executed", status == "success")
3832
+ public.setdefault("safe_to_retry", not bool(public.get("write_executed")))
3833
+ public.setdefault("verification_status", "not_requested")
3834
+ return public
3835
+
3780
3836
  def _record_update_batch_summary(self, responses: list[JSONObject]) -> JSONObject:
3781
3837
  """执行内部辅助逻辑。"""
3782
3838
  summary: JSONObject = {
@@ -4702,6 +4758,11 @@ class RecordTools(ToolBase):
4702
4758
  delete_ids = [normalize_positive_id_int(record_id, field_name="record_id")]
4703
4759
  if not delete_ids:
4704
4760
  raise_tool_error(QingflowApiError.config_error("record_id or record_ids is required"))
4761
+ seen_delete_ids: set[int] = set()
4762
+ for item in delete_ids:
4763
+ if item in seen_delete_ids:
4764
+ raise_tool_error(QingflowApiError.config_error(f"duplicate record id in delete payload: {stringify_backend_id(item)}"))
4765
+ seen_delete_ids.add(item)
4705
4766
  normalized_payload = {
4706
4767
  "operation": "delete",
4707
4768
  "record_id": stringify_backend_id(record_id) if record_id is not None else None,
@@ -4709,16 +4770,134 @@ class RecordTools(ToolBase):
4709
4770
  "answers": [],
4710
4771
  "submit_type": 1,
4711
4772
  }
4712
- raw_apply = self._record_delete_many(profile=profile, app_key=app_key, record_ids=delete_ids)
4713
- return self._record_write_apply_response(
4714
- raw_apply,
4715
- operation="delete",
4773
+ return self._record_delete_public_batch(
4774
+ profile=profile,
4775
+ app_key=app_key,
4776
+ delete_ids=delete_ids,
4716
4777
  normalized_payload=normalized_payload,
4717
4778
  output_profile=normalized_output_profile,
4718
- human_review=True,
4719
- preflight=None,
4720
4779
  )
4721
4780
 
4781
+ def _record_delete_public_batch(
4782
+ self,
4783
+ *,
4784
+ profile: str,
4785
+ app_key: str,
4786
+ delete_ids: list[int],
4787
+ normalized_payload: JSONObject,
4788
+ output_profile: str,
4789
+ ) -> JSONObject:
4790
+ items: list[JSONObject] = []
4791
+ request_route: JSONObject | None = None
4792
+ ws_id: object = None
4793
+ for index, delete_id in enumerate(delete_ids):
4794
+ record_id_text = stringify_backend_id(delete_id)
4795
+ try:
4796
+ raw_apply = self._record_delete_many(profile=profile, app_key=app_key, record_ids=[delete_id])
4797
+ request_route = cast(JSONObject, raw_apply.get("request_route")) if isinstance(raw_apply.get("request_route"), dict) else request_route
4798
+ ws_id = raw_apply.get("ws_id", ws_id)
4799
+ single_payload = {
4800
+ "operation": "delete",
4801
+ "record_id": record_id_text,
4802
+ "record_ids": [record_id_text],
4803
+ "answers": [],
4804
+ "submit_type": 1,
4805
+ }
4806
+ single_response = self._record_write_apply_response(
4807
+ raw_apply,
4808
+ operation="delete",
4809
+ normalized_payload=single_payload,
4810
+ output_profile=output_profile,
4811
+ human_review=True,
4812
+ preflight=None,
4813
+ )
4814
+ item_status = str(single_response.get("status") or "success")
4815
+ item: JSONObject = {
4816
+ "index": index,
4817
+ "row_number": index + 1,
4818
+ "record_id": record_id_text,
4819
+ "status": item_status,
4820
+ "write_executed": bool(single_response.get("write_executed")),
4821
+ "verification_status": single_response.get("verification_status", "not_requested"),
4822
+ "safe_to_retry": bool(single_response.get("safe_to_retry", False)),
4823
+ }
4824
+ if item_status != "success":
4825
+ item["error"] = (single_response.get("data") or {}).get("error") if isinstance(single_response.get("data"), dict) else None
4826
+ items.append(item)
4827
+ except (QingflowApiError, RuntimeError) as exc:
4828
+ error_response = self._record_write_exception_response(
4829
+ exc,
4830
+ operation="delete",
4831
+ profile=profile,
4832
+ app_key=app_key,
4833
+ record_id=record_id_text,
4834
+ output_profile=output_profile,
4835
+ human_review=True,
4836
+ write_executed=False,
4837
+ )
4838
+ request_route = cast(JSONObject, error_response.get("request_route")) if isinstance(error_response.get("request_route"), dict) else request_route
4839
+ item = {
4840
+ "index": index,
4841
+ "row_number": index + 1,
4842
+ "record_id": record_id_text,
4843
+ "status": "failed",
4844
+ "write_executed": False,
4845
+ "verification_status": "not_requested",
4846
+ "safe_to_retry": True,
4847
+ "error": (error_response.get("data") or {}).get("error") if isinstance(error_response.get("data"), dict) else {"message": str(exc)},
4848
+ }
4849
+ items.append(item)
4850
+ deleted_ids = [
4851
+ str(item["record_id"])
4852
+ for item in items
4853
+ if str(item.get("status") or "") == "success"
4854
+ ]
4855
+ failed_ids = [
4856
+ str(item["record_id"])
4857
+ for item in items
4858
+ if str(item.get("status") or "") != "success"
4859
+ ]
4860
+ total = len(items)
4861
+ succeeded = len(deleted_ids)
4862
+ failed = len(failed_ids)
4863
+ if succeeded and failed:
4864
+ status = "partial_success"
4865
+ ok = False
4866
+ elif succeeded:
4867
+ status = "success"
4868
+ ok = True
4869
+ else:
4870
+ status = "failed"
4871
+ ok = False
4872
+ write_executed = any(bool(item.get("write_executed")) for item in items)
4873
+ return {
4874
+ "profile": profile,
4875
+ "ws_id": ws_id,
4876
+ "ok": ok,
4877
+ "status": status,
4878
+ "mode": "batch",
4879
+ "total": total,
4880
+ "succeeded": succeeded,
4881
+ "failed": failed,
4882
+ "deleted_ids": deleted_ids,
4883
+ "failed_ids": failed_ids,
4884
+ "write_executed": write_executed,
4885
+ "verification_status": "not_requested",
4886
+ "safe_to_retry": False if write_executed else True,
4887
+ "request_route": request_route,
4888
+ "warnings": [],
4889
+ "output_profile": output_profile,
4890
+ "items": items,
4891
+ "data": {
4892
+ "action": {"operation": "delete", "executed": write_executed},
4893
+ "resource": {"type": "record", "app_key": app_key, "record_id": None, "record_ids": [stringify_backend_id(item) for item in delete_ids]},
4894
+ "normalized_payload": normalized_payload,
4895
+ "deleted_ids": deleted_ids,
4896
+ "failed_ids": failed_ids,
4897
+ "items": items,
4898
+ },
4899
+ }
4900
+
4722
4901
  @tool_cn_name("写入记录")
4723
4902
  def record_write(
4724
4903
  self,
@@ -7277,6 +7456,7 @@ class RecordTools(ToolBase):
7277
7456
  field_index_override=index,
7278
7457
  )
7279
7458
  except RecordInputError as error:
7459
+ normalized_answers = list(lookup_resolution.normalized_answers)
7280
7460
  invalid_fields.append(
7281
7461
  {
7282
7462
  "location": _stringify_json(error.details.get("location") if error.details else None),
@@ -18780,6 +18960,15 @@ def _write_format_for_field(field: FormField) -> JSONObject:
18780
18960
  return _write_support_payload(support_level="full", kind="boolean_label", examples=["是", "否"])
18781
18961
  if field.que_type in DATE_QUE_TYPES:
18782
18962
  return _write_support_payload(support_level="full", kind="date_string", examples=["2026-03-13 10:00:00"])
18963
+ if field.que_type == 8:
18964
+ allow_decimal = bool((field.raw or {}).get("canDecimal"))
18965
+ payload = _write_support_payload(
18966
+ support_level="full",
18967
+ kind="amount_number",
18968
+ examples=[100.5 if allow_decimal else 100],
18969
+ )
18970
+ payload["allow_decimal"] = allow_decimal
18971
+ return payload
18783
18972
  return _write_support_payload(support_level="full", kind="scalar_text")
18784
18973
 
18785
18974
 
@@ -18805,6 +18994,11 @@ def _ready_schema_format_hint(kind: str, write_format: JSONObject) -> str:
18805
18994
  if kind == "date":
18806
18995
  return "推荐传 'YYYY-MM-DD HH:MM:SS';只有日期时可传 'YYYY-MM-DD'。"
18807
18996
  if kind == "number":
18997
+ write_kind = _normalize_optional_text(write_format.get("kind"))
18998
+ if write_kind == "amount_number":
18999
+ if bool(write_format.get("allow_decimal")):
19000
+ return "传数字或数字字符串,支持小数。"
19001
+ return "传整数或整数字符串;该字段后端不接受小数。"
18808
19002
  return "传数字或数字字符串。"
18809
19003
  if kind == "unsupported":
18810
19004
  reason = _normalize_optional_text(write_format.get("reason"))