| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- from datetime import date
- class QueryReceivableCostListTool:
- name = 'query_receivable_cost_list'
- route_path = '/mcp/tools/queryReceivableCostList'
- NUMBER_FIELDS = (
- 'reference_numbers',
- 'tracking_numbers',
- 'order_numbers',
- 'bill_numbers',
- )
- def __init__(self, api_client=None):
- self.api_client = api_client
- def metadata(self):
- number_array = {
- 'type': 'array',
- 'minItems': 1,
- 'maxItems': 200,
- 'items': {
- 'type': 'string',
- 'minLength': 1,
- 'maxLength': 100,
- 'pattern': '.*\\S.*',
- },
- }
- date_field = {
- 'type': 'string',
- 'format': 'date',
- 'pattern': '^\\d{4}-\\d{2}-\\d{2}$',
- }
- return {
- 'name': self.name,
- 'description': (
- '分页查询当前员工有权查看的应收费用列表。仅可按已明确业务类型的参考号、'
- '跟踪号、订单号或账单编号精确查询;禁止根据格式猜测号码类型,禁止跨字段'
- '试查、并行试查或失败后改用其他号码字段。没有明确号码时必须提供不超过31个'
- '日历日的业务日期闭区间。客户、出账状态、核销状态、单据类型和费用项筛选值'
- '必须先调用list_receivable_cost_filter_options选择并使用其返回的value,'
- '不得猜测内部ID。'
- ),
- 'input_schema': {
- 'type': 'object',
- 'properties': {
- 'reference_numbers': dict(number_array),
- 'tracking_numbers': dict(number_array),
- 'order_numbers': dict(number_array),
- 'bill_numbers': dict(number_array),
- 'business_date_start': dict(date_field),
- 'business_date_end': dict(date_field),
- 'customer_id': {'type': 'integer', 'minimum': 1},
- 'sub_customer_id': {'type': 'integer', 'minimum': 1},
- 'billing_status': {'type': 'integer', 'enum': [0, 1]},
- 'verification_status': {
- 'type': 'integer', 'enum': [-1, 0, 1],
- },
- 'document_type': {'type': 'integer', 'minimum': 0},
- 'cost_type_id': {'type': 'integer', 'minimum': 1},
- 'page': {
- 'type': 'integer', 'minimum': 1, 'maximum': 100,
- 'default': 1,
- },
- 'limit': {
- 'type': 'integer', 'minimum': 1, 'maximum': 100,
- 'default': 20,
- },
- },
- 'required': [],
- 'allOf': [
- {
- 'anyOf': [
- {'required': ['reference_numbers']},
- {'required': ['tracking_numbers']},
- {'required': ['order_numbers']},
- {'required': ['bill_numbers']},
- {
- 'required': [
- 'business_date_start',
- 'business_date_end',
- ],
- },
- ],
- },
- {
- 'if': {'required': ['sub_customer_id']},
- 'then': {'required': ['customer_id']},
- },
- ],
- 'additionalProperties': False,
- },
- }
- def call(
- self,
- reference_numbers=None,
- tracking_numbers=None,
- order_numbers=None,
- bill_numbers=None,
- business_date_start=None,
- business_date_end=None,
- customer_id=None,
- sub_customer_id=None,
- billing_status=None,
- verification_status=None,
- document_type=None,
- cost_type_id=None,
- page=1,
- limit=20,
- request_id='rq_query_receivable_cost_list',
- ):
- if self.api_client is None:
- raise RuntimeError(
- 'api client is required for query_receivable_cost_list'
- )
- supplied_numbers = {}
- total_numbers = 0
- for field, values in (
- ('reference_numbers', reference_numbers),
- ('tracking_numbers', tracking_numbers),
- ('order_numbers', order_numbers),
- ('bill_numbers', bill_numbers),
- ):
- if values is not None:
- normalized = self._number_list(values, field)
- supplied_numbers[field] = normalized
- total_numbers += len(normalized)
- if total_numbers > 200:
- raise ValueError('combined exact number count is invalid')
- start = self._optional_date(business_date_start, 'business_date_start')
- end = self._optional_date(business_date_end, 'business_date_end')
- if bool(start) != bool(end):
- raise ValueError('business date range is invalid')
- if start and end:
- span = (date.fromisoformat(end) - date.fromisoformat(start)).days
- if span < 0 or span > 30:
- raise ValueError('business date range is invalid')
- if not supplied_numbers and not (start and end):
- raise ValueError('exact numbers or business date range is required')
- if sub_customer_id is not None and customer_id is None:
- raise ValueError('customer_id is required with sub_customer_id')
- payload = dict(supplied_numbers)
- if start:
- payload['business_date_start'] = start
- payload['business_date_end'] = end
- for field, value, minimum in (
- ('customer_id', customer_id, 1),
- ('sub_customer_id', sub_customer_id, 1),
- ('document_type', document_type, 0),
- ('cost_type_id', cost_type_id, 1),
- ):
- if value is not None:
- payload[field] = self._integer(value, field, minimum)
- for field, value, allowed in (
- ('billing_status', billing_status, (0, 1)),
- ('verification_status', verification_status, (-1, 0, 1)),
- ):
- if value is not None:
- value = self._integer(value, field)
- if value not in allowed:
- raise ValueError('{0} is invalid'.format(field))
- payload[field] = value
- payload['page'] = self._bounded_integer(page, 'page')
- payload['limit'] = self._bounded_integer(limit, 'limit')
- return self.api_client.call_tool(
- self.name, self.route_path, payload, request_id
- )
- @staticmethod
- def _number_list(values, field):
- if not isinstance(values, list) or not 1 <= len(values) <= 200:
- raise ValueError('{0} is invalid'.format(field))
- result = []
- for value in values:
- if not isinstance(value, str):
- raise ValueError('{0} is invalid'.format(field))
- value = value.strip()
- if not value or len(value) > 100:
- raise ValueError('{0} is invalid'.format(field))
- result.append(value)
- return result
- @staticmethod
- def _optional_date(value, field):
- if value is None or value == '':
- return ''
- if not isinstance(value, str):
- raise ValueError('{0} is invalid'.format(field))
- try:
- parsed = date.fromisoformat(value)
- except ValueError:
- raise ValueError('{0} is invalid'.format(field))
- if parsed.isoformat() != value:
- raise ValueError('{0} is invalid'.format(field))
- return value
- @staticmethod
- def _integer(value, field, minimum=None):
- if isinstance(value, bool) or not isinstance(value, int):
- raise ValueError('{0} is invalid'.format(field))
- if minimum is not None and value < minimum:
- raise ValueError('{0} is invalid'.format(field))
- return value
- @classmethod
- def _bounded_integer(cls, value, field):
- value = cls._integer(value, field, 1)
- if value > 100:
- raise ValueError('{0} is invalid'.format(field))
- return value
|