Compare commits

..

No commits in common. "e9cc331ee06cb80ac1610cdb95ede11e445ab058" and "55980367bc0abad4fd0430fe8de865a5f871dd8d" have entirely different histories.

20 changed files with 204 additions and 887 deletions

View File

@ -1,42 +0,0 @@
-- Deploy numerus:product to pg
-- requires: schema_numerus
-- requires: company
begin;
set search_path to numerus, public;
create table product (
product_id serial primary key,
company_id integer not null references company,
slug uuid not null default gen_random_uuid(),
name text not null,
description text not null,
price integer not null,
tax_id integer not null references tax,
created_at timestamptz not null default current_timestamp
);
comment on column product.price is
'Price is stored in cents.';
grant select, insert, update, delete on table product to invoicer;
grant select, insert, update, delete on table product to admin;
grant usage on sequence product_product_id_seq to invoicer;
grant usage on sequence product_product_id_seq to admin;
alter table product enable row level security;
create policy company_policy
on product
using (
exists(
select 1
from company_user
join user_profile using (user_id)
where company_user.company_id = product.company_id
)
);
commit;

View File

@ -144,7 +144,7 @@ func HandleCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httpr
} }
company := mustGetCompany(r) company := mustGetCompany(r)
conn.MustExec(r.Context(), "update company set business_name = $1, vatin = ($11 || $2)::vatin, trade_name = $3, phone = parse_packed_phone_number($4, $11), email = $5, web = $6, address = $7, city = $8, province = $9, postal_code = $10, country_code = $11, currency_code = $12 where company_id = $13", form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country, form.Currency, company.Id) conn.MustExec(r.Context(), "update company set business_name = $1, vatin = ($11 || $2)::vatin, trade_name = $3, phone = parse_packed_phone_number($4, $11), email = $5, web = $6, address = $7, city = $8, province = $9, postal_code = $10, country_code = $11, currency_code = $12 where company_id = $13", form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country, form.Currency, company.Id)
http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther) http.Redirect(w, r, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
return return
} }
@ -260,8 +260,9 @@ func HandleDeleteCompanyTax(w http.ResponseWriter, r *http.Request, params httpr
return return
} }
conn := getConn(r) conn := getConn(r)
company := mustGetCompany(r)
conn.MustExec(r.Context(), "delete from tax where tax_id = $1", taxId) conn.MustExec(r.Context(), "delete from tax where tax_id = $1", taxId)
http.Redirect(w, r, companyURI(mustGetCompany(r), "/tax-details"), http.StatusSeeOther) http.Redirect(w, r, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
} }
func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@ -283,5 +284,5 @@ func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Pa
conn := getConn(r) conn := getConn(r)
company := mustGetCompany(r) company := mustGetCompany(r)
conn.MustExec(r.Context(), "insert into tax (company_id, name, rate) values ($1, $2, $3 / 100::decimal)", company.Id, form.Name, form.Rate.Integer()) conn.MustExec(r.Context(), "insert into tax (company_id, name, rate) values ($1, $2, $3 / 100::decimal)", company.Id, form.Name, form.Rate.Integer())
http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther) http.Redirect(w, r, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
} }

View File

