blob: cb15a1663e69f8675d95f17f442061c9199cdc52 [file] [log] [blame]
package main
import (
"context"
"database/sql"
"fmt"
"strconv"
"github.com/golang/glog"
"github.com/golang/protobuf/proto"
_ "github.com/mattn/go-sqlite3"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
pb "code.hackerspace.pl/hscloud/proto/invoice"
)
type model struct {
db *sql.DB
}
func newModel(dsn string) (*model, error) {
db, err := sql.Open("sqlite3", dsn)
if err != nil {
return nil, err
}
return &model{
db: db,
}, nil
}
func (m *model) init() error {
_, err := m.db.Exec(`
create table invoice (
id integer primary key 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,
foreign key (invoice_id) references invoice(id)
);
create table invoice_blob (
id integer primary key not null,
invoice_id integer not null,
pdf blob not null,
foreign key (invoice_id) references invoice(id)
);
`)
return err
}
func (m *model) sealInvoice(ctx context.Context, uid string) error {
id, err := strconv.Atoi(uid)
if err != nil {
return status.Error(codes.InvalidArgument, "invalid uid")
}
invoice, err := m.getInvoice(ctx, uid)
if err != nil {
return err
}
tx, err := m.db.BeginTx(ctx, nil)
if err != nil {
return err
}
q := `
insert into invoice_seal (
invoice_id, final_uid
) values (
?,
( 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)
if err != nil {
return err
}
lastInvoiceSealId, err := res.LastInsertId()
if err != nil {
return err
}
q = `
select final_uid from invoice_seal where id = ?
`
var finalUid string
if err := tx.QueryRow(q, lastInvoiceSealId).Scan(&finalUid); 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
}
if err := tx.Commit(); err != nil {
return err
}
return nil
}
func (m *model) createInvoice(ctx context.Context, i *pb.Invoice) (string, error) {
data, err := proto.Marshal(i)
if err != nil {
return "", err
}
sql := `
insert into invoice (
proto
) values (
?
)
`
res, err := m.db.Exec(sql, data)
if err != nil {
return "", err
}
id, err := res.LastInsertId()
if err != nil {
return "", err
}
glog.Infof("%+v", id)
return fmt.Sprintf("%d", id), nil
}
func (m *model) getRendered(ctx context.Context, uid string) ([]byte, error) {
id, err := strconv.Atoi(uid)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "invalid uid")
}
q := `
select invoice_blob.pdf from invoice_blob where invoice_blob.invoice_id = ?
`
res := m.db.QueryRow(q, id)
data := []byte{}
if err := res.Scan(&data); err != nil {
if err == sql.ErrNoRows {
return nil, status.Error(codes.InvalidArgument, "no such invoice")
}
return nil, err
}
return data, nil
}
func (m *model) getSealedUid(ctx context.Context, uid string) (string, error) {
id, err := strconv.Atoi(uid)
if err != nil {
return "", status.Error(codes.InvalidArgument, "invalid uid")
}
q := `
select invoice_seal.final_uid from invoice_seal where invoice_seal.invoice_id = ?
`
res := m.db.QueryRow(q, id)
finalUid := ""
if err := res.Scan(&finalUid); err != nil {
if err == sql.ErrNoRows {
return "", nil
}
return "", err
}
return finalUid, nil
}
func (m *model) getInvoice(ctx context.Context, uid string) (*pb.Invoice, error) {
id, err := strconv.Atoi(uid)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "invalid uid")
}
q := `
select invoice.proto from invoice where invoice.id = ?
`
res := m.db.QueryRow(q, id)
data := []byte{}
if err := res.Scan(&data); 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
}
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
}