bocai-cli-core 0.2.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.
- bocai_cli/__init__.py +3 -0
- bocai_cli/api/__init__.py +29 -0
- bocai_cli/api/auth_client.py +57 -0
- bocai_cli/api/card_client.py +222 -0
- bocai_cli/api/center_client.py +55 -0
- bocai_cli/api/checkin_client.py +29 -0
- bocai_cli/api/course_client.py +85 -0
- bocai_cli/api/facade.py +40 -0
- bocai_cli/api/guanyuan_client.py +50 -0
- bocai_cli/api/member_client.py +97 -0
- bocai_cli/api/month_dashboard_client.py +130 -0
- bocai_cli/api/qywx_client.py +28 -0
- bocai_cli/api/report_client.py +41 -0
- bocai_cli/api/reservation_client.py +29 -0
- bocai_cli/api/transport.py +166 -0
- bocai_cli/app.py +12 -0
- bocai_cli/client.py +69 -0
- bocai_cli/command_utils.py +28 -0
- bocai_cli/commands/__init__.py +8 -0
- bocai_cli/commands/auth.py +211 -0
- bocai_cli/commands/card.py +653 -0
- bocai_cli/commands/center.py +138 -0
- bocai_cli/commands/checkin.py +71 -0
- bocai_cli/commands/course.py +260 -0
- bocai_cli/commands/customreport.py +77 -0
- bocai_cli/commands/guanyuan.py +94 -0
- bocai_cli/commands/member.py +288 -0
- bocai_cli/commands/month_dashboard.py +235 -0
- bocai_cli/commands/qywx.py +112 -0
- bocai_cli/commands/registry.py +70 -0
- bocai_cli/commands/report.py +93 -0
- bocai_cli/commands/reservation.py +71 -0
- bocai_cli/config.py +121 -0
- bocai_cli/date_range_validation.py +65 -0
- bocai_cli/errors.py +22 -0
- bocai_cli/main.py +8 -0
- bocai_cli/output.py +28 -0
- bocai_cli/product.py +10 -0
- bocai_cli_core-0.2.0.dist-info/METADATA +138 -0
- bocai_cli_core-0.2.0.dist-info/RECORD +42 -0
- bocai_cli_core-0.2.0.dist-info/WHEEL +4 -0
- bocai_cli_core-0.2.0.dist-info/licenses/LICENSE +19 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from bocai_cli.api import ApiFacade
|
|
6
|
+
from bocai_cli.command_utils import command_handler
|
|
7
|
+
from bocai_cli.config import load_config
|
|
8
|
+
from bocai_cli.output import print_json
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="门店相关命令。", invoke_without_command=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.callback()
|
|
14
|
+
def center_root(ctx: typer.Context) -> None:
|
|
15
|
+
if ctx.invoked_subcommand is None:
|
|
16
|
+
typer.echo(ctx.get_help())
|
|
17
|
+
raise typer.Exit(code=0)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _build_api() -> ApiFacade:
|
|
21
|
+
config = load_config()
|
|
22
|
+
return ApiFacade(
|
|
23
|
+
base_url=config.base_url,
|
|
24
|
+
token=config.token,
|
|
25
|
+
timeout_seconds=config.timeout_seconds,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command("list", help="获取门店列表")
|
|
30
|
+
@command_handler
|
|
31
|
+
def list_centers() -> None:
|
|
32
|
+
api = _build_api()
|
|
33
|
+
data = api.center.list()
|
|
34
|
+
print_json({"status": "ok", "data": data})
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.command("manager-phone", help="获取场馆店长手机号列表")
|
|
38
|
+
@command_handler
|
|
39
|
+
def manager_phone(
|
|
40
|
+
center_id: str = typer.Option(
|
|
41
|
+
...,
|
|
42
|
+
"--center-id",
|
|
43
|
+
help="门店ID。",
|
|
44
|
+
),
|
|
45
|
+
) -> None:
|
|
46
|
+
api = _build_api()
|
|
47
|
+
data = api.center.manager_phone(center_id=center_id)
|
|
48
|
+
print_json({"status": "ok", "data": data})
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@command_handler
|
|
52
|
+
def card_list(
|
|
53
|
+
center_id: str = typer.Option(
|
|
54
|
+
...,
|
|
55
|
+
"--center-id",
|
|
56
|
+
help="门店ID。",
|
|
57
|
+
),
|
|
58
|
+
type_: int = typer.Option(
|
|
59
|
+
...,
|
|
60
|
+
"--type",
|
|
61
|
+
min=0,
|
|
62
|
+
max=2,
|
|
63
|
+
help="卡种类型:0全部,1会员卡,2课程卡。",
|
|
64
|
+
),
|
|
65
|
+
) -> None:
|
|
66
|
+
api = _build_api()
|
|
67
|
+
data = api.center.card_list(center_id=center_id, type_=type_)
|
|
68
|
+
print_json({"status": "ok", "data": data})
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@command_handler
|
|
72
|
+
def source_list(
|
|
73
|
+
center_id: str = typer.Option(
|
|
74
|
+
...,
|
|
75
|
+
"--center-id",
|
|
76
|
+
help="门店ID。",
|
|
77
|
+
),
|
|
78
|
+
) -> None:
|
|
79
|
+
api = _build_api()
|
|
80
|
+
data = api.center.source_list(center_id=center_id)
|
|
81
|
+
print_json({"status": "ok", "data": data})
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@command_handler
|
|
85
|
+
def studio_seller_list(
|
|
86
|
+
center_id: str = typer.Option(
|
|
87
|
+
...,
|
|
88
|
+
"--center-id",
|
|
89
|
+
help="门店ID。",
|
|
90
|
+
),
|
|
91
|
+
) -> None:
|
|
92
|
+
api = _build_api()
|
|
93
|
+
data = api.center.studio_seller_list(center_id=center_id)
|
|
94
|
+
print_json({"status": "ok", "data": data})
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@command_handler
|
|
98
|
+
def studio_trainer_list(
|
|
99
|
+
center_id: str = typer.Option(
|
|
100
|
+
...,
|
|
101
|
+
"--center-id",
|
|
102
|
+
help="门店ID。",
|
|
103
|
+
),
|
|
104
|
+
) -> None:
|
|
105
|
+
api = _build_api()
|
|
106
|
+
data = api.center.studio_trainer_list(center_id=center_id)
|
|
107
|
+
print_json({"status": "ok", "data": data})
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@command_handler
|
|
111
|
+
def tag_list(
|
|
112
|
+
center_id: str = typer.Option(
|
|
113
|
+
...,
|
|
114
|
+
"--center-id",
|
|
115
|
+
help="门店ID。",
|
|
116
|
+
),
|
|
117
|
+
type_: int = typer.Option(
|
|
118
|
+
...,
|
|
119
|
+
"--type",
|
|
120
|
+
min=1,
|
|
121
|
+
max=3,
|
|
122
|
+
help="标签类型:1会员标签,2准会员标签,3会员卡标签。",
|
|
123
|
+
),
|
|
124
|
+
) -> None:
|
|
125
|
+
api = _build_api()
|
|
126
|
+
data = api.center.tag_list(center_id=center_id, type_=type_)
|
|
127
|
+
print_json({"status": "ok", "data": data})
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
studio_app = typer.Typer(help="门店相关命令。", invoke_without_command=True)
|
|
131
|
+
studio_app.callback()(center_root)
|
|
132
|
+
studio_app.command("list", help="获取门店列表")(list_centers)
|
|
133
|
+
studio_app.command("manager-phone", help="获取场馆店长手机号列表")(manager_phone)
|
|
134
|
+
studio_app.command("card-list", help="获取门店卡种列表")(card_list)
|
|
135
|
+
studio_app.command("source-list", help="获取会员来源列表")(source_list)
|
|
136
|
+
studio_app.command("seller-list", help="获取跟进人/销售人列表")(studio_seller_list)
|
|
137
|
+
studio_app.command("trainer-list", help="获取教练列表")(studio_trainer_list)
|
|
138
|
+
studio_app.command("tag-list", help="获取标签列表")(tag_list)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from bocai_cli.api import ApiFacade
|
|
6
|
+
from bocai_cli.command_utils import command_handler
|
|
7
|
+
from bocai_cli.config import load_config
|
|
8
|
+
from bocai_cli.date_range_validation import validate_date_range
|
|
9
|
+
from bocai_cli.output import print_json
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(help="签到相关命令。", invoke_without_command=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.callback()
|
|
15
|
+
def checkin_root(ctx: typer.Context) -> None:
|
|
16
|
+
if ctx.invoked_subcommand is None:
|
|
17
|
+
typer.echo(ctx.get_help())
|
|
18
|
+
raise typer.Exit(code=0)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _build_api() -> ApiFacade:
|
|
22
|
+
config = load_config()
|
|
23
|
+
return ApiFacade(
|
|
24
|
+
base_url=config.base_url,
|
|
25
|
+
token=config.token,
|
|
26
|
+
timeout_seconds=config.timeout_seconds,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command("list", help="获取签到记录数据")
|
|
31
|
+
@command_handler
|
|
32
|
+
def list_checkin(
|
|
33
|
+
start_date: str = typer.Option(
|
|
34
|
+
...,
|
|
35
|
+
"--start-date",
|
|
36
|
+
help="开始日期,格式 YYYY-MM-DD。",
|
|
37
|
+
),
|
|
38
|
+
end_date: str = typer.Option(
|
|
39
|
+
...,
|
|
40
|
+
"--end-date",
|
|
41
|
+
help="结束日期,格式 YYYY-MM-DD。",
|
|
42
|
+
),
|
|
43
|
+
page_num: int = typer.Option(
|
|
44
|
+
...,
|
|
45
|
+
"--page-num",
|
|
46
|
+
min=0,
|
|
47
|
+
help="页码,必须大于等于 0。",
|
|
48
|
+
),
|
|
49
|
+
page_size: int = typer.Option(
|
|
50
|
+
...,
|
|
51
|
+
"--page-size",
|
|
52
|
+
min=1,
|
|
53
|
+
help="每页条数,必须大于等于 1。",
|
|
54
|
+
),
|
|
55
|
+
center_id: str = typer.Option(
|
|
56
|
+
...,
|
|
57
|
+
"--center-id",
|
|
58
|
+
help="门店ID。",
|
|
59
|
+
),
|
|
60
|
+
) -> None:
|
|
61
|
+
validate_date_range(start_date, end_date)
|
|
62
|
+
|
|
63
|
+
api = _build_api()
|
|
64
|
+
data = api.checkin.list(
|
|
65
|
+
start_date=start_date,
|
|
66
|
+
end_date=end_date,
|
|
67
|
+
page_num=page_num,
|
|
68
|
+
page_size=page_size,
|
|
69
|
+
center_id=center_id,
|
|
70
|
+
)
|
|
71
|
+
print_json({"status": "ok", "data": data})
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from bocai_cli.api import ApiFacade
|
|
10
|
+
from bocai_cli.command_utils import command_handler
|
|
11
|
+
from bocai_cli.config import load_config
|
|
12
|
+
from bocai_cli.date_range_validation import validate_date_range
|
|
13
|
+
from bocai_cli.errors import ConfigValidationError
|
|
14
|
+
from bocai_cli.output import print_json
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(help="课程相关命令。", invoke_without_command=True)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.callback()
|
|
20
|
+
def course_root(ctx: typer.Context) -> None:
|
|
21
|
+
if ctx.invoked_subcommand is None:
|
|
22
|
+
typer.echo(ctx.get_help())
|
|
23
|
+
raise typer.Exit(code=0)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _build_api() -> ApiFacade:
|
|
27
|
+
config = load_config()
|
|
28
|
+
return ApiFacade(
|
|
29
|
+
base_url=config.base_url,
|
|
30
|
+
token=config.token,
|
|
31
|
+
timeout_seconds=config.timeout_seconds,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _parse_transaction_ids(raw_transaction_ids: str) -> list[str]:
|
|
36
|
+
values = [item.strip() for item in raw_transaction_ids.split(",")]
|
|
37
|
+
non_empty_values = [item for item in values if item]
|
|
38
|
+
|
|
39
|
+
unique_values: list[str] = []
|
|
40
|
+
seen: set[str] = set()
|
|
41
|
+
for value in non_empty_values:
|
|
42
|
+
if value not in seen:
|
|
43
|
+
seen.add(value)
|
|
44
|
+
unique_values.append(value)
|
|
45
|
+
|
|
46
|
+
if not unique_values:
|
|
47
|
+
raise ConfigValidationError("--transaction-ids 至少要包含一个有效ID。")
|
|
48
|
+
|
|
49
|
+
return unique_values
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _parse_performance_update_payload(raw_payload: str) -> list[dict[str, Any]]:
|
|
53
|
+
try:
|
|
54
|
+
payload = json.loads(raw_payload)
|
|
55
|
+
except json.JSONDecodeError as exc:
|
|
56
|
+
raise ConfigValidationError("--payload 必须是合法 JSON。") from exc
|
|
57
|
+
|
|
58
|
+
if not isinstance(payload, list):
|
|
59
|
+
raise ConfigValidationError("--payload 必须是数组。")
|
|
60
|
+
|
|
61
|
+
if not payload:
|
|
62
|
+
raise ConfigValidationError("--payload 至少要包含一条交易记录。")
|
|
63
|
+
|
|
64
|
+
for index, item in enumerate(payload, start=1):
|
|
65
|
+
if not isinstance(item, dict):
|
|
66
|
+
raise ConfigValidationError(f"--payload 第 {index} 项必须是对象。")
|
|
67
|
+
if "transactionId" not in item:
|
|
68
|
+
raise ConfigValidationError(f"--payload 第 {index} 项缺少 transactionId。")
|
|
69
|
+
if "performance" not in item:
|
|
70
|
+
raise ConfigValidationError(f"--payload 第 {index} 项缺少 performance。")
|
|
71
|
+
|
|
72
|
+
return payload
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _parse_performance_new_payload(raw_payload: str) -> dict[str, Any]:
|
|
76
|
+
try:
|
|
77
|
+
payload = json.loads(raw_payload)
|
|
78
|
+
except json.JSONDecodeError as exc:
|
|
79
|
+
raise ConfigValidationError("--payload 必须是合法 JSON。") from exc
|
|
80
|
+
|
|
81
|
+
if not isinstance(payload, dict):
|
|
82
|
+
raise ConfigValidationError("--payload 必须是对象。")
|
|
83
|
+
|
|
84
|
+
if "data" not in payload:
|
|
85
|
+
raise ConfigValidationError("--payload 缺少 data。")
|
|
86
|
+
|
|
87
|
+
data = payload["data"]
|
|
88
|
+
if not isinstance(data, list):
|
|
89
|
+
raise ConfigValidationError("--payload.data 必须是数组。")
|
|
90
|
+
|
|
91
|
+
if not data:
|
|
92
|
+
raise ConfigValidationError("--payload.data 至少要包含一条合同记录。")
|
|
93
|
+
|
|
94
|
+
required_fields = (
|
|
95
|
+
"seq",
|
|
96
|
+
"date",
|
|
97
|
+
"activateNow",
|
|
98
|
+
"phone",
|
|
99
|
+
"name",
|
|
100
|
+
"courseName",
|
|
101
|
+
"contractNum",
|
|
102
|
+
"count",
|
|
103
|
+
"payAmount",
|
|
104
|
+
"payMethod",
|
|
105
|
+
"performanceAmount",
|
|
106
|
+
"performance",
|
|
107
|
+
"saleType",
|
|
108
|
+
)
|
|
109
|
+
for index, item in enumerate(data, start=1):
|
|
110
|
+
if not isinstance(item, dict):
|
|
111
|
+
raise ConfigValidationError(f"--payload.data 第 {index} 项必须是对象。")
|
|
112
|
+
for field in required_fields:
|
|
113
|
+
if field not in item:
|
|
114
|
+
raise ConfigValidationError(f"--payload.data 第 {index} 项缺少 {field}。")
|
|
115
|
+
|
|
116
|
+
return payload
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@app.command("sale-performance", help="获取售课业绩数据")
|
|
120
|
+
@command_handler
|
|
121
|
+
def sale_performance(
|
|
122
|
+
start_date: str = typer.Option(
|
|
123
|
+
...,
|
|
124
|
+
"--start-date",
|
|
125
|
+
help="开始日期,格式 YYYY-MM-DD。",
|
|
126
|
+
),
|
|
127
|
+
end_date: str = typer.Option(
|
|
128
|
+
...,
|
|
129
|
+
"--end-date",
|
|
130
|
+
help="结束日期,格式 YYYY-MM-DD。",
|
|
131
|
+
),
|
|
132
|
+
page_num: int = typer.Option(
|
|
133
|
+
...,
|
|
134
|
+
"--page-num",
|
|
135
|
+
min=0,
|
|
136
|
+
help="页码,必须大于等于 0。",
|
|
137
|
+
),
|
|
138
|
+
page_size: int = typer.Option(
|
|
139
|
+
...,
|
|
140
|
+
"--page-size",
|
|
141
|
+
min=1,
|
|
142
|
+
help="每页条数,必须大于等于 1。",
|
|
143
|
+
),
|
|
144
|
+
type_: int = typer.Option(
|
|
145
|
+
...,
|
|
146
|
+
"--type",
|
|
147
|
+
min=1,
|
|
148
|
+
max=2,
|
|
149
|
+
help="课程类型:1私教课,2培训课。",
|
|
150
|
+
),
|
|
151
|
+
center_id: str = typer.Option(
|
|
152
|
+
...,
|
|
153
|
+
"--center-id",
|
|
154
|
+
help="门店ID。",
|
|
155
|
+
),
|
|
156
|
+
) -> None:
|
|
157
|
+
validate_date_range(start_date, end_date)
|
|
158
|
+
|
|
159
|
+
api = _build_api()
|
|
160
|
+
data = api.course.sale_performance(
|
|
161
|
+
start_date=start_date,
|
|
162
|
+
end_date=end_date,
|
|
163
|
+
page_num=page_num,
|
|
164
|
+
page_size=page_size,
|
|
165
|
+
type_=type_,
|
|
166
|
+
center_id=center_id,
|
|
167
|
+
)
|
|
168
|
+
print_json({"status": "ok", "data": data})
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@app.command("sale-performance-clear", help="清除售课业绩数据")
|
|
172
|
+
@command_handler
|
|
173
|
+
def sale_performance_clear(
|
|
174
|
+
transaction_ids: str = typer.Option(
|
|
175
|
+
...,
|
|
176
|
+
"--transaction-ids",
|
|
177
|
+
help="交易ID列表,逗号分隔。",
|
|
178
|
+
),
|
|
179
|
+
phone: str = typer.Option(
|
|
180
|
+
...,
|
|
181
|
+
"--phone",
|
|
182
|
+
help="不分业绩员工手机号。",
|
|
183
|
+
),
|
|
184
|
+
center_id: str = typer.Option(
|
|
185
|
+
...,
|
|
186
|
+
"--center-id",
|
|
187
|
+
help="门店ID。",
|
|
188
|
+
),
|
|
189
|
+
) -> None:
|
|
190
|
+
parsed_transaction_ids = _parse_transaction_ids(transaction_ids)
|
|
191
|
+
|
|
192
|
+
api = _build_api()
|
|
193
|
+
data = api.course.performance_clear(
|
|
194
|
+
transaction_ids=parsed_transaction_ids,
|
|
195
|
+
phone=phone,
|
|
196
|
+
center_id=center_id,
|
|
197
|
+
)
|
|
198
|
+
print_json({"status": "ok", "data": data})
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@app.command("sale-performance-update", help="更新售课业绩数据")
|
|
202
|
+
@command_handler
|
|
203
|
+
def sale_performance_update(
|
|
204
|
+
payload: str = typer.Option(
|
|
205
|
+
...,
|
|
206
|
+
"--payload",
|
|
207
|
+
help="业绩更新列表,JSON 数组字符串。[{\\\"transactionId\\\":1, \\\"performance\\\":[{\\\"sellerName\\\":\\\"测试\", \\\"amount\\\":10.0}]}]",
|
|
208
|
+
),
|
|
209
|
+
phone: str = typer.Option(
|
|
210
|
+
...,
|
|
211
|
+
"--phone",
|
|
212
|
+
help="不分业绩员工手机号。",
|
|
213
|
+
),
|
|
214
|
+
center_id: str = typer.Option(
|
|
215
|
+
...,
|
|
216
|
+
"--center-id",
|
|
217
|
+
help="门店ID。",
|
|
218
|
+
),
|
|
219
|
+
) -> None:
|
|
220
|
+
parsed_payload = _parse_performance_update_payload(payload)
|
|
221
|
+
|
|
222
|
+
api = _build_api()
|
|
223
|
+
data = api.course.performance_update(
|
|
224
|
+
phone=phone,
|
|
225
|
+
transactions=parsed_payload,
|
|
226
|
+
center_id=center_id,
|
|
227
|
+
)
|
|
228
|
+
print_json({"status": "ok", "data": data})
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@app.command("sale-performance-new", help="录课程合同业绩数据")
|
|
232
|
+
@command_handler
|
|
233
|
+
def sale_performance_new(
|
|
234
|
+
type_: int = typer.Option(
|
|
235
|
+
...,
|
|
236
|
+
"--type",
|
|
237
|
+
min=1,
|
|
238
|
+
max=2,
|
|
239
|
+
help="课程类型:1私教课,2培训课。",
|
|
240
|
+
),
|
|
241
|
+
payload: str = typer.Option(
|
|
242
|
+
...,
|
|
243
|
+
"--payload",
|
|
244
|
+
help="待录单合同列表,JSON 对象字符串。{\\\"data\\\":[{\\\"seq\\\":1,\\\"date\\\":\\\"2026-01-01\\\",\\\"activateNow\\\":true,\\\"phone\\\":\\\"xxx\\\",\\\"name\\\":\\\"xxxx\\\",\\\"courseName\\\":\\\"xxx\\\",\n\\\"contractNum\\\":\\\"xxx\\\",\\\"count\\\":3,\\\"payAmount\\\":1000,\\\"payMethod\\\":\\\"\\\",\n\\\"payAmount2\\\":1000,\\\"payMethod2\\\":\\\"\\\",\\\"performanceAmount\\\":2000,\n\\\"performance\\\":[{\\\"sellerName\\\":\\\"xxx\\\",\\\"amount\\\":2000}],\\\"saleType\\\":\\\"新购课\\\", \\\"remarks\\\":\\\"测试备注\\\",\\\"noPerformancePhone\\\":\\\"xxx\\\"}}]} ",
|
|
245
|
+
),
|
|
246
|
+
center_id: str = typer.Option(
|
|
247
|
+
...,
|
|
248
|
+
"--center-id",
|
|
249
|
+
help="门店ID。",
|
|
250
|
+
),
|
|
251
|
+
) -> None:
|
|
252
|
+
parsed_payload = _parse_performance_new_payload(payload)
|
|
253
|
+
|
|
254
|
+
api = _build_api()
|
|
255
|
+
data = api.course.performance_new(
|
|
256
|
+
type_=type_,
|
|
257
|
+
payload=parsed_payload,
|
|
258
|
+
center_id=center_id,
|
|
259
|
+
)
|
|
260
|
+
print_json({"status": "ok", "data": data})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from bocai_cli.api import ApiFacade
|
|
6
|
+
from bocai_cli.command_utils import command_handler
|
|
7
|
+
from bocai_cli.config import load_config
|
|
8
|
+
from bocai_cli.date_range_validation import validate_date_range
|
|
9
|
+
from bocai_cli.output import print_json
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(help="customreport相关命令。", invoke_without_command=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.callback()
|
|
15
|
+
def customreport_root(ctx: typer.Context) -> None:
|
|
16
|
+
if ctx.invoked_subcommand is None:
|
|
17
|
+
typer.echo(ctx.get_help())
|
|
18
|
+
raise typer.Exit(code=0)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _build_api() -> ApiFacade:
|
|
22
|
+
config = load_config()
|
|
23
|
+
return ApiFacade(
|
|
24
|
+
base_url=config.base_url,
|
|
25
|
+
token=config.token,
|
|
26
|
+
timeout_seconds=config.timeout_seconds,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command("ds-list", help="获取定制数据集列表")
|
|
31
|
+
@command_handler
|
|
32
|
+
def ds_list(
|
|
33
|
+
center_id: str = typer.Option(
|
|
34
|
+
...,
|
|
35
|
+
"--center-id",
|
|
36
|
+
help="门店ID。",
|
|
37
|
+
),
|
|
38
|
+
) -> None:
|
|
39
|
+
api = _build_api()
|
|
40
|
+
data = api.guanyuan.ds_list(center_id=center_id)
|
|
41
|
+
print_json({"status": "ok", "data": data})
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@app.command("ds-data", help="获取定制数据集数据")
|
|
45
|
+
@command_handler
|
|
46
|
+
def ds_data(
|
|
47
|
+
center_id: str = typer.Option(
|
|
48
|
+
...,
|
|
49
|
+
"--center-id",
|
|
50
|
+
help="门店ID。",
|
|
51
|
+
),
|
|
52
|
+
ds_id: str = typer.Option(
|
|
53
|
+
...,
|
|
54
|
+
"--ds-id",
|
|
55
|
+
help="数据集ID。",
|
|
56
|
+
),
|
|
57
|
+
start_date: str = typer.Option(
|
|
58
|
+
...,
|
|
59
|
+
"--start-date",
|
|
60
|
+
help="业务开始日期,格式 YYYY-MM-DD。",
|
|
61
|
+
),
|
|
62
|
+
end_date: str = typer.Option(
|
|
63
|
+
...,
|
|
64
|
+
"--end-date",
|
|
65
|
+
help="业务结束日期,格式 YYYY-MM-DD。",
|
|
66
|
+
),
|
|
67
|
+
) -> None:
|
|
68
|
+
validate_date_range(start_date, end_date)
|
|
69
|
+
|
|
70
|
+
api = _build_api()
|
|
71
|
+
data = api.guanyuan.ds_data(
|
|
72
|
+
center_id=center_id,
|
|
73
|
+
ds_id=ds_id,
|
|
74
|
+
start_date=start_date,
|
|
75
|
+
end_date=end_date,
|
|
76
|
+
)
|
|
77
|
+
print_json({"status": "ok", "data": data})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from bocai_cli.api import ApiFacade
|
|
6
|
+
from bocai_cli.command_utils import command_handler
|
|
7
|
+
from bocai_cli.config import load_config
|
|
8
|
+
from bocai_cli.output import print_json
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="观远相关命令。", invoke_without_command=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.callback()
|
|
14
|
+
def guanyuan_root(ctx: typer.Context) -> None:
|
|
15
|
+
if ctx.invoked_subcommand is None:
|
|
16
|
+
typer.echo(ctx.get_help())
|
|
17
|
+
raise typer.Exit(code=0)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _build_api() -> ApiFacade:
|
|
21
|
+
config = load_config()
|
|
22
|
+
return ApiFacade(
|
|
23
|
+
base_url=config.base_url,
|
|
24
|
+
token=config.token,
|
|
25
|
+
timeout_seconds=config.timeout_seconds,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _register_absence_commands(
|
|
30
|
+
application: typer.Typer,
|
|
31
|
+
*,
|
|
32
|
+
default_type: int,
|
|
33
|
+
min_type: int,
|
|
34
|
+
max_type: int,
|
|
35
|
+
type_help: str,
|
|
36
|
+
) -> None:
|
|
37
|
+
@application.command("absence-statistics", help="门店未出勤统计")
|
|
38
|
+
@command_handler
|
|
39
|
+
def absence_statistics(
|
|
40
|
+
center_id: str = typer.Option(
|
|
41
|
+
...,
|
|
42
|
+
"--center-id",
|
|
43
|
+
help="门店ID。",
|
|
44
|
+
),
|
|
45
|
+
type_: int = typer.Option(
|
|
46
|
+
default_type,
|
|
47
|
+
"--type",
|
|
48
|
+
min=min_type,
|
|
49
|
+
max=max_type,
|
|
50
|
+
help=type_help,
|
|
51
|
+
),
|
|
52
|
+
) -> None:
|
|
53
|
+
api = _build_api()
|
|
54
|
+
data = api.guanyuan.absence_statistics(center_id=center_id, type_=type_)
|
|
55
|
+
print_json({"status": "ok", "data": data})
|
|
56
|
+
|
|
57
|
+
@application.command("absence-detail", help="门店未出勤明细")
|
|
58
|
+
@command_handler
|
|
59
|
+
def absence_detail(
|
|
60
|
+
center_id: str = typer.Option(
|
|
61
|
+
...,
|
|
62
|
+
"--center-id",
|
|
63
|
+
help="门店ID。",
|
|
64
|
+
),
|
|
65
|
+
type_: int = typer.Option(
|
|
66
|
+
default_type,
|
|
67
|
+
"--type",
|
|
68
|
+
min=min_type,
|
|
69
|
+
max=max_type,
|
|
70
|
+
help=type_help,
|
|
71
|
+
),
|
|
72
|
+
) -> None:
|
|
73
|
+
api = _build_api()
|
|
74
|
+
data = api.guanyuan.absence_detail(center_id=center_id, type_=type_)
|
|
75
|
+
print_json({"status": "ok", "data": data})
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
_register_absence_commands(
|
|
79
|
+
app,
|
|
80
|
+
default_type=2,
|
|
81
|
+
min_type=2,
|
|
82
|
+
max_type=3,
|
|
83
|
+
type_help="未出勤类型:2健身房私教未出勤,3健身房培训未出勤。",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
studio_app = typer.Typer(help="观远相关命令。", invoke_without_command=True)
|
|
87
|
+
studio_app.callback()(guanyuan_root)
|
|
88
|
+
_register_absence_commands(
|
|
89
|
+
studio_app,
|
|
90
|
+
default_type=1,
|
|
91
|
+
min_type=1,
|
|
92
|
+
max_type=1,
|
|
93
|
+
type_help="未出勤类型:1未出勤。",
|
|
94
|
+
)
|