Skip to content

Commit e4ccf0b

Browse files
authored
feat: persist invoice id for ubp runs (#4306)
1 parent 66c3390 commit e4ccf0b

28 files changed

Lines changed: 992 additions & 30 deletions

openmeter/billing/charges/service/usagebased_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ func (s *UsageBasedChargesTestSuite) TestUsageBasedCreditThenInvoicePartialInvoi
208208
s.Equal(usagebased.RealizationRunTypePartialInvoice, currentRun.Type)
209209
s.Require().NotNil(currentRun.LineID)
210210
s.Equal(stdLine.ID, *currentRun.LineID)
211+
s.Require().NotNil(currentRun.InvoiceID)
212+
s.Equal(partialInvoice.ID, *currentRun.InvoiceID)
211213
s.True(midPeriodInvoiceAt.Equal(currentRun.ServicePeriodTo))
212214
s.True(midPeriodInvoiceAt.Equal(currentRun.StoredAtLT))
213215
s.Require().NotNil(partialInvoice.CollectionAt)
@@ -335,6 +337,8 @@ func (s *UsageBasedChargesTestSuite) TestUsageBasedCreditThenInvoicePartialInvoi
335337
currentRun, runErr := charge.GetCurrentRealizationRun()
336338
s.NoError(runErr)
337339
s.Equal(usagebased.RealizationRunTypeFinalRealization, currentRun.Type)
340+
s.Require().NotNil(currentRun.InvoiceID)
341+
s.Equal(finalInvoice.ID, *currentRun.InvoiceID)
338342

339343
// given
340344
clock.FreezeTime(finalInvoice.DefaultCollectionAtForStandardInvoice())

openmeter/billing/charges/usagebased/adapter/mapper.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func MapRealizationRunBaseFromDB(dbRun *entdb.ChargeUsageBasedRuns) usagebased.R
9898

9999
FeatureID: dbRun.FeatureID,
100100
LineID: dbRun.LineID,
101+
InvoiceID: dbRun.InvoiceID,
101102
Type: dbRun.Type,
102103
StoredAtLT: dbRun.StoredAtLt.UTC(),
103104
ServicePeriodTo: dbRun.ServicePeriodTo.UTC(),

openmeter/billing/charges/usagebased/adapter/realizationrun.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func (a *adapter) CreateRealizationRun(ctx context.Context, chargeID meta.Charge
3131
SetServicePeriodTo(meta.NormalizeTimestamp(input.ServicePeriodTo)).
3232
SetDetailedLinesPresent(false).
3333
SetNillableBillingInvoiceLineID(input.LineID).
34+
SetNillableBillingInvoiceID(input.InvoiceID).
3435
SetMeteredQuantity(input.MeteredQuantity).
3536
SetNoFiatTransactionRequired(input.NoFiatTransactionRequired)
3637

openmeter/billing/charges/usagebased/realizationrun.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ type CreateRealizationRunInput struct {
6565
StoredAtLT time.Time `json:"storedAtLT"`
6666
ServicePeriodTo time.Time `json:"servicePeriodTo"`
6767
LineID *string `json:"lineId,omitempty"`
68+
InvoiceID *string `json:"invoiceId,omitempty"`
6869
MeteredQuantity alpacadecimal.Decimal `json:"meteredQuantity"`
6970
Totals totals.Totals `json:"totals"`
7071
NoFiatTransactionRequired bool `json:"noFiatTransactionRequired"`
@@ -108,6 +109,10 @@ func (r CreateRealizationRunInput) Validate() error {
108109
errs = append(errs, fmt.Errorf("line id must be non-empty"))
109110
}
110111

112+
if r.InvoiceID != nil && *r.InvoiceID == "" {
113+
errs = append(errs, fmt.Errorf("invoice id must be non-empty"))
114+
}
115+
111116
return models.NewNillableGenericValidationError(errors.Join(errs...))
112117
}
113118

@@ -167,6 +172,7 @@ type RealizationRunBase struct {
167172

168173
FeatureID string `json:"featureId"`
169174
LineID *string `json:"lineId,omitempty"`
175+
InvoiceID *string `json:"invoiceId,omitempty"`
170176

171177
Type RealizationRunType `json:"type"`
172178
StoredAtLT time.Time `json:"storedAtLT"`
@@ -204,6 +210,10 @@ func (r RealizationRunBase) Validate() error {
204210
errs = append(errs, fmt.Errorf("line id must be non-empty"))
205211
}
206212

213+
if r.InvoiceID != nil && *r.InvoiceID == "" {
214+
errs = append(errs, fmt.Errorf("invoice id must be non-empty"))
215+
}
216+
207217
if err := r.Type.Validate(); err != nil {
208218
errs = append(errs, fmt.Errorf("type: %w", err))
209219
}

openmeter/billing/charges/usagebased/service/creditheninvoice.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ func (s *CreditThenInvoiceStateMachine) DeleteCharge(ctx context.Context, _ meta
212212

213213
type invoiceCreatedInput struct {
214214
LineID string
215+
InvoiceID string
215216
ServicePeriodTo time.Time
216217
}
217218

@@ -220,6 +221,10 @@ func (i invoiceCreatedInput) Validate() error {
220221
return fmt.Errorf("line id is required")
221222
}
222223

224+
if i.InvoiceID == "" {
225+
return fmt.Errorf("invoice id is required")
226+
}
227+
223228
if i.ServicePeriodTo.IsZero() {
224229
return fmt.Errorf("service period to is required")
225230
}
@@ -255,6 +260,7 @@ func (s *CreditThenInvoiceStateMachine) startInvoiceCreatedRun(
255260
StoredAtLT: storedAtLT,
256261
ServicePeriodTo: servicePeriodTo,
257262
LineID: lo.ToPtr(input.LineID),
263+
InvoiceID: lo.ToPtr(input.InvoiceID),
258264
CreditAllocation: usagebasedrun.CreditAllocationAvailable,
259265
CurrencyCalculator: s.CurrencyCalculator,
260266
})

openmeter/billing/charges/usagebased/service/creditheninvoice_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ func TestStartInvoiceCreatedRunValidatesInput(t *testing.T) {
1717
err := machine.startInvoiceCreatedRun(
1818
t.Context(),
1919
invoiceCreatedInput{
20-
LineID: "line-1",
20+
LineID: "line-1",
21+
InvoiceID: "invoice-1",
2122
},
2223
usagebased.RealizationRunTypePartialInvoice,
2324
)

openmeter/billing/charges/usagebased/service/lineengine.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ func (e *LineEngine) OnStandardInvoiceCreated(ctx context.Context, input billing
149149

150150
if err := stateMachine.FireAndActivate(ctx, trigger, invoiceCreatedInput{
151151
LineID: stdLine.ID,
152+
InvoiceID: input.Invoice.ID,
152153
ServicePeriodTo: stdLine.Period.To,
153154
}); err != nil {
154155
return nil, fmt.Errorf("triggering %s for charge[%s]: %w", trigger, stateMachine.GetCharge().ID, err)

openmeter/billing/charges/usagebased/service/run/create.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type CreateRatedRunInput struct {
2424
StoredAtLT time.Time
2525
ServicePeriodTo time.Time
2626
LineID *string
27+
InvoiceID *string
2728
CreditAllocation CreditAllocationMode
2829
CurrencyCalculator currencyx.Calculator
2930
// NoFiatTransactionRequired is set if either there's no fiat-based
@@ -73,6 +74,10 @@ func (i CreateRatedRunInput) Validate() error {
7374
return fmt.Errorf("line id if set, must be non-empty")
7475
}
7576

77+
if i.InvoiceID != nil && *i.InvoiceID == "" {
78+
return fmt.Errorf("invoice id if set, must be non-empty")
79+
}
80+
7681
if err := i.CreditAllocation.Validate(); err != nil {
7782
return fmt.Errorf("credit allocation: %w", err)
7883
}
@@ -152,6 +157,7 @@ func (s *Service) CreateRatedRun(ctx context.Context, in CreateRatedRunInput) (C
152157
StoredAtLT: in.StoredAtLT,
153158
ServicePeriodTo: in.ServicePeriodTo,
154159
LineID: in.LineID,
160+
InvoiceID: in.InvoiceID,
155161
MeteredQuantity: ratingResult.Quantity,
156162
Totals: runTotals,
157163
NoFiatTransactionRequired: noFiatTransactionRequired,

openmeter/ent/db/billinginvoice.go

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

openmeter/ent/db/billinginvoice/billinginvoice.go

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

0 commit comments

Comments
 (0)