invoice: calculate GTU codes for invoice, implement some tests
Also drive-by fix two proto issues:
- rename gtu_codes to gtu_code (following convention)
- move denormalized Item.due_date field past denormalized comment.
Change-Id: Ibfe0a21aadc0a5d4e2f784b182e530b9603aae62
diff --git a/bgpwtf/invoice/BUILD.bazel b/bgpwtf/invoice/BUILD.bazel
index 900f0b3..950474e 100644
--- a/bgpwtf/invoice/BUILD.bazel
+++ b/bgpwtf/invoice/BUILD.bazel
@@ -1,4 +1,4 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
go_library(
name = "go_default_library",
@@ -31,3 +31,9 @@
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)
+
+go_test(
+ name = "go_default_test",
+ srcs = ["calc_test.go"],
+ embed = [":go_default_library"],
+)
diff --git a/bgpwtf/invoice/calc.go b/bgpwtf/invoice/calc.go
index 9c411da..72933d3 100644
--- a/bgpwtf/invoice/calc.go
+++ b/bgpwtf/invoice/calc.go
@@ -1,17 +1,23 @@
package main
import (
+ "sort"
"time"
pb "code.hackerspace.pl/hscloud/bgpwtf/invoice/proto"
)
+// calculateInvoiceData applies all business logic to populate an Invoice's
+// denormalized fields from its InvoiceData.
func calculateInvoiceData(p *pb.Invoice) {
+ // Populate default unit.
+ // TODO(q3k): this really should be done on invoice submit instead.
p.Unit = p.Data.Unit
if p.Unit == "" {
p.Unit = "€"
}
+ // Calculate totals.
p.TotalNet = 0
p.Total = 0
for _, i := range p.Data.Item {
@@ -24,6 +30,21 @@
i.Total = rowTotal
}
+ // Calculate due date.
due := int64(time.Hour*24) * p.Data.DaysDue
p.DueDate = time.Unix(0, p.Date).Add(time.Duration(due)).UnixNano()
+
+ // Denormalize Items' GTUCodes into the Invoice's summary GTU codes.
+ codeSet := make(map[pb.GTUCode]bool)
+ for _, item := range p.Data.Item {
+ for _, code := range item.GtuCode {
+ codeSet[code] = true
+ }
+ }
+ var codes []pb.GTUCode
+ for c, _ := range codeSet {
+ codes = append(codes, c)
+ }
+ sort.Slice(codes, func(i, j int) bool { return codes[i] < codes[j] })
+ p.GtuCode = codes
}
diff --git a/bgpwtf/invoice/calc_test.go b/bgpwtf/invoice/calc_test.go
new file mode 100644
index 0000000..e8607c9
--- /dev/null
+++ b/bgpwtf/invoice/calc_test.go
@@ -0,0 +1,129 @@
+package main
+
+import (
+ "testing"
+ "time"
+
+ pb "code.hackerspace.pl/hscloud/bgpwtf/invoice/proto"
+)
+
+// Fake test data for test in this file.
+var (
+ itemInternet1 = &pb.Item{
+ Title: "Dostęp do Internetu - Umowa FOOBAR/10 - Opłata Abonentowa 2020/08",
+ Count: 1,
+ UnitPrice: 4200,
+ Vat: 23000,
+ }
+ itemInternet2 = &pb.Item{
+ Title: "Dostęp do Internetu - Umowa FOOBAR/10 - Opłata Abonentowa 2020/09",
+ Count: 1,
+ UnitPrice: 4200,
+ Vat: 23000,
+ }
+ itemHardware = &pb.Item{
+ Title: "Thinkpad x230, i7, 16GB RAM, Refurbished",
+ Count: 1,
+ UnitPrice: 10000,
+ Vat: 23000,
+ GtuCode: []pb.GTUCode{pb.GTUCode_GTU_05},
+ }
+ billing1 = []string{
+ "Wykop Sp. z o. o.",
+ "Zakręt 8",
+ "60-351 Poznań",
+ }
+ billing2 = []string{
+ "TEH Adam Karolczak",
+ "Zgoda 18/2",
+ "95-200 Pabianice",
+ }
+ vatID1 = "PL8086133742"
+ vatID2 = "DE133742429"
+ iban = "PL 59 1090 2402 9746 7956 2256 2375"
+ swift = "WLPPZLPAXXX"
+)
+
+func TestCalculate(t *testing.T) {
+ now := time.Now()
+ for _, te := range []struct {
+ description string
+ data *pb.InvoiceData
+ want *pb.Invoice
+ }{
+ {
+ description: "Invoice without JPK_V7 codes",
+ data: &pb.InvoiceData{
+ Item: []*pb.Item{itemInternet1, itemInternet2},
+ InvoicerBilling: billing1,
+ CustomerBilling: billing2,
+ InvoicerVatId: vatID1,
+ CustomerVatId: vatID2,
+ Date: now.UnixNano(),
+ DaysDue: 21,
+ Iban: iban,
+ Swift: swift,
+ Unit: "PLN",
+ },
+ want: &pb.Invoice{
+ TotalNet: 8400,
+ Total: 10332,
+ Unit: "PLN",
+ },
+ },
+ {
+ description: "Invoice with JPK_V7 codes",
+ data: &pb.InvoiceData{
+ // Repeated item with GTU code GTU_5, to ensure result doesn't
+ // have repeated codes.
+ Item: []*pb.Item{itemInternet1, itemHardware, itemHardware},
+ InvoicerBilling: billing1,
+ CustomerBilling: billing2,
+ InvoicerVatId: vatID1,
+ CustomerVatId: vatID2,
+ Date: now.UnixNano(),
+ DaysDue: 21,
+ Iban: iban,
+ Swift: swift,
+ Unit: "PLN",
+ },
+ want: &pb.Invoice{
+ TotalNet: 24200,
+ Total: 29766,
+ Unit: "PLN",
+ GtuCode: []pb.GTUCode{pb.GTUCode_GTU_05},
+ },
+ },
+ } {
+ t.Run(te.description, func(t *testing.T) {
+ invoice := &pb.Invoice{
+ Data: te.data,
+ Date: te.data.Date,
+ }
+ calculateInvoiceData(invoice)
+ if want, got := te.want.TotalNet, invoice.TotalNet; want != got {
+ t.Errorf("got TotalNet %d, wanted %d", got, want)
+ }
+ if want, got := te.want.Total, invoice.Total; want != got {
+ t.Errorf("got Total %d, wanted %d", got, want)
+ }
+ if want, got := te.want.Unit, invoice.Unit; want != got {
+ t.Errorf("got Unit %q, wanted %q", got, want)
+ }
+ due := time.Duration(int64(time.Hour*24) * te.data.DaysDue)
+ if want, got := now.Add(due).UnixNano(), invoice.DueDate; want != got {
+ t.Errorf("got DueDate %d, wanted %d", got, want)
+ }
+ if want, got := len(te.want.GtuCode), len(invoice.GtuCode); want != got {
+ t.Errorf("got %d GTU codes, wanted %d", got, want)
+ } else {
+ for i, want := range te.want.GtuCode {
+ got := invoice.GtuCode[i]
+ if want != got {
+ t.Errorf("GTU code %d: wanted %s, got %s", i, want.String(), got.String())
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/bgpwtf/invoice/proto/BUILD.bazel b/bgpwtf/invoice/proto/BUILD.bazel
index 51f85fe..2eeae64 100644
--- a/bgpwtf/invoice/proto/BUILD.bazel
+++ b/bgpwtf/invoice/proto/BUILD.bazel
@@ -1,3 +1,4 @@
+load("@rules_proto//proto:defs.bzl", "proto_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
diff --git a/bgpwtf/invoice/proto/invoice.proto b/bgpwtf/invoice/proto/invoice.proto
index ee2b9d8..e6caae5 100644
--- a/bgpwtf/invoice/proto/invoice.proto
+++ b/bgpwtf/invoice/proto/invoice.proto
@@ -264,12 +264,12 @@
// If sealed, otherwise 'proforma'.
string final_uid = 3;
int64 date = 4;
- int64 due_date = 5;
// Denormalized fields follow.
+ int64 due_date = 5;
uint64 total_net = 6;
uint64 total = 7;
string unit = 8;
- repeated GTUCode gtu_codes = 10;
+ repeated GTUCode gtu_code = 10;
// Next tag: 11;
}