erp-integral-mcp 1.2.0__tar.gz
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.
- erp_integral_mcp-1.2.0/PKG-INFO +88 -0
- erp_integral_mcp-1.2.0/README.md +69 -0
- erp_integral_mcp-1.2.0/pyproject.toml +40 -0
- erp_integral_mcp-1.2.0/setup.cfg +4 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp/__init__.py +3 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp/__main__.py +9 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp/config.py +59 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp/context.py +17 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp/erp/__init__.py +1 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp/erp/auth.py +65 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp/erp/client.py +93 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp/normalize.py +269 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp/person_resolve.py +80 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp/server.py +489 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp.egg-info/PKG-INFO +88 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp.egg-info/SOURCES.txt +21 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp.egg-info/dependency_links.txt +1 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp.egg-info/entry_points.txt +2 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp.egg-info/requires.txt +9 -0
- erp_integral_mcp-1.2.0/src/erp_integral_mcp.egg-info/top_level.txt +1 -0
- erp_integral_mcp-1.2.0/tests/test_auth.py +22 -0
- erp_integral_mcp-1.2.0/tests/test_normalize.py +102 -0
- erp_integral_mcp-1.2.0/tests/test_person_resolve.py +57 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: erp-integral-mcp
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: 鼎尖 ERP 业务动作积分统计 MCP:积分项查询、按日/按积分项汇总
|
|
5
|
+
License: Proprietary
|
|
6
|
+
Project-URL: Homepage, https://open.workbuddy.cn/docs/connector
|
|
7
|
+
Project-URL: Documentation, https://open.workbuddy.cn/docs/connector
|
|
8
|
+
Keywords: mcp,dingjian,erp,integral,settlement,stdio,uvx,workbuddy
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: mcp>=1.2.0
|
|
12
|
+
Requires-Dist: httpx>=0.27.0
|
|
13
|
+
Requires-Dist: pydantic>=2.0
|
|
14
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
15
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
|
|
19
|
+
|
|
20
|
+
# erp-integral-mcp
|
|
21
|
+
|
|
22
|
+
鼎尖 ERP **业务动作积分**统计 MCP:第三方 SHA1 签名鉴权,查询积分项清单,并按**日历日**或**积分项**汇总每位员工的作业积分。
|
|
23
|
+
|
|
24
|
+
## 接口
|
|
25
|
+
|
|
26
|
+
| Tool | ERP API | 说明 |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `search_persons` | `basedata/person/dataPickerData` | 姓名/工号 → personId(F7) |
|
|
29
|
+
| `list_integral_jobsets` | `settlement/integralDetail/getIntegralConditon` | 积分项(jobSet)清单 |
|
|
30
|
+
| `query_integral_stats` | `settlement/integralDetail/integralCountData` | 人员积分;支持 `personId`;`group_by=week` |
|
|
31
|
+
|
|
32
|
+
`columnType`:
|
|
33
|
+
|
|
34
|
+
- `INTEGRALDATE`:按日,`scoreN` = 当月第 N 日积分
|
|
35
|
+
- `INTEGRALJOBSET`:按积分项,字段名为积分项 `id`,需结合 `jobSetList` 解读
|
|
36
|
+
|
|
37
|
+
## 环境变量
|
|
38
|
+
|
|
39
|
+
与 `erp-property-mcp` 相同:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
ERP_BASE_URL=https://your-host/dingjian/
|
|
43
|
+
ERP_SECRET=...
|
|
44
|
+
ERP_CU_ID=...
|
|
45
|
+
ERP_USER_ID=...
|
|
46
|
+
ERP_DC=...
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 本地运行
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd mcp/erp-integral-mcp
|
|
53
|
+
cp .env.example .env # 填入凭证
|
|
54
|
+
python -m venv .venv && source .venv/bin/activate
|
|
55
|
+
pip install -e ".[dev]"
|
|
56
|
+
pytest
|
|
57
|
+
erp-integral-mcp # stdio MCP
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## uvx / WorkBuddy
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"erp_integral": {
|
|
66
|
+
"command": "uvx",
|
|
67
|
+
"args": ["erp-integral-mcp@1.0.0"],
|
|
68
|
+
"env": {
|
|
69
|
+
"ERP_BASE_URL": "${ERP_BASE_URL}",
|
|
70
|
+
"ERP_SECRET": "${ERP_SECRET}",
|
|
71
|
+
"ERP_CU_ID": "${ERP_CU_ID}",
|
|
72
|
+
"ERP_USER_ID": "${ERP_USER_ID}",
|
|
73
|
+
"ERP_DC": "${ERP_DC}"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
连接器打包:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
./scripts/pack-connector.sh
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Skill
|
|
87
|
+
|
|
88
|
+
业务作业 SOP 见 `skills/erp-integral-stats/SKILL.md`(仓库同步副本:`mcp/skills/erp-integral-stats/SKILL.md`)。
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# erp-integral-mcp
|
|
2
|
+
|
|
3
|
+
鼎尖 ERP **业务动作积分**统计 MCP:第三方 SHA1 签名鉴权,查询积分项清单,并按**日历日**或**积分项**汇总每位员工的作业积分。
|
|
4
|
+
|
|
5
|
+
## 接口
|
|
6
|
+
|
|
7
|
+
| Tool | ERP API | 说明 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `search_persons` | `basedata/person/dataPickerData` | 姓名/工号 → personId(F7) |
|
|
10
|
+
| `list_integral_jobsets` | `settlement/integralDetail/getIntegralConditon` | 积分项(jobSet)清单 |
|
|
11
|
+
| `query_integral_stats` | `settlement/integralDetail/integralCountData` | 人员积分;支持 `personId`;`group_by=week` |
|
|
12
|
+
|
|
13
|
+
`columnType`:
|
|
14
|
+
|
|
15
|
+
- `INTEGRALDATE`:按日,`scoreN` = 当月第 N 日积分
|
|
16
|
+
- `INTEGRALJOBSET`:按积分项,字段名为积分项 `id`,需结合 `jobSetList` 解读
|
|
17
|
+
|
|
18
|
+
## 环境变量
|
|
19
|
+
|
|
20
|
+
与 `erp-property-mcp` 相同:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
ERP_BASE_URL=https://your-host/dingjian/
|
|
24
|
+
ERP_SECRET=...
|
|
25
|
+
ERP_CU_ID=...
|
|
26
|
+
ERP_USER_ID=...
|
|
27
|
+
ERP_DC=...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 本地运行
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd mcp/erp-integral-mcp
|
|
34
|
+
cp .env.example .env # 填入凭证
|
|
35
|
+
python -m venv .venv && source .venv/bin/activate
|
|
36
|
+
pip install -e ".[dev]"
|
|
37
|
+
pytest
|
|
38
|
+
erp-integral-mcp # stdio MCP
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## uvx / WorkBuddy
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"erp_integral": {
|
|
47
|
+
"command": "uvx",
|
|
48
|
+
"args": ["erp-integral-mcp@1.0.0"],
|
|
49
|
+
"env": {
|
|
50
|
+
"ERP_BASE_URL": "${ERP_BASE_URL}",
|
|
51
|
+
"ERP_SECRET": "${ERP_SECRET}",
|
|
52
|
+
"ERP_CU_ID": "${ERP_CU_ID}",
|
|
53
|
+
"ERP_USER_ID": "${ERP_USER_ID}",
|
|
54
|
+
"ERP_DC": "${ERP_DC}"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
连接器打包:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
./scripts/pack-connector.sh
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Skill
|
|
68
|
+
|
|
69
|
+
业务作业 SOP 见 `skills/erp-integral-stats/SKILL.md`(仓库同步副本:`mcp/skills/erp-integral-stats/SKILL.md`)。
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "erp-integral-mcp"
|
|
7
|
+
version = "1.2.0"
|
|
8
|
+
description = "鼎尖 ERP 业务动作积分统计 MCP:积分项查询、按日/按积分项汇总"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "Proprietary" }
|
|
12
|
+
keywords = ["mcp", "dingjian", "erp", "integral", "settlement", "stdio", "uvx", "workbuddy"]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"mcp>=1.2.0",
|
|
15
|
+
"httpx>=0.27.0",
|
|
16
|
+
"pydantic>=2.0",
|
|
17
|
+
"pydantic-settings>=2.0",
|
|
18
|
+
"python-dotenv>=1.0.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://open.workbuddy.cn/docs/connector"
|
|
23
|
+
Documentation = "https://open.workbuddy.cn/docs/connector"
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
"pytest>=8.0",
|
|
28
|
+
"pytest-asyncio>=0.24",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
erp-integral-mcp = "erp_integral_mcp.__main__:main"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
asyncio_mode = "auto"
|
|
39
|
+
testpaths = ["tests"]
|
|
40
|
+
pythonpath = ["tests", "src"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
7
|
+
|
|
8
|
+
from erp_integral_mcp.erp.auth import require_config
|
|
9
|
+
|
|
10
|
+
PACKAGE_ROOT = Path(__file__).resolve().parents[2]
|
|
11
|
+
ENV_FILE = PACKAGE_ROOT / ".env"
|
|
12
|
+
|
|
13
|
+
GET_CONDITION_API = "settlement/integralDetail/getIntegralConditon"
|
|
14
|
+
COUNT_DATA_API = "settlement/integralDetail/integralCountData"
|
|
15
|
+
PERSON_PICKER_API = "basedata/person/dataPickerData"
|
|
16
|
+
COLUMN_BY_DATE = "INTEGRALDATE"
|
|
17
|
+
COLUMN_BY_JOBSET = "INTEGRALJOBSET"
|
|
18
|
+
|
|
19
|
+
load_dotenv(ENV_FILE)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Settings(BaseSettings):
|
|
23
|
+
model_config = SettingsConfigDict(
|
|
24
|
+
env_file=str(ENV_FILE),
|
|
25
|
+
env_file_encoding="utf-8",
|
|
26
|
+
extra="ignore",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
erp_base_url: str = Field(alias="ERP_BASE_URL")
|
|
30
|
+
erp_secret: str = Field(alias="ERP_SECRET")
|
|
31
|
+
erp_cu_id: str = Field(alias="ERP_CU_ID")
|
|
32
|
+
erp_user_id: str = Field(alias="ERP_USER_ID")
|
|
33
|
+
erp_dc: str = Field(alias="ERP_DC")
|
|
34
|
+
erp_timeout_seconds: int = Field(default=60, alias="ERP_TIMEOUT_SECONDS")
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def base_url(self) -> str:
|
|
38
|
+
return self.erp_base_url.rstrip("/") + "/"
|
|
39
|
+
|
|
40
|
+
def validate_credentials(self) -> None:
|
|
41
|
+
require_config(
|
|
42
|
+
{
|
|
43
|
+
"ERP_SECRET": self.erp_secret,
|
|
44
|
+
"ERP_CU_ID": self.erp_cu_id,
|
|
45
|
+
"ERP_USER_ID": self.erp_user_id,
|
|
46
|
+
"ERP_DC": self.erp_dc,
|
|
47
|
+
},
|
|
48
|
+
[
|
|
49
|
+
"ERP_SECRET",
|
|
50
|
+
"ERP_CU_ID",
|
|
51
|
+
"ERP_USER_ID",
|
|
52
|
+
"ERP_DC",
|
|
53
|
+
],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@lru_cache
|
|
58
|
+
def get_settings() -> Settings:
|
|
59
|
+
return Settings() # type: ignore[call-arg]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
|
|
5
|
+
from erp_integral_mcp.config import Settings, get_settings
|
|
6
|
+
from erp_integral_mcp.erp.client import ErpClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AppContext:
|
|
10
|
+
def __init__(self, settings: Settings) -> None:
|
|
11
|
+
self.settings = settings
|
|
12
|
+
self.client = ErpClient(settings)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@lru_cache
|
|
16
|
+
def get_context() -> AppContext:
|
|
17
|
+
return AppContext(get_settings())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""ERP 第三方接口客户端。"""
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""鼎尖 ERP 第三方签名鉴权(对齐《第三方接口规范》Java 示例)。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import time
|
|
7
|
+
from typing import Mapping
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_signature(
|
|
11
|
+
*,
|
|
12
|
+
secret: str,
|
|
13
|
+
third_timestamp: str,
|
|
14
|
+
third_cu_id: str,
|
|
15
|
+
third_user_id: str,
|
|
16
|
+
third_dc: str,
|
|
17
|
+
api_path: str,
|
|
18
|
+
) -> str:
|
|
19
|
+
"""将 secret/timestamp/cuId/userId/dc/apiPath 字典序排序后拼接,再做 SHA1 hex。"""
|
|
20
|
+
parts = [
|
|
21
|
+
secret,
|
|
22
|
+
third_timestamp,
|
|
23
|
+
third_cu_id,
|
|
24
|
+
third_user_id,
|
|
25
|
+
third_dc,
|
|
26
|
+
api_path,
|
|
27
|
+
]
|
|
28
|
+
parts.sort()
|
|
29
|
+
return hashlib.sha1("".join(parts).encode("utf-8")).hexdigest()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_auth_headers(
|
|
33
|
+
*,
|
|
34
|
+
secret: str,
|
|
35
|
+
third_cu_id: str,
|
|
36
|
+
third_user_id: str,
|
|
37
|
+
third_dc: str,
|
|
38
|
+
api_path: str,
|
|
39
|
+
timestamp_ms: str | None = None,
|
|
40
|
+
) -> dict[str, str]:
|
|
41
|
+
"""生成第三方开放接口所需请求头。"""
|
|
42
|
+
third_timestamp = timestamp_ms or str(int(time.time() * 1000))
|
|
43
|
+
signature = build_signature(
|
|
44
|
+
secret=secret,
|
|
45
|
+
third_timestamp=third_timestamp,
|
|
46
|
+
third_cu_id=third_cu_id,
|
|
47
|
+
third_user_id=third_user_id,
|
|
48
|
+
third_dc=third_dc,
|
|
49
|
+
api_path=api_path,
|
|
50
|
+
)
|
|
51
|
+
return {
|
|
52
|
+
"thirdtimestamp": third_timestamp,
|
|
53
|
+
"thirdCuId": third_cu_id,
|
|
54
|
+
"thirdUserId": third_user_id,
|
|
55
|
+
"thirdDc": third_dc,
|
|
56
|
+
"djthirdsign": signature,
|
|
57
|
+
"content-type": "application/json;charset=UTF-8",
|
|
58
|
+
"accept": "application/json, text/plain, */*",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def require_config(values: Mapping[str, str | None], keys: list[str]) -> None:
|
|
63
|
+
missing = [key for key in keys if not (values.get(key) or "").strip()]
|
|
64
|
+
if missing:
|
|
65
|
+
raise ValueError("缺少 ERP 第三方凭证,请配置环境变量: " + ", ".join(missing))
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.parse import urljoin
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from erp_integral_mcp.config import (
|
|
11
|
+
COUNT_DATA_API,
|
|
12
|
+
GET_CONDITION_API,
|
|
13
|
+
PERSON_PICKER_API,
|
|
14
|
+
Settings,
|
|
15
|
+
)
|
|
16
|
+
from erp_integral_mcp.erp.auth import build_auth_headers
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ErpClient:
|
|
22
|
+
"""调用鼎尖 ERP 第三方开放接口(签名鉴权,无需 Bearer Token)。"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, settings: Settings) -> None:
|
|
25
|
+
self._settings = settings
|
|
26
|
+
settings.validate_credentials()
|
|
27
|
+
|
|
28
|
+
def _url(self, api_path: str) -> str:
|
|
29
|
+
base = self._settings.base_url
|
|
30
|
+
return urljoin(base, f"{api_path}?djrqformat=pcjson")
|
|
31
|
+
|
|
32
|
+
async def _post(self, api_path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
33
|
+
last_error: Exception | None = None
|
|
34
|
+
for attempt in range(3):
|
|
35
|
+
headers = build_auth_headers(
|
|
36
|
+
secret=self._settings.erp_secret,
|
|
37
|
+
third_cu_id=self._settings.erp_cu_id,
|
|
38
|
+
third_user_id=self._settings.erp_user_id,
|
|
39
|
+
third_dc=self._settings.erp_dc,
|
|
40
|
+
api_path=api_path,
|
|
41
|
+
)
|
|
42
|
+
try:
|
|
43
|
+
async with httpx.AsyncClient(
|
|
44
|
+
timeout=self._settings.erp_timeout_seconds
|
|
45
|
+
) as client:
|
|
46
|
+
resp = await client.post(
|
|
47
|
+
self._url(api_path),
|
|
48
|
+
headers=headers,
|
|
49
|
+
json=body,
|
|
50
|
+
)
|
|
51
|
+
if resp.status_code >= 400:
|
|
52
|
+
raise RuntimeError(
|
|
53
|
+
f"ERP HTTP {resp.status_code} {api_path}: {resp.text[:300]}"
|
|
54
|
+
)
|
|
55
|
+
data = resp.json()
|
|
56
|
+
except Exception as exc: # noqa: BLE001
|
|
57
|
+
last_error = exc
|
|
58
|
+
await asyncio.sleep(0.6 * (attempt + 1))
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
if not isinstance(data, dict):
|
|
62
|
+
raise RuntimeError(f"ERP 返回非 JSON 对象: {type(data)}")
|
|
63
|
+
|
|
64
|
+
if data.get("resultType") in (903, "903"):
|
|
65
|
+
last_error = RuntimeError(str(data.get("resultMsg") or "QPS限流"))
|
|
66
|
+
await asyncio.sleep(0.8 * (attempt + 1))
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
if data.get("resultType") not in (None, 1):
|
|
70
|
+
raise RuntimeError(
|
|
71
|
+
f"ERP 业务失败 resultType={data.get('resultType')} "
|
|
72
|
+
f"resultMsg={data.get('resultMsg')}"
|
|
73
|
+
)
|
|
74
|
+
return data
|
|
75
|
+
|
|
76
|
+
if last_error:
|
|
77
|
+
raise RuntimeError(f"ERP 请求失败: {last_error}") from last_error
|
|
78
|
+
raise RuntimeError("ERP 请求失败")
|
|
79
|
+
|
|
80
|
+
async def get_integral_condition(
|
|
81
|
+
self, *, search_type: str = "integralCount"
|
|
82
|
+
) -> dict[str, Any]:
|
|
83
|
+
return await self._post(
|
|
84
|
+
GET_CONDITION_API,
|
|
85
|
+
{"searchType": search_type},
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
async def integral_count_data(self, body: dict[str, Any]) -> dict[str, Any]:
|
|
89
|
+
return await self._post(COUNT_DATA_API, body)
|
|
90
|
+
|
|
91
|
+
async def search_persons(self, body: dict[str, Any]) -> dict[str, Any]:
|
|
92
|
+
"""人员 F7:basedata/person/dataPickerData(与积分页员工选择器同源)。"""
|
|
93
|
+
return await self._post(PERSON_PICKER_API, body)
|