Split the tax details “mega dialog” into separate pages

I needed to place the payment accounts section somewhere, and the most
logical place seemed to be that dialog, where users can set up company
parameters.

However, that dialog was already saturated with related, but ultimately
independent forms, and adding the account section would make things
even worse, specially given that we need to be able to edit those
accounts in a separate page.

We agreed to separate that dialog into tabs, which means separate pages.
When i had everything in a separated page, then i did not know how to
actually share the code for the tabs, and decided that, for now, these
“tabs” would be items from the profile menu.  Same function, different
presentation.
This commit is contained in:
jordi fita mas 2024-08-14 04:08:13 +02:00
parent e626c7b4bd
commit f95936c523
11 changed files with 993 additions and 796 deletions

View File

@ -85,24 +85,19 @@ type PaymentMethod struct {
}
type taxDetailsForm struct {
locale *Locale
TradeName *InputField
BusinessName *InputField
VATIN *InputField
Phone *InputField
Email *InputField
Web *InputField
Address *InputField
City *InputField
Province *InputField
PostalCode *InputField
Country *SelectField
Currency *SelectField
InvoiceNumberFormat *InputField
NextInvoiceNumber *InputField
QuoteNumberFormat *InputField
NextQuoteNumber *InputField
LegalDisclaimer *InputField
locale *Locale
TradeName *InputField
BusinessName *InputField
VATIN *InputField
Phone *InputField
Email *InputField
Web *InputField
Address *InputField
City *InputField
Province *InputField
PostalCode *InputField
Country *SelectField
Currency *SelectField
}
func newTaxDetailsForm(ctx context.Context, conn *Conn, locale *Locale) *taxDetailsForm {
@ -202,6 +197,206 @@ func newTaxDetailsForm(ctx context.Context, conn *Conn, locale *Locale) *taxDeta
Required: true,
Selected: []string{"EUR"},
},
}
}
func (form *taxDetailsForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
form.TradeName.FillValue(r)
form.BusinessName.FillValue(r)
form.VATIN.FillValue(r)
form.Phone.FillValue(r)
form.Email.FillValue(r)
form.Web.FillValue(r)
form.Address.FillValue(r)
form.City.FillValue(r)
form.Province.FillValue(r)
form.PostalCode.FillValue(r)
form.Country.FillValue(r)
form.Currency.FillValue(r)
return nil
}
func (form *taxDetailsForm) Validate(ctx context.Context, conn *Conn) bool {
validator := newFormValidator()
country := ""
if validator.CheckValidSelectOption(form.Country, gettext("Selected country is not valid.", form.locale)) {
country = form.Country.Selected[0]
}
validator.CheckRequiredInput(form.BusinessName, gettext("Business name can not be empty.", form.locale))
validator.CheckInputMinLength(form.BusinessName, 2, gettext("Business name must have at least two letters.", form.locale))
if validator.CheckRequiredInput(form.VATIN, gettext("VAT number can not be empty.", form.locale)) {
validator.CheckValidVATINInput(ctx, conn, form.VATIN, country, gettext("This value is not a valid VAT number.", form.locale))
}
if validator.CheckRequiredInput(form.Phone, gettext("Phone can not be empty.", form.locale)) {
validator.CheckValidPhoneInput(ctx, conn, form.Phone, country, gettext("This value is not a valid phone number.", form.locale))
}
if validator.CheckRequiredInput(form.Email, gettext("Email can not be empty.", form.locale)) {
validator.CheckValidEmailInput(form.Email, gettext("This value is not a valid email. It should be like name@domain.com.", form.locale))
}
if form.Web.Val != "" {
validator.CheckValidURL(form.Web, gettext("This value is not a valid web address. It should be like https://domain.com/.", form.locale))
}
validator.CheckRequiredInput(form.Address, gettext("Address can not be empty.", form.locale))
validator.CheckRequiredInput(form.City, gettext("City can not be empty.", form.locale))
validator.CheckRequiredInput(form.Province, gettext("Province can not be empty.", form.locale))
if validator.CheckRequiredInput(form.PostalCode, gettext("Postal code can not be empty.", form.locale)) {
validator.CheckValidPostalCode(ctx, conn, form.PostalCode, country, gettext("This value is not a valid postal code.", form.locale))
}
validator.CheckValidSelectOption(form.Currency, gettext("Selected currency is not valid.", form.locale))
return validator.AllOK()
}
func (form *taxDetailsForm) mustFillFromDatabase(ctx context.Context, conn *Conn, company *Company) *taxDetailsForm {
err := conn.QueryRow(ctx, `
select business_name
, substr(vatin::text, 3)
, trade_name
, phone
, email
, web
, address
, city
, province
, postal_code
, country_code
, currency_code
from company
where company.company_id = $1`, company.Id).Scan(
form.BusinessName,
form.VATIN,
form.TradeName,
form.Phone,
form.Email,
form.Web,
form.Address,
form.City,
form.Province,
form.PostalCode,
form.Country,
form.Currency,
)
if err != nil {
panic(err)
}
return form
}
type TaxDetailsPage struct {
DetailsForm *taxDetailsForm
}
func (page *TaxDetailsPage) MustRender(w http.ResponseWriter, r *http.Request) {
mustRenderMainTemplate(w, r, "company/tax-details.gohtml", page)
}
func GetCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
mustRenderTaxDetailsForm(w, r, newTaxDetailsFormFromDatabase(r))
}
func newTaxDetailsFormFromDatabase(r *http.Request) *taxDetailsForm {
locale := getLocale(r)
conn := getConn(r)
form := newTaxDetailsForm(r.Context(), conn, locale)
company := mustGetCompany(r)
form.mustFillFromDatabase(r.Context(), conn, company)
return form
}
func HandleCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
locale := getLocale(r)
conn := getConn(r)
form := newTaxDetailsForm(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 ok := form.Validate(r.Context(), conn); !ok {
if !IsHTMxRequest(r) {
w.WriteHeader(http.StatusUnprocessableEntity)
}
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)
htmxRedirect(w, r, companyURI(company, "/tax-details"))
}
func mustRenderTaxDetailsForm(w http.ResponseWriter, r *http.Request, form *taxDetailsForm) {
page := &TaxDetailsPage{
DetailsForm: form,
}
page.MustRender(w, r)
}
func mustGetCompany(r *http.Request) *Company {
company := getCompany(r)
if company == nil {
panic(errors.New("company: required but not found"))
}
return company
}
func serveCompanyInvoicingForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
conn := getConn(r)
company := mustGetCompany(r)
locale := getLocale(r)
form := newInvoicingFormFromDatabase(r.Context(), conn, company, locale)
form.MustRender(w, r)
}
type InvoicingForm struct {
locale *Locale
InvoiceNumberFormat *InputField
NextInvoiceNumber *InputField
QuoteNumberFormat *InputField
NextQuoteNumber *InputField
LegalDisclaimer *InputField
}
func newInvoicingForm(locale *Locale) *InvoicingForm {
return &InvoicingForm{
locale: locale,
InvoiceNumberFormat: &InputField{
Name: "invoice_number_format",
Label: pgettext("input", "Invoice number format", locale),
@ -240,82 +435,15 @@ func newTaxDetailsForm(ctx context.Context, conn *Conn, locale *Locale) *taxDeta
}
}
func (form *taxDetailsForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
form.TradeName.FillValue(r)
form.BusinessName.FillValue(r)
form.VATIN.FillValue(r)
form.Phone.FillValue(r)
form.Email.FillValue(r)
form.Web.FillValue(r)
form.Address.FillValue(r)
form.City.FillValue(r)
form.Province.FillValue(r)
form.PostalCode.FillValue(r)
form.Country.FillValue(r)
form.Currency.FillValue(r)
form.InvoiceNumberFormat.FillValue(r)
form.NextInvoiceNumber.FillValue(r)
form.QuoteNumberFormat.FillValue(r)
form.NextQuoteNumber.FillValue(r)
form.LegalDisclaimer.FillValue(r)
return nil
func newInvoicingFormFromDatabase(ctx context.Context, conn *Conn, company *Company, locale *Locale) *InvoicingForm {
form := newInvoicingForm(locale)
form.mustFillFromDatabase(ctx, conn, company)
return form
}
func (form *taxDetailsForm) Validate(ctx context.Context, conn *Conn) bool {
validator := newFormValidator()
country := ""
if validator.CheckValidSelectOption(form.Country, gettext("Selected country is not valid.", form.locale)) {
country = form.Country.Selected[0]
}
validator.CheckRequiredInput(form.BusinessName, gettext("Business name can not be empty.", form.locale))
validator.CheckInputMinLength(form.BusinessName, 2, gettext("Business name must have at least two letters.", form.locale))
if validator.CheckRequiredInput(form.VATIN, gettext("VAT number can not be empty.", form.locale)) {
validator.CheckValidVATINInput(ctx, conn, form.VATIN, country, gettext("This value is not a valid VAT number.", form.locale))
}
if validator.CheckRequiredInput(form.Phone, gettext("Phone can not be empty.", form.locale)) {
validator.CheckValidPhoneInput(ctx, conn, form.Phone, country, gettext("This value is not a valid phone number.", form.locale))
}
if validator.CheckRequiredInput(form.Email, gettext("Email can not be empty.", form.locale)) {
validator.CheckValidEmailInput(form.Email, gettext("This value is not a valid email. It should be like name@domain.com.", form.locale))
}
if form.Web.Val != "" {
validator.CheckValidURL(form.Web, gettext("This value is not a valid web address. It should be like https://domain.com/.", form.locale))
}
validator.CheckRequiredInput(form.Address, gettext("Address can not be empty.", form.locale))
validator.CheckRequiredInput(form.City, gettext("City can not be empty.", form.locale))
validator.CheckRequiredInput(form.Province, gettext("Province can not be empty.", form.locale))
if validator.CheckRequiredInput(form.PostalCode, gettext("Postal code can not be empty.", form.locale)) {
validator.CheckValidPostalCode(ctx, conn, form.PostalCode, country, gettext("This value is not a valid postal code.", form.locale))
}
validator.CheckValidSelectOption(form.Currency, gettext("Selected currency is not valid.", form.locale))
validator.CheckRequiredInput(form.InvoiceNumberFormat, gettext("Invoice number format can not be empty.", form.locale))
validator.CheckValidInteger(form.NextInvoiceNumber, 1, math.MaxInt32, gettext("Next invoice number must be a number greater than zero.", form.locale))
validator.CheckRequiredInput(form.QuoteNumberFormat, gettext("Quotation number format can not be empty.", form.locale))
validator.CheckValidInteger(form.NextQuoteNumber, 1, math.MaxInt32, gettext("Next quotation number must be a number greater than zero.", form.locale))
return validator.AllOK()
}
func (form *taxDetailsForm) mustFillFromDatabase(ctx context.Context, conn *Conn, company *Company) *taxDetailsForm {
func (form *InvoicingForm) mustFillFromDatabase(ctx context.Context, conn *Conn, company *Company) {
err := conn.QueryRow(ctx, `
select business_name
, substr(vatin::text, 3)
, trade_name
, phone
, email
, web
, address
, city
, province
, postal_code
, country_code
, currency_code
, invoice_number_format
select invoice_number_format
, quote_number_format
, legal_disclaimer
, coalesce(invoice_number_counter.currval, 0) + 1
@ -328,18 +456,6 @@ func (form *taxDetailsForm) mustFillFromDatabase(ctx context.Context, conn *Conn
on quote_number_counter.company_id = company.company_id
and quote_number_counter.year = date_part('year', current_date)
where company.company_id = $1`, company.Id).Scan(
form.BusinessName,
form.VATIN,
form.TradeName,
form.Phone,
form.Email,
form.Web,
form.Address,
form.City,
form.Province,
form.PostalCode,
form.Country,
form.Currency,
form.InvoiceNumberFormat,
form.QuoteNumberFormat,
form.LegalDisclaimer,
@ -349,36 +465,39 @@ func (form *taxDetailsForm) mustFillFromDatabase(ctx context.Context, conn *Conn
if err != nil {
panic(err)
}
return form
}
type TaxDetailsPage struct {
DetailsForm *taxDetailsForm
NewTaxForm *taxForm
Taxes []*Tax
NewPaymentMethodForm *paymentMethodForm
PaymentMethods []*PaymentMethod
func (form *InvoicingForm) MustRender(w http.ResponseWriter, r *http.Request) {
mustRenderMainTemplate(w, r, "company/invoicing.gohtml", form)
}
func GetCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
mustRenderTaxDetailsForm(w, r, newTaxDetailsFormFromDatabase(r))
func (form *InvoicingForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
form.InvoiceNumberFormat.FillValue(r)
form.NextInvoiceNumber.FillValue(r)
form.QuoteNumberFormat.FillValue(r)
form.NextQuoteNumber.FillValue(r)
form.LegalDisclaimer.FillValue(r)
return nil
}
func newTaxDetailsFormFromDatabase(r *http.Request) *taxDetailsForm {
func (form *InvoicingForm) Validate() bool {
validator := newFormValidator()
validator.CheckRequiredInput(form.InvoiceNumberFormat, gettext("Invoice number format can not be empty.", form.locale))
validator.CheckValidInteger(form.NextInvoiceNumber, 1, math.MaxInt32, gettext("Next invoice number must be a number greater than zero.", form.locale))
validator.CheckRequiredInput(form.QuoteNumberFormat, gettext("Quotation number format can not be empty.", form.locale))
validator.CheckValidInteger(form.NextQuoteNumber, 1, math.MaxInt32, gettext("Next quotation number must be a number greater than zero.", form.locale))
return validator.AllOK()
}
func handleCompanyInvoicingForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
locale := getLocale(r)
conn := getConn(r)
form := newTaxDetailsForm(r.Context(), conn, locale)
company := mustGetCompany(r)
form.mustFillFromDatabase(r.Context(), conn, company)
return form
}
func HandleCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
locale := getLocale(r)
conn := getConn(r)
form := newTaxDetailsForm(r.Context(), conn, locale)
form := newInvoicingForm(locale)
if err := form.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
@ -387,11 +506,11 @@ func HandleCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httpr
http.Error(w, err.Error(), http.StatusForbidden)
return
}
if ok := form.Validate(r.Context(), conn); !ok {
if ok := form.Validate(); !ok {
if !IsHTMxRequest(r) {
w.WriteHeader(http.StatusUnprocessableEntity)
}
mustRenderTaxDetailsForm(w, r, form)
form.MustRender(w, r)
return
}
company := mustGetCompany(r)
@ -399,35 +518,11 @@ func HandleCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httpr
defer tx.MustRollback(r.Context())
tx.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
, invoice_number_format = $13
, quote_number_format = $14
, legal_disclaimer = $15
where company_id = $16
set invoice_number_format = $1
, quote_number_format = $2
, legal_disclaimer = $3
where company_id = $4
`,
form.BusinessName,
form.VATIN,
form.TradeName,
form.Phone,
form.Email,
form.Web,
form.Address,
form.City,
form.Province,
form.PostalCode,
form.Country,
form.Currency,
form.InvoiceNumberFormat,
form.QuoteNumberFormat,
form.LegalDisclaimer,
@ -449,60 +544,39 @@ func HandleCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httpr
company.Id,
form.NextQuoteNumber.Integer()-1)
tx.MustCommit(r.Context())
if IsHTMxRequest(r) {
w.Header().Set(HxTrigger, "closeModal")
w.WriteHeader(http.StatusNoContent)
} else {
http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther)
}
htmxRedirect(w, r, companyURI(company, "/invoicing"))
}
func mustRenderTaxDetailsForm(w http.ResponseWriter, r *http.Request, form *taxDetailsForm) {
conn := getConn(r)
locale := getLocale(r)
page := &TaxDetailsPage{
DetailsForm: form,
NewTaxForm: newTaxForm(r.Context(), conn, mustGetCompany(r), locale),
NewPaymentMethodForm: newPaymentMethodForm(locale),
}
mustRenderTexDetailsPage(w, r, page)
}
func mustRenderTaxForm(w http.ResponseWriter, r *http.Request, form *taxForm) {
page := &TaxDetailsPage{
DetailsForm: newTaxDetailsFormFromDatabase(r),
NewTaxForm: form,
NewPaymentMethodForm: newPaymentMethodForm(getLocale(r)),
}
mustRenderTexDetailsPage(w, r, page)
}
func mustRenderPaymentMethodForm(w http.ResponseWriter, r *http.Request, form *paymentMethodForm) {
page := &TaxDetailsPage{
DetailsForm: newTaxDetailsFormFromDatabase(r),
NewTaxForm: newTaxForm(r.Context(), getConn(r), mustGetCompany(r), getLocale(r)),
NewPaymentMethodForm: form,
}
mustRenderTexDetailsPage(w, r, page)
}
func mustRenderTexDetailsPage(w http.ResponseWriter, r *http.Request, page *TaxDetailsPage) {
func serveCompanyTaxes(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
conn := getConn(r)
company := mustGetCompany(r)
page.Taxes = mustGetTaxes(r.Context(), conn, company)
page.PaymentMethods = mustCollectPaymentMethods(r.Context(), conn, company)
mustRenderModalTemplate(w, r, "tax-details.gohtml", page)
locale := getLocale(r)
page := newTaxesPage(r.Context(), conn, company, locale)
page.MustRender(w, r)
}
func mustGetCompany(r *http.Request) *Company {
company := getCompany(r)
if company == nil {
panic(errors.New("company: required but not found"))
type TaxesPage struct {
Taxes []*Tax
Form *taxForm
}
func newTaxesPage(ctx context.Context, conn *Conn, company *Company, locale *Locale) *TaxesPage {
form := newTaxForm(ctx, conn, company, locale)
return newTaxesPageWithForm(ctx, conn, company, form)
}
func newTaxesPageWithForm(ctx context.Context, conn *Conn, company *Company, form *taxForm) *TaxesPage {
return &TaxesPage{
Taxes: mustCollectTaxes(ctx, conn, company),
Form: form,
}
return company
}
func mustGetTaxes(ctx context.Context, conn *Conn, company *Company) []*Tax {
func (page *TaxesPage) MustRender(w http.ResponseWriter, r *http.Request) {
mustRenderMainTemplate(w, r, "company/taxes.gohtml", page)
}
func mustCollectTaxes(ctx context.Context, conn *Conn, company *Company) []*Tax {
rows, err := conn.Query(ctx, "select tax_id, tax.name, tax_class.name, (rate * 100)::integer from tax join tax_class using (tax_class_id) where tax.company_id = $1 order by rate, tax.name", company.Id)
if err != nil {
panic(err)
@ -525,29 +599,6 @@ func mustGetTaxes(ctx context.Context, conn *Conn, company *Company) []*Tax {
return taxes
}
func mustCollectPaymentMethods(ctx context.Context, conn *Conn, company *Company) []*PaymentMethod {
rows, err := conn.Query(ctx, "select payment_method_id, name, instructions from payment_method where company_id = $1 order by name", company.Id)
if err != nil {
panic(err)
}
defer rows.Close()
var methods []*PaymentMethod
for rows.Next() {
method := &PaymentMethod{}
err = rows.Scan(&method.Id, &method.Name, &method.Instructions)
if err != nil {
panic(err)
}
methods = append(methods, method)
}
if rows.Err() != nil {
panic(rows.Err())
}
return methods
}
type taxForm struct {
locale *Locale
Name *InputField
@ -604,25 +655,6 @@ func (form *taxForm) Validate() bool {
return validator.AllOK()
}
func HandleDeleteCompanyTax(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
taxId, err := strconv.Atoi(params[0].Value)
if err != nil {
http.NotFound(w, r)
return
}
if err := verifyCsrfTokenValid(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
conn := getConn(r)
conn.MustExec(r.Context(), "delete from tax where tax_id = $1", taxId)
if IsHTMxRequest(r) {
w.WriteHeader(http.StatusOK)
} else {
http.Redirect(w, r, companyURI(mustGetCompany(r), "/tax-details"), http.StatusSeeOther)
}
}
func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
locale := getLocale(r)
conn := getConn(r)
@ -640,15 +672,80 @@ func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Pa
if !IsHTMxRequest(r) {
w.WriteHeader(http.StatusUnprocessableEntity)
}
mustRenderTaxForm(w, r, form)
page := newTaxesPageWithForm(r.Context(), conn, company, form)
page.MustRender(w, r)
return
}
conn.MustExec(r.Context(), "insert into tax (company_id, tax_class_id, name, rate) values ($1, $2, $3, $4 / 100::decimal)", company.Id, form.Class, form.Name, form.Rate.Integer())
if IsHTMxRequest(r) {
mustRenderTaxForm(w, r, newTaxForm(r.Context(), conn, company, locale))
} else {
http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther)
htmxRedirect(w, r, companyURI(company, "/taxes"))
}
func HandleDeleteCompanyTax(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
taxId, err := strconv.Atoi(params[0].Value)
if err != nil {
http.NotFound(w, r)
return
}
if err := verifyCsrfTokenValid(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
conn := getConn(r)
conn.MustExec(r.Context(), "delete from tax where tax_id = $1", taxId)
company := mustGetCompany(r)
htmxRedirect(w, r, companyURI(company, "/taxes"))
}
func servePaymentMethods(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
conn := getConn(r)
company := mustGetCompany(r)
locale := getLocale(r)
page := newPaymentMethodsPage(r.Context(), conn, company, locale)
page.MustRender(w, r)
}
type PaymentMethodsPage struct {
PaymentMethods []*PaymentMethod
Form *paymentMethodForm
}
func newPaymentMethodsPage(ctx context.Context, conn *Conn, company *Company, locale *Locale) *PaymentMethodsPage {
form := newPaymentMethodForm(locale)
return newPaymentMethodsPageWithForm(ctx, conn, company, form)
}
func newPaymentMethodsPageWithForm(ctx context.Context, conn *Conn, company *Company, form *paymentMethodForm) *PaymentMethodsPage {
return &PaymentMethodsPage{
PaymentMethods: mustCollectPaymentMethods(ctx, conn, company),
Form: form,
}
}
func (page *PaymentMethodsPage) MustRender(w http.ResponseWriter, r *http.Request) {
mustRenderMainTemplate(w, r, "company/payment_methods.gohtml", page)
}
func mustCollectPaymentMethods(ctx context.Context, conn *Conn, company *Company) []*PaymentMethod {
rows, err := conn.Query(ctx, "select payment_method_id, name, instructions from payment_method where company_id = $1 order by name", company.Id)
if err != nil {
panic(err)
}
defer rows.Close()
var methods []*PaymentMethod
for rows.Next() {
method := &PaymentMethod{}
err = rows.Scan(&method.Id, &method.Name, &method.Instructions)
if err != nil {
panic(err)
}
methods = append(methods, method)
}
if rows.Err() != nil {
panic(rows.Err())
}
return methods
}
type paymentMethodForm struct {
@ -691,25 +788,6 @@ func (form *paymentMethodForm) Validate() bool {
return validator.AllOK()
}
func HandleDeletePaymentMethod(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
paymentMethodId, err := strconv.Atoi(params[0].Value)
if err != nil {
http.NotFound(w, r)
return
}
if err := verifyCsrfTokenValid(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
conn := getConn(r)
conn.MustExec(r.Context(), "delete from payment_method where payment_method_id = $1", paymentMethodId)
if IsHTMxRequest(r) {
w.WriteHeader(http.StatusOK)
} else {
http.Redirect(w, r, companyURI(mustGetCompany(r), "/tax-details"), http.StatusSeeOther)
}
}
func HandleAddPaymentMethod(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
locale := getLocale(r)
conn := getConn(r)
@ -727,15 +805,28 @@ func HandleAddPaymentMethod(w http.ResponseWriter, r *http.Request, _ httprouter
if !IsHTMxRequest(r) {
w.WriteHeader(http.StatusUnprocessableEntity)
}
mustRenderPaymentMethodForm(w, r, form)
page := newPaymentMethodsPageWithForm(r.Context(), conn, company, form)
page.MustRender(w, r)
return
}
conn.MustExec(r.Context(), "insert into payment_method (company_id, name, instructions) values ($1, $2, $3)", company.Id, form.Name, form.Instructions)
if IsHTMxRequest(r) {
mustRenderPaymentMethodForm(w, r, newPaymentMethodForm(locale))
} else {
http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther)
htmxRedirect(w, r, companyURI(company, "/payment-methods"))
}
func HandleDeletePaymentMethod(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
paymentMethodId, err := strconv.Atoi(params[0].Value)
if err != nil {
http.NotFound(w, r)
return
}
if err := verifyCsrfTokenValid(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
conn := getConn(r)
conn.MustExec(r.Context(), "delete from payment_method where payment_method_id = $1", paymentMethodId)
company := mustGetCompany(r)
htmxRedirect(w, r, companyURI(company, "/payment-methods"))
}
func GetCompanySwitcher(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@ -755,7 +846,7 @@ type UserCompany struct {
}
func (page *CompanySwitchPage) MustRender(w http.ResponseWriter, r *http.Request) {
mustRenderModalTemplate(w, r, "switch-company.gohtml", page)
mustRenderModalTemplate(w, r, "company/switch.gohtml", page)
}
func mustCollectUserCompanies(ctx context.Context, conn *Conn) []*UserCompany {

View File

@ -11,12 +11,16 @@ func NewRouter(db *Db, demo bool) http.Handler {
companyRouter.GET("/profile", GetProfileForm)
companyRouter.POST("/profile", HandleProfileForm)
companyRouter.GET("/tax-details", GetCompanyTaxDetailsForm)
companyRouter.GET("/switch-company", GetCompanySwitcher)
companyRouter.POST("/tax-details", HandleCompanyTaxDetailsForm)
companyRouter.POST("/tax", HandleAddCompanyTax)
companyRouter.DELETE("/tax/:taxId", HandleDeleteCompanyTax)
companyRouter.POST("/payment-method", HandleAddPaymentMethod)
companyRouter.DELETE("/payment-method/:paymentMethodId", HandleDeletePaymentMethod)
companyRouter.GET("/invoicing", serveCompanyInvoicingForm)
companyRouter.POST("/invoicing", handleCompanyInvoicingForm)
companyRouter.GET("/switch-company", GetCompanySwitcher)
companyRouter.GET("/taxes", serveCompanyTaxes)
companyRouter.POST("/taxes", HandleAddCompanyTax)
companyRouter.DELETE("/taxes/:taxId", HandleDeleteCompanyTax)
companyRouter.GET("/payment-methods", servePaymentMethods)
companyRouter.POST("/payment-methods", HandleAddPaymentMethod)
companyRouter.DELETE("/payment-methods/:paymentMethodId", HandleDeletePaymentMethod)
companyRouter.GET("/contacts", IndexContacts)
companyRouter.POST("/contacts", HandleAddContact)
companyRouter.POST("/contacts/import", HandleImportContacts)

359
po/ca.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-08-13 02:06+0200\n"
"POT-Creation-Date: 2024-08-14 04:02+0200\n"
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n"
@ -26,17 +26,20 @@ msgstr "Afegeix productes a la factura"
#: web/template/invoices/products.gohtml:9 web/template/invoices/new.gohtml:9
#: web/template/invoices/index.gohtml:9 web/template/invoices/view.gohtml:9
#: web/template/invoices/edit.gohtml:9 web/template/home.gohtml:2
#: web/template/switch-company.gohtml:9 web/template/quotes/products.gohtml:9
#: web/template/quotes/new.gohtml:9 web/template/quotes/index.gohtml:9
#: web/template/quotes/view.gohtml:9 web/template/quotes/edit.gohtml:9
#: web/template/contacts/new.gohtml:9 web/template/contacts/index.gohtml:9
#: web/template/contacts/edit.gohtml:10 web/template/contacts/import.gohtml:8
#: web/template/profile.gohtml:9 web/template/expenses/new.gohtml:10
#: web/template/expenses/index.gohtml:10 web/template/expenses/edit.gohtml:10
#: web/template/tax-details.gohtml:9 web/template/products/new.gohtml:9
#: web/template/products/index.gohtml:9 web/template/products/edit.gohtml:10
#: web/template/payments/new.gohtml:10 web/template/payments/index.gohtml:10
#: web/template/payments/edit.gohtml:10
#: web/template/quotes/products.gohtml:9 web/template/quotes/new.gohtml:9
#: web/template/quotes/index.gohtml:9 web/template/quotes/view.gohtml:9
#: web/template/quotes/edit.gohtml:9 web/template/contacts/new.gohtml:9
#: web/template/contacts/index.gohtml:9 web/template/contacts/edit.gohtml:10
#: web/template/contacts/import.gohtml:8 web/template/profile.gohtml:9
#: web/template/expenses/new.gohtml:10 web/template/expenses/index.gohtml:10
#: web/template/expenses/edit.gohtml:10 web/template/company/taxes.gohtml:10
#: web/template/company/switch.gohtml:9
#: web/template/company/tax-details.gohtml:9
#: web/template/company/invoicing.gohtml:10
#: web/template/company/payment_methods.gohtml:10
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:9
#: web/template/products/edit.gohtml:10 web/template/payments/new.gohtml:10
#: web/template/payments/index.gohtml:10 web/template/payments/edit.gohtml:10
#: web/template/payments/accounts/new.gohtml:10
#: web/template/payments/accounts/index.gohtml:10
#: web/template/payments/accounts/edit.gohtml:10
@ -64,7 +67,7 @@ msgid "All"
msgstr "Tots"
#: web/template/invoices/products.gohtml:49
#: web/template/switch-company.gohtml:22 web/template/quotes/products.gohtml:49
#: web/template/quotes/products.gohtml:49 web/template/company/switch.gohtml:22
#: web/template/products/index.gohtml:45
#: web/template/payments/accounts/index.gohtml:25
msgctxt "title"
@ -214,9 +217,9 @@ msgctxt "title"
msgid "Download"
msgstr "Descàrrega"
#: web/template/invoices/index.gohtml:74 web/template/switch-company.gohtml:23
#: web/template/quotes/index.gohtml:74 web/template/contacts/index.gohtml:51
#: web/template/expenses/index.gohtml:76 web/template/products/index.gohtml:48
#: web/template/invoices/index.gohtml:74 web/template/quotes/index.gohtml:74
#: web/template/contacts/index.gohtml:51 web/template/expenses/index.gohtml:76
#: web/template/company/switch.gohtml:23 web/template/products/index.gohtml:48
#: web/template/payments/index.gohtml:31
msgctxt "title"
msgid "Actions"
@ -342,17 +345,6 @@ msgctxt "action"
msgid "Filters"
msgstr "Filtra"
#: web/template/switch-company.gohtml:2 web/template/switch-company.gohtml:10
#: web/template/switch-company.gohtml:18
msgctxt "title"
msgid "Company Switch"
msgstr "Canvi dempresa"
#: web/template/switch-company.gohtml:30
msgctxt "action"
msgid "Switch"
msgstr "Canvia"
#: web/template/dashboard.gohtml:3
msgctxt "title"
msgid "Dashboard"
@ -468,52 +460,75 @@ msgctxt "menu"
msgid "Tax Details"
msgstr "Configuració fiscal"
#: web/template/app.gohtml:36
#: web/template/app.gohtml:36 web/template/company/invoicing.gohtml:3
msgctxt "title"
msgid "Invoicing and Quoting"
msgstr "Facturació i pressuposts"
#: web/template/app.gohtml:42 web/template/company/taxes.gohtml:3
msgctxt "title"
msgid "Taxes"
msgstr "Imposts"
#: web/template/app.gohtml:48 web/template/company/payment_methods.gohtml:3
msgctxt "title"
msgid "Payment Methods"
msgstr "Mètodes de pagament"
#: web/template/app.gohtml:54 web/template/payments/accounts/new.gohtml:11
#: web/template/payments/accounts/index.gohtml:3
#: web/template/payments/accounts/index.gohtml:11
#: web/template/payments/accounts/edit.gohtml:11
msgctxt "title"
msgid "Payment Accounts"
msgstr "Comptes de pagament"
#: web/template/app.gohtml:60
msgctxt "menu"
msgid "Switch Company"
msgstr "Canvi dempresa"
#: web/template/app.gohtml:44
#: web/template/app.gohtml:68
msgctxt "action"
msgid "Logout"
msgstr "Surt"
#: web/template/app.gohtml:53
#: web/template/app.gohtml:77
msgctxt "nav"
msgid "Dashboard"
msgstr "Tauler"
#: web/template/app.gohtml:54
#: web/template/app.gohtml:78
msgctxt "nav"
msgid "Quotations"
msgstr "Pressuposts"
#: web/template/app.gohtml:55
#: web/template/app.gohtml:79
msgctxt "nav"
msgid "Invoices"
msgstr "Factures"
#: web/template/app.gohtml:56
#: web/template/app.gohtml:80
msgctxt "nav"
msgid "Expenses"
msgstr "Despeses"
#: web/template/app.gohtml:57
#: web/template/app.gohtml:81
msgctxt "nav"
msgid "Payments"
msgstr "Pagaments"
#: web/template/app.gohtml:58
#: web/template/app.gohtml:82
msgctxt "nav"
msgid "Products"
msgstr "Productes"
#: web/template/app.gohtml:59
#: web/template/app.gohtml:83
msgctxt "nav"
msgid "Contacts"
msgstr "Contactes"
#: web/template/app.gohtml:67
#: web/template/app.gohtml:91
msgid "<a href=\"https://numerus.cat/\">Numerus</a> Version: %s"
msgstr "<a href=\"https://numerus.cat/\">Numerus</a> versió: %s"
@ -604,7 +619,8 @@ msgctxt "title"
msgid "Language"
msgstr "Idioma"
#: web/template/profile.gohtml:39 web/template/tax-details.gohtml:175
#: web/template/profile.gohtml:39 web/template/company/tax-details.gohtml:36
#: web/template/company/invoicing.gohtml:31
msgctxt "action"
msgid "Save changes"
msgstr "Desa canvis"
@ -649,70 +665,75 @@ msgctxt "title"
msgid "Edit Expense “%s”"
msgstr "Edició de la despesa «%s»"
#: web/template/tax-details.gohtml:2 web/template/tax-details.gohtml:10
#: web/template/tax-details.gohtml:18
msgctxt "title"
msgid "Tax Details"
msgstr "Configuració fiscal"
#: web/template/tax-details.gohtml:35
msgctxt "title"
msgid "Currency"
msgstr "Moneda"
#: web/template/tax-details.gohtml:41
msgctxt "title"
msgid "Invoicing and Quoting"
msgstr "Facturació i pressuposts"
#: web/template/tax-details.gohtml:56
msgid "Are you sure?"
msgstr "Nesteu segur?"
#: web/template/tax-details.gohtml:62
#: web/template/company/taxes.gohtml:23
msgctxt "title"
msgid "Tax Name"
msgstr "Nom impost"
#: web/template/tax-details.gohtml:63
#: web/template/company/taxes.gohtml:24
msgctxt "title"
msgid "Rate (%)"
msgstr "Percentatge"
#: web/template/tax-details.gohtml:64
#: web/template/company/taxes.gohtml:25
msgctxt "title"
msgid "Class"
msgstr "Classe"
#: web/template/tax-details.gohtml:88
#: web/template/company/taxes.gohtml:29
#: web/template/company/payment_methods.gohtml:28
msgid "Are you sure?"
msgstr "Nesteu segur?"
#: web/template/company/taxes.gohtml:49
msgid "No taxes added yet."
msgstr "No hi ha cap impost."
#: web/template/tax-details.gohtml:94 web/template/tax-details.gohtml:155
#: web/template/company/taxes.gohtml:56
msgctxt "title"
msgid "New Line"
msgstr "Nova línia"
msgid "New Tax"
msgstr "Nou impost"
#: web/template/tax-details.gohtml:108
#: web/template/company/taxes.gohtml:66
msgctxt "action"
msgid "Add new tax"
msgstr "Afegeix nou impost"
#: web/template/tax-details.gohtml:124
#: web/template/company/switch.gohtml:2
msgctxt "title"
msgid "Company Switch"
msgstr "Canvi dempresa"
#: web/template/company/switch.gohtml:30
msgctxt "action"
msgid "Switch"
msgstr "Canvia"
#: web/template/company/tax-details.gohtml:2
msgctxt "title"
msgid "Tax Details"
msgstr "Configuració fiscal"
#: web/template/company/payment_methods.gohtml:23
msgctxt "title"
msgid "Payment Method"
msgstr "Mètode de pagament"
#: web/template/tax-details.gohtml:125
#: web/template/company/payment_methods.gohtml:24
msgctxt "title"
msgid "Instructions"
msgstr "Instruccions"
#: web/template/tax-details.gohtml:149
#: web/template/company/payment_methods.gohtml:48
msgid "No payment methods added yet."
msgstr "No hi ha cap mètode de pagament."
#: web/template/tax-details.gohtml:167
#: web/template/company/payment_methods.gohtml:55
msgctxt "title"
msgid "New Payment Method"
msgstr "Nou mètode de pagament"
#: web/template/company/payment_methods.gohtml:64
msgctxt "action"
msgid "Add new payment method"
msgstr "Afegeix nou mètode de pagament"
@ -801,14 +822,6 @@ msgctxt "title"
msgid "New Payment Account"
msgstr "Nou compte de pagament"
#: web/template/payments/accounts/new.gohtml:11
#: web/template/payments/accounts/index.gohtml:3
#: web/template/payments/accounts/index.gohtml:11
#: web/template/payments/accounts/edit.gohtml:11
msgctxt "title"
msgid "Payment Accounts"
msgstr "Comptes de pagament"
#: web/template/payments/accounts/index.gohtml:16
msgctxt "action"
msgid "New payment account"
@ -843,7 +856,7 @@ msgctxt "title"
msgid "VAT number"
msgstr "DNI / NIF"
#: pkg/login.go:38 pkg/company.go:127 pkg/profile.go:40 pkg/contacts.go:276
#: pkg/login.go:38 pkg/company.go:122 pkg/profile.go:40 pkg/contacts.go:276
msgctxt "input"
msgid "Email"
msgstr "Correu-e"
@ -853,11 +866,11 @@ msgctxt "input"
msgid "Password"
msgstr "Contrasenya"
#: pkg/login.go:76 pkg/company.go:283 pkg/profile.go:89
#: pkg/login.go:76 pkg/company.go:238 pkg/profile.go:89
msgid "Email can not be empty."
msgstr "No podeu deixar el correu-e en blanc."
#: pkg/login.go:77 pkg/company.go:284 pkg/profile.go:90 pkg/contacts.go:420
#: pkg/login.go:77 pkg/company.go:239 pkg/profile.go:90 pkg/contacts.go:420
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."
@ -951,208 +964,208 @@ msgstr "Heu seleccionat un impost que no és vàlid."
msgid "You can only select a tax of each class."
msgstr "Només podeu seleccionar un impost de cada classe."
#: pkg/company.go:113
#: pkg/company.go:108
msgctxt "input"
msgid "Trade name"
msgstr "Nom comercial"
#: pkg/company.go:118 pkg/contacts.go:268
#: pkg/company.go:113 pkg/contacts.go:268
msgctxt "input"
msgid "Phone"
msgstr "Telèfon"
#: pkg/company.go:136 pkg/contacts.go:284
#: pkg/company.go:131 pkg/contacts.go:284
msgctxt "input"
msgid "Web"
msgstr "Web"
#: pkg/company.go:144 pkg/contacts.go:296
#: pkg/company.go:139 pkg/contacts.go:296
msgctxt "input"
msgid "Business name"
msgstr "Nom i cognoms"
#: pkg/company.go:154 pkg/contacts.go:306
#: pkg/company.go:149 pkg/contacts.go:306
msgctxt "input"
msgid "VAT number"
msgstr "DNI / NIF"
#: pkg/company.go:160 pkg/contacts.go:312
#: pkg/company.go:155 pkg/contacts.go:312
msgctxt "input"
msgid "Address"
msgstr "Adreça"
#: pkg/company.go:169 pkg/contacts.go:321
#: pkg/company.go:164 pkg/contacts.go:321
msgctxt "input"
msgid "City"
msgstr "Població"
#: pkg/company.go:175 pkg/contacts.go:327
#: pkg/company.go:170 pkg/contacts.go:327
msgctxt "input"
msgid "Province"
msgstr "Província"
#: pkg/company.go:181 pkg/contacts.go:333
#: pkg/company.go:176 pkg/contacts.go:333
msgctxt "input"
msgid "Postal code"
msgstr "Codi postal"
#: pkg/company.go:190 pkg/contacts.go:342
#: pkg/company.go:185 pkg/contacts.go:342
msgctxt "input"
msgid "Country"
msgstr "País"
#: pkg/company.go:200
#: pkg/company.go:195
msgctxt "input"
msgid "Currency"
msgstr "Moneda"
#: pkg/company.go:207
#: pkg/company.go:226 pkg/contacts.go:394
msgid "Selected country is not valid."
msgstr "Heu seleccionat un país que no és vàlid."
#: pkg/company.go:230 pkg/contacts.go:397
msgid "Business name can not be empty."
msgstr "No podeu deixar el nom i els cognoms en blanc."
#: pkg/company.go:231 pkg/contacts.go:398
msgid "Business name must have at least two letters."
msgstr "Nom i cognoms han de tenir com a mínim dues lletres."
#: pkg/company.go:232 pkg/contacts.go:400
msgid "VAT number can not be empty."
msgstr "No podeu deixar el DNI o NIF en blanc."
#: pkg/company.go:233 pkg/contacts.go:401
msgid "This value is not a valid VAT number."
msgstr "Aquest valor no és un DNI o NIF vàlid."
#: pkg/company.go:235
msgid "Phone can not be empty."
msgstr "No podeu deixar el telèfon en blanc."
#: pkg/company.go:236 pkg/contacts.go:417
msgid "This value is not a valid phone number."
msgstr "Aquest valor no és un telèfon vàlid."
#: pkg/company.go:242 pkg/contacts.go:423
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/company.go:244 pkg/contacts.go:403
msgid "Address can not be empty."
msgstr "No podeu deixar ladreça en blanc."
#: pkg/company.go:245 pkg/contacts.go:404
msgid "City can not be empty."
msgstr "No podeu deixar la població en blanc."
#: pkg/company.go:246 pkg/contacts.go:405
msgid "Province can not be empty."
msgstr "No podeu deixar la província en blanc."
#: pkg/company.go:247 pkg/contacts.go:407
msgid "Postal code can not be empty."
msgstr "No podeu deixar el codi postal en blanc."
#: pkg/company.go:248 pkg/contacts.go:408
msgid "This value is not a valid postal code."
msgstr "Aquest valor no és un codi postal vàlid."
#: pkg/company.go:250
msgid "Selected currency is not valid."
msgstr "Heu seleccionat una moneda que no és vàlida."
#: pkg/company.go:402
msgctxt "input"
msgid "Invoice number format"
msgstr "Format del número de factura"
#: pkg/company.go:213
#: pkg/company.go:408
msgctxt "input"
msgid "Next invoice number"
msgstr "Següent número de factura"
#: pkg/company.go:222
#: pkg/company.go:417
msgctxt "input"
msgid "Quotation number format"
msgstr "Format del número de pressupost"
#: pkg/company.go:228
#: pkg/company.go:423
msgctxt "input"
msgid "Next quotation number"
msgstr "Següent número de pressupost"
#: pkg/company.go:237
#: pkg/company.go:432
msgctxt "input"
msgid "Legal disclaimer"
msgstr "Nota legal"
#: pkg/company.go:271 pkg/contacts.go:394
msgid "Selected country is not valid."
msgstr "Heu seleccionat un país que no és vàlid."
#: pkg/company.go:275 pkg/contacts.go:397
msgid "Business name can not be empty."
msgstr "No podeu deixar el nom i els cognoms en blanc."
#: pkg/company.go:276 pkg/contacts.go:398
msgid "Business name must have at least two letters."
msgstr "Nom i cognoms han de tenir com a mínim dues lletres."
#: pkg/company.go:277 pkg/contacts.go:400
msgid "VAT number can not be empty."
msgstr "No podeu deixar el DNI o NIF en blanc."
#: pkg/company.go:278 pkg/contacts.go:401
msgid "This value is not a valid VAT number."
msgstr "Aquest valor no és un DNI o NIF vàlid."
#: pkg/company.go:280
msgid "Phone can not be empty."
msgstr "No podeu deixar el telèfon en blanc."
#: pkg/company.go:281 pkg/contacts.go:417
msgid "This value is not a valid phone number."
msgstr "Aquest valor no és un telèfon vàlid."
#: pkg/company.go:287 pkg/contacts.go:423
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/company.go:289 pkg/contacts.go:403
msgid "Address can not be empty."
msgstr "No podeu deixar ladreça en blanc."
#: pkg/company.go:290 pkg/contacts.go:404
msgid "City can not be empty."
msgstr "No podeu deixar la població en blanc."
#: pkg/company.go:291 pkg/contacts.go:405
msgid "Province can not be empty."
msgstr "No podeu deixar la província en blanc."
#: pkg/company.go:292 pkg/contacts.go:407
msgid "Postal code can not be empty."
msgstr "No podeu deixar el codi postal en blanc."
#: pkg/company.go:293 pkg/contacts.go:408
msgid "This value is not a valid postal code."
msgstr "Aquest valor no és un codi postal vàlid."
#: pkg/company.go:295
msgid "Selected currency is not valid."
msgstr "Heu seleccionat una moneda que no és vàlida."
#: pkg/company.go:296
#: pkg/company.go:489
msgid "Invoice number format can not be empty."
msgstr "No podeu deixar el format del número de factura en blanc."
#: pkg/company.go:297
#: pkg/company.go:490
msgid "Next invoice number must be a number greater than zero."
msgstr "El següent número de factura ha de ser un número major a zero."
#: pkg/company.go:298
#: pkg/company.go:491
msgid "Quotation number format can not be empty."
msgstr "No podeu deixar el format del número de pressupost en blanc."
#: pkg/company.go:299
#: pkg/company.go:492
msgid "Next quotation number must be a number greater than zero."
msgstr "El següent número de pressupost ha de ser un número major a zero."
#: pkg/company.go:563
#: pkg/company.go:614
msgctxt "input"
msgid "Tax name"
msgstr "Nom impost"
#: pkg/company.go:569
#: pkg/company.go:620
msgctxt "input"
msgid "Tax Class"
msgstr "Classe dimpost"
#: pkg/company.go:572
#: pkg/company.go:623
msgid "Select a tax class"
msgstr "Escolliu una classe dimpost"
#: pkg/company.go:576
#: pkg/company.go:627
msgctxt "input"
msgid "Rate (%)"
msgstr "Percentatge"
#: pkg/company.go:599
#: pkg/company.go:650
msgid "Tax name can not be empty."
msgstr "No podeu deixar el nom de limpost en blanc."
#: pkg/company.go:600
#: pkg/company.go:651
msgid "Selected tax class is not valid."
msgstr "Heu seleccionat una classe dimpost que no és vàlida."
#: pkg/company.go:601
#: pkg/company.go:652
msgid "Tax rate can not be empty."
msgstr "No podeu deixar percentatge en blanc."
#: pkg/company.go:602
#: pkg/company.go:653
msgid "Tax rate must be an integer between -99 and 99."
msgstr "El percentatge ha de ser entre -99 i 99."
#: pkg/company.go:665
#: pkg/company.go:762
msgctxt "input"
msgid "Payment method name"
msgstr "Nom del mètode de pagament"
#: pkg/company.go:671
#: pkg/company.go:768
msgctxt "input"
msgid "Instructions"
msgstr "Instruccions"
#: pkg/company.go:689
#: pkg/company.go:786
msgid "Payment method name can not be empty."
msgstr "No podeu deixar el nom del mètode de pagament en blanc."
#: pkg/company.go:690
#: pkg/company.go:787
msgid "Payment instructions can not be empty."
msgstr "No podeu deixar les instruccions de pagament en blanc."
@ -1567,6 +1580,14 @@ msgctxt "input"
msgid "Holded Excel file"
msgstr "Fitxer Excel del Holded"
#~ msgctxt "title"
#~ msgid "Currency"
#~ msgstr "Moneda"
#~ msgctxt "title"
#~ msgid "New Line"
#~ msgstr "Nova línia"
#~ msgid "Selected expense status is not valid."
#~ msgstr "Heu seleccionat un estat de despesa que no és vàlid."

359
po/es.po
View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-08-13 02:06+0200\n"
"POT-Creation-Date: 2024-08-14 04:02+0200\n"
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n"
@ -26,17 +26,20 @@ msgstr "Añadir productos a la factura"
#: web/template/invoices/products.gohtml:9 web/template/invoices/new.gohtml:9
#: web/template/invoices/index.gohtml:9 web/template/invoices/view.gohtml:9
#: web/template/invoices/edit.gohtml:9 web/template/home.gohtml:2
#: web/template/switch-company.gohtml:9 web/template/quotes/products.gohtml:9
#: web/template/quotes/new.gohtml:9 web/template/quotes/index.gohtml:9
#: web/template/quotes/view.gohtml:9 web/template/quotes/edit.gohtml:9
#: web/template/contacts/new.gohtml:9 web/template/contacts/index.gohtml:9
#: web/template/contacts/edit.gohtml:10 web/template/contacts/import.gohtml:8
#: web/template/profile.gohtml:9 web/template/expenses/new.gohtml:10
#: web/template/expenses/index.gohtml:10 web/template/expenses/edit.gohtml:10
#: web/template/tax-details.gohtml:9 web/template/products/new.gohtml:9
#: web/template/products/index.gohtml:9 web/template/products/edit.gohtml:10
#: web/template/payments/new.gohtml:10 web/template/payments/index.gohtml:10
#: web/template/payments/edit.gohtml:10
#: web/template/quotes/products.gohtml:9 web/template/quotes/new.gohtml:9
#: web/template/quotes/index.gohtml:9 web/template/quotes/view.gohtml:9
#: web/template/quotes/edit.gohtml:9 web/template/contacts/new.gohtml:9
#: web/template/contacts/index.gohtml:9 web/template/contacts/edit.gohtml:10
#: web/template/contacts/import.gohtml:8 web/template/profile.gohtml:9
#: web/template/expenses/new.gohtml:10 web/template/expenses/index.gohtml:10
#: web/template/expenses/edit.gohtml:10 web/template/company/taxes.gohtml:10
#: web/template/company/switch.gohtml:9
#: web/template/company/tax-details.gohtml:9
#: web/template/company/invoicing.gohtml:10
#: web/template/company/payment_methods.gohtml:10
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:9
#: web/template/products/edit.gohtml:10 web/template/payments/new.gohtml:10
#: web/template/payments/index.gohtml:10 web/template/payments/edit.gohtml:10
#: web/template/payments/accounts/new.gohtml:10
#: web/template/payments/accounts/index.gohtml:10
#: web/template/payments/accounts/edit.gohtml:10
@ -64,7 +67,7 @@ msgid "All"
msgstr "Todos"
#: web/template/invoices/products.gohtml:49
#: web/template/switch-company.gohtml:22 web/template/quotes/products.gohtml:49
#: web/template/quotes/products.gohtml:49 web/template/company/switch.gohtml:22
#: web/template/products/index.gohtml:45
#: web/template/payments/accounts/index.gohtml:25
msgctxt "title"
@ -214,9 +217,9 @@ msgctxt "title"
msgid "Download"
msgstr "Descargar"
#: web/template/invoices/index.gohtml:74 web/template/switch-company.gohtml:23
#: web/template/quotes/index.gohtml:74 web/template/contacts/index.gohtml:51
#: web/template/expenses/index.gohtml:76 web/template/products/index.gohtml:48
#: web/template/invoices/index.gohtml:74 web/template/quotes/index.gohtml:74
#: web/template/contacts/index.gohtml:51 web/template/expenses/index.gohtml:76
#: web/template/company/switch.gohtml:23 web/template/products/index.gohtml:48
#: web/template/payments/index.gohtml:31
msgctxt "title"
msgid "Actions"
@ -342,17 +345,6 @@ msgctxt "action"
msgid "Filters"
msgstr "Filtrar"
#: web/template/switch-company.gohtml:2 web/template/switch-company.gohtml:10
#: web/template/switch-company.gohtml:18
msgctxt "title"
msgid "Company Switch"
msgstr "Cambio de empresa"
#: web/template/switch-company.gohtml:30
msgctxt "action"
msgid "Switch"
msgstr "Cambiar"
#: web/template/dashboard.gohtml:3
msgctxt "title"
msgid "Dashboard"
@ -468,52 +460,75 @@ msgctxt "menu"
msgid "Tax Details"
msgstr "Configuración fiscal"
#: web/template/app.gohtml:36
#: web/template/app.gohtml:36 web/template/company/invoicing.gohtml:3
msgctxt "title"
msgid "Invoicing and Quoting"
msgstr "Facturación y presupuestos"
#: web/template/app.gohtml:42 web/template/company/taxes.gohtml:3
msgctxt "title"
msgid "Taxes"
msgstr "Impuestos"
#: web/template/app.gohtml:48 web/template/company/payment_methods.gohtml:3
msgctxt "title"
msgid "Payment Methods"
msgstr "Métodos de pago"
#: web/template/app.gohtml:54 web/template/payments/accounts/new.gohtml:11
#: web/template/payments/accounts/index.gohtml:3
#: web/template/payments/accounts/index.gohtml:11
#: web/template/payments/accounts/edit.gohtml:11
msgctxt "title"
msgid "Payment Accounts"
msgstr "Cuenta de pago"
#: web/template/app.gohtml:60
msgctxt "menu"
msgid "Switch Company"
msgstr "Cambio de empresa"
#: web/template/app.gohtml:44
#: web/template/app.gohtml:68
msgctxt "action"
msgid "Logout"
msgstr "Salir"
#: web/template/app.gohtml:53
#: web/template/app.gohtml:77
msgctxt "nav"
msgid "Dashboard"
msgstr "Panel"
#: web/template/app.gohtml:54
#: web/template/app.gohtml:78
msgctxt "nav"
msgid "Quotations"
msgstr "Presupuestos"
#: web/template/app.gohtml:55
#: web/template/app.gohtml:79
msgctxt "nav"
msgid "Invoices"
msgstr "Facturas"
#: web/template/app.gohtml:56
#: web/template/app.gohtml:80
msgctxt "nav"
msgid "Expenses"
msgstr "Gastos"
#: web/template/app.gohtml:57
#: web/template/app.gohtml:81
msgctxt "nav"
msgid "Payments"
msgstr "Pagos"
#: web/template/app.gohtml:58
#: web/template/app.gohtml:82
msgctxt "nav"
msgid "Products"
msgstr "Productos"
#: web/template/app.gohtml:59
#: web/template/app.gohtml:83
msgctxt "nav"
msgid "Contacts"
msgstr "Contactos"
#: web/template/app.gohtml:67
#: web/template/app.gohtml:91
msgid "<a href=\"https://numerus.cat/\">Numerus</a> Version: %s"
msgstr "<a href=\"https://numerus.cat/\">Numerus</a> versión: %s"
@ -604,7 +619,8 @@ msgctxt "title"
msgid "Language"
msgstr "Idioma"
#: web/template/profile.gohtml:39 web/template/tax-details.gohtml:175
#: web/template/profile.gohtml:39 web/template/company/tax-details.gohtml:36
#: web/template/company/invoicing.gohtml:31
msgctxt "action"
msgid "Save changes"
msgstr "Guardar cambios"
@ -649,70 +665,75 @@ msgctxt "title"
msgid "Edit Expense “%s”"
msgstr "Edición del gasto «%s»"
#: web/template/tax-details.gohtml:2 web/template/tax-details.gohtml:10
#: web/template/tax-details.gohtml:18
msgctxt "title"
msgid "Tax Details"
msgstr "Configuración fiscal"
#: web/template/tax-details.gohtml:35
msgctxt "title"
msgid "Currency"
msgstr "Moneda"
#: web/template/tax-details.gohtml:41
msgctxt "title"
msgid "Invoicing and Quoting"
msgstr "Facturación y presupuestos"
#: web/template/tax-details.gohtml:56
msgid "Are you sure?"
msgstr "¿Estáis seguro?"
#: web/template/tax-details.gohtml:62
#: web/template/company/taxes.gohtml:23
msgctxt "title"
msgid "Tax Name"
msgstr "Nombre impuesto"
#: web/template/tax-details.gohtml:63
#: web/template/company/taxes.gohtml:24
msgctxt "title"
msgid "Rate (%)"
msgstr "Porcentaje"
#: web/template/tax-details.gohtml:64
#: web/template/company/taxes.gohtml:25
msgctxt "title"
msgid "Class"
msgstr "Clase"
#: web/template/tax-details.gohtml:88
#: web/template/company/taxes.gohtml:29
#: web/template/company/payment_methods.gohtml:28
msgid "Are you sure?"
msgstr "¿Estáis seguro?"
#: web/template/company/taxes.gohtml:49
msgid "No taxes added yet."
msgstr "No hay impuestos."
#: web/template/tax-details.gohtml:94 web/template/tax-details.gohtml:155
#: web/template/company/taxes.gohtml:56
msgctxt "title"
msgid "New Line"
msgstr "Nueva línea"
msgid "New Tax"
msgstr "Nuevo impuesto"
#: web/template/tax-details.gohtml:108
#: web/template/company/taxes.gohtml:66
msgctxt "action"
msgid "Add new tax"
msgstr "Añadir nuevo impuesto"
#: web/template/tax-details.gohtml:124
#: web/template/company/switch.gohtml:2
msgctxt "title"
msgid "Company Switch"
msgstr "Cambio de empresa"
#: web/template/company/switch.gohtml:30
msgctxt "action"
msgid "Switch"
msgstr "Cambiar"
#: web/template/company/tax-details.gohtml:2
msgctxt "title"
msgid "Tax Details"
msgstr "Configuración fiscal"
#: web/template/company/payment_methods.gohtml:23
msgctxt "title"
msgid "Payment Method"
msgstr "Método de pago"
#: web/template/tax-details.gohtml:125
#: web/template/company/payment_methods.gohtml:24
msgctxt "title"
msgid "Instructions"
msgstr "Instrucciones"
#: web/template/tax-details.gohtml:149
#: web/template/company/payment_methods.gohtml:48
msgid "No payment methods added yet."
msgstr "No hay métodos de pago."
#: web/template/tax-details.gohtml:167
#: web/template/company/payment_methods.gohtml:55
msgctxt "title"
msgid "New Payment Method"
msgstr "Nuevo método de pago"
#: web/template/company/payment_methods.gohtml:64
msgctxt "action"
msgid "Add new payment method"
msgstr "Añadir nuevo método de pago"
@ -801,14 +822,6 @@ msgctxt "title"
msgid "New Payment Account"
msgstr "Nueva cuenta de pago"
#: web/template/payments/accounts/new.gohtml:11
#: web/template/payments/accounts/index.gohtml:3
#: web/template/payments/accounts/index.gohtml:11
#: web/template/payments/accounts/edit.gohtml:11
msgctxt "title"
msgid "Payment Accounts"
msgstr "Cuenta de pago"
#: web/template/payments/accounts/index.gohtml:16
msgctxt "action"
msgid "New payment account"
@ -843,7 +856,7 @@ msgctxt "title"
msgid "VAT number"
msgstr "DNI / NIF"
#: pkg/login.go:38 pkg/company.go:127 pkg/profile.go:40 pkg/contacts.go:276
#: pkg/login.go:38 pkg/company.go:122 pkg/profile.go:40 pkg/contacts.go:276
msgctxt "input"
msgid "Email"
msgstr "Correo-e"
@ -853,11 +866,11 @@ msgctxt "input"
msgid "Password"
msgstr "Contraseña"
#: pkg/login.go:76 pkg/company.go:283 pkg/profile.go:89
#: pkg/login.go:76 pkg/company.go:238 pkg/profile.go:89
msgid "Email can not be empty."
msgstr "No podéis dejar el correo-e en blanco."
#: pkg/login.go:77 pkg/company.go:284 pkg/profile.go:90 pkg/contacts.go:420
#: pkg/login.go:77 pkg/company.go:239 pkg/profile.go:90 pkg/contacts.go:420
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."
@ -951,208 +964,208 @@ msgstr "Habéis escogido un impuesto que no es válido."
msgid "You can only select a tax of each class."
msgstr "Solo podéis escoger un impuesto de cada clase."
#: pkg/company.go:113
#: pkg/company.go:108
msgctxt "input"
msgid "Trade name"
msgstr "Nombre comercial"
#: pkg/company.go:118 pkg/contacts.go:268
#: pkg/company.go:113 pkg/contacts.go:268
msgctxt "input"
msgid "Phone"
msgstr "Teléfono"
#: pkg/company.go:136 pkg/contacts.go:284
#: pkg/company.go:131 pkg/contacts.go:284
msgctxt "input"
msgid "Web"
msgstr "Web"
#: pkg/company.go:144 pkg/contacts.go:296
#: pkg/company.go:139 pkg/contacts.go:296
msgctxt "input"
msgid "Business name"
msgstr "Nombre y apellidos"
#: pkg/company.go:154 pkg/contacts.go:306
#: pkg/company.go:149 pkg/contacts.go:306
msgctxt "input"
msgid "VAT number"
msgstr "DNI / NIF"
#: pkg/company.go:160 pkg/contacts.go:312
#: pkg/company.go:155 pkg/contacts.go:312
msgctxt "input"
msgid "Address"
msgstr "Dirección"
#: pkg/company.go:169 pkg/contacts.go:321
#: pkg/company.go:164 pkg/contacts.go:321
msgctxt "input"
msgid "City"
msgstr "Población"
#: pkg/company.go:175 pkg/contacts.go:327
#: pkg/company.go:170 pkg/contacts.go:327
msgctxt "input"
msgid "Province"
msgstr "Provincia"
#: pkg/company.go:181 pkg/contacts.go:333
#: pkg/company.go:176 pkg/contacts.go:333
msgctxt "input"
msgid "Postal code"
msgstr "Código postal"
#: pkg/company.go:190 pkg/contacts.go:342
#: pkg/company.go:185 pkg/contacts.go:342
msgctxt "input"
msgid "Country"
msgstr "País"
#: pkg/company.go:200
#: pkg/company.go:195
msgctxt "input"
msgid "Currency"
msgstr "Moneda"
#: pkg/company.go:207
#: pkg/company.go:226 pkg/contacts.go:394
msgid "Selected country is not valid."
msgstr "Habéis escogido un país que no es válido."
#: pkg/company.go:230 pkg/contacts.go:397
msgid "Business name can not be empty."
msgstr "No podéis dejar el nombre y los apellidos en blanco."
#: pkg/company.go:231 pkg/contacts.go:398
msgid "Business name must have at least two letters."
msgstr "El nombre y los apellidos deben contener como mínimo dos letras."
#: pkg/company.go:232 pkg/contacts.go:400
msgid "VAT number can not be empty."
msgstr "No podéis dejar el DNI o NIF en blanco."
#: pkg/company.go:233 pkg/contacts.go:401
msgid "This value is not a valid VAT number."
msgstr "Este valor no es un DNI o NIF válido."
#: pkg/company.go:235
msgid "Phone can not be empty."
msgstr "No podéis dejar el teléfono en blanco."
#: pkg/company.go:236 pkg/contacts.go:417
msgid "This value is not a valid phone number."
msgstr "Este valor no es un teléfono válido."
#: pkg/company.go:242 pkg/contacts.go:423
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/company.go:244 pkg/contacts.go:403
msgid "Address can not be empty."
msgstr "No podéis dejar la dirección en blanco."
#: pkg/company.go:245 pkg/contacts.go:404
msgid "City can not be empty."
msgstr "No podéis dejar la población en blanco."
#: pkg/company.go:246 pkg/contacts.go:405
msgid "Province can not be empty."
msgstr "No podéis dejar la provincia en blanco."
#: pkg/company.go:247 pkg/contacts.go:407
msgid "Postal code can not be empty."
msgstr "No podéis dejar el código postal en blanco."
#: pkg/company.go:248 pkg/contacts.go:408
msgid "This value is not a valid postal code."
msgstr "Este valor no es un código postal válido válido."
#: pkg/company.go:250
msgid "Selected currency is not valid."
msgstr "Habéis escogido una moneda que no es válida."
#: pkg/company.go:402
msgctxt "input"
msgid "Invoice number format"
msgstr "Formato del número de factura"
#: pkg/company.go:213
#: pkg/company.go:408
msgctxt "input"
msgid "Next invoice number"
msgstr "Siguiente número de factura"
#: pkg/company.go:222
#: pkg/company.go:417
msgctxt "input"
msgid "Quotation number format"
msgstr "Formato del número de presupuesto"
#: pkg/company.go:228
#: pkg/company.go:423
msgctxt "input"
msgid "Next quotation number"
msgstr "Siguiente número de presupuesto"
#: pkg/company.go:237
#: pkg/company.go:432
msgctxt "input"
msgid "Legal disclaimer"
msgstr "Nota legal"
#: pkg/company.go:271 pkg/contacts.go:394
msgid "Selected country is not valid."
msgstr "Habéis escogido un país que no es válido."
#: pkg/company.go:275 pkg/contacts.go:397
msgid "Business name can not be empty."
msgstr "No podéis dejar el nombre y los apellidos en blanco."
#: pkg/company.go:276 pkg/contacts.go:398
msgid "Business name must have at least two letters."
msgstr "El nombre y los apellidos deben contener como mínimo dos letras."
#: pkg/company.go:277 pkg/contacts.go:400
msgid "VAT number can not be empty."
msgstr "No podéis dejar el DNI o NIF en blanco."
#: pkg/company.go:278 pkg/contacts.go:401
msgid "This value is not a valid VAT number."
msgstr "Este valor no es un DNI o NIF válido."
#: pkg/company.go:280
msgid "Phone can not be empty."
msgstr "No podéis dejar el teléfono en blanco."
#: pkg/company.go:281 pkg/contacts.go:417
msgid "This value is not a valid phone number."
msgstr "Este valor no es un teléfono válido."
#: pkg/company.go:287 pkg/contacts.go:423
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/company.go:289 pkg/contacts.go:403
msgid "Address can not be empty."
msgstr "No podéis dejar la dirección en blanco."
#: pkg/company.go:290 pkg/contacts.go:404
msgid "City can not be empty."
msgstr "No podéis dejar la población en blanco."
#: pkg/company.go:291 pkg/contacts.go:405
msgid "Province can not be empty."
msgstr "No podéis dejar la provincia en blanco."
#: pkg/company.go:292 pkg/contacts.go:407
msgid "Postal code can not be empty."
msgstr "No podéis dejar el código postal en blanco."
#: pkg/company.go:293 pkg/contacts.go:408
msgid "This value is not a valid postal code."
msgstr "Este valor no es un código postal válido válido."
#: pkg/company.go:295
msgid "Selected currency is not valid."
msgstr "Habéis escogido una moneda que no es válida."
#: pkg/company.go:296
#: pkg/company.go:489
msgid "Invoice number format can not be empty."
msgstr "No podéis dejar el formato del número de factura en blanco."
#: pkg/company.go:297
#: pkg/company.go:490
msgid "Next invoice number must be a number greater than zero."
msgstr "El siguiente número de factura tiene que ser un número mayor a cero."
#: pkg/company.go:298
#: pkg/company.go:491
msgid "Quotation number format can not be empty."
msgstr "No podéis dejar el formato del número de presupuesto en blanco."
#: pkg/company.go:299
#: pkg/company.go:492
msgid "Next quotation number must be a number greater than zero."
msgstr "El siguiente número de presupuesto tiene que ser un número mayor a cero."
#: pkg/company.go:563
#: pkg/company.go:614
msgctxt "input"
msgid "Tax name"
msgstr "Nombre impuesto"
#: pkg/company.go:569
#: pkg/company.go:620
msgctxt "input"
msgid "Tax Class"
msgstr "Clase de impuesto"
#: pkg/company.go:572
#: pkg/company.go:623
msgid "Select a tax class"
msgstr "Escoged una clase de impuesto"
#: pkg/company.go:576
#: pkg/company.go:627
msgctxt "input"
msgid "Rate (%)"
msgstr "Porcentaje"
#: pkg/company.go:599
#: pkg/company.go:650
msgid "Tax name can not be empty."
msgstr "No podéis dejar el nombre del impuesto en blanco."
#: pkg/company.go:600
#: pkg/company.go:651
msgid "Selected tax class is not valid."
msgstr "Habéis escogido una clase impuesto que no es válida."
#: pkg/company.go:601
#: pkg/company.go:652
msgid "Tax rate can not be empty."
msgstr "No podéis dejar el porcentaje en blanco."
#: pkg/company.go:602
#: pkg/company.go:653
msgid "Tax rate must be an integer between -99 and 99."
msgstr "El porcentaje tiene que estar entre -99 y 99."
#: pkg/company.go:665
#: pkg/company.go:762
msgctxt "input"
msgid "Payment method name"
msgstr "Nombre del método de pago"
#: pkg/company.go:671
#: pkg/company.go:768
msgctxt "input"
msgid "Instructions"
msgstr "Instrucciones"
#: pkg/company.go:689
#: pkg/company.go:786
msgid "Payment method name can not be empty."
msgstr "No podéis dejar el nombre del método de pago en blanco."
#: pkg/company.go:690
#: pkg/company.go:787
msgid "Payment instructions can not be empty."
msgstr "No podéis dejar las instrucciones de pago en blanco."
@ -1567,6 +1580,14 @@ msgctxt "input"
msgid "Holded Excel file"
msgstr "Archivo Excel de Holded"
#~ msgctxt "title"
#~ msgid "Currency"
#~ msgstr "Moneda"
#~ msgctxt "title"
#~ msgid "New Line"
#~ msgstr "Nueva línea"
#~ msgid "Selected expense status is not valid."
#~ msgstr "Habéis escogido un estado de gasto que no es válido."

View File

@ -25,11 +25,35 @@
</a>
</li>
<li role="presentation">
<a role="menuitem" href="{{ companyURI "/tax-details" }}" data-hx-boost="true">
<a role="menuitem" href="{{ companyURI "/tax-details" }}">
<i class="ri-vip-diamond-line"></i>
{{( pgettext "Tax Details" "menu" )}}
</a>
</li>
<li role="presentation">
<a role="menuitem" href="{{ companyURI "/invoicing"}}">
<i class="ri-file-paper-2-line"></i>
{{( pgettext "Invoicing and Quoting" "title" )}}
</a>
</li>
<li role="presentation">
<a role="menuitem" href="{{ companyURI "/taxes"}}">
<i class="ri-government-line"></i>
{{( pgettext "Taxes" "title" )}}
</a>
</li>
<li role="presentation">
<a role="menuitem" href="{{ companyURI "/payment-methods"}}">
<i class="ri-money-euro-circle-line"></i>
{{( pgettext "Payment Methods" "title" )}}
</a>
</li>
<li role="presentation">
<a role="menuitem" href="{{ companyURI "/payment-accounts"}}">
<i class="ri-bank-card-2-line"></i>
{{( pgettext "Payment Accounts" "title" )}}
</a>
</li>
<li role="presentation">
<a role="menuitem" href="{{ companyURI "/switch-company" }}" data-hx-boost="true">
<i class="ri-briefcase-line"></i>

View File

@ -0,0 +1,35 @@
{{ define "title" -}}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.InvoicingForm*/ -}}
{{( pgettext "Invoicing and Quoting" "title" )}}
{{- end }}
{{ define "breadcrumbs" -}}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.InvoicingForm*/ -}}
<nav data-hx-target="main" data-hx-boost="true">
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a>{{ template "title" . }}</a>
</p>
</nav>
{{- end }}
{{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.InvoicingForm*/ -}}
<section data-hx-target="main" data-hx-swap="innerHTML show:false">
<h2>{{ template "title" . }}</h2>
<form method="POST" action="{{ companyURI "/invoicing" }}" data-hx-boost="true">
<fieldset id="invoicing">
{{ csrfToken }}
{{ template "input-field" .InvoiceNumberFormat }}
{{ template "input-field" .NextInvoiceNumber }}
{{ template "input-field" .QuoteNumberFormat }}
{{ template "input-field" .NextQuoteNumber }}
{{ template "input-field" .LegalDisclaimer }}
</fieldset>
<footer>
<button form="details" type="submit">{{( pgettext "Save changes" "action" )}}</button>
</footer>
</form>
</section>
{{- end }}

View File

@ -0,0 +1,68 @@
{{ define "title" -}}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.PaymentMethodsPage*/ -}}
{{( pgettext "Payment Methods" "title" )}}
{{- end }}
{{ define "breadcrumbs" -}}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.PaymentMethodsPage*/ -}}
<nav data-hx-target="main" data-hx-boost="true">
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a>{{ template "title" . }}</a>
</p>
</nav>
{{- end }}
{{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.PaymentMethodsPage*/ -}}
<section data-hx-target="main" data-hx-swap="innerHTML show:false">
<h2>{{ template "title" . }}</h2>
<table>
<thead>
<tr>
<th>{{( pgettext "Payment Method" "title" )}}</th>
<th>{{( pgettext "Instructions" "title" )}}</th>
<th></th>
</tr>
</thead>
{{ $confirm := ( gettext "Are you sure?" )}}
<tbody data-hx-confirm="{{ $confirm }}" data-hx-target="closest tr" data-hx-swap="outerHTML swap:1s">
{{ with .PaymentMethods }}
{{- range $method := . }}
<tr>
<td>{{ .Name }}</td>
<td>{{ .Instructions }}</td>
<td>
<form method="POST" action="{{ companyURI "/payment-methods"}}/{{ .Id }}"
data-hx-boost="true">
{{ csrfToken }}
{{ deleteMethod }}
<button class="icon" aria-label="{{( gettext "Delete payment method" )}}"
><i class="ri-delete-back-2-line"></i></button>
</form>
</td>
</tr>
{{- end }}
{{ else }}
<tr>
<td colspan="3">{{( gettext "No payment methods added yet." )}}</td>
</tr>
{{ end }}
</tbody>
</table>
<form method="POST" action="{{ companyURI "/payment-methods" }}" data-hx-boost="true">
<h3>{{( pgettext "New Payment Method" "title")}}</h3>
<fieldset>
{{ csrfToken }}
{{ with .Form -}}
{{ template "input-field" .Name }}
{{ template "input-field" .Instructions }}
{{- end }}
</fieldset>
<footer>
<button>{{( pgettext "Add new payment method" "action" )}}</button>
</footer>
</form>
</section>
{{- end }}

View File

@ -7,7 +7,7 @@
<nav data-hx-target="main" data-hx-boost="true">
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a>{{( pgettext "Company Switch" "title" )}}</a>
<a>{{ template "title" . }}</a>
</p>
</nav>
{{- end }}
@ -15,7 +15,7 @@
{{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.CompanySwitchPage*/ -}}
<section class="dialog-content" id="switch-company-dialog-content" data-hx-target="this">
<h2>{{(pgettext "Company Switch" "title")}}</h2>
<h2>{{ template "title" . }}</h2>
<table>
<thead>
<tr>

View File

@ -0,0 +1,41 @@
{{ define "title" -}}
{{( pgettext "Tax Details" "title" )}}
{{- end }}
{{ define "breadcrumbs" -}}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.TaxDetailsPage*/ -}}
<nav data-hx-target="main" data-hx-boost="true">
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a>{{ template "title" . }}</a>
</p>
</nav>
{{- end }}
{{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.TaxDetailsPage*/ -}}
<section data-hx-target="main" data-hx-swap="innerHTML show:false">
<h2>{{ template "title" . }}</h2>
{{ with .DetailsForm }}
<form method="POST" action="{{ companyURI "/tax-details" }}" data-hx-boost="true">
{{ csrfToken }}
{{ template "input-field" .BusinessName }}
{{ 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"` }}
{{ template "select-field" .Currency }}
<fieldset>
<button type="submit">{{( pgettext "Save changes" "action" )}}</button>
</fieldset>
</form>
{{ end }}
</section>
{{- end }}

View File

@ -0,0 +1,70 @@
{{ define "title" -}}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.TaxesPage*/ -}}
{{( pgettext "Taxes" "title" )}}
{{- end }}
{{ define "breadcrumbs" -}}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.TaxesPage*/ -}}
<nav data-hx-target="main" data-hx-boost="true">
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a>{{ template "title" . }}</a>
</p>
</nav>
{{- end }}
{{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.TaxesPage*/ -}}
<section data-hx-target="main" data-hx-swap="innerHTML show:false">
<h2>{{ template "title" . }}</h2>
<table>
<thead>
<tr>
<th>{{( pgettext "Tax Name" "title" )}}</th>
<th class="numeric">{{( pgettext "Rate (%)" "title" )}}</th>
<th>{{( pgettext "Class" "title" )}}</th>
<th></th>
</tr>
</thead>
{{ $confirm := ( gettext "Are you sure?" )}}
<tbody data-hx-confirm="{{ $confirm }}" data-hx-target="closest tr" data-hx-swap="outerHTML swap:1s">
{{ with .Taxes }}
{{- range $tax := . }}
<tr>
<td>{{ .Name }}</td>
<td class="numeric">{{ .Rate }}</td>
<td>{{ .Class }}</td>
<td>
<form method="POST" action="{{ companyURI "/taxes"}}/{{ .Id }}" data-hx-boost="true">
{{ csrfToken }}
{{ deleteMethod }}
<button class="icon" aria-label="{{( gettext "Delete tax" )}}"
><i class="ri-delete-back-2-line"></i></button>
</form>
</td>
</tr>
{{- end }}
{{ else }}
<tr>
<td colspan="4">{{( gettext "No taxes added yet." )}}</td>
</tr>
{{ end }}
</tbody>
</table>
<form method="POST" action="{{ companyURI "/taxes" }}" data-hx-boost="true">
<h3>{{( pgettext "New Tax" "title")}}</h3>
<fieldset>
{{ csrfToken }}
{{ with .Form -}}
{{ template "input-field" .Name }}
{{ template "input-field" .Rate }}
{{ template "select-field" .Class }}
{{- end }}
</fieldset>
<footer>
<button>{{( pgettext "Add new tax" "action" )}}</button>
</footer>
</form>
</section>
{{- end }}

