Skip to content

Commit

Permalink
feat(price): Introduce usage of LocaleInfo in price format and allow …
Browse files Browse the repository at this point in the history
…price format options

Switched from go-money to LocaleInfo from accounting library which is already used in other places and flamingo core to enhance flexibility with currency formatting
  • Loading branch information
tessig committed Jan 13, 2025
1 parent 9e96c2c commit 9fcbecf
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 19 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Changelog

## v3.12.0 [upcoming]

**price**
* On formatting prices, before falling back to `core.locale.accounting.default` configuration, try to get the config from `LocaleInfo` from github.com/leekchan/accounting.
* Introduce options for `FormatPrice` to override settings occasionally, e.g. to display the currency symbol after the ISO4217 enforcement from v3.10.0
* Remove currency library github.com/Rhymond/go-money in favour of `LocaleInfo` from github.com/leekchan/accounting. All currency codes still should comply to ISO4217.

**product**
* Added exported method BundleConfiguration to the GraphQL DTO for the bundle product

Expand Down
2 changes: 2 additions & 0 deletions price/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,5 @@ Can be used in places where you need to give the price value a certain extra sem

Just use the template function commercePriceFormat like this: `commercePriceFormat(priceObject)`
The template functions used the configurations of the Flamingo "locale" package. For more details on the configuration options please read there.

When there is nothing configured, it tries to load the configuration from `leekchan/accounting`'s `LocalInfo` before falling back to the `default` section of the local package.
34 changes: 32 additions & 2 deletions price/application/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Service struct {

// Inject dependencies
func (s *Service) Inject(labelService *application.LabelService, config *struct {
Config config.Map `inject:"config:locale.accounting"`
Config config.Map `inject:"config:core.locale.accounting"`
}) {
s.labelService = labelService
s.config = config.Config
Expand All @@ -28,14 +28,35 @@ func (s *Service) getConfigForCurrency(currency string) config.Map {
return configForCurrency.(config.Map)
}

if info, ok := accounting.LocaleInfo[currency]; ok {
format := "%s %v"
if !info.Pre {
format = "%v %s"
}

return config.Map{
"decimal": info.DecSep,
"thousand": info.ThouSep,
"precision": float64(info.FractionLength),
"format": format,
}
}

if defaultConfig, ok := s.config["default"].(config.Map); ok {
return defaultConfig
}
return nil
}

// WithComSymbol tries to get the commercial symbol from LocaleInfo and overrides the currency code if found
func WithComSymbol(ac *accounting.Accounting) {
if info, ok := accounting.LocaleInfo[ac.Symbol]; ok {
ac.Symbol = info.ComSymbol
}
}

// FormatPrice by price
func (s *Service) FormatPrice(price domain.Price) string {
func (s *Service) FormatPrice(price domain.Price, options ...func(*accounting.Accounting)) string {
currency := s.labelService.NewLabel(price.Currency()).String()

configForCurrency := s.getConfigForCurrency(price.Currency())
Expand All @@ -44,26 +65,35 @@ func (s *Service) FormatPrice(price domain.Price) string {
Symbol: currency,
Precision: 2,
}

precision, ok := configForCurrency["precision"].(float64)
if ok {
ac.Precision = int(precision)
}

decimal, ok := configForCurrency["decimal"].(string)
if ok {
ac.Decimal = decimal
}

thousand, ok := configForCurrency["thousand"].(string)
if ok {
ac.Thousand = thousand
}

formatZero, ok := configForCurrency["formatZero"].(string)
if ok {
ac.FormatZero = formatZero
}

format, ok := configForCurrency["format"].(string)
if ok {
ac.Format = format
}

for _, option := range options {
option(&ac)
}

return ac.FormatMoney(price.GetPayable().FloatAmount())
}
49 changes: 42 additions & 7 deletions price/application/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import (
"flamingo.me/flamingo/v3/core/locale/domain"
"flamingo.me/flamingo/v3/core/locale/infrastructure/fake"
"flamingo.me/flamingo/v3/framework/config"
"github.com/leekchan/accounting"
"github.com/stretchr/testify/assert"

"flamingo.me/flamingo-commerce/v3/price/application"
priceDomain "flamingo.me/flamingo-commerce/v3/price/domain"
)

func TestService_FormatPrice(t *testing.T) {
t.Parallel()

translationService := &fake.TranslationService{}

labelService := &localeApplication.LabelService{}
Expand All @@ -33,7 +36,7 @@ func TestService_FormatPrice(t *testing.T) {
service.Inject(
labelService,
&struct {
Config config.Map `inject:"config:locale.accounting"`
Config config.Map `inject:"config:core.locale.accounting"`
}{
Config: config.Map{
"JPY": config.Map{
Expand All @@ -56,11 +59,43 @@ func TestService_FormatPrice(t *testing.T) {
},
)

price := priceDomain.NewFromFloat(-161.92, "USD")
formatted := service.FormatPrice(price)
assert.Equal(t, "-USD161.92", formatted)
t.Run("standard USD format from LocaleInfo", func(t *testing.T) {
t.Parallel()

price := priceDomain.NewFromFloat(-161.92, "USD")
formatted := service.FormatPrice(price)
assert.Equal(t, "-USD 161.92", formatted)
})

t.Run("override USD format from LocaleInfo using options", func(t *testing.T) {
t.Parallel()

price := priceDomain.NewFromFloat(-1161.92, "USD")
formatted := service.FormatPrice(price,
application.WithComSymbol,
func(ac *accounting.Accounting) {
ac.Thousand = "#"
ac.Decimal = "«"
ac.Precision = 1
})
assert.Equal(t, "-$ 1#161«9", formatted)
})

t.Run("unknown currency fall back", func(t *testing.T) {
t.Parallel()

// unknown currency will fall back to locale default settings as well as to fraction length 0 / precision 1
price := priceDomain.NewFromFloat(-161.92, "DEFAULT")
formatted := service.FormatPrice(price)
assert.Equal(t, "-DEFAULT162.00", formatted)
})

t.Run("use core.locale.accounting config", func(t *testing.T) {
t.Parallel()

price := priceDomain.NewFromFloat(-161.92, "JPY")
formatted := service.FormatPrice(price)
assert.Equal(t, "-JPY162", formatted)
})

price = priceDomain.NewFromFloat(-161.92, "JPY")
formatted = service.FormatPrice(price)
assert.Equal(t, "-JPY162", formatted)
}
13 changes: 5 additions & 8 deletions price/domain/price.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"math/big"
"strconv"

"github.com/Rhymond/go-money"
"github.com/leekchan/accounting"
)

type (
Expand Down Expand Up @@ -366,17 +366,14 @@ func (p Price) precisionF(precision int) *big.Float {

// precisionF - 10 * n - n is the amount of decimal numbers after comma
func (p Price) payableRoundingPrecision() (string, int) {
currency := money.GetCurrency(p.currency)
if currency == nil {
currency, ok := accounting.LocaleInfo[p.currency]
if !ok {
return RoundingModeFloor, 1
}

precision := 1
for i := 1; i <= currency.Fraction; i++ {
precision *= 10
}
precision := math.Pow10(currency.FractionLength)

return RoundingModeHalfUp, precision
return RoundingModeHalfUp, int(precision)
}

// SplitInPayables returns "count" payable prices (each rounded) that in sum matches the given price
Expand Down
4 changes: 2 additions & 2 deletions price/domain/price_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ func TestPrice_GetPayable(t *testing.T) {
t.Run("rounding with Chilean Unit of Account currency code", func(t *testing.T) {
t.Parallel()

price := domain.NewFromFloat(12.34567, "CLF")
price := domain.NewFromFloat(12.34567, "BHD")

payable := price.GetPayable()
assert.Equal(t, 12.3457, payable.FloatAmount())
assert.Equal(t, 12.346, payable.FloatAmount())

price = domain.NewFromFloat(math.MaxInt64, "CLF").GetPayable()
assert.Equal(t, float64(math.MaxInt64), price.FloatAmount())
Expand Down

0 comments on commit 9fcbecf

Please sign in to comment.