openmeter / billing /lineengine /splitlinegroup.go
Leon4gr45's picture
Upload folder using huggingface_hub (part 2)
5a22efd verified
Raw
History Blame Contribute Delete
5.1 kB
package lineengine
import (
"context"
"fmt"
"github.com/samber/lo"
"github.com/openmeterio/openmeter/openmeter/billing"
"github.com/openmeterio/openmeter/openmeter/productcatalog"
"github.com/openmeterio/openmeter/openmeter/streaming"
"github.com/openmeterio/openmeter/pkg/clock"
"github.com/openmeterio/openmeter/pkg/timeutil"
)
type SplitLineGroupAdapter interface {
CreateSplitLineGroup(ctx context.Context, input billing.CreateSplitLineGroupAdapterInput) (billing.SplitLineGroup, error)
GetSplitLineGroupHeaders(ctx context.Context, input billing.GetSplitLineGroupHeadersInput) (billing.SplitLineGroupHeaders, error)
}
func (e *Engine) SplitGatheringLine(ctx context.Context, in billing.SplitGatheringLineInput) (billing.SplitGatheringLineResult, error) {
res := billing.SplitGatheringLineResult{}
if err := in.Validate(); err != nil {
return res, err
}
line := in.Line
if !line.ServicePeriod.Contains(in.SplitAt) {
return res, fmt.Errorf("line[%s]: splitAt is not within the line period", line.ID)
}
var splitLineGroupID string
if line.SplitLineGroupID == nil {
splitLineGroup, err := e.adapter.CreateSplitLineGroup(ctx, billing.CreateSplitLineGroupAdapterInput{
Namespace: line.Namespace,
SplitLineGroupMutableFields: billing.SplitLineGroupMutableFields{
Name: line.Name,
Description: line.Description,
ServicePeriod: timeutil.ClosedPeriod{
From: line.ServicePeriod.From,
To: line.ServicePeriod.To,
},
RatecardDiscounts: line.RateCardDiscounts,
},
UniqueReferenceID: line.ChildUniqueReferenceID,
Currency: line.Currency,
Price: lo.ToPtr(line.Price),
FeatureKey: lo.EmptyableToPtr(line.FeatureKey),
Subscription: line.Subscription,
})
if err != nil {
return res, fmt.Errorf("creating split line group: %w", err)
}
splitLineGroupID = splitLineGroup.ID
} else {
splitLineGroupID = lo.FromPtr(line.SplitLineGroupID)
if splitLineGroupID == "" {
return res, fmt.Errorf("split line group id is empty")
}
}
postSplitAtLine, err := line.CloneForCreate(func(l *billing.GatheringLine) {
l.ServicePeriod.From = in.SplitAt
l.SplitLineGroupID = lo.ToPtr(splitLineGroupID)
l.ChildUniqueReferenceID = nil
})
if err != nil {
return res, fmt.Errorf("cloning post split line: %w", err)
}
postSplitAtLineEmpty, err := isPeriodEmptyConsideringTruncations(postSplitAtLine)
if err != nil {
return res, fmt.Errorf("checking if post split line is empty: %w", err)
}
if !postSplitAtLineEmpty {
if err := postSplitAtLine.Validate(); err != nil {
return res, fmt.Errorf("validating post split line: %w", err)
}
}
line.ServicePeriod.To = in.SplitAt
line.InvoiceAt = in.SplitAt
line.SplitLineGroupID = lo.ToPtr(splitLineGroupID)
line.ChildUniqueReferenceID = nil
preSplitAtLine := line
preSplitAtLineEmpty, err := isPeriodEmptyConsideringTruncations(preSplitAtLine)
if err != nil {
return res, fmt.Errorf("checking if pre split line is empty: %w", err)
}
if preSplitAtLineEmpty {
preSplitAtLine.DeletedAt = lo.ToPtr(clock.Now())
} else {
if err := preSplitAtLine.Validate(); err != nil {
return res, fmt.Errorf("validating pre split line: %w", err)
}
}
var postSplitAtLinePtr *billing.GatheringLine
if !postSplitAtLineEmpty {
postSplitAtLinePtr = &postSplitAtLine
}
return billing.SplitGatheringLineResult{
PreSplitAtLine: preSplitAtLine,
PostSplitAtLine: postSplitAtLinePtr,
}, nil
}
func (e *Engine) ResolveSplitLineGroupHeaders(ctx context.Context, ns string, lines billing.StandardLines) error {
splitLineGroupIDs := lo.Uniq(
lo.Filter(
lo.Map(lines, func(line *billing.StandardLine, _ int) string { return lo.FromPtr(line.SplitLineGroupID) }),
func(id string, _ int) bool { return id != "" },
),
)
if len(splitLineGroupIDs) == 0 {
return nil
}
splitLineGroupHeaders, err := e.adapter.GetSplitLineGroupHeaders(ctx, billing.GetSplitLineGroupHeadersInput{
Namespace: ns,
SplitLineGroupIDs: splitLineGroupIDs,
})
if err != nil {
return fmt.Errorf("getting split line group headers: %w", err)
}
splitLineGroupHeadersByID := lo.SliceToMap(splitLineGroupHeaders, func(header billing.SplitLineGroup) (string, billing.SplitLineGroup) {
return header.ID, header
})
for idx := range lines {
if lines[idx].SplitLineGroupID == nil {
continue
}
splitLineGroupHeader, ok := splitLineGroupHeadersByID[lo.FromPtr(lines[idx].SplitLineGroupID)]
if !ok {
return fmt.Errorf("split line group header not found for line[%s]: id[%s]", lines[idx].ID, lo.FromPtr(lines[idx].SplitLineGroupID))
}
lines[idx].SplitLineHierarchy = &billing.SplitLineHierarchy{
Group: splitLineGroupHeader,
}
}
return nil
}
func isPeriodEmptyConsideringTruncations(line billing.GatheringLine) (bool, error) {
price := line.GetPrice()
if price == nil {
return false, fmt.Errorf("price is nil")
}
if price.Type() == productcatalog.FlatPriceType {
return false, nil
}
return line.GetServicePeriod().Truncate(streaming.MinimumWindowSizeDuration).IsEmpty(), nil
}