4 Commits

11 changed files with 105 additions and 35 deletions

View File

@@ -28,7 +28,7 @@ tmp_dir = "tmp"
rerun = false rerun = false
rerun_delay = 500 rerun_delay = 500
send_interrupt = false send_interrupt = false
stop_on_error = false stop_on_error = true
[color] [color]
app = "" app = ""

View File

@@ -9,8 +9,10 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/model" "git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/service/currencyService" "git.ma-al.com/goc_daniel/b2b/app/service/currencyService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n" "git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/logger" "git.ma-al.com/goc_daniel/b2b/app/utils/logger"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable" "git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/query_params"
"git.ma-al.com/goc_daniel/b2b/app/utils/response" "git.ma-al.com/goc_daniel/b2b/app/utils/response"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors" "git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
@@ -33,8 +35,9 @@ func NewCurrencyHandler() *CurrencyHandler {
func CurrencyHandlerRoutes(r fiber.Router) fiber.Router { func CurrencyHandlerRoutes(r fiber.Router) fiber.Router {
handler := NewCurrencyHandler() handler := NewCurrencyHandler()
r.Post("/currency-rate", middleware.Require(perms.CurrencyWrite), handler.PostCurrencyRate) r.Patch("", middleware.Require(perms.CurrencyWrite), handler.PostCurrencyRate)
r.Get("/currency-rate/:id", handler.GetCurrencyRate) r.Get("/list", handler.List)
r.Get("/:id", handler.GetCurrencyRate)
return r return r
} }
@@ -50,7 +53,8 @@ func (h *CurrencyHandler) PostCurrencyRate(c fiber.Ctx) error {
logger.Error("failed to create currency rate", logger.Error("failed to create currency rate",
"handler", "CurrencyHandler.PostCurrencyRate", "handler", "CurrencyHandler.PostCurrencyRate",
"b2b_id_currency", currencyRate.B2bIdCurrency,
"conversion_rate", currencyRate.ConversionRate,
"error", err.Error(), "error", err.Error(),
) )
return c.Status(responseErrors.GetErrorStatus(err)). return c.Status(responseErrors.GetErrorStatus(err)).
@@ -65,16 +69,13 @@ func (h *CurrencyHandler) GetCurrencyRate(c fiber.Ctx) error {
id, err := strconv.Atoi(idStr) id, err := strconv.Atoi(idStr)
if err != nil { if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err))) return c.Status(responseErrors.GetErrorStatus(err)).JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
} }
currency, err := h.CurrencyService.GetCurrency(uint(id)) currency, err := h.CurrencyService.Get(uint(id))
if err != nil { if err != nil {
logger.Error("failed to get currency", logger.Error("failed to get currency",
"handler", "CurrencyHandler.GetCurrencyRate", "handler", "CurrencyHandler.GetCurrencyRate",
"b2b_id_currency", id,
"currency_id", id,
"error", err.Error(), "error", err.Error(),
) )
return c.Status(responseErrors.GetErrorStatus(err)).JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err))) return c.Status(responseErrors.GetErrorStatus(err)).JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
@@ -82,3 +83,37 @@ func (h *CurrencyHandler) GetCurrencyRate(c fiber.Ctx) error {
return c.JSON(response.Make(currency, 0, i18n.T_(c, response.Message_OK))) return c.JSON(response.Make(currency, 0, i18n.T_(c, response.Message_OK)))
} }
func (h *CurrencyHandler) List(c fiber.Ctx) error {
langId, ok := localeExtractor.GetLangID(c)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
}
p, filt, err := query_params.ParseFilters[model.Currency](c, columnMappingCurrencies)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
list, err := h.CurrencyService.Find(langId, p, filt)
if err != nil {
logger.Error("failed to get currency list",
"handler", "CurrencyHandler.List",
"lang_id", langId,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(&list.Items, int(list.Count), i18n.T_(c, response.Message_OK)))
}
var columnMappingCurrencies map[string]string = map[string]string{
"id": "c.id",
"ps_id_currency": "c.ps_id_currency",
"is_default": "c.is_default",
"is_active": "c.is_active",
"conversion_rate": "r.conversion_rate",
}

View File

