Compare commits

..

No commits in common. "4d2379555e3547a20c6647183a5fd4f0c276af71" and "843f7746cf4b53399c30a0e31fff362420528d8f" have entirely different histories.

8 changed files with 75 additions and 147 deletions

5
debian/control vendored
View File

@ -32,7 +32,7 @@ Depends:
${misc:Depends},
golang-github-jackc-pgx-v4-dev
Description: Simple invoicing and accounting web application
A simple web application to keep invoice and accounting records, intended for
A simple web application to keep invoice and accouting records, intended for
freelancers working in Spain.
.
This is the dev package.
@ -41,8 +41,7 @@ Package: numerus
Architecture: any
Depends:
${shlibs:Depends},
${misc:Depends},
weasyprint
${misc:Depends}
Built-Using: ${misc:Built-Using}
Description: Simple invoicing and accounting web application
A simple web application to keep invoice and accouting records, intended for

View File

@ -1,18 +1,13 @@
package pkg
import (
"bytes"
"context"
"fmt"
"github.com/julienschmidt/httprouter"
"html/template"
"io"
"log"
"math"
"net/http"
"os/exec"
"strconv"
"strings"
"time"
)
@ -57,7 +52,7 @@ func mustCollectInvoiceEntries(ctx context.Context, conn *Conn, company *Company
return entries
}
func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
func GetInvoiceForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
locale := getLocale(r)
conn := getConn(r)
company := mustGetCompany(r)
@ -70,52 +65,16 @@ func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Para
return
}
pdf := false
if strings.HasSuffix(slug, ".pdf") {
pdf = true
slug = slug[:len(slug)-len(".pdf")]
}
invoice := mustGetInvoice(r.Context(), conn, company, slug)
if invoice == nil {
http.NotFound(w, r)
return
}
if pdf {
cmd := exec.Command("weasyprint", "--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 stdout.Close()
if err = cmd.Start(); err != nil {
panic(err)
}
go func() {
defer stdin.Close()
mustRenderAppTemplate(stdin, r, "invoices/view.gohtml", invoice)
}()
w.Header().Set("Content-Type", "application/pdf")
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)
}
} else {
mustRenderAppTemplate(w, r, "invoices/view.gohtml", invoice)
}
mustRenderAppTemplate(w, r, "invoices/view.gohtml", invoice)
}
type invoice struct {
Number string
Slug string
Date time.Time
Invoicer taxDetails
Invoicee taxDetails
@ -146,9 +105,7 @@ type invoiceProduct struct {
}
func mustGetInvoice(ctx context.Context, conn *Conn, company *Company, slug string) *invoice {
inv := &invoice{
Slug: slug,
}
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)) {

View File

@ -24,7 +24,7 @@ func NewRouter(db *Db) http.Handler {
companyRouter.PUT("/products/:slug", HandleUpdateProduct)
companyRouter.GET("/invoices", IndexInvoices)
companyRouter.POST("/invoices", HandleAddInvoice)
companyRouter.GET("/invoices/:slug", ServeInvoice)
companyRouter.GET("/invoices/:slug", GetInvoiceForm)
companyRouter.POST("/invoices/new/products", HandleAddProductsToInvoice)
companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
mustRenderAppTemplate(w, r, "dashboard.gohtml", nil)

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-02-26 17:11+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 <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n"
@ -59,7 +59,7 @@ msgid "Name"
msgstr "Nom"
#: web/template/invoices/products.gohtml:43
#: web/template/invoices/view.gohtml:59 web/template/products/index.gohtml:23
#: web/template/invoices/view.gohtml:42 web/template/products/index.gohtml:23
msgctxt "title"
msgid "Price"
msgstr "Preu"
@ -74,13 +74,13 @@ msgctxt "action"
msgid "Add products"
msgstr "Afegeix productes"
#: web/template/invoices/new.gohtml:41 web/template/invoices/view.gohtml:61
#: web/template/invoices/view.gohtml:87
#: 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:97
#: web/template/invoices/new.gohtml:51 web/template/invoices/view.gohtml:81
msgctxt "title"
msgid "Total"
msgstr "Total"
@ -100,7 +100,7 @@ msgctxt "invoice"
msgid "All"
msgstr "Totes"
#: web/template/invoices/index.gohtml:22 web/template/invoices/view.gohtml:25
#: web/template/invoices/index.gohtml:22 web/template/invoices/view.gohtml:18
msgctxt "title"
msgid "Date"
msgstr "Data"
@ -135,26 +135,21 @@ msgctxt "title"
msgid "Download"
msgstr "Descàrrega"
#: web/template/invoices/index.gohtml:50
#: web/template/invoices/index.gohtml:47
msgid "No invoices added yet."
msgstr "No hi ha cap factura."
#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:24
#: 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:16
msgctxt "action"
msgid "Download invoice"
msgstr "Descarrega factura"
#: web/template/invoices/view.gohtml:58
#: web/template/invoices/view.gohtml:41
msgctxt "title"
msgid "Concept"
msgstr "Concepte"
#: web/template/invoices/view.gohtml:60
#: web/template/invoices/view.gohtml:43
msgctxt "title"
msgid "Units"
msgstr "Unitats"
@ -369,40 +364,40 @@ msgstr "No podeu deixar la contrasenya en blanc."
msgid "Invalid user or password."
msgstr "Nom dusuari o contrasenya incorrectes."
#: pkg/products.go:165 pkg/invoices.go:435
#: pkg/products.go:165 pkg/invoices.go:392
msgctxt "input"
msgid "Name"
msgstr "Nom"
#: pkg/products.go:171 pkg/invoices.go:440
#: pkg/products.go:171 pkg/invoices.go:397
msgctxt "input"
msgid "Description"
msgstr "Descripció"
#: pkg/products.go:176 pkg/invoices.go:444
#: pkg/products.go:176 pkg/invoices.go:401
msgctxt "input"
msgid "Price"
msgstr "Preu"
#: pkg/products.go:186 pkg/invoices.go:350 pkg/invoices.go:470
#: 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:383
#: pkg/invoices.go:506
#: 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:507
#: 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:508
#: 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:387 pkg/invoices.go:516
#: 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."
@ -465,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:201
#: pkg/invoices.go:158
msgid "Select a customer to bill."
msgstr "Escolliu un client a facturar."
#: pkg/invoices.go:276
#: pkg/invoices.go:233
msgid "Invalid action"
msgstr "Acció invàlida."
#: pkg/invoices.go:327
#: pkg/invoices.go:284
msgctxt "input"
msgid "Customer"
msgstr "Client"
#: pkg/invoices.go:333
#: pkg/invoices.go:290
msgctxt "input"
msgid "Number"
msgstr "Número"
#: pkg/invoices.go:339
#: pkg/invoices.go:296
msgctxt "input"
msgid "Invoice Date"
msgstr "Data de factura"
#: pkg/invoices.go:345
#: pkg/invoices.go:302
msgctxt "input"
msgid "Notes"
msgstr "Notes"
#: pkg/invoices.go:384
#: 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:385
#: 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:430
#: pkg/invoices.go:387
msgctxt "input"
msgid "Id"
msgstr "Identificador"
#: pkg/invoices.go:453
#: pkg/invoices.go:410
msgctxt "input"
msgid "Quantity"
msgstr "Quantitat"
#: pkg/invoices.go:461
#: pkg/invoices.go:418
msgctxt "input"
msgid "Discount (%)"
msgstr "Descompte (%)"
#: pkg/invoices.go:510
#: pkg/invoices.go:467
msgid "Quantity can not be empty."
msgstr "No podeu deixar la quantitat en blanc."
#: pkg/invoices.go:511
#: 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:513
#: pkg/invoices.go:470
msgid "Discount can not be empty."
msgstr "No podeu deixar el descompte en blanc."
#: pkg/invoices.go:514
#: 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."

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-02-26 17:11+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 <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n"
@ -59,7 +59,7 @@ msgid "Name"
msgstr "Nombre"
#: web/template/invoices/products.gohtml:43
#: web/template/invoices/view.gohtml:59 web/template/products/index.gohtml:23
#: web/template/invoices/view.gohtml:42 web/template/products/index.gohtml:23
msgctxt "title"
msgid "Price"
msgstr "Precio"
@ -74,13 +74,13 @@ msgctxt "action"
msgid "Add products"
msgstr "Añadir productos"
#: web/template/invoices/new.gohtml:41 web/template/invoices/view.gohtml:61
#: web/template/invoices/view.gohtml:87
#: 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:97
#: web/template/invoices/new.gohtml:51 web/template/invoices/view.gohtml:81
msgctxt "title"
msgid "Total"
msgstr "Total"
@ -100,7 +100,7 @@ msgctxt "invoice"
msgid "All"
msgstr "Todas"
#: web/template/invoices/index.gohtml:22 web/template/invoices/view.gohtml:25
#: web/template/invoices/index.gohtml:22 web/template/invoices/view.gohtml:18
msgctxt "title"
msgid "Date"
msgstr "Fecha"
@ -135,26 +135,21 @@ msgctxt "title"
msgid "Download"
msgstr "Descargar"
#: web/template/invoices/index.gohtml:50
#: web/template/invoices/index.gohtml:47
msgid "No invoices added yet."
msgstr "No hay facturas."
#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:24
#: 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:16
msgctxt "action"
msgid "Download invoice"
msgstr "Descargar factura"
#: web/template/invoices/view.gohtml:58
#: web/template/invoices/view.gohtml:41
msgctxt "title"
msgid "Concept"
msgstr "Concepto"
#: web/template/invoices/view.gohtml:60
#: web/template/invoices/view.gohtml:43
msgctxt "title"
msgid "Units"
msgstr "Unidades"
@ -369,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:435
#: pkg/products.go:165 pkg/invoices.go:392
msgctxt "input"
msgid "Name"
msgstr "Nombre"
#: pkg/products.go:171 pkg/invoices.go:440
#: pkg/products.go:171 pkg/invoices.go:397
msgctxt "input"
msgid "Description"
msgstr "Descripción"
#: pkg/products.go:176 pkg/invoices.go:444
#: pkg/products.go:176 pkg/invoices.go:401
msgctxt "input"
msgid "Price"
msgstr "Precio"
#: pkg/products.go:186 pkg/invoices.go:350 pkg/invoices.go:470
#: 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:383
#: pkg/invoices.go:506
#: 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:507
#: 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:508
#: 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:387 pkg/invoices.go:516
#: 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."
@ -465,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:201
#: pkg/invoices.go:158
msgid "Select a customer to bill."
msgstr "Escoged un cliente a facturar."
#: pkg/invoices.go:276
#: pkg/invoices.go:233
msgid "Invalid action"
msgstr "Acción inválida."
#: pkg/invoices.go:327
#: pkg/invoices.go:284
msgctxt "input"
msgid "Customer"
msgstr "Cliente"
#: pkg/invoices.go:333
#: pkg/invoices.go:290
msgctxt "input"
msgid "Number"
msgstr "Número"
#: pkg/invoices.go:339
#: pkg/invoices.go:296
msgctxt "input"
msgid "Invoice Date"
msgstr "Fecha de factura"
#: pkg/invoices.go:345
#: pkg/invoices.go:302
msgctxt "input"
msgid "Notes"
msgstr "Notas"
#: pkg/invoices.go:384
#: 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:385
#: pkg/invoices.go:342
msgid "Invoice date must be a valid date."
msgstr "La fecha de factura debe ser válida."
#: pkg/invoices.go:430
#: pkg/invoices.go:387
msgctxt "input"
msgid "Id"
msgstr "Identificador"
#: pkg/invoices.go:453
#: pkg/invoices.go:410
msgctxt "input"
msgid "Quantity"
msgstr "Cantidad"
#: pkg/invoices.go:461
#: pkg/invoices.go:418
msgctxt "input"
msgid "Discount (%)"
msgstr "Descuento (%)"
#: pkg/invoices.go:510
#: pkg/invoices.go:467
msgid "Quantity can not be empty."
msgstr "No podéis dejar la cantidad en blanco."
#: pkg/invoices.go:511
#: 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:513
#: pkg/invoices.go:470
msgid "Discount can not be empty."
msgstr "No podéis dejar el descuento en blanco."
#: pkg/invoices.go:514
#: 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."

View File

@ -508,15 +508,6 @@ main > nav {
text-align: right;
}
.invoice-download {
text-align: center;
}
.invoice-download a {
color: inherit;
text-decoration: none;
}
/* Remix Icon */
@font-face {

View File

@ -39,10 +39,7 @@
<td class="invoice-status-{{ .Status }}">{{ .StatusLabel }}</td>
<td></td>
<td class="numeric">{{ .Total|formatPrice }}</td>
<td class="invoice-download"><a href="{{ companyURI "/invoices/"}}{{ .Slug }}.pdf" download="{{ .Number}}.pdf"
title="{{( pgettext "Download invoice" "action" )}}"
aria-label="{{( pgettext "Download invoice %s" "action" )}}"><i
class="ri-download-line"></i></a></td>
<td></td>
</tr>
{{- end }}
{{ else }}

View File

@ -10,13 +10,7 @@
<a href="{{ companyURI "/invoices"}}">{{( pgettext "Invoices" "title" )}}</a> /
<a>{{ .Number }}</a>
</p>
<p>
<a class="primary button"
href="{{ companyURI "/invoices/" }}{{ .Slug }}.pdf"
download="{{ .Number}}.pdf">{{( pgettext "Download invoice" "action" )}}</a>
</p>
</nav>
<link rel="stylesheet" type="text/css" href="/static/invoice.css">
<article class="invoice">
<header>