Skip to content

Commit 2d04880

Browse files
committed
feat: bulk invocie edit [WIP]
1 parent f440848 commit 2d04880

File tree

6 files changed

+137
-22
lines changed

6 files changed

+137
-22
lines changed

openmeter/billing/adapter/invoice.go

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/openmeterio/openmeter/api"
1414
"github.com/openmeterio/openmeter/openmeter/app"
1515
"github.com/openmeterio/openmeter/openmeter/billing"
16+
"github.com/openmeterio/openmeter/openmeter/customer"
1617
"github.com/openmeterio/openmeter/openmeter/ent/db"
1718
"github.com/openmeterio/openmeter/openmeter/ent/db/billinginvoice"
1819
"github.com/openmeterio/openmeter/openmeter/ent/db/billinginvoiceline"
@@ -606,33 +607,56 @@ func (a *adapter) UpdateInvoice(ctx context.Context, in billing.UpdateInvoiceAda
606607

607608
func (a *adapter) GetInvoiceOwnership(ctx context.Context, in billing.GetInvoiceOwnershipAdapterInput) (billing.GetOwnershipAdapterResponse, error) {
608609
if err := in.Validate(); err != nil {
609-
return billing.GetOwnershipAdapterResponse{}, billing.ValidationError{
610+
return nil, billing.ValidationError{
610611
Err: err,
611612
}
612613
}
613614

614615
return entutils.TransactingRepo(ctx, a, func(ctx context.Context, tx *adapter) (billing.GetOwnershipAdapterResponse, error) {
615-
dbInvoice, err := tx.db.BillingInvoice.Query().
616-
Where(billinginvoice.ID(in.ID)).
617-
Where(billinginvoice.Namespace(in.Namespace)).
618-
First(ctx)
616+
dbInvoices, err := tx.db.BillingInvoice.Query().
617+
Where(
618+
billinginvoice.IDIn(
619+
lo.Map(
620+
in.InvoiceIDs,
621+
func(invoiceID billing.InvoiceID, _ int) string {
622+
return invoiceID.ID
623+
},
624+
)...,
625+
),
626+
).
627+
All(ctx)
619628
if err != nil {
620-
if db.IsNotFound(err) {
621-
return billing.GetOwnershipAdapterResponse{}, billing.NotFoundError{
622-
Entity: billing.EntityInvoice,
623-
ID: in.ID,
624-
Err: err,
629+
return nil, err
630+
}
631+
632+
invoiceToCustomerID := lo.SliceToMap(dbInvoices, func(dbInvoice *db.BillingInvoice) (billing.InvoiceID, customer.CustomerID) {
633+
return billing.InvoiceID{
634+
Namespace: dbInvoice.Namespace,
635+
ID: dbInvoice.ID,
636+
}, customer.CustomerID{
637+
Namespace: dbInvoice.Namespace,
638+
ID: dbInvoice.CustomerID,
625639
}
640+
})
641+
642+
// Let's validate if we got all the invoices (and most importantly look up invoices with
643+
// namespaceID, to prevent looking up invoices with different than expected namespace ID)
644+
var notFoundErrs []error
645+
for _, invoiceID := range in.InvoiceIDs {
646+
if _, found := invoiceToCustomerID[invoiceID]; !found {
647+
notFoundErrs = append(notFoundErrs, billing.NotFoundError{
648+
Entity: billing.EntityInvoice,
649+
ID: invoiceID.ID,
650+
Err: fmt.Errorf("invoice not found: %s", invoiceID.ID),
651+
})
626652
}
653+
}
627654

628-
return billing.GetOwnershipAdapterResponse{}, err
655+
if len(notFoundErrs) > 0 {
656+
return nil, errors.Join(notFoundErrs...)
629657
}
630658

631-
return billing.GetOwnershipAdapterResponse{
632-
Namespace: dbInvoice.Namespace,
633-
InvoiceID: dbInvoice.ID,
634-
CustomerID: dbInvoice.CustomerID,
635-
}, nil
659+
return invoiceToCustomerID, nil
636660
})
637661
}
638662

openmeter/billing/invoice.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -867,14 +867,18 @@ type (
867867

868868
type UpdateInvoiceAdapterInput = Invoice
869869

870-
type GetInvoiceOwnershipAdapterInput = InvoiceID
870+
type GetInvoiceOwnershipAdapterInput struct {
871+
InvoiceIDs []InvoiceID
872+
}
871873

872-
type GetOwnershipAdapterResponse struct {
873-
Namespace string
874-
InvoiceID string
875-
CustomerID string
874+
func (i GetInvoiceOwnershipAdapterInput) Validate() error {
875+
if len(i.InvoiceIDs) == 0 {
876+
return errors.New("invoice IDs are required")
877+
}
876878
}
877879

880+
type GetOwnershipAdapterResponse map[InvoiceID]customer.CustomerID
881+
878882
type DeleteInvoiceInput = InvoiceID
879883

880884
type UpdateInvoiceLinesInternalInput struct {
@@ -1061,3 +1065,19 @@ func (i UpdateInvoiceFieldsInput) Validate() error {
10611065
}
10621066

10631067
type RecalculateGatheringInvoicesInput = customer.CustomerID
1068+
1069+
type StandardImmutableInvoiceUpdate struct {
1070+
UpsertValidationIssues mo.Option[ValidationIssues]
1071+
}
1072+
1073+
type BulkUpdateInvoicesInput struct {
1074+
IncludeDeletedLines bool
1075+
Invoices []InvoiceID
1076+
GatheringInvoiceEditFunction func(*Invoice) error
1077+
StandardMutableInvoiceEditFunction func(*Invoice) error
1078+
StandardImmutableInvoiceEditFunction func(*Invoice) (StandardImmutableInvoiceUpdate, error)
1079+
}
1080+
1081+
type BulkUpdateInvoicesResult struct {
1082+
InvoicesByID map[InvoiceID]Invoice
1083+
}

openmeter/billing/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ type InvoiceService interface {
7070
DeleteInvoice(ctx context.Context, input DeleteInvoiceInput) error
7171
// UpdateInvoice updates an invoice as a whole
7272
UpdateInvoice(ctx context.Context, input UpdateInvoiceInput) (Invoice, error)
73+
BulkUpdateInvoices(ctx context.Context, input BulkUpdateInvoicesInput) (BulkUpdateInvoicesResult, error)
7374

7475
// SimulateInvoice generates an invoice based on the provided input, but does not persist it
7576
// can be used to execute the invoice generation logic without actually creating an invoice in the database

openmeter/billing/service/invoice.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,26 @@ func (s *Service) UpdateInvoice(ctx context.Context, input billing.UpdateInvoice
977977
)
978978
}
979979

980+
func (s *Service) BulkUpdateInvoices(ctx context.Context, input billing.BulkUpdateInvoicesInput) (billing.BulkUpdateInvoicesResult, error) {
981+
if err := input.Validate(); err != nil {
982+
return billing.BulkUpdateInvoicesResult{}, billing.ValidationError{
983+
Err: err,
984+
}
985+
}
986+
987+
return transaction.Run(ctx, s.adapter, func(ctx context.Context) (billing.BulkUpdateInvoicesResult, error) {
988+
invoiceToCustomerID, err := s.adapter.GetInvoiceOwnership(ctx, billing.GetInvoiceOwnershipAdapterInput{
989+
InvoiceIDs: input.Invoices,
990+
})
991+
if err != nil {
992+
return billing.BulkUpdateInvoicesResult{}, fmt.Errorf("getting invoice ownership: %w", err)
993+
}
994+
})
995+
996+
return transcationForInvoiceManipulation(ctx, s, invoicesByCustomer, func(ctx context.Context) (billing.BulkUpdateInvoicesResult, error) {
997+
})
998+
}
999+
9801000
// updateInvoice calls the adapter to update the invoice and returns the updated invoice including any expands that are
9811001
// the responsibility of the service
9821002
func (s Service) updateInvoice(ctx context.Context, in billing.UpdateInvoiceAdapterInput) (billing.Invoice, error) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package billingservice
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/openmeterio/openmeter/openmeter/billing"
8+
"github.com/openmeterio/openmeter/openmeter/customer"
9+
"github.com/openmeterio/openmeter/pkg/framework/transaction"
10+
)
11+
12+
func (s *Service) BulkUpdateInvoices(ctx context.Context, input billing.BulkUpdateInvoicesInput) (billing.BulkUpdateInvoicesResult, error) {
13+
if err := input.Validate(); err != nil {
14+
return billing.BulkUpdateInvoicesResult{}, billing.ValidationError{
15+
Err: err,
16+
}
17+
}
18+
19+
return transaction.Run(ctx, s.adapter, func(ctx context.Context) (billing.BulkUpdateInvoicesResult, error) {
20+
invoiceToCustomerID, err := s.adapter.GetInvoiceOwnership(ctx, billing.GetInvoiceOwnershipAdapterInput{
21+
InvoiceIDs: input.Invoices,
22+
})
23+
if err != nil {
24+
return billing.BulkUpdateInvoicesResult{}, fmt.Errorf("getting invoice ownership: %w", err)
25+
}
26+
27+
invoiceIDsByCustomerID := map[customer.CustomerID][]billing.InvoiceID{}
28+
for invoiceID, customerID := range invoiceToCustomerID {
29+
invoiceIDsByCustomerID[customerID] = append(invoiceIDsByCustomerID[customerID], invoiceID)
30+
}
31+
32+
for customerID, invoiceIDs := range invoiceIDsByCustomerID {
33+
xxx, err := transcationForInvoiceManipulation(ctx, s, customerID, func(ctx context.Context) (billing.BulkUpdateInvoicesResult, error) {
34+
invoices := make([]*billing.Invoice, 0, len(invoiceIDs))
35+
for _, invoiceID := range invoiceIDs {
36+
invoice, err := s.GetInvoiceByID(ctx, billing.GetInvoiceByIdInput{
37+
Invoice: invoiceID,
38+
Expand: billing.InvoiceExpandAll.
39+
SetDeletedLines(input.IncludeDeletedLines),
40+
})
41+
if err != nil {
42+
return billing.BulkUpdateInvoicesResult{}, fmt.Errorf("getting invoice: %w", err)
43+
}
44+
})
45+
if err != nil {
46+
return billing.BulkUpdateInvoicesResult{}, fmt.Errorf("updating invoices: %w", err)
47+
}
48+
}
49+
})
50+
}

openmeter/billing/service/lineservice/linebase.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func (l lineBase) Validate(ctx context.Context, invoice *billing.Invoice) error
109109
// Expanding the split lines are mandatory for the lineservice to work properly.
110110
if l.line.SplitLineGroupID != nil && l.line.SplitLineHierarchy == nil {
111111
return billing.ValidationError{
112-
Err: fmt.Errorf("split line group[%s] has no hierarchy expanded hierarchy", *l.line.SplitLineGroupID),
112+
Err: fmt.Errorf("split line group[%s] has no expanded hierarchy, while being part of a split line group", *l.line.SplitLineGroupID),
113113
}
114114
}
115115

0 commit comments

Comments
 (0)