Skip to content

Commit

Permalink
feat: implement invoice lifecycle
Browse files Browse the repository at this point in the history
  • Loading branch information
turip committed Oct 29, 2024
1 parent eed5fcd commit a950b0b
Show file tree
Hide file tree
Showing 24 changed files with 1,157 additions and 94 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ require (
github.com/prometheus/client_golang v1.20.4
github.com/prometheus/client_model v0.6.1
github.com/prometheus/common v0.60.0
github.com/qmuntal/stateless v1.7.1
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
github.com/redis/go-redis/extra/redisotel/v9 v9.5.3
github.com/redis/go-redis/v9 v9.7.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1161,6 +1161,8 @@ github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 h1:sadMIs
github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
github.com/pusher/pusher-http-go v4.0.1+incompatible h1:4u6tomPG1WhHaST7Wi9mw83Y+MS/j2EplR2YmDh8Xp4=
github.com/pusher/pusher-http-go v4.0.1+incompatible/go.mod h1:XAv1fxRmVTI++2xsfofDhg7whapsLRG/gH/DXbF3a18=
github.com/qmuntal/stateless v1.7.1 h1:dI+BtLHq/nD6u46POkOINTDjY9uE33/4auEzfX3TWp0=
github.com/qmuntal/stateless v1.7.1/go.mod h1:n1HjRBM/cq4uCr3rfUjaMkgeGcd+ykAZwkjLje6jGBM=
github.com/quipo/dependencysolver v0.0.0-20170801134659-2b009cb4ddcc h1:hK577yxEJ2f5s8w2iy2KimZmgrdAUZUNftE1ESmg2/Q=
github.com/quipo/dependencysolver v0.0.0-20170801134659-2b009cb4ddcc/go.mod h1:OQt6Zo5B3Zs+C49xul8kcHo+fZ1mCLPvd0LFxiZ2DHc=
github.com/r3labs/diff/v3 v3.0.1 h1:CBKqf3XmNRHXKmdU7mZP1w7TV0pDyVCis1AUHtA4Xtg=
Expand Down
1 change: 1 addition & 0 deletions openmeter/billing/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ type InvoiceAdapter interface {
DeleteInvoices(ctx context.Context, input DeleteInvoicesAdapterInput) error
ListInvoices(ctx context.Context, input ListInvoicesInput) (ListInvoicesResponse, error)
AssociatedLineCounts(ctx context.Context, input AssociatedLineCountsAdapterInput) (AssociatedLineCountsAdapterResponse, error)
UpdateInvoice(ctx context.Context, input UpdateInvoiceAdapterInput) (billingentity.Invoice, error)
}
134 changes: 134 additions & 0 deletions openmeter/billing/adapter/invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,139 @@ func (r *adapter) AssociatedLineCounts(ctx context.Context, input billing.Associ
}, nil
}

func (r *adapter) validateUpdateRequest(req billing.UpdateInvoiceAdapterInput, existing *db.BillingInvoice) error {
if !existing.UpdatedAt.Equal(req.UpdatedAt) {
return billing.ConflictError{
Entity: billing.EntityInvoice,
ID: req.ID,
Err: fmt.Errorf("invoice has been updated since last read"),
}
}

if req.Currency != existing.Currency {
return billing.ValidationError{
Err: fmt.Errorf("currency cannot be changed"),
}
}

if req.Type != existing.Type {
return billing.ValidationError{
Err: fmt.Errorf("type cannot be changed"),
}
}

if req.Customer.CustomerID != existing.CustomerID {
return billing.ValidationError{
Err: fmt.Errorf("customer cannot be changed"),
}
}

return nil
}

