coder-firefly-cli 1.0.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.
- coder_firefly_cli-1.0.0.dist-info/METADATA +239 -0
- coder_firefly_cli-1.0.0.dist-info/RECORD +16 -0
- coder_firefly_cli-1.0.0.dist-info/WHEEL +5 -0
- coder_firefly_cli-1.0.0.dist-info/entry_points.txt +2 -0
- coder_firefly_cli-1.0.0.dist-info/top_level.txt +1 -0
- commands/__init__.py +1 -0
- commands/accounts.py +113 -0
- commands/bills.py +99 -0
- commands/budgets.py +147 -0
- commands/categories.py +79 -0
- commands/info.py +36 -0
- commands/insights.py +87 -0
- commands/piggy_banks.py +99 -0
- commands/search.py +25 -0
- commands/tags.py +100 -0
- commands/transactions.py +151 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: coder-firefly-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Firefly III CLI - 个人财务管理命令行工具
|
|
5
|
+
Home-page: https://github.com/joyous-coder/coder-firefly-cli
|
|
6
|
+
Author: coder
|
|
7
|
+
Author-email: coder@example.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: firefly-iii cli finance personal-finance
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: click>=8.0
|
|
21
|
+
Requires-Dist: requests>=2.25
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
25
|
+
Requires-Dist: responses>=0.25; extra == "dev"
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: home-page
|
|
32
|
+
Dynamic: keywords
|
|
33
|
+
Dynamic: license
|
|
34
|
+
Dynamic: provides-extra
|
|
35
|
+
Dynamic: requires-dist
|
|
36
|
+
Dynamic: requires-python
|
|
37
|
+
Dynamic: summary
|
|
38
|
+
|
|
39
|
+
# coder-firefly-cli
|
|
40
|
+
|
|
41
|
+
Firefly III CLI - 个人财务管理命令行工具
|
|
42
|
+
|
|
43
|
+
## 安装
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 前提条件
|
|
50
|
+
|
|
51
|
+
- Python 3.10+
|
|
52
|
+
- 运行中的 Firefly III 实例
|
|
53
|
+
- Personal Access Token (PAT)
|
|
54
|
+
|
|
55
|
+
## 配置
|
|
56
|
+
|
|
57
|
+
### 环境变量(推荐)
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
export FIREFLY_BASE_URL="https://firefly.yourdomain.com"
|
|
61
|
+
export FIREFLY_PAT="your-personal-access-token"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 命令行参数
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
coder-firefly-cli --base-url https://firefly.yourdomain.com --pat your-token accounts list
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 使用方法
|
|
71
|
+
|
|
72
|
+
### 账户管理
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# 列出账户
|
|
76
|
+
coder-firefly-cli accounts list
|
|
77
|
+
coder-firefly-cli accounts list --type asset
|
|
78
|
+
|
|
79
|
+
# 获取账户详情
|
|
80
|
+
coder-firefly-cli accounts get --id 123
|
|
81
|
+
|
|
82
|
+
# 创建账户
|
|
83
|
+
coder-firefly-cli accounts create --name "现金" --type asset --currency-code CNY
|
|
84
|
+
|
|
85
|
+
# 更新账户
|
|
86
|
+
coder-firefly-cli accounts update --id 123 --name "新名称"
|
|
87
|
+
|
|
88
|
+
# 删除账户
|
|
89
|
+
coder-firefly-cli accounts delete --id 123
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 交易管理
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# 列出交易
|
|
96
|
+
coder-firefly-cli transactions list
|
|
97
|
+
coder-firefly-cli transactions list --limit 10 --start 2024-01-01
|
|
98
|
+
|
|
99
|
+
# 创建支出
|
|
100
|
+
coder-firefly-cli transactions create --description "超市购物" --amount 100.00 --source-account 1
|
|
101
|
+
|
|
102
|
+
# 创建转账
|
|
103
|
+
coder-firefly-cli transactions create --description "转账" --amount 500.00 --source-account 1 --destination-account 2 --type transfer
|
|
104
|
+
|
|
105
|
+
# 获取交易详情
|
|
106
|
+
coder-firefly-cli transactions get --id 456
|
|
107
|
+
|
|
108
|
+
# 删除交易
|
|
109
|
+
coder-firefly-cli transactions delete --id 456
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 预算管理
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# 列出预算
|
|
116
|
+
coder-firefly-cli budgets list
|
|
117
|
+
|
|
118
|
+
# 创建预算
|
|
119
|
+
coder-firefly-cli budgets create --name "餐饮预算"
|
|
120
|
+
|
|
121
|
+
# 设置预算限额
|
|
122
|
+
coder-firefly-cli budgets limit-create --budget-id 1 --amount 2000 --start 2024-01-01 --end 2024-01-31
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 分类管理
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# 列出分类
|
|
129
|
+
coder-firefly-cli categories list
|
|
130
|
+
|
|
131
|
+
# 创建分类
|
|
132
|
+
coder-firefly-cli categories create --name "餐饮"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 标签管理
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# 列出标签
|
|
139
|
+
coder-firefly-cli tags list
|
|
140
|
+
|
|
141
|
+
# 创建标签
|
|
142
|
+
coder-firefly-cli tags create --tag "重要"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 账单管理
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# 列出账单
|
|
149
|
+
coder-firefly-cli bills list
|
|
150
|
+
|
|
151
|
+
# 创建账单
|
|
152
|
+
coder-firefly-cli bills create --name "房租" --amount-min 3000 --amount-max 3000 --date 2024-01-01
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 储蓄罐管理
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# 列出储蓄罐
|
|
159
|
+
coder-firefly-cli piggy-banks list
|
|
160
|
+
|
|
161
|
+
# 创建储蓄罐
|
|
162
|
+
coder-firefly-cli piggy-banks create --name "旅行基金" --account-id 1 --target-amount 10000
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 搜索
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# 搜索交易
|
|
169
|
+
coder-firefly-cli search transactions --query "超市"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 洞察报告
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# 支出洞察
|
|
176
|
+
coder-firefly-cli insights expense --start 2024-01-01 --end 2024-01-31
|
|
177
|
+
|
|
178
|
+
# 收入洞察
|
|
179
|
+
coder-firefly-cli insights income --start 2024-01-01 --end 2024-01-31
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 系统信息
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# 获取系统信息
|
|
186
|
+
coder-firefly-cli info about
|
|
187
|
+
|
|
188
|
+
# 检查连接状态
|
|
189
|
+
coder-firefly-cli info status
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## JSON 输出
|
|
193
|
+
|
|
194
|
+
所有命令都支持 `--json` 参数以结构化格式输出:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
coder-firefly-cli --json accounts list
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## 故障排除
|
|
201
|
+
|
|
202
|
+
### 连接失败
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
错误: 无法连接到 Firefly III 实例
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
检查:
|
|
209
|
+
1. Firefly III 实例是否正在运行
|
|
210
|
+
2. 基础 URL 是否正确
|
|
211
|
+
3. 网络连接是否正常
|
|
212
|
+
|
|
213
|
+
### 认证失败
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
错误: 认证失败: Personal Access Token 无效
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
检查:
|
|
220
|
+
1. PAT 是否正确
|
|
221
|
+
2. PAT 是否已过期
|
|
222
|
+
3. 在 Firefly III 的 选项 > 个人资料 > OAuth 中生成新的 PAT
|
|
223
|
+
|
|
224
|
+
## 开发
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
# 安装依赖
|
|
228
|
+
pip install -e ".[dev]"
|
|
229
|
+
|
|
230
|
+
# 运行测试
|
|
231
|
+
pytest
|
|
232
|
+
|
|
233
|
+
# 代码格式化
|
|
234
|
+
black src/
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## 许可证
|
|
238
|
+
|
|
239
|
+
MIT License
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
commands/__init__.py,sha256=CWZPsSgPbJkjvUJRTnSOoNvHzClpKAHxT1ZsYOfTxXg,33
|
|
2
|
+
commands/accounts.py,sha256=ecNFiVwi2nLE8oqsvGLp5bRVhbVtTSw-vSmIxTU1wOA,3270
|
|
3
|
+
commands/bills.py,sha256=DmjqmNVmBSxxso1cokZOxoGsuU3Xofm0Sg06x7qwv2M,2782
|
|
4
|
+
commands/budgets.py,sha256=S48263TalETyO_4qmUL1qTs7GV1sQFoMA3ZHy4Hhgos,3955
|
|
5
|
+
commands/categories.py,sha256=sOwaIym29mebnSFf8z6spDyPPpPeX-rRuU2tI8E_chY,1946
|
|
6
|
+
commands/info.py,sha256=TAeZOaGSKyQPhCVP40ZwekLxh396T5uXYc2aaRI5ClI,899
|
|
7
|
+
commands/insights.py,sha256=PgsiyE6X2zLn7SSRdCVA5CW0ptYBSLDI5ElANo0YM0Y,2449
|
|
8
|
+
commands/piggy_banks.py,sha256=zsqQyjB8-nfZh2ycr_MiuH6bqxwSzq7jdpJs8D9WtmI,2949
|
|
9
|
+
commands/search.py,sha256=SyBIdrHAXOdA6nhIckWXaFPF4sn9ujqC9qtd2w8lO3I,571
|
|
10
|
+
commands/tags.py,sha256=Gd7ir05CMrOKdpgjqIOdbFS4uVE2BS0p0ltY8T1pOvg,2564
|
|
11
|
+
commands/transactions.py,sha256=QEjtg259YU_qfC5uZ-pmWCuAG-Mxxah-1vdcdjo2gqM,4820
|
|
12
|
+
coder_firefly_cli-1.0.0.dist-info/METADATA,sha256=17ZZimvUAv7ZRsAOTaj_S98hloxf0NxqSKG_-yA02Nk,4759
|
|
13
|
+
coder_firefly_cli-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
14
|
+
coder_firefly_cli-1.0.0.dist-info/entry_points.txt,sha256=KG7AR_EGrOib73vpbyAP-W79pyIcbsUQRXcZV1lEG_g,47
|
|
15
|
+
coder_firefly_cli-1.0.0.dist-info/top_level.txt,sha256=6dNUbRvZAy7fLWNbD4YeUosLYwGk-SVew-Swd5CfFkg,9
|
|
16
|
+
coder_firefly_cli-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
commands
|
commands/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# coder-firefly-cli 命令模块
|
commands/accounts.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
账户管理命令组
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from cli import get_client, output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def accounts():
|
|
11
|
+
"""管理账户"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@accounts.command(name="list")
|
|
16
|
+
@click.option("--type",
|
|
17
|
+
type=click.Choice(['asset', 'expense', 'revenue', 'liability', 'all']),
|
|
18
|
+
default='all',
|
|
19
|
+
help="按账户类型筛选")
|
|
20
|
+
@click.option("--limit", default=50, help="限制结果数量")
|
|
21
|
+
@click.option("--page", default=1, help="页码")
|
|
22
|
+
def accounts_list(type, limit, page):
|
|
23
|
+
"""列出所有账户"""
|
|
24
|
+
client = get_client()
|
|
25
|
+
params = {"limit": limit, "page": page}
|
|
26
|
+
if type != 'all':
|
|
27
|
+
params["type"] = type
|
|
28
|
+
|
|
29
|
+
result = client.get_accounts(params)
|
|
30
|
+
output(result)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@accounts.command(name="get")
|
|
34
|
+
@click.option("--id", required=True, type=int, help="账户 ID")
|
|
35
|
+
def accounts_get(id):
|
|
36
|
+
"""获取账户详情"""
|
|
37
|
+
client = get_client()
|
|
38
|
+
result = client.get_account(id)
|
|
39
|
+
output(result)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@accounts.command(name="create")
|
|
43
|
+
@click.option("--name", required=True, help="账户名称")
|
|
44
|
+
@click.option("--type",
|
|
45
|
+
required=True,
|
|
46
|
+
type=click.Choice(['asset', 'expense', 'revenue', 'liability']),
|
|
47
|
+
help="账户类型")
|
|
48
|
+
@click.option("--currency-code", default="USD", help="货币代码 (ISO 4217)")
|
|
49
|
+
@click.option("--opening-balance", default="0", help="初始余额")
|
|
50
|
+
@click.option("--account-role", help="账户角色 (用于资产账户)")
|
|
51
|
+
@click.option("--iban", help="IBAN")
|
|
52
|
+
@click.option("--bic", help="BIC")
|
|
53
|
+
@click.option("--account-number", help="账户号码")
|
|
54
|
+
@click.option("--notes", help="备注")
|
|
55
|
+
def accounts_create(name, type, currency_code, opening_balance, account_role, iban, bic, account_number, notes):
|
|
56
|
+
"""创建新账户"""
|
|
57
|
+
client = get_client()
|
|
58
|
+
|
|
59
|
+
data = {
|
|
60
|
+
"name": name,
|
|
61
|
+
"type": type,
|
|
62
|
+
"currency_code": currency_code,
|
|
63
|
+
"opening_balance": opening_balance,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if account_role:
|
|
67
|
+
data["account_role"] = account_role
|
|
68
|
+
if iban:
|
|
69
|
+
data["iban"] = iban
|
|
70
|
+
if bic:
|
|
71
|
+
data["bic"] = bic
|
|
72
|
+
if account_number:
|
|
73
|
+
data["account_number"] = account_number
|
|
74
|
+
if notes:
|
|
75
|
+
data["notes"] = notes
|
|
76
|
+
|
|
77
|
+
result = client.create_account(data)
|
|
78
|
+
output(result)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@accounts.command(name="update")
|
|
82
|
+
@click.option("--id", required=True, type=int, help="账户 ID")
|
|
83
|
+
@click.option("--name", help="账户名称")
|
|
84
|
+
@click.option("--opening-balance", help="初始余额")
|
|
85
|
+
@click.option("--notes", help="备注")
|
|
86
|
+
def accounts_update(id, name, opening_balance, notes):
|
|
87
|
+
"""更新现有账户"""
|
|
88
|
+
client = get_client()
|
|
89
|
+
|
|
90
|
+
data = {}
|
|
91
|
+
if name:
|
|
92
|
+
data["name"] = name
|
|
93
|
+
if opening_balance:
|
|
94
|
+
data["opening_balance"] = opening_balance
|
|
95
|
+
if notes:
|
|
96
|
+
data["notes"] = notes
|
|
97
|
+
|
|
98
|
+
if not data:
|
|
99
|
+
click.echo("错误: 至少需要提供一个更新字段", err=True)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
result = client.update_account(id, data)
|
|
103
|
+
output(result)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@accounts.command(name="delete")
|
|
107
|
+
@click.option("--id", required=True, type=int, help="账户 ID")
|
|
108
|
+
@click.confirmation_option(prompt="确定要删除此账户吗?")
|
|
109
|
+
def accounts_delete(id):
|
|
110
|
+
"""删除账户"""
|
|
111
|
+
client = get_client()
|
|
112
|
+
result = client.delete_account(id)
|
|
113
|
+
output(result)
|
commands/bills.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
账单管理命令组
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from cli import get_client, output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def bills():
|
|
11
|
+
"""管理账单"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@bills.command(name="list")
|
|
16
|
+
@click.option("--limit", default=50, help="限制结果数量")
|
|
17
|
+
@click.option("--page", default=1, help="页码")
|
|
18
|
+
def bills_list(limit, page):
|
|
19
|
+
"""列出所有账单"""
|
|
20
|
+
client = get_client()
|
|
21
|
+
params = {"limit": limit, "page": page}
|
|
22
|
+
result = client.get_bills(params)
|
|
23
|
+
output(result)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@bills.command(name="get")
|
|
27
|
+
@click.option("--id", required=True, type=int, help="账单 ID")
|
|
28
|
+
def bills_get(id):
|
|
29
|
+
"""获取账单详情"""
|
|
30
|
+
client = get_client()
|
|
31
|
+
result = client.get_bill(id)
|
|
32
|
+
output(result)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@bills.command(name="create")
|
|
36
|
+
@click.option("--name", required=True, help="账单名称")
|
|
37
|
+
@click.option("--amount-min", required=True, help="最小金额")
|
|
38
|
+
@click.option("--amount-max", required=True, help="最大金额")
|
|
39
|
+
@click.option("--date", required=True, help="账单日期 (YYYY-MM-DD)")
|
|
40
|
+
@click.option("--repeat-freq", default="monthly", help="重复频率")
|
|
41
|
+
@click.option("--skip", default=0, help="跳过次数")
|
|
42
|
+
@click.option("--currency-code", default="USD", help="货币代码")
|
|
43
|
+
@click.option("--notes", help="备注")
|
|
44
|
+
def bills_create(name, amount_min, amount_max, date, repeat_freq, skip, currency_code, notes):
|
|
45
|
+
"""创建新账单"""
|
|
46
|
+
client = get_client()
|
|
47
|
+
|
|
48
|
+
data = {
|
|
49
|
+
"name": name,
|
|
50
|
+
"amount_min": amount_min,
|
|
51
|
+
"amount_max": amount_max,
|
|
52
|
+
"date": date,
|
|
53
|
+
"repeat_freq": repeat_freq,
|
|
54
|
+
"skip": skip,
|
|
55
|
+
"currency_code": currency_code,
|
|
56
|
+
}
|
|
57
|
+
if notes:
|
|
58
|
+
data["notes"] = notes
|
|
59
|
+
|
|
60
|
+
result = client.create_bill(data)
|
|
61
|
+
output(result)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@bills.command(name="update")
|
|
65
|
+
@click.option("--id", required=True, type=int, help="账单 ID")
|
|
66
|
+
@click.option("--name", help="账单名称")
|
|
67
|
+
@click.option("--amount-min", help="最小金额")
|
|
68
|
+
@click.option("--amount-max", help="最大金额")
|
|
69
|
+
@click.option("--notes", help="备注")
|
|
70
|
+
def bills_update(id, name, amount_min, amount_max, notes):
|
|
71
|
+
"""更新现有账单"""
|
|
72
|
+
client = get_client()
|
|
73
|
+
|
|
74
|
+
data = {}
|
|
75
|
+
if name:
|
|
76
|
+
data["name"] = name
|
|
77
|
+
if amount_min:
|
|
78
|
+
data["amount_min"] = amount_min
|
|
79
|
+
if amount_max:
|
|
80
|
+
data["amount_max"] = amount_max
|
|
81
|
+
if notes:
|
|
82
|
+
data["notes"] = notes
|
|
83
|
+
|
|
84
|
+
if not data:
|
|
85
|
+
click.echo("错误: 至少需要提供一个更新字段", err=True)
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
result = client.update_bill(id, data)
|
|
89
|
+
output(result)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@bills.command(name="delete")
|
|
93
|
+
@click.option("--id", required=True, type=int, help="账单 ID")
|
|
94
|
+
@click.confirmation_option(prompt="确定要删除此账单吗?")
|
|
95
|
+
def bills_delete(id):
|
|
96
|
+
"""删除账单"""
|
|
97
|
+
client = get_client()
|
|
98
|
+
result = client.delete_bill(id)
|
|
99
|
+
output(result)
|
commands/budgets.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
预算管理命令组
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from cli import get_client, output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def budgets():
|
|
11
|
+
"""管理预算"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@budgets.command(name="list")
|
|
16
|
+
@click.option("--limit", default=50, help="限制结果数量")
|
|
17
|
+
@click.option("--page", default=1, help="页码")
|
|
18
|
+
def budgets_list(limit, page):
|
|
19
|
+
"""列出所有预算"""
|
|
20
|
+
client = get_client()
|
|
21
|
+
params = {"limit": limit, "page": page}
|
|
22
|
+
result = client.get_budgets(params)
|
|
23
|
+
output(result)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@budgets.command(name="get")
|
|
27
|
+
@click.option("--id", required=True, type=int, help="预算 ID")
|
|
28
|
+
def budgets_get(id):
|
|
29
|
+
"""获取预算详情"""
|
|
30
|
+
client = get_client()
|
|
31
|
+
result = client.get_budget(id)
|
|
32
|
+
output(result)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@budgets.command(name="create")
|
|
36
|
+
@click.option("--name", required=True, help="预算名称")
|
|
37
|
+
@click.option("--notes", help="备注")
|
|
38
|
+
def budgets_create(name, notes):
|
|
39
|
+
"""创建新预算"""
|
|
40
|
+
client = get_client()
|
|
41
|
+
|
|
42
|
+
data = {"name": name}
|
|
43
|
+
if notes:
|
|
44
|
+
data["notes"] = notes
|
|
45
|
+
|
|
46
|
+
result = client.create_budget(data)
|
|
47
|
+
output(result)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@budgets.command(name="update")
|
|
51
|
+
@click.option("--id", required=True, type=int, help="预算 ID")
|
|
52
|
+
@click.option("--name", help="预算名称")
|
|
53
|
+
@click.option("--notes", help="备注")
|
|
54
|
+
def budgets_update(id, name, notes):
|
|
55
|
+
"""更新现有预算"""
|
|
56
|
+
client = get_client()
|
|
57
|
+
|
|
58
|
+
data = {}
|
|
59
|
+
if name:
|
|
60
|
+
data["name"] = name
|
|
61
|
+
if notes:
|
|
62
|
+
data["notes"] = notes
|
|
63
|
+
|
|
64
|
+
if not data:
|
|
65
|
+
click.echo("错误: 至少需要提供一个更新字段", err=True)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
result = client.update_budget(id, data)
|
|
69
|
+
output(result)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@budgets.command(name="delete")
|
|
73
|
+
@click.option("--id", required=True, type=int, help="预算 ID")
|
|
74
|
+
@click.confirmation_option(prompt="确定要删除此预算吗?")
|
|
75
|
+
def budgets_delete(id):
|
|
76
|
+
"""删除预算"""
|
|
77
|
+
client = get_client()
|
|
78
|
+
result = client.delete_budget(id)
|
|
79
|
+
output(result)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ========== 预算限额 ==========
|
|
83
|
+
|
|
84
|
+
@budgets.command(name="limits")
|
|
85
|
+
@click.option("--budget-id", required=True, type=int, help="预算 ID")
|
|
86
|
+
@click.option("--start", help="开始日期 (YYYY-MM-DD)")
|
|
87
|
+
@click.option("--end", help="结束日期 (YYYY-MM-DD)")
|
|
88
|
+
def budgets_limits(budget_id, start, end):
|
|
89
|
+
"""列出预算限额"""
|
|
90
|
+
client = get_client()
|
|
91
|
+
params = {}
|
|
92
|
+
if start:
|
|
93
|
+
params["start"] = start
|
|
94
|
+
if end:
|
|
95
|
+
params["end"] = end
|
|
96
|
+
result = client.get_budget_limits(budget_id, params)
|
|
97
|
+
output(result)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@budgets.command(name="limit-create")
|
|
101
|
+
@click.option("--budget-id", required=True, type=int, help="预算 ID")
|
|
102
|
+
@click.option("--amount", required=True, help="金额")
|
|
103
|
+
@click.option("--start", required=True, help="开始日期 (YYYY-MM-DD)")
|
|
104
|
+
@click.option("--end", required=True, help="结束日期 (YYYY-MM-DD)")
|
|
105
|
+
@click.option("--currency-code", default="USD", help="货币代码")
|
|
106
|
+
def budgets_limit_create(budget_id, amount, start, end, currency_code):
|
|
107
|
+
"""创建预算限额"""
|
|
108
|
+
client = get_client()
|
|
109
|
+
|
|
110
|
+
data = {
|
|
111
|
+
"amount": amount,
|
|
112
|
+
"start": start,
|
|
113
|
+
"end": end,
|
|
114
|
+
"currency_id": currency_code,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
result = client.create_budget_limit(budget_id, data)
|
|
118
|
+
output(result)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@budgets.command(name="limit-update")
|
|
122
|
+
@click.option("--id", required=True, type=int, help="预算限额 ID")
|
|
123
|
+
@click.option("--amount", help="金额")
|
|
124
|
+
def budgets_limit_update(id, amount):
|
|
125
|
+
"""更新预算限额"""
|
|
126
|
+
client = get_client()
|
|
127
|
+
|
|
128
|
+
data = {}
|
|
129
|
+
if amount:
|
|
130
|
+
data["amount"] = amount
|
|
131
|
+
|
|
132
|
+
if not data:
|
|
133
|
+
click.echo("错误: 金额是必填项", err=True)
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
result = client.update_budget_limit(id, data)
|
|
137
|
+
output(result)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@budgets.command(name="limit-delete")
|
|
141
|
+
@click.option("--id", required=True, type=int, help="预算限额 ID")
|
|
142
|
+
@click.confirmation_option(prompt="确定要删除此预算限额吗?")
|
|
143
|
+
def budgets_limit_delete(id):
|
|
144
|
+
"""删除预算限额"""
|
|
145
|
+
client = get_client()
|
|
146
|
+
result = client.delete_budget_limit(id)
|
|
147
|
+
output(result)
|
commands/categories.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
分类管理命令组
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from cli import get_client, output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def categories():
|
|
11
|
+
"""管理分类"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@categories.command(name="list")
|
|
16
|
+
@click.option("--limit", default=50, help="限制结果数量")
|
|
17
|
+
@click.option("--page", default=1, help="页码")
|
|
18
|
+
def categories_list(limit, page):
|
|
19
|
+
"""列出所有分类"""
|
|
20
|
+
client = get_client()
|
|
21
|
+
params = {"limit": limit, "page": page}
|
|
22
|
+
result = client.get_categories(params)
|
|
23
|
+
output(result)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@categories.command(name="get")
|
|
27
|
+
@click.option("--id", required=True, type=int, help="分类 ID")
|
|
28
|
+
def categories_get(id):
|
|
29
|
+
"""获取分类详情"""
|
|
30
|
+
client = get_client()
|
|
31
|
+
result = client.get_category(id)
|
|
32
|
+
output(result)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@categories.command(name="create")
|
|
36
|
+
@click.option("--name", required=True, help="分类名称")
|
|
37
|
+
@click.option("--notes", help="备注")
|
|
38
|
+
def categories_create(name, notes):
|
|
39
|
+
"""创建新分类"""
|
|
40
|
+
client = get_client()
|
|
41
|
+
|
|
42
|
+
data = {"name": name}
|
|
43
|
+
if notes:
|
|
44
|
+
data["notes"] = notes
|
|
45
|
+
|
|
46
|
+
result = client.create_category(data)
|
|
47
|
+
output(result)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@categories.command(name="update")
|
|
51
|
+
@click.option("--id", required=True, type=int, help="分类 ID")
|
|
52
|
+
@click.option("--name", help="分类名称")
|
|
53
|
+
@click.option("--notes", help="备注")
|
|
54
|
+
def categories_update(id, name, notes):
|
|
55
|
+
"""更新现有分类"""
|
|
56
|
+
client = get_client()
|
|
57
|
+
|
|
58
|
+
data = {}
|
|
59
|
+
if name:
|
|
60
|
+
data["name"] = name
|
|
61
|
+
if notes:
|
|
62
|
+
data["notes"] = notes
|
|
63
|
+
|
|
64
|
+
if not data:
|
|
65
|
+
click.echo("错误: 至少需要提供一个更新字段", err=True)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
result = client.update_category(id, data)
|
|
69
|
+
output(result)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@categories.command(name="delete")
|
|
73
|
+
@click.option("--id", required=True, type=int, help="分类 ID")
|
|
74
|
+
@click.confirmation_option(prompt="确定要删除此分类吗?")
|
|
75
|
+
def categories_delete(id):
|
|
76
|
+
"""删除分类"""
|
|
77
|
+
client = get_client()
|
|
78
|
+
result = client.delete_category(id)
|
|
79
|
+
output(result)
|
commands/info.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
系统信息命令组
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from cli import get_client, output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def info():
|
|
11
|
+
"""系统信息"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@info.command(name="about")
|
|
16
|
+
def info_about():
|
|
17
|
+
"""获取 Firefly III 系统信息"""
|
|
18
|
+
client = get_client()
|
|
19
|
+
result = client.get_about()
|
|
20
|
+
output(result)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@info.command(name="status")
|
|
24
|
+
def info_status():
|
|
25
|
+
"""检查 Firefly III 连接状态"""
|
|
26
|
+
try:
|
|
27
|
+
client = get_client()
|
|
28
|
+
result = client.get_about()
|
|
29
|
+
click.echo("Firefly III 连接正常")
|
|
30
|
+
if 'data' in result:
|
|
31
|
+
attrs = result['data'].get('attributes', {})
|
|
32
|
+
click.echo(f" 版本: {attrs.get('version', 'N/A')}")
|
|
33
|
+
click.echo(f" API 版本: {attrs.get('api_version', 'N/A')}")
|
|
34
|
+
click.echo(f" 环境: {attrs.get('environment', 'N/A')}")
|
|
35
|
+
except Exception as e:
|
|
36
|
+
click.echo(f"连接失败: {e}", err=True)
|
commands/insights.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
洞察命令组
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from cli import get_client, output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def insights():
|
|
11
|
+
"""查看财务洞察"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@insights.command(name="expense")
|
|
16
|
+
@click.option("--start", help="开始日期 (YYYY-MM-DD)")
|
|
17
|
+
@click.option("--end", help="结束日期 (YYYY-MM-DD)")
|
|
18
|
+
@click.option("--accounts", help="账户 ID (逗号分隔)")
|
|
19
|
+
@click.option("--categories", help="分类 ID (逗号分隔)")
|
|
20
|
+
@click.option("--budgets", help="预算 ID (逗号分隔)")
|
|
21
|
+
@click.option("--tags", help="标签 (逗号分隔)")
|
|
22
|
+
def insights_expense(start, end, accounts, categories, budgets, tags):
|
|
23
|
+
"""支出洞察"""
|
|
24
|
+
client = get_client()
|
|
25
|
+
params = {}
|
|
26
|
+
if start:
|
|
27
|
+
params["start"] = start
|
|
28
|
+
if end:
|
|
29
|
+
params["end"] = end
|
|
30
|
+
if accounts:
|
|
31
|
+
params["accounts"] = accounts
|
|
32
|
+
if categories:
|
|
33
|
+
params["categories"] = categories
|
|
34
|
+
if budgets:
|
|
35
|
+
params["budgets"] = budgets
|
|
36
|
+
if tags:
|
|
37
|
+
params["tags"] = tags
|
|
38
|
+
|
|
39
|
+
result = client.get_insight("expense", params)
|
|
40
|
+
output(result)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@insights.command(name="income")
|
|
44
|
+
@click.option("--start", help="开始日期 (YYYY-MM-DD)")
|
|
45
|
+
@click.option("--end", help="结束日期 (YYYY-MM-DD)")
|
|
46
|
+
@click.option("--accounts", help="账户 ID (逗号分隔)")
|
|
47
|
+
@click.option("--categories", help="分类 ID (逗号分隔)")
|
|
48
|
+
@click.option("--tags", help="标签 (逗号分隔)")
|
|
49
|
+
def insights_income(start, end, accounts, categories, tags):
|
|
50
|
+
"""收入洞察"""
|
|
51
|
+
client = get_client()
|
|
52
|
+
params = {}
|
|
53
|
+
if start:
|
|
54
|
+
params["start"] = start
|
|
55
|
+
if end:
|
|
56
|
+
params["end"] = end
|
|
57
|
+
if accounts:
|
|
58
|
+
params["accounts"] = accounts
|
|
59
|
+
if categories:
|
|
60
|
+
params["categories"] = categories
|
|
61
|
+
if tags:
|
|
62
|
+
params["tags"] = tags
|
|
63
|
+
|
|
64
|
+
result = client.get_insight("income", params)
|
|
65
|
+
output(result)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@insights.command(name="transfer")
|
|
69
|
+
@click.option("--start", help="开始日期 (YYYY-MM-DD)")
|
|
70
|
+
@click.option("--end", help="结束日期 (YYYY-MM-DD)")
|
|
71
|
+
@click.option("--accounts", help="账户 ID (逗号分隔)")
|
|
72
|
+
@click.option("--tags", help="标签 (逗号分隔)")
|
|
73
|
+
def insights_transfer(start, end, accounts, tags):
|
|
74
|
+
"""转账洞察"""
|
|
75
|
+
client = get_client()
|
|
76
|
+
params = {}
|
|
77
|
+
if start:
|
|
78
|
+
params["start"] = start
|
|
79
|
+
if end:
|
|
80
|
+
params["end"] = end
|
|
81
|
+
if accounts:
|
|
82
|
+
params["accounts"] = accounts
|
|
83
|
+
if tags:
|
|
84
|
+
params["tags"] = tags
|
|
85
|
+
|
|
86
|
+
result = client.get_insight("transfer", params)
|
|
87
|
+
output(result)
|
commands/piggy_banks.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
储蓄罐管理命令组
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from cli import get_client, output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def piggy_banks():
|
|
11
|
+
"""管理储蓄罐"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@piggy_banks.command(name="list")
|
|
16
|
+
@click.option("--limit", default=50, help="限制结果数量")
|
|
17
|
+
@click.option("--page", default=1, help="页码")
|
|
18
|
+
def piggy_banks_list(limit, page):
|
|
19
|
+
"""列出所有储蓄罐"""
|
|
20
|
+
client = get_client()
|
|
21
|
+
params = {"limit": limit, "page": page}
|
|
22
|
+
result = client.get_piggy_banks(params)
|
|
23
|
+
output(result)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@piggy_banks.command(name="get")
|
|
27
|
+
@click.option("--id", required=True, type=int, help="储蓄罐 ID")
|
|
28
|
+
def piggy_banks_get(id):
|
|
29
|
+
"""获取储蓄罐详情"""
|
|
30
|
+
client = get_client()
|
|
31
|
+
result = client.get_piggy_bank(id)
|
|
32
|
+
output(result)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@piggy_banks.command(name="create")
|
|
36
|
+
@click.option("--name", required=True, help="储蓄罐名称")
|
|
37
|
+
@click.option("--account-id", required=True, type=int, help="关联账户 ID")
|
|
38
|
+
@click.option("--target-amount", required=True, help="目标金额")
|
|
39
|
+
@click.option("--current-amount", default="0", help="当前金额")
|
|
40
|
+
@click.option("--start-date", help="开始日期 (YYYY-MM-DD)")
|
|
41
|
+
@click.option("--target-date", help="目标日期 (YYYY-MM-DD)")
|
|
42
|
+
@click.option("--notes", help="备注")
|
|
43
|
+
def piggy_banks_create(name, account_id, target_amount, current_amount, start_date, target_date, notes):
|
|
44
|
+
"""创建新储蓄罐"""
|
|
45
|
+
client = get_client()
|
|
46
|
+
|
|
47
|
+
data = {
|
|
48
|
+
"name": name,
|
|
49
|
+
"account_id": account_id,
|
|
50
|
+
"target_amount": target_amount,
|
|
51
|
+
"current_amount": current_amount,
|
|
52
|
+
}
|
|
53
|
+
if start_date:
|
|
54
|
+
data["start_date"] = start_date
|
|
55
|
+
if target_date:
|
|
56
|
+
data["target_date"] = target_date
|
|
57
|
+
if notes:
|
|
58
|
+
data["notes"] = notes
|
|
59
|
+
|
|
60
|
+
result = client.create_piggy_bank(data)
|
|
61
|
+
output(result)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@piggy_banks.command(name="update")
|
|
65
|
+
@click.option("--id", required=True, type=int, help="储蓄罐 ID")
|
|
66
|
+
@click.option("--name", help="储蓄罐名称")
|
|
67
|
+
@click.option("--target-amount", help="目标金额")
|
|
68
|
+
@click.option("--current-amount", help="当前金额")
|
|
69
|
+
@click.option("--notes", help="备注")
|
|
70
|
+
def piggy_banks_update(id, name, target_amount, current_amount, notes):
|
|
71
|
+
"""更新现有储蓄罐"""
|
|
72
|
+
client = get_client()
|
|
73
|
+
|
|
74
|
+
data = {}
|
|
75
|
+
if name:
|
|
76
|
+
data["name"] = name
|
|
77
|
+
if target_amount:
|
|
78
|
+
data["target_amount"] = target_amount
|
|
79
|
+
if current_amount:
|
|
80
|
+
data["current_amount"] = current_amount
|
|
81
|
+
if notes:
|
|
82
|
+
data["notes"] = notes
|
|
83
|
+
|
|
84
|
+
if not data:
|
|
85
|
+
click.echo("错误: 至少需要提供一个更新字段", err=True)
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
result = client.update_piggy_bank(id, data)
|
|
89
|
+
output(result)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@piggy_banks.command(name="delete")
|
|
93
|
+
@click.option("--id", required=True, type=int, help="储蓄罐 ID")
|
|
94
|
+
@click.confirmation_option(prompt="确定要删除此储蓄罐吗?")
|
|
95
|
+
def piggy_banks_delete(id):
|
|
96
|
+
"""删除储蓄罐"""
|
|
97
|
+
client = get_client()
|
|
98
|
+
result = client.delete_piggy_bank(id)
|
|
99
|
+
output(result)
|
commands/search.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
搜索命令组
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from cli import get_client, output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def search():
|
|
11
|
+
"""搜索交易"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@search.command(name="transactions")
|
|
16
|
+
@click.option("--query", required=True, help="搜索关键词")
|
|
17
|
+
@click.option("--limit", default=50, help="限制结果数量")
|
|
18
|
+
@click.option("--page", default=1, help="页码")
|
|
19
|
+
def search_transactions(query, limit, page):
|
|
20
|
+
"""搜索交易"""
|
|
21
|
+
client = get_client()
|
|
22
|
+
params = {"limit": limit, "page": page}
|
|
23
|
+
|
|
24
|
+
result = client.search_transactions(query, params)
|
|
25
|
+
output(result)
|
commands/tags.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
标签管理命令组
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from cli import get_client, output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def tags():
|
|
11
|
+
"""管理标签"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@tags.command(name="list")
|
|
16
|
+
@click.option("--limit", default=50, help="限制结果数量")
|
|
17
|
+
@click.option("--page", default=1, help="页码")
|
|
18
|
+
def tags_list(limit, page):
|
|
19
|
+
"""列出所有标签"""
|
|
20
|
+
client = get_client()
|
|
21
|
+
params = {"limit": limit, "page": page}
|
|
22
|
+
result = client.get_tags(params)
|
|
23
|
+
output(result)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@tags.command(name="get")
|
|
27
|
+
@click.option("--id", required=True, help="标签 ID")
|
|
28
|
+
def tags_get(id):
|
|
29
|
+
"""获取标签详情"""
|
|
30
|
+
client = get_client()
|
|
31
|
+
result = client.get_tag(id)
|
|
32
|
+
output(result)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@tags.command(name="create")
|
|
36
|
+
@click.option("--tag", required=True, help="标签名称")
|
|
37
|
+
@click.option("--date", help="日期 (YYYY-MM-DD)")
|
|
38
|
+
@click.option("--description", help="描述")
|
|
39
|
+
@click.option("--latitude", help="纬度")
|
|
40
|
+
@click.option("--longitude", help="经度")
|
|
41
|
+
@click.option("--zoom", help="缩放级别")
|
|
42
|
+
@click.option("--notes", help="备注")
|
|
43
|
+
def tags_create(tag, date, description, latitude, longitude, zoom, notes):
|
|
44
|
+
"""创建新标签"""
|
|
45
|
+
client = get_client()
|
|
46
|
+
|
|
47
|
+
data = {"tag": tag}
|
|
48
|
+
if date:
|
|
49
|
+
data["date"] = date
|
|
50
|
+
if description:
|
|
51
|
+
data["description"] = description
|
|
52
|
+
if latitude:
|
|
53
|
+
data["latitude"] = latitude
|
|
54
|
+
if longitude:
|
|
55
|
+
data["longitude"] = longitude
|
|
56
|
+
if zoom:
|
|
57
|
+
data["zoom"] = zoom
|
|
58
|
+
if notes:
|
|
59
|
+
data["notes"] = notes
|
|
60
|
+
|
|
61
|
+
result = client.create_tag(data)
|
|
62
|
+
output(result)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@tags.command(name="update")
|
|
66
|
+
@click.option("--id", required=True, help="标签 ID")
|
|
67
|
+
@click.option("--tag", help="标签名称")
|
|
68
|
+
@click.option("--date", help="日期 (YYYY-MM-DD)")
|
|
69
|
+
@click.option("--description", help="描述")
|
|
70
|
+
@click.option("--notes", help="备注")
|
|
71
|
+
def tags_update(id, tag, date, description, notes):
|
|
72
|
+
"""更新现有标签"""
|
|
73
|
+
client = get_client()
|
|
74
|
+
|
|
75
|
+
data = {}
|
|
76
|
+
if tag:
|
|
77
|
+
data["tag"] = tag
|
|
78
|
+
if date:
|
|
79
|
+
data["date"] = date
|
|
80
|
+
if description:
|
|
81
|
+
data["description"] = description
|
|
82
|
+
if notes:
|
|
83
|
+
data["notes"] = notes
|
|
84
|
+
|
|
85
|
+
if not data:
|
|
86
|
+
click.echo("错误: 至少需要提供一个更新字段", err=True)
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
result = client.update_tag(id, data)
|
|
90
|
+
output(result)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@tags.command(name="delete")
|
|
94
|
+
@click.option("--id", required=True, help="标签 ID")
|
|
95
|
+
@click.confirmation_option(prompt="确定要删除此标签吗?")
|
|
96
|
+
def tags_delete(id):
|
|
97
|
+
"""删除标签"""
|
|
98
|
+
client = get_client()
|
|
99
|
+
result = client.delete_tag(id)
|
|
100
|
+
output(result)
|
commands/transactions.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
交易管理命令组
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from cli import get_client, output
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
def transactions():
|
|
12
|
+
"""管理交易"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@transactions.command(name="list")
|
|
17
|
+
@click.option("--limit", default=50, help="限制结果数量")
|
|
18
|
+
@click.option("--page", default=1, help="页码")
|
|
19
|
+
@click.option("--start", help="开始日期 (YYYY-MM-DD)")
|
|
20
|
+
@click.option("--end", help="结束日期 (YYYY-MM-DD)")
|
|
21
|
+
@click.option("--type",
|
|
22
|
+
type=click.Choice(['withdrawal', 'deposit', 'transfer']),
|
|
23
|
+
help="交易类型")
|
|
24
|
+
@click.option("--source-account", help="源账户 ID 或名称")
|
|
25
|
+
@click.option("--destination-account", help="目标账户 ID 或名称")
|
|
26
|
+
def transactions_list(limit, page, start, end, type, source_account, destination_account):
|
|
27
|
+
"""列出交易"""
|
|
28
|
+
client = get_client()
|
|
29
|
+
params = {"limit": limit, "page": page}
|
|
30
|
+
|
|
31
|
+
if start:
|
|
32
|
+
params["start"] = start
|
|
33
|
+
if end:
|
|
34
|
+
params["end"] = end
|
|
35
|
+
if type:
|
|
36
|
+
params["type"] = type
|
|
37
|
+
if source_account:
|
|
38
|
+
params["source_id"] = source_account
|
|
39
|
+
if destination_account:
|
|
40
|
+
params["destination_id"] = destination_account
|
|
41
|
+
|
|
42
|
+
result = client.get_transactions(params)
|
|
43
|
+
output(result)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@transactions.command(name="get")
|
|
47
|
+
@click.option("--id", required=True, type=int, help="交易 ID")
|
|
48
|
+
def transactions_get(id):
|
|
49
|
+
"""获取交易详情"""
|
|
50
|
+
client = get_client()
|
|
51
|
+
result = client.get_transaction(id)
|
|
52
|
+
output(result)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@transactions.command(name="create")
|
|
56
|
+
@click.option("--description", required=True, help="交易描述")
|
|
57
|
+
@click.option("--amount", required=True, help="交易金额")
|
|
58
|
+
@click.option("--source-account", required=True, help="源账户 ID")
|
|
59
|
+
@click.option("--destination-account", help="目标账户 ID (转账时需要)")
|
|
60
|
+
@click.option("--type",
|
|
61
|
+
type=click.Choice(['withdrawal', 'deposit', 'transfer']),
|
|
62
|
+
default='withdrawal',
|
|
63
|
+
help="交易类型")
|
|
64
|
+
@click.option("--date", default=lambda: datetime.now().strftime('%Y-%m-%d'),
|
|
65
|
+
help="交易日期 (YYYY-MM-DD)")
|
|
66
|
+
@click.option("--category", help="分类名称")
|
|
67
|
+
@click.option("--tags", help="标签 (逗号分隔)")
|
|
68
|
+
@click.option("--budget", help="预算名称")
|
|
69
|
+
@click.option("--notes", help="备注")
|
|
70
|
+
def transactions_create(description, amount, source_account, destination_account,
|
|
71
|
+
type, date, category, tags, budget, notes):
|
|
72
|
+
"""创建新交易"""
|
|
73
|
+
client = get_client()
|
|
74
|
+
|
|
75
|
+
transaction_data = {
|
|
76
|
+
"type": type,
|
|
77
|
+
"date": date,
|
|
78
|
+
"amount": amount,
|
|
79
|
+
"description": description,
|
|
80
|
+
"source_id": source_account,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if destination_account:
|
|
84
|
+
transaction_data["destination_id"] = destination_account
|
|
85
|
+
if category:
|
|
86
|
+
transaction_data["category_name"] = category
|
|
87
|
+
if tags:
|
|
88
|
+
transaction_data["tags"] = [tag.strip() for tag in tags.split(",")]
|
|
89
|
+
if budget:
|
|
90
|
+
transaction_data["budget_name"] = budget
|
|
91
|
+
if notes:
|
|
92
|
+
transaction_data["notes"] = notes
|
|
93
|
+
|
|
94
|
+
data = {
|
|
95
|
+
"error_if_duplicate_hash": True,
|
|
96
|
+
"error_if_duplicate_hash_v2": True,
|
|
97
|
+
"apply_rules": True,
|
|
98
|
+
"fire_webhooks": True,
|
|
99
|
+
"group_title": description,
|
|
100
|
+
"transactions": [transaction_data]
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
result = client.create_transaction(data)
|
|
104
|
+
output(result)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@transactions.command(name="update")
|
|
108
|
+
@click.option("--id", required=True, type=int, help="交易 ID")
|
|
109
|
+
@click.option("--description", help="交易描述")
|
|
110
|
+
@click.option("--amount", help="交易金额")
|
|
111
|
+
@click.option("--category", help="分类名称")
|
|
112
|
+
@click.option("--tags", help="标签 (逗号分隔)")
|
|
113
|
+
@click.option("--notes", help="备注")
|
|
114
|
+
def transactions_update(id, description, amount, category, tags, notes):
|
|
115
|
+
"""更新现有交易"""
|
|
116
|
+
client = get_client()
|
|
117
|
+
|
|
118
|
+
transaction_data = {}
|
|
119
|
+
if description:
|
|
120
|
+
transaction_data["description"] = description
|
|
121
|
+
if amount:
|
|
122
|
+
transaction_data["amount"] = amount
|
|
123
|
+
if category:
|
|
124
|
+
transaction_data["category_name"] = category
|
|
125
|
+
if tags:
|
|
126
|
+
transaction_data["tags"] = [tag.strip() for tag in tags.split(",")]
|
|
127
|
+
if notes:
|
|
128
|
+
transaction_data["notes"] = notes
|
|
129
|
+
|
|
130
|
+
if not transaction_data:
|
|
131
|
+
click.echo("错误: 至少需要提供一个更新字段", err=True)
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
data = {
|
|
135
|
+
"apply_rules": True,
|
|
136
|
+
"fire_webhooks": True,
|
|
137
|
+
"transactions": [transaction_data]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
result = client.update_transaction(id, data)
|
|
141
|
+
output(result)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@transactions.command(name="delete")
|
|
145
|
+
@click.option("--id", required=True, type=int, help="交易 ID")
|
|
146
|
+
@click.confirmation_option(prompt="确定要删除此交易吗?")
|
|
147
|
+
def transactions_delete(id):
|
|
148
|
+
"""删除交易"""
|
|
149
|
+
client = get_client()
|
|
150
|
+
result = client.delete_transaction(id)
|
|
151
|
+
output(result)
|