diff --git a/go.mod b/go.mod index 82258c0..d59bacc 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.17 require ( github.com/gin-gonic/gin v1.7.4 github.com/shopspring/decimal v1.3.1 + golang.org/x/text v0.3.7 ) require ( @@ -21,7 +22,6 @@ require ( github.com/ugorji/go/codec v1.2.6 // indirect golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect - golang.org/x/text v0.3.7 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/script/bql.go b/script/bql.go index bd51bc7..1015ce3 100644 --- a/script/bql.go +++ b/script/bql.go @@ -105,7 +105,7 @@ func BQLQueryListByCustomSelect(ledgerConfig *Config, selectBql string, queryPar } func BeanReportAllPrices(ledgerConfig *Config) string { - beanFilePath := ledgerConfig.DataPath + "/index.bean" + beanFilePath := GetLedgerPriceFilePath(ledgerConfig.DataPath) LogInfo(ledgerConfig.Mail, "bean-report "+beanFilePath+" all_prices") cmd := exec.Command("bean-report", beanFilePath, "all_prices") diff --git a/script/config.go b/script/config.go index c965b19..fa493e5 100644 --- a/script/config.go +++ b/script/config.go @@ -15,6 +15,7 @@ var serverConfig Config var ledgerConfigMap map[string]Config var ledgerAccountsMap map[string][]Account var ledgerAccountTypesMap map[string]map[string]string +var ledgerCurrencyMap map[string][]LedgerCurrency var whiteList []string type Config struct { @@ -52,6 +53,12 @@ type AccountType struct { Name string `json:"name"` } +type LedgerCurrency struct { + Name string `json:"name"` + Currency string `json:"currency"` + Symbol string `json:"symbol"` +} + func GetServerConfig() Config { return serverConfig } @@ -253,6 +260,10 @@ func LoadLedgerAccountsMap() error { if err != nil { return err } + err = LoadLedgerCurrencyMap(config) + if err != nil { + return err + } } return nil } @@ -375,6 +386,41 @@ func EqualServerSecret(secret string) bool { return serverSecret == secret } +func LoadLedgerCurrencyMap(config Config) error { + 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\"}]") + if err != nil { + return err + } + } + + fileContent, err := ReadFile(path) + if err != nil { + return err + } + var currencies []LedgerCurrency + err = json.Unmarshal(fileContent, ¤cies) + if err != nil { + LogSystemError("Failed unmarshal config file (" + path + ")") + return err + } + if ledgerCurrencyMap == nil { + ledgerCurrencyMap = make(map[string][]LedgerCurrency) + } + ledgerCurrencyMap[config.Id] = currencies + LogSystemInfo(fmt.Sprintf("Success load [%s] account type cache", config.Mail)) + return nil +} + +func GetLedgerCurrency(ledgerId string) []LedgerCurrency { + return ledgerCurrencyMap[ledgerId] +} + func GetCommoditySymbol(commodity string) string { switch commodity { case "CNY": diff --git a/script/paths.go b/script/paths.go index fc00dd7..e22499c 100644 --- a/script/paths.go +++ b/script/paths.go @@ -40,6 +40,10 @@ func GetLedgerAccountTypeFilePath(dataPath string) string { return dataPath + "/.beancount-gs/account_type.json" } +func GetLedgerCurrenciesFilePath(dataPath string) string { + return dataPath + "/.beancount-gs/currency.json" +} + func GetLedgerPriceFilePath(dataPath string) string { return dataPath + "/price/prices.bean" } diff --git a/server.go b/server.go index af1d02f..3a188f0 100644 --- a/server.go +++ b/server.go @@ -68,6 +68,7 @@ func RegisterRouter(router *gin.Engine) { authorized.POST("/account/balance", service.BalanceAccount) authorized.POST("/account/refresh", service.RefreshAccountCache) authorized.POST("/commodity/price", service.SyncCommodityPrice) + authorized.GET("/commodity/currencies", service.QueryAllCurrencies) authorized.GET("/stats/months", service.MonthsList) authorized.GET("/stats/total", service.StatsTotal) authorized.GET("/stats/payee", service.StatsPayee) diff --git a/service/accounts.go b/service/accounts.go index e111209..93522c0 100644 --- a/service/accounts.go +++ b/service/accounts.go @@ -306,5 +306,11 @@ func RefreshAccountCache(c *gin.Context) { InternalError(c, err.Error()) return } + // 加载货币缓存 + err = script.LoadLedgerCurrencyMap(*ledgerConfig) + if err != nil { + InternalError(c, err.Error()) + return + } OK(c, nil) } diff --git a/service/commodity.go b/service/commodity.go index ec6e1f0..4f69cd5 100644 --- a/service/commodity.go +++ b/service/commodity.go @@ -4,6 +4,8 @@ import ( "fmt" "github.com/beancount-gs/script" "github.com/gin-gonic/gin" + "strings" + "time" ) type SyncCommodityPriceForm struct { @@ -30,3 +32,68 @@ func SyncCommodityPrice(c *gin.Context) { } 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"` +} + +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) +} diff --git a/service/ledger.go b/service/ledger.go index 1c4e13b..f5e5ef9 100644 --- a/service/ledger.go +++ b/service/ledger.go @@ -272,6 +272,11 @@ func createNewLedger(loginForm LoginForm, ledgerId string) (*script.Config, erro if err != nil { return nil, err } + // add currency cache + err = script.LoadLedgerCurrencyMap(ledgerConfig) + if err != nil { + return nil, err + } return &ledgerConfig, nil }