Compare commits
3 Commits
55980367bc
...
e9cc331ee0
Author | SHA1 | Date |
---|---|---|
jordi fita mas | e9cc331ee0 | |
jordi fita mas | f611162b0e | |
jordi fita mas | 2799fdb8db |
|
@ -0,0 +1,42 @@
|
|||
-- 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)
|
||||
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, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
|
||||
http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -260,9 +260,8 @@ func HandleDeleteCompanyTax(w http.ResponseWriter, r *http.Request, params httpr
|
|||
return
|
||||
}
|
||||
conn := getConn(r)
|
||||
company := mustGetCompany(r)
|
||||
conn.MustExec(r.Context(), "delete from tax where tax_id = $1", taxId)
|
||||
http.Redirect(w, r, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
|
||||
http.Redirect(w, r, companyURI(mustGetCompany(r), "/tax-details"), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
|
@ -284,5 +283,5 @@ func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Pa
|
|||
conn := getConn(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())
|
||||
http.Redirect(w, r, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
|
||||
http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther)
|
||||
}
|
||||
|
|
|
@ -21,11 +21,11 @@ type ContactsIndexPage struct {
|
|||
|
||||
func IndexContacts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
conn := getConn(r)
|
||||
company := getCompany(r)
|
||||
company := mustGetCompany(r)
|
||||
page := &ContactsIndexPage{
|
||||
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) {
|
||||
|
@ -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) {
|
||||
mustRenderAppTemplate(w, r, "contacts-new.gohtml", form)
|
||||
mustRenderAppTemplate(w, r, "contacts/new.gohtml", form)
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -75,9 +75,9 @@ func HandleAddContact(w http.ResponseWriter, r *http.Request, _ httprouter.Param
|
|||
mustRenderNewContactForm(w, r, form)
|
||||
return
|
||||
}
|
||||
company := getCompany(r)
|
||||
company := mustGetCompany(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)
|
||||
http.Redirect(w, r, "/company/"+company.Slug+"/contacts", http.StatusSeeOther)
|
||||
http.Redirect(w, r, companyURI(company, "/contacts"), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func HandleUpdateContact(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
|
@ -100,8 +100,7 @@ func HandleUpdateContact(w http.ResponseWriter, r *http.Request, params httprout
|
|||
if slug == "" {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
company := getCompany(r)
|
||||
http.Redirect(w, r, "/company/"+company.Slug+"/contacts/"+slug, http.StatusSeeOther)
|
||||
http.Redirect(w, r, companyURI(mustGetCompany(r), "/contacts/"+slug), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func mustGetContactEntries(ctx context.Context, conn *Conn, company *Company) []*ContactEntry {
|
||||
|
@ -223,7 +222,7 @@ func newContactForm(ctx context.Context, conn *Conn, locale *Locale) *contactFor
|
|||
},
|
||||
Country: &SelectField{
|
||||
Name: "country",
|
||||
Label: pgettext("input", "Country", locale),
|
||||
Label: pgettext("input", "Tax", locale),
|
||||
Options: mustGetCountryOptions(ctx, conn, locale),
|
||||
Selected: "ES",
|
||||
Attributes: []template.HTMLAttr{
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
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 != "" {
|
||||
conn.MustExec(r.Context(), "select change_password($1)", form.Password)
|
||||
}
|
||||
company := getCompany(r)
|
||||
http.Redirect(w, r, "/company/"+company.Slug+"/profile", http.StatusSeeOther)
|
||||
company := mustGetCompany(r)
|
||||
http.Redirect(w, r, companyURI(company, "/profile"), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func mustRenderProfileForm(w http.ResponseWriter, r *http.Request, form *profileForm) {
|
||||
|
|
|
@ -18,6 +18,10 @@ func NewRouter(db *Db) http.Handler {
|
|||
companyRouter.POST("/contacts", HandleAddContact)
|
||||
companyRouter.GET("/contacts/:slug", GetContactForm)
|
||||
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) {
|
||||
mustRenderAppTemplate(w, r, "dashboard.gohtml", nil)
|
||||
})
|
||||
|
@ -38,8 +42,10 @@ func NewRouter(db *Db) http.Handler {
|
|||
user := getUser(r)
|
||||
if user.LoggedIn {
|
||||
conn := getConn(r)
|
||||
slug := conn.MustGetText(r.Context(), "", "select slug::text from company order by company_id limit 1")
|
||||
http.Redirect(w, r, "/company/"+slug+"/", http.StatusFound)
|
||||
company := &Company{
|
||||
Slug: conn.MustGetText(r.Context(), "", "select slug::text from company order by company_id limit 1"),
|
||||
}
|
||||
http.Redirect(w, r, companyURI(company, "/"), http.StatusFound)
|
||||
} else {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
}
|
||||
|
|
|
@ -25,10 +25,7 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s
|
|||
return locale.Language.String()
|
||||
},
|
||||
"companyURI": func(uri string) string {
|
||||
if company == nil {
|
||||
return uri
|
||||
}
|
||||
return "/company/" + company.Slug + uri
|
||||
return companyURI(company, uri)
|
||||
},
|
||||
"csrfToken": func() template.HTML {
|
||||
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, csrfTokenField, user.CsrfToken))
|
||||
|
@ -56,6 +53,13 @@ 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 {
|
||||
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 ""
|
||||
"Project-Id-Version: numerus\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2023-02-03 13:57+0100\n"
|
||||
"POT-Creation-Date: 2023-02-04 11:24+0100\n"
|
||||
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||
|
@ -44,9 +44,73 @@ msgstr "Tauler"
|
|||
|
||||
#: web/template/app.gohtml:44
|
||||
msgctxt "nav"
|
||||
msgid "Products"
|
||||
msgstr "Productes"
|
||||
|
||||
#: web/template/app.gohtml:45
|
||||
msgctxt "nav"
|
||||
msgid "Contacts"
|
||||
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
|
||||
msgctxt "title"
|
||||
msgid "Login"
|
||||
|
@ -63,13 +127,6 @@ msgctxt "title"
|
|||
msgid "User Settings"
|
||||
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
|
||||
msgctxt "title"
|
||||
msgid "User Access Data"
|
||||
|
@ -90,51 +147,6 @@ msgctxt "action"
|
|||
msgid "Save changes"
|
||||
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:13
|
||||
msgctxt "title"
|
||||
|
@ -149,7 +161,7 @@ msgstr "Moneda"
|
|||
#: web/template/tax-details.gohtml:47
|
||||
msgctxt "title"
|
||||
msgid "Tax Name"
|
||||
msgstr "Nom import"
|
||||
msgstr "Nom impost"
|
||||
|
||||
#: web/template/tax-details.gohtml:48
|
||||
msgctxt "title"
|
||||
|
@ -170,13 +182,53 @@ msgctxt "action"
|
|||
msgid "Add new tax"
|
||||
msgstr "Afegeix nou impost"
|
||||
|
||||
#: web/template/contacts-new.gohtml:2 web/template/contacts-new.gohtml:11
|
||||
#: web/template/contacts-new.gohtml:15
|
||||
#: web/template/products/new.gohtml:2 web/template/products/new.gohtml:11
|
||||
#: web/template/products/new.gohtml:15
|
||||
msgctxt "title"
|
||||
msgid "New Contact"
|
||||
msgstr "Nou contacte"
|
||||
msgid "New Product"
|
||||
msgstr "Nou producte"
|
||||
|
||||
#: pkg/login.go:36 pkg/profile.go:40 pkg/contacts.go:179
|
||||
#: web/template/products/new.gohtml:10 web/template/products/index.gohtml:2
|
||||
#: 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"
|
||||
msgid "Email"
|
||||
msgstr "Correu-e"
|
||||
|
@ -186,11 +238,11 @@ msgctxt "input"
|
|||
msgid "Password"
|
||||
msgstr "Contrasenya"
|
||||
|
||||
#: pkg/login.go:69 pkg/profile.go:88 pkg/contacts.go:263
|
||||
#: pkg/login.go:69 pkg/profile.go:88 pkg/contacts.go:262
|
||||
msgid "Email can not be empty."
|
||||
msgstr "No podeu deixar el correu-e en blanc."
|
||||
|
||||
#: pkg/login.go:70 pkg/profile.go:89 pkg/contacts.go:264
|
||||
#: pkg/login.go:70 pkg/profile.go:89 pkg/contacts.go:263
|
||||
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."
|
||||
|
||||
|
@ -202,6 +254,42 @@ msgstr "No podeu deixar la contrasenya en blanc."
|
|||
msgid "Invalid user or password."
|
||||
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
|
||||
msgctxt "input"
|
||||
msgid "Currency"
|
||||
|
@ -253,10 +341,6 @@ msgctxt "input"
|
|||
msgid "Language"
|
||||
msgstr "Idioma"
|
||||
|
||||
#: pkg/profile.go:91
|
||||
msgid "Name can not be empty."
|
||||
msgstr "No podeu deixar el nom en blanc."
|
||||
|
||||
#: pkg/profile.go:92
|
||||
msgid "Confirmation does not match password."
|
||||
msgstr "La confirmació no és igual a la contrasenya."
|
||||
|
@ -265,104 +349,103 @@ msgstr "La confirmació no és igual a la contrasenya."
|
|||
msgid "Selected language is not valid."
|
||||
msgstr "Heu seleccionat un idioma que no és vàlid."
|
||||
|
||||
#: pkg/contacts.go:150
|
||||
#: pkg/contacts.go:149
|
||||
msgctxt "input"
|
||||
msgid "Business name"
|
||||
msgstr "Nom i cognoms"
|
||||
|
||||
#: pkg/contacts.go:159
|
||||
#: pkg/contacts.go:158
|
||||
msgctxt "input"
|
||||
msgid "VAT number"
|
||||
msgstr "DNI / NIF"
|
||||
|
||||
#: pkg/contacts.go:165
|
||||
#: pkg/contacts.go:164
|
||||
msgctxt "input"
|
||||
msgid "Trade name"
|
||||
msgstr "Nom comercial"
|
||||
|
||||
#: pkg/contacts.go:170
|
||||
#: pkg/contacts.go:169
|
||||
msgctxt "input"
|
||||
msgid "Phone"
|
||||
msgstr "Telèfon"
|
||||
|
||||
#: pkg/contacts.go:188
|
||||
#: pkg/contacts.go:187
|
||||
msgctxt "input"
|
||||
msgid "Web"
|
||||
msgstr "Web"
|
||||
|
||||
#: pkg/contacts.go:196
|
||||
#: pkg/contacts.go:195
|
||||
msgctxt "input"
|
||||
msgid "Address"
|
||||
msgstr "Adreça"
|
||||
|
||||
#: pkg/contacts.go:205
|
||||
#: pkg/contacts.go:204
|
||||
msgctxt "input"
|
||||
msgid "City"
|
||||
msgstr "Població"
|
||||
|
||||
#: pkg/contacts.go:211
|
||||
#: pkg/contacts.go:210
|
||||
msgctxt "input"
|
||||
msgid "Province"
|
||||
msgstr "Província"
|
||||
|
||||
#: pkg/contacts.go:217
|
||||
#: pkg/contacts.go:216
|
||||
msgctxt "input"
|
||||
msgid "Postal code"
|
||||
msgstr "Codi postal"
|
||||
|
||||
#: pkg/contacts.go:226
|
||||
msgctxt "input"
|
||||
msgid "Country"
|
||||
msgstr "País"
|
||||
|
||||
#: pkg/contacts.go:256
|
||||
#: pkg/contacts.go:255
|
||||
msgid "Business name can not be empty."
|
||||
msgstr "No podeu deixar el nom i els cognoms en blanc."
|
||||
|
||||
#: pkg/contacts.go:257
|
||||
#: pkg/contacts.go:256
|
||||
msgid "VAT number can not be empty."
|
||||
msgstr "No podeu deixar el DNI o NIF en blanc."
|
||||
|
||||
#: pkg/contacts.go:258
|
||||
#: pkg/contacts.go:257
|
||||
msgid "This value is not a valid VAT number."
|
||||
msgstr "Aquest valor no és un DNI o NIF vàlid."
|
||||
|
||||
#: pkg/contacts.go:260
|
||||
#: pkg/contacts.go:259
|
||||
msgid "Phone can not be empty."
|
||||
msgstr "No podeu deixar el telèfon en blanc."
|
||||
|
||||
#: pkg/contacts.go:261
|
||||
#: pkg/contacts.go:260
|
||||
msgid "This value is not a valid phone number."
|
||||
msgstr "Aquest valor no és un telèfon vàlid."
|
||||
|
||||
#: pkg/contacts.go:267
|
||||
#: pkg/contacts.go:266
|
||||
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/."
|
||||
|
||||
#: pkg/contacts.go:269
|
||||
#: pkg/contacts.go:268
|
||||
msgid "Address can not be empty."
|
||||
msgstr "No podeu deixar l’adreça en blanc."
|
||||
|
||||
#: pkg/contacts.go:270
|
||||
#: pkg/contacts.go:269
|
||||
msgid "City can not be empty."
|
||||
msgstr "No podeu deixar la població en blanc."
|
||||
|
||||
#: pkg/contacts.go:271
|
||||
#: pkg/contacts.go:270
|
||||
msgid "Province can not be empty."
|
||||
msgstr "No podeu deixar la província en blanc."
|
||||
|
||||
#: pkg/contacts.go:272
|
||||
#: pkg/contacts.go:271
|
||||
msgid "Postal code can not be empty."
|
||||
msgstr "No podeu deixar el codi postal en blanc."
|
||||
|
||||
#: pkg/contacts.go:273
|
||||
#: pkg/contacts.go:272
|
||||
msgid "This value is not a valid postal code."
|
||||
msgstr "Aquest valor no és un codi postal vàlid."
|
||||
|
||||
#: pkg/contacts.go:275
|
||||
#: pkg/contacts.go:274
|
||||
msgid "Selected country is not valid."
|
||||
msgstr "Heu seleccionat un país que no és vàlid."
|
||||
|
||||
#~ msgctxt "input"
|
||||
#~ msgid "Country"
|
||||
#~ msgstr "País"
|
||||
|
||||
#~ msgctxt "nav"
|
||||
#~ msgid "Customers"
|
||||
#~ msgstr "Clients"
|
||||
|
|
263
po/es.po
263
po/es.po
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: numerus\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2023-02-03 13:57+0100\n"
|
||||
"POT-Creation-Date: 2023-02-04 11:24+0100\n"
|
||||
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||
|
@ -44,9 +44,73 @@ msgstr "Panel"
|
|||
|
||||
#: web/template/app.gohtml:44
|
||||
msgctxt "nav"
|
||||
msgid "Products"
|
||||
msgstr "Productos"
|
||||
|
||||
#: web/template/app.gohtml:45
|
||||
msgctxt "nav"
|
||||
msgid "Contacts"
|
||||
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
|
||||
msgctxt "title"
|
||||
msgid "Login"
|
||||
|
@ -63,13 +127,6 @@ msgctxt "title"
|
|||
msgid "User Settings"
|
||||
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
|
||||
msgctxt "title"
|
||||
msgid "User Access Data"
|
||||
|
@ -90,51 +147,6 @@ msgctxt "action"
|
|||
msgid "Save changes"
|
||||
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:13
|
||||
msgctxt "title"
|
||||
|
@ -170,13 +182,53 @@ msgctxt "action"
|
|||
msgid "Add new tax"
|
||||
msgstr "Añadir nuevo impuesto"
|
||||
|
||||
#: web/template/contacts-new.gohtml:2 web/template/contacts-new.gohtml:11
|
||||
#: web/template/contacts-new.gohtml:15
|
||||
#: web/template/products/new.gohtml:2 web/template/products/new.gohtml:11
|
||||
#: web/template/products/new.gohtml:15
|
||||
msgctxt "title"
|
||||
msgid "New Contact"
|
||||
msgstr "Nuevo contacto"
|
||||
msgid "New Product"
|
||||
msgstr "Nuevo producto"
|
||||
|
||||
#: pkg/login.go:36 pkg/profile.go:40 pkg/contacts.go:179
|
||||
#: web/template/products/new.gohtml:10 web/template/products/index.gohtml:2
|
||||
#: 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"
|
||||
msgid "Email"
|
||||
msgstr "Correo-e"
|
||||
|
@ -186,11 +238,11 @@ msgctxt "input"
|
|||
msgid "Password"
|
||||
msgstr "Contraseña"
|
||||
|
||||
#: pkg/login.go:69 pkg/profile.go:88 pkg/contacts.go:263
|
||||
#: pkg/login.go:69 pkg/profile.go:88 pkg/contacts.go:262
|
||||
msgid "Email can not be empty."
|
||||
msgstr "No podéis dejar el correo-e en blanco."
|
||||
|
||||
#: pkg/login.go:70 pkg/profile.go:89 pkg/contacts.go:264
|
||||
#: pkg/login.go:70 pkg/profile.go:89 pkg/contacts.go:263
|
||||
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."
|
||||
|
||||
|
@ -202,6 +254,42 @@ msgstr "No podéis dejar la contraseña en blanco."
|
|||
msgid "Invalid user or password."
|
||||
msgstr "Nombre de usuario o contraseña inválido."
|
||||
|
||||
#: pkg/products.go: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
|
||||
msgctxt "input"
|
||||
msgid "Currency"
|
||||
|
@ -253,10 +341,6 @@ msgctxt "input"
|
|||
msgid "Language"
|
||||
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
|
||||
msgid "Confirmation does not match password."
|
||||
msgstr "La confirmación no corresponde con la contraseña."
|
||||
|
@ -265,104 +349,103 @@ msgstr "La confirmación no corresponde con la contraseña."
|
|||
msgid "Selected language is not valid."
|
||||
msgstr "Habéis escogido un idioma que no es válido."
|
||||
|
||||
#: pkg/contacts.go:150
|
||||
#: pkg/contacts.go:149
|
||||
msgctxt "input"
|
||||
msgid "Business name"
|
||||
msgstr "Nombre y apellidos"
|
||||
|
||||
#: pkg/contacts.go:159
|
||||
#: pkg/contacts.go:158
|
||||
msgctxt "input"
|
||||
msgid "VAT number"
|
||||
msgstr "DNI / NIF"
|
||||
|
||||
#: pkg/contacts.go:165
|
||||
#: pkg/contacts.go:164
|
||||
msgctxt "input"
|
||||
msgid "Trade name"
|
||||
msgstr "Nombre comercial"
|
||||
|
||||
#: pkg/contacts.go:170
|
||||
#: pkg/contacts.go:169
|
||||
msgctxt "input"
|
||||
msgid "Phone"
|
||||
msgstr "Teléfono"
|
||||
|
||||
#: pkg/contacts.go:188
|
||||
#: pkg/contacts.go:187
|
||||
msgctxt "input"
|
||||
msgid "Web"
|
||||
msgstr "Web"
|
||||
|
||||
#: pkg/contacts.go:196
|
||||
#: pkg/contacts.go:195
|
||||
msgctxt "input"
|
||||
msgid "Address"
|
||||
msgstr "Dirección"
|
||||
|
||||
#: pkg/contacts.go:205
|
||||
#: pkg/contacts.go:204
|
||||
msgctxt "input"
|
||||
msgid "City"
|
||||
msgstr "Población"
|
||||
|
||||
#: pkg/contacts.go:211
|
||||
#: pkg/contacts.go:210
|
||||
msgctxt "input"
|
||||
msgid "Province"
|
||||
msgstr "Provincia"
|
||||
|
||||
#: pkg/contacts.go:217
|
||||
#: pkg/contacts.go:216
|
||||
msgctxt "input"
|
||||
msgid "Postal code"
|
||||
msgstr "Código postal"
|
||||
|
||||
#: pkg/contacts.go:226
|
||||
msgctxt "input"
|
||||
msgid "Country"
|
||||
msgstr "País"
|
||||
|
||||
#: pkg/contacts.go:256
|
||||
#: pkg/contacts.go:255
|
||||
msgid "Business name can not be empty."
|
||||
msgstr "No podéis dejar el nombre y los apellidos en blanco."
|
||||
|
||||
#: pkg/contacts.go:257
|
||||
#: pkg/contacts.go:256
|
||||
msgid "VAT number can not be empty."
|
||||
msgstr "No podéis dejar el DNI o NIF en blanco."
|
||||
|
||||
#: pkg/contacts.go:258
|
||||
#: pkg/contacts.go:257
|
||||
msgid "This value is not a valid VAT number."
|
||||
msgstr "Este valor no es un DNI o NIF válido."
|
||||
|
||||
#: pkg/contacts.go:260
|
||||
#: pkg/contacts.go:259
|
||||
msgid "Phone can not be empty."
|
||||
msgstr "No podéis dejar el teléfono en blanco."
|
||||
|
||||
#: pkg/contacts.go:261
|
||||
#: pkg/contacts.go:260
|
||||
msgid "This value is not a valid phone number."
|
||||
msgstr "Este valor no es un teléfono válido."
|
||||
|
||||
#: pkg/contacts.go:267
|
||||
#: pkg/contacts.go:266
|
||||
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/."
|
||||
|
||||
#: pkg/contacts.go:269
|
||||
#: pkg/contacts.go:268
|
||||
msgid "Address can not be empty."
|
||||
msgstr "No podéis dejar la dirección en blanco."
|
||||
|
||||
#: pkg/contacts.go:270
|
||||
#: pkg/contacts.go:269
|
||||
msgid "City can not be empty."
|
||||
msgstr "No podéis dejar la población en blanco."
|
||||
|
||||
#: pkg/contacts.go:271
|
||||
#: pkg/contacts.go:270
|
||||
msgid "Province can not be empty."
|
||||
msgstr "No podéis dejar la provincia en blanco."
|
||||
|
||||
#: pkg/contacts.go:272
|
||||
#: pkg/contacts.go:271
|
||||
msgid "Postal code can not be empty."
|
||||
msgstr "No podéis dejar el código postal en blanco."
|
||||
|
||||
#: pkg/contacts.go:273
|
||||
#: pkg/contacts.go:272
|
||||
msgid "This value is not a valid postal code."
|
||||
msgstr "Este valor no es un código postal válido válido."
|
||||
|
||||
#: pkg/contacts.go:275
|
||||
#: pkg/contacts.go:274
|
||||
msgid "Selected country is not valid."
|
||||
msgstr "Habéis escogido un país que no es válido."
|
||||
|
||||
#~ msgctxt "input"
|
||||
#~ msgid "Country"
|
||||
#~ msgstr "País"
|
||||
|
||||
#~ msgctxt "nav"
|
||||
#~ msgid "Customers"
|
||||
#~ msgstr "Clientes"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:product from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop table if exists numerus.product;
|
||||
|
||||
commit;
|
|
@ -39,3 +39,4 @@ 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 [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
|
||||
product [schema_numerus company] 2023-02-04T09:17:24Z jordi fita mas <jordi@tandem.blog> # Add relation for products
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
-- 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;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
-- 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,6 +41,7 @@
|
|||
<nav aria-label="{{( pgettext "Main" "title" )}}">
|
||||
<ul>
|
||||
<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>
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
{{ csrfToken }}
|
||||
{{ putMethod }}
|
||||
|
||||
{{ template "input-field" .BusinessName | addInputAttr "autofocus" }}
|
||||
{{ template "input-field" .BusinessName }}
|
||||
{{ template "input-field" .VATIN }}
|
||||
{{ template "input-field" .TradeName }}
|
||||
{{ template "input-field" .Phone }}
|
|
@ -14,6 +14,7 @@
|
|||
</p>
|
||||
</nav>
|
||||
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.ContactsIndexPage*/ -}}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -25,7 +26,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{{ with .Contacts }}
|
||||
{{- range $tax := . }}
|
||||
{{- range $contact := . }}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><a href="{{ companyURI "/contacts/"}}{{ .Slug }}">{{ .Name }}</a></td>
|
|
@ -0,0 +1,30 @@
|
|||
{{ 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 }}
|
|
@ -0,0 +1,42 @@
|
|||
{{ 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 }}
|
|
@ -0,0 +1,30 @@
|
|||
{{ 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