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.
Files changed (42) hide show
  1. bocai_cli/__init__.py +3 -0
  2. bocai_cli/api/__init__.py +29 -0
  3. bocai_cli/api/auth_client.py +57 -0
  4. bocai_cli/api/card_client.py +222 -0
  5. bocai_cli/api/center_client.py +55 -0
  6. bocai_cli/api/checkin_client.py +29 -0
  7. bocai_cli/api/course_client.py +85 -0
  8. bocai_cli/api/facade.py +40 -0
  9. bocai_cli/api/guanyuan_client.py +50 -0
  10. bocai_cli/api/member_client.py +97 -0
  11. bocai_cli/api/month_dashboard_client.py +130 -0
  12. bocai_cli/api/qywx_client.py +28 -0
  13. bocai_cli/api/report_client.py +41 -0
  14. bocai_cli/api/reservation_client.py +29 -0
  15. bocai_cli/api/transport.py +166 -0
  16. bocai_cli/app.py +12 -0
  17. bocai_cli/client.py +69 -0
  18. bocai_cli/command_utils.py +28 -0
  19. bocai_cli/commands/__init__.py +8 -0
  20. bocai_cli/commands/auth.py +211 -0
  21. bocai_cli/commands/card.py +653 -0
  22. bocai_cli/commands/center.py +138 -0
  23. bocai_cli/commands/checkin.py +71 -0
  24. bocai_cli/commands/course.py +260 -0
  25. bocai_cli/commands/customreport.py +77 -0
  26. bocai_cli/commands/guanyuan.py +94 -0
  27. bocai_cli/commands/member.py +288 -0
  28. bocai_cli/commands/month_dashboard.py +235 -0
  29. bocai_cli/commands/qywx.py +112 -0
  30. bocai_cli/commands/registry.py +70 -0
  31. bocai_cli/commands/report.py +93 -0
  32. bocai_cli/commands/reservation.py +71 -0
  33. bocai_cli/config.py +121 -0
  34. bocai_cli/date_range_validation.py +65 -0
  35. bocai_cli/errors.py +22 -0
  36. bocai_cli/main.py +8 -0
  37. bocai_cli/output.py +28 -0
  38. bocai_cli/product.py +10 -0
  39. bocai_cli_core-0.2.0.dist-info/METADATA +138 -0
  40. bocai_cli_core-0.2.0.dist-info/RECORD +42 -0
  41. bocai_cli_core-0.2.0.dist-info/WHEEL +4 -0
  42. bocai_cli_core-0.2.0.dist-info/licenses/LICENSE +19 -0
