beancount-gs/service/stats.go

754 lines
23 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"encoding/json"
"fmt"
"sort"
"strings"
"time"
"github.com/beancount-gs/script"
"github.com/gin-gonic/gin"
"github.com/shopspring/decimal"
)
type YearMonth struct {
Year string `bql:"distinct year(date)" json:"year"`
Month string `bql:"month(date)" json:"month"`
}
func MonthsList(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
// 添加排序
queryParams := script.GetQueryParams(c)
queryParams.OrderBy = "year, month desc"
yearMonthList := make([]YearMonth, 0)
err := script.BQLQueryList(ledgerConfig, &queryParams, &yearMonthList)
if err != nil {
InternalError(c, err.Error())
return
}
months := make([]string, 0)
for _, yearMonth := range yearMonthList {
months = append(months, yearMonth.Year+"-"+yearMonth.Month)
}
OK(c, months)
}
type StatsResult struct {
Key string
Value string
}
func StatsTotal(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
queryParams := script.GetQueryParams(c)
selectBql := fmt.Sprintf("SELECT '\\', root(account, 1), '\\', sum(convert(value(position), '%s')), '\\'", ledgerConfig.OperatingCurrency)
accountTypeTotalList := make([]StatsResult, 0)
err := script.BQLQueryListByCustomSelect(ledgerConfig, selectBql, &queryParams, &accountTypeTotalList)
if err != nil {
InternalError(c, err.Error())
return
}
result := make(map[string]string)
for _, total := range accountTypeTotalList {
fields := strings.Fields(total.Value)
if len(fields) > 1 {
result[total.Key] = fields[0]
}
}
OK(c, result)
}
type StatsQuery struct {
Prefix string `form:"prefix"`
Year int `form:"year"`
Month int `form:"month"`
Level int `form:"level"`
Type string `form:"type"`
}
type AccountPercentQueryResult struct {
Account string
Position string
}
type AccountPercentResult struct {
Account string `json:"account"`
Amount json.Number `json:"amount"`
OperatingCurrency string `json:"operatingCurrency"`
}
func StatsAccountPercent(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
var statsQuery StatsQuery
if err := c.ShouldBindQuery(&statsQuery); err != nil {
BadRequest(c, err.Error())
return
}
queryParams := script.QueryParams{
AccountLike: statsQuery.Prefix,
Year: statsQuery.Year,
Month: statsQuery.Month,
Where: true,
}
var bql string
if statsQuery.Level != 0 {
prefixNodeLen := len(strings.Split(strings.Trim(statsQuery.Prefix, ":"), ":"))
bql = fmt.Sprintf("SELECT '\\', root(account, %d) as subAccount, '\\', sum(convert(value(position), '%s')), '\\'", statsQuery.Level+prefixNodeLen, ledgerConfig.OperatingCurrency)
} else {
bql = fmt.Sprintf("SELECT '\\', account, '\\', sum(convert(value(position), '%s')), '\\'", ledgerConfig.OperatingCurrency)
}
statsQueryResultList := make([]AccountPercentQueryResult, 0)
err := script.BQLQueryListByCustomSelect(ledgerConfig, bql, &queryParams, &statsQueryResultList)
if err != nil {
InternalError(c, err.Error())
return
}
result := make([]AccountPercentResult, 0)
for _, queryRes := range statsQueryResultList {
if queryRes.Position != "" {
fields := strings.Fields(queryRes.Position)
result = append(result, AccountPercentResult{Account: queryRes.Account, Amount: json.Number(fields[0]), OperatingCurrency: fields[1]})
}
}
OK(c, result)
}
type AccountTrendResult struct {
Date string `json:"date"`
Amount json.Number `json:"amount"`
OperatingCurrency string `json:"operatingCurrency"`
}
func StatsAccountTrend(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
var statsQuery StatsQuery
if err := c.ShouldBindQuery(&statsQuery); err != nil {
BadRequest(c, err.Error())
return
}
queryParams := script.QueryParams{
AccountLike: statsQuery.Prefix,
Year: statsQuery.Year,
Month: statsQuery.Month,
Where: true,
}
var bql string
switch {
case statsQuery.Type == "day":
bql = fmt.Sprintf("SELECT '\\', date, '\\', sum(convert(value(position), '%s')), '\\'", ledgerConfig.OperatingCurrency)
case statsQuery.Type == "month":
bql = fmt.Sprintf("SELECT '\\', year, '-', month, '\\', sum(convert(value(position), '%s')), '\\'", ledgerConfig.OperatingCurrency)
case statsQuery.Type == "year":
bql = fmt.Sprintf("SELECT '\\', year, '\\', sum(convert(value(position), '%s')), '\\'", ledgerConfig.OperatingCurrency)
case statsQuery.Type == "sum":
bql = fmt.Sprintf("SELECT '\\', date, '\\', convert(balance, '%s'), '\\'", ledgerConfig.OperatingCurrency)
default:
OK(c, new([]string))
return
}
statsResultList := make([]StatsResult, 0)
err := script.BQLQueryListByCustomSelect(ledgerConfig, bql, &queryParams, &statsResultList)
if err != nil {
InternalError(c, err.Error())
return
}
result := make([]AccountTrendResult, 0)
for _, stats := range statsResultList {
commodities := strings.Split(stats.Value, ",")
// 多币种的处理方式:例如 75799.78 USD, 18500.00 IRAUSD, 176 VACHR
// 选择账本默认ledgerConfig.OperatingCurrency币种的值
var selectedCommodity = commodities[0]
for _, commodity := range commodities {
if strings.Contains(commodity, " "+ledgerConfig.OperatingCurrency) {
selectedCommodity = commodity
break
}
}
fields := strings.Fields(selectedCommodity)
amount, _ := decimal.NewFromString(fields[0])
var date = stats.Key
// 月格式化日期
if statsQuery.Type == "month" {
yearMonth := strings.Split(date, "-")
date = fmt.Sprintf("%s-%s", strings.Trim(yearMonth[0], " "), strings.Trim(yearMonth[1], " "))
}
result = append(result, AccountTrendResult{Date: date, Amount: json.Number(amount.Round(2).String()), OperatingCurrency: fields[1]})
}
OK(c, result)
}
type AccountBalanceBQLResult struct {
Year string `bql:"year" json:"year"`
Month string `bql:"month" json:"month"`
Day string `bql:"day" json:"day"`
Balance string `bql:"balance" json:"balance"`
}
type AccountBalanceResult struct {
Date string `json:"date"`
Amount json.Number `json:"amount"`
OperatingCurrency string `json:"operatingCurrency"`
}
func StatsAccountBalance(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
var statsQuery StatsQuery
if err := c.ShouldBindQuery(&statsQuery); err != nil {
BadRequest(c, err.Error())
return
}
queryParams := script.QueryParams{
AccountLike: statsQuery.Prefix,
Year: statsQuery.Year,
Month: statsQuery.Month,
Where: true,
}
balResultList := make([]AccountBalanceBQLResult, 0)
bql := fmt.Sprintf("select '\\', year, '\\', month, '\\', day, '\\', last(convert(balance, '%s')), '\\'", ledgerConfig.OperatingCurrency)
err := script.BQLQueryListByCustomSelect(ledgerConfig, bql, &queryParams, &balResultList)
if err != nil {
InternalError(c, err.Error())
return
}
resultList := make([]AccountBalanceResult, 0)
for _, bqlResult := range balResultList {
if bqlResult.Balance != "" {
fields := strings.Fields(bqlResult.Balance)
amount, _ := decimal.NewFromString(fields[0])
resultList = append(resultList, AccountBalanceResult{
Date: bqlResult.Year + "-" + bqlResult.Month + "-" + bqlResult.Day,
Amount: json.Number(amount.Round(2).String()),
OperatingCurrency: fields[1],
})
}
}
OK(c, resultList)
}
type AccountSankeyResult struct {
Nodes []AccountSankeyNode `json:"nodes"`
Links []AccountSankeyLink `json:"links"`
}
type AccountSankeyNode struct {
Name string `json:"name"`
}
type AccountSankeyLink struct {
Source int `json:"source"`
Target int `json:"target"`
Value decimal.Decimal `json:"value"`
}
func NewAccountSankeyLink() *AccountSankeyLink {
return &AccountSankeyLink{
Source: -1,
Target: -1,
}
}
type TransactionAccountPositionBQLResult struct {
Id string
Account string
Position string
}
type TransactionAccountPosition struct {
Id string
Account string
AccountName string
Value decimal.Decimal
OperatingCurrency string
}
// StatsAccountSankey 统计账户流向
func StatsAccountSankey(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
var statsQuery StatsQuery
if err := c.ShouldBindQuery(&statsQuery); err != nil {
BadRequest(c, err.Error())
return
}
queryParams := script.QueryParams{
AccountLike: statsQuery.Prefix,
Year: statsQuery.Year,
Month: statsQuery.Month,
Where: true,
}
statsQueryResultList := make([]TransactionAccountPositionBQLResult, 0)
var bql string
// 账户不为空,则查询时间范围内所有涉及该账户的交易记录
if statsQuery.Prefix != "" {
// 清空 account 查询条件,改为使用 ID 查询包含该账户所有交易记录
queryParams.AccountLike = ""
bql = "SELECT '\\', id, '\\'"
err := script.BQLQueryListByCustomSelect(ledgerConfig, bql, &queryParams, &statsQueryResultList)
if err != nil {
InternalError(c, err.Error())
return
}
if len(statsQueryResultList) != 0 {
idSet := make(map[string]bool)
for _, bqlResult := range statsQueryResultList {
idSet[bqlResult.Id] = true
}
idList := make([]string, 0, len(idSet))
for id := range idSet {
idList = append(idList, id)
}
queryParams.IDList = strings.Join(idList, "|")
}
}
if statsQuery.Level != 0 {
prefixNodeLen := len(strings.Split(strings.Trim(statsQuery.Prefix, ":"), ":"))
bql = fmt.Sprintf("SELECT '\\', id, '\\', root(account, %d) as subAccount, '\\', sum(convert(value(position), '%s')), '\\'", statsQuery.Level+prefixNodeLen, ledgerConfig.OperatingCurrency)
} else {
bql = fmt.Sprintf("SELECT '\\', id, '\\', account, '\\', sum(convert(value(position), '%s')), '\\'", ledgerConfig.OperatingCurrency)
}
statsQueryResultList = make([]TransactionAccountPositionBQLResult, 0)
err := script.BQLQueryListByCustomSelect(ledgerConfig, bql, &queryParams, &statsQueryResultList)
if err != nil {
InternalError(c, err.Error())
return
}
result := make([]Transaction, 0)
for _, queryRes := range statsQueryResultList {
if queryRes.Position != "" {
fields := strings.Fields(queryRes.Position)
result = append(result, Transaction{
Id: queryRes.Id,
Account: queryRes.Account,
Number: fields[0],
Currency: fields[1],
})
}
}
OK(c, buildSankeyResult(result))
}
func buildSankeyResult(transactions []Transaction) AccountSankeyResult {
accountSankeyResult := AccountSankeyResult{}
accountSankeyResult.Nodes = make([]AccountSankeyNode, 0)
accountSankeyResult.Links = make([]AccountSankeyLink, 0)
// 构建 nodes 和 links
var nodes []AccountSankeyNode
// 遍历 transactions 中按id进行分组
if len(transactions) > 0 {
for _, transaction := range transactions {
// 如果nodes中不存在该节点则添加
account := transaction.Account
if !contains(nodes, account) {
nodes = append(nodes, AccountSankeyNode{Name: account})
}
}
accountSankeyResult.Nodes = nodes
transactionsMap := groupTransactionsByID(transactions)
// 声明 links
links := make([]AccountSankeyLink, 0)
// 遍历 transactionsMap
for _, transactions := range transactionsMap {
// 拼接成 links
sourceTransaction := Transaction{}
targetTransaction := Transaction{}
currentLinkNode := NewAccountSankeyLink()
// transactions 的最大长度
maxCycle := len(transactions) * 2
for {
if len(transactions) == 0 || maxCycle == 0 {
break
}
transaction := transactions[0]
transactions = transactions[1:]
account := transaction.Account
num, err := decimal.NewFromString(transaction.Number)
if err != nil {
continue
}
if currentLinkNode.Source == -1 && num.IsNegative() {
if sourceTransaction.Account == "" {
sourceTransaction = transaction
}
currentLinkNode.Source = indexOf(nodes, account)
if currentLinkNode.Target == -1 {
currentLinkNode.Value = num
} else {
// 比较 link node value 和 num 大小
delta := currentLinkNode.Value.Add(num)
if delta.IsZero() {
currentLinkNode.Value = num.Abs()
} else if delta.IsNegative() { // source > target
targetNumber, _ := decimal.NewFromString(targetTransaction.Number)
currentLinkNode.Value = targetNumber.Abs()
sourceTransaction.Number = delta.String()
transactions = append(transactions, sourceTransaction)
} else { // source < target
targetTransaction.Number = delta.String()
transactions = append(transactions, targetTransaction)
}
// 完成一个 linkNode 的构建,重置判定条件
sourceTransaction.Account = ""
targetTransaction.Account = ""
links = append(links, *currentLinkNode)
currentLinkNode = NewAccountSankeyLink()
}
} else if currentLinkNode.Target == -1 && num.IsPositive() {
if targetTransaction.Account == "" {
targetTransaction = transaction
}
currentLinkNode.Target = indexOf(nodes, account)
if currentLinkNode.Source == -1 {
currentLinkNode.Value = num
} else {
delta := currentLinkNode.Value.Add(num)
if delta.IsZero() {
currentLinkNode.Value = num.Abs()
} else if delta.IsNegative() { // source > target
currentLinkNode.Value = num.Abs()
sourceTransaction.Number = delta.String()
transactions = append(transactions, sourceTransaction)
} else { // source < target
sourceNumber, _ := decimal.NewFromString(sourceTransaction.Number)
currentLinkNode.Value = sourceNumber.Abs()
targetTransaction.Number = delta.String()
transactions = append(transactions, targetTransaction)
}
// 完成一个 linkNode 的构建,重置判定条件
sourceTransaction.Account = ""
targetTransaction.Account = ""
links = append(links, *currentLinkNode)
currentLinkNode = NewAccountSankeyLink()
}
} else {
// 将当前的 transaction 加入到队列末尾
transactions = append(transactions, transaction)
}
maxCycle -= 1
}
}
accountSankeyResult.Links = aggregateLinkNodes(links)
}
return accountSankeyResult
}
func contains(nodes []AccountSankeyNode, str string) bool {
for _, s := range nodes {
if s.Name == str {
return true
}
}
return false
}
func indexOf(nodes []AccountSankeyNode, str string) int {
idx := 0
for _, s := range nodes {
if s.Name == str {
return idx
}
idx += 1
}
return -1
}
func groupTransactionsByID(transactions []Transaction) map[string][]Transaction {
grouped := make(map[string][]Transaction)
for _, transaction := range transactions {
grouped[transaction.Id] = append(grouped[transaction.Id], transaction)
}
return grouped
}
// 聚合函数,聚合相同 source 和 target 的值
func aggregateLinkNodes(nodes []AccountSankeyLink) []AccountSankeyLink {
// 创建一个映射 key 为 "source-target"value 为 LinkNode
nodeMap := make(map[string]AccountSankeyLink)
for _, node := range nodes {
key := fmt.Sprintf("%d-%d", node.Source, node.Target)
if existingNode, found := nodeMap[key]; found {
// 如果已经存在相同的 source 和 target累加 value
existingNode.Value = existingNode.Value.Add(node.Value)
nodeMap[key] = existingNode
} else {
// 否则直接插入新的 LinkNode
nodeMap[key] = node
}
}
// 将 map 转换为 slice
result := make([]AccountSankeyLink, 0, len(nodeMap))
for _, aggregatedNode := range nodeMap {
result = append(result, aggregatedNode)
}
return result
}
type MonthTotalBQLResult struct {
Year int
Month int
Value string
}
type MonthTotal struct {
Type string `json:"type"`
Month string `json:"month"`
Amount json.Number `json:"amount"`
OperatingCurrency string `json:"operatingCurrency"`
}
type MonthTotalSort []MonthTotal
func (s MonthTotalSort) Len() int {
return len(s)
}
func (s MonthTotalSort) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s MonthTotalSort) Less(i, j int) bool {
iYearMonth, _ := time.Parse("2006-1", s[i].Month)
jYearMonth, _ := time.Parse("2006-1", s[j].Month)
return iYearMonth.Before(jYearMonth)
}
func StatsMonthTotal(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
monthSet := make(map[string]bool)
queryParams := script.QueryParams{
AccountLike: "Income",
Where: true,
OrderBy: "year, month",
}
// 按月查询收入
queryIncomeBql := fmt.Sprintf("select '\\', year, '\\', month, '\\', neg(sum(convert(value(position), '%s'))), '\\'", ledgerConfig.OperatingCurrency)
monthIncomeTotalResultList := make([]MonthTotalBQLResult, 0)
err := script.BQLQueryListByCustomSelect(ledgerConfig, queryIncomeBql, &queryParams, &monthIncomeTotalResultList)
if err != nil {
InternalError(c, err.Error())
return
}
monthIncomeMap := make(map[string]MonthTotalBQLResult)
for _, income := range monthIncomeTotalResultList {
month := fmt.Sprintf("%d-%d", income.Year, income.Month)
monthSet[month] = true
monthIncomeMap[month] = income
}
// 按月查询支出
queryParams.AccountLike = "Expenses"
queryExpensesBql := fmt.Sprintf("select '\\', year, '\\', month, '\\', sum(convert(value(position), '%s')), '\\'", ledgerConfig.OperatingCurrency)
monthExpensesTotalResultList := make([]MonthTotalBQLResult, 0)
err = script.BQLQueryListByCustomSelect(ledgerConfig, queryExpensesBql, &queryParams, &monthExpensesTotalResultList)
if err != nil {
InternalError(c, err.Error())
return
}
monthExpensesMap := make(map[string]MonthTotalBQLResult)
for _, expenses := range monthExpensesTotalResultList {
month := fmt.Sprintf("%d-%d", expenses.Year, expenses.Month)
monthSet[month] = true
monthExpensesMap[month] = expenses
}
monthTotalResult := make([]MonthTotal, 0)
// 合并结果
var monthIncome, monthExpenses MonthTotal
var monthIncomeAmount, monthExpensesAmount decimal.Decimal
for month := range monthSet {
if monthIncomeMap[month].Value != "" {
fields := strings.Fields(monthIncomeMap[month].Value)
amount, _ := decimal.NewFromString(fields[0])
monthIncomeAmount = amount
monthIncome = MonthTotal{Type: "收入", Month: month, Amount: json.Number(amount.Round(2).String()), OperatingCurrency: fields[1]}
} else {
monthIncome = MonthTotal{Type: "收入", Month: month, Amount: "0", OperatingCurrency: ledgerConfig.OperatingCurrency}
}
monthTotalResult = append(monthTotalResult, monthIncome)
if monthExpensesMap[month].Value != "" {
fields := strings.Fields(monthExpensesMap[month].Value)
amount, _ := decimal.NewFromString(fields[0])
monthExpensesAmount = amount
monthExpenses = MonthTotal{Type: "支出", Month: month, Amount: json.Number(amount.Round(2).String()), OperatingCurrency: fields[1]}
} else {
monthExpenses = MonthTotal{Type: "支出", Month: month, Amount: "0", OperatingCurrency: ledgerConfig.OperatingCurrency}
}
monthTotalResult = append(monthTotalResult, monthExpenses)
monthTotalResult = append(monthTotalResult, MonthTotal{Type: "结余", Month: month, Amount: json.Number(monthIncomeAmount.Sub(monthExpensesAmount).Round(2).String()), OperatingCurrency: ledgerConfig.OperatingCurrency})
}
sort.Sort(MonthTotalSort(monthTotalResult))
OK(c, monthTotalResult)
}
type StatsMonthQuery struct {
Year int `form:"year"`
Month int `form:"month"`
}
type StatsCalendarQueryResult struct {
Date string
Account string
Position string
}
type StatsCalendarResult struct {
Date string `json:"date"`
Account string `json:"account"`
Amount json.Number `json:"amount"`
Currency string `json:"currency"`
CurrencySymbol string `json:"currencySymbol"`
}
func StatsMonthCalendar(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
var statsMonthQuery StatsMonthQuery
if err := c.ShouldBindQuery(&statsMonthQuery); err != nil {
BadRequest(c, err.Error())
return
}
queryParams := script.QueryParams{
Year: statsMonthQuery.Year,
Month: statsMonthQuery.Month,
Where: true,
}
bql := fmt.Sprintf("SELECT '\\', date, '\\', root(account, 1), '\\', sum(convert(value(position), '%s')), '\\'", ledgerConfig.OperatingCurrency)
statsCalendarQueryResult := make([]StatsCalendarQueryResult, 0)
err := script.BQLQueryListByCustomSelect(ledgerConfig, bql, &queryParams, &statsCalendarQueryResult)
if err != nil {
InternalError(c, err.Error())
return
}
resultList := make([]StatsCalendarResult, 0)
for _, queryRes := range statsCalendarQueryResult {
if queryRes.Position != "" {
fields := strings.Fields(queryRes.Position)
resultList = append(resultList,
StatsCalendarResult{
Date: queryRes.Date,
Account: queryRes.Account,
Amount: json.Number(fields[0]),
Currency: fields[1],
CurrencySymbol: script.GetCommoditySymbol(ledgerConfig.Id, fields[1]),
})
}
}
OK(c, resultList)
}
type StatsPayeeQueryResult struct {
Payee string
Count int32
Position string
}
type StatsPayeeResult struct {
Payee string `json:"payee"`
Currency string `json:"operatingCurrency"`
Value json.Number `json:"value"`
}
type StatsPayeeResultSort []StatsPayeeResult
func (s StatsPayeeResultSort) Len() int {
return len(s)
}
func (s StatsPayeeResultSort) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s StatsPayeeResultSort) Less(i, j int) bool {
a, _ := s[i].Value.Float64()
b, _ := s[j].Value.Float64()
return a <= b
}
func StatsPayee(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
var statsQuery StatsQuery
if err := c.ShouldBindQuery(&statsQuery); err != nil {
BadRequest(c, err.Error())
return
}
queryParams := script.QueryParams{
AccountLike: statsQuery.Prefix,
Year: statsQuery.Year,
Month: statsQuery.Month,
Where: true,
Currency: ledgerConfig.OperatingCurrency,
}
bql := fmt.Sprintf("SELECT '\\', payee, '\\', count(payee), '\\', sum(convert(value(position), '%s')), '\\'", ledgerConfig.OperatingCurrency)
statsPayeeQueryResultList := make([]StatsPayeeQueryResult, 0)
err := script.BQLQueryListByCustomSelect(ledgerConfig, bql, &queryParams, &statsPayeeQueryResultList)
if err != nil {
InternalError(c, err.Error())
return
}
result := make([]StatsPayeeResult, 0)
for _, l := range statsPayeeQueryResultList {
// 交易账户名称非空
if l.Payee != "" {
payee := StatsPayeeResult{
Payee: l.Payee,
Currency: ledgerConfig.OperatingCurrency,
}
//查询交易次数
if statsQuery.Type == "cot" {
payee.Value = json.Number(decimal.NewFromInt32(l.Count).String())
} else {
//查询交易金额,要过滤掉空白交易金额的科目,
// 比如 记账购买后又全额退款导致科目交易条目数>0但是累计金额=0
if l.Position != "" {
// 读取交易金额相关信息
fields := strings.Fields(l.Position)
// 交易金额
total, err := decimal.NewFromString(fields[0])
// 错误处理
if err != nil {
panic(err)
}
if statsQuery.Type == "avg" {
// 如果是查询平均交易金额
payee.Value = json.Number(total.Div(decimal.NewFromInt32(l.Count)).Round(2).String())
} else {
// 如果是查询总交易金额
payee.Value = json.Number(fields[0])
}
}
}
result = append(result, payee)
}
}
sort.Sort(StatsPayeeResultSort(result))
OK(c, result)
}
func StatsCommodityPrice(c *gin.Context) {
OK(c, script.BeanReportAllPrices(script.GetLedgerConfigFromContext(c)))
}