Compare commits

...

4 Commits

Author SHA1 Message Date
jordi fita mas f3b841473f Add the context menu with the duplicate option
As per the design document.
2023-03-08 11:54:06 +01:00
jordi fita mas 0c8edb9cae Add option to duplicate an invoice
With Oriol we agreed that a duplicate is just the new invoice form
prefilled with the data from another invoices, but without the number or
the date.
2023-03-08 11:26:02 +01:00
jordi fita mas d725dcf529 Add missing call to PaymentMethod.FillValue 2023-03-08 11:19:59 +01:00
jordi fita mas 3dd0b0136e Adjust the design of invoice status menu to the one made by Oriol 2023-03-08 10:49:03 +01:00
8 changed files with 199 additions and 124 deletions

View File

@ -140,6 +140,10 @@ func (field *SelectField) isValidOption(selected string) bool {
return field.FindOption(selected) != nil return field.FindOption(selected) != nil
} }
func (field *SelectField) Clear() {
field.Selected = []string{}
}
func MustGetOptions(ctx context.Context, conn *Conn, sql string, args ...interface{}) []*SelectOption { func MustGetOptions(ctx context.Context, conn *Conn, sql string, args ...interface{}) []*SelectOption {
rows, err := conn.Query(ctx, sql, args...) rows, err := conn.Query(ctx, sql, args...)
if err != nil { if err != nil {

View File

@ -88,6 +88,10 @@ func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Para
form := newInvoiceForm(r.Context(), conn, locale, company) form := newInvoiceForm(r.Context(), conn, locale, company)
slug := params[0].Value slug := params[0].Value
if slug == "new" { 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") form.Date.Val = time.Now().Format("2006-01-02")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
mustRenderNewInvoiceForm(w, r, form) mustRenderNewInvoiceForm(w, r, form)
@ -404,6 +408,7 @@ func (form *invoiceForm) Parse(r *http.Request) error {
form.Number.FillValue(r) form.Number.FillValue(r)
form.Date.FillValue(r) form.Date.FillValue(r)
form.Notes.FillValue(r) form.Notes.FillValue(r)
form.PaymentMethod.FillValue(r)
if _, ok := r.Form["product.id.0"]; ok { if _, ok := r.Form["product.id.0"]; ok {
taxOptions := mustGetTaxOptions(r.Context(), getConn(r), form.company) taxOptions := mustGetTaxOptions(r.Context(), getConn(r), form.company)
for index := 0; true; index++ { for index := 0; true; index++ {
@ -450,9 +455,13 @@ func (form *invoiceForm) Update() {
} }
func (form *invoiceForm) AddProducts(ctx context.Context, conn *Conn, productsId []string) { 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) index := len(form.Products)
taxOptions := mustGetTaxOptions(ctx, conn, form.company) 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() defer rows.Close()
for rows.Next() { for rows.Next() {
product := newInvoiceProductForm(index, form.company, form.locale, taxOptions) product := newInvoiceProductForm(index, form.company, form.locale, taxOptions)
@ -467,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 { 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) 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)
} }

View File

@ -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-04 22:11+0100\n" "POT-Creation-Date: 2023-03-08 11:51+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"
@ -59,7 +59,7 @@ msgid "Name"
msgstr "Nom" msgstr "Nom"
#: web/template/invoices/products.gohtml:43 #: 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" msgctxt "title"
msgid "Price" msgid "Price"
msgstr "Preu" msgstr "Preu"
@ -74,13 +74,13 @@ msgctxt "action"
msgid "Add products" msgid "Add products"
msgstr "Afegeix productes" 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" msgctxt "title"
msgid "Subtotal" msgid "Subtotal"
msgstr "Subtotal" msgstr "Subtotal"
#: web/template/invoices/new.gohtml:52 web/template/invoices/view.gohtml:62 #: web/template/invoices/new.gohtml:52 web/template/invoices/view.gohtml:63
#: web/template/invoices/view.gohtml:102 #: web/template/invoices/view.gohtml:103
msgctxt "title" msgctxt "title"
msgid "Total" msgid "Total"
msgstr "Total" msgstr "Total"
@ -100,7 +100,7 @@ msgctxt "invoice"
msgid "All" msgid "All"
msgstr "Totes" 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" msgctxt "title"
msgid "Date" msgid "Date"
msgstr "Data" msgstr "Data"
@ -135,36 +135,46 @@ msgctxt "title"
msgid "Download" msgid "Download"
msgstr "Descàrrega" msgstr "Descàrrega"
#: web/template/invoices/index.gohtml:50 #: web/template/invoices/index.gohtml:29
msgctxt "title"
msgid "Actions"
msgstr "Accions"
#: web/template/invoices/index.gohtml:75 web/template/invoices/view.gohtml:14
msgctxt "action"
msgid "Duplicate"
msgstr "Duplica"
#: web/template/invoices/index.gohtml:85
msgid "No invoices added yet." msgid "No invoices added yet."
msgstr "No hi ha cap factura." 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" msgctxt "title"
msgid "Invoice %s" msgid "Invoice %s"
msgstr "Factura %s" msgstr "Factura %s"
#: web/template/invoices/view.gohtml:16 #: web/template/invoices/view.gohtml:17
msgctxt "action" msgctxt "action"
msgid "Download invoice" msgid "Download invoice"
msgstr "Descarrega factura" msgstr "Descarrega factura"
#: web/template/invoices/view.gohtml:52 #: web/template/invoices/view.gohtml:53
msgctxt "title" msgctxt "title"
msgid "Concept" msgid "Concept"
msgstr "Concepte" msgstr "Concepte"
#: web/template/invoices/view.gohtml:55 #: web/template/invoices/view.gohtml:56
msgctxt "title" msgctxt "title"
msgid "Discount" msgid "Discount"
msgstr "Descompte" msgstr "Descompte"
#: web/template/invoices/view.gohtml:57 #: web/template/invoices/view.gohtml:58
msgctxt "title" msgctxt "title"
msgid "Units" msgid "Units"
msgstr "Unitats" msgstr "Unitats"
#: web/template/invoices/view.gohtml:92 #: web/template/invoices/view.gohtml:93
msgctxt "title" msgctxt "title"
msgid "Tax Base" msgid "Tax Base"
msgstr "Base imposable" msgstr "Base imposable"
@ -408,44 +418,44 @@ msgstr "No podeu deixar la contrasenya en blanc."
msgid "Invalid user or password." msgid "Invalid user or password."
msgstr "Nom dusuari o contrasenya incorrectes." msgstr "Nom dusuari o contrasenya incorrectes."
#: pkg/products.go:165 pkg/invoices.go:472 #: pkg/products.go:165 pkg/invoices.go:517
msgctxt "input" msgctxt "input"
msgid "Name" msgid "Name"
msgstr "Nom" msgstr "Nom"
#: pkg/products.go:171 pkg/invoices.go:477 #: pkg/products.go:171 pkg/invoices.go:522
msgctxt "input" msgctxt "input"
msgid "Description" msgid "Description"
msgstr "Descripció" msgstr "Descripció"
#: pkg/products.go:176 pkg/invoices.go:481 #: pkg/products.go:176 pkg/invoices.go:526
msgctxt "input" msgctxt "input"
msgid "Price" msgid "Price"
msgstr "Preu" msgstr "Preu"
#: pkg/products.go:186 pkg/invoices.go:507 #: pkg/products.go:186 pkg/invoices.go:552
msgctxt "input" msgctxt "input"
msgid "Taxes" msgid "Taxes"
msgstr "Imposts" msgstr "Imposts"
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:402 #: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:431
#: pkg/invoices.go:543 #: pkg/invoices.go:588
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:544 #: pkg/products.go:207 pkg/invoices.go:589
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:545 #: pkg/products.go:208 pkg/invoices.go:590
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:553 #: pkg/products.go:210 pkg/invoices.go:598
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:554 #: pkg/products.go:211 pkg/invoices.go:599
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."
@ -553,79 +563,79 @@ 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:230 #: pkg/invoices.go:258
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:323 #: pkg/invoices.go:351
msgid "Invalid action" msgid "Invalid action"
msgstr "Acció invàlida." msgstr "Acció invàlida."
#: pkg/invoices.go:344 #: pkg/invoices.go:372
msgctxt "input" msgctxt "input"
msgid "Customer" msgid "Customer"
msgstr "Client" msgstr "Client"
#: pkg/invoices.go:350 #: pkg/invoices.go:378
msgctxt "input" msgctxt "input"
msgid "Number" msgid "Number"
msgstr "Número" msgstr "Número"
#: pkg/invoices.go:356 #: pkg/invoices.go:384
msgctxt "input" msgctxt "input"
msgid "Invoice Date" msgid "Invoice Date"
msgstr "Data de factura" msgstr "Data de factura"
#: pkg/invoices.go:362 #: pkg/invoices.go:390
msgctxt "input" msgctxt "input"
msgid "Notes" msgid "Notes"
msgstr "Notes" msgstr "Notes"
#: pkg/invoices.go:368 #: pkg/invoices.go:396
msgctxt "input" msgctxt "input"
msgid "Payment Method" msgid "Payment Method"
msgstr "Mètode de pagament" msgstr "Mètode de pagament"
#: pkg/invoices.go:403 #: pkg/invoices.go:432
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:404 #: pkg/invoices.go:433
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:406 #: pkg/invoices.go:435
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:467 #: pkg/invoices.go:512
msgctxt "input" msgctxt "input"
msgid "Id" msgid "Id"
msgstr "Identificador" msgstr "Identificador"
#: pkg/invoices.go:490 #: pkg/invoices.go:535
msgctxt "input" msgctxt "input"
msgid "Quantity" msgid "Quantity"
msgstr "Quantitat" msgstr "Quantitat"
#: pkg/invoices.go:498 #: pkg/invoices.go:543
msgctxt "input" msgctxt "input"
msgid "Discount (%)" msgid "Discount (%)"
msgstr "Descompte (%)" msgstr "Descompte (%)"
#: pkg/invoices.go:547 #: pkg/invoices.go:592
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:548 #: pkg/invoices.go:593
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:550 #: pkg/invoices.go:595
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:551 #: pkg/invoices.go:596
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."

View File

@ -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-04 22:11+0100\n" "POT-Creation-Date: 2023-03-08 11:51+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"
@ -59,7 +59,7 @@ msgid "Name"
msgstr "Nombre" msgstr "Nombre"
#: web/template/invoices/products.gohtml:43 #: 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" msgctxt "title"
msgid "Price" msgid "Price"
msgstr "Precio" msgstr "Precio"
@ -74,13 +74,13 @@ msgctxt "action"
msgid "Add products" msgid "Add products"
msgstr "Añadir productos" 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" msgctxt "title"
msgid "Subtotal" msgid "Subtotal"
msgstr "Subtotal" msgstr "Subtotal"
#: web/template/invoices/new.gohtml:52 web/template/invoices/view.gohtml:62 #: web/template/invoices/new.gohtml:52 web/template/invoices/view.gohtml:63
#: web/template/invoices/view.gohtml:102 #: web/template/invoices/view.gohtml:103
msgctxt "title" msgctxt "title"
msgid "Total" msgid "Total"
msgstr "Total" msgstr "Total"
@ -100,7 +100,7 @@ msgctxt "invoice"
msgid "All" msgid "All"
msgstr "Todas" 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" msgctxt "title"
msgid "Date" msgid "Date"
msgstr "Fecha" msgstr "Fecha"
@ -135,36 +135,46 @@ msgctxt "title"
msgid "Download" msgid "Download"
msgstr "Descargar" msgstr "Descargar"
#: web/template/invoices/index.gohtml:50 #: web/template/invoices/index.gohtml:29
msgctxt "title"
msgid "Actions"
msgstr "Acciones"
#: web/template/invoices/index.gohtml:75 web/template/invoices/view.gohtml:14
msgctxt "action"
msgid "Duplicate"
msgstr "Duplicar"
#: web/template/invoices/index.gohtml:85
msgid "No invoices added yet." msgid "No invoices added yet."
msgstr "No hay facturas." 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" msgctxt "title"
msgid "Invoice %s" msgid "Invoice %s"
msgstr "Factura %s" msgstr "Factura %s"
#: web/template/invoices/view.gohtml:16 #: web/template/invoices/view.gohtml:17
msgctxt "action" msgctxt "action"
msgid "Download invoice" msgid "Download invoice"
msgstr "Descargar factura" msgstr "Descargar factura"
#: web/template/invoices/view.gohtml:52 #: web/template/invoices/view.gohtml:53
msgctxt "title" msgctxt "title"
msgid "Concept" msgid "Concept"
msgstr "Concepto" msgstr "Concepto"
#: web/template/invoices/view.gohtml:55 #: web/template/invoices/view.gohtml:56
msgctxt "title" msgctxt "title"
msgid "Discount" msgid "Discount"
msgstr "Descuento" msgstr "Descuento"
#: web/template/invoices/view.gohtml:57 #: web/template/invoices/view.gohtml:58
msgctxt "title" msgctxt "title"
msgid "Units" msgid "Units"
msgstr "Unidades" msgstr "Unidades"
#: web/template/invoices/view.gohtml:92 #: web/template/invoices/view.gohtml:93
msgctxt "title" msgctxt "title"
msgid "Tax Base" msgid "Tax Base"
msgstr "Base imponible" msgstr "Base imponible"
@ -408,44 +418,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:472 #: pkg/products.go:165 pkg/invoices.go:517
msgctxt "input" msgctxt "input"
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
#: pkg/products.go:171 pkg/invoices.go:477 #: pkg/products.go:171 pkg/invoices.go:522
msgctxt "input" msgctxt "input"
msgid "Description" msgid "Description"
msgstr "Descripción" msgstr "Descripción"
#: pkg/products.go:176 pkg/invoices.go:481 #: pkg/products.go:176 pkg/invoices.go:526
msgctxt "input" msgctxt "input"
msgid "Price" msgid "Price"
msgstr "Precio" msgstr "Precio"
#: pkg/products.go:186 pkg/invoices.go:507 #: pkg/products.go:186 pkg/invoices.go:552
msgctxt "input" msgctxt "input"
msgid "Taxes" msgid "Taxes"
msgstr "Impuestos" msgstr "Impuestos"
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:402 #: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:431
#: pkg/invoices.go:543 #: pkg/invoices.go:588
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:544 #: pkg/products.go:207 pkg/invoices.go:589
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:545 #: pkg/products.go:208 pkg/invoices.go:590
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:553 #: pkg/products.go:210 pkg/invoices.go:598
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:554 #: pkg/products.go:211 pkg/invoices.go:599
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."
@ -553,79 +563,79 @@ 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:230 #: pkg/invoices.go:258
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:323 #: pkg/invoices.go:351
msgid "Invalid action" msgid "Invalid action"
msgstr "Acción inválida." msgstr "Acción inválida."
#: pkg/invoices.go:344 #: pkg/invoices.go:372
msgctxt "input" msgctxt "input"
msgid "Customer" msgid "Customer"
msgstr "Cliente" msgstr "Cliente"
#: pkg/invoices.go:350 #: pkg/invoices.go:378
msgctxt "input" msgctxt "input"
msgid "Number" msgid "Number"
msgstr "Número" msgstr "Número"
#: pkg/invoices.go:356 #: pkg/invoices.go:384
msgctxt "input" msgctxt "input"
msgid "Invoice Date" msgid "Invoice Date"
msgstr "Fecha de factura" msgstr "Fecha de factura"
#: pkg/invoices.go:362 #: pkg/invoices.go:390
msgctxt "input" msgctxt "input"
msgid "Notes" msgid "Notes"
msgstr "Notas" msgstr "Notas"
#: pkg/invoices.go:368 #: pkg/invoices.go:396
msgctxt "input" msgctxt "input"
msgid "Payment Method" msgid "Payment Method"
msgstr "Método de pago" msgstr "Método de pago"
#: pkg/invoices.go:403 #: pkg/invoices.go:432
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:404 #: pkg/invoices.go:433
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:406 #: pkg/invoices.go:435
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:467 #: pkg/invoices.go:512
msgctxt "input" msgctxt "input"
msgid "Id" msgid "Id"
msgstr "Identificador" msgstr "Identificador"
#: pkg/invoices.go:490 #: pkg/invoices.go:535
msgctxt "input" msgctxt "input"
msgid "Quantity" msgid "Quantity"
msgstr "Cantidad" msgstr "Cantidad"
#: pkg/invoices.go:498 #: pkg/invoices.go:543
msgctxt "input" msgctxt "input"
msgid "Discount (%)" msgid "Discount (%)"
msgstr "Descuento (%)" msgstr "Descuento (%)"
#: pkg/invoices.go:547 #: pkg/invoices.go:592
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:548 #: pkg/invoices.go:593
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:550 #: pkg/invoices.go:595
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:551 #: pkg/invoices.go:596
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."

View File

@ -418,34 +418,17 @@ fieldset {
} }
/* Profile Menu */ /* Profile Menu */
details.menu summary {
#profile-menu {
position: relative;
}
#profile-menu summary {
width: 7rem;
height: 7rem;
margin: 1rem 0;
display: flex; display: flex;
cursor: pointer;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-radius: 50%;
border: none;
} }
#profile-menu summary::-webkit-details-marker { details.menu summary::-webkit-details-marker {
display: none; display: none;
} }
#profile-menu summary, #profile-menu button {
cursor: pointer;
}
#profile-menu summary, #profile-menu ul {
background-color: var(--numerus--background-color);
}
details.menu[open] summary::before { details.menu[open] summary::before {
background-color: var(--numerus--header--background-color); background-color: var(--numerus--header--background-color);
position: fixed; position: fixed;
@ -460,23 +443,22 @@ details.menu[open] summary::before {
} }
ul[role="menu"] { ul[role="menu"] {
position: absolute;
padding: 0; padding: 0;
list-style: none; list-style: none;
} background-color: var(--numerus--background-color);
#profile-menu ul {
position: absolute;
right: -1.875em;
top: 100%;
padding: 1rem 2rem;
z-index: 20; z-index: 20;
} }
#profile-menu li + li { ul[role="menu"].action-menu {
padding: 1rem 2rem;
}
ul[role="menu"].action-menu li + li {
border-top: 1px solid var(--numerus--color--black); border-top: 1px solid var(--numerus--color--black);
} }
#profile-menu button, #profile-menu a { ul[role="menu"].action-menu button, ul[role="menu"].action-menu a {
font-size: 2rem; font-size: 2rem;
font-style: italic; font-style: italic;
height: 8rem; height: 8rem;
@ -490,12 +472,37 @@ ul[role="menu"] {
text-transform: initial; text-transform: initial;
} }
#profile-menu li i[class^='ri-'] { ul[role="menu"].action-menu li i[class^='ri-'] {
margin-right: 2rem; margin-right: 2rem;
color: var(--numerus--color--dark-gray); color: var(--numerus--color--dark-gray);
} }
#profile-menu summary:hover, #profile-menu summary:focus, #profile-menu button:hover, #profile-menu a:hover, body > nav a:hover { #profile-menu {
position: relative;
}
#profile-menu summary {
width: 7rem;
height: 7rem;
margin: 1rem 0;
border-radius: 50%;
border: none;
}
#profile-menu button {
cursor: pointer;
}
#profile-menu summary {
background-color: var(--numerus--background-color);
}
#profile-menu ul {
right: -1.875em;
top: 100%;
}
#profile-menu summary:hover, #profile-menu summary:focus, ul[role="menu"].action-menu button:hover, ul[role="menu"].action-menu a:hover, body > nav a:hover {
background-color: var(--numerus--color--light-gray); background-color: var(--numerus--color--light-gray);
} }
@ -534,18 +541,16 @@ main > nav {
.invoice-status summary { .invoice-status summary {
height: 3rem; height: 3rem;
display: block;
}
.invoice-status summary::-webkit-details-marker {
display: none;
} }
.invoice-status ul { .invoice-status ul {
position: absolute; position: absolute;
top: 1.5em; top: 0;
left: 0; left: 100%;
z-index: 20; padding: 2rem;
display: flex;
flex-direction: column;
gap: 1rem;
} }
.invoice-status button { .invoice-status button {
@ -575,6 +580,14 @@ main > nav {
background-color: var(--numerus--color--rosy); background-color: var(--numerus--color--rosy);
} }
.actions details {
position: relative;
}
.actions .action-menu {
right: 0;
}
/* Remix Icon */ /* Remix Icon */
@font-face { @font-face {

View File

@ -13,7 +13,7 @@
<summary> <summary>
<i class="ri-eye-close-line ri-3x"></i> <i class="ri-eye-close-line ri-3x"></i>
</summary> </summary>
<ul role="menu"> <ul role="menu" class="action-menu">
<li role="presentation"> <li role="presentation">
<a role="menuitem" href="{{ companyURI "/profile" }}"> <a role="menuitem" href="{{ companyURI "/profile" }}">
<i class="ri-account-circle-line"></i> <i class="ri-account-circle-line"></i>

View File

@ -26,6 +26,7 @@
<th>{{( pgettext "Label" "title" )}}</th> <th>{{( pgettext "Label" "title" )}}</th>
<th>{{( pgettext "Amount" "title" )}}</th> <th>{{( pgettext "Amount" "title" )}}</th>
<th>{{( pgettext "Download" "title" )}}</th> <th>{{( pgettext "Download" "title" )}}</th>
<th>{{( pgettext "Actions" "title" )}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -45,9 +46,11 @@
<ul role="menu"> <ul role="menu">
{{- range $status, $name := $.InvoiceStatuses }} {{- range $status, $name := $.InvoiceStatuses }}
{{- if ne $status $invoice.Status }} {{- if ne $status $invoice.Status }}
<li> <li role="presentation">
<button type="submit" name="status" class="invoice-status-{{ $status }}" <button role="menuitem" type="submit"
value="{{ $status }}">{{ $name }}</button> name="status" value="{{ $status }}"
class="invoice-status-{{ $status }}"
>{{ $name }}</button>
</li> </li>
{{- end }} {{- end }}
{{- end }} {{- end }}
@ -62,6 +65,19 @@
title="{{( pgettext "Download invoice" "action" )}}" title="{{( pgettext "Download invoice" "action" )}}"
aria-label="{{( pgettext "Download invoice %s" "action" )}}"><i aria-label="{{( pgettext "Download invoice %s" "action" )}}"><i
class="ri-download-line"></i></a></td> class="ri-download-line"></i></a></td>
<td class="actions">
<details class="menu">
<summary><i class="ri-more-line"></i></summary>
<ul role="menu" class="action-menu">
<li role="presentation">
<a role="menuitem" href="{{ companyURI "/invoices/new"}}?duplicate={{ .Slug }}">
<i class="ri-file-copy-line"></i>
{{( pgettext "Duplicate" "action" )}}
</a>
</li>
</ul>
</details>
</td>
</tr> </tr>
{{- end }} {{- end }}
{{ else }} {{ else }}

View File

@ -11,6 +11,7 @@
<a>{{ .Number }}</a> <a>{{ .Number }}</a>
</p> </p>
<p> <p>
<a class="primary button" href="{{ companyURI "/invoices/new"}}?duplicate={{ .Slug }}">{{( pgettext "Duplicate" "action" )}}</a>
<a class="primary button" <a class="primary button"
href="{{ companyURI "/invoices/" }}{{ .Slug }}.pdf" href="{{ companyURI "/invoices/" }}{{ .Slug }}.pdf"
download="{{ .Number}}.pdf">{{( pgettext "Download invoice" "action" )}}</a> download="{{ .Number}}.pdf">{{( pgettext "Download invoice" "action" )}}</a>