package pkg
import (
"archive/zip"
"bytes"
"encoding/xml"
"fmt"
"net/http"
"sort"
"strings"
"time"
)
const (
mimetype = "application/vnd.oasis.opendocument.spreadsheet"
metaDashInfManifestXml = `
`
metaXml = `
Numerus
`
stylesXml = `
`
)
func extractTaxIDs(taxColumns map[int]string) []int {
taxIDs := make([]int, len(taxColumns))
i := 0
for k := range taxColumns {
taxIDs[i] = k
i++
}
sort.Ints(taxIDs[:])
return taxIDs
}
func mustWriteInvoicesOds(invoices []*InvoiceEntry, taxes map[int]taxMap, taxColumns map[int]string, locale *Locale, company *Company) []byte {
taxIDs := extractTaxIDs(taxColumns)
columns := make([]string, 6+len(taxIDs))
columns[0] = "Date"
columns[1] = "Invoice Num."
columns[2] = "Customer"
columns[3] = "Status"
i := 4
for _, taxID := range taxIDs {
columns[i] = taxColumns[taxID]
i++
}
columns[i] = "Amount"
columns[i+1] = "Tags"
return mustWriteTableOds(invoices, columns, locale, func(sb *strings.Builder, invoice *InvoiceEntry) {
writeCellDate(sb, invoice.Date)
writeCellString(sb, invoice.Number)
writeCellString(sb, invoice.CustomerName)
writeCellString(sb, invoice.StatusLabel)
writeTaxes(sb, taxes[invoice.ID], taxIDs, locale, company)
writeCellFloat(sb, invoice.Total, locale, company)
writeCellString(sb, strings.Join(invoice.Tags, ","))
})
}
func mustWriteQuotesOds(quotes []*QuoteEntry, locale *Locale, company *Company) []byte {
columns := []string{
"Date",
"Quotation Num.",
"Customer",
"Status",
"Tags",
"Amount",
}
return mustWriteTableOds(quotes, columns, locale, func(sb *strings.Builder, quote *QuoteEntry) {
writeCellDate(sb, quote.Date)
writeCellString(sb, quote.Number)
writeCellString(sb, quote.CustomerName)
writeCellString(sb, quote.StatusLabel)
writeCellString(sb, strings.Join(quote.Tags, ","))
writeCellFloat(sb, quote.Total, locale, company)
})
}
func mustWriteExpensesOds(expenses []*ExpenseEntry, taxes map[int]taxMap, taxColumns map[int]string, locale *Locale, company *Company) []byte {
taxIDs := extractTaxIDs(taxColumns)
columns := make([]string, 7+len(taxIDs))
columns[0] = "Contact"
columns[1] = "Invoice Date"
columns[2] = "Invoice Number"
columns[3] = "Status"
columns[4] = "Amount"
i := 5
for _, taxID := range taxIDs {
columns[i] = taxColumns[taxID]
i++
}
columns[i] = "Total"
columns[i+1] = "Tags"
return mustWriteTableOds(expenses, columns, locale, func(sb *strings.Builder, expense *ExpenseEntry) {
writeCellString(sb, expense.InvoicerName)
writeCellDate(sb, expense.InvoiceDate)
writeCellString(sb, expense.InvoiceNumber)
writeCellString(sb, expense.StatusLabel)
writeCellFloat(sb, expense.Amount, locale, company)
writeTaxes(sb, taxes[expense.ID], taxIDs, locale, company)
writeCellFloat(sb, expense.Total, locale, company)
writeCellString(sb, strings.Join(expense.Tags, ","))
})
}
func mustWriteTableOds[K interface{}](rows []*K, columns []string, locale *Locale, writeRow func(*strings.Builder, *K)) []byte {
var sb strings.Builder
sb.WriteString(`
/
/
`)
sb.WriteString(fmt.Sprintf(" \n", len(columns)))
sb.WriteString(`
`)
for _, t := range columns {
writeCellString(&sb, locale.GetC(t, "title"))
}
sb.WriteString(" \n")
for _, row := range rows {
sb.WriteString(" \n")
writeRow(&sb, row)
sb.WriteString(" \n")
}
sb.WriteString(`
`)
return mustWriteOds(sb.String())
}
func mustWriteOds(content string) []byte {
buf := new(bytes.Buffer)
ods := zip.NewWriter(buf)
mustWriteOdsFile(ods, "mimetype", mimetype, zip.Store)
mustWriteOdsFile(ods, "META-INF/manifest.xml", metaDashInfManifestXml, zip.Deflate)
mustWriteOdsFile(ods, "meta.xml", metaXml, zip.Deflate)
mustWriteOdsFile(ods, "styles.xml", stylesXml, zip.Deflate)
mustWriteOdsFile(ods, "content.xml", content, zip.Deflate)
mustClose(ods)
return buf.Bytes()
}
func mustWriteOdsFile(ods *zip.Writer, name string, content string, method uint16) {
f, err := ods.CreateHeader(&zip.FileHeader{
Name: name,
Method: method,
Modified: time.Now(),
})
if err != nil {
panic(err)
}
if _, err = f.Write([]byte(content)); err != nil {
panic(err)
}
}
func writeCellString(sb *strings.Builder, s string) {
sb.WriteString(` `)
if err := xml.EscapeText(sb, []byte(s)); err != nil {
panic(err)
}
sb.WriteString("\n")
}
func writeCellDate(sb *strings.Builder, t time.Time) {
sb.WriteString(fmt.Sprintf(" %s\n", t.Format("2006-01-02"), t.Format("02/01/06")))
}
func writeCellFloat(sb *strings.Builder, s string, locale *Locale, company *Company) {
sb.WriteString(fmt.Sprintf(" %s\n", s, formatPrice(s, locale.Language, "%.[1]*[2]f", company.DecimalDigits, "")))
}
func writeOdsResponse(w http.ResponseWriter, ods []byte, filename string) {
w.Header().Set("Content-Type", "application/vnd.oasis.opendocument.spreadsheet")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
w.WriteHeader(http.StatusOK)
if _, err := w.Write(ods); err != nil {
panic(err)
}
}
func writeTaxes(sb *strings.Builder, taxes taxMap, taxIDs []int, locale *Locale, company *Company) {
for _, taxID := range taxIDs {
var amount string
if taxes != nil {
amount = taxes[taxID]
}
writeCellFloat(sb, amount, locale, company)
}
}