forked from hswaw/hscloud
go/svc/invoice: add statusz
This commit is contained in:
parent
c2d322c504
commit
57ef6b0d7f
5 changed files with 176 additions and 31 deletions
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
93
go/svc/invoice/statusz.go
Normal 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)
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue