Add expenses’ index and add form
This commit is contained in:
parent
97ad76d82c
commit
55d650bd62
|
@ -0,0 +1,239 @@
|
|||
package pkg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"html/template"
|
||||
"math"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ExpenseEntry struct {
|
||||
Slug string
|
||||
InvoiceDate time.Time
|
||||
InvoiceNumber string
|
||||
Amount string
|
||||
InvoicerName string
|
||||
Tags []string
|
||||
}
|
||||
|
||||
type expensesIndexPage struct {
|
||||
Expenses []*ExpenseEntry
|
||||
}
|
||||
|
||||
func IndexExpenses(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
conn := getConn(r)
|
||||
company := mustGetCompany(r)
|
||||
page := &expensesIndexPage{
|
||||
Expenses: mustCollectExpenseEntries(r.Context(), conn, company),
|
||||
}
|
||||
mustRenderMainTemplate(w, r, "expenses/index.gohtml", page)
|
||||
}
|
||||
|
||||
func mustCollectExpenseEntries(ctx context.Context, conn *Conn, company *Company) []*ExpenseEntry {
|
||||
rows, err := conn.Query(ctx, `
|
||||
select expense.slug
|
||||
, invoice_date
|
||||
, invoice_number
|
||||
, to_price(amount, decimal_digits)
|
||||
, contact.business_name
|
||||
, expense.tags
|
||||
from expense
|
||||
join contact using (contact_id)
|
||||
join currency using (currency_code)
|
||||
where expense.company_id = $1
|
||||
order by invoice_date
|
||||
`, company.Id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var entries []*ExpenseEntry
|
||||
for rows.Next() {
|
||||
entry := &ExpenseEntry{}
|
||||
err = rows.Scan(&entry.Slug, &entry.InvoiceDate, &entry.InvoiceNumber, &entry.Amount, &entry.InvoicerName, &entry.Tags)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
panic(rows.Err())
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
func ServeExpenseForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
locale := getLocale(r)
|
||||
conn := getConn(r)
|
||||
company := mustGetCompany(r)
|
||||
form := newExpenseForm(r.Context(), conn, locale, company)
|
||||
slug := params[0].Value
|
||||
if slug == "new" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
form.InvoiceDate.Val = time.Now().Format("2006-01-02")
|
||||
mustRenderNewExpenseForm(w, r, form)
|
||||
return
|
||||
}
|
||||
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
mustRenderEditExpenseForm(w, r, slug, form)
|
||||
}
|
||||
|
||||
func mustRenderNewExpenseForm(w http.ResponseWriter, r *http.Request, form *expenseForm) {
|
||||
locale := getLocale(r)
|
||||
form.Invoicer.EmptyLabel = gettext("Select a contact.", locale)
|
||||
mustRenderMainTemplate(w, r, "expenses/new.gohtml", form)
|
||||
}
|
||||
|
||||
func mustRenderEditExpenseForm(w http.ResponseWriter, r *http.Request, slug string, form *expenseForm) {
|
||||
page := &editExpensePage{
|
||||
Slug: slug,
|
||||
Form: form,
|
||||
}
|
||||
mustRenderMainTemplate(w, r, "expenses/edit.gohtml", page)
|
||||
}
|
||||
|
||||
type editExpensePage struct {
|
||||
Slug string
|
||||
Form *expenseForm
|
||||
}
|
||||
|
||||
type expenseForm struct {
|
||||
locale *Locale
|
||||
company *Company
|
||||
Invoicer *SelectField
|
||||
InvoiceNumber *InputField
|
||||
InvoiceDate *InputField
|
||||
Tax *SelectField
|
||||
Amount *InputField
|
||||
Tags *TagsField
|
||||
}
|
||||
|
||||
func newExpenseForm(ctx context.Context, conn *Conn, locale *Locale, company *Company) *expenseForm {
|
||||
return &expenseForm{
|
||||
locale: locale,
|
||||
company: company,
|
||||
Invoicer: &SelectField{
|
||||
Name: "invoicer",
|
||||
Label: pgettext("input", "Contact", locale),
|
||||
Required: true,
|
||||
Options: mustGetContactOptions(ctx, conn, company),
|
||||
},
|
||||
InvoiceNumber: &InputField{
|
||||
Name: "invoice_number",
|
||||
Label: pgettext("input", "Invoice number", locale),
|
||||
Type: "text",
|
||||
},
|
||||
InvoiceDate: &InputField{
|
||||
Name: "invoice_date",
|
||||
Label: pgettext("input", "Invoice Date", locale),
|
||||
Required: true,
|
||||
Type: "date",
|
||||
},
|
||||
Tax: &SelectField{
|
||||
Name: "tax",
|
||||
Label: pgettext("input", "Taxes", locale),
|
||||
Multiple: true,
|
||||
Options: mustGetTaxOptions(ctx, conn, company),
|
||||
},
|
||||
Amount: &InputField{
|
||||
Name: "amount",
|
||||
Label: pgettext("input", "Amount", locale),
|
||||
Type: "number",
|
||||
Required: true,
|
||||
Attributes: []template.HTMLAttr{
|
||||
`min="0"`,
|
||||
template.HTMLAttr(fmt.Sprintf(`step="%v"`, company.MinCents())),
|
||||
},
|
||||
},
|
||||
Tags: &TagsField{
|
||||
Name: "tags",
|
||||
Label: pgettext("input", "Tags", locale),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (form *expenseForm) Parse(r *http.Request) error {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
form.Invoicer.FillValue(r)
|
||||
form.InvoiceNumber.FillValue(r)
|
||||
form.InvoiceDate.FillValue(r)
|
||||
form.Tax.FillValue(r)
|
||||
form.Amount.FillValue(r)
|
||||
form.Tags.FillValue(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (form *expenseForm) Validate() bool {
|
||||
validator := newFormValidator()
|
||||
validator.CheckValidSelectOption(form.Invoicer, gettext("Selected contact is not valid.", form.locale))
|
||||
validator.CheckValidDate(form.InvoiceDate, gettext("Invoice date must be a valid date.", form.locale))
|
||||
validator.CheckValidSelectOption(form.Tax, gettext("Selected tax is not valid.", form.locale))
|
||||
validator.CheckAtMostOneOfEachGroup(form.Tax, gettext("You can only select a tax of each class.", form.locale))
|
||||
if validator.CheckRequiredInput(form.Amount, gettext("Amount can not be empty.", form.locale)) {
|
||||
validator.CheckValidDecimal(form.Amount, form.company.MinCents(), math.MaxFloat64, gettext("Amount must be a number greater than zero.", form.locale))
|
||||
}
|
||||
validator.CheckValidSelectOption(form.Tax, gettext("Selected tax is not valid.", form.locale))
|
||||
validator.CheckAtMostOneOfEachGroup(form.Tax, gettext("You can only select a tax of each class.", form.locale))
|
||||
return validator.AllOK()
|
||||
}
|
||||
|
||||
func (form *expenseForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug string) bool {
|
||||
return !notFoundErrorOrPanic(conn.QueryRow(ctx, `
|
||||
select contact_id
|
||||
, invoice_number
|
||||
, invoice_date
|
||||
, to_price(amount, decimal_digits)
|
||||
, array_agg(tax_id)
|
||||
, array_to_string(tags, ',')
|
||||
from expense
|
||||
left join expense_tax using (expense_id)
|
||||
join currency using (currency_code)
|
||||
where expense.slug = $1
|
||||
group by contact_id
|
||||
, invoice_number
|
||||
, invoice_date
|
||||
, amount
|
||||
, decimal_digits
|
||||
, tags
|
||||
`, slug).Scan(
|
||||
form.Invoicer,
|
||||
form.InvoiceNumber,
|
||||
form.InvoiceDate,
|
||||
form.Amount,
|
||||
form.Tax,
|
||||
form.Tags))
|
||||
}
|
||||
func HandleAddExpense(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
conn := getConn(r)
|
||||
locale := getLocale(r)
|
||||
company := mustGetCompany(r)
|
||||
form := newExpenseForm(r.Context(), conn, locale, company)
|
||||
if err := form.Parse(r); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := verifyCsrfTokenValid(r); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if !form.Validate() {
|
||||
if !IsHTMxRequest(r) {
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
}
|
||||
mustRenderNewExpenseForm(w, r, form)
|
||||
return
|
||||
}
|
||||
taxes := mustSliceAtoi(form.Tax.Selected)
|
||||
conn.MustExec(r.Context(), "select add_expense($1, $2, $3, $4, $5, $6, $7)", company.Id, form.InvoiceDate, form.Invoicer, form.InvoiceNumber, form.Amount, taxes, form.Tags)
|
||||
htmxRedirect(w, r, companyURI(company, "/expenses"))
|
||||
}
|
|
@ -34,6 +34,9 @@ func NewRouter(db *Db) http.Handler {
|
|||
companyRouter.PUT("/invoices/:slug/tags", HandleUpdateInvoiceTags)
|
||||
companyRouter.GET("/invoices/:slug/tags/edit", ServeEditInvoiceTags)
|
||||
companyRouter.GET("/search/products", HandleProductSearch)
|
||||
companyRouter.GET("/expenses", IndexExpenses)
|
||||
companyRouter.POST("/expenses", HandleAddExpense)
|
||||
companyRouter.GET("/expenses/:slug", ServeExpenseForm)
|
||||
companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
mustRenderMainTemplate(w, r, "dashboard.gohtml", nil)
|
||||
})
|
||||
|
|
158
po/ca.po
158
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-04-28 00:05+0200\n"
|
||||
"POT-Creation-Date: 2023-05-03 12:40+0200\n"
|
||||
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||
|
@ -27,9 +27,10 @@ msgstr "Afegeix productes a la factura"
|
|||
#: web/template/invoices/index.gohtml:9 web/template/invoices/view.gohtml:9
|
||||
#: web/template/invoices/edit.gohtml:9 web/template/contacts/new.gohtml:9
|
||||
#: web/template/contacts/index.gohtml:9 web/template/contacts/edit.gohtml:10
|
||||
#: web/template/profile.gohtml:9 web/template/tax-details.gohtml:9
|
||||
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:9
|
||||
#: web/template/products/edit.gohtml:10
|
||||
#: web/template/profile.gohtml:9 web/template/expenses/new.gohtml:10
|
||||
#: web/template/expenses/index.gohtml:10 web/template/expenses/edit.gohtml:10
|
||||
#: web/template/tax-details.gohtml:9 web/template/products/new.gohtml:9
|
||||
#: web/template/products/index.gohtml:9 web/template/products/edit.gohtml:10
|
||||
msgctxt "title"
|
||||
msgid "Home"
|
||||
msgstr "Inici"
|
||||
|
@ -48,7 +49,7 @@ msgid "New Invoice"
|
|||
msgstr "Nova factura"
|
||||
|
||||
#: web/template/invoices/products.gohtml:48
|
||||
#: web/template/products/index.gohtml:38
|
||||
#: web/template/expenses/index.gohtml:25 web/template/products/index.gohtml:38
|
||||
msgctxt "product"
|
||||
msgid "All"
|
||||
msgstr "Tots"
|
||||
|
@ -139,7 +140,7 @@ msgctxt "title"
|
|||
msgid "Tags"
|
||||
msgstr "Etiquetes"
|
||||
|
||||
#: web/template/invoices/index.gohtml:52
|
||||
#: web/template/invoices/index.gohtml:52 web/template/expenses/index.gohtml:29
|
||||
msgctxt "title"
|
||||
msgid "Amount"
|
||||
msgstr "Import"
|
||||
|
@ -245,10 +246,15 @@ msgstr "Factures"
|
|||
|
||||
#: web/template/app.gohtml:48
|
||||
msgctxt "nav"
|
||||
msgid "Expenses"
|
||||
msgstr "Despeses"
|
||||
|
||||
#: web/template/app.gohtml:49
|
||||
msgctxt "nav"
|
||||
msgid "Products"
|
||||
msgstr "Productes"
|
||||
|
||||
#: web/template/app.gohtml:49
|
||||
#: web/template/app.gohtml:50
|
||||
msgctxt "nav"
|
||||
msgid "Contacts"
|
||||
msgstr "Contactes"
|
||||
|
@ -335,6 +341,52 @@ msgctxt "action"
|
|||
msgid "Save changes"
|
||||
msgstr "Desa canvis"
|
||||
|
||||
#: web/template/expenses/new.gohtml:3 web/template/expenses/new.gohtml:12
|
||||
#: web/template/expenses/new.gohtml:20
|
||||
msgctxt "title"
|
||||
msgid "New Expense"
|
||||
msgstr "Nova despesa"
|
||||
|
||||
#: web/template/expenses/new.gohtml:11 web/template/expenses/index.gohtml:3
|
||||
#: web/template/expenses/index.gohtml:11 web/template/expenses/edit.gohtml:11
|
||||
msgctxt "title"
|
||||
msgid "Expenses"
|
||||
msgstr "Despeses"
|
||||
|
||||
#: web/template/expenses/new.gohtml:32 web/template/expenses/index.gohtml:15
|
||||
msgctxt "action"
|
||||
msgid "New expense"
|
||||
msgstr "Nova despesa"
|
||||
|
||||
#: web/template/expenses/index.gohtml:26
|
||||
msgctxt "title"
|
||||
msgid "Contact"
|
||||
msgstr "Contacte"
|
||||
|
||||
#: web/template/expenses/index.gohtml:27
|
||||
msgctxt "title"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Data de factura"
|
||||
|
||||
#: web/template/expenses/index.gohtml:28
|
||||
msgctxt "title"
|
||||
msgid "Invoice Number"
|
||||
msgstr "Número de factura"
|
||||
|
||||
#: web/template/expenses/index.gohtml:52
|
||||
msgid "No expenses added yet."
|
||||
msgstr "No hi ha cap despesa."
|
||||
|
||||
#: web/template/expenses/edit.gohtml:3 web/template/expenses/edit.gohtml:20
|
||||
msgctxt "title"
|
||||
msgid "Edit Expense “%s”"
|
||||
msgstr "Edició de la despesa «%s»"
|
||||
|
||||
#: web/template/expenses/edit.gohtml:35
|
||||
msgctxt "action"
|
||||
msgid "Update expense"
|
||||
msgstr "Actualitza despesa"
|
||||
|
||||
#: web/template/tax-details.gohtml:2 web/template/tax-details.gohtml:10
|
||||
#: web/template/tax-details.gohtml:18
|
||||
msgctxt "title"
|
||||
|
@ -460,13 +512,14 @@ msgstr "No podeu deixar la contrasenya en blanc."
|
|||
msgid "Invalid user or password."
|
||||
msgstr "Nom d’usuari o contrasenya incorrectes."
|
||||
|
||||
#: pkg/products.go:161 pkg/products.go:260 pkg/invoices.go:771
|
||||
#: pkg/products.go:161 pkg/products.go:260 pkg/invoices.go:776
|
||||
msgctxt "input"
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: pkg/products.go:166 pkg/products.go:287 pkg/invoices.go:187
|
||||
#: pkg/invoices.go:597 pkg/invoices.go:1031 pkg/contacts.go:256
|
||||
#: pkg/products.go:166 pkg/products.go:287 pkg/expenses.go:158
|
||||
#: pkg/invoices.go:187 pkg/invoices.go:597 pkg/invoices.go:1042
|
||||
#: pkg/contacts.go:256
|
||||
msgctxt "input"
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetes"
|
||||
|
@ -494,38 +547,40 @@ msgstr "Qualsevol"
|
|||
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."
|
||||
|
||||
#: pkg/products.go:266 pkg/invoices.go:785
|
||||
#: pkg/products.go:266 pkg/invoices.go:790
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
msgstr "Descripció"
|
||||
|
||||
#: pkg/products.go:271 pkg/invoices.go:789
|
||||
#: pkg/products.go:271 pkg/invoices.go:794
|
||||
msgctxt "input"
|
||||
msgid "Price"
|
||||
msgstr "Preu"
|
||||
|
||||
#: pkg/products.go:281 pkg/invoices.go:815
|
||||
#: pkg/products.go:281 pkg/expenses.go:142 pkg/invoices.go:823
|
||||
msgctxt "input"
|
||||
msgid "Taxes"
|
||||
msgstr "Imposts"
|
||||
|
||||
#: pkg/products.go:306 pkg/profile.go:92 pkg/invoices.go:856
|
||||
#: pkg/products.go:306 pkg/profile.go:92 pkg/invoices.go:867
|
||||
msgid "Name can not be empty."
|
||||
msgstr "No podeu deixar el nom en blanc."
|
||||
|
||||
#: pkg/products.go:307 pkg/invoices.go:857
|
||||
#: pkg/products.go:307 pkg/invoices.go:868
|
||||
msgid "Price can not be empty."
|
||||
msgstr "No podeu deixar el preu en blanc."
|
||||
|
||||
#: pkg/products.go:308 pkg/invoices.go:858
|
||||
#: pkg/products.go:308 pkg/invoices.go:869
|
||||
msgid "Price must be a number greater than zero."
|
||||
msgstr "El preu ha de ser un número major a zero."
|
||||
|
||||
#: pkg/products.go:310 pkg/invoices.go:866
|
||||
#: pkg/products.go:310 pkg/expenses.go:180 pkg/expenses.go:185
|
||||
#: pkg/invoices.go:877
|
||||
msgid "Selected tax is not valid."
|
||||
msgstr "Heu seleccionat un impost que no és vàlid."
|
||||
|
||||
#: pkg/products.go:311 pkg/invoices.go:867
|
||||
#: pkg/products.go:311 pkg/expenses.go:181 pkg/expenses.go:186
|
||||
#: pkg/invoices.go:878
|
||||
msgid "You can only select a tax of each class."
|
||||
msgstr "Només podeu seleccionar un impost de cada classe."
|
||||
|
||||
|
@ -633,6 +688,46 @@ 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/expenses.go:91
|
||||
msgid "Select a contact."
|
||||
msgstr "Escolliu un contacte."
|
||||
|
||||
#: pkg/expenses.go:125
|
||||
msgctxt "input"
|
||||
msgid "Contact"
|
||||
msgstr "Contacte"
|
||||
|
||||
#: pkg/expenses.go:131
|
||||
msgctxt "input"
|
||||
msgid "Invoice number"
|
||||
msgstr "Número de factura"
|
||||
|
||||
#: pkg/expenses.go:136 pkg/invoices.go:586
|
||||
msgctxt "input"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Data de factura"
|
||||
|
||||
#: pkg/expenses.go:148
|
||||
msgctxt "input"
|
||||
msgid "Amount"
|
||||
msgstr "Import"
|
||||
|
||||
#: pkg/expenses.go:178
|
||||
msgid "Selected contact is not valid."
|
||||
msgstr "Heu seleccionat un contacte que no és vàlid."
|
||||
|
||||
#: pkg/expenses.go:179 pkg/invoices.go:641
|
||||
msgid "Invoice date must be a valid date."
|
||||
msgstr "La data de facturació ha de ser vàlida."
|
||||
|
||||
#: pkg/expenses.go:182
|
||||
msgid "Amount can not be empty."
|
||||
msgstr "No podeu deixar l’import en blanc."
|
||||
|
||||
#: pkg/expenses.go:183
|
||||
msgid "Amount must be a number greater than zero."
|
||||
msgstr "L’import ha de ser un número major a zero."
|
||||
|
||||
#: pkg/invoices.go:160 pkg/invoices.go:580
|
||||
msgctxt "input"
|
||||
msgid "Customer"
|
||||
|
@ -674,15 +769,10 @@ msgstr "Escolliu un client a facturar."
|
|||
msgid "invoices.zip"
|
||||
msgstr "factures.zip"
|
||||
|
||||
#: pkg/invoices.go:530 pkg/invoices.go:1017
|
||||
#: pkg/invoices.go:530 pkg/invoices.go:1028
|
||||
msgid "Invalid action"
|
||||
msgstr "Acció invàlida."
|
||||
|
||||
#: pkg/invoices.go:586
|
||||
msgctxt "input"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Data de factura"
|
||||
|
||||
#: pkg/invoices.go:592
|
||||
msgctxt "input"
|
||||
msgid "Notes"
|
||||
|
@ -705,46 +795,42 @@ msgstr "Heu seleccionat un client que no és vàlid."
|
|||
msgid "Invoice date can not be empty."
|
||||
msgstr "No podeu deixar la data de la factura en blanc."
|
||||
|
||||
#: pkg/invoices.go:641
|
||||
msgid "Invoice date must be a valid date."
|
||||
msgstr "La data de facturació ha de ser vàlida."
|
||||
|
||||
#: pkg/invoices.go:643
|
||||
msgid "Selected payment method is not valid."
|
||||
msgstr "Heu seleccionat un mètode de pagament que no és vàlid."
|
||||
|
||||
#: pkg/invoices.go:761 pkg/invoices.go:766
|
||||
#: pkg/invoices.go:766 pkg/invoices.go:771
|
||||
msgctxt "input"
|
||||
msgid "Id"
|
||||
msgstr "Identificador"
|
||||
|
||||
#: pkg/invoices.go:798
|
||||
#: pkg/invoices.go:804
|
||||
msgctxt "input"
|
||||
msgid "Quantity"
|
||||
msgstr "Quantitat"
|
||||
|
||||
#: pkg/invoices.go:806
|
||||
#: pkg/invoices.go:813
|
||||
msgctxt "input"
|
||||
msgid "Discount (%)"
|
||||
msgstr "Descompte (%)"
|
||||
|
||||
#: pkg/invoices.go:854
|
||||
#: pkg/invoices.go:865
|
||||
msgid "Product ID must be a number greater than zero."
|
||||
msgstr "L’ID del producte ha de ser un número major a zero."
|
||||
|
||||
#: pkg/invoices.go:860
|
||||
#: pkg/invoices.go:871
|
||||
msgid "Quantity can not be empty."
|
||||
msgstr "No podeu deixar la quantitat en blanc."
|
||||
|
||||
#: pkg/invoices.go:861
|
||||
#: pkg/invoices.go:872
|
||||
msgid "Quantity must be a number greater than zero."
|
||||
msgstr "La quantitat ha de ser un número major a zero."
|
||||
|
||||
#: pkg/invoices.go:863
|
||||
#: pkg/invoices.go:874
|
||||
msgid "Discount can not be empty."
|
||||
msgstr "No podeu deixar el descompte en blanc."
|
||||
|
||||
#: pkg/invoices.go:864
|
||||
#: pkg/invoices.go:875
|
||||
msgid "Discount must be a percentage between 0 and 100."
|
||||
msgstr "El descompte ha de ser un percentatge entre 0 i 100."
|
||||
|
||||
|
|
160
po/es.po
160
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-04-28 00:05+0200\n"
|
||||
"POT-Creation-Date: 2023-05-03 12:40+0200\n"
|
||||
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||
|
@ -27,9 +27,10 @@ msgstr "Añadir productos a la factura"
|
|||
#: web/template/invoices/index.gohtml:9 web/template/invoices/view.gohtml:9
|
||||
#: web/template/invoices/edit.gohtml:9 web/template/contacts/new.gohtml:9
|
||||
#: web/template/contacts/index.gohtml:9 web/template/contacts/edit.gohtml:10
|
||||
#: web/template/profile.gohtml:9 web/template/tax-details.gohtml:9
|
||||
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:9
|
||||
#: web/template/products/edit.gohtml:10
|
||||
#: web/template/profile.gohtml:9 web/template/expenses/new.gohtml:10
|
||||
#: web/template/expenses/index.gohtml:10 web/template/expenses/edit.gohtml:10
|
||||
#: web/template/tax-details.gohtml:9 web/template/products/new.gohtml:9
|
||||
#: web/template/products/index.gohtml:9 web/template/products/edit.gohtml:10
|
||||
msgctxt "title"
|
||||
msgid "Home"
|
||||
msgstr "Inicio"
|
||||
|
@ -48,7 +49,7 @@ msgid "New Invoice"
|
|||
msgstr "Nueva factura"
|
||||
|
||||
#: web/template/invoices/products.gohtml:48
|
||||
#: web/template/products/index.gohtml:38
|
||||
#: web/template/expenses/index.gohtml:25 web/template/products/index.gohtml:38
|
||||
msgctxt "product"
|
||||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
@ -139,7 +140,7 @@ msgctxt "title"
|
|||
msgid "Tags"
|
||||
msgstr "Etiquetes"
|
||||
|
||||
#: web/template/invoices/index.gohtml:52
|
||||
#: web/template/invoices/index.gohtml:52 web/template/expenses/index.gohtml:29
|
||||
msgctxt "title"
|
||||
msgid "Amount"
|
||||
msgstr "Importe"
|
||||
|
@ -206,7 +207,7 @@ msgstr "Base imponible"
|
|||
#: web/template/invoices/edit.gohtml:2 web/template/invoices/edit.gohtml:19
|
||||
msgctxt "title"
|
||||
msgid "Edit Invoice “%s”"
|
||||
msgstr "Edición del la factura «%s»"
|
||||
msgstr "Edición de la factura «%s»"
|
||||
|
||||
#: web/template/invoices/edit.gohtml:68
|
||||
msgctxt "action"
|
||||
|
@ -245,10 +246,15 @@ msgstr "Facturas"
|
|||
|
||||
#: web/template/app.gohtml:48
|
||||
msgctxt "nav"
|
||||
msgid "Expenses"
|
||||
msgstr "Gastos"
|
||||
|
||||
#: web/template/app.gohtml:49
|
||||
msgctxt "nav"
|
||||
msgid "Products"
|
||||
msgstr "Productos"
|
||||
|
||||
#: web/template/app.gohtml:49
|
||||
#: web/template/app.gohtml:50
|
||||
msgctxt "nav"
|
||||
msgid "Contacts"
|
||||
msgstr "Contactos"
|
||||
|
@ -335,6 +341,52 @@ msgctxt "action"
|
|||
msgid "Save changes"
|
||||
msgstr "Guardar cambios"
|
||||
|
||||
#: web/template/expenses/new.gohtml:3 web/template/expenses/new.gohtml:12
|
||||
#: web/template/expenses/new.gohtml:20
|
||||
msgctxt "title"
|
||||
msgid "New Expense"
|
||||
msgstr "Nuevo gasto"
|
||||
|
||||
#: web/template/expenses/new.gohtml:11 web/template/expenses/index.gohtml:3
|
||||
#: web/template/expenses/index.gohtml:11 web/template/expenses/edit.gohtml:11
|
||||
msgctxt "title"
|
||||
msgid "Expenses"
|
||||
msgstr "Gastos"
|
||||
|
||||
#: web/template/expenses/new.gohtml:32 web/template/expenses/index.gohtml:15
|
||||
msgctxt "action"
|
||||
msgid "New expense"
|
||||
msgstr "Nuevo gasto"
|
||||
|
||||
#: web/template/expenses/index.gohtml:26
|
||||
msgctxt "title"
|
||||
msgid "Contact"
|
||||
msgstr "Contacto"
|
||||
|
||||
#: web/template/expenses/index.gohtml:27
|
||||
msgctxt "title"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Fecha de factura"
|
||||
|
||||
#: web/template/expenses/index.gohtml:28
|
||||
msgctxt "title"
|
||||
msgid "Invoice Number"
|
||||
msgstr "Número de factura"
|
||||
|
||||
#: web/template/expenses/index.gohtml:52
|
||||
msgid "No expenses added yet."
|
||||
msgstr "No hay gastos."
|
||||
|
||||
#: web/template/expenses/edit.gohtml:3 web/template/expenses/edit.gohtml:20
|
||||
msgctxt "title"
|
||||
msgid "Edit Expense “%s”"
|
||||
msgstr "Edición del gasto «%s»"
|
||||
|
||||
#: web/template/expenses/edit.gohtml:35
|
||||
msgctxt "action"
|
||||
msgid "Update expense"
|
||||
msgstr "Actualizar gasto"
|
||||
|
||||
#: web/template/tax-details.gohtml:2 web/template/tax-details.gohtml:10
|
||||
#: web/template/tax-details.gohtml:18
|
||||
msgctxt "title"
|
||||
|
@ -460,13 +512,14 @@ 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:161 pkg/products.go:260 pkg/invoices.go:771
|
||||
#: pkg/products.go:161 pkg/products.go:260 pkg/invoices.go:776
|
||||
msgctxt "input"
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
#: pkg/products.go:166 pkg/products.go:287 pkg/invoices.go:187
|
||||
#: pkg/invoices.go:597 pkg/invoices.go:1031 pkg/contacts.go:256
|
||||
#: pkg/products.go:166 pkg/products.go:287 pkg/expenses.go:158
|
||||
#: pkg/invoices.go:187 pkg/invoices.go:597 pkg/invoices.go:1042
|
||||
#: pkg/contacts.go:256
|
||||
msgctxt "input"
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetes"
|
||||
|
@ -494,38 +547,40 @@ msgstr "Cualquiera"
|
|||
msgid "Invoices must have at least one of the specified labels."
|
||||
msgstr "Las facturas debent tener como mínimo una de las etiquetas."
|
||||
|
||||
#: pkg/products.go:266 pkg/invoices.go:785
|
||||
#: pkg/products.go:266 pkg/invoices.go:790
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
msgstr "Descripción"
|
||||
|
||||
#: pkg/products.go:271 pkg/invoices.go:789
|
||||
#: pkg/products.go:271 pkg/invoices.go:794
|
||||
msgctxt "input"
|
||||
msgid "Price"
|
||||
msgstr "Precio"
|
||||
|
||||
#: pkg/products.go:281 pkg/invoices.go:815
|
||||
#: pkg/products.go:281 pkg/expenses.go:142 pkg/invoices.go:823
|
||||
msgctxt "input"
|
||||
msgid "Taxes"
|
||||
msgstr "Impuestos"
|
||||
|
||||
#: pkg/products.go:306 pkg/profile.go:92 pkg/invoices.go:856
|
||||
#: pkg/products.go:306 pkg/profile.go:92 pkg/invoices.go:867
|
||||
msgid "Name can not be empty."
|
||||
msgstr "No podéis dejar el nombre en blanco."
|
||||
|
||||
#: pkg/products.go:307 pkg/invoices.go:857
|
||||
#: pkg/products.go:307 pkg/invoices.go:868
|
||||
msgid "Price can not be empty."
|
||||
msgstr "No podéis dejar el precio en blanco."
|
||||
|
||||
#: pkg/products.go:308 pkg/invoices.go:858
|
||||
#: pkg/products.go:308 pkg/invoices.go:869
|
||||
msgid "Price must be a number greater than zero."
|
||||
msgstr "El precio tiene que ser un número mayor a cero."
|
||||
|
||||
#: pkg/products.go:310 pkg/invoices.go:866
|
||||
#: pkg/products.go:310 pkg/expenses.go:180 pkg/expenses.go:185
|
||||
#: pkg/invoices.go:877
|
||||
msgid "Selected tax is not valid."
|
||||
msgstr "Habéis escogido un impuesto que no es válido."
|
||||
|
||||
#: pkg/products.go:311 pkg/invoices.go:867
|
||||
#: pkg/products.go:311 pkg/expenses.go:181 pkg/expenses.go:186
|
||||
#: pkg/invoices.go:878
|
||||
msgid "You can only select a tax of each class."
|
||||
msgstr "Solo podéis escoger un impuesto de cada clase."
|
||||
|
||||
|
@ -633,6 +688,46 @@ 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/expenses.go:91
|
||||
msgid "Select a contact."
|
||||
msgstr "Escoged un contacto"
|
||||
|
||||
#: pkg/expenses.go:125
|
||||
msgctxt "input"
|
||||
msgid "Contact"
|
||||
msgstr "Contacto"
|
||||
|
||||
#: pkg/expenses.go:131
|
||||
msgctxt "input"
|
||||
msgid "Invoice number"
|
||||
msgstr "Número de factura"
|
||||
|
||||
#: pkg/expenses.go:136 pkg/invoices.go:586
|
||||
msgctxt "input"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Fecha de factura"
|
||||
|
||||
#: pkg/expenses.go:148
|
||||
msgctxt "input"
|
||||
msgid "Amount"
|
||||
msgstr "Importe"
|
||||
|
||||
#: pkg/expenses.go:178
|
||||
msgid "Selected contact is not valid."
|
||||
msgstr "Habéis escogido un contacto que no es válido."
|
||||
|
||||
#: pkg/expenses.go:179 pkg/invoices.go:641
|
||||
msgid "Invoice date must be a valid date."
|
||||
msgstr "La fecha de factura debe ser válida."
|
||||
|
||||
#: pkg/expenses.go:182
|
||||
msgid "Amount can not be empty."
|
||||
msgstr "No podéis dejar el importe en blanco."
|
||||
|
||||
#: pkg/expenses.go:183
|
||||
msgid "Amount must be a number greater than zero."
|
||||
msgstr "El importe tiene que ser un número mayor a cero."
|
||||
|
||||
#: pkg/invoices.go:160 pkg/invoices.go:580
|
||||
msgctxt "input"
|
||||
msgid "Customer"
|
||||
|
@ -674,15 +769,10 @@ msgstr "Escoged un cliente a facturar."
|
|||
msgid "invoices.zip"
|
||||
msgstr "facturas.zip"
|
||||
|
||||
#: pkg/invoices.go:530 pkg/invoices.go:1017
|
||||
#: pkg/invoices.go:530 pkg/invoices.go:1028
|
||||
msgid "Invalid action"
|
||||
msgstr "Acción inválida."
|
||||
|
||||
#: pkg/invoices.go:586
|
||||
msgctxt "input"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Fecha de factura"
|
||||
|
||||
#: pkg/invoices.go:592
|
||||
msgctxt "input"
|
||||
msgid "Notes"
|
||||
|
@ -705,46 +795,42 @@ msgstr "Habéis escogido un cliente que no es válido."
|
|||
msgid "Invoice date can not be empty."
|
||||
msgstr "No podéis dejar la fecha de la factura en blanco."
|
||||
|
||||
#: pkg/invoices.go:641
|
||||
msgid "Invoice date must be a valid date."
|
||||
msgstr "La fecha de factura debe ser válida."
|
||||
|
||||
#: pkg/invoices.go:643
|
||||
msgid "Selected payment method is not valid."
|
||||
msgstr "Habéis escogido un método de pago que no es válido."
|
||||
|
||||
#: pkg/invoices.go:761 pkg/invoices.go:766
|
||||
#: pkg/invoices.go:766 pkg/invoices.go:771
|
||||
msgctxt "input"
|
||||
msgid "Id"
|
||||
msgstr "Identificador"
|
||||
|
||||
#: pkg/invoices.go:798
|
||||
#: pkg/invoices.go:804
|
||||
msgctxt "input"
|
||||
msgid "Quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#: pkg/invoices.go:806
|
||||
#: pkg/invoices.go:813
|
||||
msgctxt "input"
|
||||
msgid "Discount (%)"
|
||||
msgstr "Descuento (%)"
|
||||
|
||||
#: pkg/invoices.go:854
|
||||
#: pkg/invoices.go:865
|
||||
msgid "Product ID must be a number greater than zero."
|
||||
msgstr "El ID de producto tiene que ser un número mayor a cero."
|
||||
|
||||
#: pkg/invoices.go:860
|
||||
#: pkg/invoices.go:871
|
||||
msgid "Quantity can not be empty."
|
||||
msgstr "No podéis dejar la cantidad en blanco."
|
||||
|
||||
#: pkg/invoices.go:861
|
||||
#: pkg/invoices.go:872
|
||||
msgid "Quantity must be a number greater than zero."
|
||||
msgstr "La cantidad tiene que ser un número mayor a cero."
|
||||
|
||||
#: pkg/invoices.go:863
|
||||
#: pkg/invoices.go:874
|
||||
msgid "Discount can not be empty."
|
||||
msgstr "No podéis dejar el descuento en blanco."
|
||||
|
||||
#: pkg/invoices.go:864
|
||||
#: pkg/invoices.go:875
|
||||
msgid "Discount must be a percentage between 0 and 100."
|
||||
msgstr "El descuento tiene que ser un porcentaje entre 0 y 100."
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
<ul>
|
||||
<li><a href="{{ companyURI "/" }}">{{( pgettext "Dashboard" "nav" )}}</a></li>
|
||||
<li><a href="{{ companyURI "/invoices" }}">{{( pgettext "Invoices" "nav" )}}</a></li>
|
||||
<li><a href="{{ companyURI "/expenses" }}">{{( pgettext "Expenses" "nav" )}}</a></li>
|
||||
<li><a href="{{ companyURI "/products" }}">{{( pgettext "Products" "nav" )}}</a></li>
|
||||
<li><a href="{{ companyURI "/contacts" }}">{{( pgettext "Contacts" "nav" )}}</a></li>
|
||||
</ul>
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
{{ define "title" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.editExpensePage*/ -}}
|
||||
{{ printf ( pgettext "Edit Expense “%s”" "title" ) .Slug }}
|
||||
{{- end }}
|
||||
|
||||
{{ define "breadcrumbs" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.editExpensePage*/ -}}
|
||||
<nav data-hx-target="main" data-hx-boost="true">
|
||||
<p>
|
||||
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||
<a href="{{ companyURI "/expenses"}}">{{( pgettext "Expenses" "title" )}}</a> /
|
||||
<a>{{ .Slug }}</a>
|
||||
</p>
|
||||
</nav>
|
||||
{{- end }}
|
||||
|
||||
{{ define "content" }}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.editExpensePage*/ -}}
|
||||
<section class="dialog-content" id="new-expense-dialog-content" data-hx-target="main">
|
||||
<h2>{{ printf (pgettext "Edit Expense “%s”" "title") .Slug }}</h2>
|
||||
<form method="POST" action="{{ companyURI "/expenses/" }}{{ .Slug }}" data-hx-boost="true">
|
||||
{{ csrfToken }}
|
||||
{{ putMethod }}
|
||||
|
||||
{{ with .Form -}}
|
||||
{{ template "select-field" .Invoicer }}
|
||||
{{ template "input-field" .InvoiceNumber }}
|
||||
{{ template "input-field" .InvoiceDate }}
|
||||
{{ template "input-field" .Amount }}
|
||||
{{ template "select-field" .Tax }}
|
||||
{{ template "tags-field" .Tags }}
|
||||
{{- end }}
|
||||
|
||||
<fieldset>
|
||||
<button class="primary" type="submit">{{( pgettext "Update expense" "action" )}}</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</section>
|
||||
{{- end }}
|
|
@ -0,0 +1,57 @@
|
|||
{{ define "title" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.expensesIndexPage*/ -}}
|
||||
{{( pgettext "Expenses" "title" )}}
|
||||
{{- end }}
|
||||
|
||||
{{ define "breadcrumbs" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.expensesIndexPage*/ -}}
|
||||
<nav data-hx-boost="true" data-hx-target="main">
|
||||
<p>
|
||||
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||
<a>{{( pgettext "Expenses" "title" )}}</a>
|
||||
</p>
|
||||
<p>
|
||||
<a class="primary button"
|
||||
href="{{ companyURI "/expenses/new" }}">{{( pgettext "New expense" "action" )}}</a>
|
||||
</p>
|
||||
</nav>
|
||||
{{- end }}
|
||||
|
||||
{{ define "content" }}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.expensesIndexPage*/ -}}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{( pgettext "All" "product" )}}</th>
|
||||
<th>{{( pgettext "Contact" "title" )}}</th>
|
||||
<th>{{( pgettext "Invoice Date" "title" )}}</th>
|
||||
<th>{{( pgettext "Invoice Number" "title" )}}</th>
|
||||
<th>{{( pgettext "Amount" "title" )}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ with .Expenses }}
|
||||
{{- range . }}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><a href="{{ companyURI "/expenses/"}}{{ .Slug }}"
|
||||
data-hx-target="main" data-hx-boost="true">{{ .InvoicerName }}</a></td>
|
||||
<td>{{ .InvoiceDate|formatDate }}</td>
|
||||
<td>{{ .InvoiceNumber }}</td>
|
||||
<td>
|
||||
{{- range $index, $tag := .Tags }}
|
||||
{{- if gt $index 0 }}, {{ end -}}
|
||||
{{ . }}
|
||||
{{- end }}
|
||||
</td>
|
||||
<td class="numeric">{{ .Amount | formatPrice }}</td>
|
||||
</tr>
|
||||
{{- end }}
|
||||
{{ else }}
|
||||
<tr>
|
||||
<td colspan="5">{{( gettext "No expenses added yet." )}}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{- end }}
|
|
@ -0,0 +1,36 @@
|
|||
{{ define "title" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.expenseForm*/ -}}
|
||||
{{( pgettext "New Expense" "title" )}}
|
||||
{{- end }}
|
||||
|
||||
{{ define "breadcrumbs" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.expenseForm*/ -}}
|
||||
<nav data-hx-target="main" data-hx-boost="true">
|
||||
<p>
|
||||
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||
<a href="{{ companyURI "/expenses"}}">{{( pgettext "Expenses" "title" )}}</a> /
|
||||
<a>{{( pgettext "New Expense" "title" )}}</a>
|
||||
</p>
|
||||
</nav>
|
||||
{{- end }}
|
||||
|
||||
{{ define "content" }}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.expenseForm*/ -}}
|
||||
<section class="dialog-content" id="new-expense-dialog-content" data-hx-target="main">
|
||||
<h2>{{(pgettext "New Expense" "title")}}</h2>
|
||||
<form method="POST" action="{{ companyURI "/expenses" }}" data-hx-boost="true">
|
||||
{{ csrfToken }}
|
||||
|
||||
{{ template "select-field" .Invoicer }}
|
||||
{{ template "input-field" .InvoiceNumber }}
|
||||
{{ template "input-field" .InvoiceDate }}
|
||||
{{ template "input-field" .Amount }}
|
||||
{{ template "select-field" .Tax }}
|
||||
{{ template "tags-field" .Tags }}
|
||||
|
||||
<fieldset>
|
||||
<button class="primary" type="submit">{{( pgettext "New expense" "action" )}}</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</section>
|
||||
{{- end }}
|
Loading…
Reference in New Issue