@ -21,11 +21,11 @@ type ContactsIndexPage struct {
func IndexContacts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func IndexContacts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
conn := getConn(r) conn := getConn(r)
company := mustGetCompany(r) company := getCompany(r)
page := &ContactsIndexPage{ page := &ContactsIndexPage{
Contacts: mustGetContactEntries(r.Context(), conn, company), Contacts: mustGetContactEntries(r.Context(), conn, company),
} }
mustRenderAppTemplate(w, r, "contacts/index.gohtml", page) mustRenderAppTemplate(w, r, "contacts-index.gohtml", page)
} }
func GetContactForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func GetContactForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
@ -52,11 +52,11 @@ func GetContactForm(w http.ResponseWriter, r *http.Request, params httprouter.Pa
} }
func mustRenderNewContactForm(w http.ResponseWriter, r *http.Request, form *contactForm) { func mustRenderNewContactForm(w http.ResponseWriter, r *http.Request, form *contactForm) {
mustRenderAppTemplate(w, r, "contacts/new.gohtml", form) mustRenderAppTemplate(w, r, "contacts-new.gohtml", form)
} }
func mustRenderEditContactForm(w http.ResponseWriter, r *http.Request, form *contactForm) { func mustRenderEditContactForm(w http.ResponseWriter, r *http.Request, form *contactForm) {
mustRenderAppTemplate(w, r, "contacts/edit.gohtml", form) mustRenderAppTemplate(w, r, "contacts-edit.gohtml", form)
} }
func HandleAddContact(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func HandleAddContact(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@ -75,9 +75,9 @@ func HandleAddContact(w http.ResponseWriter, r *http.Request, _ httprouter.Param
mustRenderNewContactForm(w, r, form) mustRenderNewContactForm(w, r, form)
return return
} }
company := mustGetCompany(r) company := getCompany(r)
conn.MustExec(r.Context(), "insert into contact (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) values ($1, $2, ($12 || $3)::vatin, $4, parse_packed_phone_number($5, $12), $6, $7, $8, $9, $10, $11, $12)", company.Id, form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country) conn.MustExec(r.Context(), "insert into contact (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) values ($1, $2, ($12 || $3)::vatin, $4, parse_packed_phone_number($5, $12), $6, $7, $8, $9, $10, $11, $12)", company.Id, form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country)
http.Redirect(w, r, companyURI(company, "/contacts"), http.StatusSeeOther) http.Redirect(w, r, "/company/"+company.Slug+"/contacts", http.StatusSeeOther)
} }
func HandleUpdateContact(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func HandleUpdateContact(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
@ -100,7 +100,8 @@ func HandleUpdateContact(w http.ResponseWriter, r *http.Request, params httprout
if slug == "" { if slug == "" {
http.NotFound(w, r) http.NotFound(w, r)
} }
http.Redirect(w, r, companyURI(mustGetCompany(r), "/contacts/"+slug), http.StatusSeeOther) company := getCompany(r)
http.Redirect(w, r, "/company/"+company.Slug+"/contacts/"+slug, http.StatusSeeOther)
} }
func mustGetContactEntries(ctx context.Context, conn *Conn, company *Company) []*ContactEntry { func mustGetContactEntries(ctx context.Context, conn *Conn, company *Company) []*ContactEntry {
@ -222,7 +223,7 @@ func newContactForm(ctx context.Context, conn *Conn, locale *Locale) *contactFor
}, },
Country: &SelectField{ Country: &SelectField{
Name: "country", Name: "country",
Label: pgettext("input", "Tax", locale), Label: pgettext("input", "Country", locale),
Options: mustGetCountryOptions(ctx, conn, locale), Options: mustGetCountryOptions(ctx, conn, locale),
Selected: "ES", Selected: "ES",
Attributes: []template.HTMLAttr{ Attributes: []template.HTMLAttr{

View File

@ -1,189 +0,0 @@
package pkg
import (
"context"
"github.com/jackc/pgx/v4"
"github.com/julienschmidt/httprouter"
"html/template"
"math"
"net/http"
)
type ProductEntry struct {
Slug string
Name string
Price int
}
type productsIndexPage struct {
Products []*ProductEntry
}
func IndexProducts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
conn := getConn(r)
company := mustGetCompany(r)
page := &productsIndexPage{
Products: mustGetProductEntries(r.Context(), conn, company),
}
mustRenderAppTemplate(w, r, "products/index.gohtml", page)
}
func GetProductForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
locale := getLocale(r)
conn := getConn(r)
company := mustGetCompany(r)
form := newProductForm(r.Context(), conn, locale, company)
slug := params[0].Value
if slug == "new" {
w.WriteHeader(http.StatusOK)
mustRenderNewProductForm(w, r, form)
return
}
err := conn.QueryRow(r.Context(), "select name, description, price, tax_id from product where slug = $1", slug).Scan(form.Name, form.Description, form.Price, form.Tax)
if err != nil {
if err == pgx.ErrNoRows {
http.NotFound(w, r)
return
} else {
panic(err)
}
}
w.WriteHeader(http.StatusOK)
mustRenderEditProductForm(w, r, form)
}
func mustRenderNewProductForm(w http.ResponseWriter, r *http.Request, form *productForm) {
mustRenderAppTemplate(w, r, "products/new.gohtml", form)
}
func mustRenderEditProductForm(w http.ResponseWriter, r *http.Request, form *productForm) {
mustRenderAppTemplate(w, r, "products/edit.gohtml", form)
}
func HandleAddProduct(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
conn := getConn(r)
locale := getLocale(r)
company := mustGetCompany(r)
form := newProductForm(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() {
mustRenderNewProductForm(w, r, form)
return
}
conn.MustExec(r.Context(), "insert into product (company_id, name, description, price, tax_id) values ($1, $2, $3, $4, $5)", company.Id, form.Name, form.Description, form.Price, form.Tax)
http.Redirect(w, r, companyURI(company, "/products"), http.StatusSeeOther)
}
func HandleUpdateProduct(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
conn := getConn(r)
locale := getLocale(r)
company := mustGetCompany(r)
form := newProductForm(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() {
mustRenderEditProductForm(w, r, form)
return
}
slug := conn.MustGetText(r.Context(), "", "update product set name = $1, description = $2, price = $3, tax_id = $4 where slug = $5 returning slug", form.Name, form.Description, form.Price, form.Tax, params[0].Value)
if slug == "" {
http.NotFound(w, r)
}
http.Redirect(w, r, companyURI(company, "/products/"+slug), http.StatusSeeOther)
}
func mustGetProductEntries(ctx context.Context, conn *Conn, company *Company) []*ProductEntry {
rows, err := conn.Query(ctx, "select slug, name, price from product where company_id = $1 order by name", company.Id)
if err != nil {
panic(err)
}
defer rows.Close()
var entries []*ProductEntry
for rows.Next() {
entry := &ProductEntry{}
err = rows.Scan(&entry.Slug, &entry.Name, &entry.Price)
if err != nil {
panic(err)
}
entries = append(entries, entry)
}
if rows.Err() != nil {
panic(rows.Err())
}
return entries
}
type productForm struct {
locale *Locale
Name *InputField
Description *InputField
Price *InputField
Tax *SelectField
}
func newProductForm(ctx context.Context, conn *Conn, locale *Locale, company *Company) *productForm {
return &productForm{
locale: locale,
Name: &InputField{
Name: "name",
Label: pgettext("input", "Name", locale),
Type: "text",
Required: true,
},
Description: &InputField{
Name: "description",
Label: pgettext("input", "Description", locale),
Type: "text",
},
Price: &InputField{
Name: "price",
Label: pgettext("input", "Price", locale),
Type: "number",
Required: true,
Attributes: []template.HTMLAttr{
`min="0"`,
},
},
Tax: &SelectField{
Name: "tax",
Label: pgettext("input", "Tax", locale),
Options: MustGetOptions(ctx, conn, "select tax_id::text, name from tax where company_id = $1 order by name", company.Id),
},
}
}
func (form *productForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
form.Name.FillValue(r)
form.Description.FillValue(r)
form.Price.FillValue(r)
form.Tax.FillValue(r)
return nil
}
func (form *productForm) 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.CheckValidInteger(form.Price, 0, math.MaxInt, gettext("Price must be a number greater than zero.", form.locale))
}
validator.CheckValidSelectOption(form.Tax, gettext("Selected tax is not valid.", form.locale))
return validator.AllOK()
}

View File

@ -130,8 +130,8 @@ func HandleProfileForm(w http.ResponseWriter, r *http.Request, _ httprouter.Para
if form.Password.Val != "" { if form.Password.Val != "" {
conn.MustExec(r.Context(), "select change_password($1)", form.Password) conn.MustExec(r.Context(), "select change_password($1)", form.Password)
} }
company := mustGetCompany(r) company := getCompany(r)
http.Redirect(w, r, companyURI(company, "/profile"), http.StatusSeeOther) http.Redirect(w, r, "/company/"+company.Slug+"/profile", http.StatusSeeOther)
} }
func mustRenderProfileForm(w http.ResponseWriter, r *http.Request, form *profileForm) { func mustRenderProfileForm(w http.ResponseWriter, r *http.Request, form *profileForm) {

View File

@ -18,10 +18,6 @@ func NewRouter(db *Db) http.Handler {
companyRouter.POST("/contacts", HandleAddContact) companyRouter.POST("/contacts", HandleAddContact)
companyRouter.GET("/contacts/:slug", GetContactForm) companyRouter.GET("/contacts/:slug", GetContactForm)
companyRouter.PUT("/contacts/:slug", HandleUpdateContact) companyRouter.PUT("/contacts/:slug", HandleUpdateContact)
companyRouter.GET("/products", IndexProducts)
companyRouter.POST("/products", HandleAddProduct)
companyRouter.GET("/products/:slug", GetProductForm)
companyRouter.PUT("/products/:slug", HandleUpdateProduct)
companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
mustRenderAppTemplate(w, r, "dashboard.gohtml", nil) mustRenderAppTemplate(w, r, "dashboard.gohtml", nil)
}) })
@ -42,10 +38,8 @@ func NewRouter(db *Db) http.Handler {
user := getUser(r) user := getUser(r)
if user.LoggedIn { if user.LoggedIn {
conn := getConn(r) conn := getConn(r)
company := &Company{ slug := conn.MustGetText(r.Context(), "", "select slug::text from company order by company_id limit 1")
Slug: conn.MustGetText(r.Context(), "", "select slug::text from company order by company_id limit 1"), http.Redirect(w, r, "/company/"+slug+"/", http.StatusFound)
}
http.Redirect(w, r, companyURI(company, "/"), http.StatusFound)
} else { } else {
http.Redirect(w, r, "/login", http.StatusSeeOther) http.Redirect(w, r, "/login", http.StatusSeeOther)
} }

View File

@ -25,7 +25,10 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s
return locale.Language.String() return locale.Language.String()
}, },
"companyURI": func(uri string) string { "companyURI": func(uri string) string {
return companyURI(company, uri) if company == nil {
return uri
}
return "/company/" + company.Slug + uri
}, },
"csrfToken": func() template.HTML { "csrfToken": func() template.HTML {
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, csrfTokenField, user.CsrfToken)) return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, csrfTokenField, user.CsrfToken))
@ -53,13 +56,6 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s
} }
} }
func companyURI(company *Company, uri string) string {
if company == nil {
return uri
}
return "/company/" + company.Slug + uri
}
func overrideMethodField(method string) template.HTML { func overrideMethodField(method string) template.HTML {
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, overrideMethodName, method)) return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, overrideMethodName, method))
} }

