Skip to content

Commit

Permalink
feat(icbc): support icbc debit new bill format
Browse files Browse the repository at this point in the history
- fix(icbc): don't ignore the first transaction!

Signed-off-by: TripleZ <[email protected]>
  • Loading branch information
Triple-Z committed Sep 18, 2024
1 parent ef964f3 commit 975c743
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 33 deletions.
2 changes: 1 addition & 1 deletion example/icbc/credit/example-icbc-credit-records.csv
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
子账户类别:

交易日期,记账日期,摘要,交易场所,交易国家或地区简称,交易金额(收入),交易金额(支出),交易币种,记账金额(收入),记账金额(支出),记账币种,余额,对方户名
2023-04-22 ,2023-04-22 ,"银联在线支付 ",XX旗舰店 ,"CHN "," ","0.01 ",人民币 ," ","0.01 ",人民币 ,"-6,086.11 "," ",
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 "," ",

Expand Down
10 changes: 10 additions & 0 deletions example/icbc/debit/example-icbc-debit-output.beancount
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,13 @@ option "operating_currency" "CNY"
Assets:Bank:CN:ICBC 1234.56 CNY
Assets:Borrow -1234.56 CNY

2023-05-02 * "广东三元麦当劳食品有 深圳市财付通支付科技有限公司"
cardName: "这是卡别名"
currency: "人民币"
peerAccount: "深圳市财付通支付科技有限公司"
source: "中国工商银行"
txType: "消费"
type: "支出"
Expenses:FIXME 13.40 CNY
Assets:Bank:CN:ICBC -13.40 CNY

10 changes: 10 additions & 0 deletions example/icbc/debit/example-icbc-debit-output.ledger
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,13 @@
Assets:Bank:CN:ICBC 1234.56 CNY
Assets:Borrow - 1234.56 CNY

2023/05/02 * 广东三元麦当劳食品有 深圳市财付通支付科技有限公司
; cardName: "这是卡别名"
; currency: "人民币"
; peerAccount: "深圳市财付通支付科技有限公司"
; source: "中国工商银行"
; txType: "消费"
; type: "支出"
Expenses:FIXME 13.40 CNY
Assets:Bank:CN:ICBC - 13.40 CNY

2 changes: 1 addition & 1 deletion example/icbc/debit/example-icbc-debit-records.csv
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
子账户序号: 00000,子账户类别: 活期,"子账户别名: "

