go/svc/invoice: refactor
We unify calculation logic, move the existing Invoice proto message into
InvoiceData, and create other messages/fields around it to hold
denormalized data.
diff --git a/go/svc/invoice/model.go b/go/svc/invoice/model.go
index cb15a16..701807a 100644
--- a/go/svc/invoice/model.go
+++ b/go/svc/invoice/model.go
@@ -5,6 +5,7 @@
"database/sql"
"fmt"
"strconv"
+ "time"
"github.com/golang/glog"
"github.com/golang/protobuf/proto"
@@ -33,12 +34,14 @@
_, err := m.db.Exec(`
create table invoice (
id integer primary key not null,
+ created_time integer not null,
proto blob not null
);
create table invoice_seal (
id integer primary key not null,
invoice_id integer not null,
final_uid text not null unique,
+ sealed_time integer not null,
foreign key (invoice_id) references invoice(id)
);
create table invoice_blob (
@@ -69,14 +72,16 @@
q := `
insert into invoice_seal (
- invoice_id, final_uid
+ invoice_id, final_uid, sealed_time
) values (
?,
- ( select printf("%04d", ifnull( (select final_uid as v from invoice_seal order by final_uid desc limit 1), 19000) + 1 ))
+ ( select printf("%04d", ifnull( (select final_uid as v from invoice_seal order by final_uid desc limit 1), 19000) + 1 )),
+ ?
)
`
- res, err := tx.Exec(q, id)
+ sealTime := time.Now()
+ res, err := tx.Exec(q, id, sealTime.UnixNano())
if err != nil {
return err
}
@@ -95,20 +100,24 @@
return err
}
+ invoice.State = pb.Invoice_STATE_SEALED
+ invoice.FinalUid = fmt.Sprintf("FV/%s", finalUid)
+ invoice.Date = sealTime.UnixNano()
+ calculateInvoiceData(invoice)
+
+ pdfBlob, err := renderInvoicePDF(invoice)
+ if err != nil {
+ return err
+ }
+
q = `
insert into invoice_blob (
invoice_id, pdf
) values (
- ?,
- ?
+ ?, ?
)
`
- pdfBlob, err := renderInvoicePDF(invoice, finalUid, false)
- if err != nil {
- return err
- }
-
if _, err := tx.Exec(q, id, pdfBlob); err != nil {
return err
}
@@ -120,30 +129,30 @@
return nil
}
-func (m *model) createInvoice(ctx context.Context, i *pb.Invoice) (string, error) {
- data, err := proto.Marshal(i)
+func (m *model) createInvoice(ctx context.Context, id *pb.InvoiceData) (string, error) {
+ data, err := proto.Marshal(id)
if err != nil {
return "", err
}
sql := `
insert into invoice (
- proto
+ proto, created_time
) values (
- ?
+ ?, ?
)
`
- res, err := m.db.Exec(sql, data)
+ res, err := m.db.Exec(sql, data, time.Now().UnixNano())
if err != nil {
return "", err
}
- id, err := res.LastInsertId()
+ uid, err := res.LastInsertId()
if err != nil {
return "", err
}
- glog.Infof("%+v", id)
- return fmt.Sprintf("%d", id), nil
+ glog.Infof("%+v", uid)
+ return fmt.Sprintf("%d", uid), nil
}
func (m *model) getRendered(ctx context.Context, uid string) ([]byte, error) {
@@ -187,6 +196,37 @@
return finalUid, nil
}
+type sqlInvoiceSealRow struct {
+ proto []byte
+ createdTime int64
+ sealedTime sql.NullInt64
+ finalUid sql.NullString
+ uid int64
+}
+
+func (s *sqlInvoiceSealRow) Proto() (*pb.Invoice, error) {
+ data := &pb.InvoiceData{}
+ if err := proto.Unmarshal(s.proto, data); err != nil {
+ return nil, err
+ }
+
+ p := &pb.Invoice{
+ Uid: fmt.Sprintf("%d", s.uid),
+ Data: data,
+ }
+ if s.finalUid.Valid {
+ p.State = pb.Invoice_STATE_SEALED
+ p.FinalUid = fmt.Sprintf("FV/%s", s.finalUid.String)
+ p.Date = s.sealedTime.Int64
+ } else {
+ p.State = pb.Invoice_STATE_PROFORMA
+ p.FinalUid = fmt.Sprintf("PROFORMA/%d", s.uid)
+ p.Date = s.createdTime
+ }
+ calculateInvoiceData(p)
+ return p, nil
+}
+
func (m *model) getInvoice(ctx context.Context, uid string) (*pb.Invoice, error) {
id, err := strconv.Atoi(uid)
if err != nil {
@@ -194,78 +234,51 @@
}
q := `
- select invoice.proto from invoice where invoice.id = ?
+ select
+ invoice.id, invoice.proto, invoice.created_time, invoice_seal.sealed_time, invoice_seal.final_uid
+ from invoice
+ left join invoice_seal
+ on invoice_seal.invoice_id = invoice.id
+ where invoice.id = ?
`
res := m.db.QueryRow(q, id)
- data := []byte{}
- if err := res.Scan(&data); err != nil {
+ row := sqlInvoiceSealRow{}
+ if err := res.Scan(&row.uid, &row.proto, &row.createdTime, &row.sealedTime, &row.finalUid); err != nil {
if err == sql.ErrNoRows {
return nil, status.Error(codes.NotFound, "no such invoice")
}
return nil, err
}
- p := &pb.Invoice{}
- if err := proto.Unmarshal(data, p); err != nil {
- return nil, err
- }
- return p, nil
+ return row.Proto()
}
-type invoice struct {
- ID int64
- Number string
- Sealed bool
- Proto *pb.Invoice
- TotalNet int
- Total int
-}
-
-func (m *model) getInvoices(ctx context.Context) ([]invoice, error) {
+func (m *model) getInvoices(ctx context.Context) ([]*pb.Invoice, error) {
q := `
- select invoice_seal.final_uid, invoice.id, invoice.proto from invoice
+ select
+ invoice.id, invoice.proto, invoice.created_time, invoice_seal.sealed_time, invoice_seal.final_uid
+ from invoice
left join invoice_seal
on invoice_seal.invoice_id = invoice.id
`
rows, err := m.db.QueryContext(ctx, q)
if err != nil {
- return []invoice{}, err
+ return nil, err
}
defer rows.Close()
- res := []invoice{}
+ res := []*pb.Invoice{}
+
for rows.Next() {
- i := invoice{
- Proto: &pb.Invoice{},
+ row := sqlInvoiceSealRow{}
+ if err := rows.Scan(&row.uid, &row.proto, &row.createdTime, &row.sealedTime, &row.finalUid); err != nil {
+ return nil, err
}
- buf := []byte{}
-
- number := sql.NullString{}
- if err := rows.Scan(&number, &i.ID, &buf); err != nil {
- return []invoice{}, err
+ p, err := row.Proto()
+ if err != nil {
+ return nil, err
}
-
- if err := proto.Unmarshal(buf, i.Proto); err != nil {
- return []invoice{}, err
- }
-
- if number.Valid {
- i.Sealed = true
- i.Number = number.String
- } else {
- i.Number = "proforma"
- }
-
- i.Total = 0
- i.TotalNet = 0
- for _, it := range i.Proto.Item {
- rowTotalNet := int(it.UnitPrice * it.Count)
- rowTotal := int(float64(rowTotalNet) * (float64(1) + float64(it.Vat)/100000))
- i.TotalNet += rowTotalNet
- i.Total += rowTotal
- }
-
- res = append(res, i)
+ res = append(res, p)
}
return res, nil