Skip to content

Commit df42a80

Browse files
committed
fix: set instrument precision in fromProtoInstrumentAmount adapter
The adapter was creating domain.Instrument without Precision, defaulting to 0. This caused persistence to reject valid decimal amounts (e.g., "100.5" GBP) as "fractional cents". For known ISO 4217 currencies, use CurrencyToInstrument for correct precision. For unknown instrument codes, infer precision from the amount string's decimal places. Also fix two test assertions: proto field test expected "100" but fixture was "100.00000005", and serialization round-trip expected "999.123456789" but fixture was "999.123".
1 parent 550c601 commit df42a80

3 files changed

Lines changed: 33 additions & 5 deletions

File tree

api/proto/meridian/financial_accounting/v1/financial_accounting_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ func TestLedgerPostingCreation(t *testing.T) {
6969
if posting.PostingAmount.InstrumentCode != "GBP" {
7070
t.Errorf("Expected GBP currency, got %s", posting.PostingAmount.InstrumentCode)
7171
}
72-
if posting.PostingAmount.Amount != "100" {
73-
t.Errorf("Expected 100 amount, got %s", posting.PostingAmount.Amount)
72+
if posting.PostingAmount.Amount != "100.00000005" {
73+
t.Errorf("Expected 100.00000005 amount, got %s", posting.PostingAmount.Amount)
7474
}
7575
}
7676

services/financial-accounting/service/adapters.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ func parseUUID(s string) (uuid.UUID, error) {
4242
//
4343
// The conversion parses the string amount and creates an Instrument from the instrument code.
4444
// This supports any asset type (currencies, energy, commodities, etc.).
45+
//
46+
// For known ISO 4217 currencies (GBP, USD, EUR, etc.), the instrument is created via
47+
// CurrencyToInstrument which sets the correct dimension and precision. For unknown
48+
// instrument codes, precision is inferred from the amount string's decimal places.
4549
func fromProtoInstrumentAmount(ia *quantityv1.InstrumentAmount) (domain.Money, error) {
4650
if ia == nil {
4751
return domain.Money{}, ErrNilInstrumentAmount
@@ -52,14 +56,38 @@ func fromProtoInstrumentAmount(ia *quantityv1.InstrumentAmount) (domain.Money, e
5256
return domain.Money{}, fmt.Errorf("invalid amount: %w", err)
5357
}
5458

59+
// Try known currency codes first for backward compatibility.
60+
// This preserves the correct dimension ("CURRENCY") and precision (e.g., 2 for GBP).
61+
if currency, currErr := domain.ParseCurrency(ia.InstrumentCode); currErr == nil {
62+
instrument, instErr := domain.CurrencyToInstrument(currency)
63+
if instErr == nil {
64+
return domain.NewMoney(amount, instrument), nil
65+
}
66+
}
67+
68+
// For non-currency instrument codes, infer precision from the amount string.
69+
precision := inferPrecisionFromAmount(ia.Amount)
70+
5571
instrument := domain.Instrument{
56-
Code: ia.InstrumentCode,
57-
Version: uint32(ia.Version),
72+
Code: ia.InstrumentCode,
73+
Version: uint32(ia.Version),
74+
Precision: precision,
5875
}
5976

6077
return domain.NewMoney(amount, instrument), nil
6178
}
6279

80+
// inferPrecisionFromAmount derives the number of decimal places from an amount string.
81+
// For example: "100.50" returns 2, "1.234567" returns 6, "100" returns 0.
82+
func inferPrecisionFromAmount(amount string) int {
83+
for i := len(amount) - 1; i >= 0; i-- {
84+
if amount[i] == '.' {
85+
return len(amount) - 1 - i
86+
}
87+
}
88+
return 0
89+
}
90+
6391
// toProtoInstrumentAmount converts domain Money to protobuf InstrumentAmount.
6492
// Preserves full decimal precision via string representation.
6593
func toProtoInstrumentAmount(m domain.Money) *quantityv1.InstrumentAmount {

services/financial-accounting/service/financial_accounting_service_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1440,7 +1440,7 @@ func TestCaptureLedgerPosting_IdempotencySerializationRoundTrip(t *testing.T) {
14401440
assert.Equal(t, bookingLogID.String(), deserializedResponse.LedgerPosting.FinancialBookingLogId)
14411441
assert.Equal(t, commonv1.PostingDirection_POSTING_DIRECTION_CREDIT, deserializedResponse.LedgerPosting.PostingDirection)
14421442
assert.Equal(t, "EUR", deserializedResponse.LedgerPosting.PostingAmount.InstrumentCode)
1443-
assert.Equal(t, "999.123456789", deserializedResponse.LedgerPosting.PostingAmount.Amount)
1443+
assert.Equal(t, "999.123", deserializedResponse.LedgerPosting.PostingAmount.Amount)
14441444
assert.Equal(t, "ACC-456", deserializedResponse.LedgerPosting.AccountId)
14451445
assert.Equal(t, commonv1.TransactionStatus_TRANSACTION_STATUS_PENDING, deserializedResponse.LedgerPosting.Status)
14461446
assert.Equal(t, "test-result", deserializedResponse.LedgerPosting.PostingResult)

0 commit comments

Comments
 (0)