交易日期,摘要,交易场所,交易国家或地区简称,钞/汇,交易金额(收入),交易金额(支出),交易币种,记账金额(收入),记账金额(支出),记账币种,余额,对方户名
2023-05-02 ,"消费 ","财付通-广东三元麦当劳食品有 ","CHN ",钞 ,"- ","- ",- ," ","13.40 ",人民币 ,"xx,yyyy.zz ","深圳市财付通支付科技有限公司 ",
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 ","银联无卡支付业务((特约)掌上生活还款) ",
Expand Down
23 changes: 15 additions & 8 deletions pkg/provider/icbc/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func (icbc *Icbc) convertToIR() *ir.IR {
for _, o := range icbc.Orders {
irO := ir.Order{
Peer: o.Peer,
Item: o.Item,
Money: o.Money,
PayTime: o.PayTime,
Type: convertType(o.Type),
Expand All @@ -36,11 +37,12 @@ func convertType(t OrderType) ir.Type {
}

// getMetadata get the metadata (e.g. status, method, category and so on.)
// from order.
//
// from order.
func (icbc *Icbc) getMetadata(o Order) map[string]string {
// FIXME(TripleZ): hard-coded, bad pattern
source := "中国工商银行"
var txTypeOriginal, guessedType, currency, balances, peerAccount string
var txTypeOriginal, guessedType, currency, balances, peerAccount, peerAccountNum string

if o.TxTypeOriginal != "" {
txTypeOriginal = o.TxTypeOriginal
Expand All @@ -62,13 +64,18 @@ func (icbc *Icbc) getMetadata(o Order) map[string]string {
peerAccount = o.PeerAccountName
}

if o.PeerAccountNum != "" {
peerAccountNum = o.PeerAccountNum
}

metadata := map[string]string{
"source": source,
"txType": txTypeOriginal,
"type": guessedType,
"currency": currency,
"balances": balances,
"peerAccount": peerAccount,
"source": source,
"txType": txTypeOriginal,
"type": guessedType,
"currency": currency,
"balances": balances,
"peerAccount": peerAccount,
"peerAccountNum": peerAccountNum,
}

if icbc.CardName != "" {
Expand Down
6 changes: 3 additions & 3 deletions pkg/provider/icbc/icbc.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ func (icbc *Icbc) Translate(filename string) (*ir.IR, error) {
}
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.
} else if icbc.LineNum <= 4 {
// The first 4 non-empty lines are useless for us.
continue
}

Expand All @@ -81,7 +81,7 @@ func (icbc *Icbc) Translate(filename string) (*ir.IR, error) {

err = icbc.translateToOrders(line)
if err != nil {
return nil, fmt.Errorf("Failed to translate bill: line %d: %v",
return nil, fmt.Errorf("failed to translate bill: line %d: %v",
icbc.LineNum, err)
}
}
Expand Down
73 changes: 54 additions & 19 deletions pkg/provider/icbc/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,33 +53,68 @@ func (icbc *Icbc) translateToOrders(array []string) error {
bill.Balances, _ = strconv.ParseFloat(strings.ReplaceAll(array[11], ",", ""), 64)
bill.PeerAccountName = array[12]
case DebitMode:
debitBillVersion := 1

Check failure on line 56 in pkg/provider/icbc/parse.go

View workflow job for this annotation

GitHub Actions / lint

ineffectual assignment to debitBillVersion (ineffassign)

Check failure on line 56 in pkg/provider/icbc/parse.go

View workflow job for this annotation

GitHub Actions / lint

ineffectual assignment to debitBillVersion (ineffassign)
if len(array) == 14 {
debitBillVersion = 1
} else if len(array) == 16 {
debitBillVersion = 2
} else {
return fmt.Errorf("cannot recognize this debit bill format, len()=%v", len(array))
}

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)
switch debitBillVersion {
case 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]
case 2:
bill.Item = array[2]
bill.Peer = array[3]
bill.Region = array[4]

_income := strings.ReplaceAll(array[9], ",", "")
_expense := strings.ReplaceAll(array[10], ",", "")
if _income == "" && _expense == "" {
bill.Type = OrderTypeUnknown
} else if _expense == "" {
bill.Type = OrderTypeRecv
bill.Money, err = strconv.ParseFloat(_income, 64)
} else {
bill.Type = OrderTypeSend
bill.Money, err = strconv.ParseFloat(_expense, 64)
}
if err != nil {
return fmt.Errorf("parse money [%s,%s] error: %v", array[8], array[9], err)
}
bill.Currency = array[11]
bill.Balances, _ = strconv.ParseFloat(strings.ReplaceAll(array[12], ",", ""), 64)
bill.PeerAccountName = array[13]
bill.PeerAccountNum = array[14]
}

bill.Currency = array[10]
bill.Balances, _ = strconv.ParseFloat(strings.ReplaceAll(array[11], ",", ""), 64)
bill.PeerAccountName = array[12]
}

if bill.Peer == "" {
Expand Down
2 changes: 2 additions & 0 deletions pkg/provider/icbc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ type Order struct {
PayTime time.Time // 记账日期
TxTypeOriginal string // 摘要
Peer string // 交易场所
Item string // 交易详情
Region string // 交易国家或地区简称
Money float64 // 记账金额 (收入/支出)
Type OrderType // 收/支 (数据中无该列,推测而来)
Currency string // 记账币种
Balances float64 // 余额
PeerAccountName string // 对方户名
PeerAccountNum string // 对方账户
}

// localTimeFmt set time format to utc+8
Expand Down
3 changes: 2 additions & 1 deletion pkg/provider/wechat/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ func convertType(t OrderType) ir.Type {
}

// getMetadata get the metadata (e.g. status, method, category and so on.)
// from order.
//
// from order.
func getMetadata(o Order) map[string]string {
// FIXME(TripleZ): hard-coded, bad pattern
source := "微信支付"
Expand Down

0 comments on commit 975c743

Please sign in to comment.