From cd53df838b1166f3b65f8a5319690eb0474b05fe Mon Sep 17 00:00:00 2001 From: Zhenzhen Zhao Date: Sat, 13 May 2023 09:35:27 +0800 Subject: [PATCH] feat(icbc): automatically change debit/credit bill mode (#90) * fix(icbc): ignore the single space line at the bottom of the csv file Signed-off-by: TripleZ * fix(icbc): set default methodAccount by cashAccount and tx type Signed-off-by: TripleZ * feat(icbc): support ICBC debit card bills Signed-off-by: TripleZ * fix(alipay): metadata `type` Signed-off-by: TripleZ --------- Signed-off-by: TripleZ --- README.md | 14 ++- .../alipay/example-alipay-output.beancount | 10 +- example/icbc/{ => credit}/config.yaml | 2 - .../example-icbc-credit-output.beancount} | 3 +- .../example-icbc-credit-records.csv} | 4 +- example/icbc/debit/config.yaml | 22 +++++ .../debit/example-icbc-debit-output.beancount | 69 +++++++++++++ .../icbc/debit/example-icbc-debit-records.csv | 20 ++++ pkg/analyser/icbc/icbc.go | 16 +-- pkg/provider/alipay/convert.go | 6 +- pkg/provider/alipay/parse.go | 10 +- pkg/provider/icbc/convert.go | 13 ++- pkg/provider/icbc/icbc.go | 26 ++++- pkg/provider/icbc/parse.go | 97 ++++++++++++++----- pkg/provider/icbc/types.go | 7 ++ test/icbc-test.sh | 32 ++++-- 16 files changed, 279 insertions(+), 72 deletions(-) rename example/icbc/{ => credit}/config.yaml (87%) rename example/icbc/{example-icbc-output.beancount => credit/example-icbc-credit-output.beancount} (95%) rename example/icbc/{example-icbc-records.csv => credit/example-icbc-credit-records.csv} (98%) create mode 100644 example/icbc/debit/config.yaml create mode 100644 example/icbc/debit/example-icbc-debit-output.beancount create mode 100644 example/icbc/debit/example-icbc-debit-records.csv diff --git a/README.md b/README.md index 7559243..39bfb43 100644 --- a/README.md +++ b/README.md @@ -104,8 +104,8 @@ double-entry-generator translate \ double-entry-generator translate \ --config ./example/icbc/config.yaml \ --provider icbc \ - --output ./example/icbc/example-icbc-output.beancount \ - ./example/icbc/example-icbc-records.csv + --output ./example/icbc/example-icbc-credit-output.beancount \ + ./example/icbc/example-icbc-credit-records.csv ``` ## 账单下载与格式问题 @@ -174,9 +174,15 @@ double-entry-generator translate \ #### 格式示例 -[example-icbc-records.csv](./example/icbc/example-icbc-records.csv) +> `double-entry-generator` 能够自动识别出中国工商银行的账单类型(借记卡/信用卡)。 -转换后的结果示例:[exmaple-icbc-output.beancount](./example/icbc/example-icbc-output.beancount). +借记卡账单示例: [example-icbc-debit-records.csv](example/icbc/debit/example-icbc-debit-records.csv) + +借记卡账单转换后的结果示例:[example-icbc-debit-output.beancount](example/icbc/debit/example-icbc-debit-output.beancount). + +信用卡账单示例: [example-icbc-credit-records.csv](example/icbc/credit/example-icbc-credit-records.csv) + +信用卡账单转换后的结果示例:[example-icbc-credit-output.beancount](example/icbc/credit/example-icbc-credit-output.beancount). ## 配置 diff --git a/example/alipay/example-alipay-output.beancount b/example/alipay/example-alipay-output.beancount index 69f74af..84142cd 100644 --- a/example/alipay/example-alipay-output.beancount +++ b/example/alipay/example-alipay-output.beancount @@ -24,7 +24,7 @@ option "operating_currency" "CNY" payTime: "2023-01-18 10:17:29 +0800 CST" source: "支付宝" status: "交易成功" - txType: "收入" + type: "收入" Assets:Alipay 222228.50 CNY Income:FIXME -222228.50 CNY @@ -35,7 +35,7 @@ option "operating_currency" "CNY" payTime: "2023-02-02 15:24:35 +0800 CST" source: "支付宝" status: "交易成功" - txType: "不计收支" + type: "不计收支" Expenses:FIXME 99.34 CNY Assets:Alipay -99.34 CNY @@ -47,7 +47,7 @@ option "operating_currency" "CNY" payTime: "2023-02-04 18:21:04 +0800 CST" source: "支付宝" status: "退款成功" - txType: "不计收支" + type: "不计收支" Liabilities:CC:COMM:7449 16.03 CNY Expenses:FIXME -16.03 CNY @@ -59,7 +59,7 @@ option "operating_currency" "CNY" payTime: "2023-02-08 14:16:52 +0800 CST" source: "支付宝" status: "等待确认收货" - txType: "支出" + type: "支出" Expenses:Groceries 20.00 CNY Assets:Alipay -20.00 CNY @@ -71,7 +71,7 @@ option "operating_currency" "CNY" payTime: "2023-02-12 21:32:14 +0800 CST" source: "支付宝" status: "交易成功" - txType: "支出" + type: "支出" Expenses:FIXME 49.74 CNY Liabilities:CC:COMM:7449 -49.74 CNY diff --git a/example/icbc/config.yaml b/example/icbc/credit/config.yaml similarity index 87% rename from example/icbc/config.yaml rename to example/icbc/credit/config.yaml index 36f2a52..0948634 100644 --- a/example/icbc/config.yaml +++ b/example/icbc/credit/config.yaml @@ -11,5 +11,3 @@ icbc: targetAccount: Expenses:Transport:Highway - txType: 人民币自动转帐还款 targetAccount: Assets:Bank:CN:ICBC:Savings - - peer: XX旗舰店 - targetAccount: Expenses:Joy diff --git a/example/icbc/example-icbc-output.beancount b/example/icbc/credit/example-icbc-credit-output.beancount similarity index 95% rename from example/icbc/example-icbc-output.beancount rename to example/icbc/credit/example-icbc-credit-output.beancount index 214c0fe..3f59860 100644 --- a/example/icbc/example-icbc-output.beancount +++ b/example/icbc/credit/example-icbc-credit-output.beancount @@ -4,7 +4,6 @@ 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 * "广东联合电子收费股份" @@ -22,7 +21,7 @@ option "operating_currency" "CNY" source: "中国工商银行" txType: "银联在线支付" type: "支出" - Expenses:Joy 0.01 CNY + Expenses:FIXME 0.01 CNY Liabilities:Bank:CN:ICBC -0.01 CNY 2023-04-25 * "XX分行银行卡中心" diff --git a/example/icbc/example-icbc-records.csv b/example/icbc/credit/example-icbc-credit-records.csv similarity index 98% rename from example/icbc/example-icbc-records.csv rename to example/icbc/credit/example-icbc-credit-records.csv index e813956..79c9930 100644 --- a/example/icbc/example-icbc-records.csv +++ b/example/icbc/credit/example-icbc-credit-records.csv @@ -14,5 +14,5 @@ 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 ", \ No newline at end of file + +合计金额,,,,,"1234.56 ","6543.21 ", ,"1234.56 ","6543.21 ", diff --git a/example/icbc/debit/config.yaml b/example/icbc/debit/config.yaml new file mode 100644 index 0000000..d534558 --- /dev/null +++ b/example/icbc/debit/config.yaml @@ -0,0 +1,22 @@ +defaultMinusAccount: Assets:FIXME +defaultPlusAccount: Expenses:FIXME +defaultCashAccount: Assets:Bank:CN:ICBC +defaultCurrency: CNY +title: 测试 +icbc: + rules: + - peer: 财付通-,支付宝- + ignore: true + - peer: 支付宝 + txType: 蚂蚁基金赎回到银行 + ignore: true + - peer: 总行信用卡合伙人 + targetAccount: Income:Bank:ICBC:CreditCard + - peer: 掌上生活还款 + targetAccount: Liabilities:Bank:CMB:CreditCard + - txType: 自动还款 + peer: 广东XX分行银行卡中心 + ignore: true + - peer: 张三,李四,王五 + txTpe: 汇款,网转,汇入 + targetAccount: Assets:Borrow diff --git a/example/icbc/debit/example-icbc-debit-output.beancount b/example/icbc/debit/example-icbc-debit-output.beancount new file mode 100644 index 0000000..d98f9c8 --- /dev/null +++ b/example/icbc/debit/example-icbc-debit-output.beancount @@ -0,0 +1,69 @@ +option "title" "测试" +option "operating_currency" "CNY" + +1970-01-01 open Assets:Borrow +1970-01-01 open Assets:FIXME +1970-01-01 open Expenses:FIXME +1970-01-01 open Income:Bank:ICBC:CreditCard +1970-01-01 open Liabilities:Bank:CMB:CreditCard + +2023-02-10 * "总行信用卡合伙人" + cardName: "这是卡别名" + currency: "人民币" + peerAccount: "总行信用卡合伙人" + source: "中国工商银行" + txType: "合伙人返现" + type: "收入" + Assets:Bank:CN:ICBC 30.00 CNY + Income:Bank:ICBC:CreditCard -30.00 CNY + +2023-02-20 * "手机银行 张三" + cardName: "这是卡别名" + currency: "人民币" + peerAccount: "张三" + source: "中国工商银行" + txType: "跨行汇款" + type: "支出" + Assets:Borrow 1234.56 CNY + Assets:Bank:CN:ICBC -1234.56 CNY + +2023-04-14 * "ABC公司" + cardName: "这是卡别名" + currency: "人民币" + peerAccount: "ABC公司" + source: "中国工商银行" + txType: "工资" + type: "收入" + Assets:Bank:CN:ICBC 1234.56 CNY + Assets:FIXME -1234.56 CNY + +2023-04-14 * "手机银行 王五" + cardName: "这是卡别名" + currency: "人民币" + peerAccount: "王五" + source: "中国工商银行" + txType: "网转" + type: "支出" + Assets:Borrow 500.00 CNY + Assets:Bank:CN:ICBC -500.00 CNY + +2023-04-20 * "银联无卡支付业务((特约)掌上生活还款)" + cardName: "这是卡别名" + currency: "人民币" + peerAccount: "银联无卡支付业务((特约)掌上生活还款)" + source: "中国工商银行" + txType: "银联消费" + type: "支出" + Liabilities:Bank:CMB:CreditCard 1234.56 CNY + Assets:Bank:CN:ICBC -1234.56 CNY + +2023-04-22 * "李四" + cardName: "这是卡别名" + currency: "人民币" + peerAccount: "李四" + source: "中国工商银行" + txType: "他行汇入" + type: "收入" + Assets:Bank:CN:ICBC 1234.56 CNY + Assets:Borrow -1234.56 CNY + diff --git a/example/icbc/debit/example-icbc-debit-records.csv b/example/icbc/debit/example-icbc-debit-records.csv new file mode 100644 index 0000000..d3a1d9e --- /dev/null +++ b/example/icbc/debit/example-icbc-debit-records.csv @@ -0,0 +1,20 @@ +明细查询文件下载 + +卡号: 1234****9876,"卡别名: 这是卡别名" + +子账户序号: 00000,子账户类别: 活期,"子账户别名: " + +交易日期,摘要,交易场所,交易国家或地区简称,钞/汇,交易金额(收入),交易金额(支出),交易币种,记账金额(收入),记账金额(支出),记账币种,余额,对方户名 +2023-05-02 ,"消费 ","财付通-广东三元麦当劳食品有 ","CHN ",钞 ,"- ","- ",- ," ","13.40 ",人民币 ,"xx,yyyy.zz ","深圳市财付通支付科技有限公司 ", +2023-04-25 ,"自动还款 ","广东XX分行银行卡中心 ","CHN ",钞 ,"- ","- ",- ," ","1,234.56 ",人民币 ,"xx,yyyy.zz ","张三 ", +2023-04-22 ,"他行汇入 "," ","CHN ",钞 ,"- ","- ",- ,"1,234.56 "," ",人民币 ,"xx,yyyy.zz ","李四 ", +2023-04-20 ,"银联消费 "," ","CHN ",钞 ,"- ","- ",- ," ","1,234.56 ",人民币 ,"xx,yyyy.zz ","银联无卡支付业务((特约)掌上生活还款) ", +2023-04-14 ,"工资 "," ","CHN ",钞 ,"- ","- ",- ,"1,234.56 "," ",人民币 ,"xx,yyyy.zz ","ABC公司 ", +2023-04-14 ,"网转 ","手机银行 ","CHN ",钞 ,"- ","- ",- ," ","500.00 ",人民币 ,"xx,yyyy.zz ","王五 ", +2023-02-28 ,"蚂蚁基金赎回到银行 ","支付宝 ","CHN ",钞 ,"- ","- ",- ,"341.58 "," ",人民币 ,"xx,yyyy.zz ","支付宝(中国)网络技术有限公司 ", +2023-02-20 ,"跨行汇款 ","手机银行 ","CHN ",钞 ,"- ","- ",- ," ","1,234.56 ",人民币 ,"xx,yyyy.zz ","张三 ", +2023-02-10 ,"合伙人返现 "," ","CHN ",钞 ,"- ","- ",- ,"30.00 "," ",人民币 ,"xx,yyyy.zz ","总行信用卡合伙人 ", +2023-02-09 ,"消费 ","财付通-群收款 ","CHN ",钞 ,"- ","- ",- ," ","24.00 ",人民币 ,"xx,yyyy.zz ","深圳市财付通支付科技有限公司 ", +2023-02-08 ,"退款 ","财付通-微信红包 ","CHN ",钞 ,"- ","- ",- ,"1.04 "," ",人民币 ,"xx,yyyy.zz ","深圳市财付通支付科技有限公司 ", + +人民币合计,,,,,,,,"1,234.56 ","9,876.54 ", diff --git a/pkg/analyser/icbc/icbc.go b/pkg/analyser/icbc/icbc.go index a228ea0..0545984 100644 --- a/pkg/analyser/icbc/icbc.go +++ b/pkg/analyser/icbc/icbc.go @@ -49,6 +49,13 @@ func (i Icbc) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provid resPlus := cfg.DefaultPlusAccount cashAccount := cfg.DefaultCashAccount + // method account (bank card account) + if o.Type == ir.TypeRecv { + resPlus = cashAccount + } else { + resMinus = cashAccount + } + //var err error for _, r := range cfg.Icbc.Rules { match := true @@ -67,7 +74,7 @@ func (i Icbc) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provid match = matchFunc(*r.Peer, o.Peer, sep, match) } if r.Type != nil { - match = matchFunc(*r.Type, string(o.Type), sep, match) + match = matchFunc(*r.Type, o.TypeOriginal, sep, match) } if r.TxType != nil { match = matchFunc(*r.TxType, o.TxTypeOriginal, sep, match) @@ -87,13 +94,6 @@ func (i Icbc) GetAccountsAndTags(o *ir.Order, cfg *config.Config, target, provid } } - // method account (bank card account) - if o.Type == ir.TypeRecv { - resPlus = cashAccount - } else { - resMinus = cashAccount - } - if r.Tag != nil { tags = strings.Split(*r.Tag, sep) } diff --git a/pkg/provider/alipay/convert.go b/pkg/provider/alipay/convert.go index 260f516..0e46167 100644 --- a/pkg/provider/alipay/convert.go +++ b/pkg/provider/alipay/convert.go @@ -45,7 +45,7 @@ func convertType(t Type) ir.Type { func getMetadata(o Order) map[string]string { // FIXME(TripleZ): hard-coded, bad pattern source := "支付宝" - var status, method, category, txType, orderId, merchantId, paytime string + var status, method, category, typeOriginal, orderId, merchantId, paytime string paytime = o.PayTime.Format(localTimeFmt) @@ -62,7 +62,7 @@ func getMetadata(o Order) map[string]string { } if o.TypeOriginal != "" { - txType = o.TypeOriginal + typeOriginal = o.TypeOriginal } if o.Method != "" { @@ -78,7 +78,7 @@ func getMetadata(o Order) map[string]string { "payTime": paytime, "orderId": orderId, "merchantId": merchantId, - "txType": txType, + "type": typeOriginal, "category": category, "method": method, "status": status, diff --git a/pkg/provider/alipay/parse.go b/pkg/provider/alipay/parse.go index baa7bcc..bad0cd3 100644 --- a/pkg/provider/alipay/parse.go +++ b/pkg/provider/alipay/parse.go @@ -30,6 +30,9 @@ func (a *Alipay) translateToOrders(array []string) error { bill.PeerAccount = array[3] bill.ItemName = array[4] bill.Method = array[7] + bill.Category = array[1] + bill.DealNo = array[9] + bill.MerchantId = array[10] bill.Money, err = strconv.ParseFloat(array[6], 32) if err != nil { log.Println("parse money error:", array[6], err) @@ -37,14 +40,11 @@ func (a *Alipay) translateToOrders(array []string) error { } bill.Status = array[8] if bill.Status == "交易关闭" { - log.Printf("Line %d: There is a mole, The tx is canceled.", a.LineNum) + log.Printf("[orderId %s ] There is a mole, The tx is canceled.", bill.DealNo) } if bill.Status == "退款成功" { - log.Printf("Lind %d: There has a refund transaction.", a.LineNum) + log.Printf("[orderId %s ] There has a refund transaction.", bill.DealNo) } - bill.Category = array[1] - bill.DealNo = array[9] - bill.MerchantId = array[10] bill.PayTime, err = time.Parse(localTimeFmt, array[0]+" +0800 CST") if err != nil { log.Println("parse create time error:", array[0], err) diff --git a/pkg/provider/icbc/convert.go b/pkg/provider/icbc/convert.go index 90c72f2..243c989 100644 --- a/pkg/provider/icbc/convert.go +++ b/pkg/provider/icbc/convert.go @@ -15,9 +15,10 @@ func (icbc *Icbc) convertToIR() *ir.IR { Money: o.Money, PayTime: o.PayTime, Type: convertType(o.Type), + TypeOriginal: string(o.Type), TxTypeOriginal: o.TxTypeOriginal, } - irO.Metadata = getMetadata(o) + irO.Metadata = icbc.getMetadata(o) i.Orders = append(i.Orders, irO) } return i @@ -36,7 +37,7 @@ func convertType(t OrderType) ir.Type { // getMetadata get the metadata (e.g. status, method, category and so on.) // from order. -func getMetadata(o Order) map[string]string { +func (icbc *Icbc) getMetadata(o Order) map[string]string { // FIXME(TripleZ): hard-coded, bad pattern source := "中国工商银行" var txTypeOriginal, guessedType, currency, balances, peerAccount string @@ -61,7 +62,7 @@ func getMetadata(o Order) map[string]string { peerAccount = o.PeerAccountName } - return map[string]string{ + metadata := map[string]string{ "source": source, "txType": txTypeOriginal, "type": guessedType, @@ -69,4 +70,10 @@ func getMetadata(o Order) map[string]string { "balances": balances, "peerAccount": peerAccount, } + + if icbc.CardName != "" { + metadata["cardName"] = icbc.CardName + } + + return metadata } diff --git a/pkg/provider/icbc/icbc.go b/pkg/provider/icbc/icbc.go index 3551425..31d537f 100644 --- a/pkg/provider/icbc/icbc.go +++ b/pkg/provider/icbc/icbc.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "log" + "strings" "github.com/deb-sig/double-entry-generator/pkg/io/reader" "github.com/deb-sig/double-entry-generator/pkg/ir" @@ -15,20 +16,24 @@ type Icbc struct { Statistics Statistics `json:"statistics,omitempty"` LineNum int `json:"line_num,omitempty"` Orders []Order `json:"orders,omitempty"` + Mode CardMode `json:"mode,omitempty"` + CardName string `json:"card_name,omitempty"` } -// New creates a new wechat provider. +// New creates a new ICBC provider. func New() *Icbc { return &Icbc{ Statistics: Statistics{}, LineNum: 0, Orders: make([]Order, 0), + Mode: CreditMode, + CardName: "", } } // Translate translates the alipay bill records to IR. func (icbc *Icbc) Translate(filename string) (*ir.IR, error) { - log.SetPrefix("[Provider-Icbc] ") + log.SetPrefix("[Provider-ICBC] ") billReader, err := reader.GetReader(filename) if err != nil { @@ -51,12 +56,25 @@ func (icbc *Icbc) Translate(filename string) (*ir.IR, error) { } icbc.LineNum++ - if icbc.LineNum <= 5 { + if icbc.LineNum == 2 && len(line) > 1 { + // 卡别名 + icbc.CardName = strings.TrimLeft(line[1], "卡别名: ") + continue + } else if icbc.LineNum == 3 { + // 借记卡 or 信用卡(default) + for _, col := range line { + if strings.TrimLeft(col, "子账户类别: ") == "活期" { + icbc.Mode = DebitMode + } + } + log.Printf("Now the ICBC provider is in `%s` mode", icbc.Mode) + continue + } else if icbc.LineNum <= 5 { // The first 5 non-empty lines are useless for us. continue } - if line[0] == "合计金额" { + if line[0] == "合计金额" || line[0] == "人民币合计" { // ignore the last line break } diff --git a/pkg/provider/icbc/parse.go b/pkg/provider/icbc/parse.go index 8dc3281..d956cfd 100644 --- a/pkg/provider/icbc/parse.go +++ b/pkg/provider/icbc/parse.go @@ -2,6 +2,7 @@ package icbc import ( "fmt" + "log" "strconv" "strings" "time" @@ -10,39 +11,83 @@ import ( // translateToOrders translates csv file to []Order. func (icbc *Icbc) translateToOrders(array []string) error { for idx, a := range array { - a = strings.Trim(a, " ") - a = strings.Trim(a, "\t") + a = strings.TrimSpace(a) array[idx] = a } + + if len(array) < 13 { + log.Printf("ignore the invalid csv line: %+v\n", array) + return nil + } + var bill Order var err error - bill.PayTime, err = time.Parse(localTimeFmt, strings.TrimSpace(array[1])+" +0800 CST") - if err != nil { - return fmt.Errorf("parse create time %s error: %v", array[1], err) - } - bill.TxTypeOriginal = strings.TrimSpace(array[2]) - bill.Peer = strings.TrimSpace(array[3]) - bill.Region = strings.TrimSpace(array[4]) - - a8 := strings.ReplaceAll(strings.TrimSpace(array[8]), ",", "") - a9 := strings.ReplaceAll(strings.TrimSpace(array[9]), ",", "") - if a8 == "" && a9 == "" { - bill.Type = OrderTypeUnknown - } else if a9 == "" { - bill.Type = OrderTypeRecv - bill.Money, err = strconv.ParseFloat(a8, 64) - } else { - bill.Type = OrderTypeSend - bill.Money, err = strconv.ParseFloat(a9, 64) - } - if err != nil { - return fmt.Errorf("parse money %s error: %v", array[5], err) + switch icbc.Mode { + case CreditMode: + bill.PayTime, err = time.Parse(localTimeFmt, array[1]+" +0800 CST") + if err != nil { + return fmt.Errorf("parse create time %s error: %v", array[1], err) + } + + bill.TxTypeOriginal = array[2] + bill.Peer = array[3] + bill.Region = array[4] + + a8 := strings.ReplaceAll(array[8], ",", "") + a9 := strings.ReplaceAll(array[9], ",", "") + if a8 == "" && a9 == "" { + bill.Type = OrderTypeUnknown + } else if a9 == "" { + bill.Type = OrderTypeRecv + bill.Money, err = strconv.ParseFloat(a8, 64) + } else { + bill.Type = OrderTypeSend + bill.Money, err = strconv.ParseFloat(a9, 64) + } + if err != nil { + return fmt.Errorf("parse money [%s,%s] error: %v", array[8], array[9], err) + } + + bill.Currency = array[10] + bill.Balances, _ = strconv.ParseFloat(strings.ReplaceAll(array[11], ",", ""), 64) + bill.PeerAccountName = array[12] + case DebitMode: + bill.PayTime, err = time.Parse(localTimeFmt, array[0]+" +0800 CST") + if err != nil { + return fmt.Errorf("parse create time %s error: %v", array[0], err) + } + + bill.TxTypeOriginal = array[1] + bill.Peer = array[2] + bill.Region = array[3] + + a8 := strings.ReplaceAll(array[8], ",", "") + a9 := strings.ReplaceAll(array[9], ",", "") + if a8 == "" && a9 == "" { + bill.Type = OrderTypeUnknown + } else if a9 == "" { + bill.Type = OrderTypeRecv + bill.Money, err = strconv.ParseFloat(a8, 64) + } else { + bill.Type = OrderTypeSend + bill.Money, err = strconv.ParseFloat(a9, 64) + } + if err != nil { + return fmt.Errorf("parse money [%s,%s] error: %v", array[8], array[9], err) + } + + bill.Currency = array[10] + bill.Balances, _ = strconv.ParseFloat(strings.ReplaceAll(array[11], ",", ""), 64) + bill.PeerAccountName = array[12] } - bill.Currency = strings.TrimSpace(array[10]) - bill.Balances, _ = strconv.ParseFloat(strings.ReplaceAll(strings.TrimSpace(array[11]), ",", ""), 64) - bill.PeerAccountName = strings.TrimSpace(array[12]) + if bill.Peer == "" { + bill.Peer = bill.PeerAccountName + } else if bill.PeerAccountName != "" { + // both Peer & PeerAccountName are not empty + bill.Peer = bill.Peer + " " + bill.PeerAccountName + } icbc.Orders = append(icbc.Orders, bill) return nil diff --git a/pkg/provider/icbc/types.go b/pkg/provider/icbc/types.go index 3132df9..c3e82e7 100644 --- a/pkg/provider/icbc/types.go +++ b/pkg/provider/icbc/types.go @@ -38,3 +38,10 @@ const ( OrderTypeRecv OrderType = "收入" OrderTypeUnknown OrderType = "Unknown" ) + +type CardMode string + +const ( + DebitMode CardMode = "Debit" + CreditMode CardMode = "Credit" +) diff --git a/test/icbc-test.sh b/test/icbc-test.sh index 241a1f3..6b0130d 100644 --- a/test/icbc-test.sh +++ b/test/icbc-test.sh @@ -3,7 +3,7 @@ # E2E test for icbc provider. # set -x # debug -set -eo errexit +# set -eo errexit TEST_DIR=`dirname "$(realpath $0)"` ROOT_DIR="$TEST_DIR/.." @@ -11,19 +11,35 @@ ROOT_DIR="$TEST_DIR/.." make -f "$ROOT_DIR/Makefile" build mkdir -p "$ROOT_DIR/test/output" -# generate icbc bills output in beancount format +# generate icbc credit bills output in beancount format "$ROOT_DIR/bin/double-entry-generator" translate \ --provider icbc \ - --config "$ROOT_DIR/example/icbc/config.yaml" \ - --output "$ROOT_DIR/test/output/test-icbc-output.beancount" \ - "$ROOT_DIR/example/icbc/example-icbc-records.csv" + --config "$ROOT_DIR/example/icbc/credit/config.yaml" \ + --output "$ROOT_DIR/test/output/test-icbc-credit-output.beancount" \ + "$ROOT_DIR/example/icbc/credit/example-icbc-credit-records.csv" diff -u --color \ - "$ROOT_DIR/example/icbc/example-icbc-output.beancount" \ - "$ROOT_DIR/test/output/test-icbc-output.beancount" + "$ROOT_DIR/example/icbc/credit/example-icbc-credit-output.beancount" \ + "$ROOT_DIR/test/output/test-icbc-credit-output.beancount" if [ $? -ne 0 ]; then - echo "[FAIL] ICBC provider output is different from expected output." + echo "[FAIL] ICBC provider (credit mode) output is different from expected output." + exit 1 +fi + +# generate icbc debit bills output in beancount format +"$ROOT_DIR/bin/double-entry-generator" translate \ + --provider icbc \ + --config "$ROOT_DIR/example/icbc/debit/config.yaml" \ + --output "$ROOT_DIR/test/output/test-icbc-debit-output.beancount" \ + "$ROOT_DIR/example/icbc/debit/example-icbc-debit-records.csv" + +diff -u --color \ + "$ROOT_DIR/example/icbc/debit/example-icbc-debit-output.beancount" \ + "$ROOT_DIR/test/output/test-icbc-debit-output.beancount" + +if [ $? -ne 0 ]; then + echo "[FAIL] ICBC provider (debit mode) output is different from expected output." exit 1 fi