func (r *adapter) UpdateInvoice(ctx context.Context, in billing.UpdateInvoiceAdapterInput) (billingentity.Invoice, error) {
existingInvoice, err := r.db.BillingInvoice.Query().
Where(billinginvoice.ID(in.ID)).
Where(billinginvoice.Namespace(in.Namespace)).
Only(ctx)
if err != nil {
return billingentity.Invoice{}, err
}

if err := r.validateUpdateRequest(in, existingInvoice); err != nil {
return billingentity.Invoice{}, err
}

updateQuery := r.db.BillingInvoice.UpdateOneID(in.ID).
Where(billinginvoice.Namespace(in.Namespace)).
SetMetadata(in.Metadata).
// Currency is immutable
SetStatus(in.Status).
// Type is immutable
SetOrClearNumber(in.Number).
SetOrClearDescription(in.Description).
SetOrClearDueAt(in.DueAt).
SetOrClearDraftUntil(in.DraftUntil).
SetOrClearIssuedAt(in.IssuedAt)

if in.Period != nil {
updateQuery = updateQuery.
SetPeriodStart(in.Period.Start).
SetPeriodEnd(in.Period.End)
} else {
updateQuery = updateQuery.
ClearPeriodStart().
ClearPeriodEnd()
}

// Supplier
updateQuery = updateQuery.
SetSupplierName(in.Supplier.Name).
SetOrClearSupplierAddressCountry(in.Supplier.Address.Country).
SetOrClearSupplierAddressPostalCode(in.Supplier.Address.PostalCode).
SetOrClearSupplierAddressCity(in.Supplier.Address.City).
SetOrClearSupplierAddressState(in.Supplier.Address.State).
SetOrClearSupplierAddressLine1(in.Supplier.Address.Line1).
SetOrClearSupplierAddressLine2(in.Supplier.Address.Line2).
SetOrClearSupplierAddressPhoneNumber(in.Supplier.Address.PhoneNumber)

// Customer
updateQuery = updateQuery.
// CustomerID is immutable
SetCustomerName(in.Customer.Name).
SetOrClearCustomerAddressCountry(in.Customer.BillingAddress.Country).
SetOrClearCustomerAddressPostalCode(in.Customer.BillingAddress.PostalCode).
SetOrClearCustomerAddressCity(in.Customer.BillingAddress.City).
SetOrClearCustomerAddressState(in.Customer.BillingAddress.State).
SetOrClearCustomerAddressLine1(in.Customer.BillingAddress.Line1).
SetOrClearCustomerAddressLine2(in.Customer.BillingAddress.Line2).
SetOrClearCustomerAddressPhoneNumber(in.Customer.BillingAddress.PhoneNumber).
SetOrClearCustomerTimezone(in.Customer.Timezone)

if in.Workflow != nil {
// Update the workflow config
// TODO: let's have a test for this
updateQuery = updateQuery.SetBillingWorkflowConfig(
mapWorkflowConfigToDB(in.Workflow.WorkflowConfig),
)
}

_, err = updateQuery.Save(ctx)
if err != nil {
return billingentity.Invoice{}, err
}

// TODO: store this as part of the invoice
expandFromIn := expandFromInvoice(in)
// We need to re-fetch the invoice to get the updated edges

return r.GetInvoiceById(ctx, billing.GetInvoiceByIdInput{
Invoice: billingentity.InvoiceID{
ID: in.ID,
Namespace: in.Namespace,
},
Expand: expandFromIn,
})
}

func expandFromInvoice(invoice billingentity.Invoice) billing.InvoiceExpand {
expand := billing.InvoiceExpand{}

if invoice.Workflow != nil {
expand.Workflow = true
}

if len(invoice.Lines) > 0 {
expand.Lines = true
}

if invoice.Workflow.Apps != nil {
expand.WorkflowApps = true
}

return expand
}

func mapInvoiceFromDB(invoice db.BillingInvoice, expand billing.InvoiceExpand) (billingentity.Invoice, error) {
res := billingentity.Invoice{
ID: invoice.ID,
Expand All @@ -327,6 +460,7 @@ func mapInvoiceFromDB(invoice db.BillingInvoice, expand billing.InvoiceExpand) (
Number: invoice.Number,
Description: invoice.Description,
DueAt: invoice.DueAt,
DraftUntil: invoice.DraftUntil,
Supplier: billingentity.SupplierContact{
Name: invoice.SupplierName,
Address: models.Address{
Expand Down
Loading

0 comments on commit a950b0b

Please sign in to comment.