package transactions import ( "context" "fmt" "time" "github.com/alpacahq/alpacadecimal" "github.com/openmeterio/openmeter/openmeter/ledger" "github.com/openmeterio/openmeter/pkg/models" ) const legacyAnnotationTransactionTemplateName = "ledger.transaction.template_name" type CorrectionInput struct { At time.Time Amount alpacadecimal.Decimal OriginalTransaction ledger.Transaction OriginalGroup ledger.TransactionGroup } type CorrectionScope = CorrectionInput func (i CorrectionScope) Validate() error { if i.At.IsZero() { return fmt.Errorf("at is required") } if err := ledger.ValidateTransactionAmount(i.Amount); err != nil { return fmt.Errorf("amount: %w", err) } if i.OriginalTransaction == nil { return fmt.Errorf("original transaction is required") } return nil } func CorrectTransaction( _ context.Context, deps ResolverDependencies, scope CorrectionScope, ) ([]ledger.TransactionInput, error) { if err := scope.Validate(); err != nil { return nil, fmt.Errorf("validate correction input: %w", err) } direction, err := ledger.TransactionDirectionFromAnnotations(scope.OriginalTransaction.Annotations()) if err != nil { return nil, fmt.Errorf("transaction direction: %w", err) } if direction == ledger.TransactionDirectionCorrection { return nil, fmt.Errorf("cannot correct a correction transaction") } template, err := transactionTemplateFromAnnotations(scope.OriginalTransaction.Annotations()) if err != nil { return nil, fmt.Errorf("transaction template: %w", err) } outputs, err := correctTemplate(scope, template) if err != nil { return nil, err } annotated := make([]ledger.TransactionInput, 0, len(outputs)) for _, output := range outputs { annotatedOutput, err := annotateTemplateTransaction(output, template, ledger.TransactionDirectionCorrection) if err != nil { return nil, err } annotated = append(annotated, annotatedOutput) } return annotated, nil } func transactionTemplateFromAnnotations(annotations models.Annotations) (TransactionTemplate, error) { if _, ok := annotations[ledger.AnnotationTransactionTemplateCode]; ok { code, err := ledger.TransactionTemplateCodeFromAnnotations(annotations) if err != nil { return nil, fmt.Errorf("code: %w", err) } return transactionTemplateByCode(code) } name, err := transactionTemplateNameFromAnnotations(annotations) if err != nil { return nil, fmt.Errorf("name: %w", err) } return transactionTemplateByLegacyName(name) } func transactionTemplateNameFromAnnotations(annotations models.Annotations) (string, error) { raw, ok := annotations[legacyAnnotationTransactionTemplateName] if !ok { return "", fmt.Errorf("transaction template name annotation is required") } templateName, ok := raw.(string) if !ok || templateName == "" { return "", fmt.Errorf("transaction template name annotation is invalid") } return templateName, nil } func correctTemplate(scope CorrectionScope, template TransactionTemplate) ([]ledger.TransactionInput, error) { switch typ := any(template).(type) { case CustomerTransactionTemplate: return typ.correct(scope) case OrgTransactionTemplate: return typ.correct(scope) default: return nil, fmt.Errorf("unsupported correction template type %T", template) } } func templateCorrectionNotImplemented(template string) error { return fmt.Errorf("%s correction is not implemented", template) }