File size: 3,191 Bytes
d6f631f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package subscriptionaddon

import (
	"errors"
	"fmt"
	"time"

	"github.com/samber/lo"

	"github.com/openmeterio/openmeter/openmeter/productcatalog/addon"
	"github.com/openmeterio/openmeter/pkg/models"
	"github.com/openmeterio/openmeter/pkg/timeutil"
)

type SubscriptionAddon struct {
	models.NamespacedID
	models.ManagedModel
	models.MetadataModel

	Name        string  `json:"name"`
	Description *string `json:"description,omitempty"`

	// Maybe break up to AddonID + AddonMeta?
	Addon          addon.Addon `json:"addon"`
	SubscriptionID string      `json:"subscriptionID"`

	// RateCards is populated from the Addon's RateCards
	RateCards  []SubscriptionAddonRateCard                  `json:"rateCards"`
	Quantities timeutil.Timeline[SubscriptionAddonQuantity] `json:"quantities"`
}

func (a SubscriptionAddon) GetInstanceAt(t time.Time) (SubscriptionAddonInstance, bool) {
	inst := SubscriptionAddonInstance{}
	found := false

	for _, q := range a.GetInstances() {
		if q.CadencedModel.IsActiveAt(t) {
			found = true

			inst = q

			break
		}
	}

	return inst, found
}

func (a SubscriptionAddon) GetInstances() []SubscriptionAddonInstance {
	quantities := a.Quantities

	// Deleted things should not get into memory but let's look out anyways
	if a.DeletedAt != nil {
		quantities = quantities.Before(*a.DeletedAt)
	}

	if len(quantities.GetTimes()) == 0 {
		return []SubscriptionAddonInstance{}
	}

	periods := quantities.GetOpenPeriods()

	if len(periods) < 1 {
		// This should never happen as len > 0
		return []SubscriptionAddonInstance{}
	}

	periods = periods[1:]

	if len(periods) != len(quantities.GetTimes()) {
		// This should never happen
		return []SubscriptionAddonInstance{}
	}

	if lo.SomeBy(periods, func(period timeutil.OpenPeriod) bool {
		return period.From == nil
	}) {
		// This should never happen
		return []SubscriptionAddonInstance{}
	}

	return lo.Map(periods, func(period timeutil.OpenPeriod, idx int) SubscriptionAddonInstance {
		quantity := quantities.GetAt(idx)

		cad, _ := models.NewCadencedModelFromPeriod(period)

		return SubscriptionAddonInstance{
			NamespacedID:  a.NamespacedID,
			ManagedModel:  a.ManagedModel,
			MetadataModel: a.MetadataModel,

			Addon:          a.Addon,
			SubscriptionID: a.SubscriptionID,

			Name:        a.Name,
			Description: a.Description,

			RateCards:     a.RateCards,
			Quantity:      quantity.GetValue().Quantity,
			CadencedModel: cad,
		}
	})
}

type CreateSubscriptionAddonInput struct {
	models.MetadataModel

	AddonID        string `json:"addonID"`
	SubscriptionID string `json:"subscriptionID"`

	InitialQuantity CreateSubscriptionAddonQuantityInput `json:"initialQuantity"`
}

func (i CreateSubscriptionAddonInput) Validate() error {
	var errs []error

	if i.AddonID == "" {
		errs = append(errs, errors.New("addonID is required"))
	}

	if i.SubscriptionID == "" {
		errs = append(errs, errors.New("subscriptionID is required"))
	}

	if err := i.InitialQuantity.Validate(); err != nil {
		errs = append(errs, fmt.Errorf("initialQuantity: %w", err))
	}

	if i.InitialQuantity.Quantity == 0 {
		errs = append(errs, errors.New("initialQuantity.quantity cannot be 0"))
	}

	return errors.Join(errs...)
}