265
po/ca.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: numerus\n" "Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-02-04 11:24+0100\n" "POT-Creation-Date: 2023-02-03 13:57+0100\n"
"PO-Revision-Date: 2023-01-18 17:08+0100\n" "PO-Revision-Date: 2023-01-18 17:08+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n" "Language-Team: Catalan <ca@dodds.net>\n"
@ -44,73 +44,9 @@ msgstr "Tauler"
#: web/template/app.gohtml:44 #: web/template/app.gohtml:44
msgctxt "nav" msgctxt "nav"
msgid "Products"
msgstr "Productes"
#: web/template/app.gohtml:45
msgctxt "nav"
msgid "Contacts" msgid "Contacts"
msgstr "Contactes" msgstr "Contactes"
#: web/template/contacts/new.gohtml:2 web/template/contacts/new.gohtml:11
#: web/template/contacts/new.gohtml:15
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"
msgid "Contacts"
msgstr "Contactes"
#: web/template/contacts/new.gohtml:31 web/template/contacts/index.gohtml:13
msgctxt "action"
msgid "New contact"
msgstr "Nou contacte"
#: web/template/contacts/index.gohtml:21
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"
msgstr "Correu-e"
#: web/template/contacts/index.gohtml:24
msgctxt "title"
msgid "Phone"
msgstr "Telèfon"
#: web/template/contacts/index.gohtml:39
msgid "No contacts added yet."
msgstr "No hi ha cap contacte."
#: web/template/contacts/edit.gohtml:2 web/template/contacts/edit.gohtml:15
msgctxt "title"
msgid "Edit Contact “%s”"
msgstr "Edició del contacte «%s»"
#: web/template/contacts/edit.gohtml:33
msgctxt "action"
msgid "Update contact"
msgstr "Actualitza contacte"
#: web/template/login.gohtml:2 web/template/login.gohtml:15 #: web/template/login.gohtml:2 web/template/login.gohtml:15
msgctxt "title" msgctxt "title"
msgid "Login" msgid "Login"
@ -127,6 +63,13 @@ msgctxt "title"
msgid "User Settings" msgid "User Settings"
msgstr "Configuració usuari" msgstr "Configuració usuari"
#: web/template/profile.gohtml:9 web/template/contacts-edit.gohtml:9
#: web/template/contacts-index.gohtml:8 web/template/tax-details.gohtml:8
#: web/template/contacts-new.gohtml:9
msgctxt "title"
msgid "Home"
msgstr "Inici"
#: web/template/profile.gohtml:18 #: web/template/profile.gohtml:18
msgctxt "title" msgctxt "title"
msgid "User Access Data" msgid "User Access Data"
@ -147,6 +90,51 @@ msgctxt "action"
msgid "Save changes" msgid "Save changes"
msgstr "Desa canvis" msgstr "Desa canvis"
#: web/template/contacts-edit.gohtml:2 web/template/contacts-edit.gohtml:15
msgctxt "title"
msgid "Edit Contact “%s”"
msgstr "Edició del contacte «%s»"
#: web/template/contacts-edit.gohtml:10 web/template/contacts-index.gohtml:2
#: web/template/contacts-index.gohtml:9 web/template/contacts-new.gohtml:10
msgctxt "title"
msgid "Contacts"
msgstr "Contactes"
#: web/template/contacts-edit.gohtml:33
msgctxt "action"
msgid "Update contact"
msgstr "Actualitza contacte"
#: web/template/contacts-index.gohtml:13 web/template/contacts-new.gohtml:31
msgctxt "action"
msgid "New contact"
msgstr "Nou contacte"
#: web/template/contacts-index.gohtml:20
msgctxt "contact"
msgid "All"
msgstr "Tots"
#: web/template/contacts-index.gohtml:21
msgctxt "title"
msgid "Customer"
msgstr "Client"
#: web/template/contacts-index.gohtml:22
msgctxt "title"
msgid "Email"
msgstr "Correu-e"
#: web/template/contacts-index.gohtml:23
msgctxt "title"
msgid "Phone"
msgstr "Telèfon"
#: web/template/contacts-index.gohtml:38
msgid "No contacts added yet."
msgstr "No hi ha cap contacte."
#: web/template/tax-details.gohtml:2 web/template/tax-details.gohtml:9 #: web/template/tax-details.gohtml:2 web/template/tax-details.gohtml:9
#: web/template/tax-details.gohtml:13 #: web/template/tax-details.gohtml:13
msgctxt "title" msgctxt "title"
@ -161,7 +149,7 @@ msgstr "Moneda"
#: web/template/tax-details.gohtml:47 #: web/template/tax-details.gohtml:47
msgctxt "title" msgctxt "title"
msgid "Tax Name" msgid "Tax Name"
msgstr "Nom impost" msgstr "Nom import"
#: web/template/tax-details.gohtml:48 #: web/template/tax-details.gohtml:48
msgctxt "title" msgctxt "title"
@ -182,53 +170,13 @@ msgctxt "action"
msgid "Add new tax" msgid "Add new tax"
msgstr "Afegeix nou impost" msgstr "Afegeix nou impost"
#: web/template/products/new.gohtml:2 web/template/products/new.gohtml:11 #: web/template/contacts-new.gohtml:2 web/template/contacts-new.gohtml:11
#: web/template/products/new.gohtml:15 #: web/template/contacts-new.gohtml:15
msgctxt "title" msgctxt "title"
msgid "New Product" msgid "New Contact"
msgstr "Nou producte" msgstr "Nou contacte"
#: web/template/products/new.gohtml:10 web/template/products/index.gohtml:2 #: pkg/login.go:36 pkg/profile.go:40 pkg/contacts.go:179
#: web/template/products/index.gohtml:9 web/template/products/edit.gohtml:10
msgctxt "title"
msgid "Products"
msgstr "Productes"
#: web/template/products/new.gohtml:25 web/template/products/index.gohtml:13
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”"
msgstr "Edició del producte «%s»"
#: web/template/products/edit.gohtml:26
msgctxt "action"
msgid "Update product"
msgstr "Actualitza producte"
#: pkg/login.go:36 pkg/profile.go:40 pkg/contacts.go:178
msgctxt "input" msgctxt "input"
msgid "Email" msgid "Email"
msgstr "Correu-e" msgstr "Correu-e"
@ -238,11 +186,11 @@ msgctxt "input"
msgid "Password" msgid "Password"
msgstr "Contrasenya" msgstr "Contrasenya"
#: pkg/login.go:69 pkg/profile.go:88 pkg/contacts.go:262 #: pkg/login.go:69 pkg/profile.go:88 pkg/contacts.go:263
msgid "Email can not be empty." msgid "Email can not be empty."
msgstr "No podeu deixar el correu-e en blanc." msgstr "No podeu deixar el correu-e en blanc."
#: pkg/login.go:70 pkg/profile.go:89 pkg/contacts.go:263 #: pkg/login.go:70 pkg/profile.go:89 pkg/contacts.go:264
msgid "This value is not a valid email. It should be like name@domain.com." msgid "This value is not a valid email. It should be like name@domain.com."
msgstr "Aquest valor no és un correu-e vàlid. Hauria de ser similar a nom@domini.cat." msgstr "Aquest valor no és un correu-e vàlid. Hauria de ser similar a nom@domini.cat."
@ -254,42 +202,6 @@ msgstr "No podeu deixar la contrasenya en blanc."
msgid "Invalid user or password." msgid "Invalid user or password."
msgstr "Nom dusuari o contrasenya incorrectes." msgstr "Nom dusuari o contrasenya incorrectes."
#: pkg/products.go:144
msgctxt "input"
msgid "Name"
msgstr "Nom"
#: pkg/products.go:150
msgctxt "input"
msgid "Description"
msgstr "Descripció"
#: pkg/products.go:155
msgctxt "input"
msgid "Price"
msgstr "Preu"
#: pkg/products.go:164 pkg/contacts.go:225
msgctxt "input"
msgid "Tax"
msgstr "Impost"
#: pkg/products.go:183 pkg/profile.go:91
msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc."
#: pkg/products.go:184
msgid "Price can not be empty."
msgstr "No podeu deixar el preu en blanc."
#: pkg/products.go:185
msgid "Price must be a number greater than zero."
msgstr "El preu ha de ser un número major a zero."
#: pkg/products.go:187
msgid "Selected tax is not valid."
msgstr "Heu seleccionat un impost que no és vàlid."
#: pkg/company.go:78 #: pkg/company.go:78
msgctxt "input" msgctxt "input"
msgid "Currency" msgid "Currency"
@ -341,6 +253,10 @@ msgctxt "input"
msgid "Language" msgid "Language"
msgstr "Idioma" msgstr "Idioma"
#: pkg/profile.go:91
msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc."
#: pkg/profile.go:92 #: pkg/profile.go:92
msgid "Confirmation does not match password." msgid "Confirmation does not match password."
msgstr "La confirmació no és igual a la contrasenya." msgstr "La confirmació no és igual a la contrasenya."
@ -349,103 +265,104 @@ msgstr "La confirmació no és igual a la contrasenya."
msgid "Selected language is not valid." msgid "Selected language is not valid."
msgstr "Heu seleccionat un idioma que no és vàlid." msgstr "Heu seleccionat un idioma que no és vàlid."
#: pkg/contacts.go:149 #: pkg/contacts.go:150
msgctxt "input" msgctxt "input"
msgid "Business name" msgid "Business name"
msgstr "Nom i cognoms" msgstr "Nom i cognoms"
#: pkg/contacts.go:158 #: pkg/contacts.go:159
msgctxt "input" msgctxt "input"
msgid "VAT number" msgid "VAT number"
msgstr "DNI / NIF" msgstr "DNI / NIF"
#: pkg/contacts.go:164 #: pkg/contacts.go:165
msgctxt "input" msgctxt "input"
msgid "Trade name" msgid "Trade name"
msgstr "Nom comercial" msgstr "Nom comercial"
#: pkg/contacts.go:169 #: pkg/contacts.go:170
msgctxt "input" msgctxt "input"
msgid "Phone" msgid "Phone"
msgstr "Telèfon" msgstr "Telèfon"
#: pkg/contacts.go:187 #: pkg/contacts.go:188
msgctxt "input" msgctxt "input"
msgid "Web" msgid "Web"
msgstr "Web" msgstr "Web"
#: pkg/contacts.go:195 #: pkg/contacts.go:196
msgctxt "input" msgctxt "input"
msgid "Address" msgid "Address"
msgstr "Adreça" msgstr "Adreça"
#: pkg/contacts.go:204 #: pkg/contacts.go:205
msgctxt "input" msgctxt "input"
msgid "City" msgid "City"
msgstr "Població" msgstr "Població"
#: pkg/contacts.go:210 #: pkg/contacts.go:211
msgctxt "input" msgctxt "input"
msgid "Province" msgid "Province"
msgstr "Província" msgstr "Província"
#: pkg/contacts.go:216 #: pkg/contacts.go:217
msgctxt "input" msgctxt "input"
msgid "Postal code" msgid "Postal code"
msgstr "Codi postal" msgstr "Codi postal"
#: pkg/contacts.go:255 #: pkg/contacts.go:226
msgctxt "input"
msgid "Country"
msgstr "País"
#: pkg/contacts.go:256
msgid "Business name can not be empty." msgid "Business name can not be empty."
msgstr "No podeu deixar el nom i els cognoms en blanc." msgstr "No podeu deixar el nom i els cognoms en blanc."
#: pkg/contacts.go:256 #: pkg/contacts.go:257
msgid "VAT number can not be empty." msgid "VAT number can not be empty."
msgstr "No podeu deixar el DNI o NIF en blanc." msgstr "No podeu deixar el DNI o NIF en blanc."
#: pkg/contacts.go:257 #: pkg/contacts.go:258
msgid "This value is not a valid VAT number." msgid "This value is not a valid VAT number."
msgstr "Aquest valor no és un DNI o NIF vàlid." msgstr "Aquest valor no és un DNI o NIF vàlid."
#: pkg/contacts.go:259 #: pkg/contacts.go:260
msgid "Phone can not be empty." msgid "Phone can not be empty."
msgstr "No podeu deixar el telèfon en blanc." msgstr "No podeu deixar el telèfon en blanc."
#: pkg/contacts.go:260 #: pkg/contacts.go:261
msgid "This value is not a valid phone number." msgid "This value is not a valid phone number."
msgstr "Aquest valor no és un telèfon vàlid." msgstr "Aquest valor no és un telèfon vàlid."
#: pkg/contacts.go:266 #: pkg/contacts.go:267
msgid "This value is not a valid web address. It should be like https://domain.com/." msgid "This value is not a valid web address. It should be like https://domain.com/."
msgstr "Aquest valor no és una adreça web vàlida. Hauria de ser similar a https://domini.cat/." msgstr "Aquest valor no és una adreça web vàlida. Hauria de ser similar a https://domini.cat/."
#: pkg/contacts.go:268 #: pkg/contacts.go:269
msgid "Address can not be empty." msgid "Address can not be empty."
msgstr "No podeu deixar ladreça en blanc." msgstr "No podeu deixar ladreça en blanc."
#: pkg/contacts.go:269 #: pkg/contacts.go:270
msgid "City can not be empty." msgid "City can not be empty."
msgstr "No podeu deixar la població en blanc." msgstr "No podeu deixar la població en blanc."
#: pkg/contacts.go:270 #: pkg/contacts.go:271
msgid "Province can not be empty." msgid "Province can not be empty."
msgstr "No podeu deixar la província en blanc." msgstr "No podeu deixar la província en blanc."
#: pkg/contacts.go:271 #: pkg/contacts.go:272
msgid "Postal code can not be empty." msgid "Postal code can not be empty."
msgstr "No podeu deixar el codi postal en blanc." msgstr "No podeu deixar el codi postal en blanc."
#: pkg/contacts.go:272 #: pkg/contacts.go:273
msgid "This value is not a valid postal code." msgid "This value is not a valid postal code."
msgstr "Aquest valor no és un codi postal vàlid." msgstr "Aquest valor no és un codi postal vàlid."
#: pkg/contacts.go:274 #: pkg/contacts.go:275
msgid "Selected country is not valid." msgid "Selected country is not valid."
msgstr "Heu seleccionat un país que no és vàlid." msgstr "Heu seleccionat un país que no és vàlid."
#~ msgctxt "input"
#~ msgid "Country"
#~ msgstr "País"
#~ msgctxt "nav" #~ msgctxt "nav"
#~ msgid "Customers" #~ msgid "Customers"
#~ msgstr "Clients" #~ msgstr "Clients"