View File

@ -1,178 +0,0 @@
{{ define "title" -}}
{{( pgettext "Tax Details" "title" )}}
{{- end }}
{{ define "breadcrumbs" -}}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.TaxDetailsPage*/ -}}
<nav data-hx-target="main" data-hx-boost="true">
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a>{{( pgettext "Tax Details" "title" )}}</a>
</p>
</nav>
{{- end }}
{{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.TaxDetailsPage*/ -}}
<section class="dialog-content" id="tax-details-dialog-content" data-hx-target="this">
<h2>{{(pgettext "Tax Details" "title")}}</h2>
{{ with .DetailsForm }}
<form id="details" method="POST" action="{{ companyURI "/tax-details" }}" data-hx-boost="true" data-hx-select="#tax-details-dialog-content">
{{ csrfToken }}
{{ template "input-field" .BusinessName }}
{{ 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>
<legend id="currency-legend">{{( pgettext "Currency" "title" )}}</legend>
{{ template "select-field" .Currency }}
</fieldset>
<fieldset id="invoicing">
<legend>{{( pgettext "Invoicing and Quoting" "title" )}}</legend>
{{ template "input-field" .InvoiceNumberFormat }}
{{ template "input-field" .NextInvoiceNumber }}
{{ template "input-field" .QuoteNumberFormat }}
{{ template "input-field" .NextQuoteNumber }}
{{ template "input-field" .LegalDisclaimer }}
</fieldset>
</form>
{{ end }}
<form id="newtax" method="POST" action="{{ companyURI "/tax" }}" data-hx-boost="true" data-hx-select="#tax-details-dialog-content">
{{ csrfToken }}
</form>
{{ $confirm := ( gettext "Are you sure?" )}}
<fieldset>
<table>
<thead>
<tr>
<th width="50%"></th>
<th>{{( pgettext "Tax Name" "title" )}}</th>
<th>{{( pgettext "Rate (%)" "title" )}}</th>
<th>{{( pgettext "Class" "title" )}}</th>
<th></th>
</tr>
</thead>
<tbody data-hx-confirm="{{ $confirm }}" data-hx-target="closest tr" data-hx-swap="outerHTML swap:1s">
{{ with .Taxes }}
{{- range $tax := . }}
<tr>
<td></td>
<td>{{ .Name }}</td>
<td>{{ .Rate }}</td>
<td>{{ .Class }}</td>
<td>
<form method="POST" action="{{ companyURI "/tax"}}/{{ .Id }}" data-hx-boost="true">
{{ csrfToken }}
{{ deleteMethod }}
<button class="icon" aria-label="{{( gettext "Delete tax" )}}" type="submit"><i
class="ri-delete-back-2-line"></i></button>
</form>
</td>
</tr>
{{- end }}
{{ else }}
<tr>
<td colspan="4">{{( gettext "No taxes added yet." )}}</td>
</tr>
{{ end }}
</tbody>
<tfoot>
<tr>
<th scope="row">{{( pgettext "New Line" "title")}}</th>
<td>
{{ template "input-field" .NewTaxForm.Name | addInputAttr `form="newtax"` }}
</td>
<td>
{{ template "input-field" .NewTaxForm.Rate | addInputAttr `form="newtax"` }}
</td>
<td>
{{ template "select-field" .NewTaxForm.Class | addSelectAttr `form="newtax"` }}
</td>
</tr>
<tr>
<td colspan="2"></td>
<td colspan="2">
<button form="newtax" type="submit">{{( pgettext "Add new tax" "action" )}}</button>
</td>
</tr>
</tfoot>
</table>
</fieldset>
<form id="new-payment-method" method="POST" action="{{ companyURI "/payment-method" }}" data-hx-boost="true" data-hx-select="#tax-details-dialog-content">
{{ csrfToken }}
</form>
<fieldset>
<table>
<thead>
<tr>
<th width="50%"></th>
<th>{{( pgettext "Payment Method" "title" )}}</th>
<th>{{( pgettext "Instructions" "title" )}}</th>
<th></th>
</tr>
</thead>
<tbody data-hx-confirm="{{ $confirm }}" data-hx-target="closest tr" data-hx-swap="outerHTML swap:1s">
{{ with .PaymentMethods }}
{{- range $method := . }}
<tr>
<td></td>
<td>{{ .Name }}</td>
<td>{{ .Instructions }}</td>
<td>
<form method="POST" action="{{ companyURI "/payment-method"}}/{{ .Id }}" data-hx-boost="true">
{{ csrfToken }}
{{ deleteMethod }}
<button class="icon" aria-label="{{( gettext "Delete payment method" )}}"
type="submit"><i
class="ri-delete-back-2-line"></i></button>
</form>
</td>
</tr>
{{- end }}
{{ else }}
<tr>
<td colspan="4">{{( gettext "No payment methods added yet." )}}</td>
</tr>
{{ end }}
</tbody>
<tfoot>
<tr>
<th scope="row">{{( pgettext "New Line" "title")}}</th>
<td>
{{ template "input-field" .NewPaymentMethodForm.Name | addInputAttr `form="new-payment-method"` }}
</td>
<td>
{{ template "input-field" .NewPaymentMethodForm.Instructions | addInputAttr `form="new-payment-method"` }}
</td>
</tr>
<tr>
<td colspan="2"></td>
<td colspan="2">
<button form="new-payment-method"
type="submit">{{( pgettext "Add new payment method" "action" )}}</button>
</td>
</tr>
</tfoot>
</table>
</fieldset>
<fieldset>
<button form="details" type="submit">{{( pgettext "Save changes" "action" )}}</button>
</fieldset>
</section>
{{- end }}