Compare commits
2 Commits
e9cc331ee0
...
ae1949024b
Author | SHA1 | Date |
---|---|---|
jordi fita mas | ae1949024b | |
jordi fita mas | 60f9792e58 |
|
@ -0,0 +1,47 @@
|
||||||
|
-- Deploy numerus:parse_price to pg
|
||||||
|
-- requires: schema_public
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
create or replace function parse_price(price text, decimal_digits integer) returns integer as
|
||||||
|
$$
|
||||||
|
declare
|
||||||
|
parts text[];
|
||||||
|
result int;
|
||||||
|
frac_part text;
|
||||||
|
begin
|
||||||
|
parts := string_to_array(price, '.');
|
||||||
|
if array_length(parts, 1) > 2 then
|
||||||
|
raise invalid_parameter_value using message = price || ' is not a valid price representation.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
result := parts[1]::integer;
|
||||||
|
for d in 1..decimal_digits loop
|
||||||
|
result := result * 10;
|
||||||
|
end loop;
|
||||||
|
|
||||||
|
if array_length(parts, 1) = 2 then
|
||||||
|
frac_part := rtrim(parts[2], '0');
|
||||||
|
if length(frac_part) > decimal_digits then
|
||||||
|
raise invalid_parameter_value using message = price || ' has too many digits in the fraction part.';
|
||||||
|
end if;
|
||||||
|
frac_part := rpad(frac_part, decimal_digits, '0');
|
||||||
|
result := result + frac_part::integer;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
end;
|
||||||
|
$$
|
||||||
|
language plpgsql
|
||||||
|
immutable;
|
||||||
|
|
||||||
|
comment on function parse_price(text, integer) is
|
||||||
|
'Converts the string representation of a price in decimal form to cents, according to the number of decimal digits passed.';
|
||||||
|
|
||||||
|
revoke execute on function parse_price(text, integer) from public;
|
||||||
|
grant execute on function parse_price(text, integer) to invoicer;
|
||||||
|
grant execute on function parse_price(text, integer) to admin;
|
||||||
|
|
||||||
|
commit;
|
|
@ -0,0 +1,28 @@
|
||||||
|
-- Deploy numerus:to_price to pg
|
||||||
|
-- requires: schema_numerus
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
create or replace function to_price(cents integer, decimal_digits integer) returns text as
|
||||||
|
$$
|
||||||
|
declare
|
||||||
|
result text;
|
||||||
|
scale integer := 10^decimal_digits;
|
||||||
|
begin
|
||||||
|
result = cents::text;
|
||||||
|
return (cents / scale)::text || '.' || to_char(mod(cents, scale), rpad('FM', decimal_digits + 2, '0'));
|
||||||
|
end;
|
||||||
|
$$
|
||||||
|
language plpgsql
|
||||||
|
immutable;
|
||||||
|
|
||||||
|
comment on function to_price(integer, integer) is
|
||||||
|
'Converts the cents to a price representation, without currency and any other separater than decimal.';
|
||||||
|
|
||||||
|
revoke execute on function to_price(integer, integer) from public;
|
||||||
|
grant execute on function to_price(integer, integer) to invoicer;
|
||||||
|
grant execute on function to_price(integer, integer) to admin;
|
||||||
|
|
||||||
|
commit;
|
|
@ -16,6 +16,8 @@ const (
|
||||||
|
|
||||||
type Company struct {
|
type Company struct {
|
||||||
Id int
|
Id int
|
||||||
|
CurrencySymbol string
|
||||||
|
DecimalDigits int
|
||||||
Slug string
|
Slug string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +27,7 @@ func CompanyHandler(next http.Handler) httprouter.Handle {
|
||||||
Slug: params[0].Value,
|
Slug: params[0].Value,
|
||||||
}
|
}
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
err := conn.QueryRow(r.Context(), "select company_id from company where slug = $1", company.Slug).Scan(&company.Id)
|
err := conn.QueryRow(r.Context(), "select company_id, currency_symbol, decimal_digits from company join currency using (currency_code) where slug = $1", company.Slug).Scan(&company.Id, &company.CurrencySymbol, &company.DecimalDigits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
|
@ -41,6 +43,15 @@ func CompanyHandler(next http.Handler) httprouter.Handle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Company) MinCents() float64 {
|
||||||
|
var r float64
|
||||||
|
r = 1
|
||||||
|
for i := 0; i < c.DecimalDigits; i++ {
|
||||||
|
r /= 10.0
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func getCompany(r *http.Request) *Company {
|
func getCompany(r *http.Request) *Company {
|
||||||
company := r.Context().Value(ContextCompanyKey)
|
company := r.Context().Value(ContextCompanyKey)
|
||||||
if company == nil {
|
if company == nil {
|
||||||
|
@ -77,6 +88,7 @@ func newTaxDetailsForm(ctx context.Context, conn *Conn, locale *Locale) *taxDeta
|
||||||
Name: "currency",
|
Name: "currency",
|
||||||
Label: pgettext("input", "Currency", locale),
|
Label: pgettext("input", "Currency", locale),
|
||||||
Options: MustGetOptions(ctx, conn, "select currency_code, currency_symbol from currency order by currency_code"),
|
Options: MustGetOptions(ctx, conn, "select currency_code, currency_symbol from currency order by currency_code"),
|
||||||
|
Required: true,
|
||||||
Selected: "EUR",
|
Selected: "EUR",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,6 +224,7 @@ func newContactForm(ctx context.Context, conn *Conn, locale *Locale) *contactFor
|
||||||
Name: "country",
|
Name: "country",
|
||||||
Label: pgettext("input", "Tax", locale),
|
Label: pgettext("input", "Tax", locale),
|
||||||
Options: mustGetCountryOptions(ctx, conn, locale),
|
Options: mustGetCountryOptions(ctx, conn, locale),
|
||||||
|
Required: true,
|
||||||
Selected: "ES",
|
Selected: "ES",
|
||||||
Attributes: []template.HTMLAttr{
|
Attributes: []template.HTMLAttr{
|
||||||
`autocomplete="country"`,
|
`autocomplete="country"`,
|
||||||
|
|
|
@ -61,6 +61,8 @@ type SelectField struct {
|
||||||
Selected string
|
Selected string
|
||||||
Options []*SelectOption
|
Options []*SelectOption
|
||||||
Attributes []template.HTMLAttr
|
Attributes []template.HTMLAttr
|
||||||
|
Required bool
|
||||||
|
EmptyLabel string
|
||||||
Errors []error
|
Errors []error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +177,11 @@ func (v *FormValidator) CheckValidInteger(field *InputField, min int, max int, m
|
||||||
return v.checkInput(field, err == nil && value >= min && value <= max, message)
|
return v.checkInput(field, err == nil && value >= min && value <= max, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *FormValidator) CheckValidDecimal(field *InputField, min float64, max float64, message string) bool {
|
||||||
|
value, err := strconv.ParseFloat(field.Val, 64)
|
||||||
|
return v.checkInput(field, err == nil && value >= min && value <= max, message)
|
||||||
|
}
|
||||||
|
|
||||||
func (v *FormValidator) checkInput(field *InputField, ok bool, message string) bool {
|
func (v *FormValidator) checkInput(field *InputField, ok bool, message string) bool {
|
||||||
if !ok {
|
if !ok {
|
||||||
field.Errors = append(field.Errors, errors.New(message))
|
field.Errors = append(field.Errors, errors.New(message))
|
||||||
|
|
|
@ -2,6 +2,7 @@ package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"github.com/jackc/pgx/v4"
|
"github.com/jackc/pgx/v4"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
@ -12,7 +13,7 @@ import (
|
||||||
type ProductEntry struct {
|
type ProductEntry struct {
|
||||||
Slug string
|
Slug string
|
||||||
Name string
|
Name string
|
||||||
Price int
|
Price string
|
||||||
}
|
}
|
||||||
|
|
||||||
type productsIndexPage struct {
|
type productsIndexPage struct {
|
||||||
|
@ -35,11 +36,12 @@ func GetProductForm(w http.ResponseWriter, r *http.Request, params httprouter.Pa
|
||||||
form := newProductForm(r.Context(), conn, locale, company)
|
form := newProductForm(r.Context(), conn, locale, company)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
if slug == "new" {
|
if slug == "new" {
|
||||||
|
form.Tax.EmptyLabel = gettext("Select a tax for this product.", locale)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
mustRenderNewProductForm(w, r, form)
|
mustRenderNewProductForm(w, r, form)
|
||||||
return
|
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)
|
err := conn.QueryRow(r.Context(), "select product.name, product.description, to_price(price, decimal_digits), tax_id from product join company using (company_id) join currency using (currency_code) where product.slug = $1", slug).Scan(form.Name, form.Description, form.Price, form.Tax)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == pgx.ErrNoRows {
|
if err == pgx.ErrNoRows {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -77,7 +79,7 @@ func HandleAddProduct(w http.ResponseWriter, r *http.Request, _ httprouter.Param
|
||||||
mustRenderNewProductForm(w, r, form)
|
mustRenderNewProductForm(w, r, form)
|
||||||
return
|
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)
|
conn.MustExec(r.Context(), "insert into product (company_id, name, description, price, tax_id) select company_id, $2, $3, parse_price($4, decimal_digits), $5 from company join currency using (currency_code) where company_id = $1", company.Id, form.Name, form.Description, form.Price, form.Tax)
|
||||||
http.Redirect(w, r, companyURI(company, "/products"), http.StatusSeeOther)
|
http.Redirect(w, r, companyURI(company, "/products"), http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +100,7 @@ func HandleUpdateProduct(w http.ResponseWriter, r *http.Request, params httprout
|
||||||
mustRenderEditProductForm(w, r, form)
|
mustRenderEditProductForm(w, r, form)
|
||||||
return
|
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)
|
slug := conn.MustGetText(r.Context(), "", "update product set name = $1, description = $2, price = parse_price($3, decimal_digits), tax_id = $4 from company join currency using (currency_code) where product.company_id = company.company_id and product.slug = $5 returning product.slug", form.Name, form.Description, form.Price, form.Tax, params[0].Value)
|
||||||
if slug == "" {
|
if slug == "" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
@ -106,7 +108,7 @@ func HandleUpdateProduct(w http.ResponseWriter, r *http.Request, params httprout
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustGetProductEntries(ctx context.Context, conn *Conn, company *Company) []*ProductEntry {
|
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)
|
rows, err := conn.Query(ctx, "select product.slug, product.name, to_price(price, decimal_digits) from product join company using (company_id) join currency using (currency_code) where company_id = $1 order by name", company.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -130,6 +132,7 @@ func mustGetProductEntries(ctx context.Context, conn *Conn, company *Company) []
|
||||||
|
|
||||||
type productForm struct {
|
type productForm struct {
|
||||||
locale *Locale
|
locale *Locale
|
||||||
|
company *Company
|
||||||
Name *InputField
|
Name *InputField
|
||||||
Description *InputField
|
Description *InputField
|
||||||
Price *InputField
|
Price *InputField
|
||||||
|
@ -139,6 +142,7 @@ type productForm struct {
|
||||||
func newProductForm(ctx context.Context, conn *Conn, locale *Locale, company *Company) *productForm {
|
func newProductForm(ctx context.Context, conn *Conn, locale *Locale, company *Company) *productForm {
|
||||||
return &productForm{
|
return &productForm{
|
||||||
locale: locale,
|
locale: locale,
|
||||||
|
company: company,
|
||||||
Name: &InputField{
|
Name: &InputField{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Label: pgettext("input", "Name", locale),
|
Label: pgettext("input", "Name", locale),
|
||||||
|
@ -157,11 +161,13 @@ func newProductForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
|
||||||
Required: true,
|
Required: true,
|
||||||
Attributes: []template.HTMLAttr{
|
Attributes: []template.HTMLAttr{
|
||||||
`min="0"`,
|
`min="0"`,
|
||||||
|
template.HTMLAttr(fmt.Sprintf(`step="%v"`, company.MinCents())),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Tax: &SelectField{
|
Tax: &SelectField{
|
||||||
Name: "tax",
|
Name: "tax",
|
||||||
Label: pgettext("input", "Tax", locale),
|
Label: pgettext("input", "Tax", locale),
|
||||||
|
Required: true,
|
||||||
Options: MustGetOptions(ctx, conn, "select tax_id::text, name from tax where company_id = $1 order by name", company.Id),
|
Options: MustGetOptions(ctx, conn, "select tax_id::text, name from tax where company_id = $1 order by name", company.Id),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -182,7 +188,7 @@ func (form *productForm) Validate() bool {
|
||||||
validator := newFormValidator()
|
validator := newFormValidator()
|
||||||
validator.CheckRequiredInput(form.Name, gettext("Name can not be empty.", form.locale))
|
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)) {
|
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.CheckValidDecimal(form.Price, form.company.MinCents(), math.MaxFloat64, gettext("Price must be a number greater than zero.", form.locale))
|
||||||
}
|
}
|
||||||
validator.CheckValidSelectOption(form.Tax, gettext("Selected tax is not valid.", form.locale))
|
validator.CheckValidSelectOption(form.Tax, gettext("Selected tax is not valid.", form.locale))
|
||||||
return validator.AllOK()
|
return validator.AllOK()
|
||||||
|
|
|
@ -64,6 +64,7 @@ func newProfileForm(ctx context.Context, conn *Conn, locale *Locale) *profileFor
|
||||||
Name: "language",
|
Name: "language",
|
||||||
Label: pgettext("input", "Language", locale),
|
Label: pgettext("input", "Language", locale),
|
||||||
Options: languages,
|
Options: languages,
|
||||||
|
Required: true,
|
||||||
Attributes: []template.HTMLAttr{
|
Attributes: []template.HTMLAttr{
|
||||||
`autocomplete="language"`,
|
`autocomplete="language"`,
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,9 +2,13 @@ package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/text/message"
|
||||||
|
"golang.org/x/text/number"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
const overrideMethodName = "_method"
|
const overrideMethodName = "_method"
|
||||||
|
@ -27,6 +31,14 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s
|
||||||
"companyURI": func(uri string) string {
|
"companyURI": func(uri string) string {
|
||||||
return companyURI(company, uri)
|
return companyURI(company, uri)
|
||||||
},
|
},
|
||||||
|
"formatPrice": func(price string) string {
|
||||||
|
p := message.NewPrinter(locale.Language)
|
||||||
|
f, err := strconv.ParseFloat(price, 64)
|
||||||
|
if err != nil {
|
||||||
|
f = math.NaN()
|
||||||
|
}
|
||||||
|
return p.Sprintf("%.*f", company.DecimalDigits, number.Decimal(f))
|
||||||
|
},
|
||||||
"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))
|
||||||
},
|
},
|
||||||
|
|
68
po/ca.po
68
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-05 14:04+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"
|
||||||
|
@ -238,11 +238,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:89 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:90 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,70 +254,74 @@ 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
|
#: pkg/products.go:39
|
||||||
|
msgid "Select a tax for this product."
|
||||||
|
msgstr "Escolliu un impost per aquest producte."
|
||||||
|
|
||||||
|
#: pkg/products.go:148
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nom"
|
msgstr "Nom"
|
||||||
|
|
||||||
#: pkg/products.go:150
|
#: pkg/products.go:154
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Descripció"
|
msgstr "Descripció"
|
||||||
|
|
||||||
#: pkg/products.go:155
|
#: pkg/products.go:159
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr "Preu"
|
msgstr "Preu"
|
||||||
|
|
||||||
#: pkg/products.go:164 pkg/contacts.go:225
|
#: pkg/products.go:169 pkg/contacts.go:225
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Tax"
|
msgid "Tax"
|
||||||
msgstr "Impost"
|
msgstr "Impost"
|
||||||
|
|
||||||
#: pkg/products.go:183 pkg/profile.go:91
|
#: pkg/products.go:189 pkg/profile.go:92
|
||||||
msgid "Name can not be empty."
|
msgid "Name can not be empty."
|
||||||
msgstr "No podeu deixar el nom en blanc."
|
msgstr "No podeu deixar el nom en blanc."
|
||||||
|
|
||||||
#: pkg/products.go:184
|
#: pkg/products.go:190
|
||||||
msgid "Price can not be empty."
|
msgid "Price can not be empty."
|
||||||
msgstr "No podeu deixar el preu en blanc."
|
msgstr "No podeu deixar el preu en blanc."
|
||||||
|
|
||||||
#: pkg/products.go:185
|
#: pkg/products.go:191
|
||||||
msgid "Price must be a number greater than zero."
|
msgid "Price must be a number greater than zero."
|
||||||
msgstr "El preu ha de ser un número major a zero."
|
msgstr "El preu ha de ser un número major a zero."
|
||||||
|
|
||||||
#: pkg/products.go:187
|
#: pkg/products.go:193
|
||||||
msgid "Selected tax is not valid."
|
msgid "Selected tax is not valid."
|
||||||
msgstr "Heu seleccionat un impost que no és vàlid."
|
msgstr "Heu seleccionat un impost que no és vàlid."
|
||||||
|
|
||||||
#: pkg/company.go:78
|
#: pkg/company.go:89
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Currency"
|
msgid "Currency"
|
||||||
msgstr "Moneda"
|
msgstr "Moneda"
|
||||||
|
|
||||||
#: pkg/company.go:95
|
#: pkg/company.go:107
|
||||||
msgid "Selected currency is not valid."
|
msgid "Selected currency is not valid."
|
||||||
msgstr "Heu seleccionat una moneda que no és vàlida."
|
msgstr "Heu seleccionat una moneda que no és vàlida."
|
||||||
|
|
||||||
#: pkg/company.go:217
|
#: pkg/company.go:229
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Tax name"
|
msgid "Tax name"
|
||||||
msgstr "Nom impost"
|
msgstr "Nom impost"
|
||||||
|
|
||||||
#: pkg/company.go:223
|
#: pkg/company.go:235
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Rate (%)"
|
msgid "Rate (%)"
|
||||||
msgstr "Percentatge"
|
msgstr "Percentatge"
|
||||||
|
|
||||||
#: pkg/company.go:245
|
#: pkg/company.go:257
|
||||||
msgid "Tax name can not be empty."
|
msgid "Tax name can not be empty."
|
||||||
msgstr "No podeu deixar el nom de l’impost en blanc."
|
msgstr "No podeu deixar el nom de l’impost en blanc."
|
||||||
|
|
||||||
#: pkg/company.go:246
|
#: pkg/company.go:258
|
||||||
msgid "Tax rate can not be empty."
|
msgid "Tax rate can not be empty."
|
||||||
msgstr "No podeu deixar percentatge en blanc."
|
msgstr "No podeu deixar percentatge en blanc."
|
||||||
|
|
||||||
#: pkg/company.go:247
|
#: pkg/company.go:259
|
||||||
msgid "Tax rate must be an integer between -99 and 99."
|
msgid "Tax rate must be an integer between -99 and 99."
|
||||||
msgstr "El percentatge ha de ser entre -99 i 99."
|
msgstr "El percentatge ha de ser entre -99 i 99."
|
||||||
|
|
||||||
|
@ -341,11 +345,11 @@ msgctxt "input"
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Idioma"
|
msgstr "Idioma"
|
||||||
|
|
||||||
#: pkg/profile.go:92
|
#: pkg/profile.go:93
|
||||||
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."
|
||||||
|
|
||||||
#: pkg/profile.go:93
|
#: pkg/profile.go:94
|
||||||
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."
|
||||||
|
|
||||||
|
@ -394,51 +398,51 @@ msgctxt "input"
|
||||||
msgid "Postal code"
|
msgid "Postal code"
|
||||||
msgstr "Codi postal"
|
msgstr "Codi postal"
|
||||||
|
|
||||||
#: pkg/contacts.go:255
|
#: 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."
|
||||||
|
|
||||||
|
|
68
po/es.po
68
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-05 14:04+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"
|
||||||
|
@ -238,11 +238,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:89 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:90 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,70 +254,74 @@ 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
|
#: pkg/products.go:39
|
||||||
|
msgid "Select a tax for this product."
|
||||||
|
msgstr "Escoged un impuesto para este producto."
|
||||||
|
|
||||||
|
#: pkg/products.go:148
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nombre"
|
msgstr "Nombre"
|
||||||
|
|
||||||
#: pkg/products.go:150
|
#: pkg/products.go:154
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Descripción"
|
msgstr "Descripción"
|
||||||
|
|
||||||
#: pkg/products.go:155
|
#: pkg/products.go:159
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr "Precio"
|
msgstr "Precio"
|
||||||
|
|
||||||
#: pkg/products.go:164 pkg/contacts.go:225
|
#: pkg/products.go:169 pkg/contacts.go:225
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Tax"
|
msgid "Tax"
|
||||||
msgstr "Impuesto"
|
msgstr "Impuesto"
|
||||||
|
|
||||||
#: pkg/products.go:183 pkg/profile.go:91
|
#: pkg/products.go:189 pkg/profile.go:92
|
||||||
msgid "Name can not be empty."
|
msgid "Name can not be empty."
|
||||||
msgstr "No podéis dejar el nombre en blanco."
|
msgstr "No podéis dejar el nombre en blanco."
|
||||||
|
|
||||||
#: pkg/products.go:184
|
#: pkg/products.go:190
|
||||||
msgid "Price can not be empty."
|
msgid "Price can not be empty."
|
||||||
msgstr "No podéis dejar el precio en blanco."
|
msgstr "No podéis dejar el precio en blanco."
|
||||||
|
|
||||||
#: pkg/products.go:185
|
#: pkg/products.go:191
|
||||||
msgid "Price must be a number greater than zero."
|
msgid "Price must be a number greater than zero."
|
||||||
msgstr "El precio tiene que ser un número mayor a cero."
|
msgstr "El precio tiene que ser un número mayor a cero."
|
||||||
|
|
||||||
#: pkg/products.go:187
|
#: pkg/products.go:193
|
||||||
msgid "Selected tax is not valid."
|
msgid "Selected tax is not valid."
|
||||||
msgstr "Habéis escogido un impuesto que no es válido."
|
msgstr "Habéis escogido un impuesto que no es válido."
|
||||||
|
|
||||||
#: pkg/company.go:78
|
#: pkg/company.go:89
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Currency"
|
msgid "Currency"
|
||||||
msgstr "Moneda"
|
msgstr "Moneda"
|
||||||
|
|
||||||
#: pkg/company.go:95
|
#: pkg/company.go:107
|
||||||
msgid "Selected currency is not valid."
|
msgid "Selected currency is not valid."
|
||||||
msgstr "Habéis escogido una moneda que no es válida."
|
msgstr "Habéis escogido una moneda que no es válida."
|
||||||
|
|
||||||
#: pkg/company.go:217
|
#: pkg/company.go:229
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Tax name"
|
msgid "Tax name"
|
||||||
msgstr "Nombre impuesto"
|
msgstr "Nombre impuesto"
|
||||||
|
|
||||||
#: pkg/company.go:223
|
#: pkg/company.go:235
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Rate (%)"
|
msgid "Rate (%)"
|
||||||
msgstr "Porcentaje"
|
msgstr "Porcentaje"
|
||||||
|
|
||||||
#: pkg/company.go:245
|
#: pkg/company.go:257
|
||||||
msgid "Tax name can not be empty."
|
msgid "Tax name can not be empty."
|
||||||
msgstr "No podéis dejar el nombre del impuesto en blanco."
|
msgstr "No podéis dejar el nombre del impuesto en blanco."
|
||||||
|
|
||||||
#: pkg/company.go:246
|
#: pkg/company.go:258
|
||||||
msgid "Tax rate can not be empty."
|
msgid "Tax rate can not be empty."
|
||||||
msgstr "No podéis dejar el porcentaje en blanco."
|
msgstr "No podéis dejar el porcentaje en blanco."
|
||||||
|
|
||||||
#: pkg/company.go:247
|
#: pkg/company.go:259
|
||||||
msgid "Tax rate must be an integer between -99 and 99."
|
msgid "Tax rate must be an integer between -99 and 99."
|
||||||
msgstr "El porcentaje tiene que estar entre -99 y 99."
|
msgstr "El porcentaje tiene que estar entre -99 y 99."
|
||||||
|
|
||||||
|
@ -341,11 +345,11 @@ msgctxt "input"
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Idioma"
|
msgstr "Idioma"
|
||||||
|
|
||||||
#: pkg/profile.go:92
|
#: pkg/profile.go:93
|
||||||
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."
|
||||||
|
|
||||||
#: pkg/profile.go:93
|
#: pkg/profile.go:94
|
||||||
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."
|
||||||
|
|
||||||
|
@ -394,51 +398,51 @@ msgctxt "input"
|
||||||
msgid "Postal code"
|
msgid "Postal code"
|
||||||
msgstr "Código postal"
|
msgstr "Código postal"
|
||||||
|
|
||||||
#: pkg/contacts.go:255
|
#: 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."
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Revert numerus:parse_price from pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
drop function if exists numerus.parse_price(text, integer);
|
||||||
|
|
||||||
|
commit;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Revert numerus:to_price from pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
drop function if exists numerus.to_price(integer, integer);
|
||||||
|
|
||||||
|
commit;
|
|
@ -40,3 +40,5 @@ tax_rate [schema_numerus] 2023-01-28T11:33:39Z jordi fita mas <jordi@tandem.blog
|
||||||
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
|
product [schema_numerus company] 2023-02-04T09:17:24Z jordi fita mas <jordi@tandem.blog> # Add relation for products
|
||||||
|
parse_price [schema_public] 2023-02-05T11:04:54Z jordi fita mas <jordi@tandem.blog> # Add function to convert from price to cents
|
||||||
|
to_price [schema_numerus] 2023-02-05T11:46:31Z jordi fita mas <jordi@tandem.blog> # Add function to format cents to prices
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
-- Test parse_price
|
||||||
|
set client_min_messages to warning;
|
||||||
|
create extension if not exists pgtap;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select plan(36);
|
||||||
|
|
||||||
|
set search_path to auth, numerus, public;
|
||||||
|
|
||||||
|
select has_function('numerus', 'parse_price', array ['text', 'integer']);
|
||||||
|
select function_lang_is('numerus', 'parse_price', array ['text', 'integer'], 'plpgsql');
|
||||||
|
select function_returns('numerus', 'parse_price', array ['text', 'integer'], 'integer');
|
||||||
|
select isnt_definer('numerus', 'parse_price', array ['text', 'integer']);
|
||||||
|
select volatility_is('numerus', 'parse_price', array ['text', 'integer'], 'immutable');
|
||||||
|
select function_privs_are('numerus', 'parse_price', array ['text', 'integer'], 'guest', array []::text[]);
|
||||||
|
select function_privs_are('numerus', 'parse_price', array ['text', 'integer'], 'invoicer', array ['EXECUTE']);
|
||||||
|
select function_privs_are('numerus', 'parse_price', array ['text', 'integer'], 'admin', array ['EXECUTE']);
|
||||||
|
select function_privs_are('numerus', 'parse_price', array ['text', 'integer'], 'authenticator', array []::text[]);
|
||||||
|
|
||||||
|
select is( parse_price('1.1', 2), 110 );
|
||||||
|
select is( parse_price('1.1', 3), 1100 );
|
||||||
|
select is( parse_price('0', 2), 0 );
|
||||||
|
select is( parse_price('0', 3), 0 );
|
||||||
|
select is( parse_price('0.01', 2), 1 );
|
||||||
|
select is( parse_price('0.001', 3), 1 );
|
||||||
|
select is( parse_price('0.1', 2), 10 );
|
||||||
|
select is( parse_price('0.01', 3), 10 );
|
||||||
|
select is( parse_price('1', 2), 100 );
|
||||||
|
select is( parse_price('0.1', 3), 100 );
|
||||||
|
select is( parse_price('10', 2), 1000 );
|
||||||
|
select is( parse_price('1', 3), 1000 );
|
||||||
|
select is( parse_price('23.23', 2), 2323 );
|
||||||
|
select is( parse_price('23.23', 3), 23230 );
|
||||||
|
select throws_ok( $$ select parse_price('234.234', 2) $$ );
|
||||||
|
select is( parse_price('234.234', 3), 234234 );
|
||||||
|
select throws_ok( $$ select parse_price('2345.2345', 2) $$ );
|
||||||
|
select throws_ok( $$ select parse_price('2345.2345', 3) $$ );
|
||||||
|
select is( parse_price('00000000000000001.100000000000000000000', 2), 110 );
|
||||||
|
select is( parse_price('00000000000000001.100000000000000000000', 3), 1100 );
|
||||||
|
select is( parse_price('00000000000000000.100000000000000000000', 2), 10 );
|
||||||
|
select is( parse_price('00000000000000000.100000000000000000000', 3), 100 );
|
||||||
|
select is( parse_price('00000000000123456.780000000000000000000', 2), 12345678 );
|
||||||
|
select is( parse_price('00000000000123456.789000000000000000000', 3), 123456789 );
|
||||||
|
select throws_ok( $$ select parse_price('1,1', 2) $$ );
|
||||||
|
select throws_ok( $$ select parse_price('1.1.1', 2) $$ );
|
||||||
|
select throws_ok( $$ select parse_price('a.b', 2) $$ );
|
||||||
|
|
||||||
|
select *
|
||||||
|
from finish();
|
||||||
|
|
||||||
|
rollback;
|
|
@ -0,0 +1,40 @@
|
||||||
|
-- Test to_price
|
||||||
|
set client_min_messages to warning;
|
||||||
|
create extension if not exists pgtap;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select plan(23);
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
select has_function('numerus', 'to_price', array ['integer', 'integer']);
|
||||||
|
select function_lang_is('numerus', 'to_price', array ['integer', 'integer'], 'plpgsql');
|
||||||
|
select function_returns('numerus', 'to_price', array ['integer', 'integer'], 'text');
|
||||||
|
select isnt_definer('numerus', 'to_price', array ['integer', 'integer']);
|
||||||
|
select volatility_is('numerus', 'to_price', array ['integer', 'integer'], 'immutable');
|
||||||
|
select function_privs_are('numerus', 'to_price', array ['integer', 'integer'], 'guest', array []::text[]);
|
||||||
|
select function_privs_are('numerus', 'to_price', array ['integer', 'integer'], 'invoicer', array ['EXECUTE']);
|
||||||
|
select function_privs_are('numerus', 'to_price', array ['integer', 'integer'], 'admin', array ['EXECUTE']);
|
||||||
|
select function_privs_are('numerus', 'to_price', array ['integer', 'integer'], 'authenticator', array []::text[]);
|
||||||
|
|
||||||
|
select is( to_price(0, 2), '0.00' );
|
||||||
|
select is( to_price(0, 3), '0.000' );
|
||||||
|
select is( to_price(1, 2), '0.01' );
|
||||||
|
select is( to_price(1, 3), '0.001' );
|
||||||
|
select is( to_price(10, 2), '0.10' );
|
||||||
|
select is( to_price(10, 3), '0.010' );
|
||||||
|
select is( to_price(100, 2), '1.00' );
|
||||||
|
select is( to_price(100, 3), '0.100' );
|
||||||
|
select is( to_price(110, 2), '1.10' );
|
||||||
|
select is( to_price(1100, 3), '1.100' );
|
||||||
|
select is( to_price(12345678, 2), '123456.78' );
|
||||||
|
select is( to_price(12345678, 3), '12345.678' );
|
||||||
|
select is( to_price(12345678, 4), '1234.5678' );
|
||||||
|
select is( to_price(12345678, 5), '123.45678' );
|
||||||
|
|
||||||
|
select *
|
||||||
|
from finish();
|
||||||
|
|
||||||
|
rollback;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Verify numerus:parse_price on pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select has_function_privilege('numerus.parse_price(text, integer)', 'execute');
|
||||||
|
|
||||||
|
rollback;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Verify numerus:to_price on pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select has_function_privilege('numerus.to_price(integer, integer)', 'execute');
|
||||||
|
|
||||||
|
rollback;
|
|
@ -342,11 +342,13 @@ input.width-2x {
|
||||||
color: var(--numerus--color--red);
|
color: var(--numerus--color--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
[lang="en"] input:not([required]) + label::after {
|
[lang="en"] input:not([required]) + label::after,
|
||||||
|
[lang="en"] select:not([required]) + label::after {
|
||||||
content: " (optional)"
|
content: " (optional)"
|
||||||
}
|
}
|
||||||
|
|
||||||
[lang="ca"] input:not([required]) + label::after, [lang="es"] input:not([required]) + label::after {
|
[lang="ca"] input:not([required]) + label::after, [lang="es"] input:not([required]) + label::after,
|
||||||
|
[lang="ca"] select:not([required]) + label::after, [lang="es"] select:not([required]) + label::after {
|
||||||
content: " (opcional)"
|
content: " (opcional)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,11 @@
|
||||||
<div class="input {{ if .Errors }}has-errors{{ end }}">
|
<div class="input {{ if .Errors }}has-errors{{ end }}">
|
||||||
<select id="{{ .Name }}-field" name="{{ .Name }}"
|
<select id="{{ .Name }}-field" name="{{ .Name }}"
|
||||||
{{- range $attribute := .Attributes }} {{$attribute}} {{ end -}}
|
{{- range $attribute := .Attributes }} {{$attribute}} {{ end -}}
|
||||||
|
{{ if .Required }}required="required"{{ end }}
|
||||||
>
|
>
|
||||||
|
{{- with .EmptyLabel }}
|
||||||
|
<option value="">{{ . }}</option>
|
||||||
|
{{- end}}
|
||||||
{{- range $option := .Options }}
|
{{- range $option := .Options }}
|
||||||
<option value="{{ .Value }}"
|
<option value="{{ .Value }}"
|
||||||
{{- if eq .Value $.Selected }} selected="selected"{{ end }}>{{ .Label }}</option>
|
{{- if eq .Value $.Selected }} selected="selected"{{ end }}>{{ .Label }}</option>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td><a href="{{ companyURI "/products/"}}{{ .Slug }}">{{ .Name }}</a></td>
|
<td><a href="{{ companyURI "/products/"}}{{ .Slug }}">{{ .Name }}</a></td>
|
||||||
<td>{{ .Price }}</td>
|
<td>{{ .Price | formatPrice }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
|
|
Loading…
Reference in New Issue