package lineengine import ( "context" "fmt" "github.com/samber/lo" "github.com/openmeterio/openmeter/openmeter/billing" "github.com/openmeterio/openmeter/openmeter/billing/service/invoicecalc" ) type QuantitySnapshotter interface { SnapshotLineQuantities(ctx context.Context, invoice billing.StandardInvoice, lines billing.StandardLines) error } func (e *Engine) BuildStandardInvoiceLines(ctx context.Context, input billing.BuildStandardInvoiceLinesInput) (billing.StandardLines, error) { stdLines, err := e.buildStandardInvoiceLinesWithQuantitySnapshot(ctx, input) if err != nil { return nil, err } return e.CalculateLines(billing.CalculateLinesInput{ Invoice: input.Invoice, Lines: stdLines, }) } func (e *Engine) BuildStandardLinesForGatheringPreview(ctx context.Context, input billing.BuildStandardInvoiceLinesInput) (billing.StandardLines, error) { return e.buildStandardInvoiceLinesWithQuantitySnapshot(ctx, input) } func (e *Engine) buildStandardInvoiceLinesWithQuantitySnapshot(ctx context.Context, input billing.BuildStandardInvoiceLinesInput) (billing.StandardLines, error) { if input.Invoice.ID == "" { return nil, fmt.Errorf("invoice id is required") } if len(input.GatheringLines) == 0 { return nil, fmt.Errorf("gathering lines are required") } stdLines, err := input.GatheringLines.ToStandardLines(input.Invoice.ID) if err != nil { return nil, err } if err := e.ResolveSplitLineGroupHeaders(ctx, input.Invoice.Namespace, stdLines); err != nil { return nil, fmt.Errorf("resolving split line group headers: %w", err) } if err := e.quantitySnapshotter.SnapshotLineQuantities(ctx, input.Invoice, stdLines); err != nil { return nil, fmt.Errorf("snapshotting line quantities: %w", err) } return stdLines, nil } func (e *Engine) CalculateLines(input billing.CalculateLinesInput) (billing.StandardLines, error) { if input.Invoice.ID == "" { return nil, fmt.Errorf("invoice id is required") } if len(input.Lines) == 0 { return nil, fmt.Errorf("lines are required") } for _, stdLine := range input.Lines { generatedDetailedLines, err := e.ratingService.GenerateDetailedLines(stdLine) if err != nil { return nil, fmt.Errorf("generating detailed lines for line[%s]: %w", stdLine.ID, err) } if err := invoicecalc.MergeGeneratedDetailedLines(stdLine, generatedDetailedLines); err != nil { return nil, fmt.Errorf("merging generated detailed lines for line[%s]: %w", stdLine.ID, err) } if err := stdLine.Validate(); err != nil { return nil, fmt.Errorf("validating standard line[%s]: %w", stdLine.ID, err) } } return input.Lines, nil } func (e *Engine) IsLineBillableAsOf(_ context.Context, input billing.IsLineBillableAsOfInput) (bool, error) { if err := input.Validate(); err != nil { return false, fmt.Errorf("validating input: %w", err) } return !lo.IsEmpty(input.ResolvedBillablePeriod), nil }