beancount-gs/service/accounts.go

341 lines
10 KiB
Go
Raw Permalink Normal View History

2021-11-22 14:50:10 +00:00
package service
2021-11-23 06:58:37 +00:00
import (
2021-11-28 12:19:40 +00:00
"encoding/json"
2021-11-23 14:42:03 +00:00
"fmt"
2021-11-23 06:58:37 +00:00
"github.com/beancount-gs/script"
"github.com/gin-gonic/gin"
2023-12-05 08:21:12 +00:00
"regexp"
2021-11-23 06:58:37 +00:00
"sort"
2021-11-23 14:42:03 +00:00
"strings"
2021-11-28 12:19:40 +00:00
"time"
2021-11-23 06:58:37 +00:00
)
2021-11-22 14:50:10 +00:00
func QueryValidAccount(c *gin.Context) {
2021-11-23 06:58:37 +00:00
ledgerConfig := script.GetLedgerConfigFromContext(c)
2021-11-28 12:19:40 +00:00
allAccounts := script.GetLedgerAccounts(ledgerConfig.Id)
currencyMap := script.GetLedgerCurrencyMap(ledgerConfig.Id)
2021-11-28 12:19:40 +00:00
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
2023-12-10 10:47:49 +00:00
account.Price = currency.Price
account.PriceDate = currency.PriceDate
account.IsAnotherCurrency = true
}
}
2021-11-28 12:19:40 +00:00
result = append(result, account)
}
}
OK(c, result)
2021-11-23 06:58:37 +00:00
}
2021-11-22 14:50:10 +00:00
2021-11-23 14:42:03 +00:00
type accountPosition struct {
2023-12-05 08:21:12 +00:00
Account string `json:"account"`
MarketPosition string `json:"market_position"`
Position string `json:"position"`
2021-11-23 14:42:03 +00:00
}
func QueryAllAccount(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
2023-12-05 16:29:38 +00:00
bql := fmt.Sprintf("select '\\', account, '\\', sum(convert(value(position), '%s')) as market_position, '\\', sum(convert(value(position), currency)) as position, '\\'", ledgerConfig.OperatingCurrency)
2021-11-23 14:42:03 +00:00
accountPositions := make([]accountPosition, 0)
err := script.BQLQueryListByCustomSelect(ledgerConfig, bql, nil, &accountPositions)
if err != nil {
InternalError(c, err.Error())
return
}
// 将查询结果放入 map 中方便查询账户金额
accountPositionMap := make(map[string]accountPosition)
for _, ap := range accountPositions {
accountPositionMap[ap.Account] = ap
}
currencyMap := script.GetLedgerCurrencyMap(ledgerConfig.Id)
2021-11-23 14:42:03 +00:00
accounts := script.GetLedgerAccounts(ledgerConfig.Id)
result := make([]script.Account, 0, len(accounts))
for i := 0; i < len(accounts); i++ {
2021-11-28 12:19:40 +00:00
account := accounts[i]
2021-11-23 14:42:03 +00:00
// 过滤已结束的账户
2021-11-28 12:19:40 +00:00
if account.EndDate != "" {
2021-11-23 14:42:03 +00:00
continue
}
// 货币实时汇率(忽略账本主货币)
if account.Currency != ledgerConfig.OperatingCurrency && account.Currency != "" {
// 从 map 中获取对应货币的实时汇率和符号
currency, ok := currencyMap[account.Currency]
if ok {
account.CurrencySymbol = currency.Symbol
2023-12-10 10:47:49 +00:00
account.Price = currency.Price
account.PriceDate = currency.PriceDate
account.IsAnotherCurrency = true
}
}
2021-11-28 12:19:40 +00:00
key := account.Acc
2021-11-23 14:42:03 +00:00
typ := script.GetAccountType(ledgerConfig.Id, key)
2021-11-28 12:19:40 +00:00
account.Type = &typ
2023-12-05 08:21:12 +00:00
marketPosition := strings.Trim(accountPositionMap[key].MarketPosition, " ")
if marketPosition != "" {
fields := strings.Fields(marketPosition)
2021-11-28 12:19:40 +00:00
account.MarketNumber = fields[0]
account.MarketCurrency = fields[1]
account.MarketCurrencySymbol = script.GetCommoditySymbol(ledgerConfig.Id, fields[1])
2021-11-23 14:42:03 +00:00
}
2023-12-05 08:21:12 +00:00
position := strings.Trim(accountPositionMap[key].Position, " ")
if position != "" {
account.Positions = parseAccountPositions(ledgerConfig.Id, position)
2023-12-05 08:21:12 +00:00
}
2021-11-28 12:19:40 +00:00
result = append(result, account)
2021-11-23 14:42:03 +00:00
}
OK(c, result)
}
func parseAccountPositions(ledgerId string, input string) []script.AccountPosition {
2023-12-05 08:21:12 +00:00
// 使用正则表达式提取数字、货币代码和金额
re := regexp.MustCompile(`(-?\d+\.\d+) (\w+)`)
matches := re.FindAllStringSubmatch(input, -1)
var positions []script.AccountPosition
// 遍历匹配项并创建 AccountPosition
for _, match := range matches {
number := match[1]
currency := match[2]
// 获取货币符号
symbol := script.GetCommoditySymbol(ledgerId, currency)
2023-12-05 08:21:12 +00:00
// 创建 AccountPosition
position := script.AccountPosition{
Number: number,
Currency: currency,
CurrencySymbol: symbol,
}
// 添加到切片中
positions = append(positions, position)
}
return positions
}
2021-11-23 06:58:37 +00:00
func QueryAccountType(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
accountTypes := script.GetLedgerAccountTypes(ledgerConfig.Id)
result := make([]script.AccountType, 0)
for k, v := range accountTypes {
result = append(result, script.AccountType{Key: k, Name: v})
}
sort.Sort(script.AccountTypeSort(result))
OK(c, result)
}
2021-11-28 12:19:40 +00:00
type AddAccountForm struct {
Date string `form:"date" binding:"required"`
Account string `form:"account" binding:"required"`
// 账户计量单位可以为空
Currency string `form:"currency"`
}
func AddAccount(c *gin.Context) {
var accountForm AddAccountForm
if err := c.ShouldBindJSON(&accountForm); err != nil {
BadRequest(c, err.Error())
return
}
ledgerConfig := script.GetLedgerConfigFromContext(c)
// 判断账户是否已存在
accounts := script.GetLedgerAccounts(ledgerConfig.Id)
for _, acc := range accounts {
if acc.Acc == accountForm.Account {
DuplicateAccount(c)
return
}
}
line := fmt.Sprintf("%s open %s %s", accountForm.Date, accountForm.Account, accountForm.Currency)
if accountForm.Currency != "" && accountForm.Currency != ledgerConfig.OperatingCurrency {
line += " \"FIFO\""
}
// 写入文件
filePath := ledgerConfig.DataPath + "/account/" + strings.ToLower(script.GetAccountPrefix(accountForm.Account)) + ".bean"
2021-11-28 12:19:40 +00:00
err := script.AppendFileInNewLine(filePath, line)
if err != nil {
InternalError(c, err.Error())
return
}
// 更新缓存
typ := script.GetAccountType(ledgerConfig.Id, accountForm.Account)
account := script.Account{Acc: accountForm.Account, StartDate: accountForm.Date, Currency: accountForm.Currency, Type: &typ}
accounts = append(accounts, account)
script.UpdateLedgerAccounts(ledgerConfig.Id, accounts)
OK(c, account)
}
type AddAccountTypeForm struct {
Type string `form:"type" binding:"required"`
Name string `form:"name" binding:"required"`
}
func AddAccountType(c *gin.Context) {
var addAccountTypeForm AddAccountTypeForm
if err := c.ShouldBindJSON(&addAccountTypeForm); err != nil {
BadRequest(c, err.Error())
return
}
ledgerConfig := script.GetLedgerConfigFromContext(c)
accountTypesMap := script.GetLedgerAccountTypes(ledgerConfig.Id)
typ := addAccountTypeForm.Type
accountTypesMap[typ] = addAccountTypeForm.Name
// 更新文件
pathFile := script.GetLedgerAccountTypeFilePath(ledgerConfig.DataPath)
bytes, err := json.Marshal(accountTypesMap)
if err != nil {
InternalError(c, err.Error())
return
}
err = script.WriteFile(pathFile, string(bytes))
if err != nil {
InternalError(c, err.Error())
return
}
// 更新缓存
script.UpdateLedgerAccountTypes(ledgerConfig.Id, accountTypesMap)
OK(c, script.AccountType{
Key: addAccountTypeForm.Type,
Name: addAccountTypeForm.Name,
})
}
type CloseAccountForm struct {
Date string `form:"date" binding:"required"`
Account string `form:"account" binding:"required"`
}
func CloseAccount(c *gin.Context) {
var accountForm CloseAccountForm
if err := c.ShouldBindJSON(&accountForm); err != nil {
BadRequest(c, err.Error())
return
}
ledgerConfig := script.GetLedgerConfigFromContext(c)
line := fmt.Sprintf("%s close %s", accountForm.Date, accountForm.Account)
// 写入文件
2022-07-23 04:20:28 +00:00
filePath := ledgerConfig.DataPath + "/account/" + strings.ToLower(script.GetAccountPrefix(accountForm.Account)) + ".bean"
2021-11-28 12:19:40 +00:00
err := script.AppendFileInNewLine(filePath, line)
if err != nil {
InternalError(c, err.Error())
return
}
// 更新缓存
accounts := script.GetLedgerAccounts(ledgerConfig.Id)
for i := 0; i < len(accounts); i++ {
if accounts[i].Acc == accountForm.Account {
accounts[i].EndDate = accountForm.Date
}
}
script.UpdateLedgerAccounts(ledgerConfig.Id, accounts)
OK(c, script.Account{
Acc: accountForm.Account, EndDate: accountForm.Date,
})
}
func ChangeAccountIcon(c *gin.Context) {
2021-11-29 14:42:49 +00:00
account := c.Query("account")
if account == "" {
BadRequest(c, "account is not blank")
return
}
file, _ := c.FormFile("file")
filePath := "./public/icons/" + script.GetAccountIconName(account) + ".png"
if err := c.SaveUploadedFile(file, filePath); err != nil {
InternalError(c, err.Error())
2022-07-23 04:20:28 +00:00
// 自己完成信息提示
2021-11-29 14:42:49 +00:00
return
}
var result = make(map[string]string)
result["filename"] = filePath
OK(c, result)
2021-11-28 12:19:40 +00:00
}
type BalanceAccountForm struct {
Date string `form:"date" binding:"required" json:"date"`
Account string `form:"account" binding:"required" json:"account"`
Number string `form:"number" binding:"required" json:"number"`
}
func BalanceAccount(c *gin.Context) {
var accountForm BalanceAccountForm
if err := c.ShouldBindJSON(&accountForm); err != nil {
BadRequest(c, err.Error())
return
}
ledgerConfig := script.GetLedgerConfigFromContext(c)
// 获取当前账户信息
var acc script.Account
accounts := script.GetLedgerAccounts(ledgerConfig.Id)
for _, account := range accounts {
if account.Acc == accountForm.Account {
acc = account
}
}
today, err := time.Parse("2006-01-02", accountForm.Date)
if err != nil {
InternalError(c, err.Error())
return
}
todayStr := today.Format("2006-01-02")
yesterdayStr := today.AddDate(0, 0, -1).Format("2006-01-02")
month := today.Format("2006-01")
line := fmt.Sprintf("\r\n%s pad %s Equity:OpeningBalances", yesterdayStr, accountForm.Account)
line += fmt.Sprintf("\r\n%s balance %s %s %s", todayStr, accountForm.Account, accountForm.Number, acc.Currency)
2023-02-16 05:43:39 +00:00
// check month bean file exist
err = CreateMonthBeanFileIfNotExist(ledgerConfig.DataPath, month)
if err != nil {
if c != nil {
InternalError(c, err.Error())
}
return
}
// append padding content to month bean file
err = script.AppendFileInNewLine(script.GetLedgerMonthFilePath(ledgerConfig.DataPath, month), line)
2021-11-28 12:19:40 +00:00
if err != nil {
InternalError(c, err.Error())
return
}
result := make(map[string]string)
result["account"] = accountForm.Account
result["date"] = accountForm.Date
result["marketNumber"] = accountForm.Number
result["marketCurrency"] = ledgerConfig.OperatingCurrency
result["marketCurrencySymbol"] = script.GetCommoditySymbol(ledgerConfig.Id, ledgerConfig.OperatingCurrency)
2021-11-28 12:19:40 +00:00
OK(c, result)
}
2021-12-14 14:09:17 +00:00
func RefreshAccountCache(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
// 加载账户缓存
err := script.LoadLedgerAccounts(ledgerConfig.Id)
if err != nil {
InternalError(c, err.Error())
return
}
2023-12-06 17:24:24 +00:00
// 加载货币缓存
2023-12-10 09:56:21 +00:00
err = script.LoadLedgerCurrencyMap(ledgerConfig)
2023-12-06 17:24:24 +00:00
if err != nil {
InternalError(c, err.Error())
return
}
2021-12-14 14:09:17 +00:00
OK(c, nil)
}