Add total amount for quotes, invoices, and expenses tables

We have shown the application to a potential user, and they told us that
it would be very useful to have a total in the table’s footer, so that
they can verify the amount with the bank’s extracts.
This commit is contained in:
jordi fita mas 2023-06-20 11:33:28 +02:00
parent 8a4f80783d
commit 07c1071975
9 changed files with 367 additions and 263 deletions

View File

@ -23,8 +23,9 @@ type ExpenseEntry struct {
} }
type expensesIndexPage struct { type expensesIndexPage struct {
Expenses []*ExpenseEntry Expenses []*ExpenseEntry
Filters *expenseFilterForm TotalAmount string
Filters *expenseFilterForm
} }
func IndexExpenses(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func IndexExpenses(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@ -37,42 +38,15 @@ func IndexExpenses(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
return return
} }
page := &expensesIndexPage{ page := &expensesIndexPage{
Expenses: mustCollectExpenseEntries(r.Context(), conn, company, filters), Expenses: mustCollectExpenseEntries(r.Context(), conn, company, filters),
Filters: filters, TotalAmount: mustComputeExpensesTotalAmount(r.Context(), conn, filters),
Filters: filters,
} }
mustRenderMainTemplate(w, r, "expenses/index.gohtml", page) mustRenderMainTemplate(w, r, "expenses/index.gohtml", page)
} }
func mustCollectExpenseEntries(ctx context.Context, conn *Conn, company *Company, filters *expenseFilterForm) []*ExpenseEntry { func mustCollectExpenseEntries(ctx context.Context, conn *Conn, company *Company, filters *expenseFilterForm) []*ExpenseEntry {
args := []interface{}{company.Id} where, args := filters.BuildQuery(nil)
where := []string{"expense.company_id = $1"}
appendWhere := func(expression string, value interface{}) {
args = append(args, value)
where = append(where, fmt.Sprintf(expression, len(args)))
}
maybeAppendWhere := func(expression string, value string, conv func(string) interface{}) {
if value != "" {
if conv == nil {
appendWhere(expression, value)
} else {
appendWhere(expression, conv(value))
}
}
}
maybeAppendWhere("contact_id = $%d", filters.Contact.String(), func(v string) interface{} {
customerId, _ := strconv.Atoi(filters.Contact.Selected[0])
return customerId
})
maybeAppendWhere("invoice_number = $%d", filters.InvoiceNumber.String(), nil)
maybeAppendWhere("invoice_date >= $%d", filters.FromDate.String(), nil)
maybeAppendWhere("invoice_date <= $%d", filters.ToDate.String(), nil)
if len(filters.Tags.Tags) > 0 {
if filters.TagsCondition.Selected == "and" {
appendWhere("expense.tags @> $%d", filters.Tags)
} else {
appendWhere("expense.tags && $%d", filters.Tags)
}
}
rows := conn.MustQuery(ctx, fmt.Sprintf(` rows := conn.MustQuery(ctx, fmt.Sprintf(`
select expense.slug select expense.slug
, invoice_date , invoice_date
@ -87,7 +61,7 @@ func mustCollectExpenseEntries(ctx context.Context, conn *Conn, company *Company
join currency using (currency_code) join currency using (currency_code)
where (%s) where (%s)
order by invoice_date order by invoice_date
`, strings.Join(where, ") AND (")), args...) `, where), args...)
defer rows.Close() defer rows.Close()
var entries []*ExpenseEntry var entries []*ExpenseEntry
@ -104,6 +78,18 @@ func mustCollectExpenseEntries(ctx context.Context, conn *Conn, company *Company
return entries return entries
} }
func mustComputeExpensesTotalAmount(ctx context.Context, conn *Conn, filters *expenseFilterForm) string {
where, args := filters.BuildQuery(nil)
return conn.MustGetText(ctx, "0", fmt.Sprintf(`
select to_price(sum(amount)::integer, decimal_digits)
from expense
join currency using (currency_code)
where (%s)
group by decimal_digits
`, where), args...)
}
func ServeExpenseForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func ServeExpenseForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
locale := getLocale(r) locale := getLocale(r)
conn := getConn(r) conn := getConn(r)
@ -391,6 +377,41 @@ func (form *expenseFilterForm) Parse(r *http.Request) error {
return nil return nil
} }
func (form *expenseFilterForm) BuildQuery(args []interface{}) (string, []interface{}) {
var where []string
appendWhere := func(expression string, value interface{}) {
args = append(args, value)
where = append(where, fmt.Sprintf(expression, len(args)))
}
maybeAppendWhere := func(expression string, value string, conv func(string) interface{}) {
if value != "" {
if conv == nil {
appendWhere(expression, value)
} else {
appendWhere(expression, conv(value))
}
}
}
appendWhere("expense.company_id = $%d", form.company.Id)
maybeAppendWhere("contact_id = $%d", form.Contact.String(), func(v string) interface{} {
customerId, _ := strconv.Atoi(form.Contact.Selected[0])
return customerId
})
maybeAppendWhere("invoice_number = $%d", form.InvoiceNumber.String(), nil)
maybeAppendWhere("invoice_date >= $%d", form.FromDate.String(), nil)
maybeAppendWhere("invoice_date <= $%d", form.ToDate.String(), nil)
if len(form.Tags.Tags) > 0 {
if form.TagsCondition.Selected == "and" {
appendWhere("expense.tags @> $%d", form.Tags)
} else {
appendWhere("expense.tags && $%d", form.Tags)
}
}
return strings.Join(where, ") AND ("), args
}
func ServeEditExpenseTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func ServeEditExpenseTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
conn := getConn(r) conn := getConn(r)
locale := getLocale(r) locale := getLocale(r)

View File

@ -35,6 +35,7 @@ type InvoiceEntry struct {
type InvoicesIndexPage struct { type InvoicesIndexPage struct {
Invoices []*InvoiceEntry Invoices []*InvoiceEntry
TotalAmount string
Filters *invoiceFilterForm Filters *invoiceFilterForm
InvoiceStatuses map[string]string InvoiceStatuses map[string]string
} }
@ -49,44 +50,16 @@ func IndexInvoices(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
return return
} }
page := &InvoicesIndexPage{ page := &InvoicesIndexPage{
Invoices: mustCollectInvoiceEntries(r.Context(), conn, company, locale, filters), Invoices: mustCollectInvoiceEntries(r.Context(), conn, locale, filters),
TotalAmount: mustComputeInvoicesTotalAmount(r.Context(), conn, filters),
Filters: filters, Filters: filters,
InvoiceStatuses: mustCollectInvoiceStatuses(r.Context(), conn, locale), InvoiceStatuses: mustCollectInvoiceStatuses(r.Context(), conn, locale),
} }
mustRenderMainTemplate(w, r, "invoices/index.gohtml", page) mustRenderMainTemplate(w, r, "invoices/index.gohtml", page)
} }
func mustCollectInvoiceEntries(ctx context.Context, conn *Conn, company *Company, locale *Locale, filters *invoiceFilterForm) []*InvoiceEntry { func mustCollectInvoiceEntries(ctx context.Context, conn *Conn, locale *Locale, filters *invoiceFilterForm) []*InvoiceEntry {
args := []interface{}{locale.Language.String(), company.Id} where, args := filters.BuildQuery([]interface{}{locale.Language.String()})
where := []string{"invoice.company_id = $2"}
appendWhere := func(expression string, value interface{}) {
args = append(args, value)
where = append(where, fmt.Sprintf(expression, len(args)))
}
maybeAppendWhere := func(expression string, value string, conv func(string) interface{}) {
if value != "" {
if conv == nil {
appendWhere(expression, value)
} else {
appendWhere(expression, conv(value))
}
}
}
maybeAppendWhere("contact_id = $%d", filters.Customer.String(), func(v string) interface{} {
customerId, _ := strconv.Atoi(filters.Customer.Selected[0])
return customerId
})
maybeAppendWhere("invoice.invoice_status = $%d", filters.InvoiceStatus.String(), nil)
maybeAppendWhere("invoice_number = $%d", filters.InvoiceNumber.String(), nil)
maybeAppendWhere("invoice_date >= $%d", filters.FromDate.String(), nil)
maybeAppendWhere("invoice_date <= $%d", filters.ToDate.String(), nil)
if len(filters.Tags.Tags) > 0 {
if filters.TagsCondition.Selected == "and" {
appendWhere("invoice.tags @> $%d", filters.Tags)
} else {
appendWhere("invoice.tags && $%d", filters.Tags)
}
}
rows := conn.MustQuery(ctx, fmt.Sprintf(` rows := conn.MustQuery(ctx, fmt.Sprintf(`
select invoice.slug select invoice.slug
, invoice_date , invoice_date
@ -104,7 +77,7 @@ func mustCollectInvoiceEntries(ctx context.Context, conn *Conn, company *Company
where (%s) where (%s)
order by invoice_date desc order by invoice_date desc
, invoice_number desc , invoice_number desc
`, strings.Join(where, ") AND (")), args...) `, where), args...)
defer rows.Close() defer rows.Close()
var entries []*InvoiceEntry var entries []*InvoiceEntry
@ -122,6 +95,18 @@ func mustCollectInvoiceEntries(ctx context.Context, conn *Conn, company *Company
return entries return entries
} }
func mustComputeInvoicesTotalAmount(ctx context.Context, conn *Conn, filters *invoiceFilterForm) string {
where, args := filters.BuildQuery(nil)
return conn.MustGetText(ctx, "0", fmt.Sprintf(`
select to_price(sum(total)::integer, decimal_digits)
from invoice
join invoice_amount using (invoice_id)
join currency using (currency_code)
where (%s)
group by decimal_digits
`, where), args...)
}
func mustCollectInvoiceStatuses(ctx context.Context, conn *Conn, locale *Locale) map[string]string { func mustCollectInvoiceStatuses(ctx context.Context, conn *Conn, locale *Locale) map[string]string {
rows := conn.MustQuery(ctx, "select invoice_status.invoice_status, isi18n.name from invoice_status join invoice_status_i18n isi18n using(invoice_status) where isi18n.lang_tag = $1 order by invoice_status", locale.Language.String()) rows := conn.MustQuery(ctx, "select invoice_status.invoice_status, isi18n.name from invoice_status join invoice_status_i18n isi18n using(invoice_status) where isi18n.lang_tag = $1 order by invoice_status", locale.Language.String())
defer rows.Close() defer rows.Close()
@ -159,7 +144,7 @@ func newInvoiceFilterForm(ctx context.Context, conn *Conn, locale *Locale, compa
company: company, company: company,
Customer: &SelectField{ Customer: &SelectField{
Name: "customer", Name: "customer",
Label: pgettext("input", "Customer", locale), Label: pgettext("input", "Contact", locale),
EmptyLabel: gettext("All customers", locale), EmptyLabel: gettext("All customers", locale),
Options: mustGetContactOptions(ctx, conn, company), Options: mustGetContactOptions(ctx, conn, company),
}, },
@ -220,6 +205,41 @@ func (form *invoiceFilterForm) Parse(r *http.Request) error {
return nil return nil
} }
func (form *invoiceFilterForm) BuildQuery(args []interface{}) (string, []interface{}) {
var where []string
appendWhere := func(expression string, value interface{}) {
args = append(args, value)
where = append(where, fmt.Sprintf(expression, len(args)))
}
maybeAppendWhere := func(expression string, value string, conv func(string) interface{}) {
if value != "" {
if conv == nil {
appendWhere(expression, value)
} else {
appendWhere(expression, conv(value))
}
}
}
appendWhere("invoice.company_id = $%d", form.company.Id)
maybeAppendWhere("contact_id = $%d", form.Customer.String(), func(v string) interface{} {
customerId, _ := strconv.Atoi(form.Customer.Selected[0])
return customerId
})
maybeAppendWhere("invoice.invoice_status = $%d", form.InvoiceStatus.String(), nil)
maybeAppendWhere("invoice_number = $%d", form.InvoiceNumber.String(), nil)
maybeAppendWhere("invoice_date >= $%d", form.FromDate.String(), nil)
maybeAppendWhere("invoice_date <= $%d", form.ToDate.String(), nil)
if len(form.Tags.Tags) > 0 {
if form.TagsCondition.Selected == "and" {
appendWhere("invoice.tags @> $%d", form.Tags)
} else {
appendWhere("invoice.tags && $%d", form.Tags)
}
}
return strings.Join(where, ") AND ("), args
}
func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
conn := getConn(r) conn := getConn(r)
company := mustGetCompany(r) company := mustGetCompany(r)
@ -583,7 +603,7 @@ func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
}, },
Customer: &SelectField{ Customer: &SelectField{
Name: "customer", Name: "customer",
Label: pgettext("input", "Customer", locale), Label: pgettext("input", "Contact", locale),
Required: true, Required: true,
Options: mustGetContactOptions(ctx, conn, company), Options: mustGetContactOptions(ctx, conn, company),
}, },

View File

@ -33,6 +33,7 @@ type QuoteEntry struct {
type QuotesIndexPage struct { type QuotesIndexPage struct {
Quotes []*QuoteEntry Quotes []*QuoteEntry
TotalAmount string
Filters *quoteFilterForm Filters *quoteFilterForm
QuoteStatuses map[string]string QuoteStatuses map[string]string
} }
@ -47,44 +48,16 @@ func IndexQuotes(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
return return
} }
page := &QuotesIndexPage{ page := &QuotesIndexPage{
Quotes: mustCollectQuoteEntries(r.Context(), conn, company, locale, filters), Quotes: mustCollectQuoteEntries(r.Context(), conn, locale, filters),
TotalAmount: mustComputeQuotesTotalAmount(r.Context(), conn, filters),
Filters: filters, Filters: filters,
QuoteStatuses: mustCollectQuoteStatuses(r.Context(), conn, locale), QuoteStatuses: mustCollectQuoteStatuses(r.Context(), conn, locale),
} }
mustRenderMainTemplate(w, r, "quotes/index.gohtml", page) mustRenderMainTemplate(w, r, "quotes/index.gohtml", page)
} }
func mustCollectQuoteEntries(ctx context.Context, conn *Conn, company *Company, locale *Locale, filters *quoteFilterForm) []*QuoteEntry { func mustCollectQuoteEntries(ctx context.Context, conn *Conn, locale *Locale, filters *quoteFilterForm) []*QuoteEntry {
args := []interface{}{locale.Language.String(), company.Id} where, args := filters.BuildQuery([]interface{}{locale.Language.String()})
where := []string{"quote.company_id = $2"}
appendWhere := func(expression string, value interface{}) {
args = append(args, value)
where = append(where, fmt.Sprintf(expression, len(args)))
}
maybeAppendWhere := func(expression string, value string, conv func(string) interface{}) {
if value != "" {
if conv == nil {
appendWhere(expression, value)
} else {
appendWhere(expression, conv(value))
}
}
}
maybeAppendWhere("contact_id = $%d", filters.Customer.String(), func(v string) interface{} {
customerId, _ := strconv.Atoi(filters.Customer.Selected[0])
return customerId
})
maybeAppendWhere("quote.quote_status = $%d", filters.QuoteStatus.String(), nil)
maybeAppendWhere("quote_number = $%d", filters.QuoteNumber.String(), nil)
maybeAppendWhere("quote_date >= $%d", filters.FromDate.String(), nil)
maybeAppendWhere("quote_date <= $%d", filters.ToDate.String(), nil)
if len(filters.Tags.Tags) > 0 {
if filters.TagsCondition.Selected == "and" {
appendWhere("quote.tags @> $%d", filters.Tags)
} else {
appendWhere("quote.tags && $%d", filters.Tags)
}
}
rows := conn.MustQuery(ctx, fmt.Sprintf(` rows := conn.MustQuery(ctx, fmt.Sprintf(`
select quote.slug select quote.slug
, quote_date , quote_date
@ -103,7 +76,7 @@ func mustCollectQuoteEntries(ctx context.Context, conn *Conn, company *Company,
where (%s) where (%s)
order by quote_date desc order by quote_date desc
, quote_number desc , quote_number desc
`, strings.Join(where, ") AND (")), args...) `, where), args...)
defer rows.Close() defer rows.Close()
var entries []*QuoteEntry var entries []*QuoteEntry
@ -121,6 +94,19 @@ func mustCollectQuoteEntries(ctx context.Context, conn *Conn, company *Company,
return entries return entries
} }
func mustComputeQuotesTotalAmount(ctx context.Context, conn *Conn, filters *quoteFilterForm) string {
where, args := filters.BuildQuery(nil)
return conn.MustGetText(ctx, "0", fmt.Sprintf(`
select to_price(sum(total)::integer, decimal_digits)
from quote
left join quote_contact using (quote_id)
join quote_amount using (quote_id)
join currency using (currency_code)
where (%s)
group by decimal_digits
`, where), args...)
}
func mustCollectQuoteStatuses(ctx context.Context, conn *Conn, locale *Locale) map[string]string { func mustCollectQuoteStatuses(ctx context.Context, conn *Conn, locale *Locale) map[string]string {
rows := conn.MustQuery(ctx, "select quote_status.quote_status, isi18n.name from quote_status join quote_status_i18n isi18n using(quote_status) where isi18n.lang_tag = $1 order by quote_status", locale.Language.String()) rows := conn.MustQuery(ctx, "select quote_status.quote_status, isi18n.name from quote_status join quote_status_i18n isi18n using(quote_status) where isi18n.lang_tag = $1 order by quote_status", locale.Language.String())
defer rows.Close() defer rows.Close()
@ -158,7 +144,7 @@ func newQuoteFilterForm(ctx context.Context, conn *Conn, locale *Locale, company
company: company, company: company,
Customer: &SelectField{ Customer: &SelectField{
Name: "customer", Name: "customer",
Label: pgettext("input", "Customer", locale), Label: pgettext("input", "Contact", locale),
EmptyLabel: gettext("All customers", locale), EmptyLabel: gettext("All customers", locale),
Options: mustGetContactOptions(ctx, conn, company), Options: mustGetContactOptions(ctx, conn, company),
}, },
@ -219,6 +205,42 @@ func (form *quoteFilterForm) Parse(r *http.Request) error {
return nil return nil
} }
func (form *quoteFilterForm) BuildQuery(args []interface{}) (string, []interface{}) {
var where []string
appendWhere := func(expression string, value interface{}) {
args = append(args, value)
where = append(where, fmt.Sprintf(expression, len(args)))
}
maybeAppendWhere := func(expression string, value string, conv func(string) interface{}) {
if value != "" {
if conv == nil {
appendWhere(expression, value)
} else {
appendWhere(expression, conv(value))
}
}
}
appendWhere("quote.company_id = $%d", form.company.Id)
maybeAppendWhere("contact_id = $%d", form.Customer.String(), func(v string) interface{} {
customerId, _ := strconv.Atoi(form.Customer.Selected[0])
return customerId
})
maybeAppendWhere("quote.quote_status = $%d", form.QuoteStatus.String(), nil)
maybeAppendWhere("quote_number = $%d", form.QuoteNumber.String(), nil)
maybeAppendWhere("quote_date >= $%d", form.FromDate.String(), nil)
maybeAppendWhere("quote_date <= $%d", form.ToDate.String(), nil)
if len(form.Tags.Tags) > 0 {
if form.TagsCondition.Selected == "and" {
appendWhere("quote.tags @> $%d", form.Tags)
} else {
appendWhere("quote.tags && $%d", form.Tags)
}
}
return strings.Join(where, ") AND ("), args
}
func ServeQuote(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func ServeQuote(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
conn := getConn(r) conn := getConn(r)
company := mustGetCompany(r) company := mustGetCompany(r)
@ -583,7 +605,7 @@ func newQuoteForm(ctx context.Context, conn *Conn, locale *Locale, company *Comp
}, },
Customer: &SelectField{ Customer: &SelectField{
Name: "customer", Name: "customer",
Label: pgettext("input", "Customer", locale), Label: pgettext("input", "Contact", locale),
EmptyLabel: gettext("Select a customer to quote.", locale), EmptyLabel: gettext("Select a customer to quote.", locale),
Options: mustGetContactOptions(ctx, conn, company), Options: mustGetContactOptions(ctx, conn, company),
}, },

161
po/ca.po
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-06-20 10:48+0200\n" "POT-Creation-Date: 2023-06-20 11:29+0200\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"
@ -157,10 +157,9 @@ msgid "Invoice Num."
msgstr "Núm. factura" msgstr "Núm. factura"
#: web/template/invoices/index.gohtml:52 web/template/quotes/index.gohtml:52 #: web/template/invoices/index.gohtml:52 web/template/quotes/index.gohtml:52
#: web/template/contacts/index.gohtml:40 web/template/expenses/index.gohtml:43
msgctxt "title" msgctxt "title"
msgid "Contact" msgid "Customer"
msgstr "Contacte" msgstr "Client"
#: web/template/invoices/index.gohtml:53 web/template/quotes/index.gohtml:53 #: web/template/invoices/index.gohtml:53 web/template/quotes/index.gohtml:53
msgctxt "title" msgctxt "title"
@ -216,6 +215,11 @@ msgstr "Duplica"
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/index.gohtml:144 web/template/quotes/index.gohtml:152
#: web/template/expenses/index.gohtml:105
msgid "Total"
msgstr "Total"
#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:33 #: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:33
msgctxt "title" msgctxt "title"
msgid "Invoice %s" msgid "Invoice %s"
@ -449,6 +453,11 @@ msgctxt "action"
msgid "New contact" msgid "New contact"
msgstr "Nou contacte" msgstr "Nou contacte"
#: web/template/contacts/index.gohtml:40 web/template/expenses/index.gohtml:43
msgctxt "title"
msgid "Contact"
msgstr "Contacte"
#: web/template/contacts/index.gohtml:41 #: web/template/contacts/index.gohtml:41
msgctxt "title" msgctxt "title"
msgid "Email" msgid "Email"
@ -665,82 +674,82 @@ 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:164 pkg/products.go:263 pkg/quote.go:801 pkg/invoices.go:851 #: pkg/products.go:164 pkg/products.go:263 pkg/quote.go:823 pkg/invoices.go:871
#: pkg/contacts.go:135 #: pkg/contacts.go:135
msgctxt "input" msgctxt "input"
msgid "Name" msgid "Name"
msgstr "Nom" msgstr "Nom"
#: pkg/products.go:169 pkg/products.go:290 pkg/quote.go:188 pkg/quote.go:608 #: pkg/products.go:169 pkg/products.go:290 pkg/quote.go:174 pkg/quote.go:630
#: pkg/expenses.go:202 pkg/expenses.go:361 pkg/invoices.go:189 #: pkg/expenses.go:188 pkg/expenses.go:347 pkg/invoices.go:174
#: pkg/invoices.go:603 pkg/invoices.go:1150 pkg/contacts.go:140 #: pkg/invoices.go:623 pkg/invoices.go:1170 pkg/contacts.go:140
#: pkg/contacts.go:325 #: pkg/contacts.go:325
msgctxt "input" msgctxt "input"
msgid "Tags" msgid "Tags"
msgstr "Etiquetes" msgstr "Etiquetes"
#: pkg/products.go:173 pkg/quote.go:192 pkg/expenses.go:365 pkg/invoices.go:193 #: pkg/products.go:173 pkg/quote.go:178 pkg/expenses.go:351 pkg/invoices.go:178
#: pkg/contacts.go:144 #: pkg/contacts.go:144
msgctxt "input" msgctxt "input"
msgid "Tags Condition" msgid "Tags Condition"
msgstr "Condició de les etiquetes" msgstr "Condició de les etiquetes"
#: pkg/products.go:177 pkg/quote.go:196 pkg/expenses.go:369 pkg/invoices.go:197 #: pkg/products.go:177 pkg/quote.go:182 pkg/expenses.go:355 pkg/invoices.go:182
#: pkg/contacts.go:148 #: pkg/contacts.go:148
msgctxt "tag condition" msgctxt "tag condition"
msgid "All" msgid "All"
msgstr "Totes" msgstr "Totes"
#: pkg/products.go:178 pkg/expenses.go:370 pkg/invoices.go:198 #: pkg/products.go:178 pkg/expenses.go:356 pkg/invoices.go:183
#: pkg/contacts.go:149 #: pkg/contacts.go:149
msgid "Invoices must have all the specified labels." msgid "Invoices must have all the specified labels."
msgstr "Les factures han de tenir totes les etiquetes." msgstr "Les factures han de tenir totes les etiquetes."
#: pkg/products.go:182 pkg/quote.go:201 pkg/expenses.go:374 pkg/invoices.go:202 #: pkg/products.go:182 pkg/quote.go:187 pkg/expenses.go:360 pkg/invoices.go:187
#: pkg/contacts.go:153 #: pkg/contacts.go:153
msgctxt "tag condition" msgctxt "tag condition"
msgid "Any" msgid "Any"
msgstr "Qualsevol" msgstr "Qualsevol"
#: pkg/products.go:183 pkg/expenses.go:375 pkg/invoices.go:203 #: pkg/products.go:183 pkg/expenses.go:361 pkg/invoices.go:188
#: pkg/contacts.go:154 #: pkg/contacts.go:154
msgid "Invoices must have at least one of the specified labels." msgid "Invoices must have at least one of the specified labels."
msgstr "Les factures han de tenir com a mínim una de les etiquetes." msgstr "Les factures han de tenir com a mínim una de les etiquetes."
#: pkg/products.go:269 pkg/quote.go:815 pkg/invoices.go:865 #: pkg/products.go:269 pkg/quote.go:837 pkg/invoices.go:885
msgctxt "input" msgctxt "input"
msgid "Description" msgid "Description"
msgstr "Descripció" msgstr "Descripció"
#: pkg/products.go:274 pkg/quote.go:819 pkg/invoices.go:869 #: pkg/products.go:274 pkg/quote.go:841 pkg/invoices.go:889
msgctxt "input" msgctxt "input"
msgid "Price" msgid "Price"
msgstr "Preu" msgstr "Preu"
#: pkg/products.go:284 pkg/quote.go:848 pkg/expenses.go:181 pkg/invoices.go:898 #: pkg/products.go:284 pkg/quote.go:870 pkg/expenses.go:167 pkg/invoices.go:918
msgctxt "input" msgctxt "input"
msgid "Taxes" msgid "Taxes"
msgstr "Imposts" msgstr "Imposts"
#: pkg/products.go:309 pkg/quote.go:897 pkg/profile.go:92 pkg/invoices.go:947 #: pkg/products.go:309 pkg/quote.go:919 pkg/profile.go:92 pkg/invoices.go:967
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:310 pkg/quote.go:898 pkg/invoices.go:948 #: pkg/products.go:310 pkg/quote.go:920 pkg/invoices.go:968
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:311 pkg/quote.go:899 pkg/invoices.go:949 #: pkg/products.go:311 pkg/quote.go:921 pkg/invoices.go:969
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:313 pkg/quote.go:907 pkg/expenses.go:227 pkg/expenses.go:232 #: pkg/products.go:313 pkg/quote.go:929 pkg/expenses.go:213 pkg/expenses.go:218
#: pkg/invoices.go:957 #: pkg/invoices.go:977
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:314 pkg/quote.go:908 pkg/expenses.go:228 pkg/expenses.go:233 #: pkg/products.go:314 pkg/quote.go:930 pkg/expenses.go:214 pkg/expenses.go:219
#: pkg/invoices.go:958 #: pkg/invoices.go:978
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."
@ -847,141 +856,141 @@ msgstr "No podeu deixar el nom del mètode de pagament en blanc."
msgid "Payment instructions can not be empty." msgid "Payment instructions can not be empty."
msgstr "No podeu deixar les instruccions de pagament en blanc." msgstr "No podeu deixar les instruccions de pagament en blanc."
#: pkg/quote.go:148 pkg/quote.go:609 pkg/expenses.go:164 pkg/expenses.go:340 #: pkg/quote.go:147 pkg/quote.go:608 pkg/expenses.go:150 pkg/expenses.go:326
#: pkg/invoices.go:147 pkg/invoices.go:606 #: pkg/invoices.go:147 pkg/invoices.go:606
msgctxt "input" msgctxt "input"
msgid "Contact" msgid "Contact"
msgstr "Contacte" msgstr "Contacte"
#: pkg/quote.go:162 pkg/expenses.go:341 pkg/invoices.go:163 #: pkg/quote.go:148 pkg/invoices.go:148
msgid "All customers" msgid "All customers"
msgstr "Tots els clients" msgstr "Tots els clients"
#: pkg/quote.go:167 pkg/quote.go:580 #: pkg/quote.go:153 pkg/quote.go:602
msgctxt "input" msgctxt "input"
msgid "Quotation Status" msgid "Quotation Status"
msgstr "Estat del pressupost" msgstr "Estat del pressupost"
#: pkg/quote.go:168 pkg/invoices.go:169 #: pkg/quote.go:154 pkg/invoices.go:154
msgid "All status" msgid "All status"
msgstr "Tots els estats" msgstr "Tots els estats"
#: pkg/quote.go:173 #: pkg/quote.go:159
msgctxt "input" msgctxt "input"
msgid "Quotation Number" msgid "Quotation Number"
msgstr "Número de pressupost" msgstr "Número de pressupost"
#: pkg/quote.go:178 pkg/expenses.go:351 pkg/invoices.go:179 #: pkg/quote.go:164 pkg/expenses.go:337 pkg/invoices.go:164
msgctxt "input" msgctxt "input"
msgid "From Date" msgid "From Date"
msgstr "A partir de la data" msgstr "A partir de la data"
#: pkg/quote.go:183 pkg/expenses.go:356 pkg/invoices.go:184 #: pkg/quote.go:169 pkg/expenses.go:342 pkg/invoices.go:169
msgctxt "input" msgctxt "input"
msgid "To Date" msgid "To Date"
msgstr "Fins la data" msgstr "Fins la data"
#: pkg/quote.go:197 #: pkg/quote.go:183
msgid "Quotations must have all the specified labels." msgid "Quotations must have all the specified labels."
msgstr "Els pressuposts han de tenir totes les etiquetes." msgstr "Els pressuposts han de tenir totes les etiquetes."
#: pkg/quote.go:202 #: pkg/quote.go:188
msgid "Quotations must have at least one of the specified labels." msgid "Quotations must have at least one of the specified labels."
msgstr "Els pressuposts han de tenir com a mínim una de les etiquetes." msgstr "Els pressuposts han de tenir com a mínim una de les etiquetes."
#: pkg/quote.go:528 #: pkg/quote.go:550
msgid "quotations.zip" msgid "quotations.zip"
msgstr "pressuposts.zip" msgstr "pressuposts.zip"
#: pkg/quote.go:534 pkg/quote.go:1063 pkg/quote.go:1071 pkg/invoices.go:535 #: pkg/quote.go:556 pkg/quote.go:1085 pkg/quote.go:1093 pkg/invoices.go:555
#: pkg/invoices.go:1125 pkg/invoices.go:1133 #: pkg/invoices.go:1145 pkg/invoices.go:1153
msgid "Invalid action" msgid "Invalid action"
msgstr "Acció invàlida." msgstr "Acció invàlida."
#: pkg/quote.go:587 #: pkg/quote.go:609
msgid "Select a customer to quote." msgid "Select a customer to quote."
msgstr "Escolliu un client a pressupostar." msgstr "Escolliu un client a pressupostar."
#: pkg/quote.go:592 #: pkg/quote.go:614
msgctxt "input" msgctxt "input"
msgid "Quotation Date" msgid "Quotation Date"
msgstr "Data del pressupost" msgstr "Data del pressupost"
#: pkg/quote.go:598 #: pkg/quote.go:620
msgctxt "input" msgctxt "input"
msgid "Terms and conditions" msgid "Terms and conditions"
msgstr "Condicions dacceptació" msgstr "Condicions dacceptació"
#: pkg/quote.go:603 pkg/invoices.go:598 #: pkg/quote.go:625 pkg/invoices.go:618
msgctxt "input" msgctxt "input"
msgid "Notes" msgid "Notes"
msgstr "Notes" msgstr "Notes"
#: pkg/quote.go:612 pkg/invoices.go:608 #: pkg/quote.go:634 pkg/invoices.go:628
msgctxt "input" msgctxt "input"
msgid "Payment Method" msgid "Payment Method"
msgstr "Mètode de pagament" msgstr "Mètode de pagament"
#: pkg/quote.go:613 #: pkg/quote.go:635
msgid "Select a payment method." msgid "Select a payment method."
msgstr "Escolliu un mètode de pagament." msgstr "Escolliu un mètode de pagament."
#: pkg/quote.go:649 #: pkg/quote.go:671
msgid "Selected quotation status is not valid." msgid "Selected quotation status is not valid."
msgstr "Heu seleccionat un estat de pressupost que no és vàlid." msgstr "Heu seleccionat un estat de pressupost que no és vàlid."
#: pkg/quote.go:651 pkg/invoices.go:645 #: pkg/quote.go:673 pkg/invoices.go:665
msgid "Selected customer is not valid." msgid "Selected customer is not valid."
msgstr "Heu seleccionat un client que no és vàlid." msgstr "Heu seleccionat un client que no és vàlid."
#: pkg/quote.go:653 #: pkg/quote.go:675
msgid "Quotation date can not be empty." msgid "Quotation date can not be empty."
msgstr "No podeu deixar la data del pressupost en blanc." msgstr "No podeu deixar la data del pressupost en blanc."
#: pkg/quote.go:654 #: pkg/quote.go:676
msgid "Quotation date must be a valid date." msgid "Quotation date must be a valid date."
msgstr "La data del pressupost ha de ser vàlida." msgstr "La data del pressupost ha de ser vàlida."
#: pkg/quote.go:657 pkg/invoices.go:649 #: pkg/quote.go:679 pkg/invoices.go:669
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/quote.go:791 pkg/quote.go:796 pkg/invoices.go:841 pkg/invoices.go:846 #: pkg/quote.go:813 pkg/quote.go:818 pkg/invoices.go:861 pkg/invoices.go:866
msgctxt "input" msgctxt "input"
msgid "Id" msgid "Id"
msgstr "Identificador" msgstr "Identificador"
#: pkg/quote.go:829 pkg/invoices.go:879 #: pkg/quote.go:851 pkg/invoices.go:899
msgctxt "input" msgctxt "input"
msgid "Quantity" msgid "Quantity"
msgstr "Quantitat" msgstr "Quantitat"
#: pkg/quote.go:838 pkg/invoices.go:888 #: pkg/quote.go:860 pkg/invoices.go:908
msgctxt "input" msgctxt "input"
msgid "Discount (%)" msgid "Discount (%)"
msgstr "Descompte (%)" msgstr "Descompte (%)"
#: pkg/quote.go:892 #: pkg/quote.go:914
msgid "Quotation product ID must be a number greater than zero." msgid "Quotation product ID must be a number greater than zero."
msgstr "LID del producte de pressupost ha de ser un número major a zero." msgstr "LID del producte de pressupost ha de ser un número major a zero."
#: pkg/quote.go:895 pkg/invoices.go:945 #: pkg/quote.go:917 pkg/invoices.go:965
msgid "Product ID must be a positive number or zero." msgid "Product ID must be a positive number or zero."
msgstr "LID del producte ha de ser un número positiu o zero." msgstr "LID del producte ha de ser un número positiu o zero."
#: pkg/quote.go:901 pkg/invoices.go:951 #: pkg/quote.go:923 pkg/invoices.go:971
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/quote.go:902 pkg/invoices.go:952 #: pkg/quote.go:924 pkg/invoices.go:972
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/quote.go:904 pkg/invoices.go:954 #: pkg/quote.go:926 pkg/invoices.go:974
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/quote.go:905 pkg/invoices.go:955 #: pkg/quote.go:927 pkg/invoices.go:975
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."
@ -1048,87 +1057,87 @@ msgctxt "period option"
msgid "Previous year" msgid "Previous year"
msgstr "Any anterior" msgstr "Any anterior"
#: pkg/expenses.go:129 #: pkg/expenses.go:115
msgid "Select a contact." msgid "Select a contact."
msgstr "Escolliu un contacte." msgstr "Escolliu un contacte."
#: pkg/expenses.go:170 #: pkg/expenses.go:156
msgctxt "input" msgctxt "input"
msgid "Invoice number" msgid "Invoice number"
msgstr "Número de factura" msgstr "Número de factura"
#: pkg/expenses.go:175 pkg/invoices.go:592 #: pkg/expenses.go:161 pkg/invoices.go:612
msgctxt "input" msgctxt "input"
msgid "Invoice Date" msgid "Invoice Date"
msgstr "Data de factura" msgstr "Data de factura"
#: pkg/expenses.go:187 #: pkg/expenses.go:173
msgctxt "input" msgctxt "input"
msgid "Amount" msgid "Amount"
msgstr "Import" msgstr "Import"
#: pkg/expenses.go:197 #: pkg/expenses.go:183
msgctxt "input" msgctxt "input"
msgid "File" msgid "File"
msgstr "Fitxer" msgstr "Fitxer"
#: pkg/expenses.go:225 #: pkg/expenses.go:211
msgid "Selected contact is not valid." msgid "Selected contact is not valid."
msgstr "Heu seleccionat un contacte que no és vàlid." msgstr "Heu seleccionat un contacte que no és vàlid."
#: pkg/expenses.go:226 pkg/invoices.go:647 #: pkg/expenses.go:212 pkg/invoices.go:667
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/expenses.go:229 #: pkg/expenses.go:215
msgid "Amount can not be empty." msgid "Amount can not be empty."
msgstr "No podeu deixar limport en blanc." msgstr "No podeu deixar limport en blanc."
#: pkg/expenses.go:230 #: pkg/expenses.go:216
msgid "Amount must be a number greater than zero." msgid "Amount must be a number greater than zero."
msgstr "Limport ha de ser un número major a zero." msgstr "Limport ha de ser un número major a zero."
#: pkg/expenses.go:341 #: pkg/expenses.go:327
msgid "All contacts" msgid "All contacts"
msgstr "Tots els contactes" msgstr "Tots els contactes"
#: pkg/expenses.go:346 pkg/invoices.go:159 #: pkg/expenses.go:332 pkg/invoices.go:159
msgctxt "input" msgctxt "input"
msgid "Invoice Number" msgid "Invoice Number"
msgstr "Número de factura" msgstr "Número de factura"
#: pkg/invoices.go:168 pkg/invoices.go:580 #: pkg/invoices.go:153 pkg/invoices.go:600
msgctxt "input" msgctxt "input"
msgid "Invoice Status" msgid "Invoice Status"
msgstr "Estat de la factura" msgstr "Estat de la factura"
#: pkg/invoices.go:428 #: pkg/invoices.go:448
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:529 #: pkg/invoices.go:549
msgid "invoices.zip" msgid "invoices.zip"
msgstr "factures.zip" msgstr "factures.zip"
#: pkg/invoices.go:644 #: pkg/invoices.go:664
msgid "Selected invoice status is not valid." msgid "Selected invoice status is not valid."
msgstr "Heu seleccionat un estat de factura que no és vàlid." msgstr "Heu seleccionat un estat de factura que no és vàlid."
#: pkg/invoices.go:646 #: pkg/invoices.go:666
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:782 #: pkg/invoices.go:802
#, c-format #, c-format
msgid "Re: quotation #%s of %s" msgid "Re: quotation #%s of %s"
msgstr "Ref: pressupost núm. %s del %s" msgstr "Ref: pressupost núm. %s del %s"
#: pkg/invoices.go:783 #: pkg/invoices.go:803
msgctxt "to_char" msgctxt "to_char"
msgid "MM/DD/YYYY" msgid "MM/DD/YYYY"
msgstr "DD/MM/YYYY" msgstr "DD/MM/YYYY"
#: pkg/invoices.go:942 #: pkg/invoices.go:962
msgid "Invoice product ID must be a number greater than zero." msgid "Invoice product ID must be a number greater than zero."
msgstr "LID del producte de factura ha de ser un número major a zero." msgstr "LID del producte de factura ha de ser un número major a zero."
@ -1234,10 +1243,6 @@ msgstr "No podeu deixar el codi postal en blanc."
msgid "This value is not a valid postal code." msgid "This value is not a valid postal code."
msgstr "Aquest valor no és un codi postal vàlid." msgstr "Aquest valor no és un codi postal vàlid."
#~ msgctxt "title"
#~ msgid "Customer"
#~ msgstr "Client"
#~ msgctxt "input" #~ msgctxt "input"
#~ msgid "Customer" #~ msgid "Customer"
#~ msgstr "Client" #~ msgstr "Client"

161
po/es.po
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-06-20 10:48+0200\n" "POT-Creation-Date: 2023-06-20 11:29+0200\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"
@ -157,10 +157,9 @@ msgid "Invoice Num."
msgstr "N.º factura" msgstr "N.º factura"
#: web/template/invoices/index.gohtml:52 web/template/quotes/index.gohtml:52 #: web/template/invoices/index.gohtml:52 web/template/quotes/index.gohtml:52
#: web/template/contacts/index.gohtml:40 web/template/expenses/index.gohtml:43
msgctxt "title" msgctxt "title"
msgid "Contact" msgid "Customer"
msgstr "Contacto" msgstr "Cliente"
#: web/template/invoices/index.gohtml:53 web/template/quotes/index.gohtml:53 #: web/template/invoices/index.gohtml:53 web/template/quotes/index.gohtml:53
msgctxt "title" msgctxt "title"
@ -216,6 +215,11 @@ msgstr "Duplicar"
msgid "No invoices added yet." msgid "No invoices added yet."
msgstr "No hay facturas." msgstr "No hay facturas."
#: web/template/invoices/index.gohtml:144 web/template/quotes/index.gohtml:152
#: web/template/expenses/index.gohtml:105
msgid "Total"
msgstr "Total"
#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:33 #: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:33
msgctxt "title" msgctxt "title"
msgid "Invoice %s" msgid "Invoice %s"
@ -449,6 +453,11 @@ msgctxt "action"
msgid "New contact" msgid "New contact"
msgstr "Nuevo contacto" msgstr "Nuevo contacto"
#: web/template/contacts/index.gohtml:40 web/template/expenses/index.gohtml:43
msgctxt "title"
msgid "Contact"
msgstr "Contacto"
#: web/template/contacts/index.gohtml:41 #: web/template/contacts/index.gohtml:41
msgctxt "title" msgctxt "title"
msgid "Email" msgid "Email"
@ -665,82 +674,82 @@ 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:164 pkg/products.go:263 pkg/quote.go:801 pkg/invoices.go:851 #: pkg/products.go:164 pkg/products.go:263 pkg/quote.go:823 pkg/invoices.go:871
#: pkg/contacts.go:135 #: pkg/contacts.go:135
msgctxt "input" msgctxt "input"
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
#: pkg/products.go:169 pkg/products.go:290 pkg/quote.go:188 pkg/quote.go:608 #: pkg/products.go:169 pkg/products.go:290 pkg/quote.go:174 pkg/quote.go:630
#: pkg/expenses.go:202 pkg/expenses.go:361 pkg/invoices.go:189 #: pkg/expenses.go:188 pkg/expenses.go:347 pkg/invoices.go:174
#: pkg/invoices.go:603 pkg/invoices.go:1150 pkg/contacts.go:140 #: pkg/invoices.go:623 pkg/invoices.go:1170 pkg/contacts.go:140
#: pkg/contacts.go:325 #: pkg/contacts.go:325
msgctxt "input" msgctxt "input"
msgid "Tags" msgid "Tags"
msgstr "Etiquetes" msgstr "Etiquetes"
#: pkg/products.go:173 pkg/quote.go:192 pkg/expenses.go:365 pkg/invoices.go:193 #: pkg/products.go:173 pkg/quote.go:178 pkg/expenses.go:351 pkg/invoices.go:178
#: pkg/contacts.go:144 #: pkg/contacts.go:144
msgctxt "input" msgctxt "input"
msgid "Tags Condition" msgid "Tags Condition"
msgstr "Condición de las etiquetas" msgstr "Condición de las etiquetas"
#: pkg/products.go:177 pkg/quote.go:196 pkg/expenses.go:369 pkg/invoices.go:197 #: pkg/products.go:177 pkg/quote.go:182 pkg/expenses.go:355 pkg/invoices.go:182
#: pkg/contacts.go:148 #: pkg/contacts.go:148
msgctxt "tag condition" msgctxt "tag condition"
msgid "All" msgid "All"
msgstr "Todas" msgstr "Todas"
#: pkg/products.go:178 pkg/expenses.go:370 pkg/invoices.go:198 #: pkg/products.go:178 pkg/expenses.go:356 pkg/invoices.go:183
#: pkg/contacts.go:149 #: pkg/contacts.go:149
msgid "Invoices must have all the specified labels." msgid "Invoices must have all the specified labels."
msgstr "Las facturas deben tener todas las etiquetas." msgstr "Las facturas deben tener todas las etiquetas."
#: pkg/products.go:182 pkg/quote.go:201 pkg/expenses.go:374 pkg/invoices.go:202 #: pkg/products.go:182 pkg/quote.go:187 pkg/expenses.go:360 pkg/invoices.go:187
#: pkg/contacts.go:153 #: pkg/contacts.go:153
msgctxt "tag condition" msgctxt "tag condition"
msgid "Any" msgid "Any"
msgstr "Cualquiera" msgstr "Cualquiera"
#: pkg/products.go:183 pkg/expenses.go:375 pkg/invoices.go:203 #: pkg/products.go:183 pkg/expenses.go:361 pkg/invoices.go:188
#: pkg/contacts.go:154 #: pkg/contacts.go:154
msgid "Invoices must have at least one of the specified labels." msgid "Invoices must have at least one of the specified labels."
msgstr "Las facturas deben tener como mínimo una de las etiquetas." msgstr "Las facturas deben tener como mínimo una de las etiquetas."
#: pkg/products.go:269 pkg/quote.go:815 pkg/invoices.go:865 #: pkg/products.go:269 pkg/quote.go:837 pkg/invoices.go:885
msgctxt "input" msgctxt "input"
msgid "Description" msgid "Description"
msgstr "Descripción" msgstr "Descripción"
#: pkg/products.go:274 pkg/quote.go:819 pkg/invoices.go:869 #: pkg/products.go:274 pkg/quote.go:841 pkg/invoices.go:889
msgctxt "input" msgctxt "input"
msgid "Price" msgid "Price"
msgstr "Precio" msgstr "Precio"
#: pkg/products.go:284 pkg/quote.go:848 pkg/expenses.go:181 pkg/invoices.go:898 #: pkg/products.go:284 pkg/quote.go:870 pkg/expenses.go:167 pkg/invoices.go:918
msgctxt "input" msgctxt "input"
msgid "Taxes" msgid "Taxes"
msgstr "Impuestos" msgstr "Impuestos"
#: pkg/products.go:309 pkg/quote.go:897 pkg/profile.go:92 pkg/invoices.go:947 #: pkg/products.go:309 pkg/quote.go:919 pkg/profile.go:92 pkg/invoices.go:967
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:310 pkg/quote.go:898 pkg/invoices.go:948 #: pkg/products.go:310 pkg/quote.go:920 pkg/invoices.go:968
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:311 pkg/quote.go:899 pkg/invoices.go:949 #: pkg/products.go:311 pkg/quote.go:921 pkg/invoices.go:969
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:313 pkg/quote.go:907 pkg/expenses.go:227 pkg/expenses.go:232 #: pkg/products.go:313 pkg/quote.go:929 pkg/expenses.go:213 pkg/expenses.go:218
#: pkg/invoices.go:957 #: pkg/invoices.go:977
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:314 pkg/quote.go:908 pkg/expenses.go:228 pkg/expenses.go:233 #: pkg/products.go:314 pkg/quote.go:930 pkg/expenses.go:214 pkg/expenses.go:219
#: pkg/invoices.go:958 #: pkg/invoices.go:978
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."
@ -847,141 +856,141 @@ msgstr "No podéis dejar el nombre del método de pago en blanco."
msgid "Payment instructions can not be empty." msgid "Payment instructions can not be empty."
msgstr "No podéis dejar las instrucciones de pago en blanco." msgstr "No podéis dejar las instrucciones de pago en blanco."
#: pkg/quote.go:148 pkg/quote.go:609 pkg/expenses.go:164 pkg/expenses.go:340 #: pkg/quote.go:147 pkg/quote.go:608 pkg/expenses.go:150 pkg/expenses.go:326
#: pkg/invoices.go:147 pkg/invoices.go:606 #: pkg/invoices.go:147 pkg/invoices.go:606
msgctxt "input" msgctxt "input"
msgid "Contact" msgid "Contact"
msgstr "Contacto" msgstr "Contacto"
#: pkg/quote.go:162 pkg/expenses.go:341 pkg/invoices.go:163 #: pkg/quote.go:148 pkg/invoices.go:148
msgid "All customers" msgid "All customers"
msgstr "Todos los clientes" msgstr "Todos los clientes"
#: pkg/quote.go:167 pkg/quote.go:580 #: pkg/quote.go:153 pkg/quote.go:602
msgctxt "input" msgctxt "input"
msgid "Quotation Status" msgid "Quotation Status"
msgstr "Estado del presupuesto" msgstr "Estado del presupuesto"
#: pkg/quote.go:168 pkg/invoices.go:169 #: pkg/quote.go:154 pkg/invoices.go:154
msgid "All status" msgid "All status"
msgstr "Todos los estados" msgstr "Todos los estados"
#: pkg/quote.go:173 #: pkg/quote.go:159
msgctxt "input" msgctxt "input"
msgid "Quotation Number" msgid "Quotation Number"
msgstr "Número de presupuesto" msgstr "Número de presupuesto"
#: pkg/quote.go:178 pkg/expenses.go:351 pkg/invoices.go:179 #: pkg/quote.go:164 pkg/expenses.go:337 pkg/invoices.go:164
msgctxt "input" msgctxt "input"
msgid "From Date" msgid "From Date"
msgstr "A partir de la fecha" msgstr "A partir de la fecha"
#: pkg/quote.go:183 pkg/expenses.go:356 pkg/invoices.go:184 #: pkg/quote.go:169 pkg/expenses.go:342 pkg/invoices.go:169
msgctxt "input" msgctxt "input"
msgid "To Date" msgid "To Date"
msgstr "Hasta la fecha" msgstr "Hasta la fecha"
#: pkg/quote.go:197 #: pkg/quote.go:183
msgid "Quotations must have all the specified labels." msgid "Quotations must have all the specified labels."
msgstr "Los presupuestos deben tener todas las etiquetas." msgstr "Los presupuestos deben tener todas las etiquetas."
#: pkg/quote.go:202 #: pkg/quote.go:188
msgid "Quotations must have at least one of the specified labels." msgid "Quotations must have at least one of the specified labels."
msgstr "Los presupuestos deben tener como mínimo una de las etiquetas." msgstr "Los presupuestos deben tener como mínimo una de las etiquetas."
#: pkg/quote.go:528 #: pkg/quote.go:550
msgid "quotations.zip" msgid "quotations.zip"
msgstr "presupuestos.zip" msgstr "presupuestos.zip"
#: pkg/quote.go:534 pkg/quote.go:1063 pkg/quote.go:1071 pkg/invoices.go:535 #: pkg/quote.go:556 pkg/quote.go:1085 pkg/quote.go:1093 pkg/invoices.go:555
#: pkg/invoices.go:1125 pkg/invoices.go:1133 #: pkg/invoices.go:1145 pkg/invoices.go:1153
msgid "Invalid action" msgid "Invalid action"
msgstr "Acción inválida." msgstr "Acción inválida."
#: pkg/quote.go:587 #: pkg/quote.go:609
msgid "Select a customer to quote." msgid "Select a customer to quote."
msgstr "Escoged un cliente a presupuestar." msgstr "Escoged un cliente a presupuestar."
#: pkg/quote.go:592 #: pkg/quote.go:614
msgctxt "input" msgctxt "input"
msgid "Quotation Date" msgid "Quotation Date"
msgstr "Fecha del presupuesto" msgstr "Fecha del presupuesto"
#: pkg/quote.go:598 #: pkg/quote.go:620
msgctxt "input" msgctxt "input"
msgid "Terms and conditions" msgid "Terms and conditions"
msgstr "Condiciones de aceptación" msgstr "Condiciones de aceptación"
#: pkg/quote.go:603 pkg/invoices.go:598 #: pkg/quote.go:625 pkg/invoices.go:618
msgctxt "input" msgctxt "input"
msgid "Notes" msgid "Notes"
msgstr "Notas" msgstr "Notas"
#: pkg/quote.go:612 pkg/invoices.go:608 #: pkg/quote.go:634 pkg/invoices.go:628
msgctxt "input" msgctxt "input"
msgid "Payment Method" msgid "Payment Method"
msgstr "Método de pago" msgstr "Método de pago"
#: pkg/quote.go:613 #: pkg/quote.go:635
msgid "Select a payment method." msgid "Select a payment method."
msgstr "Escoged un método e pago." msgstr "Escoged un método e pago."
#: pkg/quote.go:649 #: pkg/quote.go:671
msgid "Selected quotation status is not valid." msgid "Selected quotation status is not valid."
msgstr "Habéis escogido un estado de presupuesto que no es válido." msgstr "Habéis escogido un estado de presupuesto que no es válido."
#: pkg/quote.go:651 pkg/invoices.go:645 #: pkg/quote.go:673 pkg/invoices.go:665
msgid "Selected customer is not valid." msgid "Selected customer is not valid."
msgstr "Habéis escogido un cliente que no es válido." msgstr "Habéis escogido un cliente que no es válido."
#: pkg/quote.go:653 #: pkg/quote.go:675
msgid "Quotation date can not be empty." msgid "Quotation date can not be empty."
msgstr "No podéis dejar la fecha del presupuesto en blanco." msgstr "No podéis dejar la fecha del presupuesto en blanco."
#: pkg/quote.go:654 #: pkg/quote.go:676
msgid "Quotation date must be a valid date." msgid "Quotation date must be a valid date."
msgstr "La fecha de presupuesto debe ser válida." msgstr "La fecha de presupuesto debe ser válida."
#: pkg/quote.go:657 pkg/invoices.go:649 #: pkg/quote.go:679 pkg/invoices.go:669
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/quote.go:791 pkg/quote.go:796 pkg/invoices.go:841 pkg/invoices.go:846 #: pkg/quote.go:813 pkg/quote.go:818 pkg/invoices.go:861 pkg/invoices.go:866
msgctxt "input" msgctxt "input"
msgid "Id" msgid "Id"
msgstr "Identificador" msgstr "Identificador"
#: pkg/quote.go:829 pkg/invoices.go:879 #: pkg/quote.go:851 pkg/invoices.go:899
msgctxt "input" msgctxt "input"
msgid "Quantity" msgid "Quantity"
msgstr "Cantidad" msgstr "Cantidad"
#: pkg/quote.go:838 pkg/invoices.go:888 #: pkg/quote.go:860 pkg/invoices.go:908
msgctxt "input" msgctxt "input"
msgid "Discount (%)" msgid "Discount (%)"
msgstr "Descuento (%)" msgstr "Descuento (%)"
#: pkg/quote.go:892 #: pkg/quote.go:914
msgid "Quotation product ID must be a number greater than zero." msgid "Quotation product ID must be a number greater than zero."
msgstr "El ID de producto de presupuesto tiene que ser un número mayor a cero." msgstr "El ID de producto de presupuesto tiene que ser un número mayor a cero."
#: pkg/quote.go:895 pkg/invoices.go:945 #: pkg/quote.go:917 pkg/invoices.go:965
msgid "Product ID must be a positive number or zero." msgid "Product ID must be a positive number or zero."
msgstr "El ID de producto tiene que ser un número positivo o cero." msgstr "El ID de producto tiene que ser un número positivo o cero."
#: pkg/quote.go:901 pkg/invoices.go:951 #: pkg/quote.go:923 pkg/invoices.go:971
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/quote.go:902 pkg/invoices.go:952 #: pkg/quote.go:924 pkg/invoices.go:972
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/quote.go:904 pkg/invoices.go:954 #: pkg/quote.go:926 pkg/invoices.go:974
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/quote.go:905 pkg/invoices.go:955 #: pkg/quote.go:927 pkg/invoices.go:975
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."
@ -1048,87 +1057,87 @@ msgctxt "period option"
msgid "Previous year" msgid "Previous year"
msgstr "Año anterior" msgstr "Año anterior"
#: pkg/expenses.go:129 #: pkg/expenses.go:115
msgid "Select a contact." msgid "Select a contact."
msgstr "Escoged un contacto" msgstr "Escoged un contacto"
#: pkg/expenses.go:170 #: pkg/expenses.go:156
msgctxt "input" msgctxt "input"
msgid "Invoice number" msgid "Invoice number"
msgstr "Número de factura" msgstr "Número de factura"
#: pkg/expenses.go:175 pkg/invoices.go:592 #: pkg/expenses.go:161 pkg/invoices.go:612
msgctxt "input" msgctxt "input"
msgid "Invoice Date" msgid "Invoice Date"
msgstr "Fecha de factura" msgstr "Fecha de factura"
#: pkg/expenses.go:187 #: pkg/expenses.go:173
msgctxt "input" msgctxt "input"
msgid "Amount" msgid "Amount"
msgstr "Importe" msgstr "Importe"
#: pkg/expenses.go:197 #: pkg/expenses.go:183
msgctxt "input" msgctxt "input"
msgid "File" msgid "File"
msgstr "Archivo" msgstr "Archivo"
#: pkg/expenses.go:225 #: pkg/expenses.go:211
msgid "Selected contact is not valid." msgid "Selected contact is not valid."
msgstr "Habéis escogido un contacto que no es válido." msgstr "Habéis escogido un contacto que no es válido."
#: pkg/expenses.go:226 pkg/invoices.go:647 #: pkg/expenses.go:212 pkg/invoices.go:667
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/expenses.go:229 #: pkg/expenses.go:215
msgid "Amount can not be empty." msgid "Amount can not be empty."
msgstr "No podéis dejar el importe en blanco." msgstr "No podéis dejar el importe en blanco."
#: pkg/expenses.go:230 #: pkg/expenses.go:216
msgid "Amount must be a number greater than zero." msgid "Amount must be a number greater than zero."
msgstr "El importe tiene que ser un número mayor a cero." msgstr "El importe tiene que ser un número mayor a cero."
#: pkg/expenses.go:341 #: pkg/expenses.go:327
msgid "All contacts" msgid "All contacts"
msgstr "Todos los contactos" msgstr "Todos los contactos"
#: pkg/expenses.go:346 pkg/invoices.go:159 #: pkg/expenses.go:332 pkg/invoices.go:159
msgctxt "input" msgctxt "input"
msgid "Invoice Number" msgid "Invoice Number"
msgstr "Número de factura" msgstr "Número de factura"
#: pkg/invoices.go:168 pkg/invoices.go:580 #: pkg/invoices.go:153 pkg/invoices.go:600
msgctxt "input" msgctxt "input"
msgid "Invoice Status" msgid "Invoice Status"
msgstr "Estado de la factura" msgstr "Estado de la factura"
#: pkg/invoices.go:428 #: pkg/invoices.go:448
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:529 #: pkg/invoices.go:549
msgid "invoices.zip" msgid "invoices.zip"
msgstr "facturas.zip" msgstr "facturas.zip"
#: pkg/invoices.go:644 #: pkg/invoices.go:664
msgid "Selected invoice status is not valid." msgid "Selected invoice status is not valid."
msgstr "Habéis escogido un estado de factura que no es válido." msgstr "Habéis escogido un estado de factura que no es válido."
#: pkg/invoices.go:646 #: pkg/invoices.go:666
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:782 #: pkg/invoices.go:802
#, c-format #, c-format
msgid "Re: quotation #%s of %s" msgid "Re: quotation #%s of %s"
msgstr "Ref: presupuesto n.º %s del %s" msgstr "Ref: presupuesto n.º %s del %s"
#: pkg/invoices.go:783 #: pkg/invoices.go:803
msgctxt "to_char" msgctxt "to_char"
msgid "MM/DD/YYYY" msgid "MM/DD/YYYY"
msgstr "DD/MM/YYYY" msgstr "DD/MM/YYYY"
#: pkg/invoices.go:942 #: pkg/invoices.go:962
msgid "Invoice product ID must be a number greater than zero." msgid "Invoice product ID must be a number greater than zero."
msgstr "El ID de producto de factura tiene que ser un número mayor a cero." msgstr "El ID de producto de factura tiene que ser un número mayor a cero."
@ -1234,10 +1243,6 @@ msgstr "No podéis dejar el código postal en blanco."
msgid "This value is not a valid postal code." msgid "This value is not a valid postal code."
msgstr "Este valor no es un código postal válido válido." msgstr "Este valor no es un código postal válido válido."
#~ msgctxt "title"
#~ msgid "Customer"
#~ msgstr "Cliente"
#~ msgctxt "input" #~ msgctxt "input"
#~ msgid "Customer" #~ msgid "Customer"
#~ msgstr "Cliente" #~ msgstr "Cliente"

View File

@ -263,6 +263,10 @@ tbody tr:nth-child(even) {
background-color: var(--numerus--header--background-color); background-color: var(--numerus--header--background-color);
} }
tfoot th {
text-align: right;
}
div[role="alert"].error { div[role="alert"].error {
padding: 1.25em; padding: 1.25em;
background-color: var(--numerus--color--red); background-color: var(--numerus--color--red);

View File

@ -99,5 +99,14 @@
</tr> </tr>
{{ end }} {{ end }}
</tbody> </tbody>
{{ if .Expenses }}
<tfoot>
<tr>
<th scope="row" colspan="5">{{( gettext "Total" )}}</th>
<td class="numeric">{{ .TotalAmount|formatPrice }}</td>
<td colspan="2"></td>
</tr>
</tfoot>
{{ end }}
</table> </table>
{{- end }} {{- end }}

View File

@ -138,5 +138,14 @@
</tr> </tr>
{{ end }} {{ end }}
</tbody> </tbody>
{{ if .Invoices }}
<tfoot>
<tr>
<th scope="row" colspan="6">{{( gettext "Total" )}}</th>
<td class="numeric">{{ .TotalAmount|formatPrice }}</td>
<td colspan="2"></td>
</tr>
</tfoot>
{{ end }}
</table> </table>
{{- end }} {{- end }}

View File

@ -103,9 +103,9 @@
</td> </td>
<td class="numeric">{{ .Total|formatPrice }}</td> <td class="numeric">{{ .Total|formatPrice }}</td>
<td class="quote-download"><a href="{{ companyURI "/quotes/"}}{{ .Slug }}.pdf" <td class="quote-download"><a href="{{ companyURI "/quotes/"}}{{ .Slug }}.pdf"
download="{{ .Number}}.pdf" download="{{ .Number}}.pdf"
title="{{( pgettext "Download quotation" "action" )}}" title="{{( pgettext "Download quotation" "action" )}}"
aria-label="{{( pgettext "Download quotation" "action" )}}"><i aria-label="{{( pgettext "Download quotation" "action" )}}"><i
class="ri-download-line"></i></a></td> class="ri-download-line"></i></a></td>
<td class="actions"> <td class="actions">
<details class="menu"> <details class="menu">
@ -146,5 +146,14 @@
</tr> </tr>
{{ end }} {{ end }}
</tbody> </tbody>
{{ if .Quotes }}
<tfoot>
<tr>
<th scope="row" colspan="6">{{( gettext "Total" )}}</th>
<td class="numeric">{{ .TotalAmount|formatPrice }}</td>
<td colspan="2"></td>
</tr>
</tfoot>
{{ end }}
</table> </table>
{{- end }} {{- end }}