Skip to content

Commit 9cea4ab

Browse files
authored
fix: invoice edit tax code diff (#4621)
1 parent 32ac526 commit 9cea4ab

26 files changed

Lines changed: 822 additions & 48 deletions

app/config/taxcode.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func (c TaxCodeConfiguration) Validate() error {
9898
func ConfigureTaxCode(v *viper.Viper) {
9999
v.SetDefault("taxcode.seeds", []map[string]any{
100100
{
101-
"key": "default",
101+
"key": taxcode.ProviderDefaultTaxCodeKey,
102102
"name": "Provider default",
103103
"defaultInvoicing": true,
104104
},

e2e/billinginvoice_discount_edit_test.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
"github.com/openmeterio/openmeter/pkg/models"
1818
)
1919

20-
func TestInvoiceEditFlatFeeDiscountCreditThenInvoice(t *testing.T) {
20+
func TestInvoiceEditFlatFeeDiscountCreditThenInvoiceWithDefaultedTaxCode(t *testing.T) {
2121
ctx := t.Context()
2222
client := initClient(t)
2323
prefix := "discount_edit_" + strings.ToLower(ulid.Make().String())
@@ -111,6 +111,9 @@ func TestInvoiceEditFlatFeeDiscountCreditThenInvoice(t *testing.T) {
111111
PaymentTerm: lo.ToPtr(api.PricePaymentTermInAdvance),
112112
},
113113
BillingCadence: lo.ToPtr("P1M"),
114+
TaxConfig: &api.TaxConfig{
115+
Behavior: lo.ToPtr(api.TaxBehaviorExclusive),
116+
},
114117
}))
115118

116119
planResp, err := client.CreatePlanWithResponse(ctx, api.PlanCreate{
@@ -156,6 +159,7 @@ func TestInvoiceEditFlatFeeDiscountCreditThenInvoice(t *testing.T) {
156159
require.NoError(t, err)
157160
require.Equal(t, http.StatusCreated, subscriptionResp.StatusCode(), "create subscription: %s", string(subscriptionResp.Body))
158161
require.NotNil(t, subscriptionResp.JSON201)
162+
t.Logf("created customer=%s subscription=%s", customerResp.JSON201.Id, subscriptionResp.JSON201.Id)
159163

160164
customers := api.InvoiceListParamsCustomers{customerResp.JSON201.Id}
161165
invoiceExpand := api.InvoiceListParamsExpand{api.InvoiceExpandLines, api.InvoiceExpandWorkflowApps}
@@ -214,10 +218,13 @@ func TestInvoiceEditFlatFeeDiscountCreditThenInvoice(t *testing.T) {
214218
require.NotNil(t, invoice.Lines)
215219

216220
lineIdx := slices.IndexFunc(*invoice.Lines, func(line api.InvoiceLine) bool {
217-
return line.Name == lineName
221+
return line.Name == lineName && line.Subscription != nil
218222
})
219223
require.NotEqual(t, -1, lineIdx, "invoice line %q", lineName)
220224
lineID := (*invoice.Lines)[lineIdx].Id
225+
require.NotNil(t, (*invoice.Lines)[lineIdx].TaxConfig, "line[%s] tax config", lineID)
226+
require.NotNil(t, (*invoice.Lines)[lineIdx].TaxConfig.TaxCodeId, "line[%s] defaulted tax code", lineID)
227+
t.Logf("editing invoice=%s line=%s default_tax_code=%s", invoice.Id, lineID, *(*invoice.Lines)[lineIdx].TaxConfig.TaxCodeId)
221228

222229
replacementLines := make([]api.InvoiceLineReplaceUpdate, 0, len(*invoice.Lines))
223230
for _, line := range *invoice.Lines {
@@ -235,6 +242,10 @@ func TestInvoiceEditFlatFeeDiscountCreditThenInvoice(t *testing.T) {
235242
}
236243
if line.Id == lineID {
237244
require.NotNil(t, lineUpdate.RateCard, "line[%s] rate card", line.Id)
245+
require.NotNil(t, lineUpdate.RateCard.TaxConfig, "line[%s] rate card tax config", line.Id)
246+
lineUpdate.RateCard.TaxConfig.TaxCodeId = nil
247+
require.NotNil(t, lineUpdate.TaxConfig, "line[%s] tax config", line.Id)
248+
lineUpdate.TaxConfig.TaxCodeId = nil
238249
lineUpdate.RateCard.Discounts = &api.BillingDiscounts{
239250
Percentage: &api.BillingDiscountPercentage{
240251
Percentage: models.NewPercentage(50),

openmeter/billing/httpdriver/invoiceline.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -787,13 +787,7 @@ func mergeStandardLineFromInvoiceLineReplaceUpdate(existing *billing.StandardLin
787787
existing.Period.To = line.Period.To.Truncate(streaming.MinimumWindowSizeDuration)
788788
existing.InvoiceAt = line.InvoiceAt.Truncate(streaming.MinimumWindowSizeDuration)
789789

790-
taxConfig := billing.FromProductCatalog(rateCardParsed.TaxConfig)
791-
if existing.TaxConfig != nil && existing.TaxConfig.ToProductCatalog().Equal(rateCardParsed.TaxConfig) {
792-
clonedTaxConfig := existing.TaxConfig.Clone()
793-
taxConfig = &clonedTaxConfig
794-
}
795-
796-
existing.TaxConfig = taxConfig
790+
existing.TaxConfig = billing.FromProductCatalog(rateCardParsed.TaxConfig)
797791
existing.UsageBased.Price = rateCardParsed.Price
798792
existing.UsageBased.FeatureKey = rateCardParsed.FeatureKey
799793
existing.RateCardDiscounts = rateCardParsed.Discounts

openmeter/billing/httpdriver/invoiceline_test.go

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/openmeterio/openmeter/pkg/timeutil"
1717
)
1818

19-
func TestMergeStandardLineFromInvoiceLineReplaceUpdatePreservesResolvedTaxCode(t *testing.T) {
19+
func TestMergeStandardLineFromInvoiceLineReplaceUpdateOverwritesTaxConfig(t *testing.T) {
2020
price := api.RateCardUsageBasedPrice{}
2121
require.NoError(t, price.FromUnitPriceWithCommitments(api.UnitPriceWithCommitments{
2222
Amount: "1",
@@ -84,8 +84,74 @@ func TestMergeStandardLineFromInvoiceLineReplaceUpdatePreservesResolvedTaxCode(t
8484
})
8585
require.NoError(t, err)
8686
require.NotNil(t, mergedLine.TaxConfig)
87-
require.NotNil(t, mergedLine.TaxConfig.TaxCode)
88-
require.Equal(t, taxCodeID, mergedLine.TaxConfig.TaxCode.ID)
87+
require.Equal(t, taxCodeID, *mergedLine.TaxConfig.TaxCodeID)
88+
require.Nil(t, mergedLine.TaxConfig.TaxCode)
89+
}
90+
91+
func TestMergeStandardLineFromInvoiceLineReplaceUpdateLeavesProviderDefaultOmissionForInvoiceNormalization(t *testing.T) {
92+
period := invoiceLineTestPeriod()
93+
line := standardInvoiceLineForMergeTest(t, period)
94+
95+
defaultTaxCodeID := "default-tax-code-id"
96+
productCatalogTaxBehavior := productcatalog.ExclusiveTaxBehavior
97+
line.TaxConfig = &billing.TaxConfig{
98+
TaxConfig: productcatalog.TaxConfig{
99+
Behavior: &productCatalogTaxBehavior,
100+
TaxCodeID: &defaultTaxCodeID,
101+
},
102+
TaxCode: &taxcode.TaxCode{
103+
NamespacedID: models.NamespacedID{
104+
Namespace: "ns",
105+
ID: defaultTaxCodeID,
106+
},
107+
Name: "Default Tax Code",
108+
},
109+
}
110+
111+
taxBehavior := api.TaxBehaviorExclusive
112+
mergedLine, err := mergeStandardLineFromInvoiceLineReplaceUpdate(line, invoiceLineReplaceUpdateForMergeTest(t, period, line.UsageBased.FeatureKey, "1", func(update *api.InvoiceLineReplaceUpdate) {
113+
update.RateCard.TaxConfig = &api.TaxConfig{
114+
Behavior: &taxBehavior,
115+
}
116+
update.TaxConfig = update.RateCard.TaxConfig
117+
}))
118+
require.NoError(t, err)
119+
require.NotNil(t, mergedLine.TaxConfig)
120+
require.Nil(t, mergedLine.TaxConfig.TaxCodeID)
121+
require.Nil(t, mergedLine.TaxConfig.TaxCode)
122+
}
123+
124+
func TestMergeStandardLineFromInvoiceLineReplaceUpdateDoesNotPreserveExplicitTaxCodeWhenPayloadOmitsTaxCodeIdentity(t *testing.T) {
125+
period := invoiceLineTestPeriod()
126+
line := standardInvoiceLineForMergeTest(t, period)
127+
128+
explicitTaxCodeID := "explicit-tax-code-id"
129+
productCatalogTaxBehavior := productcatalog.ExclusiveTaxBehavior
130+
line.TaxConfig = &billing.TaxConfig{
131+
TaxConfig: productcatalog.TaxConfig{
132+
Behavior: &productCatalogTaxBehavior,
133+
TaxCodeID: &explicitTaxCodeID,
134+
},
135+
TaxCode: &taxcode.TaxCode{
136+
NamespacedID: models.NamespacedID{
137+
Namespace: "ns",
138+
ID: explicitTaxCodeID,
139+
},
140+
Name: "Explicit Tax Code",
141+
},
142+
}
143+
144+
taxBehavior := api.TaxBehaviorExclusive
145+
mergedLine, err := mergeStandardLineFromInvoiceLineReplaceUpdate(line, invoiceLineReplaceUpdateForMergeTest(t, period, line.UsageBased.FeatureKey, "1", func(update *api.InvoiceLineReplaceUpdate) {
146+
update.RateCard.TaxConfig = &api.TaxConfig{
147+
Behavior: &taxBehavior,
148+
}
149+
update.TaxConfig = update.RateCard.TaxConfig
150+
}))
151+
require.NoError(t, err)
152+
require.NotNil(t, mergedLine.TaxConfig)
153+
require.Nil(t, mergedLine.TaxConfig.TaxCodeID)
154+
require.Nil(t, mergedLine.TaxConfig.TaxCode)
89155
}
90156

91157
func TestMergeStandardLineFromInvoiceLineReplaceUpdateDropsResolvedTaxCodeWhenTaxConfigChanges(t *testing.T) {

openmeter/billing/service/gatheringinvoice.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func (s *Service) UpdateGatheringInvoice(ctx context.Context, input billing.Upda
7272
return billing.GatheringInvoice{}, fmt.Errorf("normalizing lines: %w", err)
7373
}
7474

75-
lineDiff, err := s.diffMutableInvoiceLines(originalInvoice, invoice, input.ChangeSource)
75+
lineDiff, err := s.diffMutableInvoiceLines(ctx, originalInvoice, invoice, input.ChangeSource)
7676
if err != nil {
7777
return billing.GatheringInvoice{}, billing.ValidationError{
7878
Err: fmt.Errorf("collecting mutable invoice line changes: %w", err),

0 commit comments

Comments
 (0)