Add button to download many invoices as PDF in a ZIP archive

This commit is contained in:
jordi fita mas 2023-03-09 12:11:53 +01:00
parent f3b841473f
commit 5dedaefc22
5 changed files with 215 additions and 114 deletions

View File

@ -1,8 +1,10 @@
package pkg package pkg
import ( import (
"archive/zip"
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"html/template" "html/template"
@ -10,6 +12,7 @@ import (
"log" "log"
"math" "math"
"net/http" "net/http"
"os"
"os/exec" "os/exec"
"sort" "sort"
"strconv" "strconv"
@ -103,41 +106,50 @@ func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Para
pdf = true pdf = true
slug = slug[:len(slug)-len(".pdf")] slug = slug[:len(slug)-len(".pdf")]
} }
invoice := mustGetInvoice(r.Context(), conn, company, slug) inv := mustGetInvoice(r.Context(), conn, company, slug)
if invoice == nil { if inv == nil {
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
if pdf { if pdf {
cmd := exec.Command("weasyprint", "--format", "pdf", "--stylesheet", "web/static/invoice.css", "-", "-")
var stderr bytes.Buffer
cmd.Stderr = &stderr
stdin, err := cmd.StdinPipe()
if err != nil {
panic(err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
defer mustClose(stdout)
if err = cmd.Start(); err != nil {
panic(err)
}
go func() {
defer mustClose(stdin)
mustRenderAppTemplate(stdin, r, "invoices/view.gohtml", invoice)
}()
w.Header().Set("Content-Type", "application/pdf") w.Header().Set("Content-Type", "application/pdf")
if _, err = io.Copy(w, stdout); err != nil { mustWriteInvoicePdf(w, r, inv)
panic(err)
}
if err := cmd.Wait(); err != nil {
log.Printf("ERR - %v\n", stderr.String())
panic(err)
}
} else { } else {
mustRenderAppTemplate(w, r, "invoices/view.gohtml", invoice) mustRenderAppTemplate(w, r, "invoices/view.gohtml", inv)
}
}
func mustWriteInvoicePdf(w io.Writer, r *http.Request, inv *invoice) {
cmd := exec.Command("weasyprint", "--format", "pdf", "--stylesheet", "web/static/invoice.css", "-", "-")
var stderr bytes.Buffer
cmd.Stderr = &stderr
stdin, err := cmd.StdinPipe()
if err != nil {
panic(err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
defer func() {
err := stdout.Close()
if !errors.Is(err, os.ErrClosed) {
panic(err)
}
}()
if err = cmd.Start(); err != nil {
panic(err)
}
go func() {
defer mustClose(stdin)
mustRenderAppTemplate(stdin, r, "invoices/view.gohtml", inv)
}()
if _, err = io.Copy(w, stdout); err != nil {
panic(err)
}
if err := cmd.Wait(); err != nil {
log.Printf("ERR - %v\n", stderr.String())
panic(err)
} }
} }
@ -352,6 +364,55 @@ func HandleNewInvoiceAction(w http.ResponseWriter, r *http.Request, _ httprouter
} }
} }
func HandleBatchInvoiceAction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := verifyCsrfTokenValid(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
slugs := r.Form["invoice"]
if len(slugs) == 0 {
http.Redirect(w, r, companyURI(mustGetCompany(r), "/invoices"), http.StatusSeeOther)
return
}
locale := getLocale(r)
switch r.Form.Get("action") {
case "download":
invoices := mustWriteInvoicesPdf(r, slugs)
w.Header().Set("Content-Type", "application/zip")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", gettext("invoices.zip", locale)))
w.WriteHeader(http.StatusOK)
if _, err := w.Write(invoices); err != nil {
panic(err)
}
default:
http.Error(w, gettext("Invalid action", locale), http.StatusBadRequest)
}
}
func mustWriteInvoicesPdf(r *http.Request, slugs []string) []byte {
conn := getConn(r)
company := mustGetCompany(r)
buf := new(bytes.Buffer)
w := zip.NewWriter(buf)
for _, slug := range slugs {
inv := mustGetInvoice(r.Context(), conn, company, slug)
if inv == nil {
continue
}
f, err := w.Create(inv.Number + ".pdf")
if err != nil {
panic(err)
}
mustWriteInvoicePdf(f, r, inv)
}
mustClose(w)
return buf.Bytes()
}
type invoiceForm struct { type invoiceForm struct {
locale *Locale locale *Locale
company *Company company *Company

View File

@ -29,6 +29,7 @@ func NewRouter(db *Db) http.Handler {
companyRouter.GET("/invoices/:slug", ServeInvoice) companyRouter.GET("/invoices/:slug", ServeInvoice)
companyRouter.PUT("/invoices/:slug", HandleUpdateInvoice) companyRouter.PUT("/invoices/:slug", HandleUpdateInvoice)
companyRouter.POST("/invoices/new", HandleNewInvoiceAction) companyRouter.POST("/invoices/new", HandleNewInvoiceAction)
companyRouter.POST("/invoices/batch", HandleBatchInvoiceAction)
companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
mustRenderAppTemplate(w, r, "dashboard.gohtml", nil) mustRenderAppTemplate(w, r, "dashboard.gohtml", nil)
}) })

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: numerus\n" "Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-03-08 11:51+0100\n" "POT-Creation-Date: 2023-03-09 12:08+0100\n"
"PO-Revision-Date: 2023-01-18 17:08+0100\n" "PO-Revision-Date: 2023-01-18 17:08+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n" "Language-Team: Catalan <ca@dodds.net>\n"
@ -90,62 +90,72 @@ msgctxt "action"
msgid "Update" msgid "Update"
msgstr "Actualitza" msgstr "Actualitza"
#: web/template/invoices/new.gohtml:66 web/template/invoices/index.gohtml:13 #: web/template/invoices/new.gohtml:66 web/template/invoices/index.gohtml:19
msgctxt "action" msgctxt "action"
msgid "New invoice" msgid "New invoice"
msgstr "Nova factura" msgstr "Nova factura"
#: web/template/invoices/index.gohtml:21 #: web/template/invoices/index.gohtml:17
msgctxt "action"
msgid "Download invoices"
msgstr "Descarrega factures"
#: web/template/invoices/index.gohtml:28
msgctxt "invoice" msgctxt "invoice"
msgid "All" msgid "All"
msgstr "Totes" msgstr "Totes"
#: web/template/invoices/index.gohtml:22 web/template/invoices/view.gohtml:26 #: web/template/invoices/index.gohtml:29 web/template/invoices/view.gohtml:26
msgctxt "title" msgctxt "title"
msgid "Date" msgid "Date"
msgstr "Data" msgstr "Data"
#: web/template/invoices/index.gohtml:23 #: web/template/invoices/index.gohtml:30
msgctxt "title" msgctxt "title"
msgid "Invoice Num." msgid "Invoice Num."
msgstr "Núm. factura" msgstr "Núm. factura"
#: web/template/invoices/index.gohtml:24 web/template/contacts/index.gohtml:22 #: web/template/invoices/index.gohtml:31 web/template/contacts/index.gohtml:22
msgctxt "title" msgctxt "title"
msgid "Customer" msgid "Customer"
msgstr "Client" msgstr "Client"
#: web/template/invoices/index.gohtml:25 #: web/template/invoices/index.gohtml:32
msgctxt "title" msgctxt "title"
msgid "Status" msgid "Status"
msgstr "Estat" msgstr "Estat"
#: web/template/invoices/index.gohtml:26 #: web/template/invoices/index.gohtml:33
msgctxt "title" msgctxt "title"
msgid "Label" msgid "Label"
msgstr "Etiqueta" msgstr "Etiqueta"
#: web/template/invoices/index.gohtml:27 #: web/template/invoices/index.gohtml:34
msgctxt "title" msgctxt "title"
msgid "Amount" msgid "Amount"
msgstr "Import" msgstr "Import"
#: web/template/invoices/index.gohtml:28 #: web/template/invoices/index.gohtml:35
msgctxt "title" msgctxt "title"
msgid "Download" msgid "Download"
msgstr "Descàrrega" msgstr "Descàrrega"
#: web/template/invoices/index.gohtml:29 #: web/template/invoices/index.gohtml:36
msgctxt "title" msgctxt "title"
msgid "Actions" msgid "Actions"
msgstr "Accions" msgstr "Accions"
#: web/template/invoices/index.gohtml:75 web/template/invoices/view.gohtml:14 #: web/template/invoices/index.gohtml:43
msgctxt "action"
msgid "Select invoice %v"
msgstr "Selecciona factura %v"
#: web/template/invoices/index.gohtml:86 web/template/invoices/view.gohtml:14
msgctxt "action" msgctxt "action"
msgid "Duplicate" msgid "Duplicate"
msgstr "Duplica" msgstr "Duplica"
#: web/template/invoices/index.gohtml:85 #: web/template/invoices/index.gohtml:96
msgid "No invoices added yet." msgid "No invoices added yet."
msgstr "No hi ha cap factura." msgstr "No hi ha cap factura."
@ -418,44 +428,44 @@ msgstr "No podeu deixar la contrasenya en blanc."
msgid "Invalid user or password." msgid "Invalid user or password."
msgstr "Nom dusuari o contrasenya incorrectes." msgstr "Nom dusuari o contrasenya incorrectes."
#: pkg/products.go:165 pkg/invoices.go:517 #: pkg/products.go:165 pkg/invoices.go:578
msgctxt "input" msgctxt "input"
msgid "Name" msgid "Name"
msgstr "Nom" msgstr "Nom"
#: pkg/products.go:171 pkg/invoices.go:522 #: pkg/products.go:171 pkg/invoices.go:583
msgctxt "input" msgctxt "input"
msgid "Description" msgid "Description"
msgstr "Descripció" msgstr "Descripció"
#: pkg/products.go:176 pkg/invoices.go:526 #: pkg/products.go:176 pkg/invoices.go:587
msgctxt "input" msgctxt "input"
msgid "Price" msgid "Price"
msgstr "Preu" msgstr "Preu"
#: pkg/products.go:186 pkg/invoices.go:552 #: pkg/products.go:186 pkg/invoices.go:613
msgctxt "input" msgctxt "input"
msgid "Taxes" msgid "Taxes"
msgstr "Imposts" msgstr "Imposts"
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:431 #: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:492
#: pkg/invoices.go:588 #: pkg/invoices.go:649
msgid "Name can not be empty." msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc." msgstr "No podeu deixar el nom en blanc."
#: pkg/products.go:207 pkg/invoices.go:589 #: pkg/products.go:207 pkg/invoices.go:650
msgid "Price can not be empty." msgid "Price can not be empty."
msgstr "No podeu deixar el preu en blanc." msgstr "No podeu deixar el preu en blanc."
#: pkg/products.go:208 pkg/invoices.go:590 #: pkg/products.go:208 pkg/invoices.go:651
msgid "Price must be a number greater than zero." msgid "Price must be a number greater than zero."
msgstr "El preu ha de ser un número major a zero." msgstr "El preu ha de ser un número major a zero."
#: pkg/products.go:210 pkg/invoices.go:598 #: pkg/products.go:210 pkg/invoices.go:659
msgid "Selected tax is not valid." msgid "Selected tax is not valid."
msgstr "Heu seleccionat un impost que no és vàlid." msgstr "Heu seleccionat un impost que no és vàlid."
#: pkg/products.go:211 pkg/invoices.go:599 #: pkg/products.go:211 pkg/invoices.go:660
msgid "You can only select a tax of each class." msgid "You can only select a tax of each class."
msgstr "Només podeu seleccionar un impost de cada classe." msgstr "Només podeu seleccionar un impost de cada classe."
@ -563,79 +573,83 @@ msgstr "La confirmació no és igual a la contrasenya."
msgid "Selected language is not valid." msgid "Selected language is not valid."
msgstr "Heu seleccionat un idioma que no és vàlid." msgstr "Heu seleccionat un idioma que no és vàlid."
#: pkg/invoices.go:258 #: pkg/invoices.go:270
msgid "Select a customer to bill." msgid "Select a customer to bill."
msgstr "Escolliu un client a facturar." msgstr "Escolliu un client a facturar."
#: pkg/invoices.go:351 #: pkg/invoices.go:363 pkg/invoices.go:392
msgid "Invalid action" msgid "Invalid action"
msgstr "Acció invàlida." msgstr "Acció invàlida."
#: pkg/invoices.go:372 #: pkg/invoices.go:386
msgid "invoices.zip"
msgstr "factures.zip"
#: pkg/invoices.go:433
msgctxt "input" msgctxt "input"
msgid "Customer" msgid "Customer"
msgstr "Client" msgstr "Client"
#: pkg/invoices.go:378 #: pkg/invoices.go:439
msgctxt "input" msgctxt "input"
msgid "Number" msgid "Number"
msgstr "Número" msgstr "Número"
#: pkg/invoices.go:384 #: pkg/invoices.go:445
msgctxt "input" msgctxt "input"
msgid "Invoice Date" msgid "Invoice Date"
msgstr "Data de factura" msgstr "Data de factura"
#: pkg/invoices.go:390 #: pkg/invoices.go:451
msgctxt "input" msgctxt "input"
msgid "Notes" msgid "Notes"
msgstr "Notes" msgstr "Notes"
#: pkg/invoices.go:396 #: pkg/invoices.go:457
msgctxt "input" msgctxt "input"
msgid "Payment Method" msgid "Payment Method"
msgstr "Mètode de pagament" msgstr "Mètode de pagament"
#: pkg/invoices.go:432 #: pkg/invoices.go:493
msgid "Invoice date can not be empty." msgid "Invoice date can not be empty."
msgstr "No podeu deixar la data de la factura en blanc." msgstr "No podeu deixar la data de la factura en blanc."
#: pkg/invoices.go:433 #: pkg/invoices.go:494
msgid "Invoice date must be a valid date." msgid "Invoice date must be a valid date."
msgstr "La data de facturació ha de ser vàlida." msgstr "La data de facturació ha de ser vàlida."
#: pkg/invoices.go:435 #: pkg/invoices.go:496
msgid "Selected payment method is not valid." msgid "Selected payment method is not valid."
msgstr "Heu seleccionat un mètode de pagament que no és vàlid." msgstr "Heu seleccionat un mètode de pagament que no és vàlid."
#: pkg/invoices.go:512 #: pkg/invoices.go:573
msgctxt "input" msgctxt "input"
msgid "Id" msgid "Id"
msgstr "Identificador" msgstr "Identificador"
#: pkg/invoices.go:535 #: pkg/invoices.go:596
msgctxt "input" msgctxt "input"
msgid "Quantity" msgid "Quantity"
msgstr "Quantitat" msgstr "Quantitat"
#: pkg/invoices.go:543 #: pkg/invoices.go:604
msgctxt "input" msgctxt "input"
msgid "Discount (%)" msgid "Discount (%)"
msgstr "Descompte (%)" msgstr "Descompte (%)"
#: pkg/invoices.go:592 #: pkg/invoices.go:653
msgid "Quantity can not be empty." msgid "Quantity can not be empty."
msgstr "No podeu deixar la quantitat en blanc." msgstr "No podeu deixar la quantitat en blanc."
#: pkg/invoices.go:593 #: pkg/invoices.go:654
msgid "Quantity must be a number greater than zero." msgid "Quantity must be a number greater than zero."
msgstr "La quantitat ha de ser un número major a zero." msgstr "La quantitat ha de ser un número major a zero."
#: pkg/invoices.go:595 #: pkg/invoices.go:656
msgid "Discount can not be empty." msgid "Discount can not be empty."
msgstr "No podeu deixar el descompte en blanc." msgstr "No podeu deixar el descompte en blanc."
#: pkg/invoices.go:596 #: pkg/invoices.go:657
msgid "Discount must be a percentage between 0 and 100." msgid "Discount must be a percentage between 0 and 100."
msgstr "El descompte ha de ser un percentatge entre 0 i 100." msgstr "El descompte ha de ser un percentatge entre 0 i 100."

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: numerus\n" "Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-03-08 11:51+0100\n" "POT-Creation-Date: 2023-03-09 12:08+0100\n"
"PO-Revision-Date: 2023-01-18 17:45+0100\n" "PO-Revision-Date: 2023-01-18 17:45+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n" "Language-Team: Spanish <es@tp.org.es>\n"
@ -90,62 +90,72 @@ msgctxt "action"
msgid "Update" msgid "Update"
msgstr "Actualizar" msgstr "Actualizar"
#: web/template/invoices/new.gohtml:66 web/template/invoices/index.gohtml:13 #: web/template/invoices/new.gohtml:66 web/template/invoices/index.gohtml:19
msgctxt "action" msgctxt "action"
msgid "New invoice" msgid "New invoice"
msgstr "Nueva factura" msgstr "Nueva factura"
#: web/template/invoices/index.gohtml:21 #: web/template/invoices/index.gohtml:17
msgctxt "action"
msgid "Download invoices"
msgstr "Descargar facturas"
#: web/template/invoices/index.gohtml:28
msgctxt "invoice" msgctxt "invoice"
msgid "All" msgid "All"
msgstr "Todas" msgstr "Todas"
#: web/template/invoices/index.gohtml:22 web/template/invoices/view.gohtml:26 #: web/template/invoices/index.gohtml:29 web/template/invoices/view.gohtml:26
msgctxt "title" msgctxt "title"
msgid "Date" msgid "Date"
msgstr "Fecha" msgstr "Fecha"
#: web/template/invoices/index.gohtml:23 #: web/template/invoices/index.gohtml:30
msgctxt "title" msgctxt "title"
msgid "Invoice Num." msgid "Invoice Num."
msgstr "Nº factura" msgstr "Nº factura"
#: web/template/invoices/index.gohtml:24 web/template/contacts/index.gohtml:22 #: web/template/invoices/index.gohtml:31 web/template/contacts/index.gohtml:22
msgctxt "title" msgctxt "title"
msgid "Customer" msgid "Customer"
msgstr "Cliente" msgstr "Cliente"
#: web/template/invoices/index.gohtml:25 #: web/template/invoices/index.gohtml:32
msgctxt "title" msgctxt "title"
msgid "Status" msgid "Status"
msgstr "Estado" msgstr "Estado"
#: web/template/invoices/index.gohtml:26 #: web/template/invoices/index.gohtml:33
msgctxt "title" msgctxt "title"
msgid "Label" msgid "Label"
msgstr "Etiqueta" msgstr "Etiqueta"
#: web/template/invoices/index.gohtml:27 #: web/template/invoices/index.gohtml:34
msgctxt "title" msgctxt "title"
msgid "Amount" msgid "Amount"
msgstr "Importe" msgstr "Importe"
#: web/template/invoices/index.gohtml:28 #: web/template/invoices/index.gohtml:35
msgctxt "title" msgctxt "title"
msgid "Download" msgid "Download"
msgstr "Descargar" msgstr "Descargar"
#: web/template/invoices/index.gohtml:29 #: web/template/invoices/index.gohtml:36
msgctxt "title" msgctxt "title"
msgid "Actions" msgid "Actions"
msgstr "Acciones" msgstr "Acciones"
#: web/template/invoices/index.gohtml:75 web/template/invoices/view.gohtml:14 #: web/template/invoices/index.gohtml:43
msgctxt "action"
msgid "Select invoice %v"
msgstr "Seleccionar factura %v"
#: web/template/invoices/index.gohtml:86 web/template/invoices/view.gohtml:14
msgctxt "action" msgctxt "action"
msgid "Duplicate" msgid "Duplicate"
msgstr "Duplicar" msgstr "Duplicar"
#: web/template/invoices/index.gohtml:85 #: web/template/invoices/index.gohtml:96
msgid "No invoices added yet." msgid "No invoices added yet."
msgstr "No hay facturas." msgstr "No hay facturas."
@ -418,44 +428,44 @@ msgstr "No podéis dejar la contraseña en blanco."
msgid "Invalid user or password." msgid "Invalid user or password."
msgstr "Nombre de usuario o contraseña inválido." msgstr "Nombre de usuario o contraseña inválido."
#: pkg/products.go:165 pkg/invoices.go:517 #: pkg/products.go:165 pkg/invoices.go:578
msgctxt "input" msgctxt "input"
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
#: pkg/products.go:171 pkg/invoices.go:522 #: pkg/products.go:171 pkg/invoices.go:583
msgctxt "input" msgctxt "input"
msgid "Description" msgid "Description"
msgstr "Descripción" msgstr "Descripción"
#: pkg/products.go:176 pkg/invoices.go:526 #: pkg/products.go:176 pkg/invoices.go:587
msgctxt "input" msgctxt "input"
msgid "Price" msgid "Price"
msgstr "Precio" msgstr "Precio"
#: pkg/products.go:186 pkg/invoices.go:552 #: pkg/products.go:186 pkg/invoices.go:613
msgctxt "input" msgctxt "input"
msgid "Taxes" msgid "Taxes"
msgstr "Impuestos" msgstr "Impuestos"
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:431 #: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:492
#: pkg/invoices.go:588 #: pkg/invoices.go:649
msgid "Name can not be empty." msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco." msgstr "No podéis dejar el nombre en blanco."
#: pkg/products.go:207 pkg/invoices.go:589 #: pkg/products.go:207 pkg/invoices.go:650
msgid "Price can not be empty." msgid "Price can not be empty."
msgstr "No podéis dejar el precio en blanco." msgstr "No podéis dejar el precio en blanco."
#: pkg/products.go:208 pkg/invoices.go:590 #: pkg/products.go:208 pkg/invoices.go:651
msgid "Price must be a number greater than zero." msgid "Price must be a number greater than zero."
msgstr "El precio tiene que ser un número mayor a cero." msgstr "El precio tiene que ser un número mayor a cero."
#: pkg/products.go:210 pkg/invoices.go:598 #: pkg/products.go:210 pkg/invoices.go:659
msgid "Selected tax is not valid." msgid "Selected tax is not valid."
msgstr "Habéis escogido un impuesto que no es válido." msgstr "Habéis escogido un impuesto que no es válido."
#: pkg/products.go:211 pkg/invoices.go:599 #: pkg/products.go:211 pkg/invoices.go:660
msgid "You can only select a tax of each class." msgid "You can only select a tax of each class."
msgstr "Solo podéis escoger un impuesto de cada clase." msgstr "Solo podéis escoger un impuesto de cada clase."
@ -563,79 +573,83 @@ msgstr "La confirmación no corresponde con la contraseña."
msgid "Selected language is not valid." msgid "Selected language is not valid."
msgstr "Habéis escogido un idioma que no es válido." msgstr "Habéis escogido un idioma que no es válido."
#: pkg/invoices.go:258 #: pkg/invoices.go:270
msgid "Select a customer to bill." msgid "Select a customer to bill."
msgstr "Escoged un cliente a facturar." msgstr "Escoged un cliente a facturar."
#: pkg/invoices.go:351 #: pkg/invoices.go:363 pkg/invoices.go:392
msgid "Invalid action" msgid "Invalid action"
msgstr "Acción inválida." msgstr "Acción inválida."
#: pkg/invoices.go:372 #: pkg/invoices.go:386
msgid "invoices.zip"
msgstr "facturas.zip"
#: pkg/invoices.go:433
msgctxt "input" msgctxt "input"
msgid "Customer" msgid "Customer"
msgstr "Cliente" msgstr "Cliente"
#: pkg/invoices.go:378 #: pkg/invoices.go:439
msgctxt "input" msgctxt "input"
msgid "Number" msgid "Number"
msgstr "Número" msgstr "Número"
#: pkg/invoices.go:384 #: pkg/invoices.go:445
msgctxt "input" msgctxt "input"
msgid "Invoice Date" msgid "Invoice Date"
msgstr "Fecha de factura" msgstr "Fecha de factura"
#: pkg/invoices.go:390 #: pkg/invoices.go:451
msgctxt "input" msgctxt "input"
msgid "Notes" msgid "Notes"
msgstr "Notas" msgstr "Notas"
#: pkg/invoices.go:396 #: pkg/invoices.go:457
msgctxt "input" msgctxt "input"
msgid "Payment Method" msgid "Payment Method"
msgstr "Método de pago" msgstr "Método de pago"
#: pkg/invoices.go:432 #: pkg/invoices.go:493
msgid "Invoice date can not be empty." msgid "Invoice date can not be empty."
msgstr "No podéis dejar la fecha de la factura en blanco." msgstr "No podéis dejar la fecha de la factura en blanco."
#: pkg/invoices.go:433 #: pkg/invoices.go:494
msgid "Invoice date must be a valid date." msgid "Invoice date must be a valid date."
msgstr "La fecha de factura debe ser válida." msgstr "La fecha de factura debe ser válida."
#: pkg/invoices.go:435 #: pkg/invoices.go:496
msgid "Selected payment method is not valid." msgid "Selected payment method is not valid."
msgstr "Habéis escogido un método de pago que no es válido." msgstr "Habéis escogido un método de pago que no es válido."
#: pkg/invoices.go:512 #: pkg/invoices.go:573
msgctxt "input" msgctxt "input"
msgid "Id" msgid "Id"
msgstr "Identificador" msgstr "Identificador"
#: pkg/invoices.go:535 #: pkg/invoices.go:596
msgctxt "input" msgctxt "input"
msgid "Quantity" msgid "Quantity"
msgstr "Cantidad" msgstr "Cantidad"
#: pkg/invoices.go:543 #: pkg/invoices.go:604
msgctxt "input" msgctxt "input"
msgid "Discount (%)" msgid "Discount (%)"
msgstr "Descuento (%)" msgstr "Descuento (%)"
#: pkg/invoices.go:592 #: pkg/invoices.go:653
msgid "Quantity can not be empty." msgid "Quantity can not be empty."
msgstr "No podéis dejar la cantidad en blanco." msgstr "No podéis dejar la cantidad en blanco."
#: pkg/invoices.go:593 #: pkg/invoices.go:654
msgid "Quantity must be a number greater than zero." msgid "Quantity must be a number greater than zero."
msgstr "La cantidad tiene que ser un número mayor a cero." msgstr "La cantidad tiene que ser un número mayor a cero."
#: pkg/invoices.go:595 #: pkg/invoices.go:656
msgid "Discount can not be empty." msgid "Discount can not be empty."
msgstr "No podéis dejar el descuento en blanco." msgstr "No podéis dejar el descuento en blanco."
#: pkg/invoices.go:596 #: pkg/invoices.go:657
msgid "Discount must be a percentage between 0 and 100." msgid "Discount must be a percentage between 0 and 100."
msgstr "El descuento tiene que ser un porcentaje entre 0 y 100." msgstr "El descuento tiene que ser un porcentaje entre 0 y 100."

View File

@ -8,10 +8,17 @@
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> / <a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a>{{( pgettext "Invoices" "title" )}}</a> <a>{{( pgettext "Invoices" "title" )}}</a>
</p> </p>
<p>
<a class="primary button" <form id="batch-form" action="{{ companyURI "/invoices/batch" }}" method="post">
href="{{ companyURI "/invoices/new" }}">{{( pgettext "New invoice" "action" )}}</a> {{ csrfToken }}
</p> <p>
<button type="submit"
name="action" value="download"
>{{( pgettext "Download invoices" "action" )}}</button>
<a class="primary button"
href="{{ companyURI "/invoices/new" }}">{{( pgettext "New invoice" "action" )}}</a>
</p>
</form>
</nav> </nav>
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.InvoicesIndexPage*/ -}} {{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.InvoicesIndexPage*/ -}}
@ -33,7 +40,11 @@
{{ with .Invoices }} {{ with .Invoices }}
{{- range $invoice := . }} {{- range $invoice := . }}
<tr> <tr>
<td></td> {{ $title := .Number | printf (pgettext "Select invoice %v" "action") }}
<td><input type="checkbox" form="batch-form"
name="invoice" value="{{ .Slug }}"
aria-label="{{ $title }}"
title="{{ $title }}"/></td>
<td>{{ .Date|formatDate }}</td> <td>{{ .Date|formatDate }}</td>
<td><a href="{{ companyURI "/invoices/"}}{{ .Slug }}">{{ .Number }}</a></td> <td><a href="{{ companyURI "/invoices/"}}{{ .Slug }}">{{ .Number }}</a></td>
<td><a href="{{ companyURI "/contacts/"}}{{ .CustomerSlug }}">{{ .CustomerName }}</a></td> <td><a href="{{ companyURI "/contacts/"}}{{ .CustomerSlug }}">{{ .CustomerName }}</a></td>