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) } }