save_remote_address.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. class SaveRemoteAddressTool:
  2. name = 'save_remote_address'
  3. route_path = '/mcp/tools/saveRemoteAddress'
  4. backend = 'base'
  5. def __init__(self, api_client=None):
  6. self.api_client = api_client
  7. def metadata(self):
  8. return {
  9. 'name': self.name,
  10. 'description': (
  11. '使用场景:新增地址库,或修改已有地址库的名称和启用状态。'
  12. '新增时不要传编号,必须带名称、状态,以及至少一条地址明细。'
  13. '编辑时必须传query_remote_address_list返回的编号,只修改名称和状态,不能提交明细。'
  14. '没有地址明细的地址库不能保存。'
  15. '状态用1表示启用,用0表示禁用。'
  16. '国家可以填国家二字码或中文名称。发件人国家和收件人国家至少填一个。'
  17. '结束邮编不能单独填写,必须同时有开始邮编。'
  18. '禁止使用:手填不存在的编号、导入文件、删除明细、修改已有明细。'
  19. '参数只用于工具内部调用;最终回答只能展示中文业务名称和地址库编号。'
  20. ),
  21. 'input_schema': {
  22. 'type': 'object',
  23. 'properties': {
  24. 'address_no': {
  25. 'type': 'string',
  26. 'minLength': 1,
  27. 'maxLength': 20,
  28. 'description': '要编辑的地址库编号,格式为PY加数字。新增时不要传。',
  29. },
  30. 'remote_name': {
  31. 'type': 'string',
  32. 'minLength': 1,
  33. 'maxLength': 100,
  34. 'description': '地址库名称。',
  35. },
  36. 'status': {
  37. 'type': 'integer',
  38. 'enum': [0, 1],
  39. 'description': '1启用,0禁用。',
  40. },
  41. 'details': {
  42. 'type': 'array',
  43. 'maxItems': 200,
  44. 'description': '新增时的地址明细。编辑时不要传。',
  45. 'items': {
  46. 'type': 'object',
  47. 'properties': {
  48. 'shipper_country': {'type': 'string', 'maxLength': 50},
  49. 'shipper_province': {'type': 'string', 'maxLength': 20},
  50. 'shipper_city': {'type': 'string', 'maxLength': 20},
  51. 'shipper_start_postcode': {'type': 'string', 'maxLength': 10},
  52. 'shipper_end_postcode': {'type': 'string', 'maxLength': 10},
  53. 'country': {'type': 'string', 'maxLength': 50},
  54. 'province': {'type': 'string', 'maxLength': 20},
  55. 'city': {'type': 'string', 'maxLength': 20},
  56. 'start_postcode': {'type': 'string', 'maxLength': 10},
  57. 'end_postcode': {'type': 'string', 'maxLength': 10},
  58. },
  59. 'additionalProperties': False,
  60. },
  61. },
  62. },
  63. 'required': ['remote_name', 'status'],
  64. 'additionalProperties': False,
  65. },
  66. }
  67. def call(
  68. self,
  69. remote_name,
  70. status,
  71. address_no=None,
  72. details=None,
  73. request_id='rq_save_remote_address',
  74. ):
  75. if self.api_client is None:
  76. raise RuntimeError('api client is required for save_remote_address')
  77. if not isinstance(remote_name, str) or not remote_name.strip() or len(remote_name.strip()) > 100:
  78. raise ValueError('remote_name is invalid')
  79. if isinstance(status, bool) or status not in (0, 1):
  80. raise ValueError('status is invalid')
  81. payload = {
  82. 'remote_name': remote_name.strip(),
  83. 'status': status,
  84. }
  85. if address_no is not None:
  86. if not isinstance(address_no, str):
  87. raise ValueError('address_no is invalid')
  88. address_no = address_no.strip()
  89. if address_no:
  90. if len(address_no) > 20:
  91. raise ValueError('address_no is invalid')
  92. payload['address_no'] = address_no
  93. if details is not None:
  94. payload['details'] = self._details(details)
  95. if payload.get('address_no') and payload.get('details'):
  96. raise ValueError('details is invalid')
  97. if not payload.get('address_no') and not payload.get('details'):
  98. raise ValueError('details is invalid')
  99. return self.api_client.call_tool(
  100. self.name, self.route_path, payload, request_id,
  101. )
  102. @staticmethod
  103. def _details(details):
  104. if not isinstance(details, list) or not details or len(details) > 200:
  105. raise ValueError('details is invalid')
  106. fields = (
  107. 'shipper_country', 'shipper_province', 'shipper_city',
  108. 'shipper_start_postcode', 'shipper_end_postcode',
  109. 'country', 'province', 'city', 'start_postcode', 'end_postcode',
  110. )
  111. postcode_fields = {
  112. 'shipper_start_postcode', 'shipper_end_postcode',
  113. 'start_postcode', 'end_postcode',
  114. }
  115. rows = []
  116. for detail in details:
  117. if not isinstance(detail, dict) or set(detail) - set(fields):
  118. raise ValueError('details is invalid')
  119. row = {}
  120. for field in fields:
  121. if field not in detail:
  122. continue
  123. value = detail[field]
  124. if not isinstance(value, str):
  125. raise ValueError('details is invalid')
  126. value = value.strip()
  127. limit = 10 if field in postcode_fields else (50 if field in ('shipper_country', 'country') else 20)
  128. if len(value) > limit:
  129. raise ValueError('details is invalid')
  130. if field in postcode_fields and value and not _postcode(value):
  131. raise ValueError('details is invalid')
  132. if value:
  133. row[field] = value
  134. if not row.get('shipper_country') and not row.get('country'):
  135. raise ValueError('details is invalid')
  136. rows.append(row)
  137. return rows
  138. def _postcode(value):
  139. for character in value:
  140. if character.isalnum() or character in ' -':
  141. continue
  142. return False
  143. return True