Add the payment method relation and corresponding form

This commit is contained in:
jordi fita mas 2023-03-03 16:49:06 +01:00
parent b84f1774f9
commit 9894925742
10 changed files with 586 additions and 109 deletions

35
deploy/payment_method.sql Normal file
View File

@ -0,0 +1,35 @@
-- Deploy numerus:payment_method to pg
-- requires: schema_numerus
-- requires: company
begin;
set search_path to numerus, public;
create table payment_method (
payment_method_id serial primary key,
company_id integer not null references company,
name text not null constraint name_not_empty check(length(trim(name)) > 0),
instructions text not null
);
grant select, insert, update, delete on table payment_method to invoicer;
grant select, insert, update, delete on table payment_method to admin;
grant usage on sequence payment_method_payment_method_id_seq to invoicer;
grant usage on sequence payment_method_payment_method_id_seq to admin;
alter table payment_method enable row level security;
create policy company_policy
on payment_method
using (
exists(
select 1
from company_user
join user_profile using (user_id)
where company_user.company_id = payment_method.company_id
)
);
commit;

View File

@ -77,6 +77,12 @@ type Tax struct {
Rate int Rate int
} }
type PaymentMethod struct {
Id int
Name string
Instructions string
}
type taxDetailsForm struct { type taxDetailsForm struct {
*contactForm *contactForm
Currency *SelectField Currency *SelectField
@ -134,9 +140,11 @@ func (form *taxDetailsForm) mustFillFromDatabase(ctx context.Context, conn *Conn
} }
type TaxDetailsPage struct { type TaxDetailsPage struct {
DetailsForm *taxDetailsForm DetailsForm *taxDetailsForm
NewTaxForm *taxForm NewTaxForm *taxForm
Taxes []*Tax Taxes []*Tax
NewPaymentMethodForm *paymentMethodForm
PaymentMethods []*PaymentMethod
} }
func GetCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func GetCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@ -178,17 +186,30 @@ func HandleCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httpr
} }
func mustRenderTaxDetailsForm(w http.ResponseWriter, r *http.Request, form *taxDetailsForm) { func mustRenderTaxDetailsForm(w http.ResponseWriter, r *http.Request, form *taxDetailsForm) {
conn := getConn(r)
locale := getLocale(r)
page := &TaxDetailsPage{ page := &TaxDetailsPage{
DetailsForm: form, DetailsForm: form,
NewTaxForm: newTaxForm(r.Context(), getConn(r), mustGetCompany(r), getLocale(r)), NewTaxForm: newTaxForm(r.Context(), conn, mustGetCompany(r), locale),
NewPaymentMethodForm: newPaymentMethodForm(locale),
} }
mustRenderTexDetailsPage(w, r, page) mustRenderTexDetailsPage(w, r, page)
} }
func mustRenderTaxForm(w http.ResponseWriter, r *http.Request, form *taxForm) { func mustRenderTaxForm(w http.ResponseWriter, r *http.Request, form *taxForm) {
page := &TaxDetailsPage{ page := &TaxDetailsPage{
DetailsForm: newTaxDetailsFormFromDatabase(r), DetailsForm: newTaxDetailsFormFromDatabase(r),
NewTaxForm: form, 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) mustRenderTexDetailsPage(w, r, page)
} }
@ -197,6 +218,7 @@ func mustRenderTexDetailsPage(w http.ResponseWriter, r *http.Request, page *TaxD
conn := getConn(r) conn := getConn(r)
company := mustGetCompany(r) company := mustGetCompany(r)
page.Taxes = mustGetTaxes(r.Context(), conn, company) page.Taxes = mustGetTaxes(r.Context(), conn, company)
page.PaymentMethods = mustCollectPaymentMethods(r.Context(), conn, company)
mustRenderAppTemplate(w, r, "tax-details.gohtml", page) mustRenderAppTemplate(w, r, "tax-details.gohtml", page)
} }
@ -231,6 +253,29 @@ func mustGetTaxes(ctx context.Context, conn *Conn, company *Company) []*Tax {
return taxes 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 { type taxForm struct {
locale *Locale locale *Locale
Name *InputField Name *InputField
@ -323,3 +368,80 @@ func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Pa
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()) 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())
http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther) http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther)
} }
type paymentMethodForm struct {
locale *Locale
Name *InputField
Instructions *InputField
}
func newPaymentMethodForm(locale *Locale) *paymentMethodForm {
return &paymentMethodForm{
locale: locale,
Name: &InputField{
Name: "method_name",
Label: pgettext("input", "Payment method name", locale),
Type: "text",
Required: true,
},
Instructions: &InputField{
Name: "method_instructions",
Label: pgettext("input", "Instructions", locale),
Type: "textarea",
Required: true,
},
}
}
func (form *paymentMethodForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
form.Name.FillValue(r)
form.Instructions.FillValue(r)
return nil
}
func (form *paymentMethodForm) Validate() bool {
validator := newFormValidator()
validator.CheckRequiredInput(form.Name, gettext("Payment method name can not be empty.", form.locale))
validator.CheckRequiredInput(form.Instructions, gettext("Payment instructions can not be empty.", form.locale))
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)
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)
company := mustGetCompany(r)
form := newPaymentMethodForm(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() {
w.WriteHeader(http.StatusUnprocessableEntity)
mustRenderPaymentMethodForm(w, r, form)
return
}
conn.MustExec(r.Context(), "insert into payment_method (company_id, name, instructions) values ($1, $2, $3)", company.Id, form.Name, form.Instructions)
http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther)
}

