Add button to download many invoices as PDF in a ZIP archive
This commit is contained in:
parent
f3b841473f
commit
5dedaefc22
119
pkg/invoices.go
119
pkg/invoices.go
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
94
po/ca.po
94
po/ca.po
|
@ -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 d’usuari o contrasenya incorrectes."
|
msgstr "Nom d’usuari 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."
|
||||||
|
|
||||||
|
|
94
po/es.po
94
po/es.po
|
@ -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."
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue