Compare commits
No commits in common. "e9cc331ee06cb80ac1610cdb95ede11e445ab058" and "55980367bc0abad4fd0430fe8de865a5f871dd8d" have entirely different histories.
e9cc331ee0
...
55980367bc
|
@ -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;
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{
|
||||||
|
|
189
pkg/products.go
189
pkg/products.go
|
@ -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()
|
|
||||||
}
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
265
po/ca.po
|
@ -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 d’usuari o contrasenya incorrectes."
|
msgstr "Nom d’usuari 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 l’adreça en blanc."
|
msgstr "No podeu deixar l’adreç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
263
po/es.po
|
@ -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"
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
-- Revert numerus:product from pg
|
|
||||||
|
|
||||||
begin;
|
|
||||||
|
|
||||||
drop table if exists numerus.product;
|
|
||||||
|
|
||||||
commit;
|
|
|
@ -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
|
|
||||||
|
|
147
test/product.sql
147
test/product.sql
|
@ -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;
|
|
||||||
|
|
|
@ -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;
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 }}
|
|
@ -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>
|
|
@ -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 }}
|
|
|
@ -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 }}
|
|
|
@ -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 }}
|
|
Loading…
Reference in New Issue