View File

@ -14,6 +14,8 @@ func NewRouter(db *Db) http.Handler {
companyRouter.POST("/tax-details", HandleCompanyTaxDetailsForm) companyRouter.POST("/tax-details", HandleCompanyTaxDetailsForm)
companyRouter.POST("/tax", HandleAddCompanyTax) companyRouter.POST("/tax", HandleAddCompanyTax)
companyRouter.DELETE("/tax/:taxId", HandleDeleteCompanyTax) companyRouter.DELETE("/tax/:taxId", HandleDeleteCompanyTax)
companyRouter.POST("/payment-method", HandleAddPaymentMethod)
companyRouter.DELETE("/payment-method/:paymentMethodId", HandleDeletePaymentMethod)
companyRouter.GET("/contacts", IndexContacts) companyRouter.GET("/contacts", IndexContacts)
companyRouter.POST("/contacts", HandleAddContact) companyRouter.POST("/contacts", HandleAddContact)
companyRouter.GET("/contacts/:slug", GetContactForm) companyRouter.GET("/contacts/:slug", GetContactForm)

158
po/ca.po
View File

@ -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-03-01 14:00+0100\n" "POT-Creation-Date: 2023-03-03 16:38+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"
@ -59,7 +59,7 @@ msgid "Name"
msgstr "Nom" msgstr "Nom"
#: web/template/invoices/products.gohtml:43 #: web/template/invoices/products.gohtml:43
#: web/template/invoices/view.gohtml:60 web/template/products/index.gohtml:23 #: web/template/invoices/view.gohtml:53 web/template/products/index.gohtml:23
msgctxt "title" msgctxt "title"
msgid "Price" msgid "Price"
msgstr "Preu" msgstr "Preu"
@ -74,13 +74,13 @@ msgctxt "action"
msgid "Add products" msgid "Add products"
msgstr "Afegeix productes" msgstr "Afegeix productes"
#: web/template/invoices/new.gohtml:41 web/template/invoices/view.gohtml:65 #: web/template/invoices/new.gohtml:41 web/template/invoices/view.gohtml:58
msgctxt "title" msgctxt "title"
msgid "Subtotal" msgid "Subtotal"
msgstr "Subtotal" msgstr "Subtotal"
#: web/template/invoices/new.gohtml:51 web/template/invoices/view.gohtml:69 #: web/template/invoices/new.gohtml:51 web/template/invoices/view.gohtml:62
#: web/template/invoices/view.gohtml:109 #: web/template/invoices/view.gohtml:102
msgctxt "title" msgctxt "title"
msgid "Total" msgid "Total"
msgstr "Total" msgstr "Total"
@ -149,22 +149,22 @@ msgctxt "action"
msgid "Download invoice" msgid "Download invoice"
msgstr "Descarrega factura" msgstr "Descarrega factura"
#: web/template/invoices/view.gohtml:59 #: web/template/invoices/view.gohtml:52
msgctxt "title" msgctxt "title"
msgid "Concept" msgid "Concept"
msgstr "Concepte" msgstr "Concepte"
#: web/template/invoices/view.gohtml:62 #: web/template/invoices/view.gohtml:55
msgctxt "title" msgctxt "title"
msgid "Discount" msgid "Discount"
msgstr "Descompte" msgstr "Descompte"
#: web/template/invoices/view.gohtml:64 #: web/template/invoices/view.gohtml:57
msgctxt "title" msgctxt "title"
msgid "Units" msgid "Units"
msgstr "Unitats" msgstr "Unitats"
#: web/template/invoices/view.gohtml:99 #: web/template/invoices/view.gohtml:92
msgctxt "title" msgctxt "title"
msgid "Tax Base" msgid "Tax Base"
msgstr "Base imposable" msgstr "Base imposable"
@ -286,7 +286,7 @@ msgctxt "title"
msgid "Language" msgid "Language"
msgstr "Idioma" msgstr "Idioma"
#: web/template/profile.gohtml:35 web/template/tax-details.gohtml:101 #: web/template/profile.gohtml:35 web/template/tax-details.gohtml:165
msgctxt "action" msgctxt "action"
msgid "Save changes" msgid "Save changes"
msgstr "Desa canvis" msgstr "Desa canvis"
@ -302,35 +302,59 @@ msgctxt "title"
msgid "Currency" msgid "Currency"
msgstr "Moneda" msgstr "Moneda"
#: web/template/tax-details.gohtml:47 #: web/template/tax-details.gohtml:37
msgctxt "title"
msgid "Invoicing"
msgstr "Facturació"
#: web/template/tax-details.gohtml:54
msgctxt "title" msgctxt "title"
msgid "Tax Name" msgid "Tax Name"
msgstr "Nom impost" msgstr "Nom impost"
#: web/template/tax-details.gohtml:48 #: web/template/tax-details.gohtml:55
msgctxt "title" msgctxt "title"
msgid "Rate (%)" msgid "Rate (%)"
msgstr "Percentatge" msgstr "Percentatge"
#: web/template/tax-details.gohtml:49 #: web/template/tax-details.gohtml:56
msgctxt "title" msgctxt "title"
msgid "Class" msgid "Class"
msgstr "Classe" msgstr "Classe"
#: web/template/tax-details.gohtml:73 #: web/template/tax-details.gohtml:80
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:79 #: web/template/tax-details.gohtml:86 web/template/tax-details.gohtml:146
msgctxt "title" msgctxt "title"
msgid "New Line" msgid "New Line"
msgstr "Nova línia" msgstr "Nova línia"
#: web/template/tax-details.gohtml:93 #: web/template/tax-details.gohtml:100
msgctxt "action" msgctxt "action"
msgid "Add new tax" msgid "Add new tax"
msgstr "Afegeix nou impost" msgstr "Afegeix nou impost"
#: web/template/tax-details.gohtml:116
msgctxt "title"
msgid "Payment Method"
msgstr "Mètode de pagament"
#: web/template/tax-details.gohtml:117
msgctxt "title"
msgid "Instructions"
msgstr "Instruccions"
#: web/template/tax-details.gohtml:140
msgid "No payment methods added yet."
msgstr "No hi ha cap mètode de pagament."
#: web/template/tax-details.gohtml:157
msgctxt "action"
msgid "Add new payment method"
msgstr "Afegeix nou mètode de pagament"
#: web/template/products/new.gohtml:2 web/template/products/new.gohtml:11 #: web/template/products/new.gohtml:2 web/template/products/new.gohtml:11
#: web/template/products/new.gohtml:15 #: web/template/products/new.gohtml:15
msgctxt "title" msgctxt "title"
@ -384,91 +408,123 @@ msgstr "No podeu deixar la contrasenya en blanc."
msgid "Invalid user or password." msgid "Invalid user or password."
msgstr "Nom dusuari o contrasenya incorrectes." msgstr "Nom dusuari o contrasenya incorrectes."
#: pkg/products.go:165 pkg/invoices.go:468 #: pkg/products.go:165 pkg/invoices.go:469
msgctxt "input" msgctxt "input"
msgid "Name" msgid "Name"
msgstr "Nom" msgstr "Nom"
#: pkg/products.go:171 pkg/invoices.go:473 #: pkg/products.go:171 pkg/invoices.go:474
msgctxt "input" msgctxt "input"
msgid "Description" msgid "Description"
msgstr "Descripció" msgstr "Descripció"
#: pkg/products.go:176 pkg/invoices.go:477 #: pkg/products.go:176 pkg/invoices.go:478
msgctxt "input" msgctxt "input"
msgid "Price" msgid "Price"
msgstr "Preu" msgstr "Preu"
#: pkg/products.go:186 pkg/invoices.go:366 pkg/invoices.go:503 #: pkg/products.go:186 pkg/invoices.go:367 pkg/invoices.go:504
msgctxt "input" msgctxt "input"
msgid "Taxes" msgid "Taxes"
msgstr "Imposts" msgstr "Imposts"
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:399 #: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:400
#: pkg/invoices.go:539 #: pkg/invoices.go:540
msgid "Name can not be empty." msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc." msgstr "No podeu deixar el nom en blanc."
#: pkg/products.go:207 pkg/invoices.go:540 #: pkg/products.go:207 pkg/invoices.go:541
msgid "Price can not be empty." msgid "Price can not be empty."
msgstr "No podeu deixar el preu en blanc." msgstr "No podeu deixar el preu en blanc."
#: pkg/products.go:208 pkg/invoices.go:541 #: pkg/products.go:208 pkg/invoices.go:542
msgid "Price must be a number greater than zero." msgid "Price must be a number greater than zero."
msgstr "El preu ha de ser un número major a zero." msgstr "El preu ha de ser un número major a zero."
#: pkg/products.go:210 pkg/invoices.go:403 pkg/invoices.go:549 #: pkg/products.go:210 pkg/invoices.go:404 pkg/invoices.go:550
msgid "Selected tax is not valid." msgid "Selected tax is not valid."
msgstr "Heu seleccionat un impost que no és vàlid." msgstr "Heu seleccionat un impost que no és vàlid."
#: pkg/products.go:211 pkg/invoices.go:550 #: pkg/products.go:211 pkg/invoices.go:551
msgid "You can only select a tax of each class." msgid "You can only select a tax of each class."
msgstr "Només podeu seleccionar un impost de cada classe." msgstr "Només podeu seleccionar un impost de cada classe."
#: pkg/company.go:90 #: pkg/company.go:98
msgctxt "input" msgctxt "input"
msgid "Currency" msgid "Currency"
msgstr "Moneda" msgstr "Moneda"
#: pkg/company.go:108 #: pkg/company.go:105
msgctxt "input"
msgid "Invoice number format"
msgstr "Format del número de factura"
#: pkg/company.go:111
msgctxt "input"
msgid "Legal disclaimer"
msgstr "Nota legal"
#: pkg/company.go:129
msgid "Selected currency is not valid." msgid "Selected currency is not valid."
msgstr "Heu seleccionat una moneda que no és vàlida." msgstr "Heu seleccionat una moneda que no és vàlida."
#: pkg/company.go:230 #: pkg/company.go:130
msgid "Invoice number format can not be empty."
msgstr "No podeu deixar el format del número de factura en blanc."
#: pkg/company.go:291
msgctxt "input" msgctxt "input"
msgid "Tax name" msgid "Tax name"
msgstr "Nom impost" msgstr "Nom impost"
#: pkg/company.go:236 #: pkg/company.go:297
msgctxt "input" msgctxt "input"
msgid "Tax Class" msgid "Tax Class"
msgstr "Classe dimpost" msgstr "Classe dimpost"
#: pkg/company.go:239 #: pkg/company.go:300
msgid "Select a tax class" msgid "Select a tax class"
msgstr "Escolliu una classe dimpost" msgstr "Escolliu una classe dimpost"
#: pkg/company.go:243 #: pkg/company.go:304
msgctxt "input" msgctxt "input"
msgid "Rate (%)" msgid "Rate (%)"
msgstr "Percentatge" msgstr "Percentatge"
#: pkg/company.go:266 #: pkg/company.go:327
msgid "Tax name can not be empty." msgid "Tax name can not be empty."
msgstr "No podeu deixar el nom de limpost en blanc." msgstr "No podeu deixar el nom de limpost en blanc."
#: pkg/company.go:267 #: pkg/company.go:328
msgid "Selected tax class is not valid." msgid "Selected tax class is not valid."
msgstr "Heu seleccionat una classe dimpost que no és vàlida." msgstr "Heu seleccionat una classe dimpost que no és vàlida."
#: pkg/company.go:268 #: pkg/company.go:329
msgid "Tax rate can not be empty." msgid "Tax rate can not be empty."
msgstr "No podeu deixar percentatge en blanc." msgstr "No podeu deixar percentatge en blanc."
#: pkg/company.go:269 #: pkg/company.go:330
msgid "Tax rate must be an integer between -99 and 99." msgid "Tax rate must be an integer between -99 and 99."
msgstr "El percentatge ha de ser entre -99 i 99." msgstr "El percentatge ha de ser entre -99 i 99."
#: pkg/company.go:383
msgctxt "input"
msgid "Payment method name"
msgstr "Nom del mètode de pagament"
#: pkg/company.go:389
msgctxt "input"
msgid "Instructions"
msgstr "Instruccions"
#: pkg/company.go:407
msgid "Payment method name can not be empty."
msgstr "No podeu deixar el nom del mètode de pagament en blanc."
#: pkg/company.go:408
msgid "Payment instructions can not be empty."
msgstr "No podeu deixar les instruccions de pagament en blanc."
#: pkg/profile.go:25 #: pkg/profile.go:25
msgctxt "language option" msgctxt "language option"
msgid "Automatic" msgid "Automatic"
@ -497,70 +553,70 @@ msgstr "La confirmació no és igual a la contrasenya."
msgid "Selected language is not valid." msgid "Selected language is not valid."
msgstr "Heu seleccionat un idioma que no és vàlid." msgstr "Heu seleccionat un idioma que no és vàlid."
#: pkg/invoices.go:229 #: pkg/invoices.go:230
msgid "Select a customer to bill." msgid "Select a customer to bill."
msgstr "Escolliu un client a facturar." msgstr "Escolliu un client a facturar."
#: pkg/invoices.go:322 #: pkg/invoices.go:323
msgid "Invalid action" msgid "Invalid action"
msgstr "Acció invàlida." msgstr "Acció invàlida."
#: pkg/invoices.go:343 #: pkg/invoices.go:344
msgctxt "input" msgctxt "input"
msgid "Customer" msgid "Customer"
msgstr "Client" msgstr "Client"
#: pkg/invoices.go:349 #: pkg/invoices.go:350
msgctxt "input" msgctxt "input"
msgid "Number" msgid "Number"
msgstr "Número" msgstr "Número"
#: pkg/invoices.go:355 #: pkg/invoices.go:356
msgctxt "input" msgctxt "input"
msgid "Invoice Date" msgid "Invoice Date"
msgstr "Data de factura" msgstr "Data de factura"
#: pkg/invoices.go:361 #: pkg/invoices.go:362
msgctxt "input" msgctxt "input"
msgid "Notes" msgid "Notes"
msgstr "Notes" msgstr "Notes"
#: pkg/invoices.go:400 #: pkg/invoices.go:401
msgid "Invoice date can not be empty." msgid "Invoice date can not be empty."
msgstr "No podeu deixar la data de la factura en blanc." msgstr "No podeu deixar la data de la factura en blanc."
#: pkg/invoices.go:401 #: pkg/invoices.go:402
msgid "Invoice date must be a valid date." msgid "Invoice date must be a valid date."
msgstr "La data de facturació ha de ser vàlida." msgstr "La data de facturació ha de ser vàlida."
#: pkg/invoices.go:463 #: pkg/invoices.go:464
msgctxt "input" msgctxt "input"
msgid "Id" msgid "Id"
msgstr "Identificador" msgstr "Identificador"
#: pkg/invoices.go:486 #: pkg/invoices.go:487
msgctxt "input" msgctxt "input"
msgid "Quantity" msgid "Quantity"
msgstr "Quantitat" msgstr "Quantitat"
#: pkg/invoices.go:494 #: pkg/invoices.go:495
msgctxt "input" msgctxt "input"
msgid "Discount (%)" msgid "Discount (%)"
msgstr "Descompte (%)" msgstr "Descompte (%)"
#: pkg/invoices.go:543 #: pkg/invoices.go:544
msgid "Quantity can not be empty." msgid "Quantity can not be empty."
msgstr "No podeu deixar la quantitat en blanc." msgstr "No podeu deixar la quantitat en blanc."
#: pkg/invoices.go:544 #: pkg/invoices.go:545
msgid "Quantity must be a number greater than zero." msgid "Quantity must be a number greater than zero."
msgstr "La quantitat ha de ser un número major a zero." msgstr "La quantitat ha de ser un número major a zero."
#: pkg/invoices.go:546 #: pkg/invoices.go:547
msgid "Discount can not be empty." msgid "Discount can not be empty."
msgstr "No podeu deixar el descompte en blanc." msgstr "No podeu deixar el descompte en blanc."
#: pkg/invoices.go:547 #: pkg/invoices.go:548
msgid "Discount must be a percentage between 0 and 100." msgid "Discount must be a percentage between 0 and 100."
msgstr "El descompte ha de ser un percentatge entre 0 i 100." msgstr "El descompte ha de ser un percentatge entre 0 i 100."

158
po/es.po
View File

@ -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-03-01 14:00+0100\n" "POT-Creation-Date: 2023-03-03 16:38+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"
@ -59,7 +59,7 @@ msgid "Name"
msgstr "Nombre" msgstr "Nombre"
#: web/template/invoices/products.gohtml:43 #: web/template/invoices/products.gohtml:43
#: web/template/invoices/view.gohtml:60 web/template/products/index.gohtml:23 #: web/template/invoices/view.gohtml:53 web/template/products/index.gohtml:23
msgctxt "title" msgctxt "title"
msgid "Price" msgid "Price"
msgstr "Precio" msgstr "Precio"
@ -74,13 +74,13 @@ msgctxt "action"
msgid "Add products" msgid "Add products"
msgstr "Añadir productos" msgstr "Añadir productos"
#: web/template/invoices/new.gohtml:41 web/template/invoices/view.gohtml:65 #: web/template/invoices/new.gohtml:41 web/template/invoices/view.gohtml:58
msgctxt "title" msgctxt "title"
msgid "Subtotal" msgid "Subtotal"
msgstr "Subtotal" msgstr "Subtotal"
#: web/template/invoices/new.gohtml:51 web/template/invoices/view.gohtml:69 #: web/template/invoices/new.gohtml:51 web/template/invoices/view.gohtml:62
#: web/template/invoices/view.gohtml:109 #: web/template/invoices/view.gohtml:102
msgctxt "title" msgctxt "title"
msgid "Total" msgid "Total"
msgstr "Total" msgstr "Total"
@ -149,22 +149,22 @@ msgctxt "action"
msgid "Download invoice" msgid "Download invoice"
msgstr "Descargar factura" msgstr "Descargar factura"
#: web/template/invoices/view.gohtml:59 #: web/template/invoices/view.gohtml:52
msgctxt "title" msgctxt "title"
msgid "Concept" msgid "Concept"
msgstr "Concepto" msgstr "Concepto"
#: web/template/invoices/view.gohtml:62 #: web/template/invoices/view.gohtml:55
msgctxt "title" msgctxt "title"
msgid "Discount" msgid "Discount"
msgstr "Descuento" msgstr "Descuento"
#: web/template/invoices/view.gohtml:64 #: web/template/invoices/view.gohtml:57
msgctxt "title" msgctxt "title"
msgid "Units" msgid "Units"
msgstr "Unidades" msgstr "Unidades"
#: web/template/invoices/view.gohtml:99 #: web/template/invoices/view.gohtml:92
msgctxt "title" msgctxt "title"
msgid "Tax Base" msgid "Tax Base"
msgstr "Base imponible" msgstr "Base imponible"
@ -286,7 +286,7 @@ msgctxt "title"
msgid "Language" msgid "Language"
msgstr "Idioma" msgstr "Idioma"
#: web/template/profile.gohtml:35 web/template/tax-details.gohtml:101 #: web/template/profile.gohtml:35 web/template/tax-details.gohtml:165
msgctxt "action" msgctxt "action"
msgid "Save changes" msgid "Save changes"
msgstr "Guardar cambios" msgstr "Guardar cambios"
@ -302,35 +302,59 @@ msgctxt "title"
msgid "Currency" msgid "Currency"
msgstr "Moneda" msgstr "Moneda"
#: web/template/tax-details.gohtml:47 #: web/template/tax-details.gohtml:37
msgctxt "title"
msgid "Invoicing"
msgstr "Facturació"
#: web/template/tax-details.gohtml:54
msgctxt "title" msgctxt "title"
msgid "Tax Name" msgid "Tax Name"
msgstr "Nombre impuesto" msgstr "Nombre impuesto"
#: web/template/tax-details.gohtml:48 #: web/template/tax-details.gohtml:55
msgctxt "title" msgctxt "title"
msgid "Rate (%)" msgid "Rate (%)"
msgstr "Porcentaje" msgstr "Porcentaje"
#: web/template/tax-details.gohtml:49 #: web/template/tax-details.gohtml:56
msgctxt "title" msgctxt "title"
msgid "Class" msgid "Class"
msgstr "Clase" msgstr "Clase"
#: web/template/tax-details.gohtml:73 #: web/template/tax-details.gohtml:80
msgid "No taxes added yet." msgid "No taxes added yet."
msgstr "No hay impuestos." msgstr "No hay impuestos."
#: web/template/tax-details.gohtml:79 #: web/template/tax-details.gohtml:86 web/template/tax-details.gohtml:146
msgctxt "title" msgctxt "title"
msgid "New Line" msgid "New Line"
msgstr "Nueva línea" msgstr "Nueva línea"
#: web/template/tax-details.gohtml:93 #: web/template/tax-details.gohtml:100
msgctxt "action" msgctxt "action"
msgid "Add new tax" msgid "Add new tax"
msgstr "Añadir nuevo impuesto" msgstr "Añadir nuevo impuesto"
#: web/template/tax-details.gohtml:116
msgctxt "title"
msgid "Payment Method"
msgstr "Método de pago"
#: web/template/tax-details.gohtml:117
msgctxt "title"
msgid "Instructions"
msgstr "Instrucciones"
#: web/template/tax-details.gohtml:140
msgid "No payment methods added yet."
msgstr "No hay métodos de pago."
#: web/template/tax-details.gohtml:157
msgctxt "action"
msgid "Add new payment method"
msgstr "Añadir nuevo método de pago"
#: web/template/products/new.gohtml:2 web/template/products/new.gohtml:11 #: web/template/products/new.gohtml:2 web/template/products/new.gohtml:11
#: web/template/products/new.gohtml:15 #: web/template/products/new.gohtml:15
msgctxt "title" msgctxt "title"
@ -384,91 +408,123 @@ msgstr "No podéis dejar la contraseña en blanco."
msgid "Invalid user or password." msgid "Invalid user or password."
msgstr "Nombre de usuario o contraseña inválido." msgstr "Nombre de usuario o contraseña inválido."
#: pkg/products.go:165 pkg/invoices.go:468 #: pkg/products.go:165 pkg/invoices.go:469
msgctxt "input" msgctxt "input"
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
#: pkg/products.go:171 pkg/invoices.go:473 #: pkg/products.go:171 pkg/invoices.go:474
msgctxt "input" msgctxt "input"
msgid "Description" msgid "Description"
msgstr "Descripción" msgstr "Descripción"
#: pkg/products.go:176 pkg/invoices.go:477 #: pkg/products.go:176 pkg/invoices.go:478
msgctxt "input" msgctxt "input"
msgid "Price" msgid "Price"
msgstr "Precio" msgstr "Precio"
#: pkg/products.go:186 pkg/invoices.go:366 pkg/invoices.go:503 #: pkg/products.go:186 pkg/invoices.go:367 pkg/invoices.go:504
msgctxt "input" msgctxt "input"
msgid "Taxes" msgid "Taxes"
msgstr "Impuestos" msgstr "Impuestos"
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:399 #: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:400
#: pkg/invoices.go:539 #: pkg/invoices.go:540
msgid "Name can not be empty." msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco." msgstr "No podéis dejar el nombre en blanco."
#: pkg/products.go:207 pkg/invoices.go:540 #: pkg/products.go:207 pkg/invoices.go:541
msgid "Price can not be empty." msgid "Price can not be empty."
msgstr "No podéis dejar el precio en blanco." msgstr "No podéis dejar el precio en blanco."
#: pkg/products.go:208 pkg/invoices.go:541 #: pkg/products.go:208 pkg/invoices.go:542
msgid "Price must be a number greater than zero." msgid "Price must be a number greater than zero."
msgstr "El precio tiene que ser un número mayor a cero." msgstr "El precio tiene que ser un número mayor a cero."
#: pkg/products.go:210 pkg/invoices.go:403 pkg/invoices.go:549 #: pkg/products.go:210 pkg/invoices.go:404 pkg/invoices.go:550
msgid "Selected tax is not valid." msgid "Selected tax is not valid."
msgstr "Habéis escogido un impuesto que no es válido." msgstr "Habéis escogido un impuesto que no es válido."
#: pkg/products.go:211 pkg/invoices.go:550 #: pkg/products.go:211 pkg/invoices.go:551
msgid "You can only select a tax of each class." msgid "You can only select a tax of each class."
msgstr "Solo podéis escojer un impuesto de cada clase." msgstr "Solo podéis escojer un impuesto de cada clase."
#: pkg/company.go:90 #: pkg/company.go:98
msgctxt "input" msgctxt "input"
msgid "Currency" msgid "Currency"
msgstr "Moneda" msgstr "Moneda"
#: pkg/company.go:108 #: pkg/company.go:105
msgctxt "input"
msgid "Invoice number format"
msgstr "Formato del número de factura"
#: pkg/company.go:111
msgctxt "input"
msgid "Legal disclaimer"
msgstr "Nota legal"
#: pkg/company.go:129
msgid "Selected currency is not valid." msgid "Selected currency is not valid."
msgstr "Habéis escogido una moneda que no es válida." msgstr "Habéis escogido una moneda que no es válida."
#: pkg/company.go:230 #: pkg/company.go:130
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:291
msgctxt "input" msgctxt "input"
msgid "Tax name" msgid "Tax name"
msgstr "Nombre impuesto" msgstr "Nombre impuesto"
#: pkg/company.go:236 #: pkg/company.go:297
msgctxt "input" msgctxt "input"
msgid "Tax Class" msgid "Tax Class"
msgstr "Clase de impuesto" msgstr "Clase de impuesto"
#: pkg/company.go:239 #: pkg/company.go:300
msgid "Select a tax class" msgid "Select a tax class"
msgstr "Escoged una clase de impuesto" msgstr "Escoged una clase de impuesto"
#: pkg/company.go:243 #: pkg/company.go:304
msgctxt "input" msgctxt "input"
msgid "Rate (%)" msgid "Rate (%)"
msgstr "Porcentaje" msgstr "Porcentaje"
#: pkg/company.go:266 #: pkg/company.go:327
msgid "Tax name can not be empty." msgid "Tax name can not be empty."
msgstr "No podéis dejar el nombre del impuesto en blanco." msgstr "No podéis dejar el nombre del impuesto en blanco."
#: pkg/company.go:267 #: pkg/company.go:328
msgid "Selected tax class is not valid." msgid "Selected tax class is not valid."
msgstr "Habéis escogido una clase impuesto que no es válida." msgstr "Habéis escogido una clase impuesto que no es válida."
#: pkg/company.go:268 #: pkg/company.go:329
msgid "Tax rate can not be empty." msgid "Tax rate can not be empty."
msgstr "No podéis dejar el porcentaje en blanco." msgstr "No podéis dejar el porcentaje en blanco."
#: pkg/company.go:269 #: pkg/company.go:330
msgid "Tax rate must be an integer between -99 and 99." msgid "Tax rate must be an integer between -99 and 99."
msgstr "El porcentaje tiene que estar entre -99 y 99." msgstr "El porcentaje tiene que estar entre -99 y 99."
#: pkg/company.go:383
msgctxt "input"
msgid "Payment method name"
msgstr "Nombre del método de pago"
#: pkg/company.go:389
msgctxt "input"
msgid "Instructions"
msgstr "Instrucciones"
#: pkg/company.go:407
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:408
msgid "Payment instructions can not be empty."
msgstr "No podéis dejar las instrucciones de pago en blanco."
#: pkg/profile.go:25 #: pkg/profile.go:25
msgctxt "language option" msgctxt "language option"
msgid "Automatic" msgid "Automatic"
@ -497,70 +553,70 @@ msgstr "La confirmación no corresponde con la contraseña."
msgid "Selected language is not valid." msgid "Selected language is not valid."
msgstr "Habéis escogido un idioma que no es válido." msgstr "Habéis escogido un idioma que no es válido."
#: pkg/invoices.go:229 #: pkg/invoices.go:230
msgid "Select a customer to bill." msgid "Select a customer to bill."
msgstr "Escoged un cliente a facturar." msgstr "Escoged un cliente a facturar."
#: pkg/invoices.go:322 #: pkg/invoices.go:323
msgid "Invalid action" msgid "Invalid action"
msgstr "Acción inválida." msgstr "Acción inválida."
#: pkg/invoices.go:343 #: pkg/invoices.go:344
msgctxt "input" msgctxt "input"
msgid "Customer" msgid "Customer"
msgstr "Cliente" msgstr "Cliente"
#: pkg/invoices.go:349 #: pkg/invoices.go:350
msgctxt "input" msgctxt "input"
msgid "Number" msgid "Number"
msgstr "Número" msgstr "Número"
#: pkg/invoices.go:355 #: pkg/invoices.go:356
msgctxt "input" msgctxt "input"
msgid "Invoice Date" msgid "Invoice Date"
msgstr "Fecha de factura" msgstr "Fecha de factura"
#: pkg/invoices.go:361 #: pkg/invoices.go:362
msgctxt "input" msgctxt "input"
msgid "Notes" msgid "Notes"
msgstr "Notas" msgstr "Notas"
#: pkg/invoices.go:400 #: pkg/invoices.go:401
msgid "Invoice date can not be empty." msgid "Invoice date can not be empty."
msgstr "No podéis dejar la fecha de la factura en blanco." msgstr "No podéis dejar la fecha de la factura en blanco."
#: pkg/invoices.go:401 #: pkg/invoices.go:402
msgid "Invoice date must be a valid date." msgid "Invoice date must be a valid date."
msgstr "La fecha de factura debe ser válida." msgstr "La fecha de factura debe ser válida."
#: pkg/invoices.go:463 #: pkg/invoices.go:464
msgctxt "input" msgctxt "input"
msgid "Id" msgid "Id"
msgstr "Identificador" msgstr "Identificador"
#: pkg/invoices.go:486 #: pkg/invoices.go:487
msgctxt "input" msgctxt "input"
msgid "Quantity" msgid "Quantity"
msgstr "Cantidad" msgstr "Cantidad"
#: pkg/invoices.go:494 #: pkg/invoices.go:495
msgctxt "input" msgctxt "input"
msgid "Discount (%)" msgid "Discount (%)"
msgstr "Descuento (%)" msgstr "Descuento (%)"
#: pkg/invoices.go:543 #: pkg/invoices.go:544
msgid "Quantity can not be empty." msgid "Quantity can not be empty."
msgstr "No podéis dejar la cantidad en blanco." msgstr "No podéis dejar la cantidad en blanco."
#: pkg/invoices.go:544 #: pkg/invoices.go:545
msgid "Quantity must be a number greater than zero." msgid "Quantity must be a number greater than zero."
msgstr "La cantidad tiene que ser un número mayor a cero." msgstr "La cantidad tiene que ser un número mayor a cero."
#: pkg/invoices.go:546 #: pkg/invoices.go:547
msgid "Discount can not be empty." msgid "Discount can not be empty."
msgstr "No podéis dejar el descuento en blanco." msgstr "No podéis dejar el descuento en blanco."
#: pkg/invoices.go:547 #: pkg/invoices.go:548
msgid "Discount must be a percentage between 0 and 100." msgid "Discount must be a percentage between 0 and 100."
msgstr "El descuento tiene que ser un percentage entre 0 y 100." msgstr "El descuento tiene que ser un percentage entre 0 y 100."

View File

@ -0,0 +1,7 @@
-- Revert numerus:payment_method from pg
begin;
drop table if exists numerus.payment_method;
commit;

View File

@ -62,3 +62,4 @@ invoice_product_amount [schema_numerus invoice_product invoice_product_tax] 2023
invoice_amount [schema_numerus invoice_product invoice_product_amount] 2023-02-22T12:58:46Z jordi fita mas <jordi@tandem.blog> # Add view to compute subtotal and total for invoices invoice_amount [schema_numerus invoice_product invoice_product_amount] 2023-02-22T12:58:46Z jordi fita mas <jordi@tandem.blog> # Add view to compute subtotal and total for invoices
new_invoice_amount [schema_numerus] 2023-02-23T12:08:25Z jordi fita mas <jordi@tandem.blog> # Add type to return when computing new invoice amounts new_invoice_amount [schema_numerus] 2023-02-23T12:08:25Z jordi fita mas <jordi@tandem.blog> # Add type to return when computing new invoice amounts
compute_new_invoice_amount [schema_numerus company currency tax new_invoice_product new_invoice_amount] 2023-02-23T12:20:13Z jordi fita mas <jordi@tandem.blog> # Add function to compute the subtotal, taxes, and total amounts for a new invoice compute_new_invoice_amount [schema_numerus company currency tax new_invoice_product new_invoice_amount] 2023-02-23T12:20:13Z jordi fita mas <jordi@tandem.blog> # Add function to compute the subtotal, taxes, and total amounts for a new invoice
payment_method [schema_numerus company] 2023-03-03T15:00:41Z jordi fita mas <jordi@tandem.blog> # Add relation of payment method

126
test/payment_method.sql Normal file
View File

@ -0,0 +1,126 @@
-- Test payment_method
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(36);
set search_path to numerus, auth, public;
select has_table('payment_method');
select has_pk('payment_method' );
select table_privs_are('payment_method', 'guest', array []::text[]);
select table_privs_are('payment_method', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('payment_method', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('payment_method', 'authenticator', array []::text[]);
select has_sequence('payment_method_payment_method_id_seq');
select sequence_privs_are('payment_method_payment_method_id_seq', 'guest', array[]::text[]);
select sequence_privs_are('payment_method_payment_method_id_seq', 'invoicer', array['USAGE']);
select sequence_privs_are('payment_method_payment_method_id_seq', 'admin', array['USAGE']);
select sequence_privs_are('payment_method_payment_method_id_seq', 'authenticator', array[]::text[]);
select has_column('payment_method', 'payment_method_id');
select col_is_pk('payment_method', 'payment_method_id');
select col_type_is('payment_method', 'payment_method_id', 'integer');
select col_not_null('payment_method', 'payment_method_id');
select col_has_default('payment_method', 'payment_method_id');
select col_default_is('payment_method', 'payment_method_id', 'nextval(''payment_method_payment_method_id_seq''::regclass)');
select has_column('payment_method', 'company_id');
select col_is_fk('payment_method', 'company_id');
select fk_ok('payment_method', 'company_id', 'company', 'company_id');
select col_type_is('payment_method', 'company_id', 'integer');
select col_not_null('payment_method', 'company_id');
select col_hasnt_default('payment_method', 'company_id');
select has_column('payment_method', 'name');
select col_type_is('payment_method', 'name', 'text');
select col_not_null('payment_method', 'name');
select col_hasnt_default('payment_method', 'name');
select has_column('payment_method', 'instructions');
select col_type_is('payment_method', 'instructions', 'text');
select col_not_null('payment_method', 'instructions');
select col_hasnt_default('payment_method', 'instructions');
set client_min_messages to warning;
truncate payment_method cascade;
truncate company_user cascade;
truncate company cascade;
truncate auth."user" cascade;
reset client_min_messages;
insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at)
values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
, (5, 'admin@tandem.blog', 'Demo', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
;
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code)
values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR')
, (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD')
;
insert into company_user (company_id, user_id)
values (2, 1)
, (4, 5)
;
insert into payment_method(company_id, name, instructions)
values (2, 'Cash', 'Payment in cash.')
, (2, 'Wire Transfer', 'Please, transfer money to my Nigerian bank')
, (4, 'Pigeon', 'Send money via carrier pigeon.')
;
prepare payment_method_data as
select company_id, name, instructions
from payment_method;
set role invoicer;
select is_empty('payment_method_data', 'Should show no data when cookie is not set yet');
reset role;
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog');
select bag_eq(
'payment_method_data',
$$ values ( 2, 'Cash', 'Payment in cash.' )
, ( 2, 'Wire Transfer', 'Please, transfer money to my Nigerian bank' )
$$,
'Should only list payment methods of the companies where demo@tandem.blog is user of'
);
reset role;
select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog');
select bag_eq(
'payment_method_data',
$$ values (4, 'Pigeon', 'Send money via carrier pigeon.' )
$$,
'Should only list payment methods of the companies where admin@tandem.blog is user of'
);
reset role;
select set_cookie('not-a-cookie');
select throws_ok(
'payment_method_data',
'42501', 'permission denied for table payment_method',
'Should not allow select to guest users'
);
reset role;
select throws_ok( $$
insert into payment_method (company_id, name, instructions)
values (2, ' ', 'atasht')
$$,
'23514', 'new row for relation "payment_method" violates check constraint "name_not_empty"',
'Should not allow payment methods with blank name'
);
select *
from finish();
rollback;

15
verify/payment_method.sql Normal file
View File

@ -0,0 +1,15 @@
-- Verify numerus:payment_method on pg
begin;
select payment_method_id
, company_id
, name
, instructions
from numerus.payment_method
where false;
select 1 / count(*) from pg_class where oid = 'numerus.payment_method'::regclass and relrowsecurity;
select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.payment_method'::regclass;
rollback;

View File

@ -104,6 +104,63 @@
</table> </table>
</fieldset> </fieldset>
<form id="new-payment-method" method="POST" action="{{ companyURI "/payment-method" }}">
{{ 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>
{{ with .PaymentMethods }}
{{- range $method := . }}
<tr>
<td></td>
<td>{{ .Name }}</td>
<td>{{ .Instructions }}</td>
<td>
<form method="POST" action="{{ companyURI "/payment-method"}}/{{ .Id }}">
{{ 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> <fieldset>
<button form="details" type="submit">{{( pgettext "Save changes" "action" )}}</button> <button form="details" type="submit">{{( pgettext "Save changes" "action" )}}</button>
</fieldset> </fieldset>