From 89f287fe9053bd3dc96c70038971195c6aabd3bc Mon Sep 17 00:00:00 2001 From: BaoXuebin Date: Thu, 7 Dec 2023 13:57:43 +0800 Subject: [PATCH] feat: currency account add excahnge rate value and currency flag --- script/config.go | 154 ++++++++++++++++++++++++++++++++-------- service/accounts.go | 34 +++++++-- service/commodity.go | 67 ++--------------- service/import.go | 8 +-- service/ledger.go | 4 +- service/stats.go | 2 +- service/transactions.go | 2 +- 7 files changed, 168 insertions(+), 103 deletions(-) diff --git a/script/config.go b/script/config.go index fa493e5..4d893f9 100644 --- a/script/config.go +++ b/script/config.go @@ -6,12 +6,14 @@ import ( "os" "sort" "strings" + "time" "github.com/gin-gonic/gin" ) var serverSecret string var serverConfig Config +var serverCurrencies []LedgerCurrency var ledgerConfigMap map[string]Config var ledgerAccountsMap map[string][]Account var ledgerAccountTypesMap map[string]map[string]string @@ -33,7 +35,10 @@ type Config struct { type Account struct { Acc string `json:"account"` StartDate string `json:"startDate"` - Currency string `json:"currency,omitempty"` + Currency string `json:"currency,omitempty"` // 货币 + CurrencySymbol string `json:"currencySymbol,omitempty"` // 货币符号 + ExRate string `json:"exRate,omitempty"` // 汇率 + ExDate string `json:"exDate,omitempty"` // 汇率日期 Positions []AccountPosition `json:"positions,omitempty"` MarketNumber string `json:"marketNumber,omitempty"` MarketCurrency string `json:"marketCurrency,omitempty"` @@ -54,9 +59,13 @@ type AccountType struct { } type LedgerCurrency struct { - Name string `json:"name"` - Currency string `json:"currency"` - Symbol string `json:"symbol"` + Name string `json:"name"` + Currency string `json:"currency"` + Symbol string `json:"symbol"` + IsCurrent bool `json:"isCurrent,omitempty"` // 是否是货币(非货币的为投资单位) + Current bool `json:"current,omitempty"` + ExRate string `json:"exRate,omitempty"` + Date string `json:"date,omitempty"` } func GetServerConfig() Config { @@ -194,7 +203,7 @@ func GetAccountType(ledgerId string, acc string) AccountType { // 默认取最后一个节点 Name: accNodes[len(accNodes)-1], } - var matchKey string = "" + var matchKey = "" for key, name := range accountTypes { if strings.Contains(acc, key) && len(matchKey) < len(key) { matchKey = key @@ -386,14 +395,32 @@ func EqualServerSecret(secret string) bool { return serverSecret == secret } +func LoadServerCurrencyMap() { + if serverCurrencies == nil { + serverCurrencies = make([]LedgerCurrency, 0) + } + serverCurrencies = append(serverCurrencies, LedgerCurrency{Name: "人民币", Currency: "CNY", Symbol: "¥"}) + serverCurrencies = append(serverCurrencies, LedgerCurrency{Name: "美元", Currency: "USD", Symbol: "$"}) + serverCurrencies = append(serverCurrencies, LedgerCurrency{Name: "欧元", Currency: "EUR", Symbol: "€"}) + serverCurrencies = append(serverCurrencies, LedgerCurrency{Name: "日元", Currency: "JPY", Symbol: "¥"}) + serverCurrencies = append(serverCurrencies, LedgerCurrency{Name: "加拿大元", Currency: "CAD", Symbol: "$"}) + serverCurrencies = append(serverCurrencies, LedgerCurrency{Name: "俄罗斯卢布", Currency: "RUB", Symbol: "₽"}) +} + func LoadLedgerCurrencyMap(config Config) error { + LoadServerCurrencyMap() path := GetLedgerCurrenciesFilePath(config.DataPath) if !FileIfExist(path) { err := CreateFile(path) if err != nil { return err } - err = WriteFile(path, "[{\"name\":\"人民币\",\"symbol\":\"¥\",\"currency\":\"CNY\"},{\"name\":\"美元\",\"symbol\":\"$\",\"currency\":\"USD\"},{\"name\":\"欧元\",\"symbol\":\"€\",\"currency\":\"EUR\"},{\"name\":\"英镑\",\"symbol\":\"£\",\"currency\":\"GBP\"},{\"name\":\"日元\",\"symbol\":\"¥\",\"currency\":\"JPY\"},{\"name\":\"加拿大元\",\"symbol\":\"$\",\"currency\":\"CAD\"},{\"name\":\"澳大利亚元\",\"symbol\":\"$\",\"currency\":\"AUD\"},{\"name\":\"瑞士法郎\",\"symbol\":\"CHF\",\"currency\":\"CHF\"},{\"name\":\"俄罗斯卢布\",\"symbol\":\"₽\",\"currency\":\"RUB\"}]") + + bytes, err := json.Marshal(serverCurrencies) + if err != nil { + return err + } + err = WriteFile(path, string(bytes)) if err != nil { return err } @@ -414,6 +441,8 @@ func LoadLedgerCurrencyMap(config Config) error { } ledgerCurrencyMap[config.Id] = currencies LogSystemInfo(fmt.Sprintf("Success load [%s] account type cache", config.Mail)) + // 刷新汇率 + RefreshLedgerCurrency(&config) return nil } @@ -421,28 +450,97 @@ func GetLedgerCurrency(ledgerId string) []LedgerCurrency { return ledgerCurrencyMap[ledgerId] } -func GetCommoditySymbol(commodity string) string { - switch commodity { - case "CNY": - return "¥" - case "USD": - return "$" - case "EUR": - return "€" - case "JPY": - return "¥" - case "GBP": - return "£" - case "AUD": - return "$" - case "CAD": - return "$" - case "INR": - return "₹" - case "RUB": - return "₽" - case "BRL": - return "R$" +type CommodityPrice struct { + Date string `json:"date"` + Commodity string `json:"commodity"` + Currency string `json:"operatingCurrency"` + Value string `json:"value"` +} + +func RefreshLedgerCurrency(ledgerConfig *Config) []LedgerCurrency { + // 查询货币获取当前汇率 + output := BeanReportAllPrices(ledgerConfig) + statsPricesResultList := make([]CommodityPrice, 0) + lines := strings.Split(output, "\n") + // foreach lines + for _, line := range lines { + if strings.Trim(line, " ") == "" { + continue + } + // split line by " " + words := strings.Fields(line) + statsPricesResultList = append(statsPricesResultList, CommodityPrice{ + Date: words[0], + Commodity: words[2], + Value: words[3], + Currency: words[4], + }) + } + + // statsPricesResultList 转为 map + existCurrencyMap := make(map[string]CommodityPrice) + for _, statsPricesResult := range statsPricesResultList { + existCurrencyMap[statsPricesResult.Commodity] = statsPricesResult + } + + result := make([]LedgerCurrency, 0) + currencies := GetLedgerCurrency(ledgerConfig.Id) + for _, c := range currencies { + current := c.Currency == ledgerConfig.OperatingCurrency + var exRate string + var date string + if current { + exRate = "1" + date = time.Now().Format("2006-01-02") + } else { + value, exists := existCurrencyMap[c.Currency] + if exists { + exRate = value.Value + date = value.Date + } + } + result = append(result, LedgerCurrency{ + Name: c.Name, + Currency: c.Currency, + Symbol: c.Symbol, + Current: current, + ExRate: exRate, + Date: date, + }) + } + // 刷新账本货币缓存 + ledgerCurrencyMap[ledgerConfig.Id] = result + return result +} + +func GetLedgerCurrencyMap(ledgerId string) map[string]LedgerCurrency { + currencyMap := make(map[string]LedgerCurrency) + currencies := GetLedgerCurrency(ledgerId) + if currencies == nil { + return currencyMap + } + for _, currency := range currencies { + currencyMap[currency.Currency] = currency + } + return currencyMap +} + +func GetCommoditySymbol(ledgerId string, commodity string) string { + currencyMap := GetLedgerCurrencyMap(ledgerId) + if currencyMap == nil { + return commodity + } + if _, ok := currencyMap[commodity]; !ok { + return commodity + } + return currencyMap[commodity].Symbol +} + +func GetServerCommoditySymbol(commodity string) string { + for _, currency := range serverCurrencies { + if currency.Currency == commodity { + return currency.Symbol + } } return commodity } diff --git a/service/accounts.go b/service/accounts.go index 93522c0..7036151 100644 --- a/service/accounts.go +++ b/service/accounts.go @@ -14,9 +14,21 @@ import ( func QueryValidAccount(c *gin.Context) { ledgerConfig := script.GetLedgerConfigFromContext(c) allAccounts := script.GetLedgerAccounts(ledgerConfig.Id) + currencyMap := script.GetLedgerCurrencyMap(ledgerConfig.Id) result := make([]script.Account, 0) for _, account := range allAccounts { if account.EndDate == "" { + // 货币实时汇率(忽略账本主货币) + if account.Currency != ledgerConfig.OperatingCurrency && account.Currency != "" { + // 从 map 中获取对应货币的实时汇率和符号 + currency, ok := currencyMap[account.Currency] + if ok { + account.CurrencySymbol = currency.Symbol + account.ExRate = currency.ExRate + account.ExDate = currency.Date + account.IsAnotherCurrency = true + } + } result = append(result, account) } } @@ -45,6 +57,7 @@ func QueryAllAccount(c *gin.Context) { accountPositionMap[ap.Account] = ap } + currencyMap := script.GetLedgerCurrencyMap(ledgerConfig.Id) accounts := script.GetLedgerAccounts(ledgerConfig.Id) result := make([]script.Account, 0, len(accounts)) for i := 0; i < len(accounts); i++ { @@ -53,6 +66,17 @@ func QueryAllAccount(c *gin.Context) { if account.EndDate != "" { continue } + // 货币实时汇率(忽略账本主货币) + if account.Currency != ledgerConfig.OperatingCurrency && account.Currency != "" { + // 从 map 中获取对应货币的实时汇率和符号 + currency, ok := currencyMap[account.Currency] + if ok { + account.CurrencySymbol = currency.Symbol + account.ExRate = currency.ExRate + account.ExDate = currency.Date + account.IsAnotherCurrency = true + } + } key := account.Acc typ := script.GetAccountType(ledgerConfig.Id, key) account.Type = &typ @@ -61,18 +85,18 @@ func QueryAllAccount(c *gin.Context) { fields := strings.Fields(marketPosition) account.MarketNumber = fields[0] account.MarketCurrency = fields[1] - account.MarketCurrencySymbol = script.GetCommoditySymbol(fields[1]) + account.MarketCurrencySymbol = script.GetCommoditySymbol(ledgerConfig.Id, fields[1]) } position := strings.Trim(accountPositionMap[key].Position, " ") if position != "" { - account.Positions = parseAccountPositions(position) + account.Positions = parseAccountPositions(ledgerConfig.Id, position) } result = append(result, account) } OK(c, result) } -func parseAccountPositions(input string) []script.AccountPosition { +func parseAccountPositions(ledgerId string, input string) []script.AccountPosition { // 使用正则表达式提取数字、货币代码和金额 re := regexp.MustCompile(`(-?\d+\.\d+) (\w+)`) matches := re.FindAllStringSubmatch(input, -1) @@ -85,7 +109,7 @@ func parseAccountPositions(input string) []script.AccountPosition { currency := match[2] // 获取货币符号 - symbol := script.GetCommoditySymbol(currency) + symbol := script.GetCommoditySymbol(ledgerId, currency) // 创建 AccountPosition position := script.AccountPosition{ @@ -294,7 +318,7 @@ func BalanceAccount(c *gin.Context) { result["date"] = accountForm.Date result["marketNumber"] = accountForm.Number result["marketCurrency"] = ledgerConfig.OperatingCurrency - result["marketCurrencySymbol"] = script.GetCommoditySymbol(ledgerConfig.OperatingCurrency) + result["marketCurrencySymbol"] = script.GetCommoditySymbol(ledgerConfig.Id, ledgerConfig.OperatingCurrency) OK(c, result) } diff --git a/service/commodity.go b/service/commodity.go index 4f69cd5..7e54506 100644 --- a/service/commodity.go +++ b/service/commodity.go @@ -4,8 +4,6 @@ import ( "fmt" "github.com/beancount-gs/script" "github.com/gin-gonic/gin" - "strings" - "time" ) type SyncCommodityPriceForm struct { @@ -30,70 +28,15 @@ func SyncCommodityPrice(c *gin.Context) { InternalError(c, err.Error()) return } - OK(c, syncCommodityPriceForm) -} -type CommodityCurrency struct { - Name string `json:"name"` - Currency string `json:"currency"` - Symbol string `json:"symbol"` - Current bool `json:"current"` - ExRate string `json:"exRate"` - Date string `json:"date"` + // 刷新货币最新汇率值 + script.RefreshLedgerCurrency(ledgerConfig) + OK(c, syncCommodityPriceForm) } func QueryAllCurrencies(c *gin.Context) { ledgerConfig := script.GetLedgerConfigFromContext(c) - // 查询货币获取当前汇率 - output := script.BeanReportAllPrices(ledgerConfig) - statsPricesResultList := make([]StatsPricesResult, 0) - lines := strings.Split(output, "\n") - // foreach lines - for _, line := range lines { - if strings.Trim(line, " ") == "" { - continue - } - // split line by " " - words := strings.Fields(line) - statsPricesResultList = append(statsPricesResultList, StatsPricesResult{ - Date: words[0], - Commodity: words[2], - Value: words[3], - Currency: words[4], - }) - } - // statsPricesResultList 转为 map - existCurrencyMap := make(map[string]StatsPricesResult) - for _, statsPricesResult := range statsPricesResultList { - existCurrencyMap[statsPricesResult.Commodity] = statsPricesResult - } - - result := make([]CommodityCurrency, 0) - currencies := script.GetLedgerCurrency(ledgerConfig.Id) - for _, c := range currencies { - current := c.Currency == ledgerConfig.OperatingCurrency - var exRate string - var date string - if current { - exRate = "1" - date = time.Now().Format("2006-01-02") - } else { - value, exists := existCurrencyMap[c.Currency] - if exists { - exRate = value.Value - date = value.Date - } - } - result = append(result, CommodityCurrency{ - Name: c.Name, - Currency: c.Currency, - Symbol: c.Symbol, - Current: current, - ExRate: exRate, - Date: date, - }) - } - - OK(c, result) + currency := script.RefreshLedgerCurrency(ledgerConfig) + OK(c, currency) } diff --git a/service/import.go b/service/import.go index af806d0..36f77a9 100644 --- a/service/import.go +++ b/service/import.go @@ -23,7 +23,7 @@ func ImportAliPayCSV(c *gin.Context) { result := make([]Transaction, 0) currency := "CNY" - currencySymbol := script.GetCommoditySymbol(currency) + currencySymbol := script.GetCommoditySymbol(ledgerConfig.Id, currency) for { lines, err := reader.Read() @@ -124,7 +124,7 @@ func ImportWxPayCSV(c *gin.Context) { result := make([]Transaction, 0) currency := "CNY" - currencySymbol := script.GetCommoditySymbol(currency) + currencySymbol := script.GetCommoditySymbol(ledgerConfig.Id, currency) for { lines, err := reader.Read() @@ -173,7 +173,7 @@ func ImportICBCCSV(c *gin.Context) { result := make([]Transaction, 0) currency := "CNY" - currencySymbol := script.GetCommoditySymbol(currency) + currencySymbol := script.GetCommoditySymbol(ledgerConfig.Id, currency) id := 0 for { @@ -226,7 +226,7 @@ func ImportABCCSV(c *gin.Context) { result := make([]Transaction, 0) currency := "CNY" - currencySymbol := script.GetCommoditySymbol(currency) + currencySymbol := script.GetCommoditySymbol(ledgerConfig.Id, currency) id := 0 for { diff --git a/service/ledger.go b/service/ledger.go index f5e5ef9..8f82123 100644 --- a/service/ledger.go +++ b/service/ledger.go @@ -160,7 +160,7 @@ func OpenOrCreateLedger(c *gin.Context) { resultMap["ledgerId"] = ledgerId resultMap["title"] = userLedger.Title resultMap["currency"] = userLedger.OperatingCurrency - resultMap["currencySymbol"] = script.GetCommoditySymbol(userLedger.OperatingCurrency) + resultMap["currencySymbol"] = script.GetServerCommoditySymbol(userLedger.OperatingCurrency) resultMap["createDate"] = userLedger.CreateDate OK(c, resultMap) return @@ -176,7 +176,7 @@ func OpenOrCreateLedger(c *gin.Context) { resultMap["ledgerId"] = ledgerId resultMap["title"] = userLedger.Title resultMap["currency"] = userLedger.OperatingCurrency - resultMap["currencySymbol"] = script.GetCommoditySymbol(userLedger.OperatingCurrency) + resultMap["currencySymbol"] = script.GetCommoditySymbol(ledgerId, userLedger.OperatingCurrency) resultMap["createDate"] = userLedger.CreateDate OK(c, resultMap) } diff --git a/service/stats.go b/service/stats.go index edfb32a..62450f0 100644 --- a/service/stats.go +++ b/service/stats.go @@ -386,7 +386,7 @@ func StatsMonthCalendar(c *gin.Context) { Account: queryRes.Account, Amount: json.Number(fields[0]), Currency: fields[1], - CurrencySymbol: script.GetCommoditySymbol(fields[1]), + CurrencySymbol: script.GetCommoditySymbol(ledgerConfig.Id, fields[1]), }) } } diff --git a/service/transactions.go b/service/transactions.go index fa5c320..6fc1d70 100644 --- a/service/transactions.go +++ b/service/transactions.go @@ -46,7 +46,7 @@ func QueryTransactions(c *gin.Context) { } // 格式化金额 for i := 0; i < len(transactions); i++ { - symbol := script.GetCommoditySymbol(transactions[i].Currency) + symbol := script.GetCommoditySymbol(ledgerConfig.Id, transactions[i].Currency) transactions[i].CurrencySymbol = symbol transactions[i].CostCurrencySymbol = symbol if transactions[i].Price != "" {