@@ -150,7 +150,8 @@ func (s *Server) Setup() error {
restricted.StorageHandlerRoutes(restrictedStorage) restricted.StorageHandlerRoutes(restrictedStorage)
webdav.StorageHandlerRoutes(webdavStorage) webdav.StorageHandlerRoutes(webdavStorage)
restricted.CurrencyHandlerRoutes(s.restricted) restrictedCurrency := s.restricted.Group("/currency-rate")
restricted.CurrencyHandlerRoutes(restrictedCurrency)
s.api.All("*", func(c fiber.Ctx) error { s.api.All("*", func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusNotFound) return c.SendStatus(fiber.StatusNotFound)

View File

@@ -7,7 +7,7 @@ type Currency struct {
PsIDCurrency uint `json:"ps_id_currency"` PsIDCurrency uint `json:"ps_id_currency"`
IsDefault bool `json:"is_default"` IsDefault bool `json:"is_default"`
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
ConversionRate *float64 `json:"conversion_rate,omitempty"` ConversionRate *float64 `json:"conversion_rate,omitempty" gorm:"column:conversion_rate"`
} }
func (Currency) TableName() string { func (Currency) TableName() string {

View File

@@ -5,11 +5,13 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/model" "git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters" "git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find" "git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
"gorm.io/gorm"
) )
type UICurrencyRepo interface { type UICurrencyRepo interface {
CreateConversionRate(currencyRate *model.CurrencyRate) error CreateConversionRate(currencyRate *model.CurrencyRate) error
Get(id uint) (*model.Currency, error) Get(id uint) (*model.Currency, error)
Find(langId uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.Currency], error)
} }
type CurrencyRepo struct{} type CurrencyRepo struct{}
@@ -25,19 +27,12 @@ func (repo *CurrencyRepo) CreateConversionRate(currencyRate *model.CurrencyRate)
func (repo *CurrencyRepo) Get(id uint) (*model.Currency, error) { func (repo *CurrencyRepo) Get(id uint) (*model.Currency, error) {
var currency model.Currency var currency model.Currency
err := db.DB.Table("b2b_currencies c"). err := db.DB.
Select("c.*, r.conversion_rate"). Model(&model.Currency{}).
Joins(` Scopes(WithLatestRate()).
LEFT JOIN b2b_currency_rates r Select("b2b_currencies.*, r.conversion_rate").
ON r.b2b_id_currency = c.id Where("b2b_currencies.id = ?", id).
AND r.created_at = ( First(&currency).Error
SELECT MAX(created_at)
FROM b2b_currency_rates
WHERE b2b_id_currency = c.id
)
`).
Where("c.id = ?", id).
Scan(&currency).Error
return &currency, err return &currency, err
} }
@@ -46,8 +41,24 @@ func (repo *CurrencyRepo) Find(langId uint, p find.Paging, filt *filters.Filters
found, err := find.Paginate[model.Currency](langId, p, db.DB. found, err := find.Paginate[model.Currency](langId, p, db.DB.
Model(&model.Currency{}). Model(&model.Currency{}).
Scopes(WithLatestRate()).
Select("b2b_currencies.*, r.conversion_rate").
Scopes(filt.All()...), Scopes(filt.All()...),
) )
return &found, err return &found, err
} }
func WithLatestRate() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Joins(`
LEFT JOIN b2b_currency_rates r
ON r.b2b_id_currency = b2b_currencies.id
AND r.created_at = (
SELECT MAX(created_at)
FROM b2b_currency_rates
WHERE b2b_id_currency = b2b_currencies.id
)
`)
}
}

View File

@@ -3,16 +3,22 @@ package currencyService
import ( import (
"git.ma-al.com/goc_daniel/b2b/app/model" "git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/repos/currencyRepo" "git.ma-al.com/goc_daniel/b2b/app/repos/currencyRepo"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
) )
type CurrencyService struct { type CurrencyService struct {
repo currencyRepo.UICurrencyRepo repo currencyRepo.UICurrencyRepo
} }
func (s *CurrencyService) GetCurrency(id uint) (*model.Currency, error) { func (s *CurrencyService) Get(id uint) (*model.Currency, error) {
return s.repo.Get(id) return s.repo.Get(id)
} }
func (s *CurrencyService) Find(langId uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.Currency], error) {
return s.repo.Find(langId, p, filt)
}
func (s *CurrencyService) CreateCurrencyRate(currency *model.CurrencyRate) error { func (s *CurrencyService) CreateCurrencyRate(currency *model.CurrencyRate) error {
return s.repo.CreateConversionRate(currency) return s.repo.CreateConversionRate(currency)
} }

View File

@@ -31,7 +31,11 @@ func Paginate[T any](langID uint, paging Paging, stmt *gorm.DB) (Found[T], error
var items []T var items []T
var count int64 var count int64
stmt.Count(&count) countStmt := stmt.Session(&gorm.Session{}).Select("count(*)").Offset(-1).Limit(-1)
if err := countStmt.Count(&count).Error; err != nil {
return Found[T]{}, err
}
err := stmt. err := stmt.
Offset(paging.Offset()). Offset(paging.Offset()).
@@ -42,15 +46,10 @@ func Paginate[T any](langID uint, paging Paging, stmt *gorm.DB) (Found[T], error
return Found[T]{}, err return Found[T]{}, err
} }
// columnsSpec := GetColumnsSpec[T](langID)
return Found[T]{ return Found[T]{
Items: items, Items: items,
Count: uint(count), Count: uint(count),
// Spec: map[string]interface{}{ }, nil
// "columns": columnsSpec,
// },
}, err
} }
// GetColumnsSpec[T any] generates a column specification map for a given struct type T. // GetColumnsSpec[T any] generates a column specification map for a given struct type T.

View File

@@ -1,5 +1,5 @@
info: info:
name: currency name: Currency
type: http type: http
seq: 1 seq: 1

View File

@@ -0,0 +1,15 @@
info:
name: List
type: http
seq: 3
http:
method: GET
url: "{{bas_url}}/restricted/currency-rate/list"
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -1,10 +1,10 @@
info: info:
name: currency-rate name: Update currency rate
type: http type: http
seq: 2 seq: 2
http: http:
method: POST method: PATCH
url: "{{bas_url}}/restricted/currency-rate" url: "{{bas_url}}/restricted/currency-rate"
body: body:
type: json type: json

View File

@@ -43,6 +43,9 @@ INSERT INTO `b2b_currencies` (`ps_id_currency`, `is_default`, `is_active`) VALUE
('1','1','1'), ('1','1','1'),
('2','0','1'); ('2','0','1');
INSERT INTO `b2b_currency_rates` (`id`, `b2b_id_currency`, `created_at`, `conversion_rate`) VALUES ('1', '1', '2026-04-17 14:32:03', '1.000000');
INSERT INTO `b2b_currency_rates` (`id`, `b2b_id_currency`, `created_at`, `conversion_rate`) VALUES ('2', '2', '2026-04-17 14:32:03', '4.600000');
INSERT IGNORE INTO b2b_countries INSERT IGNORE INTO b2b_countries
(id, flag, ps_id_country, b2b_id_currency) (id, flag, ps_id_country, b2b_id_currency)
VALUES VALUES