Compare commits

...

2 Commits

9 changed files with 626 additions and 75 deletions

239
pkg/expenses.go Normal file
View File

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

View File

@ -159,7 +159,7 @@ func newInvoiceFilterForm(ctx context.Context, conn *Conn, locale *Locale, compa
Name: "customer",
Label: pgettext("input", "Customer", locale),
EmptyLabel: gettext("All customers", locale),
Options: MustGetOptions(ctx, conn, "select contact_id::text, business_name from contact where company_id = $1 order by business_name", company.Id),
Options: mustGetContactOptions(ctx, conn, company),
},
InvoiceStatus: &SelectField{
Name: "invoice_status",
@ -579,7 +579,7 @@ func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
Name: "customer",
Label: pgettext("input", "Customer", locale),
Required: true,
Options: MustGetOptions(ctx, conn, "select contact_id::text, business_name from contact where company_id = $1 order by business_name", company.Id),
Options: mustGetContactOptions(ctx, conn, company),
},
Date: &InputField{
Name: "date",
@ -738,6 +738,10 @@ func mustGetTaxOptions(ctx context.Context, conn *Conn, company *Company) []*Sel
return MustGetGroupedOptions(ctx, conn, "select tax_id::text, tax.name, tax_class.name from tax join tax_class using (tax_class_id) where tax.company_id = $1 order by tax_class.name, tax.name", company.Id)
}
func mustGetContactOptions(ctx context.Context, conn *Conn, company *Company) []*SelectOption {
return MustGetOptions(ctx, conn, "select contact_id::text, business_name from contact where company_id = $1 order by business_name", company.Id)
}
type invoiceProductForm struct {
locale *Locale
company *Company

View File

@ -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
View File

@ -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 dusuari 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 limport en blanc."
#: pkg/expenses.go:183
msgid "Amount must be a number greater than zero."
msgstr "Limport 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 "LID 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
View File

@ -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."

View File

@ -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>

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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 }}