Skip to content
6 changes: 0 additions & 6 deletions api/v3/handlers/taxcodes/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ type (
DeleteTaxCodeHandler = httptransport.HandlerWithArgs[DeleteTaxCodeRequest, DeleteTaxCodeResponse, DeleteTaxCodeParams]
)

const deleteTaxCodeEnabled = false

// DeleteTaxCode returns a handler for deleting a tax code.
func (h *handler) DeleteTaxCode() DeleteTaxCodeHandler {
return httptransport.NewHandlerWithArgs(
Expand All @@ -37,10 +35,6 @@ func (h *handler) DeleteTaxCode() DeleteTaxCodeHandler {
}, nil
},
func(ctx context.Context, request DeleteTaxCodeRequest) (DeleteTaxCodeResponse, error) {
if !deleteTaxCodeEnabled {
return nil, apierrors.NewForbiddenErrorDetail(ctx, "deleting tax codes is not allowed")
}

err := h.service.DeleteTaxCode(ctx, taxcode.DeleteTaxCodeInput{
NamespacedID: models.NamespacedID{
Namespace: request.Namespace,
Expand Down
6 changes: 6 additions & 0 deletions app/common/productcatalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/openmeterio/openmeter/openmeter/productcatalog/planaddon"
planaddonadapter "github.com/openmeterio/openmeter/openmeter/productcatalog/planaddon/adapter"
planaddonservice "github.com/openmeterio/openmeter/openmeter/productcatalog/planaddon/service"
"github.com/openmeterio/openmeter/openmeter/productcatalog/taxcoderesolver"
"github.com/openmeterio/openmeter/openmeter/streaming"
"github.com/openmeterio/openmeter/openmeter/taxcode"
"github.com/openmeterio/openmeter/openmeter/watermill/eventbus"
Expand All @@ -49,6 +50,7 @@ var Cost = wire.NewSet(

var Plan = wire.NewSet(
NewPlanService,
NewTaxCodeResolver,
)

var Addon = wire.NewSet(
Expand All @@ -71,6 +73,8 @@ func NewFeatureConnector(

var NewFeatureResolver = featureresolver.New

var NewTaxCodeResolver = taxcoderesolver.New

func NewCostService(
featureConnector feature.FeatureConnector,
meterService meter.Service,
Expand All @@ -88,6 +92,7 @@ func NewPlanService(
logger *slog.Logger,
db *entdb.Client,
featureResolver productcatalog.FeatureResolver,
taxCodeResolver productcatalog.TaxCodeResolver,
taxCodeService taxcode.Service,
publisher eventbus.Publisher,
) (plan.Service, error) {
Expand All @@ -102,6 +107,7 @@ func NewPlanService(
return planservice.New(planservice.Config{
Adapter: adapter,
FeatureResolver: featureResolver,
TaxCodeResolver: taxCodeResolver,
TaxCode: taxCodeService,
Logger: logger.With("subsystem", "productcatalog.plan"),
Publisher: publisher,
Expand Down
25 changes: 25 additions & 0 deletions app/common/taxcode.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package common

import (
"fmt"
"log/slog"
"strings"

Expand All @@ -10,9 +11,11 @@ import (
"github.com/openmeterio/openmeter/app/config"
"github.com/openmeterio/openmeter/openmeter/app"
entdb "github.com/openmeterio/openmeter/openmeter/ent/db"
"github.com/openmeterio/openmeter/openmeter/productcatalog/plan"
"github.com/openmeterio/openmeter/openmeter/taxcode"
taxcodeadapter "github.com/openmeterio/openmeter/openmeter/taxcode/adapter"
taxcodeservice "github.com/openmeterio/openmeter/openmeter/taxcode/service"
taxcodehooks "github.com/openmeterio/openmeter/openmeter/taxcode/service/hooks"
)

var TaxCode = wire.NewSet(
Expand Down Expand Up @@ -42,6 +45,28 @@ func NewTaxCodeService(
})
}

// TaxCodePlanHook prevents deleting tax codes that are still referenced by plans.
type TaxCodePlanHook taxcodehooks.PlanHook

// NewTaxCodePlanServiceHook builds the plan-reference hook and registers it
// on the tax code service. It depends on both the plan and tax code services so wire constructs
// it only after both exist, avoiding a construction cycle (plan already depends on tax code).
func NewTaxCodePlanServiceHook(
planService plan.Service,
taxCodeService taxcode.Service,
) (TaxCodePlanHook, error) {
h, err := taxcodehooks.NewPlanHook(taxcodehooks.PlanHookConfig{
PlanService: planService,
})
if err != nil {
return nil, fmt.Errorf("failed to create tax code plan hook: %w", err)
}

taxCodeService.RegisterHooks(h)

return h, nil
}

func NewTaxCodeNamespaceHandler(
logger *slog.Logger,
service taxcode.Service,
Expand Down
14 changes: 13 additions & 1 deletion cmd/billing-worker/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion cmd/jobs/internal/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cmd/server/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type Application struct {
StreamingConnector streaming.Connector
TaxCodeNamespaceHandler *taxcode.NamespaceHandler
TaxCodeService taxcode.Service
TaxCodePlanHook common.TaxCodePlanHook
TelemetryServer common.TelemetryServer
TerminationChecker *common.TerminationChecker
RuntimeMetricsCollector common.RuntimeMetricsCollector
Expand Down Expand Up @@ -148,6 +149,7 @@ func initializeApplication(ctx context.Context, conf config.Configuration) (Appl
common.Server,
common.TaxCode,
common.TaxCodeNamespaceHandler,
common.NewTaxCodePlanServiceHook,
common.Subscription,
common.Lockr,
common.Secret,
Expand Down
28 changes: 27 additions & 1 deletion cmd/server/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions openmeter/billing/service/invoiceupdate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,8 @@ func (s *invoiceUpdateTaxCodeService) UpsertOrganizationDefaultTaxCodes(context.
return taxcode.OrganizationDefaultTaxCodes{}, errors.New("UpsertOrganizationDefaultTaxCodes is not supported in this test")
}

func (s *invoiceUpdateTaxCodeService) RegisterHooks(...models.ServiceHook[taxcode.TaxCode]) {}

type preallocatingInvoiceLineAdapter struct {
billing.Adapter
}
Expand Down
10 changes: 10 additions & 0 deletions openmeter/productcatalog/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ var ErrRateCardFeatureArchived = models.NewValidationIssue(
commonhttp.WithHTTPStatusCodeAttribute(http.StatusBadRequest),
)

const ErrCodeRateCardTaxCodeNotFound models.ErrorCode = "rate_card_tax_code_not_found"

var ErrRateCardTaxCodeNotFound = models.NewValidationIssue(
ErrCodeRateCardTaxCodeNotFound,
"tax code not found",
models.WithFieldString("taxConfig"),
models.WithCriticalSeverity(),
commonhttp.WithHTTPStatusCodeAttribute(http.StatusBadRequest),
)

const ErrCodeRateCardFeatureMismatch models.ErrorCode = "rate_card_feature_mismatch"

var ErrRateCardFeatureMismatch = models.NewValidationIssue(
Expand Down
21 changes: 21 additions & 0 deletions openmeter/productcatalog/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,24 @@ func ValidatePlanWithFeatures(ctx context.Context, resolver NamespacedFeatureRes
return errors.Join(errs...)
}
}

func ValidatePlanWithTaxCodes(ctx context.Context, resolver NamespacedTaxCodeResolver) models.ValidatorFunc[Plan] {
return func(p Plan) error {
var errs []error

for _, phase := range p.Phases {
phaseFieldSelector := models.NewFieldSelectorGroup(
models.NewFieldSelector("phases").
WithExpression(
models.NewFieldAttrValue("key", phase.Key),
),
)

if err := ValidateRateCardsWithTaxCodes(ctx, resolver)(phase.RateCards); err != nil {
errs = append(errs, models.ErrorWithFieldPrefix(phaseFieldSelector, err))
}
}

return errors.Join(errs...)
}
}
Loading
Loading