| package addondiff |
|
|
| import ( |
| "fmt" |
| "slices" |
|
|
| "github.com/openmeterio/openmeter/openmeter/subscription" |
| subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon" |
| "github.com/openmeterio/openmeter/pkg/datetime" |
| "github.com/openmeterio/openmeter/pkg/models" |
| "github.com/openmeterio/openmeter/pkg/timeutil" |
| ) |
|
|
| |
| func (d *diffable) getApplyForRateCard(rc subscriptionaddon.SubscriptionAddonRateCard) subscription.AppliesToSpec { |
| return subscription.NewAppliesToSpec(func(spec *subscription.SubscriptionSpec, _ subscription.ApplyContext) error { |
| phaseAtCadenceStart, ok := spec.GetCurrentPhaseAt(d.addon.ActiveFrom) |
| if !ok { |
| return fmt.Errorf("no phase found at %s", d.addon.ActiveFrom) |
| } |
|
|
| phases := spec.GetSortedPhases() |
|
|
| lastPhaseKey := phases[len(phases)-1].PhaseKey |
| lastPhase, ok := spec.Phases[lastPhaseKey] |
| if !ok { |
| return fmt.Errorf("no last phase found at %s", lastPhaseKey) |
| } |
|
|
| phaseAtCadenceEnd := lastPhase |
| if d.addon.ActiveTo != nil { |
| phaseAtCadenceEnd, ok = spec.GetCurrentPhaseAt(*d.addon.ActiveTo) |
| if !ok { |
| return fmt.Errorf("no phase found at %s", *d.addon.ActiveTo) |
| } |
| } |
|
|
| |
| |
| |
| |
| reachedFinal := false |
| reachedFirst := false |
| for _, phase := range spec.GetSortedPhases() { |
| if reachedFinal { |
| break |
| } |
|
|
| if phase.PhaseKey == phaseAtCadenceStart.PhaseKey { |
| reachedFirst = true |
| } |
|
|
| if phase.PhaseKey == phaseAtCadenceEnd.PhaseKey { |
| reachedFinal = true |
| } |
|
|
| if !reachedFirst { |
| continue |
| } |
|
|
| |
| addPer := d.addon.CadencedModel.AsPeriod() |
|
|
| pCad, err := spec.GetPhaseCadence(phase.PhaseKey) |
| if err != nil { |
| return fmt.Errorf("failed to get phase cadence for %s: %w", phase.PhaseKey, err) |
| } |
|
|
| addInPhase := pCad.AsPeriod().Intersection(addPer) |
| if addInPhase == nil { |
| |
| continue |
| } |
|
|
| items := phase.ItemsByKey[rc.AddonRateCard.Key()] |
|
|
| newItems := make([]*subscription.SubscriptionItemSpec, 0, len(items)) |
|
|
| |
| gaps := []timeutil.OpenPeriod{ |
| *addInPhase, |
| } |
|
|
| |
| for _, item := range items { |
| itemPer := item.GetCadence(pCad).AsPeriod() |
|
|
| { |
| |
| nGaps := make([]timeutil.OpenPeriod, 0, len(gaps)) |
| for _, g := range gaps { |
| nGaps = append(nGaps, g.Difference(itemPer)...) |
| } |
|
|
| slices.SortFunc(nGaps, func(a, b timeutil.OpenPeriod) int { |
| if a.From == nil { |
| return 1 |
| } |
|
|
| if b.From == nil { |
| return -1 |
| } |
|
|
| return a.From.Compare(*b.From) |
| }) |
|
|
| gaps = nGaps |
| } |
|
|
| inter := itemPer.Intersection(addPer) |
| if inter == nil { |
| newItems = append(newItems, item) |
|
|
| continue |
| } |
|
|
| |
| |
| diff := itemPer.Difference(*inter) |
|
|
| for _, diffPer := range diff { |
| inst := subscription.SubscriptionItemSpec{ |
| CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ |
| CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ |
| PhaseKey: phase.PhaseKey, |
| ItemKey: item.ItemKey, |
| RateCard: item.RateCard.Clone(), |
| }, |
| Annotations: item.Annotations, |
| }, |
| } |
|
|
| d.setItemRelativeCadence(&inst, pCad, diffPer) |
|
|
| newItems = append(newItems, &inst) |
| } |
|
|
| |
| inst := subscription.SubscriptionItemSpec{ |
| CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ |
| CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ |
| PhaseKey: phase.PhaseKey, |
| ItemKey: item.ItemKey, |
| RateCard: item.RateCard.Clone(), |
| }, |
| Annotations: item.Annotations, |
| }, |
| } |
|
|
| if inst.Annotations == nil { |
| inst.Annotations = models.Annotations{} |
| } |
|
|
| for range d.addon.Quantity { |
| err := rc.Apply(inst.RateCard, inst.Annotations) |
| if err != nil { |
| return fmt.Errorf("failed to extend rate card %s: %w", rc.AddonRateCard.Key(), err) |
| } |
| } |
|
|
| d.setItemRelativeCadence(&inst, pCad, *inter) |
|
|
| newItems = append(newItems, &inst) |
| } |
|
|
| |
| for _, gap := range gaps { |
| inst := subscription.SubscriptionItemSpec{ |
| CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ |
| CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ |
| PhaseKey: phase.PhaseKey, |
| ItemKey: rc.AddonRateCard.Key(), |
| RateCard: rc.AddonRateCard.RateCard.Clone(), |
| }, |
| Annotations: models.Annotations{}, |
| }, |
| } |
|
|
| for range d.addon.Quantity - 1 { |
| err := rc.Apply(inst.RateCard, inst.Annotations) |
| if err != nil { |
| return fmt.Errorf("failed to extend gap rate card %s: %w", rc.AddonRateCard.Key(), err) |
| } |
| } |
|
|
| d.setItemRelativeCadence(&inst, pCad, gap) |
|
|
| newItems = append(newItems, &inst) |
| } |
|
|
| slices.SortFunc(newItems, func(a, b *subscription.SubscriptionItemSpec) int { |
| return a.GetCadence(pCad).ActiveFrom.Compare(b.GetCadence(pCad).ActiveFrom) |
| }) |
|
|
| phase.ItemsByKey[rc.AddonRateCard.Key()] = newItems |
| } |
|
|
| return nil |
| }) |
| } |
|
|
| |
| func (d *diffable) setItemRelativeCadence(item *subscription.SubscriptionItemSpec, phaseCadence models.CadencedModel, target timeutil.OpenPeriod) { |
| if target.From != nil { |
| diff := datetime.ISODurationBetween(phaseCadence.ActiveFrom, *target.From) |
|
|
| if !diff.IsZero() { |
| item.ActiveFromOverrideRelativeToPhaseStart = &diff |
| } |
| } |
|
|
| if target.To != nil { |
| diff := datetime.ISODurationBetween(phaseCadence.ActiveFrom, *target.To) |
|
|
| if phaseCadence.ActiveTo == nil || !target.To.Equal(*phaseCadence.ActiveTo) { |
| item.ActiveToOverrideRelativeToPhaseStart = &diff |
| } |
| } |
| } |
|
|