Compare commits
5 Commits
917db31227
...
55980367bc
Author | SHA1 | Date |
---|---|---|
jordi fita mas | 55980367bc | |
jordi fita mas | a0a3a5561d | |
jordi fita mas | 7d17620f48 | |
jordi fita mas | 1ab48cfcbc | |
jordi fita mas | 80f14d5818 |
|
@ -8,6 +8,7 @@ Build-Depends:
|
||||||
gettext,
|
gettext,
|
||||||
golang-any,
|
golang-any,
|
||||||
golang-github-jackc-pgx-v4-dev,
|
golang-github-jackc-pgx-v4-dev,
|
||||||
|
golang-github-julienschmidt-httprouter-dev,
|
||||||
golang-github-leonelquinteros-gotext-dev,
|
golang-github-leonelquinteros-gotext-dev,
|
||||||
golang-golang-x-text-dev,
|
golang-golang-x-text-dev,
|
||||||
postgresql-all (>= 217~),
|
postgresql-all (>= 217~),
|
||||||
|
|
|
@ -20,4 +20,13 @@ values (1, 'Retenció 15 %', -0.15)
|
||||||
, (1, 'IVA 21 %', 0.21)
|
, (1, 'IVA 21 %', 0.21)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code)
|
||||||
|
values (1, 1, 'Melcior', 'IR1', 'Rei Blanc', parse_packed_phone_number('0732621', 'IR'), 'melcio@reismags.cat', '', 'C/ Principal, 1', 'Shiraz', 'Fars', '1', 'IR')
|
||||||
|
, (2, 1, 'Gaspar', 'IN2', 'Rei Ros', parse_packed_phone_number('111', 'IN'), 'gaspar@reismags.cat', '', 'C/ Principal, 2', 'Nova Delhi', 'Delhi', '2', 'IN')
|
||||||
|
, (3, 1, 'Baltasar', 'YE3', 'Rei Negre', parse_packed_phone_number('1-111-111', 'YE'), 'baltasar@reismags.cat', '', 'C/ Principal, 3', 'Sanaa', 'Sanaa', '3', 'YE')
|
||||||
|
, (4, 1, 'Caganera', 'ES41414141L', '', parse_packed_phone_number('222 222 222', 'ES'), 'caganera@pesebre.cat', '', 'C/ De l’Hort, 4', 'Olot', 'Girona', '17800', 'ES')
|
||||||
|
, (5, 1, 'Bou', 'ES41414142C', '', parse_packed_phone_number('333 333 333', 'ES'), 'bou@pesebre.cat', '', 'C/ De la Palla, 5', 'Sant Climent Sescebes', 'Girona', '17751', 'ES')
|
||||||
|
, (6, 1, 'Rabadà', 'ES41414143K', '', parse_packed_phone_number('444 444 444', 'ES'), 'rabada@pesebre.cat', '', 'C/ De les Ovelles, 6', 'Fornells de la Selva', 'Girona', '17458', 'ES')
|
||||||
|
;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -4,6 +4,7 @@ go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/jackc/pgx/v4 v4.17.2
|
github.com/jackc/pgx/v4 v4.17.2
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
github.com/leonelquinteros/gotext v1.5.1
|
github.com/leonelquinteros/gotext v1.5.1
|
||||||
golang.org/x/text v0.3.8
|
golang.org/x/text v0.3.8
|
||||||
)
|
)
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -62,6 +62,8 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f
|
||||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
|
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
|
||||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
|
223
pkg/company.go
223
pkg/company.go
|
@ -3,11 +3,11 @@ package pkg
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -19,44 +19,26 @@ type Company struct {
|
||||||
Slug string
|
Slug string
|
||||||
}
|
}
|
||||||
|
|
||||||
func CompanyHandler(next http.Handler) http.Handler {
|
func CompanyHandler(next http.Handler) httprouter.Handle {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
slug := r.URL.Path
|
|
||||||
if idx := strings.IndexByte(slug, '/'); idx >= 0 {
|
|
||||||
slug = slug[:idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := getConn(r)
|
|
||||||
company := &Company{
|
company := &Company{
|
||||||
Slug: slug,
|
Slug: params[0].Value,
|
||||||
}
|
}
|
||||||
err := conn.QueryRow(r.Context(), "select company_id from company where slug = $1", slug).Scan(&company.Id)
|
conn := getConn(r)
|
||||||
|
err := conn.QueryRow(r.Context(), "select company_id from company where slug = $1", company.Slug).Scan(&company.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := context.WithValue(r.Context(), ContextCompanyKey, company)
|
ctx := context.WithValue(r.Context(), ContextCompanyKey, company)
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
|
r2 := new(http.Request)
|
||||||
// Same as StripPrefix
|
*r2 = *r
|
||||||
p := strings.TrimPrefix(r.URL.Path, slug)
|
r2.URL = new(url.URL)
|
||||||
rp := strings.TrimPrefix(r.URL.RawPath, slug)
|
*r2.URL = *r.URL
|
||||||
if len(p) < len(r.URL.Path) && (r.URL.RawPath == "" || len(rp) < len(r.URL.RawPath)) {
|
r2.URL.Path = params[1].Value
|
||||||
r2 := new(http.Request)
|
next.ServeHTTP(w, r2)
|
||||||
*r2 = *r
|
}
|
||||||
r2.URL = new(url.URL)
|
|
||||||
*r2.URL = *r.URL
|
|
||||||
if p == "" {
|
|
||||||
r2.URL.Path = "/"
|
|
||||||
} else {
|
|
||||||
r2.URL.Path = p
|
|
||||||
}
|
|
||||||
r2.URL.RawPath = rp
|
|
||||||
next.ServeHTTP(w, r2)
|
|
||||||
} else {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCompany(r *http.Request) *Company {
|
func getCompany(r *http.Request) *Company {
|
||||||
|
@ -124,41 +106,70 @@ func (form *taxDetailsForm) mustFillFromDatabase(ctx context.Context, conn *Conn
|
||||||
|
|
||||||
type TaxDetailsPage struct {
|
type TaxDetailsPage struct {
|
||||||
DetailsForm *taxDetailsForm
|
DetailsForm *taxDetailsForm
|
||||||
NewTaxForm *newTaxForm
|
NewTaxForm *taxForm
|
||||||
Taxes []*Tax
|
Taxes []*Tax
|
||||||
}
|
}
|
||||||
|
|
||||||
func CompanyTaxDetailsHandler() http.Handler {
|
func GetCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mustRenderTaxDetailsForm(w, r, newTaxDetailsFormFromDatabase(r))
|
||||||
locale := getLocale(r)
|
}
|
||||||
conn := getConn(r)
|
|
||||||
page := &TaxDetailsPage{
|
func newTaxDetailsFormFromDatabase(r *http.Request) *taxDetailsForm {
|
||||||
DetailsForm: newTaxDetailsForm(r.Context(), conn, locale),
|
locale := getLocale(r)
|
||||||
NewTaxForm: newNewTaxForm(locale),
|
conn := getConn(r)
|
||||||
}
|
form := newTaxDetailsForm(r.Context(), conn, locale)
|
||||||
company := mustGetCompany(r)
|
|
||||||
if r.Method == "POST" {
|
company := mustGetCompany(r)
|
||||||
if err := page.DetailsForm.Parse(r); err != nil {
|
form.mustFillFromDatabase(r.Context(), conn, company)
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
return form
|
||||||
}
|
}
|
||||||
if err := verifyCsrfTokenValid(r); err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
func HandleCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
return
|
locale := getLocale(r)
|
||||||
}
|
conn := getConn(r)
|
||||||
if ok := page.DetailsForm.Validate(r.Context(), conn); ok {
|
form := newTaxDetailsForm(r.Context(), conn, locale)
|
||||||
form := page.DetailsForm
|
if err := form.Parse(r); err != nil {
|
||||||
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.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
http.Redirect(w, r, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
|
return
|
||||||
return
|
}
|
||||||
}
|
if err := verifyCsrfTokenValid(r); err != nil {
|
||||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
} else {
|
return
|
||||||
page.DetailsForm.mustFillFromDatabase(r.Context(), conn, company)
|
}
|
||||||
}
|
if ok := form.Validate(r.Context(), conn); !ok {
|
||||||
page.Taxes = mustGetTaxes(r.Context(), conn, company)
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||||
mustRenderAppTemplate(w, r, "tax-details.gohtml", page)
|
mustRenderTaxDetailsForm(w, r, form)
|
||||||
})
|
return
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustRenderTaxDetailsForm(w http.ResponseWriter, r *http.Request, form *taxDetailsForm) {
|
||||||
|
locale := getLocale(r)
|
||||||
|
page := &TaxDetailsPage{
|
||||||
|
DetailsForm: form,
|
||||||
|
NewTaxForm: newTaxForm(locale),
|
||||||
|
}
|
||||||
|
mustRenderTexDetailsPage(w, r, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustRenderTaxForm(w http.ResponseWriter, r *http.Request, form *taxForm) {
|
||||||
|
page := &TaxDetailsPage{
|
||||||
|
DetailsForm: newTaxDetailsFormFromDatabase(r),
|
||||||
|
NewTaxForm: form,
|
||||||
|
}
|
||||||
|
mustRenderTexDetailsPage(w, r, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustRenderTexDetailsPage(w http.ResponseWriter, r *http.Request, page *TaxDetailsPage) {
|
||||||
|
conn := getConn(r)
|
||||||
|
company := mustGetCompany(r)
|
||||||
|
page.Taxes = mustGetTaxes(r.Context(), conn, company)
|
||||||
|
mustRenderAppTemplate(w, r, "tax-details.gohtml", page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustGetCompany(r *http.Request) *Company {
|
func mustGetCompany(r *http.Request) *Company {
|
||||||
|
@ -192,14 +203,14 @@ func mustGetTaxes(ctx context.Context, conn *Conn, company *Company) []*Tax {
|
||||||
return taxes
|
return taxes
|
||||||
}
|
}
|
||||||
|
|
||||||
type newTaxForm struct {
|
type taxForm struct {
|
||||||
locale *Locale
|
locale *Locale
|
||||||
Name *InputField
|
Name *InputField
|
||||||
Rate *InputField
|
Rate *InputField
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNewTaxForm(locale *Locale) *newTaxForm {
|
func newTaxForm(locale *Locale) *taxForm {
|
||||||
return &newTaxForm{
|
return &taxForm{
|
||||||
locale: locale,
|
locale: locale,
|
||||||
Name: &InputField{
|
Name: &InputField{
|
||||||
Name: "tax_name",
|
Name: "tax_name",
|
||||||
|
@ -220,7 +231,7 @@ func newNewTaxForm(locale *Locale) *newTaxForm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (form *newTaxForm) Parse(r *http.Request) error {
|
func (form *taxForm) Parse(r *http.Request) error {
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -229,7 +240,7 @@ func (form *newTaxForm) Parse(r *http.Request) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (form *newTaxForm) Validate() bool {
|
func (form *taxForm) Validate() bool {
|
||||||
validator := newFormValidator()
|
validator := newFormValidator()
|
||||||
validator.CheckRequiredInput(form.Name, gettext("Tax name can not be empty.", form.locale))
|
validator.CheckRequiredInput(form.Name, gettext("Tax name can not be empty.", form.locale))
|
||||||
if validator.CheckRequiredInput(form.Rate, gettext("Tax rate can not be empty.", form.locale)) {
|
if validator.CheckRequiredInput(form.Rate, gettext("Tax rate can not be empty.", form.locale)) {
|
||||||
|
@ -238,44 +249,40 @@ func (form *newTaxForm) Validate() bool {
|
||||||
return validator.AllOK()
|
return validator.AllOK()
|
||||||
}
|
}
|
||||||
|
|
||||||
func CompanyTaxHandler() http.Handler {
|
func HandleDeleteCompanyTax(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
taxId, err := strconv.Atoi(params[0].Value)
|
||||||
param := r.URL.Path
|
if err != nil {
|
||||||
if idx := strings.LastIndexByte(param, '/'); idx >= 0 {
|
http.NotFound(w, r)
|
||||||
param = param[idx+1:]
|
return
|
||||||
}
|
}
|
||||||
conn := getConn(r)
|
if err := verifyCsrfTokenValid(r); err != nil {
|
||||||
company := mustGetCompany(r)
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
if taxId, err := strconv.Atoi(param); err == nil {
|
return
|
||||||
if err := verifyCsrfTokenValid(r); err != nil {
|
}
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
conn := getConn(r)
|
||||||
return
|
company := mustGetCompany(r)
|
||||||
}
|
conn.MustExec(r.Context(), "delete from tax where tax_id = $1", taxId)
|
||||||
conn.MustExec(r.Context(), "delete from tax where tax_id = $1", taxId)
|
http.Redirect(w, r, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
|
||||||
http.Redirect(w, r, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
|
}
|
||||||
} else {
|
|
||||||
locale := getLocale(r)
|
func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
form := newNewTaxForm(locale)
|
locale := getLocale(r)
|
||||||
if err := form.Parse(r); err != nil {
|
form := newTaxForm(locale)
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
if err := form.Parse(r); err != nil {
|
||||||
return
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
}
|
return
|
||||||
if err := verifyCsrfTokenValid(r); err != nil {
|
}
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
if err := verifyCsrfTokenValid(r); err != nil {
|
||||||
return
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
}
|
return
|
||||||
if form.Validate() {
|
}
|
||||||
conn.MustExec(r.Context(), "insert into tax (company_id, name, rate) values ($1, $2, $3 / 100::decimal)", company.Id, form.Name, form.Rate.Integer())
|
if !form.Validate() {
|
||||||
http.Redirect(w, r, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||||
} else {
|
mustRenderTaxForm(w, r, form)
|
||||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
return
|
||||||
}
|
}
|
||||||
page := &TaxDetailsPage{
|
conn := getConn(r)
|
||||||
DetailsForm: newTaxDetailsForm(r.Context(), conn, locale).mustFillFromDatabase(r.Context(), conn, company),
|
company := mustGetCompany(r)
|
||||||
NewTaxForm: form,
|
conn.MustExec(r.Context(), "insert into tax (company_id, name, rate) values ($1, $2, $3 / 100::decimal)", company.Id, form.Name, form.Rate.Integer())
|
||||||
Taxes: mustGetTaxes(r.Context(), conn, company),
|
http.Redirect(w, r, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
|
||||||
}
|
|
||||||
mustRenderAppTemplate(w, r, "tax-details.gohtml", page)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
123
pkg/contacts.go
123
pkg/contacts.go
|
@ -2,11 +2,14 @@ package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/jackc/pgx/v4"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContactEntry struct {
|
type ContactEntry struct {
|
||||||
|
Slug string
|
||||||
Name string
|
Name string
|
||||||
Email string
|
Email string
|
||||||
Phone string
|
Phone string
|
||||||
|
@ -16,38 +19,93 @@ type ContactsIndexPage struct {
|
||||||
Contacts []*ContactEntry
|
Contacts []*ContactEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
func ContactsHandler() http.Handler {
|
func IndexContacts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
conn := getConn(r)
|
||||||
conn := getConn(r)
|
company := getCompany(r)
|
||||||
company := getCompany(r)
|
page := &ContactsIndexPage{
|
||||||
if r.Method == "POST" {
|
Contacts: mustGetContactEntries(r.Context(), conn, company),
|
||||||
locale := getLocale(r)
|
}
|
||||||
form := newContactForm(r.Context(), conn, locale)
|
mustRenderAppTemplate(w, r, "contacts-index.gohtml", page)
|
||||||
if err := form.Parse(r); err != nil {
|
}
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
func GetContactForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
}
|
locale := getLocale(r)
|
||||||
if err := verifyCsrfTokenValid(r); err != nil {
|
conn := getConn(r)
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
form := newContactForm(r.Context(), conn, locale)
|
||||||
return
|
slug := params[0].Value
|
||||||
}
|
if slug == "new" {
|
||||||
if form.Validate(r.Context(), conn) {
|
w.WriteHeader(http.StatusOK)
|
||||||
conn.MustExec(r.Context(), "insert into contact (company_id, business_name, vatin, trade_name, phone, email, web, address, province, city, 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)
|
mustRenderNewContactForm(w, r, form)
|
||||||
http.Redirect(w, r, "/company/"+company.Slug+"/contacts", http.StatusSeeOther)
|
return
|
||||||
} else {
|
}
|
||||||
mustRenderAppTemplate(w, r, "contacts-new.gohtml", form)
|
err := conn.QueryRow(r.Context(), "select business_name, substr(vatin::text, 3), trade_name, phone, email, web, address, city, province, postal_code, country_code from contact where slug = $1", slug).Scan(form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country)
|
||||||
}
|
if err != nil {
|
||||||
|
if err == pgx.ErrNoRows {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
page := &ContactsIndexPage{
|
panic(err)
|
||||||
Contacts: mustGetContactEntries(r.Context(), conn, company),
|
|
||||||
}
|
|
||||||
mustRenderAppTemplate(w, r, "contacts-index.gohtml", page)
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
mustRenderEditContactForm(w, r, form)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustRenderNewContactForm(w http.ResponseWriter, r *http.Request, form *contactForm) {
|
||||||
|
mustRenderAppTemplate(w, r, "contacts-new.gohtml", form)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustRenderEditContactForm(w http.ResponseWriter, r *http.Request, form *contactForm) {
|
||||||
|
mustRenderAppTemplate(w, r, "contacts-edit.gohtml", form)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleAddContact(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
conn := getConn(r)
|
||||||
|
locale := getLocale(r)
|
||||||
|
form := newContactForm(r.Context(), conn, locale)
|
||||||
|
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(r.Context(), conn) {
|
||||||
|
mustRenderNewContactForm(w, r, form)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
company := getCompany(r)
|
||||||
|
conn.MustExec(r.Context(), "insert into contact (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) values ($1, $2, ($12 || $3)::vatin, $4, parse_packed_phone_number($5, $12), $6, $7, $8, $9, $10, $11, $12)", company.Id, form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country)
|
||||||
|
http.Redirect(w, r, "/company/"+company.Slug+"/contacts", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleUpdateContact(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
|
conn := getConn(r)
|
||||||
|
locale := getLocale(r)
|
||||||
|
form := newContactForm(r.Context(), conn, locale)
|
||||||
|
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(r.Context(), conn) {
|
||||||
|
mustRenderEditContactForm(w, r, form)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slug := conn.MustGetText(r.Context(), "", "update contact 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 where slug = $12 returning slug", form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country, params[0].Value)
|
||||||
|
if slug == "" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
company := getCompany(r)
|
||||||
|
http.Redirect(w, r, "/company/"+company.Slug+"/contacts/"+slug, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustGetContactEntries(ctx context.Context, conn *Conn, company *Company) []*ContactEntry {
|
func mustGetContactEntries(ctx context.Context, conn *Conn, company *Company) []*ContactEntry {
|
||||||
rows, err := conn.Query(ctx, "select business_name, email, phone from contact where company_id = $1 order by business_name", company.Id)
|
rows, err := conn.Query(ctx, "select slug, business_name, email, phone from contact where company_id = $1 order by business_name", company.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -56,7 +114,7 @@ func mustGetContactEntries(ctx context.Context, conn *Conn, company *Company) []
|
||||||
var entries []*ContactEntry
|
var entries []*ContactEntry
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
entry := &ContactEntry{}
|
entry := &ContactEntry{}
|
||||||
err = rows.Scan(&entry.Name, &entry.Email, &entry.Phone)
|
err = rows.Scan(&entry.Slug, &entry.Name, &entry.Email, &entry.Phone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -217,12 +275,3 @@ func (form *contactForm) Validate(ctx context.Context, conn *Conn) bool {
|
||||||
validator.CheckValidSelectOption(form.Country, gettext("Selected country is not valid.", form.locale))
|
validator.CheckValidSelectOption(form.Country, gettext("Selected country is not valid.", form.locale))
|
||||||
return validator.AllOK()
|
return validator.AllOK()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContactHandler() http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
locale := getLocale(r)
|
|
||||||
conn := getConn(r)
|
|
||||||
form := newContactForm(r.Context(), conn, locale)
|
|
||||||
mustRenderAppTemplate(w, r, "contacts-new.gohtml", form)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ func NewLocale(lang language.Tag) *Locale {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetLocale(db *Db, next http.Handler) http.Handler {
|
func LocaleSetter(db *Db, next http.Handler) http.Handler {
|
||||||
availableLanguages := mustGetAvailableLanguages(db)
|
availableLanguages := mustGetAvailableLanguages(db)
|
||||||
var matcher = language.NewMatcher(availableLanguages)
|
var matcher = language.NewMatcher(availableLanguages)
|
||||||
|
|
||||||
|
|
97
pkg/login.go
97
pkg/login.go
|
@ -3,6 +3,7 @@ package pkg
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -72,49 +73,59 @@ func (form *loginForm) Validate() bool {
|
||||||
return validator.AllOK()
|
return validator.AllOK()
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoginHandler() http.Handler {
|
func GetLoginForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
user := getUser(r)
|
||||||
user := getUser(r)
|
if user.LoggedIn {
|
||||||
if user.LoggedIn {
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
locale := getLocale(r)
|
||||||
|
form := newLoginForm(locale)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
mustRenderLoginForm(w, r, form)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleLoginForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
user := getUser(r)
|
||||||
|
if user.LoggedIn {
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
locale := getLocale(r)
|
||||||
|
form := newLoginForm(locale)
|
||||||
|
if err := form.Parse(r); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if form.Validate() {
|
||||||
|
conn := getConn(r)
|
||||||
|
cookie := conn.MustGetText(r.Context(), "", "select login($1, $2, $3)", form.Email, form.Password, remoteAddr(r))
|
||||||
|
if cookie != "" {
|
||||||
|
setSessionCookie(w, cookie)
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
locale := getLocale(r)
|
form.Errors = append(form.Errors, errors.New(gettext("Invalid user or password.", locale)))
|
||||||
form := newLoginForm(locale)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
if r.Method == "POST" {
|
} else {
|
||||||
if err := form.Parse(r); err != nil {
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
}
|
||||||
return
|
mustRenderLoginForm(w, r, form)
|
||||||
}
|
|
||||||
if form.Validate() {
|
|
||||||
conn := getConn(r)
|
|
||||||
cookie := conn.MustGetText(r.Context(), "", "select login($1, $2, $3)", form.Email, form.Password, remoteAddr(r))
|
|
||||||
if cookie != "" {
|
|
||||||
setSessionCookie(w, cookie)
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
form.Errors = append(form.Errors, errors.New(gettext("Invalid user or password.", locale)))
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mustRenderWebTemplate(w, r, "login.gohtml", form)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogoutHandler() http.Handler {
|
func mustRenderLoginForm(w http.ResponseWriter, r *http.Request, form *loginForm) {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mustRenderWebTemplate(w, r, "login.gohtml", form)
|
||||||
if err := verifyCsrfTokenValid(r); err != nil {
|
}
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
|
||||||
return
|
func HandleLogout(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
}
|
if err := verifyCsrfTokenValid(r); err != nil {
|
||||||
conn := getConn(r)
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
conn.MustExec(r.Context(), "select logout()")
|
return
|
||||||
http.SetCookie(w, createSessionCookie("", -24*time.Hour))
|
}
|
||||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
conn := getConn(r)
|
||||||
})
|
conn.MustExec(r.Context(), "select logout()")
|
||||||
|
http.SetCookie(w, createSessionCookie("", -24*time.Hour))
|
||||||
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
func remoteAddr(r *http.Request) string {
|
func remoteAddr(r *http.Request) string {
|
||||||
|
@ -148,7 +159,7 @@ type AppUser struct {
|
||||||
CsrfToken string
|
CsrfToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckLogin(db *Db, next http.Handler) http.Handler {
|
func LoginChecker(db *Db, next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var ctx = r.Context()
|
var ctx = r.Context()
|
||||||
if cookie, err := r.Cookie(sessionCookie); err == nil {
|
if cookie, err := r.Cookie(sessionCookie); err == nil {
|
||||||
|
@ -195,13 +206,13 @@ func getConn(r *http.Request) *Conn {
|
||||||
return r.Context().Value(ContextConnKey).(*Conn)
|
return r.Context().Value(ContextConnKey).(*Conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Authenticated(next http.Handler) http.Handler {
|
func Authenticated(next httprouter.Handle) httprouter.Handle {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
user := getUser(r)
|
user := getUser(r)
|
||||||
if user.LoggedIn {
|
if user.LoggedIn {
|
||||||
next.ServeHTTP(w, r)
|
next(w, r, params)
|
||||||
} else {
|
} else {
|
||||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
@ -93,38 +94,46 @@ func (form *profileForm) Validate() bool {
|
||||||
return validator.AllOK()
|
return validator.AllOK()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProfileHandler() http.Handler {
|
func GetProfileForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
user := getUser(r)
|
||||||
user := getUser(r)
|
conn := getConn(r)
|
||||||
conn := getConn(r)
|
locale := getLocale(r)
|
||||||
locale := getLocale(r)
|
form := newProfileForm(r.Context(), conn, locale)
|
||||||
form := newProfileForm(r.Context(), conn, locale)
|
form.Name.Val = conn.MustGetText(r.Context(), "", "select name from user_profile")
|
||||||
if r.Method == "POST" {
|
form.Email.Val = user.Email
|
||||||
if err := form.Parse(r); err != nil {
|
form.Language.Selected = user.Language.String()
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
mustRenderProfileForm(w, r, form)
|
||||||
if err := verifyCsrfTokenValid(r); err != nil {
|
}
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
|
||||||
return
|
func HandleProfileForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
}
|
conn := getConn(r)
|
||||||
if ok := form.Validate(); ok {
|
locale := getLocale(r)
|
||||||
//goland:noinspection SqlWithoutWhere
|
form := newProfileForm(r.Context(), conn, locale)
|
||||||
cookie := conn.MustGetText(r.Context(), "", "update user_profile set name = $1, email = $2, lang_tag = $3 returning build_cookie()", form.Name, form.Email, form.Language)
|
if err := form.Parse(r); err != nil {
|
||||||
setSessionCookie(w, cookie)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
if form.Password.Val != "" {
|
return
|
||||||
conn.MustExec(r.Context(), "select change_password($1)", form.Password)
|
}
|
||||||
}
|
if err := verifyCsrfTokenValid(r); err != nil {
|
||||||
company := getCompany(r)
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
http.Redirect(w, r, "/company/"+company.Slug+"/profile", http.StatusSeeOther)
|
return
|
||||||
return
|
}
|
||||||
}
|
if ok := form.Validate(); !ok {
|
||||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||||
} else {
|
mustRenderProfileForm(w, r, form)
|
||||||
form.Name.Val = conn.MustGetText(r.Context(), "", "select name from user_profile")
|
return
|
||||||
form.Email.Val = user.Email
|
}
|
||||||
form.Language.Selected = user.Language.String()
|
//goland:noinspection SqlWithoutWhere
|
||||||
}
|
cookie := conn.MustGetText(r.Context(), "", "update user_profile set name = $1, email = $2, lang_tag = $3 returning build_cookie()", form.Name, form.Email, form.Language)
|
||||||
mustRenderAppTemplate(w, r, "profile.gohtml", form)
|
setSessionCookie(w, cookie)
|
||||||
})
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustRenderProfileForm(w http.ResponseWriter, r *http.Request, form *profileForm) {
|
||||||
|
mustRenderAppTemplate(w, r, "profile.gohtml", form)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,43 +2,73 @@ package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRouter(db *Db) http.Handler {
|
func NewRouter(db *Db) http.Handler {
|
||||||
companyRouter := http.NewServeMux()
|
companyRouter := httprouter.New()
|
||||||
companyRouter.Handle("/tax-details", CompanyTaxDetailsHandler())
|
companyRouter.GET("/profile", GetProfileForm)
|
||||||
companyRouter.Handle("/tax/", CompanyTaxHandler())
|
companyRouter.POST("/profile", HandleProfileForm)
|
||||||
companyRouter.Handle("/tax", CompanyTaxHandler())
|
companyRouter.GET("/tax-details", GetCompanyTaxDetailsForm)
|
||||||
companyRouter.Handle("/profile", ProfileHandler())
|
companyRouter.POST("/tax-details", HandleCompanyTaxDetailsForm)
|
||||||
companyRouter.Handle("/contacts/new", NewContactHandler())
|
companyRouter.POST("/tax", HandleAddCompanyTax)
|
||||||
companyRouter.Handle("/contacts", ContactsHandler())
|
companyRouter.DELETE("/tax/:taxId", HandleDeleteCompanyTax)
|
||||||
companyRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
companyRouter.GET("/contacts", IndexContacts)
|
||||||
|
companyRouter.POST("/contacts", HandleAddContact)
|
||||||
|
companyRouter.GET("/contacts/:slug", GetContactForm)
|
||||||
|
companyRouter.PUT("/contacts/:slug", HandleUpdateContact)
|
||||||
|
companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
mustRenderAppTemplate(w, r, "dashboard.gohtml", nil)
|
mustRenderAppTemplate(w, r, "dashboard.gohtml", nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
router := http.NewServeMux()
|
router := httprouter.New()
|
||||||
router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))
|
router.ServeFiles("/static/*filepath", http.Dir("web/static"))
|
||||||
router.Handle("/login", LoginHandler())
|
router.GET("/login", GetLoginForm)
|
||||||
router.Handle("/logout", Authenticated(LogoutHandler()))
|
router.POST("/login", HandleLoginForm)
|
||||||
router.Handle("/company/", Authenticated(http.StripPrefix("/company/", CompanyHandler(companyRouter))))
|
router.POST("/logout", Authenticated(HandleLogout))
|
||||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
companyHandler := Authenticated(CompanyHandler(companyRouter))
|
||||||
|
router.GET("/company/:slug/*rest", companyHandler)
|
||||||
|
router.POST("/company/:slug/*rest", companyHandler)
|
||||||
|
router.PUT("/company/:slug/*rest", companyHandler)
|
||||||
|
router.DELETE("/company/:slug/*rest", companyHandler)
|
||||||
|
|
||||||
|
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
user := getUser(r)
|
user := getUser(r)
|
||||||
if user.LoggedIn {
|
if user.LoggedIn {
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
var slug string
|
slug := conn.MustGetText(r.Context(), "", "select slug::text from company order by company_id limit 1")
|
||||||
err := conn.QueryRow(r.Context(), "select slug::text from company order by company_id limit 1").Scan(&slug)
|
http.Redirect(w, r, "/company/"+slug+"/", http.StatusFound)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
http.Redirect(w, r, "/company/"+slug, http.StatusFound)
|
|
||||||
} else {
|
} else {
|
||||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var handler http.Handler = router
|
var handler http.Handler = router
|
||||||
handler = SetLocale(db, handler)
|
handler = MethodOverrider(handler)
|
||||||
handler = CheckLogin(db, handler)
|
handler = LocaleSetter(db, handler)
|
||||||
|
handler = LoginChecker(db, handler)
|
||||||
handler = Recoverer(handler)
|
handler = Recoverer(handler)
|
||||||
handler = Logger(handler)
|
handler = Logger(handler)
|
||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MethodOverrider(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
override := r.FormValue(overrideMethodName)
|
||||||
|
if override == http.MethodDelete || override == http.MethodPut {
|
||||||
|
r2 := new(http.Request)
|
||||||
|
*r2 = *r
|
||||||
|
r2.Method = override
|
||||||
|
r = r2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const overrideMethodName = "_method"
|
||||||
|
|
||||||
func templateFile(name string) string {
|
func templateFile(name string) string {
|
||||||
return "web/template/" + name
|
return "web/template/" + name
|
||||||
}
|
}
|
||||||
|
@ -39,6 +41,12 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s
|
||||||
field.Attributes = append(field.Attributes, template.HTMLAttr(attr))
|
field.Attributes = append(field.Attributes, template.HTMLAttr(attr))
|
||||||
return field
|
return field
|
||||||
},
|
},
|
||||||
|
"deleteMethod": func() template.HTML {
|
||||||
|
return overrideMethodField(http.MethodDelete)
|
||||||
|
},
|
||||||
|
"putMethod": func() template.HTML {
|
||||||
|
return overrideMethodField(http.MethodPut)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if _, err := t.ParseFiles(templateFile(filename), templateFile(layout), templateFile("form.gohtml")); err != nil {
|
if _, err := t.ParseFiles(templateFile(filename), templateFile(layout), templateFile("form.gohtml")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -48,6 +56,9 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func overrideMethodField(method string) template.HTML {
|
||||||
|
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, overrideMethodName, method))
|
||||||
|
}
|
||||||
func mustRenderAppTemplate(w io.Writer, r *http.Request, filename string, data interface{}) {
|
func mustRenderAppTemplate(w io.Writer, r *http.Request, filename string, data interface{}) {
|
||||||
mustRenderTemplate(w, r, "app.gohtml", filename, data)
|
mustRenderTemplate(w, r, "app.gohtml", filename, data)
|
||||||
}
|
}
|
||||||
|
|
295
po/ca.po
295
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-01 11:26+0100\n"
|
"POT-Creation-Date: 2023-02-03 13:57+0100\n"
|
||||||
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
||||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||||
|
@ -32,17 +32,17 @@ msgctxt "menu"
|
||||||
msgid "Tax Details"
|
msgid "Tax Details"
|
||||||
msgstr "Configuració fiscal"
|
msgstr "Configuració fiscal"
|
||||||
|
|
||||||
#: web/template/app.gohtml:33
|
#: web/template/app.gohtml:34
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Logout"
|
msgid "Logout"
|
||||||
msgstr "Surt"
|
msgstr "Surt"
|
||||||
|
|
||||||
#: web/template/app.gohtml:42
|
#: web/template/app.gohtml:43
|
||||||
msgctxt "nav"
|
msgctxt "nav"
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Tauler"
|
msgstr "Tauler"
|
||||||
|
|
||||||
#: web/template/app.gohtml:43
|
#: web/template/app.gohtml:44
|
||||||
msgctxt "nav"
|
msgctxt "nav"
|
||||||
msgid "Contacts"
|
msgid "Contacts"
|
||||||
msgstr "Contactes"
|
msgstr "Contactes"
|
||||||
|
@ -57,223 +57,312 @@ msgctxt "action"
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "Entra"
|
msgstr "Entra"
|
||||||
|
|
||||||
#: web/template/profile.gohtml:2 web/template/profile.gohtml:7
|
#: web/template/profile.gohtml:2 web/template/profile.gohtml:10
|
||||||
|
#: web/template/profile.gohtml:14
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "User Settings"
|
msgid "User Settings"
|
||||||
msgstr "Configuració usuari"
|
msgstr "Configuració usuari"
|
||||||
|
|
||||||
#: web/template/profile.gohtml:10
|
#: 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"
|
msgctxt "title"
|
||||||
msgid "User Access Data"
|
msgid "User Access Data"
|
||||||
msgstr "Dades accés usuari"
|
msgstr "Dades accés usuari"
|
||||||
|
|
||||||
#: web/template/profile.gohtml:16
|
#: web/template/profile.gohtml:24
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Password Change"
|
msgid "Password Change"
|
||||||
msgstr "Canvi contrasenya"
|
msgstr "Canvi contrasenya"
|
||||||
|
|
||||||
#: web/template/profile.gohtml:23
|
#: web/template/profile.gohtml:31
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Idioma"
|
msgstr "Idioma"
|
||||||
|
|
||||||
#: web/template/profile.gohtml:27 web/template/tax-details.gohtml:133
|
#: web/template/profile.gohtml:35 web/template/tax-details.gohtml:96
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Save changes"
|
msgid "Save changes"
|
||||||
msgstr "Desa canvis"
|
msgstr "Desa canvis"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:2
|
#: 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"
|
msgctxt "title"
|
||||||
msgid "Contacts"
|
msgid "Contacts"
|
||||||
msgstr "Contactes"
|
msgstr "Contactes"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:6 web/template/contacts-new.gohtml:60
|
#: 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"
|
msgctxt "action"
|
||||||
msgid "New contact"
|
msgid "New contact"
|
||||||
msgstr "Nou contacte"
|
msgstr "Nou contacte"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:11
|
#: web/template/contacts-index.gohtml:20
|
||||||
msgctxt "contact"
|
msgctxt "contact"
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Tots"
|
msgstr "Tots"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:12
|
#: web/template/contacts-index.gohtml:21
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Customer"
|
msgid "Customer"
|
||||||
msgstr "Client"
|
msgstr "Client"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:13
|
#: web/template/contacts-index.gohtml:22
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Correu-e"
|
msgstr "Correu-e"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:14
|
#: web/template/contacts-index.gohtml:23
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Phone"
|
msgid "Phone"
|
||||||
msgstr "Telèfon"
|
msgstr "Telèfon"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:29
|
#: web/template/contacts-index.gohtml:38
|
||||||
msgid "No contacts added yet."
|
msgid "No contacts added yet."
|
||||||
msgstr "No hi ha cap contacte."
|
msgstr "No hi ha cap contacte."
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:2 web/template/tax-details.gohtml:7
|
#: web/template/tax-details.gohtml:2 web/template/tax-details.gohtml:9
|
||||||
|
#: web/template/tax-details.gohtml:13
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Tax Details"
|
msgid "Tax Details"
|
||||||
msgstr "Configuració fiscal"
|
msgstr "Configuració fiscal"
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:11 web/template/contacts-new.gohtml:11
|
#: web/template/tax-details.gohtml:31
|
||||||
msgctxt "input"
|
msgctxt "title"
|
||||||
msgid "Business name"
|
|
||||||
msgstr "Nom i cognom"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:15 web/template/contacts-new.gohtml:15
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "VAT number"
|
|
||||||
msgstr "DNI / NIF"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:19 web/template/contacts-new.gohtml:19
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Trade name"
|
|
||||||
msgstr "Nom comercial"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:23 web/template/contacts-new.gohtml:23
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Phone"
|
|
||||||
msgstr "Telèfon"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:27 web/template/contacts-new.gohtml:27
|
|
||||||
#: pkg/login.go:33 pkg/profile.go:35
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Email"
|
|
||||||
msgstr "Correu-e"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:31 web/template/contacts-new.gohtml:31
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Web"
|
|
||||||
msgstr "Web"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:35 web/template/contacts-new.gohtml:35
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Address"
|
|
||||||
msgstr "Adreça"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:39 web/template/contacts-new.gohtml:39
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "City"
|
|
||||||
msgstr "Població"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:43 web/template/contacts-new.gohtml:43
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Province"
|
|
||||||
msgstr "Província"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:47 web/template/contacts-new.gohtml:47
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Postal code"
|
|
||||||
msgstr "Codi postal"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:56 web/template/contacts-new.gohtml:56
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Country"
|
|
||||||
msgstr "País"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:60
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Currency"
|
msgid "Currency"
|
||||||
msgstr "Moneda"
|
msgstr "Moneda"
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:78
|
#: web/template/tax-details.gohtml:47
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Tax Name"
|
msgid "Tax Name"
|
||||||
msgstr "Nom import"
|
msgstr "Nom import"
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:79
|
#: web/template/tax-details.gohtml:48
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Rate (%)"
|
msgid "Rate (%)"
|
||||||
msgstr "Percentatge"
|
msgstr "Percentatge"
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:100
|
#: web/template/tax-details.gohtml:71
|
||||||
msgid "No taxes added yet."
|
msgid "No taxes added yet."
|
||||||
msgstr "No hi ha cap impost."
|
msgstr "No hi ha cap impost."
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:106
|
#: web/template/tax-details.gohtml:77
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "New Line"
|
msgid "New Line"
|
||||||
msgstr "Nova línia"
|
msgstr "Nova línia"
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:111
|
#: web/template/tax-details.gohtml:88
|
||||||
msgctxt "input"
|
|
||||||
msgid "Tax name"
|
|
||||||
msgstr "Nom impost"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:118
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Rate (%)"
|
|
||||||
msgstr "Percentatge"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:125
|
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Add new tax"
|
msgid "Add new tax"
|
||||||
msgstr "Afegeix nou impost"
|
msgstr "Afegeix nou impost"
|
||||||
|
|
||||||
#: web/template/contacts-new.gohtml:2 web/template/contacts-new.gohtml:7
|
#: web/template/contacts-new.gohtml:2 web/template/contacts-new.gohtml:11
|
||||||
|
#: web/template/contacts-new.gohtml:15
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "New Contact"
|
msgid "New Contact"
|
||||||
msgstr "Nou contacte"
|
msgstr "Nou contacte"
|
||||||
|
|
||||||
#: pkg/login.go:44 pkg/profile.go:41
|
#: pkg/login.go:36 pkg/profile.go:40 pkg/contacts.go:179
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Email"
|
||||||
|
msgstr "Correu-e"
|
||||||
|
|
||||||
|
#: pkg/login.go:47 pkg/profile.go:49
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Contrasenya"
|
msgstr "Contrasenya"
|
||||||
|
|
||||||
#: pkg/login.go:66 pkg/profile.go:71
|
#: pkg/login.go:69 pkg/profile.go:88 pkg/contacts.go:263
|
||||||
msgid "Email can not be empty."
|
msgid "Email can not be empty."
|
||||||
msgstr "No podeu deixar el correu-e en blanc."
|
msgstr "No podeu deixar el correu-e en blanc."
|
||||||
|
|
||||||
#: pkg/login.go:67 pkg/profile.go:72
|
#: pkg/login.go:70 pkg/profile.go:89 pkg/contacts.go:264
|
||||||
msgid "This value is not a valid email. It should be like name@domain.com."
|
msgid "This value is not a valid email. It should be like name@domain.com."
|
||||||
msgstr "Aquest valor no és un correu-e vàlid. Hauria de ser similar a nom@domini.cat."
|
msgstr "Aquest valor no és un correu-e vàlid. Hauria de ser similar a nom@domini.cat."
|
||||||
|
|
||||||
#: pkg/login.go:69
|
#: pkg/login.go:72
|
||||||
msgid "Password can not be empty."
|
msgid "Password can not be empty."
|
||||||
msgstr "No podeu deixar la contrasenya en blanc."
|
msgstr "No podeu deixar la contrasenya en blanc."
|
||||||
|
|
||||||
#: pkg/login.go:95
|
#: pkg/login.go:108
|
||||||
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/profile.go:23
|
#: pkg/company.go:78
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Currency"
|
||||||
|
msgstr "Moneda"
|
||||||
|
|
||||||
|
#: pkg/company.go:95
|
||||||
|
msgid "Selected currency is not valid."
|
||||||
|
msgstr "Heu seleccionat una moneda que no és vàlida."
|
||||||
|
|
||||||
|
#: pkg/company.go:217
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Tax name"
|
||||||
|
msgstr "Nom impost"
|
||||||
|
|
||||||
|
#: pkg/company.go:223
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Rate (%)"
|
||||||
|
msgstr "Percentatge"
|
||||||
|
|
||||||
|
#: pkg/company.go:245
|
||||||
|
msgid "Tax name can not be empty."
|
||||||
|
msgstr "No podeu deixar el nom de l’impost en blanc."
|
||||||
|
|
||||||
|
#: pkg/company.go:246
|
||||||
|
msgid "Tax rate can not be empty."
|
||||||
|
msgstr "No podeu deixar percentatge en blanc."
|
||||||
|
|
||||||
|
#: pkg/company.go:247
|
||||||
|
msgid "Tax rate must be an integer between -99 and 99."
|
||||||
|
msgstr "El percentatge ha de ser entre -99 i 99."
|
||||||
|
|
||||||
|
#: pkg/profile.go:25
|
||||||
msgctxt "language option"
|
msgctxt "language option"
|
||||||
msgid "Automatic"
|
msgid "Automatic"
|
||||||
msgstr "Automàtic"
|
msgstr "Automàtic"
|
||||||
|
|
||||||
#: pkg/profile.go:29
|
#: pkg/profile.go:31
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "User name"
|
msgid "User name"
|
||||||
msgstr "Nom d’usuari"
|
msgstr "Nom d’usuari"
|
||||||
|
|
||||||
#: pkg/profile.go:46
|
#: pkg/profile.go:57
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Password Confirmation"
|
msgid "Password Confirmation"
|
||||||
msgstr "Confirmació contrasenya"
|
msgstr "Confirmació contrasenya"
|
||||||
|
|
||||||
#: pkg/profile.go:51
|
#: pkg/profile.go:65
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Idioma"
|
msgstr "Idioma"
|
||||||
|
|
||||||
#: pkg/profile.go:74
|
#: pkg/profile.go:91
|
||||||
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/profile.go:75
|
#: pkg/profile.go:92
|
||||||
msgid "Confirmation does not match password."
|
msgid "Confirmation does not match password."
|
||||||
msgstr "La confirmació no és igual a la contrasenya."
|
msgstr "La confirmació no és igual a la contrasenya."
|
||||||
|
|
||||||
#: pkg/profile.go:76
|
#: pkg/profile.go:93
|
||||||
msgid "Selected language is not valid."
|
msgid "Selected language is not valid."
|
||||||
msgstr "Heu seleccionat un idioma que no és vàlid."
|
msgstr "Heu seleccionat un idioma que no és vàlid."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:150
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Business name"
|
||||||
|
msgstr "Nom i cognoms"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:159
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "VAT number"
|
||||||
|
msgstr "DNI / NIF"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:165
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Trade name"
|
||||||
|
msgstr "Nom comercial"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:170
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Phone"
|
||||||
|
msgstr "Telèfon"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:188
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Web"
|
||||||
|
msgstr "Web"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:196
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Address"
|
||||||
|
msgstr "Adreça"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:205
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "City"
|
||||||
|
msgstr "Població"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:211
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Province"
|
||||||
|
msgstr "Província"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:217
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Postal code"
|
||||||
|
msgstr "Codi postal"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:226
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Country"
|
||||||
|
msgstr "País"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:256
|
||||||
|
msgid "Business name can not be empty."
|
||||||
|
msgstr "No podeu deixar el nom i els cognoms en blanc."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:257
|
||||||
|
msgid "VAT number can not be empty."
|
||||||
|
msgstr "No podeu deixar el DNI o NIF en blanc."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:258
|
||||||
|
msgid "This value is not a valid VAT number."
|
||||||
|
msgstr "Aquest valor no és un DNI o NIF vàlid."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:260
|
||||||
|
msgid "Phone can not be empty."
|
||||||
|
msgstr "No podeu deixar el telèfon en blanc."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:261
|
||||||
|
msgid "This value is not a valid phone number."
|
||||||
|
msgstr "Aquest valor no és un telèfon vàlid."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:267
|
||||||
|
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
|
||||||
|
msgid "Address can not be empty."
|
||||||
|
msgstr "No podeu deixar l’adreça en blanc."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:270
|
||||||
|
msgid "City can not be empty."
|
||||||
|
msgstr "No podeu deixar la població en blanc."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:271
|
||||||
|
msgid "Province can not be empty."
|
||||||
|
msgstr "No podeu deixar la província en blanc."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:272
|
||||||
|
msgid "Postal code can not be empty."
|
||||||
|
msgstr "No podeu deixar el codi postal en blanc."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:273
|
||||||
|
msgid "This value is not a valid postal code."
|
||||||
|
msgstr "Aquest valor no és un codi postal vàlid."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:275
|
||||||
|
msgid "Selected country is not valid."
|
||||||
|
msgstr "Heu seleccionat un país que no és vàlid."
|
||||||
|
|
||||||
#~ msgctxt "nav"
|
#~ msgctxt "nav"
|
||||||
#~ msgid "Customers"
|
#~ msgid "Customers"
|
||||||
#~ msgstr "Clients"
|
#~ msgstr "Clients"
|
||||||
|
|
301
po/es.po
301
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-01 11:26+0100\n"
|
"POT-Creation-Date: 2023-02-03 13:57+0100\n"
|
||||||
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
||||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||||
|
@ -32,17 +32,17 @@ msgctxt "menu"
|
||||||
msgid "Tax Details"
|
msgid "Tax Details"
|
||||||
msgstr "Configuración fiscal"
|
msgstr "Configuración fiscal"
|
||||||
|
|
||||||
#: web/template/app.gohtml:33
|
#: web/template/app.gohtml:34
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Logout"
|
msgid "Logout"
|
||||||
msgstr "Salir"
|
msgstr "Salir"
|
||||||
|
|
||||||
#: web/template/app.gohtml:42
|
#: web/template/app.gohtml:43
|
||||||
msgctxt "nav"
|
msgctxt "nav"
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Panel"
|
msgstr "Panel"
|
||||||
|
|
||||||
#: web/template/app.gohtml:43
|
#: web/template/app.gohtml:44
|
||||||
msgctxt "nav"
|
msgctxt "nav"
|
||||||
msgid "Contacts"
|
msgid "Contacts"
|
||||||
msgstr "Contactos"
|
msgstr "Contactos"
|
||||||
|
@ -57,223 +57,312 @@ msgctxt "action"
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "Entrar"
|
msgstr "Entrar"
|
||||||
|
|
||||||
#: web/template/profile.gohtml:2 web/template/profile.gohtml:7
|
#: web/template/profile.gohtml:2 web/template/profile.gohtml:10
|
||||||
|
#: web/template/profile.gohtml:14
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "User Settings"
|
msgid "User Settings"
|
||||||
msgstr "Configuración usuario"
|
msgstr "Configuración usuario"
|
||||||
|
|
||||||
#: web/template/profile.gohtml:10
|
#: 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"
|
msgctxt "title"
|
||||||
msgid "User Access Data"
|
msgid "User Access Data"
|
||||||
msgstr "Datos acceso usuario"
|
msgstr "Datos acceso usuario"
|
||||||
|
|
||||||
#: web/template/profile.gohtml:16
|
#: web/template/profile.gohtml:24
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Password Change"
|
msgid "Password Change"
|
||||||
msgstr "Cambio de contraseña"
|
msgstr "Cambio de contraseña"
|
||||||
|
|
||||||
#: web/template/profile.gohtml:23
|
#: web/template/profile.gohtml:31
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Idioma"
|
msgstr "Idioma"
|
||||||
|
|
||||||
#: web/template/profile.gohtml:27 web/template/tax-details.gohtml:133
|
#: web/template/profile.gohtml:35 web/template/tax-details.gohtml:96
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Save changes"
|
msgid "Save changes"
|
||||||
msgstr "Guardar cambios"
|
msgstr "Guardar cambios"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:2
|
#: 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"
|
msgctxt "title"
|
||||||
msgid "Contacts"
|
msgid "Contacts"
|
||||||
msgstr "Contactos"
|
msgstr "Contactos"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:6 web/template/contacts-new.gohtml:60
|
#: 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"
|
msgctxt "action"
|
||||||
msgid "New contact"
|
msgid "New contact"
|
||||||
msgstr "Nuevo contacto"
|
msgstr "Nuevo contacto"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:11
|
#: web/template/contacts-index.gohtml:20
|
||||||
msgctxt "contact"
|
msgctxt "contact"
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Todos"
|
msgstr "Todos"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:12
|
#: web/template/contacts-index.gohtml:21
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Customer"
|
msgid "Customer"
|
||||||
msgstr "Cliente"
|
msgstr "Cliente"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:13
|
#: web/template/contacts-index.gohtml:22
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Correo-e"
|
msgstr "Correo-e"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:14
|
#: web/template/contacts-index.gohtml:23
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Phone"
|
msgid "Phone"
|
||||||
msgstr "Teléfono"
|
msgstr "Teléfono"
|
||||||
|
|
||||||
#: web/template/contacts-index.gohtml:29
|
#: web/template/contacts-index.gohtml:38
|
||||||
msgid "No contacts added yet."
|
msgid "No contacts added yet."
|
||||||
msgstr "No hay contactos."
|
msgstr "No hay contactos."
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:2 web/template/tax-details.gohtml:7
|
#: web/template/tax-details.gohtml:2 web/template/tax-details.gohtml:9
|
||||||
|
#: web/template/tax-details.gohtml:13
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Tax Details"
|
msgid "Tax Details"
|
||||||
msgstr "Configuración fiscal"
|
msgstr "Configuración fiscal"
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:11 web/template/contacts-new.gohtml:11
|
#: web/template/tax-details.gohtml:31
|
||||||
msgctxt "input"
|
msgctxt "title"
|
||||||
msgid "Business name"
|
|
||||||
msgstr "Nombre y apellidos"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:15 web/template/contacts-new.gohtml:15
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "VAT number"
|
|
||||||
msgstr "DNI / NIF"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:19 web/template/contacts-new.gohtml:19
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Trade name"
|
|
||||||
msgstr "Nombre comercial"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:23 web/template/contacts-new.gohtml:23
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Phone"
|
|
||||||
msgstr "Teléfono"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:27 web/template/contacts-new.gohtml:27
|
|
||||||
#: pkg/login.go:33 pkg/profile.go:35
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Email"
|
|
||||||
msgstr "Correo-e"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:31 web/template/contacts-new.gohtml:31
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Web"
|
|
||||||
msgstr "Web"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:35 web/template/contacts-new.gohtml:35
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Address"
|
|
||||||
msgstr "Dirección"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:39 web/template/contacts-new.gohtml:39
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "City"
|
|
||||||
msgstr "Población"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:43 web/template/contacts-new.gohtml:43
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Province"
|
|
||||||
msgstr "Provincia"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:47 web/template/contacts-new.gohtml:47
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Postal code"
|
|
||||||
msgstr "Código postal"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:56 web/template/contacts-new.gohtml:56
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Country"
|
|
||||||
msgstr "País"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:60
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Currency"
|
msgid "Currency"
|
||||||
msgstr "Moneda"
|
msgstr "Moneda"
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:78
|
#: web/template/tax-details.gohtml:47
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Tax Name"
|
msgid "Tax Name"
|
||||||
msgstr "Nombre impuesto"
|
msgstr "Nombre impuesto"
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:79
|
#: web/template/tax-details.gohtml:48
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Rate (%)"
|
msgid "Rate (%)"
|
||||||
msgstr "Porcentage"
|
msgstr "Porcentaje"
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:100
|
#: web/template/tax-details.gohtml:71
|
||||||
msgid "No taxes added yet."
|
msgid "No taxes added yet."
|
||||||
msgstr "No hay impuestos."
|
msgstr "No hay impuestos."
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:106
|
#: web/template/tax-details.gohtml:77
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "New Line"
|
msgid "New Line"
|
||||||
msgstr "Nueva línea"
|
msgstr "Nueva línea"
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:111
|
#: web/template/tax-details.gohtml:88
|
||||||
msgctxt "input"
|
|
||||||
msgid "Tax name"
|
|
||||||
msgstr "Nombre impuesto"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:118
|
|
||||||
msgctxt "input"
|
|
||||||
msgid "Rate (%)"
|
|
||||||
msgstr "Porcentage"
|
|
||||||
|
|
||||||
#: web/template/tax-details.gohtml:125
|
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Add new tax"
|
msgid "Add new tax"
|
||||||
msgstr "Añadir nuevo impuesto"
|
msgstr "Añadir nuevo impuesto"
|
||||||
|
|
||||||
#: web/template/contacts-new.gohtml:2 web/template/contacts-new.gohtml:7
|
#: web/template/contacts-new.gohtml:2 web/template/contacts-new.gohtml:11
|
||||||
|
#: web/template/contacts-new.gohtml:15
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "New Contact"
|
msgid "New Contact"
|
||||||
msgstr "Nuevo contacto"
|
msgstr "Nuevo contacto"
|
||||||
|
|
||||||
#: pkg/login.go:44 pkg/profile.go:41
|
#: pkg/login.go:36 pkg/profile.go:40 pkg/contacts.go:179
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Email"
|
||||||
|
msgstr "Correo-e"
|
||||||
|
|
||||||
|
#: pkg/login.go:47 pkg/profile.go:49
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Contraseña"
|
msgstr "Contraseña"
|
||||||
|
|
||||||
#: pkg/login.go:66 pkg/profile.go:71
|
#: pkg/login.go:69 pkg/profile.go:88 pkg/contacts.go:263
|
||||||
msgid "Email can not be empty."
|
msgid "Email can not be empty."
|
||||||
msgstr "No podéis dejar el correo-e en blanco."
|
msgstr "No podéis dejar el correo-e en blanco."
|
||||||
|
|
||||||
#: pkg/login.go:67 pkg/profile.go:72
|
#: pkg/login.go:70 pkg/profile.go:89 pkg/contacts.go:264
|
||||||
msgid "This value is not a valid email. It should be like name@domain.com."
|
msgid "This value is not a valid email. It should be like name@domain.com."
|
||||||
msgstr "Este valor no es un correo-e válido. Tiene que ser parecido a nombre@dominio.es."
|
msgstr "Este valor no es un correo-e válido. Tiene que ser parecido a nombre@dominio.es."
|
||||||
|
|
||||||
#: pkg/login.go:69
|
#: pkg/login.go:72
|
||||||
msgid "Password can not be empty."
|
msgid "Password can not be empty."
|
||||||
msgstr "No podéis dejar la contaseña en blanco."
|
msgstr "No podéis dejar la contraseña en blanco."
|
||||||
|
|
||||||
#: pkg/login.go:95
|
#: pkg/login.go:108
|
||||||
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/profile.go:23
|
#: pkg/company.go:78
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Currency"
|
||||||
|
msgstr "Moneda"
|
||||||
|
|
||||||
|
#: pkg/company.go:95
|
||||||
|
msgid "Selected currency is not valid."
|
||||||
|
msgstr "Habéis escogido una moneda que no es válida."
|
||||||
|
|
||||||
|
#: pkg/company.go:217
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Tax name"
|
||||||
|
msgstr "Nombre impuesto"
|
||||||
|
|
||||||
|
#: pkg/company.go:223
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Rate (%)"
|
||||||
|
msgstr "Porcentaje"
|
||||||
|
|
||||||
|
#: pkg/company.go:245
|
||||||
|
msgid "Tax name can not be empty."
|
||||||
|
msgstr "No podéis dejar el nombre del impuesto en blanco."
|
||||||
|
|
||||||
|
#: pkg/company.go:246
|
||||||
|
msgid "Tax rate can not be empty."
|
||||||
|
msgstr "No podéis dejar el porcentaje en blanco."
|
||||||
|
|
||||||
|
#: pkg/company.go:247
|
||||||
|
msgid "Tax rate must be an integer between -99 and 99."
|
||||||
|
msgstr "El porcentaje tiene que estar entre -99 y 99."
|
||||||
|
|
||||||
|
#: pkg/profile.go:25
|
||||||
msgctxt "language option"
|
msgctxt "language option"
|
||||||
msgid "Automatic"
|
msgid "Automatic"
|
||||||
msgstr "Automático"
|
msgstr "Automático"
|
||||||
|
|
||||||
#: pkg/profile.go:29
|
#: pkg/profile.go:31
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "User name"
|
msgid "User name"
|
||||||
msgstr "Nombre de usuario"
|
msgstr "Nombre de usuario"
|
||||||
|
|
||||||
#: pkg/profile.go:46
|
#: pkg/profile.go:57
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Password Confirmation"
|
msgid "Password Confirmation"
|
||||||
msgstr "Confirmación contrasenya"
|
msgstr "Confirmación contraseña"
|
||||||
|
|
||||||
#: pkg/profile.go:51
|
#: pkg/profile.go:65
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Idioma"
|
msgstr "Idioma"
|
||||||
|
|
||||||
#: pkg/profile.go:74
|
#: pkg/profile.go:91
|
||||||
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/profile.go:75
|
#: pkg/profile.go:92
|
||||||
msgid "Confirmation does not match password."
|
msgid "Confirmation does not match password."
|
||||||
msgstr "La confirmación no corresponde con la contraseña."
|
msgstr "La confirmación no corresponde con la contraseña."
|
||||||
|
|
||||||
#: pkg/profile.go:76
|
#: pkg/profile.go:93
|
||||||
msgid "Selected language is not valid."
|
msgid "Selected language is not valid."
|
||||||
msgstr "Habéis escogido un idioma que no es válido."
|
msgstr "Habéis escogido un idioma que no es válido."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:150
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Business name"
|
||||||
|
msgstr "Nombre y apellidos"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:159
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "VAT number"
|
||||||
|
msgstr "DNI / NIF"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:165
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Trade name"
|
||||||
|
msgstr "Nombre comercial"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:170
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Phone"
|
||||||
|
msgstr "Teléfono"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:188
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Web"
|
||||||
|
msgstr "Web"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:196
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Address"
|
||||||
|
msgstr "Dirección"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:205
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "City"
|
||||||
|
msgstr "Población"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:211
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Province"
|
||||||
|
msgstr "Provincia"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:217
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Postal code"
|
||||||
|
msgstr "Código postal"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:226
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Country"
|
||||||
|
msgstr "País"
|
||||||
|
|
||||||
|
#: pkg/contacts.go:256
|
||||||
|
msgid "Business name can not be empty."
|
||||||
|
msgstr "No podéis dejar el nombre y los apellidos en blanco."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:257
|
||||||
|
msgid "VAT number can not be empty."
|
||||||
|
msgstr "No podéis dejar el DNI o NIF en blanco."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:258
|
||||||
|
msgid "This value is not a valid VAT number."
|
||||||
|
msgstr "Este valor no es un DNI o NIF válido."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:260
|
||||||
|
msgid "Phone can not be empty."
|
||||||
|
msgstr "No podéis dejar el teléfono en blanco."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:261
|
||||||
|
msgid "This value is not a valid phone number."
|
||||||
|
msgstr "Este valor no es un teléfono válido."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:267
|
||||||
|
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
|
||||||
|
msgid "Address can not be empty."
|
||||||
|
msgstr "No podéis dejar la dirección en blanco."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:270
|
||||||
|
msgid "City can not be empty."
|
||||||
|
msgstr "No podéis dejar la población en blanco."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:271
|
||||||
|
msgid "Province can not be empty."
|
||||||
|
msgstr "No podéis dejar la provincia en blanco."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:272
|
||||||
|
msgid "Postal code can not be empty."
|
||||||
|
msgstr "No podéis dejar el código postal en blanco."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:273
|
||||||
|
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
|
||||||
|
msgid "Selected country is not valid."
|
||||||
|
msgstr "Habéis escogido un país que no es válido."
|
||||||
|
|
||||||
#~ msgctxt "nav"
|
#~ msgctxt "nav"
|
||||||
#~ msgid "Customers"
|
#~ msgid "Customers"
|
||||||
#~ msgstr "Clientes"
|
#~ msgstr "Clientes"
|
||||||
|
|
11989
web/static/numerus.css
11989
web/static/numerus.css
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
||||||
|
{{ define "title" -}}
|
||||||
|
{{printf (pgettext "Edit Contact “%s”" "title") .BusinessName.Val }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{ define "content" }}
|
||||||
|
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.contactForm*/ -}}
|
||||||
|
<nav>
|
||||||
|
<p>
|
||||||
|
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||||
|
<a href="{{ companyURI "/contacts"}}">{{( pgettext "Contacts" "title" )}}</a> /
|
||||||
|
<a>{{ .BusinessName.Val }}</a>
|
||||||
|
</p>
|
||||||
|
</nav>
|
||||||
|
<section class="dialog-content">
|
||||||
|
<h2>{{printf (pgettext "Edit Contact “%s”" "title") .BusinessName.Val }}</h2>
|
||||||
|
<form method="POST">
|
||||||
|
{{ csrfToken }}
|
||||||
|
{{ putMethod }}
|
||||||
|
|
||||||
|
{{ template "input-field" .BusinessName | addInputAttr "autofocus" }}
|
||||||
|
{{ template "input-field" .VATIN }}
|
||||||
|
{{ template "input-field" .TradeName }}
|
||||||
|
{{ template "input-field" .Phone }}
|
||||||
|
{{ template "input-field" .Email }}
|
||||||
|
{{ template "input-field" .Web }}
|
||||||
|
{{ template "input-field" .Address | addInputAttr `class="width-2x"` }}
|
||||||
|
{{ template "input-field" .City }}
|
||||||
|
{{ template "input-field" .Province }}
|
||||||
|
{{ template "input-field" .PostalCode }}
|
||||||
|
{{ template "select-field" .Country | addSelectAttr `class="width-fixed"` }}
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<button class="primary" type="submit">{{( pgettext "Update contact" "action" )}}</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
{{- end }}
|
|
@ -1,34 +1,43 @@
|
||||||
{{ define "title" -}}
|
{{ define "title" -}}
|
||||||
{{( pgettext "Contacts" "title" )}}
|
{{( pgettext "Contacts" "title" )}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<a class="primary button" href="{{ companyURI "/contacts/new" }}">{{( pgettext "New contact" "action" )}}</a>
|
<nav>
|
||||||
|
<p>
|
||||||
|
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||||
|
<a>{{( pgettext "Contacts" "title" )}}</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a class="primary button"
|
||||||
|
href="{{ companyURI "/contacts/new" }}">{{( pgettext "New contact" "action" )}}</a>
|
||||||
|
</p>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{( pgettext "All" "contact" )}}</th>
|
<th>{{( pgettext "All" "contact" )}}</th>
|
||||||
<th>{{( pgettext "Customer" "title" )}}</th>
|
<th>{{( pgettext "Customer" "title" )}}</th>
|
||||||
<th>{{( pgettext "Email" "title" )}}</th>
|
<th>{{( pgettext "Email" "title" )}}</th>
|
||||||
<th>{{( pgettext "Phone" "title" )}}</th>
|
<th>{{( pgettext "Phone" "title" )}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ with .Contacts }}
|
{{ with .Contacts }}
|
||||||
{{- range $tax := . }}
|
{{- range $tax := . }}
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td>{{ .Name }}</td>
|
<td><a href="{{ companyURI "/contacts/"}}{{ .Slug }}">{{ .Name }}</a></td>
|
||||||
<td>{{ .Email }}</td>
|
<td><a href="mailto:{{ .Email }}">{{ .Email }}</a></td>
|
||||||
<td>{{ .Phone }}</td>
|
<td><a href="tel:{{ .Phone }}">{{ .Phone }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">{{( gettext "No contacts added yet." )}}</td>
|
<td colspan="4">{{( gettext "No contacts added yet." )}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
|
@ -4,6 +4,13 @@
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.contactForm*/ -}}
|
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.contactForm*/ -}}
|
||||||
|
<nav>
|
||||||
|
<p>
|
||||||
|
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||||
|
<a href="{{ companyURI "/contacts"}}">{{( pgettext "Contacts" "title" )}}</a> /
|
||||||
|
<a>{{( pgettext "New Contact" "title" )}}</a>
|
||||||
|
</p>
|
||||||
|
</nav>
|
||||||
<section class="dialog-content">
|
<section class="dialog-content">
|
||||||
<h2>{{(pgettext "New Contact" "title")}}</h2>
|
<h2>{{(pgettext "New Contact" "title")}}</h2>
|
||||||
<form method="POST" action="{{ companyURI "/contacts" }}">
|
<form method="POST" action="{{ companyURI "/contacts" }}">
|
||||||
|
@ -21,7 +28,7 @@
|
||||||
{{ template "select-field" .Country | addSelectAttr `class="width-fixed"` }}
|
{{ template "select-field" .Country | addSelectAttr `class="width-fixed"` }}
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<button type="submit">{{( pgettext "New contact" "action" )}}</button>
|
<button class="primary" type="submit">{{( pgettext "New contact" "action" )}}</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -4,6 +4,12 @@
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.profileForm*/ -}}
|
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.profileForm*/ -}}
|
||||||
|
<nav>
|
||||||
|
<p>
|
||||||
|
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||||
|
<a>{{( pgettext "User Settings" "title" )}}</a>
|
||||||
|
</p>
|
||||||
|
</nav>
|
||||||
<section class="dialog-content">
|
<section class="dialog-content">
|
||||||
<h2>{{(pgettext "User Settings" "title")}}</h2>
|
<h2>{{(pgettext "User Settings" "title")}}</h2>
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
|
|
|
@ -3,6 +3,12 @@
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
|
<nav>
|
||||||
|
<p>
|
||||||
|
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||||
|
<a>{{( pgettext "Tax Details" "title" )}}</a>
|
||||||
|
</p>
|
||||||
|
</nav>
|
||||||
<section class="dialog-content">
|
<section class="dialog-content">
|
||||||
<h2>{{(pgettext "Tax Details" "title")}}</h2>
|
<h2>{{(pgettext "Tax Details" "title")}}</h2>
|
||||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.TaxDetailsPage*/ -}}
|
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.TaxDetailsPage*/ -}}
|
||||||
|
@ -53,7 +59,7 @@
|
||||||
<td>
|
<td>
|
||||||
<form method="POST" action="{{ companyURI "/tax"}}/{{ .Id }}">
|
<form method="POST" action="{{ companyURI "/tax"}}/{{ .Id }}">
|
||||||
{{ csrfToken }}
|
{{ csrfToken }}
|
||||||
<input type="hidden" name="_method" value="DELETE"/>
|
{{ deleteMethod }}
|
||||||
<button class="icon" aria-label="{{( gettext "Delete tax" )}}" type="submit"><i
|
<button class="icon" aria-label="{{( gettext "Delete tax" )}}" type="submit"><i
|
||||||
class="ri-delete-back-2-line"></i></button>
|
class="ri-delete-back-2-line"></i></button>
|
||||||
</form>
|
</form>
|
||||||
|
|
Loading…
Reference in New Issue