go/svc/invoice: add statusz

changes/03/3/1
q3k 2019-05-01 14:08:29 +02:00
parent c2d322c504
commit 57ef6b0d7f
5 changed files with 176 additions and 31 deletions

View File

@ -6,11 +6,13 @@ go_library(
"main.go",
"model.go",
"render.go",
"statusz.go",
],
importpath = "code.hackerspace.pl/hscloud/go/svc/invoice",
visibility = ["//visibility:private"],
deps = [
"//go/mirko:go_default_library",
"//go/statusz:go_default_library",
"//go/svc/invoice/templates:go_default_library",
"//proto/invoice:go_default_library",
"@com_github_golang_glog//:go_default_library",

View File

@ -111,45 +111,43 @@ func newService(m *model) *service {
}
}
func (s *service) RenderInvoice(req *pb.RenderInvoiceRequest, srv pb.Invoicer_RenderInvoiceServer) error {
sealed, err := s.m.getSealedUid(srv.Context(), req.Uid)
func (s *service) invoicePDF(ctx context.Context, uid string) ([]byte, error) {
sealed, err := s.m.getSealedUid(ctx, uid)
if err != nil {
if _, ok := status.FromError(err); ok {
return err
}
glog.Errorf("getSealedUid(_, %q): %v", req.Uid, err)
return status.Error(codes.Unavailable, "internal server error")
return nil, err
}
var rendered []byte
if sealed != "" {
// Invoice is sealed, return stored PDF.
rendered, err = s.m.getRendered(srv.Context(), req.Uid)
rendered, err = s.m.getRendered(ctx, uid)
if err != nil {
if _, ok := status.FromError(err); ok {
return err
}
glog.Errorf("getRendered(_, %q): %v", req.Uid, err)
return status.Error(codes.Unavailable, "internal server error")
return nil, err
}
} else {
// Invoice is proforma, render.
invoice, err := s.m.getInvoice(srv.Context(), req.Uid)
invoice, err := s.m.getInvoice(ctx, uid)
if err != nil {
if _, ok := status.FromError(err); ok {
return err
}
glog.Errorf("getInvoice(_, %q): %v", req.Uid, err)
return status.Error(codes.Unavailable, "internal server error")
return nil, err
}
glog.Infof("%+v", invoice)
rendered, err = renderInvoicePDF(invoice, "xxxx", true)
if err != nil {
glog.Errorf("renderProformaPDF(_): %v", err)
return status.Error(codes.Unavailable, "internal server error")
return nil, err
}
}
return rendered, nil
}
func (s *service) RenderInvoice(req *pb.RenderInvoiceRequest, srv pb.Invoicer_RenderInvoiceServer) error {
rendered, err := s.invoicePDF(srv.Context(), req.Uid)
if err != nil {
if _, ok := status.FromError(err); ok {
return err
}
glog.Errorf("invoicePDF(_, %q): %v", req.Uid, err)
return status.Error(codes.Unavailable, "internal server error")
}
chunkSize := 16 * 1024
chunk := &pb.RenderInvoiceResponse{}
@ -201,6 +199,7 @@ func main() {
}
s := newService(m)
s.setupStatusz(mi)
pb.RegisterInvoicerServer(mi.GRPC(), s)
if err := mi.Serve(); err != nil {

View File

@ -211,3 +211,62 @@ func (m *model) getInvoice(ctx context.Context, uid string) (*pb.Invoice, error)
}
return p, nil
}
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) {
q := `
select invoice_seal.final_uid, invoice.id, invoice.proto 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
}
defer rows.Close()
res := []invoice{}
for rows.Next() {
i := invoice{
Proto: &pb.Invoice{},
}
buf := []byte{}
number := sql.NullString{}
if err := rows.Scan(&number, &i.ID, &buf); err != nil {
return []invoice{}, 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)
}
return res, nil
}

View File

@ -2,15 +2,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"generate.go",
"inboice.pb.go",
],
srcs = ["generate.go"],
importpath = "code.hackerspace.pl/hscloud/go/svc/invoice/proto",
visibility = ["//visibility:public"],
deps = [
"@com_github_golang_protobuf//proto:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)

93
go/svc/invoice/statusz.go Normal file
View File

@ -0,0 +1,93 @@
package main
import (
"context"
"fmt"
"net/http"
"code.hackerspace.pl/hscloud/go/mirko"
"code.hackerspace.pl/hscloud/go/statusz"
"github.com/golang/glog"
)
const invoicesFragment = `
<style type="text/css">
.table td,th {
background-color: #eee;
padding: 0.2em 0.4em 0.2em 0.4em;
}
.table th {
background-color: #c0c0c0;
}
.table {
background-color: #fff;
border-spacing: 0.2em;
margin-left: auto;
margin-right: auto;
}
</style>
<div>
{{ .Msg }}
<table class="table">
<tr>
<th>Internal ID</th>
<th>Number</th>
<th>Customer</th>
<th>Amount (net)</th>
<th>Actions</th>
</tr>
{{ range .Invoices }}
{{ if .Sealed }}
<tr>
{{ else }}
<tr style="opacity: 0.5">
{{ end }}
<td>{{ .ID }}</td>
<td>{{ .Number }}</td>
<td>{{ index .Proto.CustomerBilling 0 }}</td>
<td>{{ .TotalNetPretty }}</td>
<td>
<a href="/debug/view?id={{ .ID }}">View</a>
</td>
</tr>
{{ end }}
</table>
</div>
`
type templateInvoice struct {
invoice
TotalNetPretty string
}
func (s *service) setupStatusz(m *mirko.Mirko) {
statusz.AddStatusPart("Invoices", invoicesFragment, func(ctx context.Context) interface{} {
var res struct {
Invoices []templateInvoice
Msg string
}
invoices, err := s.m.getInvoices(ctx)
res.Invoices = make([]templateInvoice, len(invoices))
for i, inv := range invoices {
res.Invoices[i] = templateInvoice{
invoice: inv,
TotalNetPretty: fmt.Sprintf("%.2f %s", float64(inv.TotalNet)/100, inv.Proto.Unit),
}
}
if err != nil {
glog.Errorf("Could not get invoices for statusz: %v", err)
res.Msg = fmt.Sprintf("Could not get invoices: %v", err)
}
return res
})
m.HTTPMux().HandleFunc("/debug/view", func(w http.ResponseWriter, r *http.Request) {
rendered, err := s.invoicePDF(r.Context(), r.URL.Query().Get("id"))
if err != nil {
fmt.Fprintf(w, "error: %v", err)
}
w.Header().Set("Content-type", "application/pdf")
w.Write(rendered)
})
}