Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensuring that all invoice discounts and charges are correctly inverted #258

Merged
merged 2 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions bill/invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,22 @@ func (inv *Invoice) ValidateWithContext(ctx context.Context) error {
}

// Invert effectively reverses the invoice by inverting the sign of all quantity
// or amount values.
func (inv *Invoice) Invert() {
// or amount values. Caution should be taken when using this method as
// advances will also be inverted, while payment terms will remain the same,
// which could be confusing if no further modifications are made.
// After inverting the invoice is recalculated and any differences will raise
// an error.
func (inv *Invoice) Invert() error {
payable := inv.Totals.Payable.Invert()

for _, row := range inv.Lines {
row.Quantity = row.Quantity.Invert()
for _, d := range row.Discounts {
d.Amount = d.Amount.Invert()
}
for _, c := range row.Charges {
c.Amount = c.Amount.Invert()
}
}
for _, row := range inv.Charges {
row.Amount = row.Amount.Invert()
Expand All @@ -198,7 +210,24 @@ func (inv *Invoice) Invert() {
for _, row := range inv.Outlays {
row.Amount = row.Amount.Invert()
}
if inv.Payment != nil {
for _, row := range inv.Payment.Advances {
row.Amount = row.Amount.Invert()
}
}
inv.Totals = nil

if err := inv.Calculate(); err != nil {
return err
}

// The following check tries to ensure that any future fields do not cause
// unexpected results.
if !payable.Equals(inv.Totals.Payable) {
return fmt.Errorf("inverted invoice totals do not match %s != %s", payable.String(), inv.Totals.Payable.String())
}

return nil
}

// Empty is a convenience method that will empty all the lines and
Expand Down
71 changes: 71 additions & 0 deletions bill/invoice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,77 @@ func TestCalculate(t *testing.T) {
assert.Equal(t, i.Totals.Due.String(), "675.00")
}

func TestCalculateInverted(t *testing.T) {
i := &bill.Invoice{
Code: "123TEST",
Tax: &bill.Tax{
PricesInclude: tax.CategoryVAT,
},
Supplier: &org.Party{
TaxID: &tax.Identity{
Country: l10n.ES,
Code: "B98602642",
},
},
Customer: &org.Party{
TaxID: &tax.Identity{
Country: l10n.ES,
Code: "54387763P",
},
},
IssueDate: cal.MakeDate(2022, 6, 13),
Lines: []*bill.Line{
{
Quantity: num.MakeAmount(10, 0),
Item: &org.Item{
Name: "Test Item",
Price: num.MakeAmount(10000, 2),
},
Taxes: tax.Set{
{
Category: "VAT",
Rate: "standard",
},
},
Discounts: []*bill.LineDiscount{
{
Reason: "Testing",
Amount: num.MakeAmount(10000, 2),
},
},
Charges: []*bill.LineCharge{
{
Reason: "Testing Charge",
Amount: num.MakeAmount(5000, 2),
},
},
},
},
Outlays: []*bill.Outlay{
{
Description: "Something paid in advance",
Amount: num.MakeAmount(1000, 2),
},
},
Payment: &bill.Payment{
Advances: []*pay.Advance{
{
Description: "Test Advance",
Amount: num.MakeAmount(25000, 2),
},
},
},
}

require.NoError(t, i.Calculate())
assert.Equal(t, i.Totals.Sum.String(), "950.00")
assert.Equal(t, i.Totals.Due.String(), "710.00")

require.NoError(t, i.Invert())
assert.Equal(t, i.Totals.Sum.String(), "-950.00")
assert.Equal(t, i.Totals.Due.String(), "-710.00")
}

func TestValidation(t *testing.T) {
inv := &bill.Invoice{
Currency: currency.EUR,
Expand Down
Loading