| package billing |
|
|
| import ( |
| "errors" |
| "fmt" |
| "strings" |
| "time" |
|
|
| "github.com/samber/lo" |
| "github.com/samber/mo" |
|
|
| "github.com/openmeterio/openmeter/openmeter/app" |
| "github.com/openmeterio/openmeter/openmeter/billing/models/externalid" |
| "github.com/openmeterio/openmeter/openmeter/billing/models/totals" |
| "github.com/openmeterio/openmeter/openmeter/customer" |
| "github.com/openmeterio/openmeter/openmeter/streaming" |
| "github.com/openmeterio/openmeter/pkg/currencyx" |
| "github.com/openmeterio/openmeter/pkg/expand" |
| "github.com/openmeterio/openmeter/pkg/models" |
| "github.com/openmeterio/openmeter/pkg/pagination" |
| "github.com/openmeterio/openmeter/pkg/slicesx" |
| "github.com/openmeterio/openmeter/pkg/timeutil" |
| ) |
|
|
| type StandardInvoiceStatusCategory string |
|
|
| const ( |
| StandardInvoiceStatusCategoryGathering StandardInvoiceStatusCategory = "gathering" |
| StandardInvoiceStatusCategoryDraft StandardInvoiceStatusCategory = "draft" |
| StandardInvoiceStatusCategoryDelete StandardInvoiceStatusCategory = "delete" |
| StandardInvoiceStatusCategoryDeleted StandardInvoiceStatusCategory = "deleted" |
| StandardInvoiceStatusCategoryIssuing StandardInvoiceStatusCategory = "issuing" |
| StandardInvoiceStatusCategoryIssued StandardInvoiceStatusCategory = "issued" |
| StandardInvoiceStatusCategoryPaymentProcessing StandardInvoiceStatusCategory = "payment_processing" |
| StandardInvoiceStatusCategoryOverdue StandardInvoiceStatusCategory = "overdue" |
| StandardInvoiceStatusCategoryPaid StandardInvoiceStatusCategory = "paid" |
| StandardInvoiceStatusCategoryUncollectible StandardInvoiceStatusCategory = "uncollectible" |
| StandardInvoiceStatusCategoryVoided StandardInvoiceStatusCategory = "voided" |
| ) |
|
|
| func (s StandardInvoiceStatusCategory) MatchesInvoiceStatus(status StandardInvoiceStatus) bool { |
| return status.ShortStatus() == string(s) |
| } |
|
|
| type StandardInvoiceStatus string |
|
|
| const ( |
| |
| StandardInvoiceStatusGathering StandardInvoiceStatus = "gathering" |
|
|
| StandardInvoiceStatusDraftCreated StandardInvoiceStatus = "draft.created" |
| |
| StandardInvoiceStatusDraftWaitingForCollection StandardInvoiceStatus = "draft.waiting_for_collection" |
| |
| StandardInvoiceStatusDraftCollecting StandardInvoiceStatus = "draft.collecting" |
| StandardInvoiceStatusDraftUpdating StandardInvoiceStatus = "draft.updating" |
| StandardInvoiceStatusDraftManualApprovalNeeded StandardInvoiceStatus = "draft.manual_approval_needed" |
| StandardInvoiceStatusDraftValidating StandardInvoiceStatus = "draft.validating" |
| StandardInvoiceStatusDraftInvalid StandardInvoiceStatus = "draft.invalid" |
| StandardInvoiceStatusDraftSyncing StandardInvoiceStatus = "draft.syncing" |
| StandardInvoiceStatusDraftSyncFailed StandardInvoiceStatus = "draft.sync_failed" |
| StandardInvoiceStatusDraftWaitingAutoApproval StandardInvoiceStatus = "draft.waiting_auto_approval" |
| StandardInvoiceStatusDraftReadyToIssue StandardInvoiceStatus = "draft.ready_to_issue" |
|
|
| StandardInvoiceStatusDeleteInProgress StandardInvoiceStatus = "delete.in_progress" |
| StandardInvoiceStatusDeleteSyncing StandardInvoiceStatus = "delete.syncing" |
| StandardInvoiceStatusDeleteFailed StandardInvoiceStatus = "delete.failed" |
| StandardInvoiceStatusDeleted StandardInvoiceStatus = "deleted" |
|
|
| StandardInvoiceStatusIssuingSyncing StandardInvoiceStatus = "issuing.syncing" |
| StandardInvoiceStatusIssuingSyncFailed StandardInvoiceStatus = "issuing.failed" |
| StandardInvoiceStatusIssuingChargeBooking StandardInvoiceStatus = "issuing.charge_booking" |
| StandardInvoiceStatusIssuingChargeBookingFailed StandardInvoiceStatus = "issuing.charge_booking_failed" |
|
|
| StandardInvoiceStatusIssued StandardInvoiceStatus = "issued" |
|
|
| StandardInvoiceStatusPaymentProcessingPending StandardInvoiceStatus = "payment_processing.pending" |
| StandardInvoiceStatusPaymentProcessingBookingAuthorized StandardInvoiceStatus = "payment_processing.booking_authorized" |
| StandardInvoiceStatusPaymentProcessingBookingAuthorizedFailed StandardInvoiceStatus = "payment_processing.booking_authorized_failed" |
| StandardInvoiceStatusPaymentProcessingBookingAuthorizedAndSettled StandardInvoiceStatus = "payment_processing.booking_authorized_and_settled" |
| StandardInvoiceStatusPaymentProcessingBookingAuthorizedAndSettledFailed StandardInvoiceStatus = "payment_processing.booking_authorized_and_settled_failed" |
| StandardInvoiceStatusPaymentProcessingAuthorized StandardInvoiceStatus = "payment_processing.authorized" |
| StandardInvoiceStatusPaymentProcessingFailed StandardInvoiceStatus = "payment_processing.failed" |
| StandardInvoiceStatusPaymentProcessingActionRequired StandardInvoiceStatus = "payment_processing.action_required" |
| StandardInvoiceStatusPaymentProcessingBookingSettled StandardInvoiceStatus = "payment_processing.booking_settled" |
| StandardInvoiceStatusPaymentProcessingBookingSettledFailed StandardInvoiceStatus = "payment_processing.booking_settled_failed" |
|
|
| |
|
|
| StandardInvoiceStatusOverdue StandardInvoiceStatus = "overdue" |
|
|
| StandardInvoiceStatusPaid StandardInvoiceStatus = "paid" |
|
|
| StandardInvoiceStatusUncollectible StandardInvoiceStatus = "uncollectible" |
|
|
| StandardInvoiceStatusVoided StandardInvoiceStatus = "voided" |
| ) |
|
|
| var validStatuses = []StandardInvoiceStatus{ |
| StandardInvoiceStatusGathering, |
| StandardInvoiceStatusDraftCreated, |
| StandardInvoiceStatusDraftWaitingForCollection, |
| StandardInvoiceStatusDraftCollecting, |
| StandardInvoiceStatusDraftUpdating, |
| StandardInvoiceStatusDraftManualApprovalNeeded, |
| StandardInvoiceStatusDraftValidating, |
| StandardInvoiceStatusDraftInvalid, |
| StandardInvoiceStatusDraftSyncing, |
| StandardInvoiceStatusDraftSyncFailed, |
| StandardInvoiceStatusDraftWaitingAutoApproval, |
| StandardInvoiceStatusDraftReadyToIssue, |
|
|
| StandardInvoiceStatusDeleteInProgress, |
| StandardInvoiceStatusDeleteSyncing, |
| StandardInvoiceStatusDeleteFailed, |
| StandardInvoiceStatusDeleted, |
|
|
| StandardInvoiceStatusIssuingSyncing, |
| StandardInvoiceStatusIssuingSyncFailed, |
| StandardInvoiceStatusIssuingChargeBooking, |
| StandardInvoiceStatusIssuingChargeBookingFailed, |
|
|
| StandardInvoiceStatusIssued, |
|
|
| StandardInvoiceStatusPaymentProcessingPending, |
| StandardInvoiceStatusPaymentProcessingBookingAuthorized, |
| StandardInvoiceStatusPaymentProcessingBookingAuthorizedFailed, |
| StandardInvoiceStatusPaymentProcessingBookingAuthorizedAndSettled, |
| StandardInvoiceStatusPaymentProcessingBookingAuthorizedAndSettledFailed, |
| StandardInvoiceStatusPaymentProcessingAuthorized, |
| StandardInvoiceStatusPaymentProcessingFailed, |
| StandardInvoiceStatusPaymentProcessingActionRequired, |
| StandardInvoiceStatusPaymentProcessingBookingSettled, |
| StandardInvoiceStatusPaymentProcessingBookingSettledFailed, |
|
|
| StandardInvoiceStatusOverdue, |
|
|
| StandardInvoiceStatusPaid, |
|
|
| StandardInvoiceStatusUncollectible, |
|
|
| StandardInvoiceStatusVoided, |
| } |
|
|
| func (s StandardInvoiceStatus) Values() []string { |
| return lo.Map( |
| validStatuses, |
| func(item StandardInvoiceStatus, _ int) string { |
| return string(item) |
| }, |
| ) |
| } |
|
|
| func (s StandardInvoiceStatus) ShortStatus() string { |
| parts := strings.SplitN(string(s), ".", 2) |
| return parts[0] |
| } |
|
|
| type StandardInvoiceStatusMatcher interface { |
| MatchesInvoiceStatus(StandardInvoiceStatus) bool |
| } |
|
|
| func (s StandardInvoiceStatus) Matches(statuses ...StandardInvoiceStatusMatcher) bool { |
| for _, matcher := range statuses { |
| if matcher.MatchesInvoiceStatus(s) { |
| return true |
| } |
| } |
|
|
| return false |
| } |
|
|
| func (s StandardInvoiceStatus) MatchesInvoiceStatus(status StandardInvoiceStatus) bool { |
| return s == status |
| } |
|
|
| var failedStatuses = []StandardInvoiceStatus{ |
| StandardInvoiceStatusDraftSyncFailed, |
| StandardInvoiceStatusIssuingSyncFailed, |
| StandardInvoiceStatusIssuingChargeBookingFailed, |
| StandardInvoiceStatusDeleteFailed, |
| StandardInvoiceStatusPaymentProcessingBookingAuthorizedFailed, |
| StandardInvoiceStatusPaymentProcessingBookingAuthorizedAndSettledFailed, |
| StandardInvoiceStatusPaymentProcessingFailed, |
| StandardInvoiceStatusPaymentProcessingBookingSettledFailed, |
| } |
|
|
| func (s StandardInvoiceStatus) IsFailed() bool { |
| return lo.Contains(failedStatuses, s) |
| } |
|
|
| var finalStatuses = []StandardInvoiceStatus{ |
| StandardInvoiceStatusDeleted, |
| StandardInvoiceStatusPaid, |
| StandardInvoiceStatusUncollectible, |
| StandardInvoiceStatusVoided, |
| } |
|
|
| func (s StandardInvoiceStatus) IsFinal() bool { |
| return lo.Contains(finalStatuses, s) |
| } |
|
|
| func (s StandardInvoiceStatus) Validate() error { |
| if !lo.Contains(validStatuses, s) { |
| return fmt.Errorf("invalid invoice status: %s", s) |
| } |
|
|
| return nil |
| } |
|
|
| |
| |
| |
| |
| type InvoiceShortStatus string |
|
|
| |
| |
| func (s InvoiceShortStatus) Values() []InvoiceShortStatus { |
| unsupportedStatuses := []StandardInvoiceStatus{ |
| StandardInvoiceStatusGathering, |
| StandardInvoiceStatusDeleteInProgress, |
| StandardInvoiceStatusDeleteSyncing, |
| StandardInvoiceStatusDeleteFailed, |
| StandardInvoiceStatusDeleted, |
| } |
| return lo.Uniq(lo.Map(lo.Without(validStatuses, unsupportedStatuses...), func(st StandardInvoiceStatus, _ int) InvoiceShortStatus { |
| return InvoiceShortStatus(st.ShortStatus()) |
| })) |
| } |
|
|
| func (s InvoiceShortStatus) Validate() error { |
| if !lo.Contains(s.Values(), s) { |
| return fmt.Errorf("invalid invoice short status: %s", s) |
| } |
|
|
| return nil |
| } |
|
|
| type StandardInvoiceBase struct { |
| Namespace string `json:"namespace"` |
| ID string `json:"id"` |
|
|
| Number string `json:"number"` |
| Description *string `json:"description,omitempty"` |
|
|
| Type InvoiceType `json:"type"` |
|
|
| Metadata map[string]string `json:"metadata"` |
|
|
| Currency currencyx.Code `json:"currency,omitempty"` |
| Status StandardInvoiceStatus `json:"status"` |
| StatusDetails StandardInvoiceStatusDetails `json:"statusDetail,omitempty"` |
|
|
| Period *timeutil.ClosedPeriod `json:"period,omitempty"` |
|
|
| DueAt *time.Time `json:"dueDate,omitempty"` |
|
|
| CreatedAt time.Time `json:"createdAt"` |
| UpdatedAt time.Time `json:"updatedAt"` |
| VoidedAt *time.Time `json:"voidedAt,omitempty"` |
| DraftUntil *time.Time `json:"draftUntil,omitempty"` |
| IssuedAt *time.Time `json:"issuedAt,omitempty"` |
| DeletedAt *time.Time `json:"deletedAt,omitempty"` |
| DeletionSource ChangeSource `json:"deletionSource,omitempty"` |
| SentToCustomerAt *time.Time `json:"sentToCustomerAt,omitempty"` |
| QuantitySnapshotedAt *time.Time `json:"quantitySnapshotedAt,omitempty"` |
|
|
| CollectionAt *time.Time `json:"collectionAt,omitempty"` |
| |
| PaymentProcessingEnteredAt *time.Time `json:"paymentProcessingEnteredAt,omitempty"` |
|
|
| |
| |
| |
| Customer InvoiceCustomer `json:"customer"` |
| Supplier SupplierContact `json:"supplier"` |
| Workflow InvoiceWorkflow `json:"workflow,omitempty"` |
|
|
| ExternalIDs externalid.InvoiceExternalIDs `json:"externalIds,omitempty"` |
|
|
| SchemaLevel int `json:"schemaLevel"` |
|
|
| |
| } |
|
|
| func (i StandardInvoiceBase) Validate() error { |
| var outErr error |
|
|
| if err := i.Type.Validate(); err != nil { |
| outErr = errors.Join(outErr, ValidationWithFieldPrefix("type", err)) |
| } |
|
|
| if err := i.Currency.Validate(); err != nil { |
| outErr = errors.Join(outErr, ValidationWithFieldPrefix("currency", err)) |
| } |
|
|
| if err := i.Status.Validate(); err != nil { |
| outErr = errors.Join(outErr, ValidationWithFieldPrefix("status", err)) |
| } |
|
|
| if err := i.Customer.Validate(); err != nil { |
| outErr = errors.Join(outErr, ValidationWithFieldPrefix("customer", err)) |
| } |
|
|
| if err := i.Supplier.Validate(); err != nil { |
| outErr = errors.Join(outErr, ValidationWithFieldPrefix("supplier", err)) |
| } |
|
|
| if i.Period != nil { |
| if err := i.Period.Validate(); err != nil { |
| outErr = errors.Join(outErr, ValidationWithFieldPrefix("period", err)) |
| } |
| } |
|
|
| return outErr |
| } |
|
|
| func (i StandardInvoiceBase) DefaultCollectionAtForStandardInvoice() time.Time { |
| if i.CollectionAt == nil { |
| return i.CreatedAt |
| } |
|
|
| return lo.FromPtr(i.CollectionAt) |
| } |
|
|
| func (i StandardInvoiceBase) GetDeletedAt() *time.Time { |
| return i.DeletedAt |
| } |
|
|
| func (i StandardInvoiceBase) GetID() string { |
| return i.ID |
| } |
|
|
| func (i StandardInvoiceBase) GetInvoiceID() InvoiceID { |
| return InvoiceID{ |
| Namespace: i.Namespace, |
| ID: i.ID, |
| } |
| } |
|
|
| func (i StandardInvoiceBase) GetCustomerID() customer.CustomerID { |
| return customer.CustomerID{ |
| Namespace: i.Namespace, |
| ID: i.Customer.CustomerID, |
| } |
| } |
|
|
| var _ GenericInvoice = (*StandardInvoice)(nil) |
|
|
| type StandardInvoice struct { |
| StandardInvoiceBase `json:",inline"` |
|
|
| |
| Lines StandardInvoiceLines `json:"lines,omitempty"` |
| ValidationIssues ValidationIssues `json:"validationIssues,omitempty"` |
|
|
| Totals totals.Totals `json:"totals"` |
|
|
| |
| ExpandedFields StandardInvoiceExpands `json:"-"` |
| } |
|
|
| func (i StandardInvoice) Validate() error { |
| var outErr error |
|
|
| if err := i.StandardInvoiceBase.Validate(); err != nil { |
| outErr = errors.Join(outErr, err) |
| } |
|
|
| if err := i.Lines.Validate(); err != nil { |
| outErr = errors.Join(outErr, ValidationWithFieldPrefix("lines", err)) |
| } |
|
|
| if i.Lines.IsPresent() { |
| for _, line := range i.Lines.OrEmpty() { |
| if line.Currency != i.Currency { |
| outErr = errors.Join(outErr, fmt.Errorf("line[%s]: currency[%s] is not equal to invoice currency[%s]", line.ID, line.Currency, i.Currency)) |
| } |
| } |
| } |
|
|
| return outErr |
| } |
|
|
| func (i StandardInvoice) CustomerID() customer.CustomerID { |
| return customer.CustomerID{ |
| Namespace: i.Namespace, |
| ID: i.Customer.CustomerID, |
| } |
| } |
|
|
| func (i StandardInvoice) AsInvoice() Invoice { |
| return Invoice{ |
| t: InvoiceTypeStandard, |
| standardInvoice: &i, |
| } |
| } |
|
|
| func (i StandardInvoice) GetType() InvoiceType { |
| return InvoiceTypeStandard |
| } |
|
|
| func (i StandardInvoice) CloneAsGenericInvoice() (GenericInvoice, error) { |
| cloned, err := i.Clone() |
| if err != nil { |
| return nil, err |
| } |
|
|
| return &cloned, nil |
| } |
|
|
| func (i StandardInvoice) GetGenericLines() mo.Option[[]GenericInvoiceLine] { |
| if !i.Lines.IsPresent() { |
| return mo.None[[]GenericInvoiceLine]() |
| } |
|
|
| return mo.Some(lo.Map(i.Lines.OrEmpty(), func(l *StandardLine, _ int) GenericInvoiceLine { |
| return &standardInvoiceLineGenericWrapper{StandardLine: l} |
| })) |
| } |
|
|
| func (i *StandardInvoice) SetLines(lines []GenericInvoiceLine) error { |
| mappedLines, err := slicesx.MapWithErr(lines, func(l GenericInvoiceLine) (*StandardLine, error) { |
| line, err := l.AsInvoiceLine().AsStandardLine() |
| if err != nil { |
| return nil, err |
| } |
|
|
| return &line, nil |
| }) |
| if err != nil { |
| return fmt.Errorf("mapping lines: %w", err) |
| } |
|
|
| i.Lines = NewStandardInvoiceLines(mappedLines) |
| return nil |
| } |
|
|
| func (i *StandardInvoice) UnsetLines() { |
| i.Lines = StandardInvoiceLines{} |
| } |
|
|
| func (i *StandardInvoice) MergeValidationIssues(errIn error, reportingComponent ComponentName) error { |
| i.ValidationIssues = lo.Filter(i.ValidationIssues, func(issue ValidationIssue, _ int) bool { |
| return issue.Component != reportingComponent |
| }) |
|
|
| |
| |
| newIssues, finalErrs := ToValidationIssues(errIn) |
| i.ValidationIssues = append(i.ValidationIssues, newIssues...) |
|
|
| return finalErrs |
| } |
|
|
| func (i *StandardInvoice) HasCriticalValidationIssues() bool { |
| _, found := lo.Find(i.ValidationIssues, func(issue ValidationIssue) bool { |
| return issue.Severity == ValidationIssueSeverityCritical |
| }) |
|
|
| return found |
| } |
|
|
| |
| |
| |
| |
| |
| func (i StandardInvoice) RemoveMetaForCompare() (StandardInvoice, error) { |
| invoice := i |
| newLines, err := i.Lines.MapWithErr(func(line *StandardLine) (*StandardLine, error) { |
| return line.RemoveMetaForCompare() |
| }) |
| if err != nil { |
| return StandardInvoice{}, err |
| } |
|
|
| invoice.Lines = newLines |
|
|
| return invoice, nil |
| } |
|
|
| |
| func (i *StandardInvoice) getLeafLines() DetailedLines { |
| out := []DetailedLine{} |
|
|
| for _, line := range i.Lines.OrEmpty() { |
| |
|
|
| out = append(out, line.DetailedLines...) |
| } |
|
|
| return out |
| } |
|
|
| type DetailedLineWithResolvedTaxConfig struct { |
| DetailedLine |
| TaxConfig *TaxConfig |
| } |
|
|
| |
| |
| func (i *StandardInvoice) GetLeafLinesWithResolvedTaxConfig() []DetailedLineWithResolvedTaxConfig { |
| out := make([]DetailedLineWithResolvedTaxConfig, 0) |
|
|
| for _, parentLine := range i.Lines.OrEmpty() { |
| taxConfig := MergeTaxConfigs(FromProductCatalog(i.Workflow.Config.Invoicing.DefaultTaxConfig), parentLine.TaxConfig) |
| for _, line := range parentLine.DetailedLines { |
| out = append(out, DetailedLineWithResolvedTaxConfig{ |
| DetailedLine: line, |
| TaxConfig: taxConfig, |
| }) |
| } |
| } |
|
|
| return out |
| } |
|
|
| func (i StandardInvoice) Clone() (StandardInvoice, error) { |
| clone := i |
|
|
| clonedLines, err := i.Lines.Clone() |
| if err != nil { |
| return StandardInvoice{}, err |
| } |
|
|
| clone.Lines = clonedLines |
| clone.ValidationIssues = i.ValidationIssues.Clone() |
| clone.Totals = i.Totals |
|
|
| return clone, nil |
| } |
|
|
| func (i StandardInvoice) RemoveCircularReferences() (StandardInvoice, error) { |
| clone, err := i.Clone() |
| if err != nil { |
| return StandardInvoice{}, err |
| } |
|
|
| clone.Lines, err = clone.Lines.MapWithErr(func(line *StandardLine) (*StandardLine, error) { |
| return line.RemoveCircularReferences() |
| }) |
| if err != nil { |
| return StandardInvoice{}, err |
| } |
|
|
| return clone, nil |
| } |
|
|
| func (i *StandardInvoice) SortLines() { |
| if !i.Lines.IsPresent() { |
| return |
| } |
|
|
| i.Lines.Sort() |
| } |
|
|
| type StandardInvoiceLines struct { |
| mo.Option[StandardLines] |
| } |
|
|
| func NewStandardInvoiceLines(children []*StandardLine) StandardInvoiceLines { |
| |
| if len(children) == 0 { |
| children = nil |
| } |
|
|
| return StandardInvoiceLines{mo.Some(StandardLines(children))} |
| } |
|
|
| func (i StandardInvoiceLines) Validate() error { |
| return errors.Join(lo.Map(i.OrEmpty(), func(line *StandardLine, idx int) error { |
| return ValidationWithFieldPrefix(fmt.Sprintf("%d", idx), line.Validate()) |
| })...) |
| } |
|
|
| func (c StandardInvoiceLines) Map(fn func(*StandardLine) *StandardLine) StandardInvoiceLines { |
| if !c.IsPresent() { |
| return c |
| } |
|
|
| return StandardInvoiceLines{ |
| mo.Some( |
| c.OrEmpty().Map(fn), |
| ), |
| } |
| } |
|
|
| func (c StandardInvoiceLines) MapWithErr(fn func(*StandardLine) (*StandardLine, error)) (StandardInvoiceLines, error) { |
| if !c.IsPresent() { |
| return c, nil |
| } |
|
|
| res, err := slicesx.MapWithErr(c.OrEmpty(), fn) |
| if err != nil { |
| return StandardInvoiceLines{}, err |
| } |
|
|
| return StandardInvoiceLines{mo.Some(StandardLines(res))}, nil |
| } |
|
|
| func (c StandardInvoiceLines) WithNormalizedValues() (StandardInvoiceLines, error) { |
| return c.MapWithErr(func(line *StandardLine) (*StandardLine, error) { |
| return line.WithNormalizedValues() |
| }) |
| } |
|
|
| func (c StandardInvoiceLines) Clone() (StandardInvoiceLines, error) { |
| return c.MapWithErr(func(l *StandardLine) (*StandardLine, error) { |
| return l.Clone() |
| }) |
| } |
|
|
| func (c StandardInvoiceLines) GetByID(id string) *StandardLine { |
| return lo.FindOrElse(c.Option.OrEmpty(), nil, func(line *StandardLine) bool { |
| return line.ID == id |
| }) |
| } |
|
|
| func (c *StandardInvoiceLines) ReplaceByID(id string, newLine *StandardLine) bool { |
| if c.IsAbsent() { |
| return false |
| } |
|
|
| lines := c.OrEmpty() |
|
|
| for i, line := range lines { |
| if line.ID == id { |
| |
| originalDBState := line.DBState |
|
|
| lines[i] = newLine |
| lines[i].DBState = originalDBState |
| return true |
| } |
| } |
|
|
| return false |
| } |
|
|
| func (c *StandardInvoiceLines) ReplaceLinesByID(lines ...*StandardLine) error { |
| for _, line := range lines { |
| if line == nil { |
| return fmt.Errorf("line is required") |
| } |
|
|
| if c.GetByID(line.ID) == nil { |
| return fmt.Errorf("replacing line[%s]: line not found", line.ID) |
| } |
| } |
|
|
| for _, line := range lines { |
| if ok := c.ReplaceByID(line.ID, line); !ok { |
| return fmt.Errorf("replacing line[%s]", line.ID) |
| } |
| } |
|
|
| return nil |
| } |
|
|
| func (c *StandardInvoiceLines) Sort() { |
| if c.IsAbsent() { |
| return |
| } |
|
|
| lines := c.OrEmpty() |
| lines.Sort() |
| c.Option = mo.Some(lines) |
| } |
|
|
| |
| func (c StandardInvoiceLines) NonDeletedLineCount() int { |
| return lo.CountBy(c.OrEmpty(), func(l *StandardLine) bool { |
| return l.DeletedAt == nil |
| }) |
| } |
|
|
| func (c *StandardInvoiceLines) Append(l ...*StandardLine) { |
| c.Option = mo.Some(append(c.OrEmpty(), l...)) |
| } |
|
|
| func (c *StandardInvoiceLines) RemoveByID(id string) bool { |
| toBeRemoved := c.GetByID(id) |
| if toBeRemoved == nil { |
| return false |
| } |
|
|
| c.Option = mo.Some( |
| lo.Filter(c.Option.OrEmpty(), func(l *StandardLine, _ int) bool { |
| return l.ID != id |
| }), |
| ) |
|
|
| return true |
| } |
|
|
| func (c StandardInvoiceLines) GetReferencedFeatureKeys() ([]string, error) { |
| if c.IsAbsent() { |
| return nil, nil |
| } |
|
|
| return c.OrEmpty().GetReferencedFeatureKeys() |
| } |
|
|
| type StandardInvoiceAvailableActions struct { |
| Advance *StandardInvoiceAvailableActionDetails `json:"advance,omitempty"` |
| Approve *StandardInvoiceAvailableActionDetails `json:"approve,omitempty"` |
| Delete *StandardInvoiceAvailableActionDetails `json:"delete,omitempty"` |
| Retry *StandardInvoiceAvailableActionDetails `json:"retry,omitempty"` |
| Void *StandardInvoiceAvailableActionDetails `json:"void,omitempty"` |
| SnapshotQuantities *StandardInvoiceAvailableActionDetails `json:"snapshotQuantities,omitempty"` |
|
|
| Invoice *StandardInvoiceAvailableActionInvoiceDetails `json:"invoice,omitempty"` |
| } |
|
|
| type StandardInvoiceAvailableActionDetails struct { |
| ResultingState StandardInvoiceStatus `json:"resultingState"` |
| } |
|
|
| type StandardInvoiceAvailableActionInvoiceDetails struct{} |
|
|
| type StandardInvoiceStatusDetails struct { |
| Immutable bool `json:"immutable"` |
| Failed bool `json:"failed"` |
| AvailableActions StandardInvoiceAvailableActions `json:"availableActions"` |
| } |
|
|
| const ( |
| CustomerUsageAttributionTypeVersionV1 = "customer_usage_attribution.v1" |
| CustomerUsageAttributionTypeVersionV2 = "customer_usage_attribution.v2" |
| ) |
|
|
| type ( |
| VersionedCustomerUsageAttribution struct { |
| streaming.CustomerUsageAttribution `json:",inline"` |
| Type string `json:"type"` |
| } |
| ) |
|
|
| type CreateInvoiceAdapterInput struct { |
| Namespace string |
| Customer customer.Customer |
| Profile Profile |
| Number string |
| Currency currencyx.Code |
| Status StandardInvoiceStatus |
| Metadata map[string]string |
| IssuedAt time.Time |
|
|
| Type InvoiceType |
| Description *string |
| DueAt *time.Time |
| CollectionAt *time.Time |
|
|
| Totals totals.Totals |
| } |
|
|
| func (c CreateInvoiceAdapterInput) Validate() error { |
| if c.Namespace == "" { |
| return errors.New("namespace is required") |
| } |
|
|
| if err := c.Customer.Validate(); err != nil { |
| return fmt.Errorf("customer: %w", err) |
| } |
|
|
| if err := c.Profile.Validate(); err != nil { |
| return fmt.Errorf("profile: %w", err) |
| } |
|
|
| if c.Profile.Apps == nil { |
| return errors.New("profile: apps must be expanded") |
| } |
|
|
| if err := c.Currency.Validate(); err != nil { |
| return fmt.Errorf("currency: %w", err) |
| } |
|
|
| if err := c.Status.Validate(); err != nil { |
| return fmt.Errorf("status: %w", err) |
| } |
|
|
| if err := c.Type.Validate(); err != nil { |
| return fmt.Errorf("type: %w", err) |
| } |
|
|
| if err := c.Totals.Validate(); err != nil { |
| return fmt.Errorf("totals: %w", err) |
| } |
|
|
| if c.Number == "" { |
| return errors.New("invoice number is required") |
| } |
|
|
| if c.CollectionAt != nil && c.Status != StandardInvoiceStatusGathering { |
| return errors.New("setting collectionAt is only allowed when creating gathering invoices") |
| } |
|
|
| return nil |
| } |
|
|
| type CreateInvoiceAdapterRespone = StandardInvoice |
|
|
| type AssociatedLineCountsAdapterResponse struct { |
| Counts map[InvoiceID]int64 |
| } |
|
|
| type ( |
| AdvanceInvoiceInput = InvoiceID |
| ApproveInvoiceInput = InvoiceID |
| ForceCollectInvoiceInput = InvoiceID |
| PaymentAuthorizedInput = InvoiceID |
| RetryInvoiceInput = InvoiceID |
| ) |
|
|
| type UpdateStandardInvoiceAdapterInput = StandardInvoice |
|
|
| type GetInvoiceOwnershipAdapterInput = InvoiceID |
|
|
| type GetOwnershipAdapterResponse struct { |
| Namespace string |
| InvoiceID string |
| CustomerID string |
| } |
|
|
| type DeleteInvoiceInput struct { |
| |
| Invoice InvoiceID |
| |
| |
| |
| |
| |
| |
| DeletionSource ChangeSource |
| } |
|
|
| func (i DeleteInvoiceInput) Validate() error { |
| var errs []error |
|
|
| if err := i.Invoice.Validate(); err != nil { |
| errs = append(errs, fmt.Errorf("invoice: %w", err)) |
| } |
|
|
| if err := i.DeletionSource.Validate(); err != nil { |
| errs = append(errs, fmt.Errorf("deletion source: %w", err)) |
| } |
|
|
| return errors.Join(errs...) |
| } |
|
|
| type DeleteInvoiceTriggerInput struct { |
| Source ChangeSource |
| } |
|
|
| func (i DeleteInvoiceTriggerInput) Validate() error { |
| if err := i.Source.Validate(); err != nil { |
| return fmt.Errorf("source: %w", err) |
| } |
|
|
| return nil |
| } |
|
|
| var _ models.Validator = DeleteInvoiceTriggerInput{} |
|
|
| type UpdateInvoiceLinesInternalInput struct { |
| Namespace string |
| CustomerID string |
| Lines []*StandardLine |
| } |
|
|
| func (i UpdateInvoiceLinesInternalInput) Validate() error { |
| if i.Namespace == "" { |
| return errors.New("namespace is required") |
| } |
|
|
| if i.CustomerID == "" { |
| return errors.New("customer ID is required") |
| } |
|
|
| return nil |
| } |
|
|
| |
| |
| |
| type UpdateStandardInvoiceInput struct { |
| |
| Invoice InvoiceID |
| |
| EditFn func(*StandardInvoice) error |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| ChangeSource ChangeSource |
| |
| IncludeDeletedLines bool |
| } |
|
|
| func (i UpdateStandardInvoiceInput) Validate() error { |
| if err := i.Invoice.Validate(); err != nil { |
| return fmt.Errorf("id: %w", err) |
| } |
|
|
| if i.EditFn == nil { |
| return errors.New("edit function is required") |
| } |
|
|
| if err := i.ChangeSource.Validate(); err != nil { |
| return fmt.Errorf("line change source: %w", err) |
| } |
|
|
| return nil |
| } |
|
|
| type SimulateInvoiceInput struct { |
| Namespace string |
| CustomerID *string |
| Customer *customer.Customer |
|
|
| Number *string |
| Currency currencyx.Code |
| Lines StandardInvoiceLines |
| } |
|
|
| func (i SimulateInvoiceInput) Validate() error { |
| if i.CustomerID != nil { |
| if *i.CustomerID == "" { |
| return errors.New("customer ID is required") |
| } |
| } |
|
|
| if i.Customer != nil { |
| if err := i.Customer.Validate(); err != nil { |
| return fmt.Errorf("customer: %w", err) |
| } |
| } |
|
|
| if i.CustomerID == nil && i.Customer == nil { |
| return errors.New("either customer ID or customer is required") |
| } |
|
|
| if i.CustomerID != nil && i.Customer != nil { |
| return errors.New("only one of customer ID or customer can be specified") |
| } |
|
|
| if i.Namespace == "" { |
| return errors.New("namespace is required") |
| } |
|
|
| if i.Currency == "" { |
| return errors.New("currency is required") |
| } |
|
|
| if len(i.Lines.OrEmpty()) == 0 { |
| return errors.New("lines are required") |
| } |
|
|
| return nil |
| } |
|
|
| type UpsertValidationIssuesInput struct { |
| Invoice InvoiceID |
| Issues ValidationIssues |
| } |
|
|
| func (i UpsertValidationIssuesInput) Validate() error { |
| if err := i.Invoice.Validate(); err != nil { |
| return fmt.Errorf("id: %w", err) |
| } |
|
|
| if len(i.Issues) == 0 { |
| return errors.New("issues are required") |
| } |
|
|
| return nil |
| } |
|
|
| type InvoiceTriggerValidationInput struct { |
| |
| |
| Operation StandardInvoiceOperation |
| Errors []error |
| } |
|
|
| func (i InvoiceTriggerValidationInput) Validate() error { |
| if err := i.Operation.Validate(); err != nil { |
| return fmt.Errorf("operation: %w", err) |
| } |
|
|
| if len(i.Errors) == 0 { |
| return errors.New("validation errors are required") |
| } |
|
|
| return nil |
| } |
|
|
| type InvoiceTriggerInput struct { |
| Invoice InvoiceID |
| |
| Trigger InvoiceTrigger |
|
|
| ValidationErrors *InvoiceTriggerValidationInput |
| } |
|
|
| func (i InvoiceTriggerInput) Validate() error { |
| if err := i.Invoice.Validate(); err != nil { |
| return fmt.Errorf("id: %w", err) |
| } |
|
|
| if i.Trigger == "" { |
| return errors.New("trigger is required") |
| } |
|
|
| if i.ValidationErrors != nil { |
| if err := i.ValidationErrors.Validate(); err != nil { |
| return fmt.Errorf("validation errors: %w", err) |
| } |
| } |
|
|
| return nil |
| } |
|
|
| type InvoiceTriggerServiceInput struct { |
| InvoiceTriggerInput |
|
|
| |
| AppType app.AppType |
| |
| Capability app.CapabilityType |
| } |
|
|
| func (i InvoiceTriggerServiceInput) Validate() error { |
| if err := i.InvoiceTriggerInput.Validate(); err != nil { |
| return fmt.Errorf("trigger: %w", err) |
| } |
|
|
| if i.AppType == "" { |
| return errors.New("app type is required") |
| } |
|
|
| if i.Capability == "" { |
| return errors.New("capability is required") |
| } |
|
|
| return nil |
| } |
|
|
| type UpdateInvoiceFieldsInput struct { |
| Invoice InvoiceID |
| SentToCustomerAt mo.Option[*time.Time] |
| } |
|
|
| func (i UpdateInvoiceFieldsInput) Validate() error { |
| if err := i.Invoice.Validate(); err != nil { |
| return fmt.Errorf("id: %w", err) |
| } |
|
|
| return nil |
| } |
|
|
| type RecalculateGatheringInvoicesInput = customer.CustomerID |
|
|
| type StandardInvoiceExpand string |
|
|
| const ( |
| StandardInvoiceExpandLines StandardInvoiceExpand = "lines" |
| StandardInvoiceExpandDeletedLines StandardInvoiceExpand = "deletedLines" |
| ) |
|
|
| func (e StandardInvoiceExpand) Values() []StandardInvoiceExpand { |
| return []StandardInvoiceExpand{ |
| StandardInvoiceExpandLines, |
| StandardInvoiceExpandDeletedLines, |
| } |
| } |
|
|
| type StandardInvoiceExpands = expand.Expand[StandardInvoiceExpand] |
|
|
| var StandardInvoiceExpandAll = StandardInvoiceExpands{ |
| StandardInvoiceExpandLines, |
| |
| } |
|
|
| |
| |
| |
| |
| type InvoicePendingAdvancementFilter struct { |
| AsOf time.Time |
| MinimumAge time.Duration |
| } |
|
|
| var _ models.Validator = (*InvoicePendingAdvancementFilter)(nil) |
|
|
| func (f InvoicePendingAdvancementFilter) Validate() error { |
| var errs []error |
|
|
| if f.AsOf.IsZero() { |
| errs = append(errs, errors.New("as of is required")) |
| } |
|
|
| if f.MinimumAge < 0 { |
| errs = append(errs, errors.New("minimum age cannot be negative")) |
| } |
|
|
| return models.NewNillableGenericValidationError(errors.Join(errs...)) |
| } |
|
|
| |
| |
| type ListStandardInvoicesPendingAdvancementInput struct { |
| Namespaces []string |
| IDs []string |
| AsOf time.Time |
| MinimumAge time.Duration |
| } |
|
|
| var _ models.Validator = (*ListStandardInvoicesPendingAdvancementInput)(nil) |
|
|
| func (i ListStandardInvoicesPendingAdvancementInput) Validate() error { |
| return InvoicePendingAdvancementFilter{ |
| AsOf: i.AsOf, |
| MinimumAge: i.MinimumAge, |
| }.Validate() |
| } |
|
|
| |
| |
| |
| type CountStandardInvoicesPendingAdvancementInput struct { |
| Filter InvoicePendingAdvancementFilter |
| ExcludedNamespaces []string |
| } |
|
|
| var _ models.Validator = (*CountStandardInvoicesPendingAdvancementInput)(nil) |
|
|
| func (i CountStandardInvoicesPendingAdvancementInput) Validate() error { |
| return i.Filter.Validate() |
| } |
|
|
| type GetStandardInvoiceByIdInput struct { |
| Invoice InvoiceID |
| Expand StandardInvoiceExpands |
| } |
|
|
| func (i GetStandardInvoiceByIdInput) Validate() error { |
| var errs []error |
|
|
| if err := i.Invoice.Validate(); err != nil { |
| errs = append(errs, fmt.Errorf("id: %w", err)) |
| } |
|
|
| if err := i.Expand.Validate(); err != nil { |
| errs = append(errs, fmt.Errorf("expand: %w", err)) |
| } |
|
|
| return errors.Join(errs...) |
| } |
|
|
| type ListStandardInvoicesInput struct { |
| pagination.Page |
|
|
| Namespaces []string |
| IDs []string |
| Statuses []string |
| ExtendedStatuses []StandardInvoiceStatus |
| HasAvailableAction []InvoiceAvailableActionsFilter |
|
|
| Expand StandardInvoiceExpands |
| ExternalIDs *ListInvoicesExternalIDFilter |
| DraftUntilLTE *time.Time |
| CollectionAtLTE *time.Time |
|
|
| IncludeDeleted bool |
| } |
|
|
| func (i ListStandardInvoicesInput) Validate() error { |
| var errs []error |
|
|
| |
| |
|
|
| |
|
|
| if err := i.Expand.Validate(); err != nil { |
| errs = append(errs, fmt.Errorf("expand: %w", err)) |
| } |
|
|
| if i.ExternalIDs != nil { |
| if err := i.ExternalIDs.Validate(); err != nil { |
| errs = append(errs, fmt.Errorf("externalIDs: %w", err)) |
| } |
| } |
|
|
| return errors.Join(errs...) |
| } |
|
|
| type ListStandardInvoicesResponse = pagination.Result[StandardInvoice] |
|
|
| type CreateStandardInvoiceFromGatheringLinesInput struct { |
| Customer customer.CustomerID |
| Currency currencyx.Code |
| Description *string |
|
|
| Lines GatheringLines |
| PostCreationCalculationHook PostCreationCalculationHook |
| ForceAsyncAdvance bool |
| } |
|
|
| type ( |
| PostCreationCalculationHook func(StandardInvoice, StandardLine) (LineMutators, error) |
| LineMutator func(*StandardLine) error |
| LineMutators = []LineMutator |
| ) |
|
|
| func (i CreateStandardInvoiceFromGatheringLinesInput) Validate() error { |
| var errs []error |
|
|
| if err := i.Customer.Validate(); err != nil { |
| errs = append(errs, fmt.Errorf("customer: %w", err)) |
| } |
|
|
| if err := i.Currency.Validate(); err != nil { |
| errs = append(errs, fmt.Errorf("currency: %w", err)) |
| } |
|
|
| if len(i.Lines) == 0 { |
| errs = append(errs, fmt.Errorf("lines are required")) |
| } |
|
|
| for _, line := range i.Lines { |
| if err := line.Validate(); err != nil { |
| errs = append(errs, fmt.Errorf("line[%s]: %w", line.ID, err)) |
| } |
|
|
| if line.Currency != i.Currency { |
| errs = append(errs, fmt.Errorf("line[%s]: currency[%s] is not equal to invoice currency[%s]", line.ID, line.Currency, i.Currency)) |
| } |
|
|
| if line.Namespace != i.Customer.Namespace { |
| errs = append(errs, fmt.Errorf("line[%s]: namespace[%s] is not equal to invoice namespace[%s]", line.ID, line.Namespace, i.Customer.Namespace)) |
| } |
| } |
|
|
| return errors.Join(errs...) |
| } |
|
|
| type ( |
| StandardInvoiceHook = models.ServiceHook[StandardInvoice] |
| StandardInvoiceHooks = models.ServiceHookRegistry[StandardInvoice] |
| ) |
|
|
| func NewSetCreditsAppliedOperation(creditsApplied CreditsApplied) LineMutator { |
| return func(line *StandardLine) error { |
| line.CreditsApplied = creditsApplied |
| return nil |
| } |
| } |
|
|