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,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
+ )