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.
Files changed (46) hide show
  1. fane-1.2.7.dist-info/METADATA +14 -0
  2. fane-1.2.7.dist-info/RECORD +46 -0
  3. fane-1.2.7.dist-info/WHEEL +5 -0
  4. fane-1.2.7.dist-info/entry_points.txt +2 -0
  5. fane-1.2.7.dist-info/top_level.txt +3 -0
  6. ir/__init__.py +1 -0
  7. ir/ir.py +73 -0
  8. package/__init__.py +0 -0
  9. package/cmd/__init__.py +4 -0
  10. package/cmd/root.py +50 -0
  11. package/cmd/trans.py +32 -0
  12. package/compiler/__init__.py +0 -0
  13. package/compiler/compiler.py +71 -0
  14. package/config/__init__.py +2 -0
  15. package/config/config.py +32 -0
  16. package/config/init.py +24 -0
  17. package/enums/__init__.py +0 -0
  18. package/enums/const.py +6 -0
  19. package/parser/__init__.py +0 -0
  20. package/parser/ali/__init__.py +0 -0
  21. package/parser/ali/alipay.py +80 -0
  22. package/parser/paser.py +19 -0
  23. package/parser/utils/__init__.py +0 -0
  24. package/parser/utils/utils.py +10 -0
  25. package/parser/wechat/__init__.py +0 -0
  26. package/parser/wechat/wechat.py +76 -0
  27. package/strategy/__init__.py +0 -0
  28. package/strategy/template/__init__.py +0 -0
  29. package/strategy/template/normal.py +46 -0
  30. package/strategy/template/strategy.py +17 -0
  31. package/template/__init__.py +0 -0
  32. package/template/normal.j2 +16 -0
  33. package/template/template.py +36 -0
  34. provider/__init__.py +1 -0
  35. provider/ali/__init__.py +1 -0
  36. provider/ali/ali_types.py +38 -0
  37. provider/ali/alipay.py +89 -0
  38. provider/ali/converter.py +73 -0
  39. provider/ali/processor.py +94 -0
  40. provider/ali/rules.py +48 -0
  41. provider/provider.py +9 -0
  42. provider/wechat/__init__.py +0 -0
  43. provider/wechat/converter.py +70 -0
  44. provider/wechat/rules.py +48 -0
  45. provider/wechat/wecaht_types.py +34 -0
  46. 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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ fa = package.cmd:app
@@ -0,0 +1,3 @@
1
+ ir
2
+ package
3
+ provider
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
@@ -0,0 +1,4 @@
1
+ from .root import app
2
+ from . import trans
3
+
4
+ __all__ = ["app", "trans"]
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)
@@ -0,0 +1,2 @@
1
+ from .config import Config
2
+ from .init import init_config, get_config
@@ -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
@@ -0,0 +1,6 @@
1
+ from enum import Enum
2
+
3
+
4
+ class Const(Enum):
5
+ PROVIDERALIPAY = "alipay"
6
+ PROVIDERWECHAT = "wechat"
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
+ # 获取对应的匹配函数
@@ -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
@@ -0,0 +1,10 @@
1
+ def split_find_contains(str: str, target: str, sep: str, match: bool) -> bool:
2
+ ss = str.split(sep)
3
+ is_contains = False
4
+
5
+ for s in ss:
6
+ if s in target:
7
+ is_contains = True
8
+ break
9
+
10
+ return is_contains and match
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