Fane 1.2.7__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.
- fane-1.2.7.dist-info/METADATA +14 -0
- fane-1.2.7.dist-info/RECORD +46 -0
- fane-1.2.7.dist-info/WHEEL +5 -0
- fane-1.2.7.dist-info/entry_points.txt +2 -0
- fane-1.2.7.dist-info/top_level.txt +3 -0
- ir/__init__.py +1 -0
- ir/ir.py +73 -0
- package/__init__.py +0 -0
- package/cmd/__init__.py +4 -0
- package/cmd/root.py +50 -0
- package/cmd/trans.py +32 -0
- package/compiler/__init__.py +0 -0
- package/compiler/compiler.py +71 -0
- package/config/__init__.py +2 -0
- package/config/config.py +32 -0
- package/config/init.py +24 -0
- package/enums/__init__.py +0 -0
- package/enums/const.py +6 -0
- package/parser/__init__.py +0 -0
- package/parser/ali/__init__.py +0 -0
- package/parser/ali/alipay.py +80 -0
- package/parser/paser.py +19 -0
- package/parser/utils/__init__.py +0 -0
- package/parser/utils/utils.py +10 -0
- package/parser/wechat/__init__.py +0 -0
- package/parser/wechat/wechat.py +76 -0
- package/strategy/__init__.py +0 -0
- package/strategy/template/__init__.py +0 -0
- package/strategy/template/normal.py +46 -0
- package/strategy/template/strategy.py +17 -0
- package/template/__init__.py +0 -0
- package/template/normal.j2 +16 -0
- package/template/template.py +36 -0
- provider/__init__.py +1 -0
- provider/ali/__init__.py +1 -0
- provider/ali/ali_types.py +38 -0
- provider/ali/alipay.py +89 -0
- provider/ali/converter.py +73 -0
- provider/ali/processor.py +94 -0
- provider/ali/rules.py +48 -0
- provider/provider.py +9 -0
- provider/wechat/__init__.py +0 -0
- provider/wechat/converter.py +70 -0
- provider/wechat/rules.py +48 -0
- provider/wechat/wecaht_types.py +34 -0
- provider/wechat/wechat.py +65 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Fane
|
|
3
|
+
Version: 1.2.7
|
|
4
|
+
Summary: bill-generater
|
|
5
|
+
License: MIT
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: requests
|
|
8
|
+
Requires-Dist: typer
|
|
9
|
+
Requires-Dist: pandas
|
|
10
|
+
Requires-Dist: openpyxl
|
|
11
|
+
Requires-Dist: beautifulsoup4
|
|
12
|
+
Requires-Dist: pydantic
|
|
13
|
+
Requires-Dist: jinja2
|
|
14
|
+
Requires-Dist: pyyaml
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
ir/__init__.py,sha256=TNjpp97HZ3Dutw83hYdNflRpzaMdcINcEkSs8fth8mE,43
|
|
2
|
+
ir/ir.py,sha256=3lt3Y1fxvfUV7hv-qRL7STL0i3YdFygcDIF7ajElPaQ,2939
|
|
3
|
+
package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
package/cmd/__init__.py,sha256=WTZoSFeRHwPvXKNOXA4ezFVXTfMctI4c3rtLhW_eJyw,70
|
|
5
|
+
package/cmd/root.py,sha256=xufNYft6alp4JVwlihJkExOFQdzURVSkak_OwBgAFW0,1293
|
|
6
|
+
package/cmd/trans.py,sha256=sXNA-A-y2UtZChuLl7wSBgcdypWnMGlSuf3I7l5Q7N4,1091
|
|
7
|
+
package/compiler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
package/compiler/compiler.py,sha256=V-VYcQBg1PUV6w7jxM4-3hxm6nt48ggx9ZF-_7DbGAk,2147
|
|
9
|
+
package/config/__init__.py,sha256=Tjfe0B7oZ7Xlrd5nBXkvDOfdnyOJkWobatIx3r6yMk0,69
|
|
10
|
+
package/config/config.py,sha256=M-wcSyGfnJggUgnaLjKVhU9mGhzo9nkO0w5FrPUpakE,1235
|
|
11
|
+
package/config/init.py,sha256=gKLMJCdlnc9C0ePsUPNMXxcj7dKZhrq06vv1L51Dizk,465
|
|
12
|
+
package/enums/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
package/enums/const.py,sha256=tzi707pqpfj6yYnVKZ2DXI05yILQae5xW7r2uT-AHe0,103
|
|
14
|
+
package/parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
package/parser/paser.py,sha256=Kwz0fPXWb-MlXC_8TX7giUObQnS9RmSBlCEhner3iDo,490
|
|
16
|
+
package/parser/ali/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
package/parser/ali/alipay.py,sha256=osKOcW4bIdkp3FKyBPin0R-is6m5JXtqtM9vrwDpQ24,2655
|
|
18
|
+
package/parser/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
package/parser/utils/utils.py,sha256=A14jltxynNeWQ5EyfjZxIcXnOmGScQw4aMnhaAk90nA,252
|
|
20
|
+
package/parser/wechat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
package/parser/wechat/wechat.py,sha256=0vHMWv-Wt1pcVlHg9CQtuaiqG1Y_1ZZzBi-YWG7b1dI,2690
|
|
22
|
+
package/strategy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
package/strategy/template/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
package/strategy/template/normal.py,sha256=7oUWP-b7aJKwetIn_LnAYNy65yqk7E1A0lzYAImFfP4,1562
|
|
25
|
+
package/strategy/template/strategy.py,sha256=8uuzfIX-JElwVV7v8CkkP_Rp9FFmhJ4mPB0cPV5I98g,333
|
|
26
|
+
package/template/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
package/template/normal.j2,sha256=KLWsHntbCcFrOsm9MzMExShHWEag8hsBbM12WaOJ0uA,854
|
|
28
|
+
package/template/template.py,sha256=hX0Uz-8Vs1bIuZEiPeAdt2SVNYRslOOVGR3dNgatW0M,934
|
|
29
|
+
provider/__init__.py,sha256=l7ooJKeIpqcIG-l7x0ZTRa6oT0KfQHWaHm3RLs-XyvI,37
|
|
30
|
+
provider/provider.py,sha256=tipyzmJt4mjKmm2_x5X87yCFwLimQTgSCopnSLjOpH0,230
|
|
31
|
+
provider/ali/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
32
|
+
provider/ali/ali_types.py,sha256=ZLB1_e_PdF0m2GwWIkYa9iYfaLNOevU1atf3MLZnrZg,715
|
|
33
|
+
provider/ali/alipay.py,sha256=e61La6TDw7DLe1_ndnph_KYK7s3wxUnUwtkRp4hIxGg,2977
|
|
34
|
+
provider/ali/converter.py,sha256=dsu6rmQxtEIszaN5WZG0QeEN8S52rNS51jql64Hx9i0,2125
|
|
35
|
+
provider/ali/processor.py,sha256=XQCp6LdFyfBQ2WdhqBvxs8XEYTpEr7W94mjqIfVgWzg,2655
|
|
36
|
+
provider/ali/rules.py,sha256=AMAa8eOrhLWtztq97K0PZXTGjru-VQgJwq4309YzovY,2032
|
|
37
|
+
provider/wechat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
+
provider/wechat/converter.py,sha256=NJLrMNmYysCV2KiHukdLAfQ_6-JlVgh8FvfZCF7_5WQ,2180
|
|
39
|
+
provider/wechat/rules.py,sha256=fRyCTf8uHSSk0ry1Zf7QTAtGR812VQJ86ou2NyTB5cM,2051
|
|
40
|
+
provider/wechat/wecaht_types.py,sha256=YXGV6vmKlUFkuI7C_Q0bTZVLlpZ21FpCgVBaNbI_FOk,644
|
|
41
|
+
provider/wechat/wechat.py,sha256=NLUM4sjS7kTnuxm4Bx7djodbb_L2VlItaIE2aB4IZ3Q,2024
|
|
42
|
+
fane-1.2.7.dist-info/METADATA,sha256=cx0PZ6OQaDB1LgQKPmlfKNsqqpQr1V9gOxNikahf45w,314
|
|
43
|
+
fane-1.2.7.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
44
|
+
fane-1.2.7.dist-info/entry_points.txt,sha256=LDV5HAakkrbI6dMrDrhCyE1xzuqhBe1IcTM63_efVi0,39
|
|
45
|
+
fane-1.2.7.dist-info/top_level.txt,sha256=X7hDPXo2ct8SjQaKmJwpE_lXl3VTF5F1UVft1BkVuoI,20
|
|
46
|
+
fane-1.2.7.dist-info/RECORD,,
|
ir/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# from .ir import Order, IR, Type, Account
|
ir/ir.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# 定义中间值
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Optional, Dict, List, Any
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
from pydantic.dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OrderType(Enum):
|
|
11
|
+
NORMAL = "normal"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Type(Enum):
|
|
15
|
+
SEND = "支"
|
|
16
|
+
RECV = "收"
|
|
17
|
+
UNKNOW = "未知"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Account(Enum):
|
|
21
|
+
cash_account = "cash_account"
|
|
22
|
+
position_account = "position_account"
|
|
23
|
+
commission_account = "commission_account"
|
|
24
|
+
pnl_account = "pnl_account"
|
|
25
|
+
third_party_custody_account = "third_party_custody_account"
|
|
26
|
+
plus_account = "plus_account"
|
|
27
|
+
minus_account = "minus_account"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class Order:
|
|
32
|
+
order_type: Optional["OrderType"] = Field(
|
|
33
|
+
default=OrderType.NORMAL, description="订单类型"
|
|
34
|
+
)
|
|
35
|
+
peer: Optional[str] = Field(default=None, description="交易对手")
|
|
36
|
+
item: Optional[str] = Field(default="", description="商品名")
|
|
37
|
+
category: Optional[str] = Field(default="", description="分类")
|
|
38
|
+
merchant_order_id: Optional[str] = Field(default="", description="商户订单号")
|
|
39
|
+
order_id: Optional[str] = Field(default="", description="内部订单号")
|
|
40
|
+
money: Optional[Decimal] = Field(default=Decimal("0.0"), description="金额")
|
|
41
|
+
note: Optional[str] = Field(default="", description="备注")
|
|
42
|
+
pay_time: Optional[datetime] = Field(default=None, description="支付时间")
|
|
43
|
+
type: Optional["Type"] = Field(default=None, description="收支类型")
|
|
44
|
+
type_original: Optional[str] = Field(default="", description="原始类型字符串")
|
|
45
|
+
tx_type_original: Optional[str] = Field(default="", description="原始交易类型")
|
|
46
|
+
method: Optional[str] = Field(default="", description="支付方式")
|
|
47
|
+
amount: Optional[Decimal] = Field(default=Decimal("0.0"))
|
|
48
|
+
price: Optional[Decimal] = Field(default=Decimal("0.0"))
|
|
49
|
+
currency: Optional[str] = Field(default="CNY")
|
|
50
|
+
commission: Optional[Decimal] = Field(default=Decimal("0.0"), description="手续费")
|
|
51
|
+
units: Optional[Dict[str, Any]] = Field(default_factory=dict)
|
|
52
|
+
extra_account: Optional[Dict[str, str]] = Field(
|
|
53
|
+
default_factory=dict, description="额外账户,盈亏账户"
|
|
54
|
+
)
|
|
55
|
+
minus_account: Optional[str] = Field(default="", description="负向账户")
|
|
56
|
+
plus_account: Optional[str] = Field(default="", description="正向账户")
|
|
57
|
+
minus_str: Optional[str] = Field(
|
|
58
|
+
default="", description="负向字符串,用来解决外币转换问题"
|
|
59
|
+
)
|
|
60
|
+
plus_str: Optional[str] = Field(
|
|
61
|
+
default="", description="正向字符串,用来解决外币转换问题"
|
|
62
|
+
)
|
|
63
|
+
meta_data: Optional[Dict[str, str]] = Field(
|
|
64
|
+
default_factory=dict, description="元数据"
|
|
65
|
+
)
|
|
66
|
+
tags: Optional[List[str]] = Field(default_factory=list, description="标签")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class IR:
|
|
71
|
+
orders: Optional[List[Order]] = Field(
|
|
72
|
+
default_factory=list, description="放置同用类型的订单"
|
|
73
|
+
)
|
package/__init__.py
ADDED
|
File without changes
|
package/cmd/__init__.py
ADDED
package/cmd/root.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import package.config.init as cfg
|
|
3
|
+
from typing_extensions import Annotated
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
cfg_file: str = None
|
|
9
|
+
app = typer.Typer(help="fflow")
|
|
10
|
+
config_file = Path().home() / ".flow" / "bill.yaml"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def version_callback(value: bool):
|
|
14
|
+
if value:
|
|
15
|
+
try:
|
|
16
|
+
pkg_version = version("bill-flow-enmu")
|
|
17
|
+
print(f"Task Flow Version: {pkg_version}")
|
|
18
|
+
except PackageNotFoundError:
|
|
19
|
+
print("Task Flow Version: Unknown (Package not installed)")
|
|
20
|
+
|
|
21
|
+
raise typer.Exit()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.callback()
|
|
25
|
+
def initialize(
|
|
26
|
+
ctx: typer.Context,
|
|
27
|
+
config: Annotated[
|
|
28
|
+
Path,
|
|
29
|
+
typer.Option(
|
|
30
|
+
"--config",
|
|
31
|
+
"-c",
|
|
32
|
+
help="config file (default is $HOME/.double-entry-generator.yaml)",
|
|
33
|
+
),
|
|
34
|
+
] = str(config_file),
|
|
35
|
+
toogle: Annotated[
|
|
36
|
+
bool, typer.Option("--toggle", "-t", help="Help message for toggle")
|
|
37
|
+
] = False,
|
|
38
|
+
version: Annotated[
|
|
39
|
+
bool,
|
|
40
|
+
typer.Option(
|
|
41
|
+
"--version", "-v", is_eager=True, callback=version_callback, help="version"
|
|
42
|
+
),
|
|
43
|
+
] = False,
|
|
44
|
+
):
|
|
45
|
+
|
|
46
|
+
if ctx.invoked_subcommand == "version":
|
|
47
|
+
return
|
|
48
|
+
global cfg_file
|
|
49
|
+
cfg_file = str(config)
|
|
50
|
+
cfg.init_config(cfg_file)
|
package/cmd/trans.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .root import app
|
|
3
|
+
from package.config import get_config, Config
|
|
4
|
+
from typing_extensions import Annotated
|
|
5
|
+
from provider.provider import get_provider
|
|
6
|
+
from package.compiler.compiler import Compiler
|
|
7
|
+
from package.strategy.template.normal import NormalStrategy
|
|
8
|
+
from package.parser.paser import get_analyser
|
|
9
|
+
import traceback
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command()
|
|
13
|
+
def trans(
|
|
14
|
+
provider: Annotated[
|
|
15
|
+
str, typer.Option("--provider", "-p", help="Bills provder")
|
|
16
|
+
] = "alipay",
|
|
17
|
+
source: Annotated[str, typer.Option("--source", "-s", help="source file")] = "",
|
|
18
|
+
):
|
|
19
|
+
try:
|
|
20
|
+
p = get_provider(provider)
|
|
21
|
+
if p is None:
|
|
22
|
+
typer.echo(f"不支持的 provider: {provider},可选属有: alipay, wechat")
|
|
23
|
+
raise typer.Exit(code=1)
|
|
24
|
+
s = p.translate(source)
|
|
25
|
+
config_content = get_config()
|
|
26
|
+
config = Config.model_validate(config_content)
|
|
27
|
+
Compiler(
|
|
28
|
+
provider, config, s, NormalStrategy(), get_analyser(provider)
|
|
29
|
+
).compile()
|
|
30
|
+
except Exception as e:
|
|
31
|
+
typer.echo(f"编译出错: {e}")
|
|
32
|
+
traceback.print_exc()
|
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from package.config import Config
|
|
4
|
+
from ir.ir import IR, Order
|
|
5
|
+
from package.strategy.template.strategy import TemplateStrategy
|
|
6
|
+
from package.parser.paser import Paser
|
|
7
|
+
import re
|
|
8
|
+
import json
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Compiler:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
privider: str,
|
|
16
|
+
config: Config,
|
|
17
|
+
ir: IR,
|
|
18
|
+
template_strategy: TemplateStrategy,
|
|
19
|
+
analyser: Paser,
|
|
20
|
+
):
|
|
21
|
+
self.privider = privider
|
|
22
|
+
self.config = config
|
|
23
|
+
self.ir = ir
|
|
24
|
+
self.template_strategy = template_strategy
|
|
25
|
+
self.analyser = analyser
|
|
26
|
+
|
|
27
|
+
def compile(self):
|
|
28
|
+
logging.debug("start compile")
|
|
29
|
+
orders: list[Order] = []
|
|
30
|
+
for o in self.ir.orders:
|
|
31
|
+
ignore, res_minus, res_plus, extra_account, tags = (
|
|
32
|
+
self.analyser.get_account_and_tags(o, self.config)
|
|
33
|
+
)
|
|
34
|
+
if ignore:
|
|
35
|
+
continue
|
|
36
|
+
o.minus_account = res_minus
|
|
37
|
+
o.plus_account = res_plus
|
|
38
|
+
o.extra_account = extra_account
|
|
39
|
+
o.tags = tags
|
|
40
|
+
orders.append(o)
|
|
41
|
+
|
|
42
|
+
self.ir.orders = orders
|
|
43
|
+
|
|
44
|
+
if self.privider == "alipay":
|
|
45
|
+
from provider.ali.processor import post_process
|
|
46
|
+
|
|
47
|
+
self.ir = post_process(self.ir)
|
|
48
|
+
|
|
49
|
+
for io in self.ir.orders:
|
|
50
|
+
self.template_strategy.template_parser(io)
|
|
51
|
+
expense_data = self.distribution(self.template_strategy.expense_list)
|
|
52
|
+
income_data = self.distribution(self.template_strategy.income_list)
|
|
53
|
+
|
|
54
|
+
result = {
|
|
55
|
+
"expense": {k: sorted(v) for k, v in sorted(expense_data.items())},
|
|
56
|
+
"income": {k: sorted(v) for k, v in sorted(income_data.items())},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
data_str = json.dumps(result, ensure_ascii=False)
|
|
60
|
+
print(data_str)
|
|
61
|
+
|
|
62
|
+
def distribution(self, bean_bill_list: list):
|
|
63
|
+
r = r"^\d{4}-(\d{2})-\d{2}"
|
|
64
|
+
monthly_data = defaultdict(list)
|
|
65
|
+
|
|
66
|
+
for item in bean_bill_list:
|
|
67
|
+
match_obj = re.match(r, item)
|
|
68
|
+
if match_obj:
|
|
69
|
+
month = match_obj.group(1)
|
|
70
|
+
monthly_data[month].append(item)
|
|
71
|
+
return dict(monthly_data)
|
package/config/config.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from provider.ali.rules import ALi
|
|
3
|
+
from provider.wechat.rules import WeChat
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Config(BaseModel):
|
|
8
|
+
title: Optional[str] = Field(alias="title", default=None)
|
|
9
|
+
default_minus_account: Optional[str] = Field(
|
|
10
|
+
alias="default-minus-account", default=None
|
|
11
|
+
)
|
|
12
|
+
default_plus_account: Optional[str] = Field(
|
|
13
|
+
alias="default-plus-account", default=None
|
|
14
|
+
)
|
|
15
|
+
default_cash_account: Optional[str] = Field(
|
|
16
|
+
alias="default-cash-account", default=None
|
|
17
|
+
)
|
|
18
|
+
default_position_account: Optional[str] = Field(
|
|
19
|
+
alias="default-position-account", default=None
|
|
20
|
+
)
|
|
21
|
+
default_commission_account: Optional[str] = Field(
|
|
22
|
+
alias="default-commission-account", default=None
|
|
23
|
+
)
|
|
24
|
+
default_pnl_account: Optional[str] = Field(
|
|
25
|
+
alias="default-pnl-account", default=None
|
|
26
|
+
)
|
|
27
|
+
default_third_party_custody_account: Optional[str] = Field(
|
|
28
|
+
alias="default-third-party-custody-account", default=None
|
|
29
|
+
)
|
|
30
|
+
default_currency: Optional[str] = Field(alias="default-currency", default=None)
|
|
31
|
+
ali: Optional[ALi] = Field(alias="alipay", default=None)
|
|
32
|
+
wechat: Optional[WeChat] = Field(alias="wechat", default=None)
|
package/config/init.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
_config = None
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def init_config(file: str):
|
|
9
|
+
global _config
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
if file == "":
|
|
13
|
+
file = Path.home() / ".flow" / "bflow.yaml"
|
|
14
|
+
|
|
15
|
+
with open(file, "r", encoding="utf-8") as f:
|
|
16
|
+
_config = yaml.safe_load(f)
|
|
17
|
+
|
|
18
|
+
except FileNotFoundError as fe:
|
|
19
|
+
logging.error("找不到配置文件,请手动创建: %s", file)
|
|
20
|
+
raise
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_config():
|
|
24
|
+
return _config
|
|
File without changes
|
package/enums/const.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from ir.ir import Order, Type, Account
|
|
2
|
+
from package.config import Config
|
|
3
|
+
from package.parser.utils.utils import split_find_contains
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AlipayAnalyser:
|
|
7
|
+
def get_account_and_tags(self, o: Order, cfg: Config) -> tuple:
|
|
8
|
+
|
|
9
|
+
ignore = False
|
|
10
|
+
|
|
11
|
+
if cfg.ali == None or cfg.ali.rules == None or len(cfg.ali.rules) == 0:
|
|
12
|
+
return (
|
|
13
|
+
ignore,
|
|
14
|
+
cfg.default_minus_account,
|
|
15
|
+
cfg.default_plus_account,
|
|
16
|
+
{},
|
|
17
|
+
[],
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
res_minus = cfg.default_minus_account
|
|
21
|
+
res_plus = cfg.default_plus_account
|
|
22
|
+
|
|
23
|
+
extra_account = {}
|
|
24
|
+
tags = []
|
|
25
|
+
|
|
26
|
+
for r in cfg.ali.rules:
|
|
27
|
+
match = True
|
|
28
|
+
sep = ","
|
|
29
|
+
|
|
30
|
+
match_func = split_find_contains
|
|
31
|
+
|
|
32
|
+
if r.separator != None:
|
|
33
|
+
sep = r.separator
|
|
34
|
+
|
|
35
|
+
if r.peer != None:
|
|
36
|
+
match = match_func(r.peer, o.peer, sep, match)
|
|
37
|
+
|
|
38
|
+
if r.type != None:
|
|
39
|
+
match = match_func(r.type, o.type_original, sep, match)
|
|
40
|
+
|
|
41
|
+
if r.item != None:
|
|
42
|
+
match = match_func(r.item, o.item, sep, match)
|
|
43
|
+
|
|
44
|
+
if r.method != None:
|
|
45
|
+
match = match_func(r.method, o.method, sep, match)
|
|
46
|
+
|
|
47
|
+
if r.category != None:
|
|
48
|
+
match = match_func(r.category, o.category, sep, match)
|
|
49
|
+
|
|
50
|
+
if r.note != None:
|
|
51
|
+
match = match_func(r.note, o.note, sep, match)
|
|
52
|
+
|
|
53
|
+
if match:
|
|
54
|
+
if r.ignore:
|
|
55
|
+
ignore = True
|
|
56
|
+
break
|
|
57
|
+
if r.target_account != None:
|
|
58
|
+
if o.type == Type.RECV:
|
|
59
|
+
res_minus = r.target_account
|
|
60
|
+
else:
|
|
61
|
+
res_plus = r.target_account
|
|
62
|
+
if r.method_account != None:
|
|
63
|
+
if o.type == Type.RECV:
|
|
64
|
+
res_plus = r.method_account
|
|
65
|
+
else:
|
|
66
|
+
res_minus = r.method_account
|
|
67
|
+
if r.pnl_account != None:
|
|
68
|
+
extra_account = {Account.pnl_account: r.pnl_account}
|
|
69
|
+
if r.tags != None:
|
|
70
|
+
tags = r.tags.split(sep)
|
|
71
|
+
# 判断是否为退款
|
|
72
|
+
if match and str.startswith(o.item, "退款"):
|
|
73
|
+
return ignore, res_plus, res_minus, extra_account, tags
|
|
74
|
+
|
|
75
|
+
# 循环结束后再判断是否退款(没有规则匹配的情况)
|
|
76
|
+
if str.startswith(o.item, "退款"):
|
|
77
|
+
return ignore, res_plus, res_minus, extra_account, tags
|
|
78
|
+
|
|
79
|
+
return ignore, res_minus, res_plus, extra_account, tags
|
|
80
|
+
# 获取对应的匹配函数
|
package/parser/paser.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
|
|
3
|
+
from ir.ir import Order
|
|
4
|
+
from package.config import Config
|
|
5
|
+
from package.parser.ali.alipay import AlipayAnalyser
|
|
6
|
+
from package.parser.wechat.wechat import WechatAnalyser
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Paser(Protocol):
|
|
10
|
+
def get_account_and_tags(self, o: Order, cfg: Config) -> tuple:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_analyser(provider_name: str):
|
|
15
|
+
if provider_name == "alipay":
|
|
16
|
+
return AlipayAnalyser()
|
|
17
|
+
if provider_name == "wechat":
|
|
18
|
+
return WechatAnalyser()
|
|
19
|
+
return None
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from ir.ir import Order, Type
|
|
2
|
+
from package.config import Config
|
|
3
|
+
from package.parser.utils.utils import split_find_contains
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class WechatAnalyser:
|
|
7
|
+
def get_account_and_tags(self, o: Order, cfg: Config) -> tuple:
|
|
8
|
+
|
|
9
|
+
ignore = False
|
|
10
|
+
# 如果没有配置规则,返回事先配置的默认配置
|
|
11
|
+
if cfg.wechat == None or cfg.wechat.rules == None or len(cfg.wechat.rules) == 0:
|
|
12
|
+
return (
|
|
13
|
+
ignore,
|
|
14
|
+
cfg.default_minus_account,
|
|
15
|
+
cfg.default_plus_account,
|
|
16
|
+
{},
|
|
17
|
+
[],
|
|
18
|
+
)
|
|
19
|
+
# 初始化默认账户FixMe
|
|
20
|
+
res_minus = cfg.default_minus_account
|
|
21
|
+
res_plus = cfg.default_plus_account
|
|
22
|
+
extra_account = {}
|
|
23
|
+
tags = []
|
|
24
|
+
|
|
25
|
+
for r in cfg.wechat.rules:
|
|
26
|
+
match = True
|
|
27
|
+
sep = ","
|
|
28
|
+
|
|
29
|
+
match_func = split_find_contains
|
|
30
|
+
|
|
31
|
+
if r.separator != None:
|
|
32
|
+
sep = r.separator
|
|
33
|
+
|
|
34
|
+
if r.peer != None:
|
|
35
|
+
match = match_func(r.peer, o.peer, sep, match)
|
|
36
|
+
|
|
37
|
+
if r.tx_type != None:
|
|
38
|
+
match = match_func(r.tx_type, o.tx_type_original, sep, match)
|
|
39
|
+
|
|
40
|
+
if r.item != None:
|
|
41
|
+
match = match_func(r.item, o.item, sep, match)
|
|
42
|
+
|
|
43
|
+
if r.method != None:
|
|
44
|
+
match = match_func(r.method, o.method, sep, match)
|
|
45
|
+
|
|
46
|
+
if r.category != None:
|
|
47
|
+
match = match_func(r.category, o.category, sep, match)
|
|
48
|
+
|
|
49
|
+
if match:
|
|
50
|
+
if r.ignore:
|
|
51
|
+
ignore = True
|
|
52
|
+
break
|
|
53
|
+
if r.target_account != None:
|
|
54
|
+
if o.type == Type.RECV:
|
|
55
|
+
res_minus = r.target_account
|
|
56
|
+
else:
|
|
57
|
+
res_plus = r.target_account
|
|
58
|
+
if r.method_account != None:
|
|
59
|
+
if o.type == Type.RECV:
|
|
60
|
+
res_plus = r.method_account
|
|
61
|
+
else:
|
|
62
|
+
res_minus = r.method_account
|
|
63
|
+
# if r.pnl_account != None:
|
|
64
|
+
# extra_account = {Account.pnl_account: r.pnl_account}
|
|
65
|
+
if r.tags != None:
|
|
66
|
+
tags = r.tags.split(sep)
|
|
67
|
+
# 循环内匹配后检查退款
|
|
68
|
+
if match and str.startswith(o.item, "退款"):
|
|
69
|
+
return ignore, res_plus, res_minus, extra_account, tags
|
|
70
|
+
|
|
71
|
+
# 循环结束后再判断是否退款(没有规则匹配的情况)
|
|
72
|
+
if str.startswith(o.item, "退款"):
|
|
73
|
+
return ignore, res_plus, res_minus, extra_account, tags
|
|
74
|
+
|
|
75
|
+
return ignore, res_minus, res_plus, extra_account, tags
|
|
76
|
+
# 获取对应的匹配函数
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from package.template.template import get_template
|
|
2
|
+
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from package.strategy.template.strategy import TemplateStrategy
|
|
5
|
+
from package.template.template import NormalOrder
|
|
6
|
+
from ir.ir import Order, Account
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NormalStrategy(TemplateStrategy):
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.expense_list: list[str] = []
|
|
13
|
+
self.income_list: list[str] = []
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
@lru_cache(maxsize=5)
|
|
17
|
+
def get_template_content(cls, template_name: str):
|
|
18
|
+
return get_template(template_name)
|
|
19
|
+
|
|
20
|
+
def template_parser(self, order: Order):
|
|
21
|
+
template = self.get_template_content(f"{order.order_type.value}.j2")
|
|
22
|
+
norml_order = NormalOrder(
|
|
23
|
+
pay_time=order.pay_time,
|
|
24
|
+
peer=order.peer,
|
|
25
|
+
item=order.item,
|
|
26
|
+
note=order.note,
|
|
27
|
+
money=order.money,
|
|
28
|
+
commission=order.commission,
|
|
29
|
+
minus_account=order.minus_account,
|
|
30
|
+
plus_account=order.plus_account,
|
|
31
|
+
plus_str=order.plus_str,
|
|
32
|
+
minus_str=order.minus_str,
|
|
33
|
+
pnl_account=order.extra_account.get(Account.pnl_account.value, ""),
|
|
34
|
+
commission_account=order.extra_account.get(
|
|
35
|
+
Account.commission_account.value, ""
|
|
36
|
+
),
|
|
37
|
+
currency=order.currency,
|
|
38
|
+
metadata=order.meta_data,
|
|
39
|
+
tags=order.tags,
|
|
40
|
+
)
|
|
41
|
+
data = template.render(**vars(norml_order))
|
|
42
|
+
|
|
43
|
+
if "收益发放" in norml_order.item:
|
|
44
|
+
self.income_list.append(data)
|
|
45
|
+
else:
|
|
46
|
+
self.expense_list.append(data)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from abc import abstractmethod, ABC
|
|
2
|
+
from ir.ir import Order
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TemplateStrategy(ABC):
|
|
6
|
+
|
|
7
|
+
expense_list: list[str]
|
|
8
|
+
income_list: list[str]
|
|
9
|
+
|
|
10
|
+
@classmethod
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def template_parser(self, order: Order):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def get_template_content(self, template_name: str):
|
|
17
|
+
pass
|
|
File without changes
|