bocai-cli 0.1.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 +19 -0
- bocai_cli/api/auth_client.py +26 -0
- bocai_cli/api/card_client.py +85 -0
- bocai_cli/api/checkin_client.py +29 -0
- bocai_cli/api/course_client.py +85 -0
- bocai_cli/api/facade.py +30 -0
- bocai_cli/api/report_client.py +41 -0
- bocai_cli/api/reservation_client.py +29 -0
- bocai_cli/api/transport.py +153 -0
- bocai_cli/client.py +69 -0
- bocai_cli/command_utils.py +28 -0
- bocai_cli/commands/__init__.py +7 -0
- bocai_cli/commands/auth.py +66 -0
- bocai_cli/commands/card.py +260 -0
- bocai_cli/commands/checkin.py +71 -0
- bocai_cli/commands/course.py +260 -0
- bocai_cli/commands/registry.py +36 -0
- bocai_cli/commands/report.py +93 -0
- bocai_cli/commands/reservation.py +71 -0
- bocai_cli/config.py +116 -0
- bocai_cli/date_range_validation.py +48 -0
- bocai_cli/errors.py +22 -0
- bocai_cli/main.py +11 -0
- bocai_cli/output.py +28 -0
- bocai_cli-0.1.0.dist-info/METADATA +83 -0
- bocai_cli-0.1.0.dist-info/RECORD +29 -0
- bocai_cli-0.1.0.dist-info/WHEEL +4 -0
- bocai_cli-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -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 card_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
|
+
"cardName",
|
|
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 | None = typer.Option(
|
|
152
|
+
None,
|
|
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.card.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 | None = typer.Option(
|
|
185
|
+
None,
|
|
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.card.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 | None = typer.Option(
|
|
215
|
+
None,
|
|
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.card.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\\\",\\\"cardName\\\":\\\"xxx\\\",\\\"contractNum\\\":\\\"xxx\\\",\\\"count\\\":3,\\\"payAmount\\\":1000,\\\"payMethod\\\":\\\"\\\",\\\"performanceAmount\\\":1000,\\\"performance\\\":[{\\\"sellerName\\\":\\\"xxx\\\",\\\"amount\\\":1000}],\\\"saleType\\\":\\\"新卡\\\",\\\"remarks\\\":\\\"\\\"}]} ",
|
|
245
|
+
),
|
|
246
|
+
center_id: str | None = typer.Option(
|
|
247
|
+
None,
|
|
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.card.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,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 | None = typer.Option(
|
|
56
|
+
None,
|
|
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 | None = typer.Option(
|
|
152
|
+
None,
|
|
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 | None = typer.Option(
|
|
185
|
+
None,
|
|
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 | None = typer.Option(
|
|
215
|
+
None,
|
|
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\\\",\\\"contractNum\\\":\\\"xxx\\\",\\\"count\\\":3,\\\"payAmount\\\":1000,\\\"payMethod\\\":\\\"\\\",\\\"performanceAmount\\\":1000,\\\"performance\\\":[{\\\"sellerName\\\":\\\"xxx\\\",\\\"amount\\\":1000}],\\\"saleType\\\":\\\"新购课\\\", \\\"remarks\\\":\\\"测试备注\\\"}]} ",
|
|
245
|
+
),
|
|
246
|
+
center_id: str | None = typer.Option(
|
|
247
|
+
None,
|
|
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,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from bocai_cli.commands.auth import app as auth_app
|
|
8
|
+
from bocai_cli.commands.card import app as card_app
|
|
9
|
+
from bocai_cli.commands.checkin import app as checkin_app
|
|
10
|
+
from bocai_cli.commands.course import app as course_app
|
|
11
|
+
from bocai_cli.commands.reservation import app as reservation_app
|
|
12
|
+
from bocai_cli.commands.report import app as report_app
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class CommandRegistration:
|
|
17
|
+
name: str
|
|
18
|
+
app: typer.Typer
|
|
19
|
+
aliases: tuple[str, ...] = ()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
COMMAND_REGISTRY: tuple[CommandRegistration, ...] = (
|
|
23
|
+
CommandRegistration(name="auth", app=auth_app),
|
|
24
|
+
CommandRegistration(name="card", app=card_app),
|
|
25
|
+
CommandRegistration(name="checkin", app=checkin_app),
|
|
26
|
+
CommandRegistration(name="course", app=course_app),
|
|
27
|
+
CommandRegistration(name="reservation", app=reservation_app),
|
|
28
|
+
CommandRegistration(name="report", app=report_app),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def register_commands(root_app: typer.Typer) -> None:
|
|
33
|
+
for registration in COMMAND_REGISTRY:
|
|
34
|
+
root_app.add_typer(registration.app, name=registration.name)
|
|
35
|
+
for alias in registration.aliases:
|
|
36
|
+
root_app.add_typer(registration.app, name=alias, hidden=True)
|