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,93 @@
|
|
|
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_optional_date_range
|
|
9
|
+
from bocai_cli.output import print_json
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(help="report相关命令。", invoke_without_command=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.callback()
|
|
15
|
+
def report_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("revenue", help="获取营收数据")
|
|
31
|
+
@command_handler
|
|
32
|
+
def revenue(
|
|
33
|
+
start_date: str | None = typer.Option(
|
|
34
|
+
None,
|
|
35
|
+
"--start-date",
|
|
36
|
+
help="开始日期,格式 YYYY-MM-DD。",
|
|
37
|
+
),
|
|
38
|
+
end_date: str | None = typer.Option(
|
|
39
|
+
None,
|
|
40
|
+
"--end-date",
|
|
41
|
+
help="结束日期,格式 YYYY-MM-DD。",
|
|
42
|
+
),
|
|
43
|
+
center_id: str = typer.Option(
|
|
44
|
+
...,
|
|
45
|
+
"--center-id",
|
|
46
|
+
help="门店ID。",
|
|
47
|
+
),
|
|
48
|
+
) -> None:
|
|
49
|
+
validate_optional_date_range(start_date, end_date)
|
|
50
|
+
|
|
51
|
+
api = _build_api()
|
|
52
|
+
data = api.report.get_revenue(
|
|
53
|
+
start_date=start_date,
|
|
54
|
+
end_date=end_date,
|
|
55
|
+
center_id=center_id,
|
|
56
|
+
)
|
|
57
|
+
print_json({"status": "ok", "data": data})
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command("revenue-detail", help="获取营收明细数据")
|
|
61
|
+
@command_handler
|
|
62
|
+
def revenue_detail(
|
|
63
|
+
detail_type: str = typer.Option(
|
|
64
|
+
...,
|
|
65
|
+
"--type",
|
|
66
|
+
help="营收明细类型。",
|
|
67
|
+
),
|
|
68
|
+
start_date: str | None = typer.Option(
|
|
69
|
+
None,
|
|
70
|
+
"--start-date",
|
|
71
|
+
help="开始日期,格式 YYYY-MM-DD。",
|
|
72
|
+
),
|
|
73
|
+
end_date: str | None = typer.Option(
|
|
74
|
+
None,
|
|
75
|
+
"--end-date",
|
|
76
|
+
help="结束日期,格式 YYYY-MM-DD。",
|
|
77
|
+
),
|
|
78
|
+
center_id: str = typer.Option(
|
|
79
|
+
...,
|
|
80
|
+
"--center-id",
|
|
81
|
+
help="门店ID。",
|
|
82
|
+
),
|
|
83
|
+
) -> None:
|
|
84
|
+
validate_optional_date_range(start_date, end_date)
|
|
85
|
+
|
|
86
|
+
api = _build_api()
|
|
87
|
+
data = api.report.get_revenue_detail(
|
|
88
|
+
type_=detail_type,
|
|
89
|
+
start_date=start_date,
|
|
90
|
+
end_date=end_date,
|
|
91
|
+
center_id=center_id,
|
|
92
|
+
)
|
|
93
|
+
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 reservation_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_reservation(
|
|
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.reservation.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})
|
bocai_cli/config.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from dataclasses import asdict, dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import tomli_w
|
|
10
|
+
|
|
11
|
+
from bocai_cli.errors import ConfigValidationError
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import tomllib
|
|
15
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
16
|
+
import tomli as tomllib # type: ignore[no-redef]
|
|
17
|
+
|
|
18
|
+
DEFAULT_CONFIG_PATH = Path.home() / ".config" / "bocai-cli" / "config.toml"
|
|
19
|
+
DEFAULT_BASE_URL = "https://openapi.forzadata.cn/"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class AppConfig:
|
|
24
|
+
base_url: str | None = DEFAULT_BASE_URL
|
|
25
|
+
token: str | None = None
|
|
26
|
+
timeout_seconds: float = 30.0
|
|
27
|
+
app_id: str | None = None
|
|
28
|
+
secret: str | None = None
|
|
29
|
+
token_url: str | None = None
|
|
30
|
+
token_backup: str | None = None
|
|
31
|
+
token_updated_at: str | None = None
|
|
32
|
+
refresh_token: str | None = None
|
|
33
|
+
expires_at: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_config_path() -> Path:
|
|
37
|
+
raw_path = os.getenv("BOCAI_CLI_CONFIG_PATH")
|
|
38
|
+
if raw_path:
|
|
39
|
+
return Path(raw_path).expanduser()
|
|
40
|
+
return DEFAULT_CONFIG_PATH
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_config(path: Path | None = None) -> AppConfig:
|
|
44
|
+
config_path = path or get_config_path()
|
|
45
|
+
if not config_path.exists():
|
|
46
|
+
return AppConfig()
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
with config_path.open("rb") as f:
|
|
50
|
+
data = tomllib.load(f)
|
|
51
|
+
except tomllib.TOMLDecodeError as exc:
|
|
52
|
+
raise ConfigValidationError(
|
|
53
|
+
"配置文件格式错误,请检查 TOML 语法。",
|
|
54
|
+
details={"config_path": str(config_path)},
|
|
55
|
+
) from exc
|
|
56
|
+
except OSError as exc:
|
|
57
|
+
raise ConfigValidationError(
|
|
58
|
+
"配置文件读取失败,请检查文件权限或路径。",
|
|
59
|
+
details={"config_path": str(config_path)},
|
|
60
|
+
) from exc
|
|
61
|
+
|
|
62
|
+
return AppConfig(
|
|
63
|
+
base_url=_optional_string(data.get("base_url")) or DEFAULT_BASE_URL,
|
|
64
|
+
token=_optional_string(data.get("token")),
|
|
65
|
+
timeout_seconds=_optional_float(data.get("timeout_seconds"), default=30.0),
|
|
66
|
+
app_id=_optional_string(data.get("app_id")),
|
|
67
|
+
secret=_optional_string(data.get("secret")),
|
|
68
|
+
token_url=_optional_string(data.get("token_url")),
|
|
69
|
+
token_backup=_optional_string(data.get("token_backup")),
|
|
70
|
+
token_updated_at=_optional_string(data.get("token_updated_at")),
|
|
71
|
+
refresh_token=_optional_string(data.get("refresh_token")),
|
|
72
|
+
expires_at=_optional_string(data.get("expires_at")),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def save_config(config: AppConfig, path: Path | None = None) -> Path:
|
|
77
|
+
config_path = path or get_config_path()
|
|
78
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
|
|
80
|
+
payload = _to_toml_payload(config)
|
|
81
|
+
with config_path.open("wb") as f:
|
|
82
|
+
f.write(tomli_w.dumps(payload).encode("utf-8"))
|
|
83
|
+
|
|
84
|
+
_enforce_permissions_best_effort(config_path)
|
|
85
|
+
return config_path
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _to_toml_payload(config: AppConfig) -> dict[str, Any]:
|
|
89
|
+
values = asdict(config)
|
|
90
|
+
payload: dict[str, Any] = {}
|
|
91
|
+
for key, value in values.items():
|
|
92
|
+
if value is None:
|
|
93
|
+
continue
|
|
94
|
+
payload[key] = value
|
|
95
|
+
return payload
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _optional_string(raw: Any) -> str | None:
|
|
99
|
+
if raw is None:
|
|
100
|
+
return None
|
|
101
|
+
value = str(raw).strip()
|
|
102
|
+
return value or None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _optional_float(raw: Any, default: float) -> float:
|
|
106
|
+
if raw is None:
|
|
107
|
+
return default
|
|
108
|
+
try:
|
|
109
|
+
return float(raw)
|
|
110
|
+
except (TypeError, ValueError):
|
|
111
|
+
return default
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _enforce_permissions_best_effort(path: Path) -> None:
|
|
115
|
+
if sys.platform.startswith("win"):
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
path.chmod(0o600)
|
|
120
|
+
except OSError:
|
|
121
|
+
return
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
|
|
5
|
+
from bocai_cli.errors import ConfigValidationError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _parse_date(value: str, *, option_name: str) -> date:
|
|
9
|
+
try:
|
|
10
|
+
return datetime.strptime(value, "%Y-%m-%d").date()
|
|
11
|
+
except ValueError as exc:
|
|
12
|
+
raise ConfigValidationError(
|
|
13
|
+
f"--{option_name} 格式必须为 YYYY-MM-DD。",
|
|
14
|
+
details={"invalid": [option_name]},
|
|
15
|
+
) from exc
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def validate_date_range(start_date: str, end_date: str) -> None:
|
|
19
|
+
start = _parse_date(start_date, option_name="start-date")
|
|
20
|
+
end = _parse_date(end_date, option_name="end-date")
|
|
21
|
+
if start > end:
|
|
22
|
+
raise ConfigValidationError(
|
|
23
|
+
"--start-date 不能晚于 --end-date。",
|
|
24
|
+
details={"start_date": start_date, "end_date": end_date},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
range_days = (end - start).days + 1
|
|
28
|
+
if range_days > 31:
|
|
29
|
+
raise ConfigValidationError(
|
|
30
|
+
"--start-date 到 --end-date 的时间范围不能超过 31 天。",
|
|
31
|
+
details={
|
|
32
|
+
"start_date": start_date,
|
|
33
|
+
"end_date": end_date,
|
|
34
|
+
"range_days": range_days,
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def validate_optional_date_order(
|
|
40
|
+
start_date: str | None,
|
|
41
|
+
end_date: str | None,
|
|
42
|
+
*,
|
|
43
|
+
start_option_name: str = "start-date",
|
|
44
|
+
end_option_name: str = "end-date",
|
|
45
|
+
) -> None:
|
|
46
|
+
start = _parse_date(start_date, option_name=start_option_name) if start_date else None
|
|
47
|
+
end = _parse_date(end_date, option_name=end_option_name) if end_date else None
|
|
48
|
+
|
|
49
|
+
if start is not None and end is not None and start > end:
|
|
50
|
+
raise ConfigValidationError(
|
|
51
|
+
f"--{start_option_name} 不能晚于 --{end_option_name}。",
|
|
52
|
+
details={"start_date": start_date, "end_date": end_date},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def validate_optional_date_range(start_date: str | None, end_date: str | None) -> None:
|
|
57
|
+
if (start_date is None) != (end_date is None):
|
|
58
|
+
raise ConfigValidationError("--start-date 和 --end-date 必须同时提供。")
|
|
59
|
+
|
|
60
|
+
if start_date is None and end_date is None:
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
assert start_date is not None
|
|
64
|
+
assert end_date is not None
|
|
65
|
+
validate_date_range(start_date, end_date)
|
bocai_cli/errors.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class BocaiCliError(Exception):
|
|
9
|
+
message: str
|
|
10
|
+
status_code: int | None = None
|
|
11
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
12
|
+
|
|
13
|
+
def __str__(self) -> str:
|
|
14
|
+
return self.message
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConfigValidationError(BocaiCliError):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ApiRequestError(BocaiCliError):
|
|
22
|
+
pass
|
bocai_cli/main.py
ADDED
bocai_cli/output.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def print_json(payload: dict[str, Any]) -> None:
|
|
10
|
+
typer.echo(json.dumps(payload, ensure_ascii=False))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def print_error(
|
|
14
|
+
message: str,
|
|
15
|
+
*,
|
|
16
|
+
status_code: int | None = None,
|
|
17
|
+
details: dict[str, Any] | None = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
error_payload: dict[str, Any] = {
|
|
20
|
+
"status": "error",
|
|
21
|
+
"message": message,
|
|
22
|
+
}
|
|
23
|
+
if status_code is not None:
|
|
24
|
+
error_payload["status_code"] = status_code
|
|
25
|
+
if details:
|
|
26
|
+
error_payload["details"] = details
|
|
27
|
+
|
|
28
|
+
print_json(error_payload)
|
bocai_cli/product.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bocai-cli-core
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Shared core for Bocai CLI products
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: httpx>=0.27.0
|
|
9
|
+
Requires-Dist: tenacity>=9.0.0
|
|
10
|
+
Requires-Dist: tomli-w>=1.0.0
|
|
11
|
+
Requires-Dist: typer>=0.12.3
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=8.2.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: respx>=0.21.1; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# bocai-cli
|
|
18
|
+
|
|
19
|
+
使用 Python + Typer 封装 SaaS API 的命令行工具。当前提供健身房和工作室两个产品入口,共享同一套核心代码和配置。
|
|
20
|
+
|
|
21
|
+
| 产品 | 安装包 | 命令 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| 健身房 | `bocai-cli` | `bocai-cli` |
|
|
24
|
+
| 工作室 | `bocai-studio-cli` | `bocai-studio-cli` |
|
|
25
|
+
|
|
26
|
+
## 开发安装
|
|
27
|
+
|
|
28
|
+
先安装共享核心,再安装两个产品入口:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
python -m pip install -e .
|
|
32
|
+
python -m pip install --no-deps -e packages/bocai-cli -e packages/bocai-studio-cli
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
也可以只安装某一个产品入口:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
python -m pip install --no-deps -e packages/bocai-cli
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 构建离线 wheel
|
|
42
|
+
|
|
43
|
+
构建机需要先安装 hatchling:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
python -m pip install "hatchling>=1.24.2"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
构建三个同版本 wheel,并同时收集运行依赖:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
python scripts/build_wheels.py
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
如果依赖已经由内网镜像提供,只构建产品 wheel:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
python scripts/build_wheels.py --no-deps
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
构建产物位于 `dist/`,包括 `bocai-cli-core`、`bocai-cli` 和 `bocai-studio-cli`。
|
|
62
|
+
|
|
63
|
+
## 离线安装
|
|
64
|
+
|
|
65
|
+
健身房机器:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python -m pip install --no-index --find-links dist bocai-cli==0.2.0
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
工作室机器:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python -m pip install --no-index --find-links dist bocai-studio-cli==0.2.0
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
如果机器上已有旧版 `bocai-cli`,仅安装工作室版前先执行:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
python -m pip uninstall -y bocai-cli
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
如果机器上安装过旧包名 `bocai-cli-studio`,先卸载旧包再安装 `bocai-studio-cli`:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
python -m pip uninstall -y bocai-cli-studio
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 安装后验证
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
bocai-cli --help
|
|
93
|
+
bocai-studio-cli --help
|
|
94
|
+
bocai-cli report --help
|
|
95
|
+
bocai-studio-cli card --help
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 工作室命令迁移
|
|
99
|
+
|
|
100
|
+
健身房版只包含健身房命令,不包含 `studio-*` 命令。工作室能力统一在 `bocai-studio-cli` 中提供,并去掉 `studio-` 前缀。旧版命令迁移如下:
|
|
101
|
+
|
|
102
|
+
| 旧版 `bocai-cli` 命令 | 新工作室命令 |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| `center studio-seller-list` | `bocai-studio-cli center seller-list` |
|
|
105
|
+
| `center studio-trainer-list` | `bocai-studio-cli center trainer-list` |
|
|
106
|
+
| `member studio-proposed-list` | `bocai-studio-cli member proposed-list` |
|
|
107
|
+
| `member studio-member-list` | `bocai-studio-cli member member-list` |
|
|
108
|
+
| `card studio-contract-list` | `bocai-studio-cli card contract-list` |
|
|
109
|
+
| `card studio-sale-performance` | `bocai-studio-cli card sale-performance` |
|
|
110
|
+
| `card studio-private-consume-performance` | `bocai-studio-cli card private-consume-performance` |
|
|
111
|
+
|
|
112
|
+
`center card-list`、`center source-list`、`center tag-list` 现在只保留在工作室版;健身房 `bocai-cli center` 只提供 `list` 和 `manager-phone`。
|
|
113
|
+
|
|
114
|
+
`bocai-cli` 暂不提供 `member` 命令组,所有会员命令请使用 `bocai-studio-cli member ...`,包括 `detail`、`proposed-list` 和 `member-list`。
|
|
115
|
+
|
|
116
|
+
`bocai-cli card contract-detail` 暂时移除,需要使用 `bocai-studio-cli card contract-detail`。
|
|
117
|
+
|
|
118
|
+
工作室版不提供 `course` 命令组;课程相关命令仅保留在健身房版 `bocai-cli course ...`。
|
|
119
|
+
|
|
120
|
+
`bocai-studio-cli card sale-performance` 指向工作室销售业绩;`bocai-cli card sale-performance` 仍指向健身房售卡业绩。两组命令不会互相出现在对方的帮助中。
|
|
121
|
+
|
|
122
|
+
`guanyuan absence-statistics` 和 `guanyuan absence-detail` 按产品设置默认业态:健身房默认 `--type 2`,允许 `2/3`;工作室默认 `--type 1`,只允许 `1`。
|
|
123
|
+
|
|
124
|
+
## 配置文件
|
|
125
|
+
|
|
126
|
+
两个产品共用现有配置:
|
|
127
|
+
|
|
128
|
+
- 优先读取环境变量 `BOCAI_CLI_CONFIG_PATH`。
|
|
129
|
+
- 未设置时读取 `~/.config/bocai-cli/config.toml`。
|
|
130
|
+
- Windows 默认路径为 `C:\Users\{用户名}\.config\bocai-cli\config.toml`。
|
|
131
|
+
|
|
132
|
+
`config.toml` 示例(`base_url`、`app_id`、`secret` 按实际替换):
|
|
133
|
+
|
|
134
|
+
```toml
|
|
135
|
+
base_url = "http://10.0.0.227:10086"
|
|
136
|
+
app_id = "test_app_001"
|
|
137
|
+
secret = "test_secret_123456"
|
|
138
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
bocai_cli/__init__.py,sha256=QwLGZDZhVE7dBKbOwCLRt3tRMXTtg8fYYHHDXi3sZ1c,49
|
|
2
|
+
bocai_cli/app.py,sha256=B-lN54_hDnM0ftMDKWQFACPJNTcFrO7evdonu1flXVI,357
|
|
3
|
+
bocai_cli/client.py,sha256=xjtirUDW9ip3kHAhKaTnaXiqtZaFMqx81Yx5KFQogzs,1754
|
|
4
|
+
bocai_cli/command_utils.py,sha256=JlE_fh_cWRpEnoUamNLrZSEm-GOWy-QdiXHDW3Xgt9c,764
|
|
5
|
+
bocai_cli/config.py,sha256=dwu7X-5wSN-tJTyquBvYlxZu13UXtv08KVKBK1Od2BI,3657
|
|
6
|
+
bocai_cli/date_range_validation.py,sha256=22UqaOtL8q4KrxsAz_TI4qsmq7Aqwtvh06N5_yzWnqw,2202
|
|
7
|
+
bocai_cli/errors.py,sha256=1r9104aIibAmGIIGOBwwggZNPl-PpT-6mZo-wMi5qa0,418
|
|
8
|
+
bocai_cli/main.py,sha256=6_BDJbCwg4L89EntF6DMZ7KmE13vDOMikTU97GsA0rI,137
|
|
9
|
+
bocai_cli/output.py,sha256=c-kvmVpuTxY-CEVuRv93nyEGy5Tu-jPGsrfuHKd3F4o,598
|
|
10
|
+
bocai_cli/product.py,sha256=V_vegkhYX2U5Nc-Z084WlzU3WEncbCSl7z8K8M3Zagw,208
|
|
11
|
+
bocai_cli/api/__init__.py,sha256=3gmi1ICIKAmyBqLPfQS2XdIb-IgdN_fTuXy0aV1RAqk,978
|
|
12
|
+
bocai_cli/api/auth_client.py,sha256=C38FDhrWh1WpK1cpfznFPY35n-OeufgwFzTGexD_s6E,1776
|
|
13
|
+
bocai_cli/api/card_client.py,sha256=9De6Q8h79ixoMWNkyqI3NzStqLdOyLNZ01159zUTORs,6714
|
|
14
|
+
bocai_cli/api/center_client.py,sha256=_3SLniorOkkQh7c730g8GNGCt5WazlNyWK0FgB-yElM,1653
|
|
15
|
+
bocai_cli/api/checkin_client.py,sha256=H620MXbPtc_06ncVuQgj7ksh2ZkiTUEntxWZeb0J_ts,755
|
|
16
|
+
bocai_cli/api/course_client.py,sha256=dDx-a-SBx2ilUcDudqqh4BTmCUa0oFkjlokj0_l3dCo,2300
|
|
17
|
+
bocai_cli/api/facade.py,sha256=mA0PMmxWNjB_a1RH4H6KXjBwwY1EfDjVqL-8_KsMPQY,1593
|
|
18
|
+
bocai_cli/api/guanyuan_client.py,sha256=BY-vuFM1lrAkQ7ogkv92u6h4RnJMb39zypt5M3Z7Tck,1419
|
|
19
|
+
bocai_cli/api/member_client.py,sha256=sQl1NTOzD2h1Z2LwZPyBFq6ZdBrmvM5RlMJuZKL6mEQ,3139
|
|
20
|
+
bocai_cli/api/month_dashboard_client.py,sha256=NjS1GY7Vc2jwOLUH0B3WB52f8pEeFwh3-rNEXZ0Q3A0,3143
|
|
21
|
+
bocai_cli/api/qywx_client.py,sha256=t5QY8l2v5pvBZR4tsgjx0yihUvhV2i7AxQ10vRrQ2Is,873
|
|
22
|
+
bocai_cli/api/report_client.py,sha256=pQQ71_gIowMH3AX9cVnOd3UZWpgf5gpoNKhkx-2qZCY,1343
|
|
23
|
+
bocai_cli/api/reservation_client.py,sha256=4DmgazRQIPLtuvxNR_p-QUyzexk6NMemtuXz3uX4wDw,763
|
|
24
|
+
bocai_cli/api/transport.py,sha256=WoafWXQcVZRb2TaoOGARkbjyUK2ymPyybCz40NTY_Yw,6142
|
|
25
|
+
bocai_cli/commands/__init__.py,sha256=PP8ldg76MZGWkuSZ4ht-61OZX8Fz-E3JvsBKJDtMlm4,444
|
|
26
|
+
bocai_cli/commands/auth.py,sha256=sJ5bJ-4DY2Rs3gAJO6Bcjm0N9naGVwKO_5wtpwJq_SM,5739
|
|
27
|
+
bocai_cli/commands/card.py,sha256=pj_32H4Q2xBQ4X11fYaEs4lPNA-PEoYtEsV_v9WYtC4,19566
|
|
28
|
+
bocai_cli/commands/center.py,sha256=nT42odMuVPRDDC3K0ONpDM4MsH7IM2A8KvitxHIvN-A,3684
|
|
29
|
+
bocai_cli/commands/checkin.py,sha256=xpD80uyHu4SgVCe5pSmsnaeQkl2uaHFs88Ydi38jKho,1781
|
|
30
|
+
bocai_cli/commands/course.py,sha256=VitQQeuBT-VNJVRS0_9wT-Eu5iYupFg4m6FKnl3x3dk,7876
|
|
31
|
+
bocai_cli/commands/customreport.py,sha256=Gq50LtWvVB-yjNXXBbxjs0FhA3Q9aSg2rvgTTon77Ck,1919
|
|
32
|
+
bocai_cli/commands/guanyuan.py,sha256=xJEW1h_gg44F8KFOoo7GCoQEu1SjCiXinr_QxTlnvxU,2567
|
|
33
|
+
bocai_cli/commands/member.py,sha256=5WvhqrLHHhTN1iAKUFYgJOke_SDWMxdOpL8IvkrcCdI,8231
|
|
34
|
+
bocai_cli/commands/month_dashboard.py,sha256=sm4CDZMwv1tN3s3V0W09DVPYaHfYko-CoH4HhI3t0vY,5547
|
|
35
|
+
bocai_cli/commands/qywx.py,sha256=vooHJM9R80Kh8kiBllJKadtjYJiubPWAlTxiXdi9D68,3435
|
|
36
|
+
bocai_cli/commands/registry.py,sha256=Kk9PjPOLdayuqJMET-cVGA2odMslJQUPf68BKjMTmbM,2950
|
|
37
|
+
bocai_cli/commands/report.py,sha256=u7yKBkFJnCys_btkOM5ZrhHxyxq-MYroSgJx2HEUy2k,2365
|
|
38
|
+
bocai_cli/commands/reservation.py,sha256=H7RQoKT2hxI5QRiz9K23MkqM3P-_2ambc5LMOr8Zlys,1793
|
|
39
|
+
bocai_cli_core-0.2.0.dist-info/METADATA,sha256=Mc1QYwCxnX6KYd8f2oD96dntoD4wMucWgKIZ6PU5R6o,4317
|
|
40
|
+
bocai_cli_core-0.2.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
41
|
+
bocai_cli_core-0.2.0.dist-info/licenses/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
|
42
|
+
bocai_cli_core-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2018 The Python Packaging Authority
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|