diff --git a/script/bql.go b/script/bql.go index 7f5979d..47013b9 100644 --- a/script/bql.go +++ b/script/bql.go @@ -9,9 +9,12 @@ import ( ) type QueryParams struct { + Where bool `bql:"where"` Year int `bql:"year ="` Month int `bql:"month ="` AccountType string `bql:"account ~"` + OrderBy string `bql:"order by"` + Limit int `bql:"limit"` } func BQLQueryOne(ledgerConfig *Config, queryParams *QueryParams, queryResultPtr interface{}) error { @@ -62,9 +65,9 @@ func bqlRawQuery(ledgerConfig *Config, queryParamsPtr *QueryParams, queryResultP } } } + bql += " '\\'" // 查询条件不为空时,拼接查询条件 if queryParamsPtr != nil { - bql += " '\\' WHERE" queryParamsType := reflect.TypeOf(queryParamsPtr).Elem() queryParamsValue := reflect.ValueOf(queryParamsPtr).Elem() for i := 0; i < queryParamsType.NumField(); i++ { @@ -74,7 +77,13 @@ func bqlRawQuery(ledgerConfig *Config, queryParamsPtr *QueryParams, queryResultP case reflect.String: val := valueField.String() if val != "" { - bql = fmt.Sprintf("%s %s '%s' AND", bql, typeField.Tag.Get("bql"), val) + if typeField.Name == "OrderBy" { + // 去除上一个条件后缀的 AND 关键字 + bql = strings.Trim(bql, " AND") + bql = fmt.Sprintf("%s %s %s", bql, typeField.Tag.Get("bql"), val) + } else { + bql = fmt.Sprintf("%s %s '%s' AND", bql, typeField.Tag.Get("bql"), val) + } } break case reflect.Int: @@ -83,11 +92,15 @@ func bqlRawQuery(ledgerConfig *Config, queryParamsPtr *QueryParams, queryResultP bql = fmt.Sprintf("%s %s %d AND", bql, typeField.Tag.Get("bql"), val) } break + case reflect.Bool: + val := valueField.Bool() + if val { + bql = fmt.Sprintf("%s %s ", bql, typeField.Tag.Get("bql")) + } + break } } bql = strings.TrimRight(bql, " AND") - } else { - bql += " '\\'" } return queryByBQL(ledgerConfig, bql) } @@ -120,7 +133,10 @@ func parseResult(output string, queryResultPtr interface{}, selectOne bool) erro } switch field.Type.Kind() { case reflect.String: - temp[jsonName] = strings.Trim(val, " ") + v := strings.Trim(val, " ") + if v != "" { + temp[jsonName] = v + } break case reflect.Array, reflect.Slice: // 去除空格 diff --git a/script/paths.go b/script/paths.go index 2323191..88ac947 100644 --- a/script/paths.go +++ b/script/paths.go @@ -13,3 +13,7 @@ func GetExampleLedgerConfigDirPath() string { } return currentPath + "/example" } + +func GetLedgerTransactionsTemplateFilePath(dataPath string) string { + return dataPath + "/.beancount-ns/transaction_template.json" +} diff --git a/server.go b/server.go index fe737b4..c93febe 100644 --- a/server.go +++ b/server.go @@ -47,9 +47,14 @@ func RegisterRouter(router *gin.Engine) { // need authorized authorized.GET("/stats/months", service.MonthsList) authorized.GET("/transactions", service.QueryTransactions) + authorized.GET("/transactions/payee", service.QueryTransactionsPayee) + authorized.GET("/transactions/template", service.QueryTransactionsTemplate) + authorized.GET("/tags", service.QueryTags) // 兼容旧版本 authorized.GET("/entry", service.QueryTransactions) + authorized.GET("/payee", service.QueryTransactionsPayee) + authorized.GET("/transaction/template", service.QueryTransactionsTemplate) } } diff --git a/service/tags.go b/service/tags.go new file mode 100644 index 0000000..29f3380 --- /dev/null +++ b/service/tags.go @@ -0,0 +1,29 @@ +package service + +import ( + "github.com/beancount-gs/script" + "github.com/gin-gonic/gin" +) + +type Tags struct { + Value string `bql:"distinct tags" json:"value"` +} + +func QueryTags(c *gin.Context) { + ledgerConfig := script.GetLedgerConfigFromContext(c) + tags := make([]Tags, 0) + err := script.BQLQueryList(ledgerConfig, nil, &tags) + if err != nil { + InternalError(c, err.Error()) + return + } + + result := make([]string, 0) + for _, t := range tags { + if t.Value != "" { + result = append(result, t.Value) + } + } + + OK(c, result) +} diff --git a/service/transactions.go b/service/transactions.go index 08db6d6..c8b23bc 100644 --- a/service/transactions.go +++ b/service/transactions.go @@ -1,12 +1,38 @@ package service import ( - "github.com/beancount-gs/script" + "encoding/json" + script "github.com/beancount-gs/script" "github.com/gin-gonic/gin" "strconv" "strings" ) +func getQueryModel(c *gin.Context) script.QueryParams { + var queryParams script.QueryParams + var hasWhere bool + if c.Query("year") != "" { + val, err := strconv.Atoi(c.Query("year")) + if err == nil { + queryParams.Year = val + hasWhere = true + } + } + if c.Query("month") != "" { + val, err := strconv.Atoi(c.Query("month")) + if err == nil { + queryParams.Month = val + hasWhere = true + } + } + if c.Query("type") != "" { + queryParams.AccountType = c.Query("type") + hasWhere = true + } + queryParams.Where = hasWhere + return queryParams +} + type Transactions struct { Id string `bql:"id" json:"id"` Date string `bql:"date" json:"date"` @@ -20,29 +46,11 @@ type Transactions struct { CommoditySymbol string `json:"commoditySymbol"` } -func getQueryModel(c *gin.Context) script.QueryParams { - var queryParams script.QueryParams - if c.Query("year") != "" { - val, err := strconv.Atoi(c.Query("year")) - if err == nil { - queryParams.Year = val - } - } - if c.Query("month") != "" { - val, err := strconv.Atoi(c.Query("month")) - if err == nil { - queryParams.Month = val - } - } - if c.Query("type") != "" { - queryParams.AccountType = c.Query("type") - } - return queryParams -} - func QueryTransactions(c *gin.Context) { ledgerConfig := script.GetLedgerConfigFromContext(c) queryParams := getQueryModel(c) + // 倒序查询 + queryParams.OrderBy = "date desc" transactions := make([]Transactions, 0) err := script.BQLQueryList(ledgerConfig, &queryParams, &transactions) if err != nil { @@ -61,3 +69,61 @@ func QueryTransactions(c *gin.Context) { } OK(c, transactions) } + +type transactionsPayee struct { + Value string `bql:"distinct payee" json:"value"` +} + +func QueryTransactionsPayee(c *gin.Context) { + ledgerConfig := script.GetLedgerConfigFromContext(c) + payeeList := make([]transactionsPayee, 0) + queryParams := script.QueryParams{Where: false, OrderBy: "date desc", Limit: 100} + err := script.BQLQueryList(ledgerConfig, &queryParams, &payeeList) + if err != nil { + InternalError(c, err.Error()) + return + } + result := make([]string, 0) + for _, payee := range payeeList { + if payee.Value != "" { + result = append(result, payee.Value) + } + } + OK(c, result) +} + +type transactionsTemplate struct { + Id string `json:"id"` + Date string `json:"date"` + TemplateName string `json:"templateName"` + Payee string `json:"payee"` + Desc string `json:"desc"` + Entries []transactionsTemplateEntity `json:"entries"` +} + +type transactionsTemplateEntity struct { + Account string `json:"account"` + Commodity string `json:"commodity"` + Amount string `json:"amount"` +} + +func QueryTransactionsTemplate(c *gin.Context) { + ledgerConfig := script.GetLedgerConfigFromContext(c) + filePath := script.GetLedgerTransactionsTemplateFilePath(ledgerConfig.DataPath) + if script.FileIfExist(filePath) { + bytes, err := script.ReadFile(filePath) + if err != nil { + InternalError(c, err.Error()) + return + } + result := make([]transactionsTemplate, 0) + err = json.Unmarshal(bytes, &result) + if err != nil { + InternalError(c, err.Error()) + return + } + OK(c, result) + } else { + OK(c, new([]string)) + } +}