263
po/es.po
View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: numerus\n" "Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-02-04 11:24+0100\n" "POT-Creation-Date: 2023-02-03 13:57+0100\n"
"PO-Revision-Date: 2023-01-18 17:45+0100\n" "PO-Revision-Date: 2023-01-18 17:45+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n" "Language-Team: Spanish <es@tp.org.es>\n"
@ -44,73 +44,9 @@ msgstr "Panel"
#: web/template/app.gohtml:44 #: web/template/app.gohtml:44
msgctxt "nav" msgctxt "nav"
msgid "Products"
msgstr "Productos"
#: web/template/app.gohtml:45
msgctxt "nav"
msgid "Contacts" msgid "Contacts"
msgstr "Contactos" msgstr "Contactos"
#: web/template/contacts/new.gohtml:2 web/template/contacts/new.gohtml:11
#: web/template/contacts/new.gohtml:15
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"
msgid "Contacts"
msgstr "Contactos"
#: web/template/contacts/new.gohtml:31 web/template/contacts/index.gohtml:13
msgctxt "action"
msgid "New contact"
msgstr "Nuevo contacto"
#: web/template/contacts/index.gohtml:21
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"
msgstr "Correo-e"
#: web/template/contacts/index.gohtml:24
msgctxt "title"
msgid "Phone"
msgstr "Teléfono"
#: web/template/contacts/index.gohtml:39
msgid "No contacts added yet."
msgstr "No hay contactos."
#: web/template/contacts/edit.gohtml:2 web/template/contacts/edit.gohtml:15
msgctxt "title"
msgid "Edit Contact “%s”"
msgstr "Edición del contacto «%s»"
#: web/template/contacts/edit.gohtml:33
msgctxt "action"
msgid "Update contact"
msgstr "Actualizar contacto"
#: web/template/login.gohtml:2 web/template/login.gohtml:15 #: web/template/login.gohtml:2 web/template/login.gohtml:15
msgctxt "title" msgctxt "title"
msgid "Login" msgid "Login"
@ -127,6 +63,13 @@ msgctxt "title"
msgid "User Settings" msgid "User Settings"
msgstr "Configuración usuario" msgstr "Configuración usuario"
#: web/template/profile.gohtml:9 web/template/contacts-edit.gohtml:9
#: web/template/contacts-index.gohtml:8 web/template/tax-details.gohtml:8
#: web/template/contacts-new.gohtml:9
msgctxt "title"
msgid "Home"
msgstr "Inicio"
#: web/template/profile.gohtml:18 #: web/template/profile.gohtml:18
msgctxt "title" msgctxt "title"
msgid "User Access Data" msgid "User Access Data"
@ -147,6 +90,51 @@ msgctxt "action"
msgid "Save changes" msgid "Save changes"
msgstr "Guardar cambios" msgstr "Guardar cambios"
#: web/template/contacts-edit.gohtml:2 web/template/contacts-edit.gohtml:15
msgctxt "title"
msgid "Edit Contact “%s”"
msgstr "Edición del contacto «%s»"
#: web/template/contacts-edit.gohtml:10 web/template/contacts-index.gohtml:2
#: web/template/contacts-index.gohtml:9 web/template/contacts-new.gohtml:10
msgctxt "title"
msgid "Contacts"
msgstr "Contactos"
#: web/template/contacts-edit.gohtml:33
msgctxt "action"
msgid "Update contact"
msgstr "Actualizar contacto"
#: web/template/contacts-index.gohtml:13 web/template/contacts-new.gohtml:31
msgctxt "action"
msgid "New contact"
msgstr "Nuevo contacto"
#: web/template/contacts-index.gohtml:20
msgctxt "contact"
msgid "All"
msgstr "Todos"
#: web/template/contacts-index.gohtml:21
msgctxt "title"
msgid "Customer"
msgstr "Cliente"
#: web/template/contacts-index.gohtml:22
msgctxt "title"
msgid "Email"
msgstr "Correo-e"
#: web/template/contacts-index.gohtml:23
msgctxt "title"
msgid "Phone"
msgstr "Teléfono"
#: web/template/contacts-index.gohtml:38
msgid "No contacts added yet."
msgstr "No hay contactos."
#: web/template/tax-details.gohtml:2 web/template/tax-details.gohtml:9 #: web/template/tax-details.gohtml:2 web/template/tax-details.gohtml:9
#: web/template/tax-details.gohtml:13 #: web/template/tax-details.gohtml:13
msgctxt "title" msgctxt "title"
@ -182,53 +170,13 @@ msgctxt "action"
msgid "Add new tax" msgid "Add new tax"
msgstr "Añadir nuevo impuesto" msgstr "Añadir nuevo impuesto"
#: web/template/products/new.gohtml:2 web/template/products/new.gohtml:11 #: web/template/contacts-new.gohtml:2 web/template/contacts-new.gohtml:11
#: web/template/products/new.gohtml:15 #: web/template/contacts-new.gohtml:15
msgctxt "title" msgctxt "title"
msgid "New Product" msgid "New Contact"
msgstr "Nuevo producto" msgstr "Nuevo contacto"
#: web/template/products/new.gohtml:10 web/template/products/index.gohtml:2 #: pkg/login.go:36 pkg/profile.go:40 pkg/contacts.go:179
#: web/template/products/index.gohtml:9 web/template/products/edit.gohtml:10
msgctxt "title"
msgid "Products"
msgstr "Productos"
#: web/template/products/new.gohtml:25 web/template/products/index.gohtml:13
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”"
msgstr "Edición del producto «%s»"
#: web/template/products/edit.gohtml:26
msgctxt "action"
msgid "Update product"
msgstr "Actualizar producto"
#: pkg/login.go:36 pkg/profile.go:40 pkg/contacts.go:178
msgctxt "input" msgctxt "input"
msgid "Email" msgid "Email"
msgstr "Correo-e" msgstr "Correo-e"
@ -238,11 +186,11 @@ msgctxt "input"
msgid "Password" msgid "Password"
msgstr "Contraseña" msgstr "Contraseña"
#: pkg/login.go:69 pkg/profile.go:88 pkg/contacts.go:262 #: pkg/login.go:69 pkg/profile.go:88 pkg/contacts.go:263
msgid "Email can not be empty." msgid "Email can not be empty."
msgstr "No podéis dejar el correo-e en blanco." msgstr "No podéis dejar el correo-e en blanco."
#: pkg/login.go:70 pkg/profile.go:89 pkg/contacts.go:263 #: pkg/login.go:70 pkg/profile.go:89 pkg/contacts.go:264
msgid "This value is not a valid email. It should be like name@domain.com." msgid "This value is not a valid email. It should be like name@domain.com."
msgstr "Este valor no es un correo-e válido. Tiene que ser parecido a nombre@dominio.es." msgstr "Este valor no es un correo-e válido. Tiene que ser parecido a nombre@dominio.es."
@ -254,42 +202,6 @@ msgstr "No podéis dejar la contraseña en blanco."
msgid "Invalid user or password." msgid "Invalid user or password."
msgstr "Nombre de usuario o contraseña inválido." msgstr "Nombre de usuario o contraseña inválido."
#: pkg/products.go:144
msgctxt "input"
msgid "Name"
msgstr "Nombre"
#: pkg/products.go:150
msgctxt "input"
msgid "Description"
msgstr "Descripción"
#: pkg/products.go:155
msgctxt "input"
msgid "Price"
msgstr "Precio"
#: pkg/products.go:164 pkg/contacts.go:225
msgctxt "input"
msgid "Tax"
msgstr "Impuesto"
#: pkg/products.go:183 pkg/profile.go:91
msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco."
#: pkg/products.go:184
msgid "Price can not be empty."
msgstr "No podéis dejar el precio en blanco."
#: pkg/products.go:185
msgid "Price must be a number greater than zero."
msgstr "El precio tiene que ser un número mayor a cero."
#: pkg/products.go:187
msgid "Selected tax is not valid."
msgstr "Habéis escogido un impuesto que no es válido."
#: pkg/company.go:78 #: pkg/company.go:78
msgctxt "input" msgctxt "input"
msgid "Currency" msgid "Currency"
@ -341,6 +253,10 @@ msgctxt "input"
msgid "Language" msgid "Language"
msgstr "Idioma" msgstr "Idioma"
#: pkg/profile.go:91
msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco."
#: pkg/profile.go:92 #: pkg/profile.go:92
msgid "Confirmation does not match password." msgid "Confirmation does not match password."
msgstr "La confirmación no corresponde con la contraseña." msgstr "La confirmación no corresponde con la contraseña."
@ -349,103 +265,104 @@ msgstr "La confirmación no corresponde con la contraseña."
msgid "Selected language is not valid." msgid "Selected language is not valid."
msgstr "Habéis escogido un idioma que no es válido." msgstr "Habéis escogido un idioma que no es válido."
#: pkg/contacts.go:149 #: pkg/contacts.go:150
msgctxt "input" msgctxt "input"
msgid "Business name" msgid "Business name"
msgstr "Nombre y apellidos" msgstr "Nombre y apellidos"
#: pkg/contacts.go:158 #: pkg/contacts.go:159
msgctxt "input" msgctxt "input"
msgid "VAT number" msgid "VAT number"
msgstr "DNI / NIF" msgstr "DNI / NIF"
#: pkg/contacts.go:164 #: pkg/contacts.go:165
msgctxt "input" msgctxt "input"
msgid "Trade name" msgid "Trade name"
msgstr "Nombre comercial" msgstr "Nombre comercial"
#: pkg/contacts.go:169 #: pkg/contacts.go:170
msgctxt "input" msgctxt "input"
msgid "Phone" msgid "Phone"
msgstr "Teléfono" msgstr "Teléfono"
#: pkg/contacts.go:187 #: pkg/contacts.go:188
msgctxt "input" msgctxt "input"
msgid "Web" msgid "Web"
msgstr "Web" msgstr "Web"
#: pkg/contacts.go:195 #: pkg/contacts.go:196
msgctxt "input" msgctxt "input"
msgid "Address" msgid "Address"
msgstr "Dirección" msgstr "Dirección"
#: pkg/contacts.go:204 #: pkg/contacts.go:205
msgctxt "input" msgctxt "input"
msgid "City" msgid "City"
msgstr "Población" msgstr "Población"
#: pkg/contacts.go:210 #: pkg/contacts.go:211
msgctxt "input" msgctxt "input"
msgid "Province" msgid "Province"
msgstr "Provincia" msgstr "Provincia"
#: pkg/contacts.go:216 #: pkg/contacts.go:217
msgctxt "input" msgctxt "input"
msgid "Postal code" msgid "Postal code"
msgstr "Código postal" msgstr "Código postal"
#: pkg/contacts.go:255 #: pkg/contacts.go:226
msgctxt "input"
msgid "Country"
msgstr "País"
#: pkg/contacts.go:256
msgid "Business name can not be empty." msgid "Business name can not be empty."
msgstr "No podéis dejar el nombre y los apellidos en blanco." msgstr "No podéis dejar el nombre y los apellidos en blanco."
#: pkg/contacts.go:256 #: pkg/contacts.go:257
msgid "VAT number can not be empty." msgid "VAT number can not be empty."
msgstr "No podéis dejar el DNI o NIF en blanco." msgstr "No podéis dejar el DNI o NIF en blanco."
#: pkg/contacts.go:257 #: pkg/contacts.go:258
msgid "This value is not a valid VAT number." msgid "This value is not a valid VAT number."
msgstr "Este valor no es un DNI o NIF válido." msgstr "Este valor no es un DNI o NIF válido."
#: pkg/contacts.go:259 #: pkg/contacts.go:260
msgid "Phone can not be empty." msgid "Phone can not be empty."
msgstr "No podéis dejar el teléfono en blanco." msgstr "No podéis dejar el teléfono en blanco."
#: pkg/contacts.go:260 #: pkg/contacts.go:261
msgid "This value is not a valid phone number." msgid "This value is not a valid phone number."
msgstr "Este valor no es un teléfono válido." msgstr "Este valor no es un teléfono válido."
#: pkg/contacts.go:266 #: pkg/contacts.go:267
msgid "This value is not a valid web address. It should be like https://domain.com/." msgid "This value is not a valid web address. It should be like https://domain.com/."
msgstr "Este valor no es una dirección web válida. Tiene que ser parecida a https://dominio.es/." msgstr "Este valor no es una dirección web válida. Tiene que ser parecida a https://dominio.es/."
#: pkg/contacts.go:268 #: pkg/contacts.go:269
msgid "Address can not be empty." msgid "Address can not be empty."
msgstr "No podéis dejar la dirección en blanco." msgstr "No podéis dejar la dirección en blanco."
#: pkg/contacts.go:269 #: pkg/contacts.go:270
msgid "City can not be empty." msgid "City can not be empty."
msgstr "No podéis dejar la población en blanco." msgstr "No podéis dejar la población en blanco."
#: pkg/contacts.go:270 #: pkg/contacts.go:271
msgid "Province can not be empty." msgid "Province can not be empty."
msgstr "No podéis dejar la provincia en blanco." msgstr "No podéis dejar la provincia en blanco."
#: pkg/contacts.go:271 #: pkg/contacts.go:272
msgid "Postal code can not be empty." msgid "Postal code can not be empty."
msgstr "No podéis dejar el código postal en blanco." msgstr "No podéis dejar el código postal en blanco."
#: pkg/contacts.go:272 #: pkg/contacts.go:273
msgid "This value is not a valid postal code." msgid "This value is not a valid postal code."
msgstr "Este valor no es un código postal válido válido." msgstr "Este valor no es un código postal válido válido."
#: pkg/contacts.go:274 #: pkg/contacts.go:275
msgid "Selected country is not valid." msgid "Selected country is not valid."
msgstr "Habéis escogido un país que no es válido." msgstr "Habéis escogido un país que no es válido."
#~ msgctxt "input"
#~ msgid "Country"
#~ msgstr "País"
#~ msgctxt "nav" #~ msgctxt "nav"
#~ msgid "Customers" #~ msgid "Customers"
#~ msgstr "Clientes" #~ msgstr "Clientes"

