diff --git a/pkg/form.go b/pkg/form.go
index e984d4e..964953f 100644
--- a/pkg/form.go
+++ b/pkg/form.go
@@ -140,6 +140,10 @@ func (field *SelectField) isValidOption(selected string) bool {
return field.FindOption(selected) != nil
}
+func (field *SelectField) Clear() {
+ field.Selected = []string{}
+}
+
func MustGetOptions(ctx context.Context, conn *Conn, sql string, args ...interface{}) []*SelectOption {
rows, err := conn.Query(ctx, sql, args...)
if err != nil {
diff --git a/pkg/invoices.go b/pkg/invoices.go
index 7bf541d..c203950 100644
--- a/pkg/invoices.go
+++ b/pkg/invoices.go
@@ -88,6 +88,10 @@ func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Para
form := newInvoiceForm(r.Context(), conn, locale, company)
slug := params[0].Value
if slug == "new" {
+ if invoiceToDuplicate := r.URL.Query().Get("duplicate"); invoiceToDuplicate != "" {
+ form.MustFillFromDatabase(r.Context(), conn, invoiceToDuplicate)
+ form.Number.Val = ""
+ }
form.Date.Val = time.Now().Format("2006-01-02")
w.WriteHeader(http.StatusOK)
mustRenderNewInvoiceForm(w, r, form)
@@ -451,9 +455,13 @@ func (form *invoiceForm) Update() {
}
func (form *invoiceForm) AddProducts(ctx context.Context, conn *Conn, productsId []string) {
+ form.mustAddProductsFromQuery(ctx, conn, "select product_id, name, description, to_price(price, decimal_digits), 1 as quantity, 0 as discount, array_remove(array_agg(tax_id), null) from product join company using (company_id) join currency using (currency_code) left join product_tax using (product_id) where product_id = any ($1) group by product_id, name, description, price, decimal_digits", productsId)
+}
+
+func (form *invoiceForm) mustAddProductsFromQuery(ctx context.Context, conn *Conn, sql string, args ...interface{}) {
index := len(form.Products)
taxOptions := mustGetTaxOptions(ctx, conn, form.company)
- rows := conn.MustQuery(ctx, "select product_id, name, description, to_price(price, decimal_digits), 1 as quantity, 0 as discount, array_remove(array_agg(tax_id), null) from product join company using (company_id) join currency using (currency_code) left join product_tax using (product_id) where product_id = any ($1) group by product_id, name, description, price, decimal_digits", productsId)
+ rows := conn.MustQuery(ctx, sql, args...)
defer rows.Close()
for rows.Next() {
product := newInvoiceProductForm(index, form.company, form.locale, taxOptions)
@@ -468,6 +476,18 @@ func (form *invoiceForm) AddProducts(ctx context.Context, conn *Conn, productsId
}
}
+func (form *invoiceForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug string) {
+ var invoiceId int
+ selectedPaymentMethod := form.PaymentMethod.Selected
+ form.PaymentMethod.Clear()
+ if notFoundErrorOrPanic(conn.QueryRow(ctx, "select invoice_id, contact_id, invoice_number, invoice_date, notes, payment_method_id from invoice where slug = $1", slug).Scan(&invoiceId, form.Customer, form.Number, form.Date, form.Notes, form.PaymentMethod)) {
+ form.PaymentMethod.Selected = selectedPaymentMethod
+ return
+ }
+ form.Products = []*invoiceProductForm{}
+ form.mustAddProductsFromQuery(ctx, conn, "select product_id, name, description, to_price(price, $2), quantity, (discount_rate * 100)::integer, array_remove(array_agg(tax_id), null) from invoice_product left join invoice_product_tax using (invoice_product_id) where invoice_id = $1 group by product_id, name, description, discount_rate, price, quantity", invoiceId, form.company.DecimalDigits)
+}
+
func mustGetTaxOptions(ctx context.Context, conn *Conn, company *Company) []*SelectOption {
return MustGetGroupedOptions(ctx, conn, "select tax_id::text, tax.name, tax_class.name from tax join tax_class using (tax_class_id) where tax.company_id = $1 order by tax_class.name, tax.name", company.Id)
}
diff --git a/po/ca.po b/po/ca.po
index 037f51a..352f856 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-03-04 22:11+0100\n"
+"POT-Creation-Date: 2023-03-08 11:23+0100\n"
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
"Last-Translator: jordi fita mas \n"
"Language-Team: Catalan \n"
@@ -59,7 +59,7 @@ msgid "Name"
msgstr "Nom"
#: web/template/invoices/products.gohtml:43
-#: web/template/invoices/view.gohtml:53 web/template/products/index.gohtml:23
+#: web/template/invoices/view.gohtml:54 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:42 web/template/invoices/view.gohtml:58
+#: web/template/invoices/new.gohtml:42 web/template/invoices/view.gohtml:59
msgctxt "title"
msgid "Subtotal"
msgstr "Subtotal"
-#: web/template/invoices/new.gohtml:52 web/template/invoices/view.gohtml:62
-#: web/template/invoices/view.gohtml:102
+#: web/template/invoices/new.gohtml:52 web/template/invoices/view.gohtml:63
+#: web/template/invoices/view.gohtml:103
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:26
msgctxt "title"
msgid "Date"
msgstr "Data"
@@ -135,36 +135,41 @@ msgctxt "title"
msgid "Download"
msgstr "Descàrrega"
-#: web/template/invoices/index.gohtml:50
+#: web/template/invoices/index.gohtml:69
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:25
msgctxt "title"
msgid "Invoice %s"
msgstr "Factura %s"
-#: web/template/invoices/view.gohtml:16
+#: web/template/invoices/view.gohtml:14
+msgctxt "action"
+msgid "Duplicate"
+msgstr "Duplica"
+
+#: web/template/invoices/view.gohtml:17
msgctxt "action"
msgid "Download invoice"
msgstr "Descarrega factura"
-#: web/template/invoices/view.gohtml:52
+#: web/template/invoices/view.gohtml:53
msgctxt "title"
msgid "Concept"
msgstr "Concepte"
-#: web/template/invoices/view.gohtml:55
+#: web/template/invoices/view.gohtml:56
msgctxt "title"
msgid "Discount"
msgstr "Descompte"
-#: web/template/invoices/view.gohtml:57
+#: web/template/invoices/view.gohtml:58
msgctxt "title"
msgid "Units"
msgstr "Unitats"
-#: web/template/invoices/view.gohtml:92
+#: web/template/invoices/view.gohtml:93
msgctxt "title"
msgid "Tax Base"
msgstr "Base imposable"
@@ -408,44 +413,44 @@ 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:472
+#: pkg/products.go:165 pkg/invoices.go:517
msgctxt "input"
msgid "Name"
msgstr "Nom"
-#: pkg/products.go:171 pkg/invoices.go:477
+#: pkg/products.go:171 pkg/invoices.go:522
msgctxt "input"
msgid "Description"
msgstr "Descripció"
-#: pkg/products.go:176 pkg/invoices.go:481
+#: pkg/products.go:176 pkg/invoices.go:526
msgctxt "input"
msgid "Price"
msgstr "Preu"
-#: pkg/products.go:186 pkg/invoices.go:507
+#: pkg/products.go:186 pkg/invoices.go:552
msgctxt "input"
msgid "Taxes"
msgstr "Imposts"
-#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:402
-#: pkg/invoices.go:543
+#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:431
+#: pkg/invoices.go:588
msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc."
-#: pkg/products.go:207 pkg/invoices.go:544
+#: pkg/products.go:207 pkg/invoices.go:589
msgid "Price can not be empty."
msgstr "No podeu deixar el preu en blanc."
-#: pkg/products.go:208 pkg/invoices.go:545
+#: pkg/products.go:208 pkg/invoices.go:590
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:553
+#: pkg/products.go:210 pkg/invoices.go:598
msgid "Selected tax is not valid."
msgstr "Heu seleccionat un impost que no és vàlid."
-#: pkg/products.go:211 pkg/invoices.go:554
+#: pkg/products.go:211 pkg/invoices.go:599
msgid "You can only select a tax of each class."
msgstr "Només podeu seleccionar un impost de cada classe."
@@ -553,79 +558,79 @@ 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:230
+#: pkg/invoices.go:258
msgid "Select a customer to bill."
msgstr "Escolliu un client a facturar."
-#: pkg/invoices.go:323
+#: pkg/invoices.go:351
msgid "Invalid action"
msgstr "Acció invàlida."
-#: pkg/invoices.go:344
+#: pkg/invoices.go:372
msgctxt "input"
msgid "Customer"
msgstr "Client"
-#: pkg/invoices.go:350
+#: pkg/invoices.go:378
msgctxt "input"
msgid "Number"
msgstr "Número"
-#: pkg/invoices.go:356
+#: pkg/invoices.go:384
msgctxt "input"
msgid "Invoice Date"
msgstr "Data de factura"
-#: pkg/invoices.go:362
+#: pkg/invoices.go:390
msgctxt "input"
msgid "Notes"
msgstr "Notes"
-#: pkg/invoices.go:368
+#: pkg/invoices.go:396
msgctxt "input"
msgid "Payment Method"
msgstr "Mètode de pagament"
-#: pkg/invoices.go:403
+#: pkg/invoices.go:432
msgid "Invoice date can not be empty."
msgstr "No podeu deixar la data de la factura en blanc."
-#: pkg/invoices.go:404
+#: pkg/invoices.go:433
msgid "Invoice date must be a valid date."
msgstr "La data de facturació ha de ser vàlida."
-#: pkg/invoices.go:406
+#: pkg/invoices.go:435
msgid "Selected payment method is not valid."
msgstr "Heu seleccionat un mètode de pagament que no és vàlid."
-#: pkg/invoices.go:467
+#: pkg/invoices.go:512
msgctxt "input"
msgid "Id"
msgstr "Identificador"
-#: pkg/invoices.go:490
+#: pkg/invoices.go:535
msgctxt "input"
msgid "Quantity"
msgstr "Quantitat"
-#: pkg/invoices.go:498
+#: pkg/invoices.go:543
msgctxt "input"
msgid "Discount (%)"
msgstr "Descompte (%)"
-#: pkg/invoices.go:547
+#: pkg/invoices.go:592
msgid "Quantity can not be empty."
msgstr "No podeu deixar la quantitat en blanc."
-#: pkg/invoices.go:548
+#: pkg/invoices.go:593
msgid "Quantity must be a number greater than zero."
msgstr "La quantitat ha de ser un número major a zero."
-#: pkg/invoices.go:550
+#: pkg/invoices.go:595
msgid "Discount can not be empty."
msgstr "No podeu deixar el descompte en blanc."
-#: pkg/invoices.go:551
+#: pkg/invoices.go:596
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 da4f2c2..53c9e5e 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-03-04 22:11+0100\n"
+"POT-Creation-Date: 2023-03-08 11:23+0100\n"
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
"Last-Translator: jordi fita mas \n"
"Language-Team: Spanish \n"
@@ -59,7 +59,7 @@ msgid "Name"
msgstr "Nombre"
#: web/template/invoices/products.gohtml:43
-#: web/template/invoices/view.gohtml:53 web/template/products/index.gohtml:23
+#: web/template/invoices/view.gohtml:54 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:42 web/template/invoices/view.gohtml:58
+#: web/template/invoices/new.gohtml:42 web/template/invoices/view.gohtml:59
msgctxt "title"
msgid "Subtotal"
msgstr "Subtotal"
-#: web/template/invoices/new.gohtml:52 web/template/invoices/view.gohtml:62
-#: web/template/invoices/view.gohtml:102
+#: web/template/invoices/new.gohtml:52 web/template/invoices/view.gohtml:63
+#: web/template/invoices/view.gohtml:103
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:26
msgctxt "title"
msgid "Date"
msgstr "Fecha"
@@ -135,36 +135,41 @@ msgctxt "title"
msgid "Download"
msgstr "Descargar"
-#: web/template/invoices/index.gohtml:50
+#: web/template/invoices/index.gohtml:69
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:25
msgctxt "title"
msgid "Invoice %s"
msgstr "Factura %s"
-#: web/template/invoices/view.gohtml:16
+#: web/template/invoices/view.gohtml:14
+msgctxt "action"
+msgid "Duplicate"
+msgstr "Duplicar"
+
+#: web/template/invoices/view.gohtml:17
msgctxt "action"
msgid "Download invoice"
msgstr "Descargar factura"
-#: web/template/invoices/view.gohtml:52
+#: web/template/invoices/view.gohtml:53
msgctxt "title"
msgid "Concept"
msgstr "Concepto"
-#: web/template/invoices/view.gohtml:55
+#: web/template/invoices/view.gohtml:56
msgctxt "title"
msgid "Discount"
msgstr "Descuento"
-#: web/template/invoices/view.gohtml:57
+#: web/template/invoices/view.gohtml:58
msgctxt "title"
msgid "Units"
msgstr "Unidades"
-#: web/template/invoices/view.gohtml:92
+#: web/template/invoices/view.gohtml:93
msgctxt "title"
msgid "Tax Base"
msgstr "Base imponible"
@@ -408,44 +413,44 @@ 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:472
+#: pkg/products.go:165 pkg/invoices.go:517
msgctxt "input"
msgid "Name"
msgstr "Nombre"
-#: pkg/products.go:171 pkg/invoices.go:477
+#: pkg/products.go:171 pkg/invoices.go:522
msgctxt "input"
msgid "Description"
msgstr "Descripción"
-#: pkg/products.go:176 pkg/invoices.go:481
+#: pkg/products.go:176 pkg/invoices.go:526
msgctxt "input"
msgid "Price"
msgstr "Precio"
-#: pkg/products.go:186 pkg/invoices.go:507
+#: pkg/products.go:186 pkg/invoices.go:552
msgctxt "input"
msgid "Taxes"
msgstr "Impuestos"
-#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:402
-#: pkg/invoices.go:543
+#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:431
+#: pkg/invoices.go:588
msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco."
-#: pkg/products.go:207 pkg/invoices.go:544
+#: pkg/products.go:207 pkg/invoices.go:589
msgid "Price can not be empty."
msgstr "No podéis dejar el precio en blanco."
-#: pkg/products.go:208 pkg/invoices.go:545
+#: pkg/products.go:208 pkg/invoices.go:590
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:553
+#: pkg/products.go:210 pkg/invoices.go:598
msgid "Selected tax is not valid."
msgstr "Habéis escogido un impuesto que no es válido."
-#: pkg/products.go:211 pkg/invoices.go:554
+#: pkg/products.go:211 pkg/invoices.go:599
msgid "You can only select a tax of each class."
msgstr "Solo podéis escoger un impuesto de cada clase."
@@ -553,79 +558,79 @@ 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:230
+#: pkg/invoices.go:258
msgid "Select a customer to bill."
msgstr "Escoged un cliente a facturar."
-#: pkg/invoices.go:323
+#: pkg/invoices.go:351
msgid "Invalid action"
msgstr "Acción inválida."
-#: pkg/invoices.go:344
+#: pkg/invoices.go:372
msgctxt "input"
msgid "Customer"
msgstr "Cliente"
-#: pkg/invoices.go:350
+#: pkg/invoices.go:378
msgctxt "input"
msgid "Number"
msgstr "Número"
-#: pkg/invoices.go:356
+#: pkg/invoices.go:384
msgctxt "input"
msgid "Invoice Date"
msgstr "Fecha de factura"
-#: pkg/invoices.go:362
+#: pkg/invoices.go:390
msgctxt "input"
msgid "Notes"
msgstr "Notas"
-#: pkg/invoices.go:368
+#: pkg/invoices.go:396
msgctxt "input"
msgid "Payment Method"
msgstr "Método de pago"
-#: pkg/invoices.go:403
+#: pkg/invoices.go:432
msgid "Invoice date can not be empty."
msgstr "No podéis dejar la fecha de la factura en blanco."
-#: pkg/invoices.go:404
+#: pkg/invoices.go:433
msgid "Invoice date must be a valid date."
msgstr "La fecha de factura debe ser válida."
-#: pkg/invoices.go:406
+#: pkg/invoices.go:435
msgid "Selected payment method is not valid."
msgstr "Habéis escogido un método de pago que no es válido."
-#: pkg/invoices.go:467
+#: pkg/invoices.go:512
msgctxt "input"
msgid "Id"
msgstr "Identificador"
-#: pkg/invoices.go:490
+#: pkg/invoices.go:535
msgctxt "input"
msgid "Quantity"
msgstr "Cantidad"
-#: pkg/invoices.go:498
+#: pkg/invoices.go:543
msgctxt "input"
msgid "Discount (%)"
msgstr "Descuento (%)"
-#: pkg/invoices.go:547
+#: pkg/invoices.go:592
msgid "Quantity can not be empty."
msgstr "No podéis dejar la cantidad en blanco."
-#: pkg/invoices.go:548
+#: pkg/invoices.go:593
msgid "Quantity must be a number greater than zero."
msgstr "La cantidad tiene que ser un número mayor a cero."
-#: pkg/invoices.go:550
+#: pkg/invoices.go:595
msgid "Discount can not be empty."
msgstr "No podéis dejar el descuento en blanco."
-#: pkg/invoices.go:551
+#: pkg/invoices.go:596
msgid "Discount must be a percentage between 0 and 100."
msgstr "El descuento tiene que ser un porcentaje entre 0 y 100."
diff --git a/web/template/invoices/view.gohtml b/web/template/invoices/view.gohtml
index f693f43..6e1e111 100644
--- a/web/template/invoices/view.gohtml
+++ b/web/template/invoices/view.gohtml
@@ -11,6 +11,7 @@
{{ .Number }}
+ {{( pgettext "Duplicate" "action" )}}
{{( pgettext "Download invoice" "action" )}}