Skip to content

Commit

Permalink
feat(provider): support ICBC bank card bills (#88)
Browse files Browse the repository at this point in the history
* feat(provider): support icbc in provider package

Signed-off-by: TripleZ <[email protected]>

* feat(analyser): support ICBC provider & ignore rules

Signed-off-by: TripleZ <[email protected]>

* Update README

Signed-off-by: TripleZ <[email protected]>

* chore: lint code

Signed-off-by: TripleZ <[email protected]>

---------

Signed-off-by: TripleZ <[email protected]>
  • Loading branch information
Triple-Z authored May 10, 2023
1 parent da0c4e7 commit 1d0542b
Show file tree
Hide file tree
Showing 29 changed files with 642 additions and 32 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ clean: ## Clean all the temporary files
@rm -rf ./double-entry-generator
@rm -rf ./wasm-dist

test: test-go test-alipay test-wechat test-huobi test-htsec ## Run all tests
test: test-go test-alipay test-wechat test-huobi test-htsec test-icbc ## Run all tests

test-go: ## Run Golang tests
@go test ./...
Expand All @@ -107,6 +107,9 @@ test-huobi: ## Run tests for huobi provider
test-htsec: ## Run tests for htsec provider
@$(SHELL) ./test/htsec-test.sh

test-icbc: ## Run tests for ICBC provider
@$(SHELL) ./test/icbc-test.sh

format: ## Format code
@gofmt -s -w pkg
@goimports -w pkg
Expand Down
94 changes: 92 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- 微信
- 火币-币币交易
- 海通证券
- 中国工商银行

目前记账语言支持:

Expand All @@ -20,6 +21,8 @@
alipay beancount alipay
wechat wechat
huobi huobi
htsec htsec
icbc icbc
```

## 安装
Expand Down Expand Up @@ -95,13 +98,23 @@ double-entry-generator translate \
./example/htsec/example-htsec-records.xlsx
```

### 中国工商银行

```bash
double-entry-generator translate \
--config ./example/icbc/config.yaml \
--provider icbc \
--output ./example/icbc/example-icbc-output.beancount \
./example/icbc/example-icbc-records.csv
```

## 账单下载与格式问题

### 支付宝

#### 下载方式

`v1.0.0` 及以上的版本请参考[此文章](https://blog.triplez.cn/posts/bills-export-methods/#支付宝)获取支付宝账单。
`v1.0.0` 及以上的版本请参考[此文章](https://blog.triplez.cn/posts/bills-export-methods/#%e6%94%af%e4%bb%98%e5%ae%9d)获取支付宝账单。

`v0.2.0` 及以下版本请使用此方式获取账单:登录 PC 支付宝后,访问 [这里](https://consumeprod.alipay.com/record/standard.htm),选择时间区间,下拉到页面底端,点击下载查询结果。注意:请下载查询结果,而非[收支明细](https://cshall.alipay.com/lab/help_detail.htm?help_id=212688)

Expand All @@ -117,7 +130,7 @@ double-entry-generator translate \

#### 下载方式

微信支付的下载方式[见此](https://blog.triplez.cn/posts/bills-export-methods/#微信支付)
微信支付的下载方式[见此](https://blog.triplez.cn/posts/bills-export-methods/#%e5%be%ae%e4%bf%a1%e6%94%af%e4%bb%98)

#### 格式示例

Expand Down Expand Up @@ -153,6 +166,17 @@ double-entry-generator translate \

转换后的结果示例:[exmaple-htsec-output.beancount](./example/htsec/example-htsec-output.beancount).

### 中国工商银行

#### 下载方式

中国工商银行账单的下载方式[见此](https://blog.triplez.cn/posts/bills-export-methods/#%e4%b8%ad%e5%9b%bd%e5%b7%a5%e5%95%86%e9%93%b6%e8%a1%8c)

#### 格式示例

[example-icbc-records.csv](./example/icbc/example-icbc-records.csv)

转换后的结果示例:[exmaple-icbc-output.beancount](./example/icbc/example-icbc-output.beancount).

## 配置

Expand Down Expand Up @@ -260,6 +284,10 @@ alipay:

在单条规则中可以使用 `fullMatch` 来设置字符匹配规则,`true` 表示使用完全匹配(full match),`false` 表示使用包含匹配(partial match),不设置该项则默认使用包含匹配。

在单条规则中可以使用 `tag` 来设置流水的 [Tag](https://beancount.github.io/docs/beancount_language_syntax.html#tags),使用 `sep` 作为分隔符。

在单条规则中可以使用 `ignore` 来设置是否忽略匹配上该规则的交易,`true` 表示忽略匹配上该规则的交易,`fasle` 则为不忽略,缺省为 `false` 。

匹配成功则使用规则中定义的 `targetAccount` 、 `methodAccount` 等账户覆盖默认定义账户。

规则匹配的顺序是:从 `rules` 配置中的第一条开始匹配,如果匹配成功仍继续匹配。也就是后面的规则优先级要**高于**前面的规则。
Expand Down Expand Up @@ -386,6 +414,8 @@ wechat:

在单条规则中可以使用 `tag` 来设置流水的 [Tag](https://beancount.github.io/docs/beancount_language_syntax.html#tags),使用 `sep` 作为分隔符。

在单条规则中可以使用 `ignore` 来设置是否忽略匹配上该规则的交易,`true` 表示忽略匹配上该规则的交易,`fasle` 则为不忽略,缺省为 `false` 。

匹配成功则使用规则中定义的 `targetAccount` 、 `methodAccount` 等账户覆盖默认定义账户。

规则匹配的顺序是:从 `rules` 配置中的第一条开始匹配,如果匹配成功仍继续匹配。也就是后面的规则优先级要**高于**前面的规则。
Expand Down Expand Up @@ -446,6 +476,8 @@ huobi:

在单条规则中可以使用 `fullMatch` 来设置字符匹配规则,`true` 表示使用完全匹配(full match),`false` 表示使用包含匹配(partial match),不设置该项则默认使用包含匹配。

在单条规则中可以使用 `ignore` 来设置是否忽略匹配上该规则的交易,`true` 表示忽略匹配上该规则的交易,`fasle` 则为不忽略,缺省为 `false` 。

匹配成功则使用规则中定义的 `cashAccount`, `positionAccount`, `commissionAccount` 和 `pnlAccount` 覆盖默认定义。

规则匹配的顺序是:从 `rules` 配置中的第一条开始匹配,如果匹配成功仍继续匹配。也就是后面的规则优先级要**高于**前面的规则。
Expand Down Expand Up @@ -501,6 +533,8 @@ htsec:

在单条规则中可以使用 `fullMatch` 来设置字符匹配规则,`true` 表示使用完全匹配(full match),`false` 表示使用包含匹配(partial match),不设置该项则默认使用包含匹配。

在单条规则中可以使用 `ignore` 来设置是否忽略匹配上该规则的交易,`true` 表示忽略匹配上该规则的交易,`fasle` 则为不忽略,缺省为 `false` 。

匹配成功则使用规则中定义的 `cashAccount`, `positionAccount`, `commissionAccount` 和 `pnlAccount` 覆盖默认定义。

规则匹配的顺序是:从 `rules` 配置中的第一条开始匹配,如果匹配成功仍继续匹配。也就是后面的规则优先级要**高于**前面的规则。
Expand All @@ -512,6 +546,62 @@ htsec:
- `defaultPnlAccount` 是默认损益账户。
- `defaultCurrency` 是默认货币。

### 中国工商银行

<details>
<summary>
中国工商银行配置文件示例
</summary>

```yaml
defaultMinusAccount: Assets:FIXME
defaultPlusAccount: Expenses:FIXME
defaultCashAccount: Liabilities:Bank:CN:ICBC
defaultCurrency: CNY
title: 测试
icbc:
rules:
- peer: 财付通,支付宝
ignore: true
- peer: 广东联合电子收费股份
targetAccount: Expenses:Transport:Highway
- txType: 人民币自动转帐还款
targetAccount: Assets:Bank:CN:ICBC:Savings
- peer: XX旗舰店
targetAccount: Expenses:Joy
```

</details></br>

`defaultMinusAccount`, `defaultPlusAccount`, `defaultCashAccount` 和 `defaultCurrency` 是全局的必填默认值。其中 `defaultMinusAccount` 是默认金额减少的账户,`defaultPlusAccount` 是默认金额增加的账户, `defaultCashAccount` 是该配置中默认使用的银行卡账户(等同于支付宝/微信中的 `methodAccount` )。 `defaultCurrency` 是默认货币。

`icbc` 是中国工商银行相关的配置。它提供基于规则的匹配。可以指定:
- `peer`(交易对方)的完全/包含匹配。
- `type`(收/支)的完全/包含匹配。
- `txType`(交易类型)的完全/包含匹配。

在单条规则中可以使用分隔符 `sep` 填写多个关键字,在同一对象中,每个关键字之间是或的关系。

在单条规则中可以使用 `fullMatch` 来设置字符匹配规则,`true` 表示使用完全匹配(full match),`false` 表示使用包含匹配(partial match),不设置该项则默认使用包含匹配。

在单条规则中可以使用 `tag` 来设置流水的 [Tag](https://beancount.github.io/docs/beancount_language_syntax.html#tags),使用 `sep` 作为分隔符。

在单条规则中可以使用 `ignore` 来设置是否忽略匹配上该规则的交易,`true` 表示忽略匹配上该规则的交易,`fasle` 则为不忽略,缺省为 `false` 。

匹配成功则使用规则中定义的 `targetAccount` 账户覆盖默认定义账户。

规则匹配的顺序是:从 `rules` 配置中的第一条开始匹配,如果匹配成功仍继续匹配。也就是后面的规则优先级要**高于**前面的规则。

中国工商银行账单中的记账金额中存在收入/支出之分,通过这个机制就可以判断银行卡账户在交易中的正负关系。如支付宝配置类似,匹配成功则使用规则中定义的 `targetAccount` 和全局值 `defaultCashAccount` ,并通过确认该笔交易是收入还是支出,决定 `targetAccount` 和 `defaultCashAccount` 的正负关系,来覆盖默认定义的增减账户。

`targetAccount` 与 `defaultCashAccount` 的增减账户关系如下表:

|收/支|defaultCashAccount|targetAccount|
|----|----|----|
|收入|plusAccount|minusAccount|
|支出|minusAccount|plusAccount|


## Special Thanks

- [dilfish/atb](https://github.com/dilfish/atb) convert alipay bill to beancount version
15 changes: 15 additions & 0 deletions example/icbc/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defaultMinusAccount: Assets:FIXME
defaultPlusAccount: Expenses:FIXME
defaultCashAccount: Liabilities:Bank:CN:ICBC
defaultCurrency: CNY
title: 测试
icbc:
rules:
- peer: 财付通,支付宝
ignore: true
- peer: 广东联合电子收费股份
targetAccount: Expenses:Transport:Highway
- txType: 人民币自动转帐还款
targetAccount: Assets:Bank:CN:ICBC:Savings
- peer: XX旗舰店
targetAccount: Expenses:Joy
45 changes: 45 additions & 0 deletions example/icbc/example-icbc-output.beancount
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
option "title" "测试"
option "operating_currency" "CNY"

1970-01-01 open Assets:Bank:CN:ICBC:Savings
1970-01-01 open Assets:FIXME
1970-01-01 open Expenses:FIXME
1970-01-01 open Expenses:Joy
1970-01-01 open Expenses:Transport:Highway

2023-03-20 * "广东联合电子收费股份"
balances: "-16042.73"
currency: "人民币"
source: "中国工商银行"
txType: "********************"
type: "支出"
Expenses:Transport:Highway 29.45 CNY
Liabilities:Bank:CN:ICBC -29.45 CNY

2023-04-22 * "XX旗舰店"
balances: "-6086.11"
currency: "人民币"
source: "中国工商银行"
txType: "银联在线支付"
type: "支出"
Expenses:Joy 0.01 CNY
Liabilities:Bank:CN:ICBC -0.01 CNY

2023-04-25 * "XX分行银行卡中心"
balances: "-1971.63"
currency: "人民币"
source: "中国工商银行"
txType: "人民币自动转帐还款"
type: "收入"
Liabilities:Bank:CN:ICBC 4621.01 CNY
Assets:Bank:CN:ICBC:Savings -4621.01 CNY

2023-05-04 * "广东联合电子收费股份"
balances: "-3684.62"
currency: "人民币"
source: "中国工商银行"
txType: "ETC"
type: "支出"
Expenses:Transport:Highway 12.35 CNY
Liabilities:Bank:CN:ICBC -12.35 CNY

18 changes: 18 additions & 0 deletions example/icbc/example-icbc-records.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
明细查询文件下载

卡号: 1234****5678,"卡别名: "

子账户类别:

交易日期,记账日期,摘要,交易场所,交易国家或地区简称,交易金额(收入),交易金额(支出),交易币种,记账金额(收入),记账金额(支出),记账币种,余额,对方户名
2023-05-05 ,2023-05-05 ,"消费 ","财付通-拼多多平台商户 ","CHN "," ","18.00 ",人民币 ," ","18.00 ",人民币 ,"-3,728.62 "," ",
2023-05-05 ,2023-05-05 ,"消费 ","支付宝-金拱门(中国)有限公司 ","CHN "," ","26.00 ",人民币 ," ","26.00 ",人民币 ,"-3,710.62 "," ",
2023-05-04 ,2023-05-04 ,"ETC ","广东联合电子收费股份 ","CHN "," ","12.35 ",人民币 ," ","12.35 ",人民币 ,"-3,684.62 "," ",
2023-05-02 ,2023-05-02 ,"消费 ","支付宝-北京百度网讯科技有限公司 ","CHN "," ","17.32 ",人民币 ," ","17.32 ",人民币 ,"-3,648.27 "," ",
2023-04-25 ,2023-04-25 ,"人民币自动转帐还款 ",XX分行银行卡中心 ,"CHN ","4,621.01 "," ",人民币 ,"4,621.01 "," ",人民币 ,"-1,971.63 "," ",
2023-04-22 ,2023-04-22 ,"消费 ","财付通-美团平台商户 ","CHN "," ","238.00 ",人民币 ," ","238.00 ",人民币 ,"-6,324.11 "," ",
2023-04-22 ,2023-04-22 ,"银联在线支付 ",XX旗舰店 ,"CHN "," ","0.01 ",人民币 ," ","0.01 ",人民币 ,"-6,086.11 "," ",
2023-03-20 ,2023-03-20 ,"消费 ",财付通-餐馆 ,"CHN "," ","22.00 ",人民币 ," ","22.00 ",人民币 ,"-16,064.73 "," ",
2023-03-20 ,2023-03-20 ,"******************** ","广东联合电子收费股份 ","CHN "," ","29.45 ",人民币 ," ","29.45 ",人民币 ,"-16,042.73 "," ",

合计金额,,,,,"1234.56 ","6543.21 ", ,"1234.56 ","6543.21 ",
14 changes: 10 additions & 4 deletions pkg/analyser/alipay/alipay.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ func (a Alipay) GetAllCandidateAccounts(cfg *config.Config) map[string]bool {
}

// GetAccounts returns minus and plus account.
func (a Alipay) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provider string) (string, string, map[ir.Account]string, []string) {
func (a Alipay) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provider string) (bool, string, string, map[ir.Account]string, []string) {
ignore := false

if cfg.Alipay == nil || len(cfg.Alipay.Rules) == 0 {
return cfg.DefaultMinusAccount, cfg.DefaultPlusAccount, nil, nil
return ignore, cfg.DefaultMinusAccount, cfg.DefaultPlusAccount, nil, nil
}
resMinus := cfg.DefaultMinusAccount
resPlus := cfg.DefaultPlusAccount
Expand Down Expand Up @@ -91,6 +92,11 @@ func (a Alipay) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, prov
}

if match {
if r.Ignore {
ignore = true
break
}

// Support multiple matches, like one rule matches the
// minus account, the other rule matches the plus account.
if r.TargetAccount != nil {
Expand Down Expand Up @@ -121,7 +127,7 @@ func (a Alipay) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, prov
}

if strings.HasPrefix(o.Item, "退款-") && ir.TypeRecv != o.Type {
return resPlus, resMinus, extraAccounts, tags
return ignore, resPlus, resMinus, extraAccounts, tags
}
return resMinus, resPlus, extraAccounts, tags
return ignore, resMinus, resPlus, extraAccounts, tags
}
11 changes: 8 additions & 3 deletions pkg/analyser/htsec/htsec.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ func (h Htsec) GetAllCandidateAccounts(cfg *config.Config) map[string]bool {
return uniqMap
}

func (h Htsec) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provider string) (string, string, map[ir.Account]string, []string) {
func (h Htsec) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provider string) (bool, string, string, map[ir.Account]string, []string) {
ignore := false
if cfg.Htsec == nil || len(cfg.Htsec.Rules) == 0 {
return "", "", map[ir.Account]string{
return ignore, "", "", map[ir.Account]string{
ir.CashAccount: cfg.DefaultCashAccount,
ir.PositionAccount: cfg.DefaultPositionAccount,
ir.CommissionAccount: cfg.DefaultCommissionAccount,
Expand Down Expand Up @@ -89,6 +90,10 @@ func (h Htsec) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provi
}

if match {
if r.Ignore {
ignore = true
break
}
if r.CashAccount != nil {
cashAccount = *r.CashAccount
}
Expand All @@ -105,7 +110,7 @@ func (h Htsec) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provi
}
}

return "", "", map[ir.Account]string{
return ignore, "", "", map[ir.Account]string{
ir.CashAccount: cashAccount,
ir.PositionAccount: positionAccount,
ir.CommissionAccount: commissionAccount,
Expand Down
11 changes: 8 additions & 3 deletions pkg/analyser/huobi/huobi.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ func (h Huobi) GetAllCandidateAccounts(cfg *config.Config) map[string]bool {
return uniqMap
}

func (h Huobi) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provider string) (string, string, map[ir.Account]string, []string) {
func (h Huobi) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provider string) (bool, string, string, map[ir.Account]string, []string) {
ignore := false
if cfg.Huobi == nil || len(cfg.Huobi.Rules) == 0 {
return "", "", map[ir.Account]string{
return ignore, "", "", map[ir.Account]string{
ir.CashAccount: cfg.DefaultCashAccount,
ir.PositionAccount: cfg.DefaultPositionAccount,
ir.CommissionAccount: cfg.DefaultCommissionAccount,
Expand Down Expand Up @@ -91,6 +92,10 @@ func (h Huobi) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provi
}
}
if match {
if r.Ignore {
ignore = true
break
}
if r.CashAccount != nil {
cashAccount = *r.CashAccount
}
Expand All @@ -106,7 +111,7 @@ func (h Huobi) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provi
}
}

return "", "", map[ir.Account]string{
return ignore, "", "", map[ir.Account]string{
ir.CashAccount: cashAccount,
ir.PositionAccount: positionAccount,
ir.CommissionAccount: commissionAccount,
Expand Down
Loading

0 comments on commit 1d0542b

Please sign in to comment.