diff --git a/README.md b/README.md index 9215f95..c7f3c26 100644 --- a/README.md +++ b/README.md @@ -39,40 +39,32 @@ go get -u github.com/deb-sig/double-entry-generator ### 支付宝 ```bash -double-entry-generator translate --config ./example/alipay/config.yaml ./example/alipay/example-alipay-records.csv +double-entry-generator translate \ + --config ./example/alipay/config.yaml \ + --output ./example/alipay/example-alipay-output.beancount \ + ./example/alipay/example-alipay-records.csv ``` -其中 `--config` 是配置文件,默认情况下,使用支付宝作为提供方,也可手动指定 `--provider`。具体参考[使用文档](doc/double-entry-generator_translate.md)。默认生成的文件是 `default_output.beancount`: - -``` -option "title" "测试" -option "operating_currency" "CNY" - -1970-01-01 open Expenses:Food -1970-01-01 open Income:Earnings -1970-01-01 open Assets:Alipay -1970-01-01 open Expenses:Test -1970-01-01 open Liabilities:CreditCard:Test - -2019-09-30 * "肯德基(张江高科餐厅)" "张江高科餐厅" - Expenses:Food 27.00 CNY - Liabilities:CreditCard:Test -27.00 CNY - -2019-09-30 * "中欧基金管理有限公司" "余额宝-2019.09.29-收益发放" - Assets:Alipay 0.01 CNY - Income:Earnings -0.01 CNY -``` +其中 `--config` 是配置文件,默认情况下,使用支付宝作为提供方,也可手动指定 `--provider`。具体参考[使用文档](doc/double-entry-generator_translate.md)。默认生成的文件是 `default_output.beancount`,若有 `--output` 或 `-o` 指定输出文件,则会输出到指定的文件中。如上述例子会将转换结果输出至 `./example/alipay/example-alipay-output.beancount` 文件中。 ### 微信 ```bash -double-entry-generator translate --config ./example/wechat/config.yaml --provider wechat ./example/wechat/example-wechat-records.csv +double-entry-generator translate \ + --config ./example/wechat/config.yaml \ + --provider wechat \ + --output ./example/wechat/example-wechat-output.beancount \ + ./example/wechat/example-wechat-records.csv ``` ### Huobi Global (Crypto) ```bash -double-entry-generator translate --config ./example/huobi/config.yaml --provider huobi ./example/huobi/example-huobi-records.csv +double-entry-generator translate \ + --config ./example/huobi/config.yaml \ + --provider huobi \ + --output ./example/huobi/example-huobi-output.beancount \ + ./example/huobi/example-huobi-records.csv ``` ## 账单下载与格式问题 diff --git a/example/alipay/example-alipay-output.beancount b/example/alipay/example-alipay-output.beancount index 3b98441..2eb4b5c 100644 --- a/example/alipay/example-alipay-output.beancount +++ b/example/alipay/example-alipay-output.beancount @@ -15,48 +15,130 @@ option "operating_currency" "CNY" 1970-01-01 open Income:Alipay:YuEBao:PnL 1970-01-01 open Income:FIXME -2020-06-02 * "蚂蚁财富-蚂蚁(杭州)基金销售有限公司" "蚂蚁财富-ABCD-卖出至余额宝" ; 其他-交易成功-投资理财 +2020-06-02 * "蚂蚁财富-蚂蚁(杭州)基金销售有限公司" "蚂蚁财富-ABCD-卖出至余额宝" Assets:Alipay 15.40 CNY Assets:Alipay:Invest -15.40 CNY Income:Alipay:Invest:PnL + category: "投资理财" + method: "余额宝" + orderId: "123456" + payTime: "2020-06-02 15:34:06 +0800 CST" + source: "支付宝" + status: "交易成功" + txType: "其他" -2020-06-09 * "广发基金管理有限公司" "余额宝-2020.06.08-收益发放" ; 其他-交易成功-投资理财 +2020-06-09 * "广发基金管理有限公司" "余额宝-2020.06.08-收益发放" Assets:Alipay 0.11 CNY Income:Alipay:YuEBao:PnL -0.11 CNY + category: "投资理财" + method: "余额宝" + orderId: "123456" + payTime: "2020-06-09 02:57:13 +0800 CST" + source: "支付宝" + status: "交易成功" + txType: "其他" -2020-06-14 * "东莞肯德基有限公司" "KFCAPIDGB012345" ; 支出-交易成功-餐饮美食 +2020-06-14 * "东莞肯德基有限公司" "KFCAPIDGB012345" Expenses:Food 79.00 CNY Assets:Alipay -79.00 CNY + category: "餐饮美食" + merchantId: "ABC9876" + method: "余额宝" + orderId: "123456" + payTime: "2020-06-14 12:43:26 +0800 CST" + source: "支付宝" + status: "交易成功" + txType: "支出" -2021-05-19 * "蚂蚁财富-蚂蚁(杭州)基金销售有限公司" "蚂蚁财富-XZY指数C-买入" ; 其他-交易成功-投资理财 +2021-05-19 * "蚂蚁财富-蚂蚁(杭州)基金销售有限公司" "蚂蚁财富-XZY指数C-买入" Assets:Alipay:Invest 50.00 CNY Assets:Alipay -50.00 CNY + category: "投资理财" + orderId: "123456" + payTime: "2021-05-19 09:42:54 +0800 CST" + source: "支付宝" + status: "交易成功" + txType: "其他" -2021-05-20 * "苏宁易购官方旗舰店" "【礼遇价】三星128g内存卡microSD存储卡tf卡行车记录仪卡switch监控摄像头" ; 支出-交易成功-数码电器 +2021-05-20 * "苏宁易购官方旗舰店" "【礼遇价】三星128g内存卡microSD存储卡tf卡行车记录仪卡switch监控摄像头" Expenses:Electronics 87.90 CNY Assets:Bank:CN:CMB-9876:Savings -87.90 CNY + category: "数码电器" + merchantId: "ABC9876" + method: "招商银行(9876)" + orderId: "123456" + payTime: "2021-05-20 12:34:34 +0800 CST" + source: "支付宝" + status: "交易成功" + txType: "支出" -2021-05-30 * "广发基金管理有限公司" "余额宝-单次转入" ; 其他-交易成功-投资理财 +2021-05-30 * "广发基金管理有限公司" "余额宝-单次转入" Assets:Alipay 2.00 CNY Assets:Alipay -2.00 CNY + category: "投资理财" + merchantId: "ABC9876" + method: "余额" + orderId: "123456" + payTime: "2021-05-30 23:43:37 +0800 CST" + source: "支付宝" + status: "交易成功" + txType: "其他" -2021-06-01 * "dettol滴露官方旗舰店" "【直接下单付款】滴露消毒液1.2L*2家用杀菌消毒水衣物地板宠物" ; 支出-交易关闭-日用百货 +2021-06-01 * "dettol滴露官方旗舰店" "【直接下单付款】滴露消毒液1.2L*2家用杀菌消毒水衣物地板宠物" Expenses:Groceries 109.90 CNY Assets:Bank:CN:CMB-9876:Savings -109.90 CNY + category: "日用百货" + merchantId: "ABC9876" + method: "招商银行(9876)" + orderId: "123456" + payTime: "2021-06-01 00:10:22 +0800 CST" + source: "支付宝" + status: "交易关闭" + txType: "支出" -2021-06-01 * "dettol滴露官方旗舰店" "退款-【直接下单付款】滴露消毒液1.2L*2家用杀菌消毒水衣物地板宠物" ; 其他-退款成功-退款 +2021-06-01 * "dettol滴露官方旗舰店" "退款-【直接下单付款】滴露消毒液1.2L*2家用杀菌消毒水衣物地板宠物" Assets:Bank:CN:CMB-9876:Savings 109.90 CNY Expenses:Groceries -109.90 CNY + category: "退款" + merchantId: "ABC9876" + method: "招商银行(9876)" + orderId: "123456" + payTime: "2021-06-01 00:13:56 +0800 CST" + source: "支付宝" + status: "退款成功" + txType: "其他" -2021-06-14 * "蚂蚁会员(北京)网络技术服务有限公司" "相互宝分摊-6月第1期-*" ; 支出-交易成功-互助保障 +2021-06-14 * "蚂蚁会员(北京)网络技术服务有限公司" "相互宝分摊-6月第1期-*" Expenses:Insurance 6.51 CNY Assets:Bank:CN:CMB-9876:Savings -6.51 CNY + category: "互助保障" + merchantId: "ABC9876" + method: "招商银行(9876)" + orderId: "123456" + payTime: "2021-06-14 14:00:38 +0800 CST" + source: "支付宝" + status: "交易成功" + txType: "支出" -2021-06-24 * "*三" "商品" ; 收入-交易成功-收入 +2021-06-24 * "*三" "商品" Assets:Alipay 10.00 CNY Income:Alipay:ShouKuanMa -10.00 CNY + category: "收入" + merchantId: "123456" + orderId: "123456" + payTime: "2021-06-24 00:00:00 +0800 CST" + source: "支付宝" + status: "交易成功" + txType: "收入" -2021-06-25 * "*三" "我就随便转着玩" ; 收入-交易成功-转账红包 +2021-06-25 * "*三" "我就随便转着玩" Assets:Alipay 42.00 CNY Income:FIXME -42.00 CNY + category: "转账红包" + method: "余额" + orderId: "123456" + payTime: "2021-06-25 00:00:00 +0800 CST" + source: "支付宝" + status: "交易成功" + txType: "收入" diff --git a/example/wechat/example-wechat-output.beancount b/example/wechat/example-wechat-output.beancount index 075e669..566aad2 100644 --- a/example/wechat/example-wechat-output.beancount +++ b/example/wechat/example-wechat-output.beancount @@ -15,24 +15,60 @@ option "operating_currency" "CNY" 2019-09-24 * "同性好友" "/" Assets:Digital:Wechat:Cash 0.35 CNY Income:Wechat:RedPacket -0.35 CNY + merchantId: "129847129" + method: "/" + orderId: "3985734" + payTime: "2019-09-24 10:10:11 +0800 CST" + source: "微信支付" + txType: "收入" 2019-09-26 * "云膳过桥米线(传奇广场店)" "总共消费:28.16" Expenses:Food:Meal 28.16 CNY Assets:Bank:CN:BOC:Savings -28.16 CNY + merchantId: "129847129" + method: "中国银行(1234)" + orderId: "3985734" + payTime: "2019-09-26 12:45:27 +0800 CST" + source: "微信支付" + txType: "支出" 2020-11-27 * "用户A" "收款方备注:二维码收款" Assets:Digital:Wechat:Cash 23.00 CNY Income:Service -23.00 CNY + merchantId: "/" + method: "零钱" + orderId: "3985734" + payTime: "2020-11-27 19:29:00 +0800 CST" + source: "微信支付" + txType: "收入" 2021-01-17 * "/" "/" Assets:Digital:WeChat:Cash 2000.00 CNY Assets:Bank:CN:ICBC:Savings -2000.00 CNY + merchantId: "129847129" + method: "工商银行(9876)" + orderId: "3985734" + payTime: "2021-01-17 10:07:31 +0800 CST" + source: "微信支付" + txType: "/" 2021-01-17 * "某餐厅" "收款方备注:二维码收款" Expenses:Food:Meal 12.00 CNY Assets:Digital:Wechat:Cash -12.00 CNY + merchantId: "129847129" + method: "零钱通" + orderId: "3985734" + payTime: "2021-01-17 18:03:35 +0800 CST" + source: "微信支付" + txType: "支出" 2021-01-22 * "房东" "转账备注:微信转账" Expenses:Housing:Rent 500.00 CNY Assets:Digital:Wechat:Cash -500.00 CNY + merchantId: "129847129" + method: "零钱通" + orderId: "3985734" + payTime: "2021-01-22 12:34:56 +0800 CST" + source: "微信支付" + txType: "支出" diff --git a/pkg/compiler/beancount/compiler.go b/pkg/compiler/beancount/compiler.go index dfe3ab2..e5b4c29 100644 --- a/pkg/compiler/beancount/compiler.go +++ b/pkg/compiler/beancount/compiler.go @@ -169,6 +169,7 @@ func (b *BeanCount) writeBill(file *os.File, index int) error { PlusAccount: o.PlusAccount, MinusAccount: o.MinusAccount, PnlAccount: o.ExtraAccounts[ir.PnlAccount], + Metadata: o.Metadata, Currency: b.Config.DefaultCurrency, }) case ir.OrderTypeHuobiTrade: // Huobi trades diff --git a/pkg/compiler/beancount/template.go b/pkg/compiler/beancount/template.go index 346fcc0..b611a8c 100644 --- a/pkg/compiler/beancount/template.go +++ b/pkg/compiler/beancount/template.go @@ -9,7 +9,7 @@ import ( var normalOrder = `{{ .PayTime.Format "2006-01-02" }} * "{{ .Peer }}" "{{ .Item }}"{{ if .Note }} ; {{ .Note }}{{ end }} {{ .PlusAccount }} {{ .Money | printf "%.2f" }} {{ .Currency }} {{ .MinusAccount }} -{{ .Money | printf "%.2f" }} {{ .Currency }} -{{ if .PnlAccount }} {{ .PnlAccount }}{{ printf "\n" }}{{ end }} +{{ if .PnlAccount }} {{ .PnlAccount }}{{ printf "\n" }}{{ end }}{{ range $key, $value := .Metadata }}{{ if $value }} {{ $key }}: "{{ $value }}"{{ printf "\n" }}{{end}}{{end}} ` type NormalOrderVars struct { @@ -22,6 +22,7 @@ type NormalOrderVars struct { MinusAccount string PnlAccount string Currency string + Metadata map[string]string // unordered metadata map } // 火币买入模版(手续费单位为购买单位货币) diff --git a/pkg/ir/ir.go b/pkg/ir/ir.go index 9cab6ea..e5b4eea 100644 --- a/pkg/ir/ir.go +++ b/pkg/ir/ir.go @@ -47,6 +47,7 @@ type Order struct { ExtraAccounts map[Account]string MinusAccount string PlusAccount string + Metadata map[string]string } // Unit is the key commodity names diff --git a/pkg/provider/alipay/convert.go b/pkg/provider/alipay/convert.go index 61e0fe8..8d6ed05 100644 --- a/pkg/provider/alipay/convert.go +++ b/pkg/provider/alipay/convert.go @@ -1,8 +1,6 @@ package alipay import ( - "fmt" - "github.com/deb-sig/double-entry-generator/pkg/ir" ) @@ -15,13 +13,13 @@ func (a *Alipay) convertToIR() *ir.IR { Peer: o.Peer, Item: o.ItemName, Method: o.Method, - Note: fmt.Sprintf("%s-%s-%s", o.TxTypeOriginal, o.Status, o.Category), PayTime: o.PayTime, Money: o.Money, OrderID: &o.DealNo, TxType: conevertType(o.TxType), TxTypeOriginal: o.TxTypeOriginal, } + irO.Metadata = getMetadata(o) if o.MerchantId != "" { irO.MerchantOrderID = &o.MerchantId } @@ -40,3 +38,46 @@ func conevertType(t TxTypeType) ir.TxType { return ir.TxTypeUnknown } } + +func getMetadata(o Order) map[string]string { + // FIXME(TripleZ): hard-coded, bad pattern + source := "支付宝" + var status, method, category, txType, orderId, merchantId, paytime string + + paytime = o.PayTime.String() + + if o.DealNo != "" { + orderId = o.DealNo + } + + if o.MerchantId != "" { + merchantId = o.MerchantId + } + + if o.Category != "" { + category = o.Category + } + + if o.TxTypeOriginal != "" { + txType = o.TxTypeOriginal + } + + if o.Method != "" { + method = o.Method + } + + if o.Status != "" { + status = o.Status + } + + return map[string]string{ + "source": source, + "payTime": paytime, + "orderId": orderId, + "merchantId": merchantId, + "txType": txType, + "category": category, + "method": method, + "status": status, + } +} diff --git a/pkg/provider/wechat/convert.go b/pkg/provider/wechat/convert.go index 1c8b829..28e1e15 100644 --- a/pkg/provider/wechat/convert.go +++ b/pkg/provider/wechat/convert.go @@ -18,6 +18,7 @@ func (w *Wechat) convertToIR() *ir.IR { TypeOriginal: o.TypeOriginal, Method: o.Method, } + irO.Metadata = getMetadata(o) if o.MechantOrderID != "" { irO.MerchantOrderID = &o.MechantOrderID } @@ -36,3 +37,42 @@ func conevertType(t OrderType) ir.TxType { return ir.TxTypeUnknown } } + +func getMetadata(o Order) map[string]string { + // FIXME(TripleZ): hard-coded, bad pattern + source := "微信支付" + var status, method, category, txType, orderId, merchantId, paytime string + + paytime = o.PayTime.String() + + if o.OrderID != "" { + orderId = o.OrderID + } + + if o.MechantOrderID != "" { + merchantId = o.MechantOrderID + } + + if o.TypeOriginal != "" { + txType = o.TypeOriginal + } + + if o.Method != "" { + method = o.Method + } + + if o.Status != "" { + status = o.Status + } + + return map[string]string{ + "source": source, + "payTime": paytime, + "orderId": orderId, + "merchantId": merchantId, + "txType": txType, + "category": category, + "method": method, + "status": status, + } +}