View File

@ -1,7 +0,0 @@
-- Revert numerus:product from pg
begin;
drop table if exists numerus.product;
commit;

View File

@ -39,4 +39,3 @@ company_user [schema_numerus user company] 2023-01-24T17:50:06Z jordi fita mas <
tax_rate [schema_numerus] 2023-01-28T11:33:39Z jordi fita mas <jordi@tandem.blog> # Add domain for tax rates tax_rate [schema_numerus] 2023-01-28T11:33:39Z jordi fita mas <jordi@tandem.blog> # Add domain for tax rates
tax [schema_numerus company tax_rate] 2023-01-28T11:45:47Z jordi fita mas <jordi@tandem.blog> # Add relation for taxes tax [schema_numerus company tax_rate] 2023-01-28T11:45:47Z jordi fita mas <jordi@tandem.blog> # Add relation for taxes
contact [schema_numerus company extension_vat email extension_pg_libphonenumber extension_uri currency_code currency country_code country] 2023-01-29T12:59:18Z jordi fita mas <jordi@tandem.blog> # Add the relation for contacts contact [schema_numerus company extension_vat email extension_pg_libphonenumber extension_uri currency_code currency country_code country] 2023-01-29T12:59:18Z jordi fita mas <jordi@tandem.blog> # Add the relation for contacts
product [schema_numerus company] 2023-02-04T09:17:24Z jordi fita mas <jordi@tandem.blog> # Add relation for products

