Compare commits
3 Commits
5c15b9de20
...
4903c8a3b9
Author | SHA1 | Date |
---|---|---|
jordi fita mas | 4903c8a3b9 | |
jordi fita mas | 72fbed68ac | |
jordi fita mas | c2d8006748 |
|
@ -270,7 +270,7 @@ func (form *contactForm) Validate(ctx context.Context, conn *Conn) bool {
|
|||
validator.CheckValidEmailInput(form.Email, gettext("This value is not a valid email. It should be like name@domain.com.", form.locale))
|
||||
}
|
||||
if form.Web.Val != "" {
|
||||
validator.checkValidURL(form.Web, gettext("This value is not a valid web address. It should be like https://domain.com/.", form.locale))
|
||||
validator.CheckValidURL(form.Web, gettext("This value is not a valid web address. It should be like https://domain.com/.", form.locale))
|
||||
}
|
||||
validator.CheckRequiredInput(form.Address, gettext("Address can not be empty.", form.locale))
|
||||
validator.CheckRequiredInput(form.City, gettext("City can not be empty.", form.locale))
|
||||
|
|
|
@ -96,6 +96,14 @@ func (c *Conn) MustExec(ctx context.Context, sql string, args ...interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Conn) MustQuery(ctx context.Context, sql string, args ...interface{}) pgx.Rows {
|
||||
rows, err := c.Conn.Query(ctx, sql, args...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
type Tx struct {
|
||||
pgx.Tx
|
||||
}
|
||||
|
|
17
pkg/form.go
17
pkg/form.go
|
@ -5,6 +5,7 @@ import (
|
|||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/jackc/pgtype"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Attribute struct {
|
||||
|
@ -72,6 +74,14 @@ func (field *SelectField) Scan(value interface{}) error {
|
|||
field.Selected = append(field.Selected, "")
|
||||
return nil
|
||||
}
|
||||
if str, ok := value.(string); ok {
|
||||
if array, err := pgtype.ParseUntypedTextArray(str); err == nil {
|
||||
for _, element := range array.Elements {
|
||||
field.Selected = append(field.Selected, element)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
field.Selected = append(field.Selected, fmt.Sprintf("%v", value))
|
||||
return nil
|
||||
}
|
||||
|
@ -180,11 +190,16 @@ func (v *FormValidator) CheckValidSelectOption(field *SelectField, message strin
|
|||
return v.checkSelect(field, field.HasValidOptions(), message)
|
||||
}
|
||||
|
||||
func (v *FormValidator) checkValidURL(field *InputField, message string) bool {
|
||||
func (v *FormValidator) CheckValidURL(field *InputField, message string) bool {
|
||||
_, err := url.ParseRequestURI(field.Val)
|
||||
return v.checkInput(field, err == nil, message)
|
||||
}
|
||||
|
||||
func (v *FormValidator) CheckValidDate(field *InputField, message string) bool {
|
||||
_, err := time.Parse("2006-02-01", field.Val)
|
||||
return v.checkInput(field, err == nil, message)
|
||||
}
|
||||
|
||||
func (v *FormValidator) CheckValidPostalCode(ctx context.Context, conn *Conn, field *InputField, country string, message string) bool {
|
||||
pattern := "^" + conn.MustGetText(ctx, ".{1,255}", "select postal_code_regex from country where country_code = $1", country) + "$"
|
||||
match, err := regexp.MatchString(pattern, field.Val)
|
||||
|
|
290
pkg/invoices.go
290
pkg/invoices.go
|
@ -3,20 +3,59 @@ package pkg
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"html/template"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type InvoiceEntry struct {
|
||||
Slug string
|
||||
Date time.Time
|
||||
Number string
|
||||
CustomerName string
|
||||
CustomerSlug string
|
||||
Status string
|
||||
StatusLabel string
|
||||
}
|
||||
|
||||
type InvoicesIndexPage struct {
|
||||
Invoices []*InvoiceEntry
|
||||
}
|
||||
|
||||
func IndexInvoices(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
page := &InvoicesIndexPage{}
|
||||
page := &InvoicesIndexPage{
|
||||
Invoices: mustGetInvoiceEntries(r.Context(), getConn(r), mustGetCompany(r), getLocale(r)),
|
||||
}
|
||||
mustRenderAppTemplate(w, r, "invoices/index.gohtml", page)
|
||||
}
|
||||
|
||||
func mustGetInvoiceEntries(ctx context.Context, conn *Conn, company *Company, locale *Locale) []*InvoiceEntry {
|
||||
rows, err := conn.Query(ctx, "select invoice.slug, invoice_date, invoice_number, contact.business_name, contact.slug, invoice.invoice_status, isi18n.name from invoice join contact using (contact_id) join invoice_status_i18n isi18n on invoice.invoice_status = isi18n.invoice_status and isi18n.lang_tag = $2 where invoice.company_id = $1 order by invoice_date, invoice_number", company.Id, locale.Language.String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var entries []*InvoiceEntry
|
||||
for rows.Next() {
|
||||
entry := &InvoiceEntry{}
|
||||
err = rows.Scan(&entry.Slug, &entry.Date, &entry.Number, &entry.CustomerName, &entry.CustomerSlug, &entry.Status, &entry.StatusLabel)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
panic(rows.Err())
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
func GetInvoiceForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
locale := getLocale(r)
|
||||
conn := getConn(r)
|
||||
|
@ -36,19 +75,123 @@ func mustRenderNewInvoiceForm(w http.ResponseWriter, r *http.Request, form *invo
|
|||
mustRenderAppTemplate(w, r, "invoices/new.gohtml", form)
|
||||
}
|
||||
|
||||
func HandleAddInvoice(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
r.ParseForm()
|
||||
w.Write([]byte("OK"))
|
||||
func mustRenderNewInvoiceProductsForm(w http.ResponseWriter, r *http.Request, form *invoiceForm) {
|
||||
conn := getConn(r)
|
||||
company := mustGetCompany(r)
|
||||
page := newInvoiceProductsPage{
|
||||
Form: form,
|
||||
Products: mustGetProductChoices(r.Context(), conn, company),
|
||||
}
|
||||
mustRenderAppTemplate(w, r, "invoices/products.gohtml", page)
|
||||
}
|
||||
|
||||
type invoiceProductForm struct {
|
||||
ProductId *InputField
|
||||
Name *InputField
|
||||
Description *InputField
|
||||
Price *InputField
|
||||
Quantity *InputField
|
||||
Discount *InputField
|
||||
Tax *SelectField
|
||||
func mustGetProductChoices(ctx context.Context, conn *Conn, company *Company) []*productChoice {
|
||||
rows := conn.MustQuery(ctx, "select product.product_id, product.name, to_price(price, decimal_digits) from product join company using (company_id) join currency using (currency_code) where company_id = $1 order by name", company.Id)
|
||||
defer rows.Close()
|
||||
|
||||
var choices []*productChoice
|
||||
for rows.Next() {
|
||||
entry := &productChoice{}
|
||||
if err := rows.Scan(&entry.Id, &entry.Name, &entry.Price); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
choices = append(choices, entry)
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
panic(rows.Err())
|
||||
}
|
||||
|
||||
return choices
|
||||
}
|
||||
|
||||
type newInvoiceProductsPage struct {
|
||||
Form *invoiceForm
|
||||
Products []*productChoice
|
||||
}
|
||||
|
||||
type productChoice struct {
|
||||
Id int
|
||||
Name string
|
||||
Price string
|
||||
}
|
||||
|
||||
func HandleAddInvoice(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
locale := getLocale(r)
|
||||
conn := getConn(r)
|
||||
company := mustGetCompany(r)
|
||||
form := newInvoiceForm(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
|
||||
}
|
||||
switch r.Form.Get("action") {
|
||||
case "update":
|
||||
form.Update()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
mustRenderNewInvoiceForm(w, r, form)
|
||||
case "products":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
mustRenderNewInvoiceProductsForm(w, r, form)
|
||||
case "add":
|
||||
if !form.Validate() {
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
mustRenderNewInvoiceForm(w, r, form)
|
||||
return
|
||||
}
|
||||
tx := conn.MustBegin(r.Context())
|
||||
invoiceId := tx.MustGetInteger(r.Context(), "insert into invoice (company_id, invoice_number, invoice_date, contact_id, notes, currency_code) select company_id, $2, $3, $4, $5, currency_code from company join currency using (currency_code) where company_id = $1 returning invoice_id", company.Id, form.Number, form.Date, form.Customer, form.Notes)
|
||||
batch := &pgx.Batch{}
|
||||
for _, product := range form.Products {
|
||||
batch.Queue("insert into invoice_product(invoice_id, product_id, name, description, price, quantity, discount_rate) select $2, $3, $4, $5, parse_price($6, decimal_digits), $7, $8 / 100::decimal from company join currency using (currency_code) where company_id = $1", company.Id, invoiceId, product.ProductId, product.Name, product.Description, product.Price, product.Quantity.Integer(), product.Discount.Integer())
|
||||
}
|
||||
br := tx.SendBatch(r.Context(), batch)
|
||||
for range form.Products {
|
||||
if _, err := br.Exec(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
if err := br.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tx.MustCommit(r.Context())
|
||||
http.Redirect(w, r, companyURI(company, "/invoices"), http.StatusSeeOther)
|
||||
default:
|
||||
http.Error(w, gettext("Invalid action", locale), http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleAddProductsToInvoice(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
locale := getLocale(r)
|
||||
conn := getConn(r)
|
||||
company := mustGetCompany(r)
|
||||
form := newInvoiceForm(r.Context(), conn, locale, company)
|
||||
if err := form.Parse(r); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
index := len(form.Products)
|
||||
productsId := r.Form["id"]
|
||||
rows := conn.MustQuery(r.Context(), "select product_id, name, description, to_price(price, decimal_digits), 1 as quantity, 0 as discount, array_agg(tax_id) from product join company using (company_id) join currency using (currency_code) left join product_tax using (product_id) where product_id = any ($1) group by product_id, name, description, price, decimal_digits", productsId)
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
product := newInvoiceProductForm(index, company, locale, form.Tax.Options)
|
||||
if err := rows.Scan(product.ProductId, product.Name, product.Description, product.Price, product.Quantity, product.Discount, product.Tax); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
form.Products = append(form.Products, product)
|
||||
index++
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
panic(rows.Err())
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
mustRenderNewInvoiceForm(w, r, form)
|
||||
}
|
||||
|
||||
type invoiceForm struct {
|
||||
|
@ -58,6 +201,7 @@ type invoiceForm struct {
|
|||
Number *InputField
|
||||
Date *InputField
|
||||
Notes *InputField
|
||||
Tax *SelectField
|
||||
Products []*invoiceProductForm
|
||||
}
|
||||
|
||||
|
@ -88,33 +232,103 @@ func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
|
|||
Label: pgettext("input", "Notes", locale),
|
||||
Type: "textarea",
|
||||
},
|
||||
Products: []*invoiceProductForm{
|
||||
newInvoiceProductForm(ctx, conn, company, locale),
|
||||
Tax: &SelectField{
|
||||
Name: "text",
|
||||
Label: pgettext("input", "Taxes", locale),
|
||||
Multiple: true,
|
||||
Options: mustGetTaxOptions(ctx, conn, company),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newInvoiceProductForm(ctx context.Context, conn *Conn, company *Company, locale *Locale) *invoiceProductForm {
|
||||
func (form *invoiceForm) Parse(r *http.Request) error {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
form.Customer.FillValue(r)
|
||||
form.Number.FillValue(r)
|
||||
form.Date.FillValue(r)
|
||||
form.Notes.FillValue(r)
|
||||
if r.Form.Has("product.id.0") {
|
||||
for index := 0; r.Form.Has("product.id." + strconv.Itoa(index)); index++ {
|
||||
productForm := newInvoiceProductForm(index, form.company, form.locale, form.Tax.Options)
|
||||
if err := productForm.Parse(r); err != nil {
|
||||
return err
|
||||
}
|
||||
form.Products = append(form.Products, productForm)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (form *invoiceForm) Validate() bool {
|
||||
validator := newFormValidator()
|
||||
|
||||
validator.CheckValidSelectOption(form.Customer, gettext("Name can not be empty.", form.locale))
|
||||
if validator.CheckRequiredInput(form.Date, gettext("Invoice date can not be empty.", form.locale)) {
|
||||
validator.CheckValidDate(form.Date, gettext("Invoice date must be a valid date.", form.locale))
|
||||
}
|
||||
validator.CheckValidSelectOption(form.Tax, gettext("Selected tax is not valid.", form.locale))
|
||||
|
||||
allOK := validator.AllOK()
|
||||
for _, product := range form.Products {
|
||||
allOK = product.Validate() && allOK
|
||||
}
|
||||
return allOK
|
||||
}
|
||||
|
||||
func (form *invoiceForm) Update() {
|
||||
products := form.Products
|
||||
form.Products = nil
|
||||
index := 0
|
||||
for _, product := range products {
|
||||
if product.Quantity.Val != "0" {
|
||||
form.Products = append(form.Products, product)
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustGetTaxOptions(ctx context.Context, conn *Conn, company *Company) []*SelectOption {
|
||||
return MustGetOptions(ctx, conn, "select tax_id::text, name from tax where company_id = $1 order by name", company.Id)
|
||||
}
|
||||
|
||||
type invoiceProductForm struct {
|
||||
locale *Locale
|
||||
company *Company
|
||||
ProductId *InputField
|
||||
Name *InputField
|
||||
Description *InputField
|
||||
Price *InputField
|
||||
Quantity *InputField
|
||||
Discount *InputField
|
||||
Tax *SelectField
|
||||
}
|
||||
|
||||
func newInvoiceProductForm(index int, company *Company, locale *Locale, taxOptions []*SelectOption) *invoiceProductForm {
|
||||
suffix := "." + strconv.Itoa(index)
|
||||
return &invoiceProductForm{
|
||||
locale: locale,
|
||||
company: company,
|
||||
ProductId: &InputField{
|
||||
Name: "product[][id]",
|
||||
Name: "product.id" + suffix,
|
||||
Label: pgettext("input", "Id", locale),
|
||||
Type: "hidden",
|
||||
Required: true,
|
||||
},
|
||||
Name: &InputField{
|
||||
Name: "product[][name]",
|
||||
Name: "product.name" + suffix,
|
||||
Label: pgettext("input", "Name", locale),
|
||||
Type: "text",
|
||||
Required: true,
|
||||
},
|
||||
Description: &InputField{
|
||||
Name: "product[][description]",
|
||||
Name: "product.description" + suffix,
|
||||
Label: pgettext("input", "Description", locale),
|
||||
Type: "textarea",
|
||||
},
|
||||
Price: &InputField{
|
||||
Name: "product[][price]",
|
||||
Name: "product.price" + suffix,
|
||||
Label: pgettext("input", "Price", locale),
|
||||
Type: "number",
|
||||
Required: true,
|
||||
|
@ -124,7 +338,7 @@ func newInvoiceProductForm(ctx context.Context, conn *Conn, company *Company, lo
|
|||
},
|
||||
},
|
||||
Quantity: &InputField{
|
||||
Name: "product[][quantity]",
|
||||
Name: "product.quantity" + suffix,
|
||||
Label: pgettext("input", "Quantity", locale),
|
||||
Type: "number",
|
||||
Required: true,
|
||||
|
@ -133,7 +347,7 @@ func newInvoiceProductForm(ctx context.Context, conn *Conn, company *Company, lo
|
|||
},
|
||||
},
|
||||
Discount: &InputField{
|
||||
Name: "product[][discount]",
|
||||
Name: "product.discount" + suffix,
|
||||
Label: pgettext("input", "Discount (%)", locale),
|
||||
Type: "number",
|
||||
Required: true,
|
||||
|
@ -143,10 +357,40 @@ func newInvoiceProductForm(ctx context.Context, conn *Conn, company *Company, lo
|
|||
},
|
||||
},
|
||||
Tax: &SelectField{
|
||||
Name: "product[][tax]",
|
||||
Name: "product.tax" + suffix,
|
||||
Label: pgettext("input", "Taxes", locale),
|
||||
Multiple: true,
|
||||
Options: MustGetOptions(ctx, conn, "select tax_id::text, name from tax where company_id = $1 order by name", company.Id),
|
||||
Options: taxOptions,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (form *invoiceProductForm) Parse(r *http.Request) error {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
form.ProductId.FillValue(r)
|
||||
form.Name.FillValue(r)
|
||||
form.Description.FillValue(r)
|
||||
form.Price.FillValue(r)
|
||||
form.Quantity.FillValue(r)
|
||||
form.Discount.FillValue(r)
|
||||
form.Tax.FillValue(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (form *invoiceProductForm) Validate() bool {
|
||||
validator := newFormValidator()
|
||||
validator.CheckRequiredInput(form.Name, gettext("Name can not be empty.", form.locale))
|
||||
if validator.CheckRequiredInput(form.Price, gettext("Price can not be empty.", form.locale)) {
|
||||
validator.CheckValidDecimal(form.Price, form.company.MinCents(), math.MaxFloat64, gettext("Price must be a number greater than zero.", form.locale))
|
||||
}
|
||||
if validator.CheckRequiredInput(form.Quantity, gettext("Quantity can not be empty.", form.locale)) {
|
||||
validator.CheckValidInteger(form.Quantity, 1, math.MaxInt, gettext("Quantity must be a number greater than zero.", form.locale))
|
||||
}
|
||||
if validator.CheckRequiredInput(form.Discount, gettext("Discount can not be empty.", form.locale)) {
|
||||
validator.CheckValidInteger(form.Discount, 0, 100, gettext("Discount must be a percentage between 0 and 100.", form.locale))
|
||||
}
|
||||
validator.CheckValidSelectOption(form.Tax, gettext("Selected tax is not valid.", form.locale))
|
||||
return validator.AllOK()
|
||||
}
|
||||
|
|
|
@ -124,6 +124,7 @@ func HandleUpdateProduct(w http.ResponseWriter, r *http.Request, params httprout
|
|||
return
|
||||
}
|
||||
if !form.Validate() {
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
mustRenderEditProductForm(w, r, form)
|
||||
return
|
||||
}
|
||||
|
@ -213,7 +214,7 @@ func newProductForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
|
|||
Name: "tax",
|
||||
Label: pgettext("input", "Taxes", locale),
|
||||
Multiple: true,
|
||||
Options: MustGetOptions(ctx, conn, "select tax_id::text, name from tax where company_id = $1 order by name", company.Id),
|
||||
Options: mustGetTaxOptions(ctx, conn, company),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ func NewRouter(db *Db) http.Handler {
|
|||
companyRouter.GET("/invoices", IndexInvoices)
|
||||
companyRouter.POST("/invoices", HandleAddInvoice)
|
||||
companyRouter.GET("/invoices/:slug", GetInvoiceForm)
|
||||
companyRouter.POST("/invoices/new/products", HandleAddProductsToInvoice)
|
||||
companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
mustRenderAppTemplate(w, r, "dashboard.gohtml", nil)
|
||||
})
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const overrideMethodName = "_method"
|
||||
|
@ -39,6 +40,9 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s
|
|||
}
|
||||
return p.Sprintf("%.*f", company.DecimalDigits, number.Decimal(f))
|
||||
},
|
||||
"formatDate": func(time time.Time) string {
|
||||
return time.Format("02/01/2006")
|
||||
},
|
||||
"csrfToken": func() template.HTML {
|
||||
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, csrfTokenField, user.CsrfToken))
|
||||
},
|
||||
|
|
230
po/ca.po
230
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-02-08 13:43+0100\n"
|
||||
"POT-Creation-Date: 2023-02-12 20:51+0100\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"
|
||||
|
@ -17,6 +17,111 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: web/template/invoices/products.gohtml:2
|
||||
#: web/template/invoices/products.gohtml:15
|
||||
msgctxt "title"
|
||||
msgid "Add Products to Invoice"
|
||||
msgstr "Afegeix productes a la factura"
|
||||
|
||||
#: web/template/invoices/products.gohtml:9 web/template/invoices/new.gohtml:9
|
||||
#: web/template/invoices/index.gohtml:8 web/template/contacts/new.gohtml:9
|
||||
#: web/template/contacts/index.gohtml:8 web/template/contacts/edit.gohtml:9
|
||||
#: web/template/profile.gohtml:9 web/template/tax-details.gohtml:8
|
||||
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:8
|
||||
#: web/template/products/edit.gohtml:9
|
||||
msgctxt "title"
|
||||
msgid "Home"
|
||||
msgstr "Inici"
|
||||
|
||||
#: web/template/invoices/products.gohtml:10 web/template/invoices/new.gohtml:10
|
||||
#: web/template/invoices/index.gohtml:2 web/template/invoices/index.gohtml:9
|
||||
msgctxt "title"
|
||||
msgid "Invoices"
|
||||
msgstr "Factures"
|
||||
|
||||
#: web/template/invoices/products.gohtml:11 web/template/invoices/new.gohtml:2
|
||||
#: web/template/invoices/new.gohtml:11 web/template/invoices/new.gohtml:15
|
||||
msgctxt "title"
|
||||
msgid "New Invoice"
|
||||
msgstr "Nova factura"
|
||||
|
||||
#: web/template/invoices/products.gohtml:41
|
||||
#: web/template/products/index.gohtml:21
|
||||
msgctxt "product"
|
||||
msgid "All"
|
||||
msgstr "Tots"
|
||||
|
||||
#: web/template/invoices/products.gohtml:42
|
||||
#: web/template/products/index.gohtml:22
|
||||
msgctxt "title"
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: web/template/invoices/products.gohtml:43
|
||||
#: web/template/products/index.gohtml:23
|
||||
msgctxt "title"
|
||||
msgid "Price"
|
||||
msgstr "Preu"
|
||||
|
||||
#: web/template/invoices/products.gohtml:57
|
||||
#: web/template/products/index.gohtml:37
|
||||
msgid "No products added yet."
|
||||
msgstr "No hi ha cap producte."
|
||||
|
||||
#: web/template/invoices/products.gohtml:64 web/template/invoices/new.gohtml:37
|
||||
msgctxt "action"
|
||||
msgid "Add products"
|
||||
msgstr "Afegeix productes"
|
||||
|
||||
#: web/template/invoices/new.gohtml:38
|
||||
msgctxt "action"
|
||||
msgid "Update"
|
||||
msgstr "Actualitza"
|
||||
|
||||
#: web/template/invoices/new.gohtml:40 web/template/invoices/index.gohtml:13
|
||||
msgctxt "action"
|
||||
msgid "New invoice"
|
||||
msgstr "Nova factura"
|
||||
|
||||
#: web/template/invoices/index.gohtml:21
|
||||
msgctxt "invoice"
|
||||
msgid "All"
|
||||
msgstr "Totes"
|
||||
|
||||
#: web/template/invoices/index.gohtml:22
|
||||
msgctxt "title"
|
||||
msgid "Date"
|
||||
msgstr "Data"
|
||||
|
||||
#: web/template/invoices/index.gohtml:23
|
||||
msgctxt "title"
|
||||
msgid "Invoice Num."
|
||||
msgstr "Núm. factura"
|
||||
|
||||
#: web/template/invoices/index.gohtml:24 web/template/contacts/index.gohtml:22
|
||||
msgctxt "title"
|
||||
msgid "Customer"
|
||||
msgstr "Client"
|
||||
|
||||
#: web/template/invoices/index.gohtml:25
|
||||
msgctxt "title"
|
||||
msgid "Status"
|
||||
msgstr "Estat"
|
||||
|
||||
#: web/template/invoices/index.gohtml:26
|
||||
msgctxt "title"
|
||||
msgid "Label"
|
||||
msgstr "Etiqueta"
|
||||
|
||||
#: web/template/invoices/index.gohtml:27
|
||||
msgctxt "title"
|
||||
msgid "Download"
|
||||
msgstr "Descàrrega"
|
||||
|
||||
#: web/template/invoices/index.gohtml:45
|
||||
msgid "No invoices added yet."
|
||||
msgstr "No hi ha cap factura."
|
||||
|
||||
#: web/template/dashboard.gohtml:2
|
||||
msgctxt "title"
|
||||
msgid "Dashboard"
|
||||
|
@ -44,10 +149,15 @@ msgstr "Tauler"
|
|||
|
||||
#: web/template/app.gohtml:44
|
||||
msgctxt "nav"
|
||||
msgid "Invoices"
|
||||
msgstr "Factures"
|
||||
|
||||
#: web/template/app.gohtml:45
|
||||
msgctxt "nav"
|
||||
msgid "Products"
|
||||
msgstr "Productes"
|
||||
|
||||
#: web/template/app.gohtml:45
|
||||
#: web/template/app.gohtml:46
|
||||
msgctxt "nav"
|
||||
msgid "Contacts"
|
||||
msgstr "Contactes"
|
||||
|
@ -58,14 +168,6 @@ msgctxt "title"
|
|||
msgid "New Contact"
|
||||
msgstr "Nou contacte"
|
||||
|
||||
#: web/template/contacts/new.gohtml:9 web/template/contacts/index.gohtml:8
|
||||
#: web/template/contacts/edit.gohtml:9 web/template/profile.gohtml:9
|
||||
#: web/template/tax-details.gohtml:8 web/template/products/new.gohtml:9
|
||||
#: web/template/products/index.gohtml:8 web/template/products/edit.gohtml:9
|
||||
msgctxt "title"
|
||||
msgid "Home"
|
||||
msgstr "Inici"
|
||||
|
||||
#: web/template/contacts/new.gohtml:10 web/template/contacts/index.gohtml:2
|
||||
#: web/template/contacts/index.gohtml:9 web/template/contacts/edit.gohtml:10
|
||||
msgctxt "title"
|
||||
|
@ -82,11 +184,6 @@ msgctxt "contact"
|
|||
msgid "All"
|
||||
msgstr "Tots"
|
||||
|
||||
#: web/template/contacts/index.gohtml:22
|
||||
msgctxt "title"
|
||||
msgid "Customer"
|
||||
msgstr "Client"
|
||||
|
||||
#: web/template/contacts/index.gohtml:23
|
||||
msgctxt "title"
|
||||
msgid "Email"
|
||||
|
@ -199,25 +296,6 @@ msgctxt "action"
|
|||
msgid "New product"
|
||||
msgstr "Nou producte"
|
||||
|
||||
#: web/template/products/index.gohtml:21
|
||||
msgctxt "product"
|
||||
msgid "All"
|
||||
msgstr "Tots"
|
||||
|
||||
#: web/template/products/index.gohtml:22
|
||||
msgctxt "title"
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: web/template/products/index.gohtml:23
|
||||
msgctxt "title"
|
||||
msgid "Price"
|
||||
msgstr "Preu"
|
||||
|
||||
#: web/template/products/index.gohtml:37
|
||||
msgid "No products added yet."
|
||||
msgstr "No hi ha cap producte."
|
||||
|
||||
#: web/template/products/edit.gohtml:2 web/template/products/edit.gohtml:15
|
||||
msgctxt "title"
|
||||
msgid "Edit Product “%s”"
|
||||
|
@ -254,39 +332,40 @@ msgstr "No podeu deixar la contrasenya en blanc."
|
|||
msgid "Invalid user or password."
|
||||
msgstr "Nom d’usuari o contrasenya incorrectes."
|
||||
|
||||
#: pkg/products.go:193
|
||||
#: pkg/products.go:194 pkg/invoices.go:321
|
||||
msgctxt "input"
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: pkg/products.go:199
|
||||
#: pkg/products.go:200 pkg/invoices.go:327
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
msgstr "Descripció"
|
||||
|
||||
#: pkg/products.go:204
|
||||
#: pkg/products.go:205 pkg/invoices.go:332
|
||||
msgctxt "input"
|
||||
msgid "Price"
|
||||
msgstr "Preu"
|
||||
|
||||
#: pkg/products.go:214
|
||||
#: pkg/products.go:215 pkg/invoices.go:237 pkg/invoices.go:361
|
||||
msgctxt "input"
|
||||
msgid "Taxes"
|
||||
msgstr "Imposts"
|
||||
|
||||
#: pkg/products.go:234 pkg/profile.go:92
|
||||
#: pkg/products.go:235 pkg/profile.go:92 pkg/invoices.go:267
|
||||
#: pkg/invoices.go:384
|
||||
msgid "Name can not be empty."
|
||||
msgstr "No podeu deixar el nom en blanc."
|
||||
|
||||
#: pkg/products.go:235
|
||||
#: pkg/products.go:236 pkg/invoices.go:385
|
||||
msgid "Price can not be empty."
|
||||
msgstr "No podeu deixar el preu en blanc."
|
||||
|
||||
#: pkg/products.go:236
|
||||
#: pkg/products.go:237 pkg/invoices.go:386
|
||||
msgid "Price must be a number greater than zero."
|
||||
msgstr "El preu ha de ser un número major a zero."
|
||||
|
||||
#: pkg/products.go:238
|
||||
#: pkg/products.go:239 pkg/invoices.go:271 pkg/invoices.go:394
|
||||
msgid "Selected tax is not valid."
|
||||
msgstr "Heu seleccionat un impost que no és vàlid."
|
||||
|
||||
|
@ -349,6 +428,73 @@ msgstr "La confirmació no és igual a la contrasenya."
|
|||
msgid "Selected language is not valid."
|
||||
msgstr "Heu seleccionat un idioma que no és vàlid."
|
||||
|
||||
#: pkg/invoices.go:66
|
||||
msgid "Select a customer to bill."
|
||||
msgstr "Escolliu un client a facturar."
|
||||
|
||||
#: pkg/invoices.go:163
|
||||
msgid "Invalid action"
|
||||
msgstr "Acció invàlida."
|
||||
|
||||
#: pkg/invoices.go:214
|
||||
msgctxt "input"
|
||||
msgid "Customer"
|
||||
msgstr "Client"
|
||||
|
||||
#: pkg/invoices.go:220
|
||||
msgctxt "input"
|
||||
msgid "Number"
|
||||
msgstr "Número"
|
||||
|
||||
#: pkg/invoices.go:226
|
||||
msgctxt "input"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Data de factura"
|
||||
|
||||
#: pkg/invoices.go:232
|
||||
msgctxt "input"
|
||||
msgid "Notes"
|
||||
msgstr "Notes"
|
||||
|
||||
#: pkg/invoices.go:268
|
||||
msgid "Invoice date can not be empty."
|
||||
msgstr "No podeu deixar la data de la factura en blanc."
|
||||
|
||||
#: pkg/invoices.go:269
|
||||
msgid "Invoice date must be a valid date."
|
||||
msgstr "La data de facturació ha de ser vàlida."
|
||||
|
||||
#: pkg/invoices.go:315
|
||||
msgctxt "input"
|
||||
msgid "Id"
|
||||
msgstr "Identificador"
|
||||
|
||||
#: pkg/invoices.go:342
|
||||
msgctxt "input"
|
||||
msgid "Quantity"
|
||||
msgstr "Quantitat"
|
||||
|
||||
#: pkg/invoices.go:351
|
||||
msgctxt "input"
|
||||
msgid "Discount (%)"
|
||||
msgstr "Descompte (%)"
|
||||
|
||||
#: pkg/invoices.go:388
|
||||
msgid "Quantity can not be empty."
|
||||
msgstr "No podeu deixar la quantitat en blanc."
|
||||
|
||||
#: pkg/invoices.go:389
|
||||
msgid "Quantity must be a number greater than zero."
|
||||
msgstr "La quantitat ha de ser un número major a zero."
|
||||
|
||||
#: pkg/invoices.go:391
|
||||
msgid "Discount can not be empty."
|
||||
msgstr "No podeu deixar el descompte en blanc."
|
||||
|
||||
#: pkg/invoices.go:392
|
||||
msgid "Discount must be a percentage between 0 and 100."
|
||||
msgstr "El descompte ha de ser un percentatge entre 0 i 100."
|
||||
|
||||
#: pkg/contacts.go:149
|
||||
msgctxt "input"
|
||||
msgid "Business name"
|
||||
|
|
230
po/es.po
230
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-02-08 13:43+0100\n"
|
||||
"POT-Creation-Date: 2023-02-12 20:51+0100\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"
|
||||
|
@ -17,6 +17,111 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: web/template/invoices/products.gohtml:2
|
||||
#: web/template/invoices/products.gohtml:15
|
||||
msgctxt "title"
|
||||
msgid "Add Products to Invoice"
|
||||
msgstr "Añadir productos a la factura"
|
||||
|
||||
#: web/template/invoices/products.gohtml:9 web/template/invoices/new.gohtml:9
|
||||
#: web/template/invoices/index.gohtml:8 web/template/contacts/new.gohtml:9
|
||||
#: web/template/contacts/index.gohtml:8 web/template/contacts/edit.gohtml:9
|
||||
#: web/template/profile.gohtml:9 web/template/tax-details.gohtml:8
|
||||
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:8
|
||||
#: web/template/products/edit.gohtml:9
|
||||
msgctxt "title"
|
||||
msgid "Home"
|
||||
msgstr "Inicio"
|
||||
|
||||
#: web/template/invoices/products.gohtml:10 web/template/invoices/new.gohtml:10
|
||||
#: web/template/invoices/index.gohtml:2 web/template/invoices/index.gohtml:9
|
||||
msgctxt "title"
|
||||
msgid "Invoices"
|
||||
msgstr "Facturas"
|
||||
|
||||
#: web/template/invoices/products.gohtml:11 web/template/invoices/new.gohtml:2
|
||||
#: web/template/invoices/new.gohtml:11 web/template/invoices/new.gohtml:15
|
||||
msgctxt "title"
|
||||
msgid "New Invoice"
|
||||
msgstr "Nueva factura"
|
||||
|
||||
#: web/template/invoices/products.gohtml:41
|
||||
#: web/template/products/index.gohtml:21
|
||||
msgctxt "product"
|
||||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
||||
#: web/template/invoices/products.gohtml:42
|
||||
#: web/template/products/index.gohtml:22
|
||||
msgctxt "title"
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
#: web/template/invoices/products.gohtml:43
|
||||
#: web/template/products/index.gohtml:23
|
||||
msgctxt "title"
|
||||
msgid "Price"
|
||||
msgstr "Precio"
|
||||
|
||||
#: web/template/invoices/products.gohtml:57
|
||||
#: web/template/products/index.gohtml:37
|
||||
msgid "No products added yet."
|
||||
msgstr "No hay productos."
|
||||
|
||||
#: web/template/invoices/products.gohtml:64 web/template/invoices/new.gohtml:37
|
||||
msgctxt "action"
|
||||
msgid "Add products"
|
||||
msgstr "Añadir productos"
|
||||
|
||||
#: web/template/invoices/new.gohtml:38
|
||||
msgctxt "action"
|
||||
msgid "Update"
|
||||
msgstr "Actualizar"
|
||||
|
||||
#: web/template/invoices/new.gohtml:40 web/template/invoices/index.gohtml:13
|
||||
msgctxt "action"
|
||||
msgid "New invoice"
|
||||
msgstr "Nueva factura"
|
||||
|
||||
#: web/template/invoices/index.gohtml:21
|
||||
msgctxt "invoice"
|
||||
msgid "All"
|
||||
msgstr "Todas"
|
||||
|
||||
#: web/template/invoices/index.gohtml:22
|
||||
msgctxt "title"
|
||||
msgid "Date"
|
||||
msgstr "Fecha"
|
||||
|
||||
#: web/template/invoices/index.gohtml:23
|
||||
msgctxt "title"
|
||||
msgid "Invoice Num."
|
||||
msgstr "Nº factura"
|
||||
|
||||
#: web/template/invoices/index.gohtml:24 web/template/contacts/index.gohtml:22
|
||||
msgctxt "title"
|
||||
msgid "Customer"
|
||||
msgstr "Cliente"
|
||||
|
||||
#: web/template/invoices/index.gohtml:25
|
||||
msgctxt "title"
|
||||
msgid "Status"
|
||||
msgstr "Estado"
|
||||
|
||||
#: web/template/invoices/index.gohtml:26
|
||||
msgctxt "title"
|
||||
msgid "Label"
|
||||
msgstr "Etiqueta"
|
||||
|
||||
#: web/template/invoices/index.gohtml:27
|
||||
msgctxt "title"
|
||||
msgid "Download"
|
||||
msgstr "Descargar"
|
||||
|
||||
#: web/template/invoices/index.gohtml:45
|
||||
msgid "No invoices added yet."
|
||||
msgstr "No hay facturas."
|
||||
|
||||
#: web/template/dashboard.gohtml:2
|
||||
msgctxt "title"
|
||||
msgid "Dashboard"
|
||||
|
@ -44,10 +149,15 @@ msgstr "Panel"
|
|||
|
||||
#: web/template/app.gohtml:44
|
||||
msgctxt "nav"
|
||||
msgid "Invoices"
|
||||
msgstr "Facturas"
|
||||
|
||||
#: web/template/app.gohtml:45
|
||||
msgctxt "nav"
|
||||
msgid "Products"
|
||||
msgstr "Productos"
|
||||
|
||||
#: web/template/app.gohtml:45
|
||||
#: web/template/app.gohtml:46
|
||||
msgctxt "nav"
|
||||
msgid "Contacts"
|
||||
msgstr "Contactos"
|
||||
|
@ -58,14 +168,6 @@ msgctxt "title"
|
|||
msgid "New Contact"
|
||||
msgstr "Nuevo contacto"
|
||||
|
||||
#: web/template/contacts/new.gohtml:9 web/template/contacts/index.gohtml:8
|
||||
#: web/template/contacts/edit.gohtml:9 web/template/profile.gohtml:9
|
||||
#: web/template/tax-details.gohtml:8 web/template/products/new.gohtml:9
|
||||
#: web/template/products/index.gohtml:8 web/template/products/edit.gohtml:9
|
||||
msgctxt "title"
|
||||
msgid "Home"
|
||||
msgstr "Inicio"
|
||||
|
||||
#: web/template/contacts/new.gohtml:10 web/template/contacts/index.gohtml:2
|
||||
#: web/template/contacts/index.gohtml:9 web/template/contacts/edit.gohtml:10
|
||||
msgctxt "title"
|
||||
|
@ -82,11 +184,6 @@ msgctxt "contact"
|
|||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
||||
#: web/template/contacts/index.gohtml:22
|
||||
msgctxt "title"
|
||||
msgid "Customer"
|
||||
msgstr "Cliente"
|
||||
|
||||
#: web/template/contacts/index.gohtml:23
|
||||
msgctxt "title"
|
||||
msgid "Email"
|
||||
|
@ -199,25 +296,6 @@ msgctxt "action"
|
|||
msgid "New product"
|
||||
msgstr "Nuevo producto"
|
||||
|
||||
#: web/template/products/index.gohtml:21
|
||||
msgctxt "product"
|
||||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
||||
#: web/template/products/index.gohtml:22
|
||||
msgctxt "title"
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
#: web/template/products/index.gohtml:23
|
||||
msgctxt "title"
|
||||
msgid "Price"
|
||||
msgstr "Precio"
|
||||
|
||||
#: web/template/products/index.gohtml:37
|
||||
msgid "No products added yet."
|
||||
msgstr "No hay productos."
|
||||
|
||||
#: web/template/products/edit.gohtml:2 web/template/products/edit.gohtml:15
|
||||
msgctxt "title"
|
||||
msgid "Edit Product “%s”"
|
||||
|
@ -254,39 +332,40 @@ 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:193
|
||||
#: pkg/products.go:194 pkg/invoices.go:321
|
||||
msgctxt "input"
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
#: pkg/products.go:199
|
||||
#: pkg/products.go:200 pkg/invoices.go:327
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
msgstr "Descripción"
|
||||
|
||||
#: pkg/products.go:204
|
||||
#: pkg/products.go:205 pkg/invoices.go:332
|
||||
msgctxt "input"
|
||||
msgid "Price"
|
||||
msgstr "Precio"
|
||||
|
||||
#: pkg/products.go:214
|
||||
#: pkg/products.go:215 pkg/invoices.go:237 pkg/invoices.go:361
|
||||
msgctxt "input"
|
||||
msgid "Taxes"
|
||||
msgstr "Impuestos"
|
||||
|
||||
#: pkg/products.go:234 pkg/profile.go:92
|
||||
#: pkg/products.go:235 pkg/profile.go:92 pkg/invoices.go:267
|
||||
#: pkg/invoices.go:384
|
||||
msgid "Name can not be empty."
|
||||
msgstr "No podéis dejar el nombre en blanco."
|
||||
|
||||
#: pkg/products.go:235
|
||||
#: pkg/products.go:236 pkg/invoices.go:385
|
||||
msgid "Price can not be empty."
|
||||
msgstr "No podéis dejar el precio en blanco."
|
||||
|
||||
#: pkg/products.go:236
|
||||
#: pkg/products.go:237 pkg/invoices.go:386
|
||||
msgid "Price must be a number greater than zero."
|
||||
msgstr "El precio tiene que ser un número mayor a cero."
|
||||
|
||||
#: pkg/products.go:238
|
||||
#: pkg/products.go:239 pkg/invoices.go:271 pkg/invoices.go:394
|
||||
msgid "Selected tax is not valid."
|
||||
msgstr "Habéis escogido un impuesto que no es válido."
|
||||
|
||||
|
@ -349,6 +428,73 @@ msgstr "La confirmación no corresponde con la contraseña."
|
|||
msgid "Selected language is not valid."
|
||||
msgstr "Habéis escogido un idioma que no es válido."
|
||||
|
||||
#: pkg/invoices.go:66
|
||||
msgid "Select a customer to bill."
|
||||
msgstr "Escoged un cliente a facturar."
|
||||
|
||||
#: pkg/invoices.go:163
|
||||
msgid "Invalid action"
|
||||
msgstr "Acción inválida."
|
||||
|
||||
#: pkg/invoices.go:214
|
||||
msgctxt "input"
|
||||
msgid "Customer"
|
||||
msgstr "Cliente"
|
||||
|
||||
#: pkg/invoices.go:220
|
||||
msgctxt "input"
|
||||
msgid "Number"
|
||||
msgstr "Número"
|
||||
|
||||
#: pkg/invoices.go:226
|
||||
msgctxt "input"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Fecha de factura"
|
||||
|
||||
#: pkg/invoices.go:232
|
||||
msgctxt "input"
|
||||
msgid "Notes"
|
||||
msgstr "Notas"
|
||||
|
||||
#: pkg/invoices.go:268
|
||||
msgid "Invoice date can not be empty."
|
||||
msgstr "No podéis dejar la fecha de la factura en blanco."
|
||||
|
||||
#: pkg/invoices.go:269
|
||||
msgid "Invoice date must be a valid date."
|
||||
msgstr "La fecha de factura debe ser válida."
|
||||
|
||||
#: pkg/invoices.go:315
|
||||
msgctxt "input"
|
||||
msgid "Id"
|
||||
msgstr "Identificador"
|
||||
|
||||
#: pkg/invoices.go:342
|
||||
msgctxt "input"
|
||||
msgid "Quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#: pkg/invoices.go:351
|
||||
msgctxt "input"
|
||||
msgid "Discount (%)"
|
||||
msgstr "Descuento (%)"
|
||||
|
||||
#: pkg/invoices.go:388
|
||||
msgid "Quantity can not be empty."
|
||||
msgstr "No podéis dejar la cantidad en blanco."
|
||||
|
||||
#: pkg/invoices.go:389
|
||||
msgid "Quantity must be a number greater than zero."
|
||||
msgstr "La cantidad tiene que ser un número mayor a cero."
|
||||
|
||||
#: pkg/invoices.go:391
|
||||
msgid "Discount can not be empty."
|
||||
msgstr "No podéis dejar el descuento en blanco."
|
||||
|
||||
#: pkg/invoices.go:392
|
||||
msgid "Discount must be a percentage between 0 and 100."
|
||||
msgstr "El descuento tiene que ser un percentage entre 0 y 100."
|
||||
|
||||
#: pkg/contacts.go:149
|
||||
msgctxt "input"
|
||||
msgid "Business name"
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
{{ define "hidden-field" -}}
|
||||
<input type="{{ .Type }}" name="{{ .Name }}" id="{{ .Name }}-field"
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.InputField*/ -}}
|
||||
<input type="hidden" name="{{ .Name }}"
|
||||
{{- range $attribute := .Attributes }} {{$attribute}} {{ end }}
|
||||
{{ if .Required }}required="required"{{ end }} value="{{ .Val }}">
|
||||
value="{{ .Val }}">
|
||||
{{- end }}
|
||||
|
||||
{{ define "input-field" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.InputField*/ -}}
|
||||
<div class="input {{ if .Errors }}has-errors{{ end }}">
|
||||
{{ if eq .Type "textarea" }}
|
||||
<textarea name="{{ .Name }}" id="{{ .Name }}-field"
|
||||
|
@ -27,7 +29,17 @@
|
|||
</div>
|
||||
{{- end }}
|
||||
|
||||
{{ define "hidden-select-field" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.SelectField*/ -}}
|
||||
{{- range $selected := .Selected }}
|
||||
<input type="hidden" name="{{ $.Name }}"
|
||||
{{- range $attribute := $.Attributes }} {{$attribute}} {{ end }}
|
||||
value="{{ . }}">
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{ define "select-field" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.SelectField*/ -}}
|
||||
<div class="input {{ if .Errors }}has-errors{{ end }}">
|
||||
<select id="{{ .Name }}-field" name="{{ .Name }}"
|
||||
{{- range $attribute := .Attributes }} {{$attribute}} {{ end -}}
|
||||
|
|
|
@ -28,9 +28,23 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="7">{{( gettext "No invoices added yet." )}}</td>
|
||||
</tr>
|
||||
{{ with .Invoices }}
|
||||
{{- range $invoice := . }}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>{{ .Date|formatDate }}</td>
|
||||
<td><a href="{{ companyURI "/invoices/"}}{{ .Slug }}">{{ .Number }}</a></td>
|
||||
<td><a href="{{ companyURI "/contacts/"}}{{ .CustomerSlug }}">{{ .CustomerName }}</a></td>
|
||||
<td class="invoice-status-{{ .Status }}">{{ .StatusLabel }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{- end }}
|
||||
{{ else }}
|
||||
<tr>
|
||||
<td colspan="7">{{( gettext "No invoices added yet." )}}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{- end }}
|
||||
|
|
|
@ -34,7 +34,10 @@
|
|||
{{- end }}
|
||||
|
||||
<fieldset>
|
||||
<button class="primary" name="action" value="add" type="submit">{{( pgettext "New invoice" "action" )}}</button>
|
||||
<button name="action" value="products" type="submit">{{( pgettext "Add products" "action" )}}</button>
|
||||
<button name="action" value="update" type="submit">{{( pgettext "Update" "action" )}}</button>
|
||||
<button class="primary" name="action" value="add"
|
||||
type="submit">{{( pgettext "New invoice" "action" )}}</button>
|
||||
</fieldset>
|
||||
|
||||
</form>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
{{ define "title" -}}
|
||||
{{( pgettext "Add Products to Invoice" "title" )}}
|
||||
{{- end }}
|
||||
|
||||
{{ define "content" }}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.newInvoiceProductsPage*/ -}}
|
||||
<nav>
|
||||
<p>
|
||||
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||
<a href="{{ companyURI "/invoices"}}">{{( pgettext "Invoices" "title" )}}</a> /
|
||||
<a>{{( pgettext "New Invoice" "title" )}}</a>
|
||||
</p>
|
||||
</nav>
|
||||
<section class="dialog-content">
|
||||
<h2>{{(pgettext "Add Products to Invoice" "title")}}</h2>
|
||||
<form method="POST" action="{{ companyURI "/invoices/new/products" }}">
|
||||
{{ csrfToken }}
|
||||
|
||||
{{- with .Form }}
|
||||
{{ template "hidden-select-field" .Customer }}
|
||||
{{ template "hidden-field" .Number }}
|
||||
{{ template "hidden-field" .Date }}
|
||||
{{ template "hidden-field" .Notes }}
|
||||
|
||||
{{- range $product := .Products }}
|
||||
<fieldset>
|
||||
{{ template "hidden-field" .ProductId }}
|
||||
{{ template "hidden-field" .Name }}
|
||||
{{ template "hidden-field" .Description }}
|
||||
{{ template "hidden-field" .Price }}
|
||||
{{ template "hidden-field" .Quantity }}
|
||||
{{ template "hidden-field" .Discount }}
|
||||
{{ template "hidden-select-field" .Tax }}
|
||||
</fieldset>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{( pgettext "All" "product" )}}</th>
|
||||
<th>{{( pgettext "Name" "title" )}}</th>
|
||||
<th>{{( pgettext "Price" "title" )}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ with .Products }}
|
||||
{{- range $product, $key := . }}
|
||||
<tr>
|
||||
<td><input type="checkbox" name="id" id="new-product-id-{{$key}}" value="{{.Id}}"></td>
|
||||
<td><label for="new-product-id-{{$key}}">{{ .Name }}</label></td>
|
||||
<td>{{ .Price | formatPrice }}</td>
|
||||
</tr>
|
||||
{{- end }}
|
||||
{{ else }}
|
||||
<tr>
|
||||
<td colspan="4">{{( gettext "No products added yet." )}}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<fieldset>
|
||||
<button class="primary" type="submit">{{( pgettext "Add products" "action" )}}</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</section>
|
||||
{{- end }}
|
Loading…
Reference in New Issue