@@ -0,0 +1,653 @@
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, validate_optional_date_order
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_int_ids(raw_ids: str, *, option_name: str) -> list[int]:
53
+ values = [item.strip() for item in raw_ids.split(",") if item.strip()]
54
+ unique_values: list[int] = []
55
+ for value in values:
56
+ try:
57
+ parsed_id = int(value)
58
+ except ValueError as exc:
59
+ raise ConfigValidationError(
60
+ f"--{option_name} 必须是逗号分隔的正整数。"
61
+ ) from exc
62
+ if parsed_id <= 0:
63
+ raise ConfigValidationError(f"--{option_name} 必须是逗号分隔的正整数。")
64
+ if parsed_id not in unique_values:
65
+ unique_values.append(parsed_id)
66
+ return unique_values
67
+
68
+
69
+ def _parse_card_ids(raw_card_ids: str) -> list[int]:
70
+ return _parse_int_ids(raw_card_ids, option_name="card-ids")
71
+
72
+
73
+ def _parse_tag_ids(raw_tag_ids: str) -> list[int]:
74
+ return _parse_int_ids(raw_tag_ids, option_name="tag-ids")
75
+
76
+
77
+ def _validate_allowed_option(
78
+ value: int,
79
+ allowed_values: set[int],
80
+ *,
81
+ option_name: str,
82
+ description: str,
83
+ ) -> None:
84
+ if value not in allowed_values:
85
+ raise ConfigValidationError(f"--{option_name} 只支持 {description}。")
86
+
87
+
88
+ def _parse_performance_update_payload(raw_payload: str) -> list[dict[str, Any]]:
89
+ try:
90
+ payload = json.loads(raw_payload)
91
+ except json.JSONDecodeError as exc:
92
+ raise ConfigValidationError("--payload 必须是合法 JSON。") from exc
93
+
94
+ if not isinstance(payload, list):
95
+ raise ConfigValidationError("--payload 必须是数组。")
96
+
97
+ if not payload:
98
+ raise ConfigValidationError("--payload 至少要包含一条交易记录。")
99
+
100
+ for index, item in enumerate(payload, start=1):
101
+ if not isinstance(item, dict):
102
+ raise ConfigValidationError(f"--payload 第 {index} 项必须是对象。")
103
+ if "transactionId" not in item:
104
+ raise ConfigValidationError(f"--payload 第 {index} 项缺少 transactionId。")
105
+ if "performance" not in item:
106
+ raise ConfigValidationError(f"--payload 第 {index} 项缺少 performance。")
107
+
108
+ return payload
109
+
110
+
111
+ def _parse_performance_new_payload(raw_payload: str) -> dict[str, Any]:
112
+ try:
113
+ payload = json.loads(raw_payload)
114
+ except json.JSONDecodeError as exc:
115
+ raise ConfigValidationError("--payload 必须是合法 JSON。") from exc
116
+
117
+ if not isinstance(payload, dict):
118
+ raise ConfigValidationError("--payload 必须是对象。")
119
+
120
+ if "data" not in payload:
121
+ raise ConfigValidationError("--payload 缺少 data。")
122
+
123
+ data = payload["data"]
124
+ if not isinstance(data, list):
125
+ raise ConfigValidationError("--payload.data 必须是数组。")
126
+
127
+ if not data:
128
+ raise ConfigValidationError("--payload.data 至少要包含一条合同记录。")
129
+
130
+ required_fields = (
131
+ "seq",
132
+ "date",
133
+ "activateNow",
134
+ "phone",
135
+ "name",
136
+ "cardName",
137
+ "contractNum",
138
+ "count",
139
+ "payAmount",
140
+ "payMethod",
141
+ "performanceAmount",
142
+ "performance",
143
+ "saleType",
144
+ )
145
+ for index, item in enumerate(data, start=1):
146
+ if not isinstance(item, dict):
147
+ raise ConfigValidationError(f"--payload.data 第 {index} 项必须是对象。")
148
+ for field in required_fields:
149
+ if field not in item:
150
+ raise ConfigValidationError(f"--payload.data 第 {index} 项缺少 {field}。")
151
+
152
+ return payload
153
+
154
+
155
+ @app.command("sale-performance", help="获取售卡业绩数据")
156
+ @command_handler
157
+ def sale_performance(
158
+ start_date: str = typer.Option(
159
+ ...,
160
+ "--start-date",
161
+ help="开始日期,格式 YYYY-MM-DD。",
162
+ ),
163
+ end_date: str = typer.Option(
164
+ ...,
165
+ "--end-date",
166
+ help="结束日期,格式 YYYY-MM-DD。",
167
+ ),
168
+ page_num: int = typer.Option(
169
+ ...,
170
+ "--page-num",
171
+ min=0,
172
+ help="页码,必须大于等于 0。",
173
+ ),
174
+ page_size: int = typer.Option(
175
+ ...,
176
+ "--page-size",
177
+ min=1,
178
+ help="每页条数,必须大于等于 1。",
179
+ ),
180
+ type_: int = typer.Option(
181
+ ...,
182
+ "--type",
183
+ min=1,
184
+ max=2,
185
+ help="卡类型:1会员卡,2培训卡。",
186
+ ),
187
+ center_id: str = typer.Option(
188
+ ...,
189
+ "--center-id",
190
+ help="门店ID。",
191
+ ),
192
+ ) -> None:
193
+ validate_date_range(start_date, end_date)
194
+
195
+ api = _build_api()
196
+ data = api.card.sale_performance(
197
+ start_date=start_date,
198
+ end_date=end_date,
199
+ page_num=page_num,
200
+ page_size=page_size,
201
+ type_=type_,
202
+ center_id=center_id,
203
+ )
204
+ print_json({"status": "ok", "data": data})
205
+
206
+
207
+ @app.command("sale-performance-clear", help="清除售卡业绩数据")
208
+ @command_handler
209
+ def sale_performance_clear(
210
+ transaction_ids: str = typer.Option(
211
+ ...,
212
+ "--transaction-ids",
213
+ help="交易ID列表,逗号分隔。",
214
+ ),
215
+ phone: str = typer.Option(
216
+ ...,
217
+ "--phone",
218
+ help="不分业绩员工手机号。",
219
+ ),
220
+ center_id: str = typer.Option(
221
+ ...,
222
+ "--center-id",
223
+ help="门店ID。",
224
+ ),
225
+ ) -> None:
226
+ parsed_transaction_ids = _parse_transaction_ids(transaction_ids)
227
+
228
+ api = _build_api()
229
+ data = api.card.performance_clear(
230
+ transaction_ids=parsed_transaction_ids,
231
+ phone=phone,
232
+ center_id=center_id,
233
+ )
234
+ print_json({"status": "ok", "data": data})
235
+
236
+
237
+ @app.command("sale-performance-update", help="更新售卡业绩数据")
238
+ @command_handler
239
+ def sale_performance_update(
240
+ payload: str = typer.Option(
241
+ ...,
242
+ "--payload",
243
+ help="业绩更新列表,JSON 数组字符串。[{\\\"transactionId\\\":1, \\\"performance\\\":[{\\\"sellerName\\\":\\\"测试\", \\\"amount\\\":10.0}]}]",
244
+ ),
245
+ phone: str = typer.Option(
246
+ ...,
247
+ "--phone",
248
+ help="不分业绩员工手机号。",
249
+ ),
250
+ center_id: str = typer.Option(
251
+ ...,
252
+ "--center-id",
253
+ help="门店ID。",
254
+ ),
255
+ ) -> None:
256
+ parsed_payload = _parse_performance_update_payload(payload)
257
+
258
+ api = _build_api()
259
+ data = api.card.performance_update(
260
+ phone=phone,
261
+ transactions=parsed_payload,
262
+ center_id=center_id,
263
+ )
264
+ print_json({"status": "ok", "data": data})
265
+
266
+
267
+ @app.command("sale-performance-new", help="录卡合同业绩数据")
268
+ @command_handler
269
+ def sale_performance_new(
270
+ type_: int = typer.Option(
271
+ ...,
272
+ "--type",
273
+ min=1,
274
+ max=2,
275
+ help="卡类型:1会员卡,2培训卡。",
276
+ ),
277
+ payload: str = typer.Option(
278
+ ...,
279
+ "--payload",
280
+ help="待录单合同列表,JSON 对象字符串。{\\\"data\":[{\\\"seq\\\":1,\\\"date\\\":\"2026-01-01\\\",\\\"activateNow\\\":true,\\\"phone\\\":\\\"xxx\\\",\\\"name\\\":\\\"xxxx\\\",\\\"cardName\\\":\\\"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\\\"}]} ",
281
+ ),
282
+ center_id: str = typer.Option(
283
+ ...,
284
+ "--center-id",
285
+ help="门店ID。",
286
+ ),
287
+ ) -> None:
288
+ parsed_payload = _parse_performance_new_payload(payload)
289
+
290
+ api = _build_api()
291
+ data = api.card.performance_new(
292
+ type_=type_,
293
+ payload=parsed_payload,
294
+ center_id=center_id,
295
+ )
296
+ print_json({"status": "ok", "data": data})
297
+
298
+
299
+ @command_handler
300
+ def studio_contract_list(
301
+ center_id: str = typer.Option(
302
+ ...,
303
+ "--center-id",
304
+ help="门店ID。",
305
+ ),
306
+ card_type: int = typer.Option(
307
+ -1,
308
+ "--card-type",
309
+ help="卡种类型:-1全部,3期限卡,4次卡,5储值卡。",
310
+ ),
311
+ card_ids: str = typer.Option(
312
+ "",
313
+ "--card-ids",
314
+ help="卡种ID,英文逗号分隔,例如 1,2,3。",
315
+ ),
316
+ status: int = typer.Option(
317
+ 99,
318
+ "--status",
319
+ help="卡状态:-1全部,99有效卡,1已开卡,0未开卡,3已过期,2请假中,8停卡中,4已转让,5已退卡,6已升级,7已转店。",
320
+ ),
321
+ remain_start: int | None = typer.Option(
322
+ None,
323
+ "--remain-start",
324
+ min=0,
325
+ help="剩余开始,可选。",
326
+ ),
327
+ remain_end: int | None = typer.Option(
328
+ None,
329
+ "--remain-end",
330
+ min=0,
331
+ help="剩余结束,可选。",
332
+ ),
333
+ sale_type: int = typer.Option(
334
+ -1,
335
+ "--sale-type",
336
+ help="销售类型:-1全部,11新卡,12续卡,13转卡,14升级卡,15退卡,19转店。",
337
+ ),
338
+ contract_status: int = typer.Option(
339
+ -1,
340
+ "--contract-status",
341
+ help="合同状态:-1全部,1电子合同,2纸质合同,3无合同,4电子合同(未签署)。",
342
+ ),
343
+ seller_id: int = typer.Option(
344
+ -1,
345
+ "--seller-id",
346
+ help="销售人ID,-1表示全部。",
347
+ ),
348
+ tag_id: int = typer.Option(
349
+ -1,
350
+ "--tag-id",
351
+ help="会员卡标签ID,-1表示全部。",
352
+ ),
353
+ card_create_start_date: str = typer.Option(
354
+ "",
355
+ "--card-create-start-date",
356
+ help="办卡开始日期,格式 YYYY-MM-DD。",
357
+ ),
358
+ card_create_end_date: str = typer.Option(
359
+ "",
360
+ "--card-create-end-date",
361
+ help="办卡结束日期,格式 YYYY-MM-DD。",
362
+ ),
363
+ card_activate_start_date: str = typer.Option(
364
+ "",
365
+ "--card-activate-start-date",
366
+ help="开卡开始日期,格式 YYYY-MM-DD。",
367
+ ),
368
+ card_activate_end_date: str = typer.Option(
369
+ "",
370
+ "--card-activate-end-date",
371
+ help="开卡结束日期,格式 YYYY-MM-DD。",
372
+ ),
373
+ card_expire_start_date: str = typer.Option(
374
+ "",
375
+ "--card-expire-start-date",
376
+ help="过期开始日期,格式 YYYY-MM-DD。",
377
+ ),
378
+ card_expire_end_date: str = typer.Option(
379
+ "",
380
+ "--card-expire-end-date",
381
+ help="过期结束日期,格式 YYYY-MM-DD。",
382
+ ),
383
+ course_start_date: str = typer.Option(
384
+ "",
385
+ "--course-start-date",
386
+ help="最后上课开始日期,格式 YYYY-MM-DD。",
387
+ ),
388
+ course_end_date: str = typer.Option(
389
+ "",
390
+ "--course-end-date",
391
+ help="最后上课结束日期,格式 YYYY-MM-DD。",
392
+ ),
393
+ page_num: int = typer.Option(
394
+ ...,
395
+ "--page-num",
396
+ min=0,
397
+ help="页码,从 0 开始。",
398
+ ),
399
+ page_size: int = typer.Option(
400
+ ...,
401
+ "--page-size",
402
+ min=1,
403
+ help="每页数量,必须大于等于 1。",
404
+ ),
405
+ type_: int = typer.Option(
406
+ 0,
407
+ "--type",
408
+ min=0,
409
+ max=2,
410
+ help="合同类型:0全部合同,1会员卡合同,2课程卡合同。",
411
+ ),
412
+ ) -> None:
413
+ _validate_allowed_option(
414
+ card_type,
415
+ {-1, 3, 4, 5},
416
+ option_name="card-type",
417
+ description="-1、3、4、5",
418
+ )
419
+ _validate_allowed_option(
420
+ status,
421
+ {-1, 99, 1, 0, 3, 2, 8, 4, 5, 6, 7},
422
+ option_name="status",
423
+ description="-1、0、1、2、3、4、5、6、7、8、99",
424
+ )
425
+ _validate_allowed_option(
426
+ sale_type,
427
+ {-1, 11, 12, 13, 14, 15, 19},
428
+ option_name="sale-type",
429
+ description="-1、11、12、13、14、15、19",
430
+ )
431
+ _validate_allowed_option(
432
+ contract_status,
433
+ {-1, 1, 2, 3, 4},
434
+ option_name="contract-status",
435
+ description="-1、1、2、3、4",
436
+ )
437
+ if remain_start is not None and remain_end is not None and remain_start > remain_end:
438
+ raise ConfigValidationError("--remain-start 不能大于 --remain-end。")
439
+
440
+ validate_optional_date_order(
441
+ card_create_start_date,
442
+ card_create_end_date,
443
+ start_option_name="card-create-start-date",
444
+ end_option_name="card-create-end-date",
445
+ )
446
+ validate_optional_date_order(
447
+ card_activate_start_date,
448
+ card_activate_end_date,
449
+ start_option_name="card-activate-start-date",
450
+ end_option_name="card-activate-end-date",
451
+ )
452
+ validate_optional_date_order(
453
+ card_expire_start_date,
454
+ card_expire_end_date,
455
+ start_option_name="card-expire-start-date",
456
+ end_option_name="card-expire-end-date",
457
+ )
458
+ validate_optional_date_order(
459
+ course_start_date,
460
+ course_end_date,
461
+ start_option_name="course-start-date",
462
+ end_option_name="course-end-date",
463
+ )
464
+
465
+ parsed_card_ids = _parse_card_ids(card_ids)
466
+ api = _build_api()
467
+ data = api.card.studio_contract_list(
468
+ center_id=center_id,
469
+ page_num=page_num,
470
+ page_size=page_size,
471
+ type_=type_,
472
+ card_type=card_type,
473
+ card_ids=parsed_card_ids,
474
+ status=status,
475
+ remain_start=remain_start,
476
+ remain_end=remain_end,
477
+ sale_type=sale_type,
478
+ contract_status=contract_status,
479
+ seller_id=seller_id,
480
+ tag_id=tag_id,
481
+ card_create_start_date=card_create_start_date,
482
+ card_create_end_date=card_create_end_date,
483
+ card_activate_start_date=card_activate_start_date,
484
+ card_activate_end_date=card_activate_end_date,
485
+ card_expire_start_date=card_expire_start_date,
486
+ card_expire_end_date=card_expire_end_date,
487
+ course_start_date=course_start_date,
488
+ course_end_date=course_end_date,
489
+ )
490
+ print_json({"status": "ok", "data": data})
491
+
492
+
493
+ @command_handler
494
+ def contract_detail(
495
+ center_id: str = typer.Option(
496
+ ...,
497
+ "--center-id",
498
+ help="门店ID。",
499
+ ),
500
+ contract_id: str = typer.Option(
501
+ ...,
502
+ "--contract-id",
503
+ help="合同ID。",
504
+ ),
505
+ ) -> None:
506
+ api = _build_api()
507
+ data = api.card.contract_detail(center_id=center_id, contract_id=contract_id)
508
+ print_json({"status": "ok", "data": data})
509
+
510
+
511
+ @command_handler
512
+ def studio_sale_performance(
513
+ center_id: str = typer.Option(
514
+ ...,
515
+ "--center-id",
516
+ help="门店ID。",
517
+ ),
518
+ start_date: str = typer.Option(
519
+ ...,
520
+ "--start-date",
521
+ help="开始日期,格式 YYYY-MM-DD。",
522
+ ),
523
+ end_date: str = typer.Option(
524
+ ...,
525
+ "--end-date",
526
+ help="结束日期,格式 YYYY-MM-DD。",
527
+ ),
528
+ seller_id: int = typer.Option(
529
+ -1,
530
+ "--seller-id",
531
+ help="销售人ID,-1表示全部。",
532
+ ),
533
+ type_: int = typer.Option(
534
+ 0,
535
+ "--type",
536
+ min=0,
537
+ max=2,
538
+ help="业绩类型:0全部业绩,1会员卡业绩,2课程卡业绩。",
539
+ ),
540
+ card_ids: str = typer.Option(
541
+ "",
542
+ "--card-ids",
543
+ help="卡种ID,英文逗号分隔,例如 1,2,3。",
544
+ ),
545
+ tag_ids: str = typer.Option(
546
+ "",
547
+ "--tag-ids",
548
+ help="会员卡标签ID,英文逗号分隔,例如 1,2,3。",
549
+ ),
550
+ page_num: int = typer.Option(
551
+ 0,
552
+ "--page-num",
553
+ min=0,
554
+ help="页码,从 0 开始。",
555
+ ),
556
+ page_size: int = typer.Option(
557
+ 20,
558
+ "--page-size",
559
+ min=1,
560
+ help="每页数量,必须大于等于 1。",
561
+ ),
562
+ ) -> None:
563
+ validate_date_range(start_date, end_date)
564
+
565
+ parsed_card_ids = _parse_card_ids(card_ids)
566
+ parsed_tag_ids = _parse_tag_ids(tag_ids)
567
+ api = _build_api()
568
+ data = api.card.studio_sale(
569
+ center_id=center_id,
570
+ start_date=start_date,
571
+ end_date=end_date,
572
+ type_=type_,
573
+ seller_id=seller_id,
574
+ card_ids=parsed_card_ids,
575
+ tag_ids=parsed_tag_ids,
576
+ page_num=page_num,
577
+ page_size=page_size,
578
+ )
579
+ print_json({"status": "ok", "data": data})
580
+
581
+
582
+ @command_handler
583
+ def studio_private_consume_performance(
584
+ center_id: str = typer.Option(
585
+ ...,
586
+ "--center-id",
587
+ help="门店ID。",
588
+ ),
589
+ start_date: str = typer.Option(
590
+ ...,
591
+ "--start-date",
592
+ help="开始日期,格式 YYYY-MM-DD。",
593
+ ),
594
+ end_date: str = typer.Option(
595
+ ...,
596
+ "--end-date",
597
+ help="结束日期,格式 YYYY-MM-DD。",
598
+ ),
599
+ trainer_id: int = typer.Option(
600
+ -1,
601
+ "--trainer-id",
602
+ help="教练ID,-1表示全部。",
603
+ ),
604
+ teach_mode: int = typer.Option(
605
+ -1,
606
+ "--teach-mode",
607
+ help="售课方式:-1全部,1一对一,2一对多。",
608
+ ),
609
+ page_num: int = typer.Option(
610
+ ...,
611
+ "--page-num",
612
+ min=0,
613
+ help="页码,从 0 开始,必填。",
614
+ ),
615
+ page_size: int = typer.Option(
616
+ ...,
617
+ "--page-size",
618
+ min=1,
619
+ help="每页数量,必须大于等于 1,必填。",
620
+ ),
621
+ ) -> None:
622
+ validate_date_range(start_date, end_date)
623
+ _validate_allowed_option(
624
+ teach_mode,
625
+ {-1, 1, 2},
626
+ option_name="teach-mode",
627
+ description="-1、1、2",
628
+ )
629
+
630
+ api = _build_api()
631
+ data = api.card.studio_private_consume_performance(
632
+ center_id=center_id,
633
+ start_date=start_date,
634
+ end_date=end_date,
635
+ trainer_id=trainer_id,
636
+ teach_mode=teach_mode,
637
+ page_num=page_num,
638
+ page_size=page_size,
639
+ )
640
+ print_json({"status": "ok", "data": data})
641
+
642
+
643
+ studio_app = typer.Typer(help="卡相关命令。", invoke_without_command=True)
644
+ studio_app.callback()(card_root)
645
+ studio_app.command("sale-performance", help="销售业绩报表")(studio_sale_performance)
646
+ studio_app.command("sale-performance-clear", help="清除售卡业绩数据")(sale_performance_clear)
647
+ studio_app.command("sale-performance-update", help="更新售卡业绩数据")(sale_performance_update)
648
+ studio_app.command("sale-performance-new", help="录卡合同业绩数据")(sale_performance_new)
649
+ studio_app.command("contract-list", help="获取合同列表")(studio_contract_list)
650
+ studio_app.command("contract-detail", help="获取合同详情")(contract_detail)
651
+ studio_app.command("private-consume-performance", help="获取私教上课业绩报表")(
652
+ studio_private_consume_performance
653
+ )