View File

@ -1,147 +0,0 @@
-- Test product
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(55);
set search_path to numerus, auth, public;
select has_table('product');
select has_pk('product' );
select table_privs_are('product', 'guest', array []::text[]);
select table_privs_are('product', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('product', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('product', 'authenticator', array []::text[]);
select has_sequence('product_product_id_seq');
select sequence_privs_are('product_product_id_seq', 'guest', array[]::text[]);
select sequence_privs_are('product_product_id_seq', 'invoicer', array['USAGE']);
select sequence_privs_are('product_product_id_seq', 'admin', array['USAGE']);
select sequence_privs_are('product_product_id_seq', 'authenticator', array[]::text[]);
select has_column('product', 'product_id');
select col_is_pk('product', 'product_id');
select col_type_is('product', 'product_id', 'integer');
select col_not_null('product', 'product_id');
select col_has_default('product', 'product_id');
select col_default_is('product', 'product_id', 'nextval(''product_product_id_seq''::regclass)');
select has_column('product', 'company_id');
select col_is_fk('product', 'company_id');
select fk_ok('product', 'company_id', 'company', 'company_id');
select col_type_is('product', 'company_id', 'integer');
select col_not_null('product', 'company_id');
select col_hasnt_default('product', 'company_id');
select has_column('product', 'slug');
select col_type_is('product', 'slug', 'uuid');
select col_not_null('product', 'slug');
select col_has_default('product', 'slug');
select col_default_is('product', 'slug', 'gen_random_uuid()');
select has_column('product', 'name');
select col_type_is('product', 'name', 'text');
select col_not_null('product', 'name');
select col_hasnt_default('product', 'name');
select has_column('product', 'description');
select col_type_is('product', 'description', 'text');
select col_not_null('product', 'description');
select col_hasnt_default('product', 'description');
select has_column('product', 'price');
select col_type_is('product', 'price', 'integer');
select col_not_null('product', 'price');
select col_hasnt_default('product', 'price');
select has_column('product', 'tax_id');
select col_is_fk('product', 'tax_id');
select fk_ok('product', 'tax_id', 'tax', 'tax_id');
select col_type_is('product', 'tax_id', 'integer');
select col_not_null('product', 'tax_id');
select col_hasnt_default('product', 'tax_id');
select has_column('product', 'created_at');
select col_type_is('product', 'created_at', 'timestamp with time zone');
select col_not_null('product', 'created_at');
select col_has_default('product', 'created_at');
select col_default_is('product', 'created_at', current_timestamp);
set client_min_messages to warning;
truncate product cascade;
truncate tax cascade;
truncate company_user cascade;
truncate company cascade;
truncate auth."user" cascade;
reset client_min_messages;
insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at)
values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
, (5, 'admin@tandem.blog', 'Demo', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
;
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code)
values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR')
, (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD')
;
insert into company_user (company_id, user_id)
values (2, 1)
, (4, 5)
;
insert into tax (tax_id, company_id, name, rate)
values (3, 2, 'IVA 21 %', 0.21)
, (6, 4, 'IVA 10 %', 0.10)
;
insert into product (company_id, name, description, price, tax_id)
values (2, 'Product 1', 'Description 1', 1200, 3)
, (4, 'Product 2', 'Description 2', 2400, 6)
;
prepare product_data as
select company_id, name
from product
order by company_id, name;
set role invoicer;
select is_empty('product_data', 'Should show no data when cookie is not set yet');
reset role;
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog');
select bag_eq(
'product_data',
$$ values (2, 'Product 1')
$$,
'Should only list products of the companies where demo@tandem.blog is user of'
);
reset role;
select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog');
select bag_eq(
'product_data',
$$ values (4, 'Product 2')
$$,
'Should only list products of the companies where admin@tandem.blog is user of'
);
reset role;
select set_cookie('not-a-cookie');
select throws_ok(
'product_data',
'42501', 'permission denied for table product',
'Should not allow select to guest users'
);
reset role;
select *
from finish();
rollback;

View File

@ -1,19 +0,0 @@
-- Verify numerus:product on pg
begin;
select product_id
, company_id
, slug
, name
, description
, price
, tax_id
, created_at
from numerus.product
where false;
select 1 / count(*) from pg_class where oid = 'numerus.product'::regclass and relrowsecurity;
select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.product'::regclass;
rollback;

View File

@ -41,7 +41,6 @@
<nav aria-label="{{( pgettext "Main" "title" )}}"> <nav aria-label="{{( pgettext "Main" "title" )}}">
<ul> <ul>
<li><a href="{{ companyURI "/" }}">{{( pgettext "Dashboard" "nav" )}}</a></li> <li><a href="{{ companyURI "/" }}">{{( pgettext "Dashboard" "nav" )}}</a></li>
<li><a href="{{ companyURI "/products" }}">{{( pgettext "Products" "nav" )}}</a></li>
<li><a href="{{ companyURI "/contacts" }}">{{( pgettext "Contacts" "nav" )}}</a></li> <li><a href="{{ companyURI "/contacts" }}">{{( pgettext "Contacts" "nav" )}}</a></li>
</ul> </ul>
</nav> </nav>

View File

@ -17,7 +17,7 @@
{{ csrfToken }} {{ csrfToken }}
{{ putMethod }} {{ putMethod }}
{{ template "input-field" .BusinessName }} {{ template "input-field" .BusinessName | addInputAttr "autofocus" }}
{{ template "input-field" .VATIN }} {{ template "input-field" .VATIN }}
{{ template "input-field" .TradeName }} {{ template "input-field" .TradeName }}
{{ template "input-field" .Phone }} {{ template "input-field" .Phone }}

View File

@ -14,7 +14,6 @@
</p> </p>
</nav> </nav>
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.ContactsIndexPage*/ -}}
<table> <table>
<thead> <thead>
<tr> <tr>
@ -26,7 +25,7 @@
</thead> </thead>
<tbody> <tbody>
{{ with .Contacts }} {{ with .Contacts }}
{{- range $contact := . }} {{- range $tax := . }}
<tr> <tr>
<td></td> <td></td>
<td><a href="{{ companyURI "/contacts/"}}{{ .Slug }}">{{ .Name }}</a></td> <td><a href="{{ companyURI "/contacts/"}}{{ .Slug }}">{{ .Name }}</a></td>

