Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions openmeter/billing/service/gatheringinvoicependinglines.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ type gatheringInvoiceWithFeatureMeters struct {
Invoice billing.StandardInvoice
FeatureMeters billing.FeatureMeters
}

type gatherInScopeLineInput struct {
GatheringInvoicesByCurrency map[currencyx.Code]gatheringInvoiceWithFeatureMeters
// If set restricts the lines to be included to these IDs, otherwise the AsOf is used
Expand All @@ -292,12 +293,12 @@ func (s *Service) gatherInScopeLines(ctx context.Context, in gatherInScopeLineIn
billableLineIDs := make(map[string]interface{})

for currency, invoice := range in.GatheringInvoicesByCurrency {
lineSrvs, err := s.lineService.FromEntities(invoice.Invoice.Lines.OrEmpty(), invoice.FeatureMeters)
lineSrvs, err := lineservice.FromEntities(invoice.Invoice.Lines.OrEmpty(), invoice.FeatureMeters)
if err != nil {
return nil, err
}

linesWithResolvedPeriods, err := lineSrvs.ResolveBillablePeriod(ctx, lineservice.ResolveBillablePeriodInput{
linesWithResolvedPeriods, err := lineSrvs.ResolveBillablePeriod(lineservice.ResolveBillablePeriodInput{
AsOf: in.AsOf,
ProgressiveBilling: in.ProgressiveBilling,
})
Expand Down Expand Up @@ -555,17 +556,17 @@ func (s *Service) splitGatheringInvoiceLine(ctx context.Context, in splitGatheri
l.ChildUniqueReferenceID = nil
})

postSplitAtLineSvc, err := s.lineService.FromEntity(postSplitAtLine, in.FeatureMeters)
postSplitAtLineSvc, err := lineservice.FromEntity(postSplitAtLine, in.FeatureMeters)
if err != nil {
return res, fmt.Errorf("creating line service: %w", err)
}

if !postSplitAtLineSvc.IsPeriodEmptyConsideringTruncations() {
gatheringInvoice.Lines.Append(postSplitAtLine)

if err := postSplitAtLineSvc.Validate(ctx, &gatheringInvoice); err != nil {
if err := postSplitAtLine.Validate(); err != nil {
return res, fmt.Errorf("validating post split line: %w", err)
}

gatheringInvoice.Lines.Append(postSplitAtLine)
}

// Let's update the original line to only contain the period up to the splitAt time
Expand All @@ -574,7 +575,9 @@ func (s *Service) splitGatheringInvoiceLine(ctx context.Context, in splitGatheri
line.SplitLineGroupID = lo.ToPtr(splitLineGroupID)
line.ChildUniqueReferenceID = nil

preSplitAtLineSvc, err := s.lineService.FromEntity(line, in.FeatureMeters)
preSplitAtLine := line

preSplitAtLineSvc, err := lineservice.FromEntity(line, in.FeatureMeters)
if err != nil {
return res, fmt.Errorf("creating line service: %w", err)
}
Expand All @@ -583,7 +586,7 @@ func (s *Service) splitGatheringInvoiceLine(ctx context.Context, in splitGatheri
if preSplitAtLineSvc.IsPeriodEmptyConsideringTruncations() {
line.DeletedAt = lo.ToPtr(clock.Now())
} else {
if err := preSplitAtLineSvc.Validate(ctx, &gatheringInvoice); err != nil {
if err := preSplitAtLine.Validate(); err != nil {
return res, fmt.Errorf("validating pre split line: %w", err)
}
}
Expand Down Expand Up @@ -824,16 +827,17 @@ func (s *Service) moveLinesToInvoice(ctx context.Context, in moveLinesToInvoiceI
return slices.Contains(in.LineIDsToMove, line.ID)
})

if len(linesToMove) != len(in.LineIDsToMove) {
return nil, fmt.Errorf("lines to move[%d] must contain the same number of lines as line IDs to move[%d]", len(linesToMove), len(in.LineIDsToMove))
for _, line := range linesToMove {
if line.Currency != dstInvoice.Currency {
return nil, fmt.Errorf("line[%s]: currency[%s] is not equal to target invoice currency[%s]", line.ID, line.Currency, dstInvoice.Currency)
}
}

linesToAssociate, err := s.lineService.FromEntities(linesToMove, in.FeatureMeters)
if err != nil {
return nil, fmt.Errorf("creating line services for lines to move: %w", err)
if len(linesToMove) != len(in.LineIDsToMove) {
return nil, fmt.Errorf("lines to move[%d] must contain the same number of lines as line IDs to move[%d]", len(linesToMove), len(in.LineIDsToMove))
}

if err := linesToAssociate.ValidateForInvoice(ctx, &dstInvoice); err != nil {
if err := linesToMove.Validate(); err != nil {
return nil, fmt.Errorf("validating lines to move: %w", err)
}

Expand Down
71 changes: 32 additions & 39 deletions openmeter/billing/service/invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,15 @@ func (s *Service) recalculateGatheringInvoice(ctx context.Context, in recalculat
return invoice, fmt.Errorf("fetching profile: %w", err)
}

if customerProfile.Customer == nil {
return invoice, fmt.Errorf("customer profile is nil")
}

featureMeters, err := s.resolveFeatureMeters(ctx, invoice.Lines.OrEmpty())
if err != nil {
return invoice, fmt.Errorf("resolving feature meters: %w", err)
}

if customerProfile.Customer == nil {
return invoice, fmt.Errorf("customer profile is nil")
}

inScopeLines := lo.Filter(invoice.Lines.OrEmpty(), func(line *billing.StandardLine, _ int) bool {
return line.DeletedAt == nil
})
Expand All @@ -167,14 +167,14 @@ func (s *Service) recalculateGatheringInvoice(ctx context.Context, in recalculat
return invoice, fmt.Errorf("snapshotting lines: %w", err)
}

inScopeLineSvcs, err := s.lineService.FromEntities(inScopeLines, featureMeters)
inScopeLineSvcs, err := lineservice.FromEntities(inScopeLines, featureMeters)
if err != nil {
return invoice, fmt.Errorf("creating line services: %w", err)
}

hasInvoicableLines := mo.Option[bool]{}
for _, lineSvc := range inScopeLineSvcs {
period, err := lineSvc.CanBeInvoicedAsOf(ctx, lineservice.CanBeInvoicedAsOfInput{
period, err := lineSvc.CanBeInvoicedAsOf(lineservice.CanBeInvoicedAsOfInput{
AsOf: now,
ProgressiveBilling: customerProfile.MergedProfile.WorkflowConfig.Invoicing.ProgressiveBilling,
})
Expand All @@ -190,7 +190,6 @@ func (s *Service) recalculateGatheringInvoice(ctx context.Context, in recalculat
invoice.QuantitySnapshotedAt = lo.ToPtr(now)

if err := s.invoiceCalculator.CalculateGatheringInvoiceWithLiveData(&invoice, invoicecalc.CalculatorDependencies{
LineService: s.lineService,
FeatureMeters: featureMeters,
}); err != nil {
return invoice, fmt.Errorf("calculating invoice: %w", err)
Expand Down Expand Up @@ -570,18 +569,11 @@ func (s *Service) UpdateInvoice(ctx context.Context, input billing.UpdateInvoice
return billing.StandardInvoice{}, fmt.Errorf("editing invoice: %w", err)
}

featureMeters, err := s.resolveFeatureMeters(ctx, invoice.Lines.OrEmpty())
if err != nil {
return billing.StandardInvoice{}, fmt.Errorf("resolving feature meters: %w", err)
}

normalizedLines, err := invoice.Lines.WithNormalizedValues()
invoice.Lines, err = invoice.Lines.WithNormalizedValues()
if err != nil {
return billing.StandardInvoice{}, fmt.Errorf("normalizing lines: %w", err)
}

invoice.Lines = normalizedLines

if err := s.invoiceCalculator.CalculateGatheringInvoice(&invoice); err != nil {
return billing.StandardInvoice{}, fmt.Errorf("calculating invoice[%s]: %w", invoice.ID, err)
}
Expand All @@ -592,6 +584,11 @@ func (s *Service) UpdateInvoice(ctx context.Context, input billing.UpdateInvoice
}
}

featureMeters, err := s.resolveFeatureMeters(ctx, invoice.Lines.OrEmpty())
if err != nil {
return billing.StandardInvoice{}, fmt.Errorf("resolving feature meters: %w", err)
}

// Check if the new lines are still invoicable
if err := s.checkIfLinesAreInvoicable(ctx, &invoice, customerProfile.MergedProfile.WorkflowConfig.Invoicing.ProgressiveBilling, featureMeters); err != nil {
return billing.StandardInvoice{}, err
Expand Down Expand Up @@ -662,23 +659,22 @@ func (s Service) updateInvoice(ctx context.Context, in billing.UpdateInvoiceAdap
}

func (s Service) checkIfLinesAreInvoicable(ctx context.Context, invoice *billing.StandardInvoice, progressiveBilling bool, featureMeters billing.FeatureMeters) error {
inScopeLineServices, err := s.lineService.FromEntities(
lo.Filter(invoice.Lines.OrEmpty(), func(line *billing.StandardLine, _ int) bool {
return line.DeletedAt == nil
}),
featureMeters,
)
if err != nil {
return fmt.Errorf("creating line services: %w", err)
}
linesToCheck := lo.Filter(invoice.Lines.OrEmpty(), func(line *billing.StandardLine, _ int) bool {
return line.DeletedAt == nil
})

return errors.Join(
lo.Map(inScopeLineServices, func(lineSvc lineservice.Line, _ int) error {
if err := lineSvc.Validate(ctx, invoice); err != nil {
return fmt.Errorf("validating line[%s]: %w", lineSvc.ID(), err)
lo.Map(linesToCheck, func(line *billing.StandardLine, _ int) error {
if err := line.Validate(); err != nil {
return fmt.Errorf("validating line[%s]: %w", line.ID, err)
}

lineSvc, err := lineservice.FromEntity(line, featureMeters)
if err != nil {
return fmt.Errorf("creating line service: %w", err)
}

period, err := lineSvc.CanBeInvoicedAsOf(ctx, lineservice.CanBeInvoicedAsOfInput{
period, err := lineSvc.CanBeInvoicedAsOf(lineservice.CanBeInvoicedAsOfInput{
AsOf: lineSvc.InvoiceAt(),
ProgressiveBilling: progressiveBilling,
})
Expand Down Expand Up @@ -799,17 +795,15 @@ func (s Service) SimulateInvoice(ctx context.Context, input billing.SimulateInvo
return billing.StandardInvoice{}, fmt.Errorf("resolving feature meters: %w", err)
}

inScopeLineSvcs, err := s.lineService.FromEntities(invoice.Lines.OrEmpty(), featureMeters)
if err != nil {
return billing.StandardInvoice{}, fmt.Errorf("creating line services: %w", err)
}

// Let's update the lines and the detailed lines
for _, lineSvc := range inScopeLineSvcs {
if err := lineSvc.Validate(ctx, &invoice); err != nil {
return billing.StandardInvoice{}, billing.ValidationError{
Err: err,
}
for _, line := range invoice.Lines.OrEmpty() {
if err := line.Validate(); err != nil {
return billing.StandardInvoice{}, fmt.Errorf("validating line[%s]: %w", line.ID, err)
}

lineSvc, err := lineservice.FromEntity(line, featureMeters)
if err != nil {
return billing.StandardInvoice{}, fmt.Errorf("creating line service: %w", err)
}

if err := lineSvc.CalculateDetailedLines(); err != nil {
Expand All @@ -823,7 +817,6 @@ func (s Service) SimulateInvoice(ctx context.Context, input billing.SimulateInvo

// Let's simulate a recalculation of the invoice
if err := s.invoiceCalculator.Calculate(&invoice, invoicecalc.CalculatorDependencies{
LineService: s.lineService,
FeatureMeters: featureMeters,
}); err != nil {
return billing.StandardInvoice{}, err
Expand Down
2 changes: 0 additions & 2 deletions openmeter/billing/service/invoicecalc/calculator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"

"github.com/openmeterio/openmeter/openmeter/billing"
"github.com/openmeterio/openmeter/openmeter/billing/service/lineservice"
)

type invoiceCalculatorsByType struct {
Expand Down Expand Up @@ -50,7 +49,6 @@ type Calculator interface {
}

type CalculatorDependencies struct {
LineService *lineservice.Service
FeatureMeters billing.FeatureMeters
}

Expand Down
3 changes: 2 additions & 1 deletion openmeter/billing/service/invoicecalc/details.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import (
"github.com/samber/lo"

"github.com/openmeterio/openmeter/openmeter/billing"
"github.com/openmeterio/openmeter/openmeter/billing/service/lineservice"
)

func RecalculateDetailedLinesAndTotals(invoice *billing.StandardInvoice, deps CalculatorDependencies) error {
if invoice.Lines.IsAbsent() {
return errors.New("cannot recaulculate invoice without expanded lines")
}

lines, err := deps.LineService.FromEntities(invoice.Lines.OrEmpty(), deps.FeatureMeters)
lines, err := lineservice.FromEntities(invoice.Lines.OrEmpty(), deps.FeatureMeters)
if err != nil {
return fmt.Errorf("creating line services: %w", err)
}
Expand Down
16 changes: 0 additions & 16 deletions openmeter/billing/service/lineservice/linebase.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package lineservice

import (
"context"
"time"

"github.com/openmeterio/openmeter/openmeter/billing"
Expand Down Expand Up @@ -41,7 +40,6 @@ var _ LineBase = (*lineBase)(nil)

type lineBase struct {
line *billing.StandardLine
service *Service
featureMeters billing.FeatureMeters
currency currencyx.Calculator
}
Expand Down Expand Up @@ -70,16 +68,6 @@ func (l lineBase) Period() billing.Period {
return l.line.Period
}

func (l lineBase) Validate(ctx context.Context, invoice *billing.StandardInvoice) error {
if l.line.Currency != invoice.Currency || l.line.Currency == "" {
return billing.ValidationError{
Err: billing.ErrInvoiceLineCurrencyMismatch,
}
}

return nil
}

func (l lineBase) IsLastInPeriod() bool {
if l.line.SplitLineGroupID == nil {
return true
Expand Down Expand Up @@ -116,10 +104,6 @@ func (l lineBase) IsDeleted() bool {
return l.line.DeletedAt != nil
}

func (l lineBase) Service() *Service {
return l.service
}

func (l lineBase) ResetTotals() {
l.line.Totals = billing.Totals{}
}
Loading
Loading