diff --git a/pkg/invoices.go b/pkg/invoices.go index c8188ff..eb6eb3b 100644 --- a/pkg/invoices.go +++ b/pkg/invoices.go @@ -64,6 +64,73 @@ func GetInvoiceForm(w http.ResponseWriter, r *http.Request, params httprouter.Pa mustRenderNewInvoiceForm(w, r, form) return } + + invoice := mustGetInvoice(r.Context(), conn, company, slug) + if invoice == nil { + http.NotFound(w, r) + return + } + mustRenderAppTemplate(w, r, "invoices/view.gohtml", invoice) +} + +type invoice struct { + Number string + Date time.Time + Invoicer taxDetails + Invoicee taxDetails + Notes string + Products []*invoiceProduct + Subtotal string + Taxes [][]string + Total string +} + +type taxDetails struct { + Name string + VATIN string + Address string + City string + PostalCode string + Province string + Email string + Phone string +} + +type invoiceProduct struct { + Name string + Description string + Price string + Quantity int + Total string +} + +func mustGetInvoice(ctx context.Context, conn *Conn, company *Company, slug string) *invoice { + inv := &invoice{} + var invoiceId int + var decimalDigits int + if notFoundErrorOrPanic(conn.QueryRow(ctx, "select invoice_id, decimal_digits, invoice_number, invoice_date, notes, business_name, vatin, phone, email, address, city, province, postal_code, to_price(subtotal, decimal_digits), to_price(total, decimal_digits) from invoice join contact using (contact_id) join invoice_amount using (invoice_id) join currency using (currency_code) where invoice.slug = $1", slug).Scan(&invoiceId, &decimalDigits, &inv.Number, &inv.Date, &inv.Notes, &inv.Invoicee.Name, &inv.Invoicee.VATIN, &inv.Invoicee.Phone, &inv.Invoicee.Email, &inv.Invoicee.Address, &inv.Invoicee.City, &inv.Invoicee.Province, &inv.Invoicee.PostalCode, &inv.Subtotal, &inv.Total)) { + return nil + } + if err := conn.QueryRow(ctx, "select business_name, vatin, phone, email, address, city, province, postal_code from company where company_id = $1", company.Id).Scan(&inv.Invoicer.Name, &inv.Invoicer.VATIN, &inv.Invoicer.Phone, &inv.Invoicer.Email, &inv.Invoicer.Address, &inv.Invoicer.City, &inv.Invoicer.Province, &inv.Invoicer.PostalCode); err != nil { + panic(err) + } + if err := conn.QueryRow(ctx, "select array_agg(array[name, to_price(amount, $2)]) from invoice_tax_amount join tax using (tax_id) where invoice_id = $1", invoiceId, decimalDigits).Scan(&inv.Taxes); err != nil { + panic(err) + } + rows := conn.MustQuery(ctx, "select name, description, to_price(price, $2), quantity, to_price(round(price * quantity * (1 - discount_rate))::integer, 2) from invoice_product where invoice_id = $1", invoiceId, decimalDigits) + defer rows.Close() + for rows.Next() { + product := &invoiceProduct{} + if err := rows.Scan(&product.Name, &product.Description, &product.Price, &product.Quantity, &product.Total); err != nil { + panic(err) + } + inv.Products = append(inv.Products, product) + } + if rows.Err() != nil { + panic(rows.Err()) + } + + return inv } type newInvoicePage struct { diff --git a/pkg/template.go b/pkg/template.go index 3ef5742..ee5a862 100644 --- a/pkg/template.go +++ b/pkg/template.go @@ -40,8 +40,8 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s } return p.Sprintf(locale.CurrencyPattern, company.DecimalDigits, number.Decimal(f), company.CurrencySymbol) }, - "formatDate": func(time time.Time) string { - return time.Format("02/01/2006") + "formatDate": func(time time.Time) template.HTML { + return template.HTML(`") }, "csrfToken": func() template.HTML { return template.HTML(fmt.Sprintf(``, csrfTokenField, user.CsrfToken)) diff --git a/po/ca.po b/po/ca.po index b295911..c6cc89e 100644 --- a/po/ca.po +++ b/po/ca.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: numerus\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n" -"POT-Creation-Date: 2023-02-22 14:35+0100\n" +"POT-Creation-Date: 2023-02-24 11:52+0100\n" "PO-Revision-Date: 2023-01-18 17:08+0100\n" "Last-Translator: jordi fita mas \n" "Language-Team: Catalan \n" @@ -24,17 +24,18 @@ msgid "Add Products to Invoice" msgstr "Afegeix productes a la factura" #: web/template/invoices/products.gohtml:9 web/template/invoices/new.gohtml:9 -#: web/template/invoices/index.gohtml:8 web/template/contacts/new.gohtml:9 -#: web/template/contacts/index.gohtml:8 web/template/contacts/edit.gohtml:9 -#: web/template/profile.gohtml:9 web/template/tax-details.gohtml:8 -#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:8 -#: web/template/products/edit.gohtml:9 +#: web/template/invoices/index.gohtml:8 web/template/invoices/view.gohtml:9 +#: web/template/contacts/new.gohtml:9 web/template/contacts/index.gohtml:8 +#: web/template/contacts/edit.gohtml:9 web/template/profile.gohtml:9 +#: web/template/tax-details.gohtml:8 web/template/products/new.gohtml:9 +#: web/template/products/index.gohtml:8 web/template/products/edit.gohtml:9 msgctxt "title" msgid "Home" msgstr "Inici" #: web/template/invoices/products.gohtml:10 web/template/invoices/new.gohtml:10 #: web/template/invoices/index.gohtml:2 web/template/invoices/index.gohtml:9 +#: web/template/invoices/view.gohtml:10 msgctxt "title" msgid "Invoices" msgstr "Factures" @@ -58,7 +59,7 @@ msgid "Name" msgstr "Nom" #: web/template/invoices/products.gohtml:43 -#: web/template/products/index.gohtml:23 +#: web/template/invoices/view.gohtml:42 web/template/products/index.gohtml:23 msgctxt "title" msgid "Price" msgstr "Preu" @@ -68,17 +69,28 @@ msgstr "Preu" msgid "No products added yet." msgstr "No hi ha cap producte." -#: web/template/invoices/products.gohtml:64 web/template/invoices/new.gohtml:38 +#: web/template/invoices/products.gohtml:64 web/template/invoices/new.gohtml:59 msgctxt "action" msgid "Add products" msgstr "Afegeix productes" -#: web/template/invoices/new.gohtml:40 +#: web/template/invoices/new.gohtml:41 web/template/invoices/view.gohtml:44 +#: web/template/invoices/view.gohtml:71 +msgctxt "title" +msgid "Subtotal" +msgstr "Subtotal" + +#: web/template/invoices/new.gohtml:51 web/template/invoices/view.gohtml:81 +msgctxt "title" +msgid "Total" +msgstr "Total" + +#: web/template/invoices/new.gohtml:61 msgctxt "action" msgid "Update" msgstr "Actualitza" -#: web/template/invoices/new.gohtml:42 web/template/invoices/index.gohtml:13 +#: web/template/invoices/new.gohtml:63 web/template/invoices/index.gohtml:13 msgctxt "action" msgid "New invoice" msgstr "Nova factura" @@ -88,7 +100,7 @@ msgctxt "invoice" msgid "All" msgstr "Totes" -#: web/template/invoices/index.gohtml:22 +#: web/template/invoices/index.gohtml:22 web/template/invoices/view.gohtml:18 msgctxt "title" msgid "Date" msgstr "Data" @@ -127,6 +139,21 @@ msgstr "Descàrrega" msgid "No invoices added yet." msgstr "No hi ha cap factura." +#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:17 +msgctxt "title" +msgid "Invoice %s" +msgstr "Factura %s" + +#: web/template/invoices/view.gohtml:41 +msgctxt "title" +msgid "Concept" +msgstr "Concepte" + +#: web/template/invoices/view.gohtml:43 +msgctxt "title" +msgid "Units" +msgstr "Unitats" + #: web/template/dashboard.gohtml:2 msgctxt "title" msgid "Dashboard" @@ -337,40 +364,40 @@ msgstr "No podeu deixar la contrasenya en blanc." msgid "Invalid user or password." msgstr "Nom d’usuari o contrasenya incorrectes." -#: pkg/products.go:165 pkg/invoices.go:304 +#: pkg/products.go:165 pkg/invoices.go:392 msgctxt "input" msgid "Name" msgstr "Nom" -#: pkg/products.go:171 pkg/invoices.go:309 +#: pkg/products.go:171 pkg/invoices.go:397 msgctxt "input" msgid "Description" msgstr "Descripció" -#: pkg/products.go:176 pkg/invoices.go:313 +#: pkg/products.go:176 pkg/invoices.go:401 msgctxt "input" msgid "Price" msgstr "Preu" -#: pkg/products.go:186 pkg/invoices.go:219 pkg/invoices.go:339 +#: pkg/products.go:186 pkg/invoices.go:307 pkg/invoices.go:427 msgctxt "input" msgid "Taxes" msgstr "Imposts" -#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:252 -#: pkg/invoices.go:375 +#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:340 +#: pkg/invoices.go:463 msgid "Name can not be empty." msgstr "No podeu deixar el nom en blanc." -#: pkg/products.go:207 pkg/invoices.go:376 +#: pkg/products.go:207 pkg/invoices.go:464 msgid "Price can not be empty." msgstr "No podeu deixar el preu en blanc." -#: pkg/products.go:208 pkg/invoices.go:377 +#: pkg/products.go:208 pkg/invoices.go:465 msgid "Price must be a number greater than zero." msgstr "El preu ha de ser un número major a zero." -#: pkg/products.go:210 pkg/invoices.go:256 pkg/invoices.go:385 +#: pkg/products.go:210 pkg/invoices.go:344 pkg/invoices.go:473 msgid "Selected tax is not valid." msgstr "Heu seleccionat un impost que no és vàlid." @@ -433,70 +460,70 @@ msgstr "La confirmació no és igual a la contrasenya." msgid "Selected language is not valid." msgstr "Heu seleccionat un idioma que no és vàlid." -#: pkg/invoices.go:71 +#: pkg/invoices.go:158 msgid "Select a customer to bill." msgstr "Escolliu un client a facturar." -#: pkg/invoices.go:145 +#: pkg/invoices.go:233 msgid "Invalid action" msgstr "Acció invàlida." -#: pkg/invoices.go:196 +#: pkg/invoices.go:284 msgctxt "input" msgid "Customer" msgstr "Client" -#: pkg/invoices.go:202 +#: pkg/invoices.go:290 msgctxt "input" msgid "Number" msgstr "Número" -#: pkg/invoices.go:208 +#: pkg/invoices.go:296 msgctxt "input" msgid "Invoice Date" msgstr "Data de factura" -#: pkg/invoices.go:214 +#: pkg/invoices.go:302 msgctxt "input" msgid "Notes" msgstr "Notes" -#: pkg/invoices.go:253 +#: pkg/invoices.go:341 msgid "Invoice date can not be empty." msgstr "No podeu deixar la data de la factura en blanc." -#: pkg/invoices.go:254 +#: pkg/invoices.go:342 msgid "Invoice date must be a valid date." msgstr "La data de facturació ha de ser vàlida." -#: pkg/invoices.go:299 +#: pkg/invoices.go:387 msgctxt "input" msgid "Id" msgstr "Identificador" -#: pkg/invoices.go:322 +#: pkg/invoices.go:410 msgctxt "input" msgid "Quantity" msgstr "Quantitat" -#: pkg/invoices.go:330 +#: pkg/invoices.go:418 msgctxt "input" msgid "Discount (%)" msgstr "Descompte (%)" -#: pkg/invoices.go:379 +#: pkg/invoices.go:467 msgid "Quantity can not be empty." msgstr "No podeu deixar la quantitat en blanc." -#: pkg/invoices.go:380 +#: pkg/invoices.go:468 msgid "Quantity must be a number greater than zero." msgstr "La quantitat ha de ser un número major a zero." -#: pkg/invoices.go:382 +#: pkg/invoices.go:470 msgid "Discount can not be empty." msgstr "No podeu deixar el descompte en blanc." -#: pkg/invoices.go:383 +#: pkg/invoices.go:471 msgid "Discount must be a percentage between 0 and 100." msgstr "El descompte ha de ser un percentatge entre 0 i 100." diff --git a/po/es.po b/po/es.po index f5b1c58..d3a2596 100644 --- a/po/es.po +++ b/po/es.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: numerus\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n" -"POT-Creation-Date: 2023-02-22 14:35+0100\n" +"POT-Creation-Date: 2023-02-24 11:52+0100\n" "PO-Revision-Date: 2023-01-18 17:45+0100\n" "Last-Translator: jordi fita mas \n" "Language-Team: Spanish \n" @@ -24,17 +24,18 @@ msgid "Add Products to Invoice" msgstr "Añadir productos a la factura" #: web/template/invoices/products.gohtml:9 web/template/invoices/new.gohtml:9 -#: web/template/invoices/index.gohtml:8 web/template/contacts/new.gohtml:9 -#: web/template/contacts/index.gohtml:8 web/template/contacts/edit.gohtml:9 -#: web/template/profile.gohtml:9 web/template/tax-details.gohtml:8 -#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:8 -#: web/template/products/edit.gohtml:9 +#: web/template/invoices/index.gohtml:8 web/template/invoices/view.gohtml:9 +#: web/template/contacts/new.gohtml:9 web/template/contacts/index.gohtml:8 +#: web/template/contacts/edit.gohtml:9 web/template/profile.gohtml:9 +#: web/template/tax-details.gohtml:8 web/template/products/new.gohtml:9 +#: web/template/products/index.gohtml:8 web/template/products/edit.gohtml:9 msgctxt "title" msgid "Home" msgstr "Inicio" #: web/template/invoices/products.gohtml:10 web/template/invoices/new.gohtml:10 #: web/template/invoices/index.gohtml:2 web/template/invoices/index.gohtml:9 +#: web/template/invoices/view.gohtml:10 msgctxt "title" msgid "Invoices" msgstr "Facturas" @@ -58,7 +59,7 @@ msgid "Name" msgstr "Nombre" #: web/template/invoices/products.gohtml:43 -#: web/template/products/index.gohtml:23 +#: web/template/invoices/view.gohtml:42 web/template/products/index.gohtml:23 msgctxt "title" msgid "Price" msgstr "Precio" @@ -68,17 +69,28 @@ msgstr "Precio" msgid "No products added yet." msgstr "No hay productos." -#: web/template/invoices/products.gohtml:64 web/template/invoices/new.gohtml:38 +#: web/template/invoices/products.gohtml:64 web/template/invoices/new.gohtml:59 msgctxt "action" msgid "Add products" msgstr "Añadir productos" -#: web/template/invoices/new.gohtml:40 +#: web/template/invoices/new.gohtml:41 web/template/invoices/view.gohtml:44 +#: web/template/invoices/view.gohtml:71 +msgctxt "title" +msgid "Subtotal" +msgstr "Subtotal" + +#: web/template/invoices/new.gohtml:51 web/template/invoices/view.gohtml:81 +msgctxt "title" +msgid "Total" +msgstr "Total" + +#: web/template/invoices/new.gohtml:61 msgctxt "action" msgid "Update" msgstr "Actualizar" -#: web/template/invoices/new.gohtml:42 web/template/invoices/index.gohtml:13 +#: web/template/invoices/new.gohtml:63 web/template/invoices/index.gohtml:13 msgctxt "action" msgid "New invoice" msgstr "Nueva factura" @@ -88,7 +100,7 @@ msgctxt "invoice" msgid "All" msgstr "Todas" -#: web/template/invoices/index.gohtml:22 +#: web/template/invoices/index.gohtml:22 web/template/invoices/view.gohtml:18 msgctxt "title" msgid "Date" msgstr "Fecha" @@ -127,6 +139,21 @@ msgstr "Descargar" msgid "No invoices added yet." msgstr "No hay facturas." +#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:17 +msgctxt "title" +msgid "Invoice %s" +msgstr "Factura %s" + +#: web/template/invoices/view.gohtml:41 +msgctxt "title" +msgid "Concept" +msgstr "Concepto" + +#: web/template/invoices/view.gohtml:43 +msgctxt "title" +msgid "Units" +msgstr "Unidades" + #: web/template/dashboard.gohtml:2 msgctxt "title" msgid "Dashboard" @@ -337,40 +364,40 @@ msgstr "No podéis dejar la contraseña en blanco." msgid "Invalid user or password." msgstr "Nombre de usuario o contraseña inválido." -#: pkg/products.go:165 pkg/invoices.go:304 +#: pkg/products.go:165 pkg/invoices.go:392 msgctxt "input" msgid "Name" msgstr "Nombre" -#: pkg/products.go:171 pkg/invoices.go:309 +#: pkg/products.go:171 pkg/invoices.go:397 msgctxt "input" msgid "Description" msgstr "Descripción" -#: pkg/products.go:176 pkg/invoices.go:313 +#: pkg/products.go:176 pkg/invoices.go:401 msgctxt "input" msgid "Price" msgstr "Precio" -#: pkg/products.go:186 pkg/invoices.go:219 pkg/invoices.go:339 +#: pkg/products.go:186 pkg/invoices.go:307 pkg/invoices.go:427 msgctxt "input" msgid "Taxes" msgstr "Impuestos" -#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:252 -#: pkg/invoices.go:375 +#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:340 +#: pkg/invoices.go:463 msgid "Name can not be empty." msgstr "No podéis dejar el nombre en blanco." -#: pkg/products.go:207 pkg/invoices.go:376 +#: pkg/products.go:207 pkg/invoices.go:464 msgid "Price can not be empty." msgstr "No podéis dejar el precio en blanco." -#: pkg/products.go:208 pkg/invoices.go:377 +#: pkg/products.go:208 pkg/invoices.go:465 msgid "Price must be a number greater than zero." msgstr "El precio tiene que ser un número mayor a cero." -#: pkg/products.go:210 pkg/invoices.go:256 pkg/invoices.go:385 +#: pkg/products.go:210 pkg/invoices.go:344 pkg/invoices.go:473 msgid "Selected tax is not valid." msgstr "Habéis escogido un impuesto que no es válido." @@ -433,70 +460,70 @@ msgstr "La confirmación no corresponde con la contraseña." msgid "Selected language is not valid." msgstr "Habéis escogido un idioma que no es válido." -#: pkg/invoices.go:71 +#: pkg/invoices.go:158 msgid "Select a customer to bill." msgstr "Escoged un cliente a facturar." -#: pkg/invoices.go:145 +#: pkg/invoices.go:233 msgid "Invalid action" msgstr "Acción inválida." -#: pkg/invoices.go:196 +#: pkg/invoices.go:284 msgctxt "input" msgid "Customer" msgstr "Cliente" -#: pkg/invoices.go:202 +#: pkg/invoices.go:290 msgctxt "input" msgid "Number" msgstr "Número" -#: pkg/invoices.go:208 +#: pkg/invoices.go:296 msgctxt "input" msgid "Invoice Date" msgstr "Fecha de factura" -#: pkg/invoices.go:214 +#: pkg/invoices.go:302 msgctxt "input" msgid "Notes" msgstr "Notas" -#: pkg/invoices.go:253 +#: pkg/invoices.go:341 msgid "Invoice date can not be empty." msgstr "No podéis dejar la fecha de la factura en blanco." -#: pkg/invoices.go:254 +#: pkg/invoices.go:342 msgid "Invoice date must be a valid date." msgstr "La fecha de factura debe ser válida." -#: pkg/invoices.go:299 +#: pkg/invoices.go:387 msgctxt "input" msgid "Id" msgstr "Identificador" -#: pkg/invoices.go:322 +#: pkg/invoices.go:410 msgctxt "input" msgid "Quantity" msgstr "Cantidad" -#: pkg/invoices.go:330 +#: pkg/invoices.go:418 msgctxt "input" msgid "Discount (%)" msgstr "Descuento (%)" -#: pkg/invoices.go:379 +#: pkg/invoices.go:467 msgid "Quantity can not be empty." msgstr "No podéis dejar la cantidad en blanco." -#: pkg/invoices.go:380 +#: pkg/invoices.go:468 msgid "Quantity must be a number greater than zero." msgstr "La cantidad tiene que ser un número mayor a cero." -#: pkg/invoices.go:382 +#: pkg/invoices.go:470 msgid "Discount can not be empty." msgstr "No podéis dejar el descuento en blanco." -#: pkg/invoices.go:383 +#: pkg/invoices.go:471 msgid "Discount must be a percentage between 0 and 100." msgstr "El descuento tiene que ser un percentage entre 0 y 100." diff --git a/web/static/invoice.css b/web/static/invoice.css new file mode 100644 index 0000000..1d0ad36 --- /dev/null +++ b/web/static/invoice.css @@ -0,0 +1,126 @@ +.invoice { + display: flex; + gap: 2.5rem; +} + +.invoice header { + flex: 1; + min-width: 20rem; + padding: 0 1rem 0 0; + display: block; + background: initial; + border-right: 2px dashed lightgray; +} + +.invoice h1 { + font-size: 1.6rem; +} + +.invoice .invoicer { + margin-top: 20rem; +} + +.invoice > div { + display: flex; + flex-direction: column; + flex: 3; + gap: 5rem; + align-items: end; +} + +.invoice th { + font-weight: normal; + text-transform: uppercase; +} + +.invoice thead th { + text-align: left; + border-bottom: 2px solid black; +} + +.invoice .tfoot th, .invoice .numeric { + text-align: right; +} + +.invoice .notes, .invoice footer { + white-space: pre; + text-align: right; +} + +.invoice td { + vertical-align: top; +} + +.invoice tbody, .invoice footer { + page-break-inside: avoid; +} + +.invoice tbody tr { + background-color: initial; +} + + +.invoice tbody:not(:first-of-type) tr:first-child td, .invoice .tfoot th, .invoice .tfoot td { + padding-top: 1em; +} + +.invoice tbody .name td:first-child { + font-weight: bold; +} + +.invoice tbody td:first-child { + max-width: 20rem; +} + +.invoice footer { + margin-top: 8rem; + font-size: 1.2rem; +} + +@media print { + @page { + size: A4; + } + + *, *::before, *::after { + box-sizing: border-box; + } + + * { + margin: 0; + } + + html, body { + height: 100%; + } + + html { + font-family: sans-serif; + font-size: 62.5%; + } + + body { + background-color: white; + color: black; + font-size: 1.6rem; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + } + + p, h1, h2, h3, h4, h5, h6 { + overflow-wrap: break-word; + } + + table { + width: 100%; + border-collapse: collapse; + } + + body > header, nav { + display: none; + } + + .invoice { + height: 100vh; + } +} diff --git a/web/template/invoices/view.gohtml b/web/template/invoices/view.gohtml new file mode 100644 index 0000000..cb22192 --- /dev/null +++ b/web/template/invoices/view.gohtml @@ -0,0 +1,102 @@ +{{ define "title" -}} + {{ .Number | printf ( pgettext "Invoice %s" "title" )}} +{{- end }} + +{{ define "content" }} + {{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.invoice*/ -}} + + +
+
+

{{ .Number | printf ( pgettext "Invoice %s" "title" )}}

+

{{( pgettext "Date" "title" )}} {{ .Date | formatDate }}

+ +
+ {{ .Invoicer.Name }}
+ {{ .Invoicer.VATIN }}
+ {{ .Invoicer.Address }}
+ {{ .Invoicer.City }} ({{ .Invoicer.PostalCode}}), {{ .Invoicer.Province }}
+ {{ .Invoicer.Email }}
+ {{ .Invoicer.Phone }}
+
+
+ +
+
+ {{ .Invoicee.Name }}
+ {{ .Invoicee.VATIN }}
+ {{ .Invoicee.Address }}
+ {{ .Invoicee.City }} ({{ .Invoicee.PostalCode}}), {{ .Invoicee.Province }}
+
+ + + + + + + + + + + {{ range $product := .Products -}} + {{ if .Description }} + + + + + + + + + + + + {{ else }} + + + + + + + + + {{- end }} + {{- end }} + + + + + + {{ range $tax := .Taxes -}} + + + + + {{- end }} + + + + + +
{{( pgettext "Concept" "title" )}}{{( pgettext "Price" "title" )}}{{( pgettext "Units" "title" )}}{{( pgettext "Subtotal" "title" )}}
{{ .Name }}
{{ .Description }}{{ .Price | formatPrice }}{{ .Quantity }}{{ .Total | formatPrice }}
{{ .Name }}{{ .Price | formatPrice }}{{ .Quantity }}{{ .Total | formatPrice }}
{{( pgettext "Subtotal" "title" )}}{{ .Subtotal | formatPrice }}
{{ index . 0 }}{{ index . 1 | formatPrice }}
{{( pgettext "Total" "title" )}}{{ .Total | formatPrice }}
+ +
{{ .Notes }}
+ + +
+
+{{- end}}