View File

@ -1,30 +0,0 @@
{{ define "title" -}}
{{printf (pgettext "Edit Product “%s”" "title") .Name.Val }}
{{- end }}
{{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.productForm*/ -}}
<nav>
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a href="{{ companyURI "/products"}}">{{( pgettext "Products" "title" )}}</a> /
<a>{{ .Name.Val }}</a>
</p>
</nav>
<section class="dialog-content">
<h2>{{printf (pgettext "Edit Product “%s”" "title") .Name.Val }}</h2>
<form method="POST">
{{ csrfToken }}
{{ putMethod }}
{{ template "input-field" .Name | addInputAttr "autofocus" }}
{{ template "input-field" .Description }}
{{ template "input-field" .Price }}
{{ template "select-field" .Tax }}
<fieldset>
<button class="primary" type="submit">{{( pgettext "Update product" "action" )}}</button>
</fieldset>
</form>
</section>
{{- end }}

View File

@ -1,42 +0,0 @@
{{ define "title" -}}
{{( pgettext "Products" "title" )}}
{{- end }}
{{ define "content" }}
<nav>
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a>{{( pgettext "Products" "title" )}}</a>
</p>
<p>
<a class="primary button"
href="{{ companyURI "/products/new" }}">{{( pgettext "New product" "action" )}}</a>
</p>
</nav>
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.productsIndexPage*/ -}}
<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 := . }}
<tr>
<td></td>
<td><a href="{{ companyURI "/products/"}}{{ .Slug }}">{{ .Name }}</a></td>
<td>{{ .Price }}</td>
</tr>
{{- end }}
{{ else }}
<tr>
<td colspan="4">{{( gettext "No products added yet." )}}</td>
</tr>
{{ end }}
</tbody>
</table>
{{- end }}

View File

@ -1,30 +0,0 @@
{{ define "title" -}}
{{( pgettext "New Product" "title" )}}
{{- end }}
{{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.productForm*/ -}}
<nav>
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a href="{{ companyURI "/products"}}">{{( pgettext "Products" "title" )}}</a> /
<a>{{( pgettext "New Product" "title" )}}</a>
</p>
</nav>
<section class="dialog-content">
<h2>{{(pgettext "New Product" "title")}}</h2>
<form method="POST" action="{{ companyURI "/products" }}">
{{ csrfToken }}
{{ template "input-field" .Name | addInputAttr "autofocus" }}
{{ template "input-field" .Description }}
{{ template "input-field" .Price }}
{{ template "select-field" .Tax }}
<fieldset>
<button class="primary" type="submit">{{( pgettext "New product" "action" )}}</button>
</fieldset>
</form>
</section>
{{- end }}