diff --git a/CHANGELOG.md b/CHANGELOG.md index 40885482f..7ff1dd1dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/price/Readme.md b/price/Readme.md index 34ab6fbfe..ec77a92a0 100644 --- a/price/Readme.md +++ b/price/Readme.md @@ -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. diff --git a/price/application/service.go b/price/application/service.go index 104a75db3..861c13a0c 100644 --- a/price/application/service.go +++ b/price/application/service.go @@ -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 @@ -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()) @@ -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()) } diff --git a/price/application/service_test.go b/price/application/service_test.go index 32441b0d5..432ac6550 100644 --- a/price/application/service_test.go +++ b/price/application/service_test.go @@ -7,6 +7,7 @@ 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" @@ -14,6 +15,8 @@ import ( ) func TestService_FormatPrice(t *testing.T) { + t.Parallel() + translationService := &fake.TranslationService{} labelService := &localeApplication.LabelService{} @@ -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{ @@ -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) } diff --git a/price/domain/price.go b/price/domain/price.go index 8e4ebb0d8..d49fdfe54 100644 --- a/price/domain/price.go +++ b/price/domain/price.go @@ -8,7 +8,7 @@ import ( "math/big" "strconv" - "github.com/Rhymond/go-money" + "github.com/leekchan/accounting" ) type ( @@ -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 diff --git a/price/domain/price_test.go b/price/domain/price_test.go index 211a414c5..df6061dd7 100644 --- a/price/domain/price_test.go +++ b/price/domain/price_test.go @@ -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())