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" )}}