Skip to content

Commit 69a34f1

Browse files
authored
feat: migrate financial_accounting.proto from Money to InstrumentAmount (#2114)
* feat: migrate financial_accounting.proto from google.type.Money to InstrumentAmount Replace google.type.Money with meridian.quantity.v1.InstrumentAmount for LedgerPosting.posting_amount and CaptureLedgerPostingRequest.posting_amount. This enables universal asset support (currencies, energy, commodities, etc.) instead of being limited to ISO 4217 currency codes. Changes: - Replace Money with InstrumentAmount for posting_amount fields (field numbers preserved) - Update CEL validation rules for string-based positive amount enforcement - Rename ListLedgerPostingsRequest.currency to instrument_code with expanded validation - Add fromProtoInstrumentAmount/toProtoInstrumentAmount adapter functions - Retain toProtoMoney in grpc_mappers.go for event publishing (events proto unchanged) - Fix all dependent services: current-account, payment-order, mcp-server - Update all test files across the repository * chore: regenerate proto files for CI freshness check * chore: regenerate financial_accounting proto Go bindings * chore: regenerate financial_accounting proto with buf 1.67.0 Skip gofumpt hook - it reformats .pb.go imports, causing CI proto freshness check to fail since CI compares raw buf output. * fix: address review feedback on InstrumentAmount migration - Add round-trip assertions for PostingAmount fields in serialization test - Deduplicate cents-to-InstrumentAmount conversion in grpc_lifecycle.go to use shared buildPostingAmount helper * 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". * fix: use instrument precision in buildPostingAmount instead of hardcoded 2 - Accept precision parameter to support non-standard currencies (JPY, KWD) and non-currency instruments (KWH, GPU_HOUR) - Handle negative amounts correctly using absolute value with sign prefix - Add unit tests covering precision variants, negatives, and zero amounts - Document intentional struct literal in adapters.go for non-currency path where proto InstrumentAmount lacks dimension/precision fields --------- Co-authored-by: Ben Coombs <bjcoombs@users.noreply.github.com>
1 parent d429050 commit 69a34f1

24 files changed

Lines changed: 1109 additions & 932 deletions

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

Lines changed: 535 additions & 523 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/proto/meridian/financial_accounting/v1/financial_accounting.proto

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ package meridian.financial_accounting.v1;
55
import "buf/validate/validate.proto";
66
import "google/api/annotations.proto";
77
import "google/protobuf/timestamp.proto";
8-
import "google/type/money.proto";
98
import "meridian/common/v1/types.proto";
9+
import "meridian/quantity/v1/quantity.proto";
1010
import "protoc-gen-openapiv2/options/annotations.proto";
1111

1212
option go_package = "github.com/meridianhub/meridian/api/proto/meridian/financial_accounting/v1;financialaccountingv1";
@@ -135,13 +135,14 @@ message LedgerPosting {
135135
not_in: [0]
136136
}];
137137

138-
// posting_amount is the monetary amount being posted (must be positive for accounting entries).
139-
google.type.Money posting_amount = 4 [
138+
// posting_amount is the amount being posted (must be positive for accounting entries).
139+
// Uses InstrumentAmount for universal asset support (currencies, energy, commodities, etc.).
140+
meridian.quantity.v1.InstrumentAmount posting_amount = 4 [
140141
(buf.validate.field).required = true,
141142
(buf.validate.field).cel = {
142143
id: "positive_posting_amount"
143144
message: "posting amount must be greater than zero"
144-
expression: "this.units > 0 || (this.units == 0 && this.nanos > 0)"
145+
expression: "this.amount.matches('^[1-9][0-9]*(\\\\.[0-9]+)?$') || this.amount.matches('^0\\\\.[0-9]*[1-9][0-9]*$')"
145146
}
146147
];
147148

@@ -283,12 +284,13 @@ message CaptureLedgerPostingRequest {
283284
}];
284285

285286
// posting_amount is the amount to post (must be positive).
286-
google.type.Money posting_amount = 3 [
287+
// Uses InstrumentAmount for universal asset support (currencies, energy, commodities, etc.).
288+
meridian.quantity.v1.InstrumentAmount posting_amount = 3 [
287289
(buf.validate.field).required = true,
288290
(buf.validate.field).cel = {
289291
id: "positive_posting_amount_request"
290292
message: "posting amount must be greater than zero"
291-
expression: "this.units > 0 || (this.units == 0 && this.nanos > 0)"
293+
expression: "this.amount.matches('^[1-9][0-9]*(\\\\.[0-9]+)?$') || this.amount.matches('^0\\\\.[0-9]*[1-9][0-9]*$')"
292294
}
293295
];
294296

@@ -399,7 +401,7 @@ message ListFinancialBookingLogsResponse {
399401
// - AccountCode: Filter by account identifier
400402
// - PostingDirection: Filter by DEBIT or CREDIT
401403
// - Date range: Filter by value date (from/to)
402-
// - Currency: Filter by currency code
404+
// - InstrumentCode: Filter by instrument code
403405
//
404406
// Pagination: If pagination is not provided, the service will apply
405407
// default limits (typically 50 items) to prevent unbounded queries.
@@ -430,10 +432,10 @@ message ListLedgerPostingsRequest {
430432
// value_date_to filters postings on or before this date.
431433
google.protobuf.Timestamp value_date_to = 6;
432434

433-
// currency filters by currency code (from posting_amount).
434-
string currency = 7 [(buf.validate.field).string = {
435-
max_len: 3
436-
pattern: "^[A-Z]{0,3}$"
435+
// instrument_code filters by instrument code (from posting_amount).
436+
string instrument_code = 7 [(buf.validate.field).string = {
437+
max_len: 32
438+
pattern: "^$|^[A-Z][A-Z0-9_]*$"
437439
}];
438440

439441
// status filters by transaction status.

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

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"buf.build/go/protovalidate"
99
commonv1 "github.com/meridianhub/meridian/api/proto/meridian/common/v1"
1010
financialaccountingv1 "github.com/meridianhub/meridian/api/proto/meridian/financial_accounting/v1"
11-
"google.golang.org/genproto/googleapis/type/money"
11+
quantityv1 "github.com/meridianhub/meridian/api/proto/meridian/quantity/v1"
1212
"google.golang.org/protobuf/types/known/timestamppb"
1313
)
1414

@@ -48,10 +48,10 @@ func TestLedgerPostingCreation(t *testing.T) {
4848
Id: "posting-123",
4949
FinancialBookingLogId: "log-456",
5050
PostingDirection: commonv1.PostingDirection_POSTING_DIRECTION_DEBIT,
51-
PostingAmount: &money.Money{
52-
CurrencyCode: "GBP",
53-
Units: 100,
54-
Nanos: 50,
51+
PostingAmount: &quantityv1.InstrumentAmount{
52+
Amount: "100.00000005",
53+
InstrumentCode: "GBP",
54+
Version: 1,
5555
},
5656
AccountId: "acc-789",
5757
ValueDate: now,
@@ -66,11 +66,11 @@ func TestLedgerPostingCreation(t *testing.T) {
6666
if posting.PostingDirection != commonv1.PostingDirection_POSTING_DIRECTION_DEBIT {
6767
t.Errorf("Expected DEBIT direction, got %v", posting.PostingDirection)
6868
}
69-
if posting.PostingAmount.CurrencyCode != "GBP" {
70-
t.Errorf("Expected GBP currency, got %s", posting.PostingAmount.CurrencyCode)
69+
if posting.PostingAmount.InstrumentCode != "GBP" {
70+
t.Errorf("Expected GBP currency, got %s", posting.PostingAmount.InstrumentCode)
7171
}
72-
if posting.PostingAmount.Units != 100 {
73-
t.Errorf("Expected 100 units, got %d", posting.PostingAmount.Units)
72+
if posting.PostingAmount.Amount != "100.00000005" {
73+
t.Errorf("Expected 100.00000005 amount, got %s", posting.PostingAmount.Amount)
7474
}
7575
}
7676

@@ -142,10 +142,10 @@ func TestCaptureLedgerPostingRequest(t *testing.T) {
142142
req := &financialaccountingv1.CaptureLedgerPostingRequest{
143143
FinancialBookingLogId: "log-123",
144144
PostingDirection: commonv1.PostingDirection_POSTING_DIRECTION_CREDIT,
145-
PostingAmount: &money.Money{
146-
CurrencyCode: "USD",
147-
Units: 250,
148-
Nanos: 0,
145+
PostingAmount: &quantityv1.InstrumentAmount{
146+
Amount: "250",
147+
InstrumentCode: "USD",
148+
Version: 1,
149149
},
150150
AccountId: "acc-456",
151151
ValueDate: now,
@@ -347,10 +347,10 @@ func TestValidation_PositivePostingAmount(t *testing.T) {
347347
Id: "posting-1",
348348
FinancialBookingLogId: "log-1",
349349
PostingDirection: commonv1.PostingDirection_POSTING_DIRECTION_DEBIT,
350-
PostingAmount: &money.Money{
351-
CurrencyCode: "GBP",
352-
Units: 100,
353-
Nanos: 0,
350+
PostingAmount: &quantityv1.InstrumentAmount{
351+
Amount: "100",
352+
InstrumentCode: "GBP",
353+
Version: 1,
354354
},
355355
AccountId: "acc-123",
356356
ValueDate: now,
@@ -365,10 +365,10 @@ func TestValidation_PositivePostingAmount(t *testing.T) {
365365
Id: "posting-2",
366366
FinancialBookingLogId: "log-2",
367367
PostingDirection: commonv1.PostingDirection_POSTING_DIRECTION_CREDIT,
368-
PostingAmount: &money.Money{
369-
CurrencyCode: "USD",
370-
Units: 0,
371-
Nanos: 50000000, // 0.05 USD
368+
PostingAmount: &quantityv1.InstrumentAmount{
369+
Amount: "0.05",
370+
InstrumentCode: "USD",
371+
Version: 1,
372372
},
373373
AccountId: "acc-456",
374374
ValueDate: now,
@@ -383,10 +383,10 @@ func TestValidation_PositivePostingAmount(t *testing.T) {
383383
Id: "posting-3",
384384
FinancialBookingLogId: "log-3",
385385
PostingDirection: commonv1.PostingDirection_POSTING_DIRECTION_DEBIT,
386-
PostingAmount: &money.Money{
387-
CurrencyCode: "GBP",
388-
Units: 0,
389-
Nanos: 0,
386+
PostingAmount: &quantityv1.InstrumentAmount{
387+
Amount: "0",
388+
InstrumentCode: "GBP",
389+
Version: 1,
390390
},
391391
AccountId: "acc-789",
392392
ValueDate: now,
@@ -402,10 +402,10 @@ func TestValidation_PositivePostingAmount(t *testing.T) {
402402
Id: "posting-4",
403403
FinancialBookingLogId: "log-4",
404404
PostingDirection: commonv1.PostingDirection_POSTING_DIRECTION_CREDIT,
405-
PostingAmount: &money.Money{
406-
CurrencyCode: "EUR",
407-
Units: -50,
408-
Nanos: 0,
405+
PostingAmount: &quantityv1.InstrumentAmount{
406+
Amount: "-50",
407+
InstrumentCode: "EUR",
408+
Version: 1,
409409
},
410410
AccountId: "acc-999",
411411
ValueDate: now,
@@ -492,10 +492,10 @@ func TestValidation_AccountIDPattern(t *testing.T) {
492492
Id: "posting-1",
493493
FinancialBookingLogId: "log-1",
494494
PostingDirection: commonv1.PostingDirection_POSTING_DIRECTION_DEBIT,
495-
PostingAmount: &money.Money{
496-
CurrencyCode: "GBP",
497-
Units: 100,
498-
Nanos: 0,
495+
PostingAmount: &quantityv1.InstrumentAmount{
496+
Amount: "100",
497+
InstrumentCode: "GBP",
498+
Version: 1,
499499
},
500500
AccountId: tt.accountID,
501501
ValueDate: now,
@@ -537,10 +537,10 @@ func TestValidation_CaptureLedgerPostingRequest(t *testing.T) {
537537
req: &financialaccountingv1.CaptureLedgerPostingRequest{
538538
FinancialBookingLogId: "log-123",
539539
PostingDirection: commonv1.PostingDirection_POSTING_DIRECTION_CREDIT,
540-
PostingAmount: &money.Money{
541-
CurrencyCode: "GBP",
542-
Units: 100,
543-
Nanos: 50,
540+
PostingAmount: &quantityv1.InstrumentAmount{
541+
Amount: "100.00000005",
542+
InstrumentCode: "GBP",
543+
Version: 1,
544544
},
545545
AccountId: "acc-valid-123",
546546
ValueDate: now,
@@ -556,10 +556,10 @@ func TestValidation_CaptureLedgerPostingRequest(t *testing.T) {
556556
req: &financialaccountingv1.CaptureLedgerPostingRequest{
557557
FinancialBookingLogId: "log-123",
558558
PostingDirection: commonv1.PostingDirection_POSTING_DIRECTION_DEBIT,
559-
PostingAmount: &money.Money{
560-
CurrencyCode: "GBP",
561-
Units: 0,
562-
Nanos: 0,
559+
PostingAmount: &quantityv1.InstrumentAmount{
560+
Amount: "0",
561+
InstrumentCode: "GBP",
562+
Version: 1,
563563
},
564564
AccountId: "acc-123",
565565
ValueDate: now,
@@ -572,10 +572,10 @@ func TestValidation_CaptureLedgerPostingRequest(t *testing.T) {
572572
req: &financialaccountingv1.CaptureLedgerPostingRequest{
573573
FinancialBookingLogId: "log-123",
574574
PostingDirection: commonv1.PostingDirection_POSTING_DIRECTION_CREDIT,
575-
PostingAmount: &money.Money{
576-
CurrencyCode: "GBP",
577-
Units: 100,
578-
Nanos: 0,
575+
PostingAmount: &quantityv1.InstrumentAmount{
576+
Amount: "100",
577+
InstrumentCode: "GBP",
578+
Version: 1,
579579
},
580580
AccountId: "acc@invalid#123",
581581
ValueDate: now,

0 commit comments

Comments
 (0)