Allow removal of payments
I am using an htmx-infused button to remove the payment, but that button can not have the CSRF token as value, thus i have to send it in a header. The removal of payments warrants a functions, instead of just DELETE (and CASCADE) as i do for payment methods, because i have to adjust the status of expenses too. Since i already have functions for everything, it is not worth using triggers just for that.
This commit is contained in:
parent
ad5bc271b6
commit
778f9c1555
|
@ -0,0 +1,39 @@
|
||||||
|
-- Deploy numerus:remove_payment to pg
|
||||||
|
-- requires: roles
|
||||||
|
-- requires: schema_numerus
|
||||||
|
-- requires: expense_payment
|
||||||
|
-- requires: payment
|
||||||
|
-- requires: extension_pgcrypto
|
||||||
|
-- requires: update_expense_payment_status
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
create or replace function remove_payment(payment_slug uuid) returns void as
|
||||||
|
$$
|
||||||
|
declare
|
||||||
|
pid integer;
|
||||||
|
eid integer;
|
||||||
|
begin
|
||||||
|
select payment_id into pid from payment where slug = payment_slug;
|
||||||
|
if not found then
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
delete from expense_payment where payment_id = pid returning expense_id into eid;
|
||||||
|
if eid is not null then
|
||||||
|
perform update_expense_payment_status(null, eid, 0);
|
||||||
|
end if;
|
||||||
|
|
||||||
|
delete from payment where payment_id = pid;
|
||||||
|
end
|
||||||
|
$$
|
||||||
|
language plpgsql
|
||||||
|
;
|
||||||
|
|
||||||
|
revoke execute on function remove_payment(uuid) from public;
|
||||||
|
grant execute on function remove_payment(uuid) to invoicer;
|
||||||
|
grant execute on function remove_payment(uuid) to admin;
|
||||||
|
|
||||||
|
commit;
|
|
@ -21,16 +21,17 @@ $$
|
||||||
;
|
;
|
||||||
|
|
||||||
update expense
|
update expense
|
||||||
set expense_status = case when paid_amount >= expense.amount then 'paid' else 'partial' end
|
set expense_status = case
|
||||||
|
when paid_amount >= expense.amount then 'paid'
|
||||||
|
when paid_amount = 0 then 'pending'
|
||||||
|
else 'partial' end
|
||||||
from (
|
from (
|
||||||
select expense_payment.expense_id
|
select coalesce (sum(payment.amount), 0) as paid_amount
|
||||||
, sum(payment.amount) as paid_amount
|
|
||||||
from expense_payment
|
from expense_payment
|
||||||
join payment using (payment_id)
|
join payment using (payment_id)
|
||||||
group by expense_payment.expense_id
|
where expense_payment.expense_id = eid
|
||||||
) as payment
|
) as payment
|
||||||
where payment.expense_id = expense.expense_id
|
where expense.expense_id = eid
|
||||||
and expense.expense_id = eid
|
|
||||||
;
|
;
|
||||||
$$
|
$$
|
||||||
language sql
|
language sql
|
||||||
|
|
|
@ -20,6 +20,7 @@ const (
|
||||||
sessionCookie = "numerus-session"
|
sessionCookie = "numerus-session"
|
||||||
defaultRole = "guest"
|
defaultRole = "guest"
|
||||||
csrfTokenField = "csfrToken"
|
csrfTokenField = "csfrToken"
|
||||||
|
csrfTokenHeader = "X-CSRFToken"
|
||||||
)
|
)
|
||||||
|
|
||||||
type loginForm struct {
|
type loginForm struct {
|
||||||
|
@ -205,7 +206,10 @@ func LoginChecker(db *Db, next http.Handler) http.Handler {
|
||||||
|
|
||||||
func verifyCsrfTokenValid(r *http.Request) error {
|
func verifyCsrfTokenValid(r *http.Request) error {
|
||||||
user := getUser(r)
|
user := getUser(r)
|
||||||
token := r.FormValue(csrfTokenField)
|
token := r.Header.Get(csrfTokenHeader)
|
||||||
|
if token == "" {
|
||||||
|
token = r.FormValue(csrfTokenField)
|
||||||
|
}
|
||||||
if user.CsrfToken == token {
|
if user.CsrfToken == token {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -259,3 +259,22 @@ func handleEditPayment(w http.ResponseWriter, r *http.Request, params httprouter
|
||||||
}
|
}
|
||||||
htmxRedirect(w, r, companyURI(company, "/payments"))
|
htmxRedirect(w, r, companyURI(company, "/payments"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleRemovePayment(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
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(), "select remove_payment($1)", slug)
|
||||||
|
|
||||||
|
company := mustGetCompany(r)
|
||||||
|
htmxRedirect(w, r, companyURI(company, "/payments"))
|
||||||
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ func NewRouter(db *Db, demo bool) http.Handler {
|
||||||
companyRouter.POST("/payments", handleAddPayment)
|
companyRouter.POST("/payments", handleAddPayment)
|
||||||
companyRouter.GET("/payments/:slug", servePaymentForm)
|
companyRouter.GET("/payments/:slug", servePaymentForm)
|
||||||
companyRouter.PUT("/payments/:slug", handleEditPayment)
|
companyRouter.PUT("/payments/:slug", handleEditPayment)
|
||||||
|
companyRouter.DELETE("/payments/:slug", handleRemovePayment)
|
||||||
companyRouter.GET("/payment-accounts", servePaymentAccountIndex)
|
companyRouter.GET("/payment-accounts", servePaymentAccountIndex)
|
||||||
companyRouter.POST("/payment-accounts", handleAddPaymentAccount)
|
companyRouter.POST("/payment-accounts", handleAddPaymentAccount)
|
||||||
companyRouter.GET("/payment-accounts/:slug", servePaymentAccountForm)
|
companyRouter.GET("/payment-accounts/:slug", servePaymentAccountForm)
|
||||||
|
|
|
@ -58,6 +58,9 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s
|
||||||
"csrfToken": func() template.HTML {
|
"csrfToken": func() template.HTML {
|
||||||
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, csrfTokenField, user.CsrfToken))
|
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, csrfTokenField, user.CsrfToken))
|
||||||
},
|
},
|
||||||
|
"csrfHeader": func() string {
|
||||||
|
return fmt.Sprintf(`"%s": "%s"`, csrfTokenHeader, user.CsrfToken)
|
||||||
|
},
|
||||||
"addInputAttr": func(attr string, field *InputField) *InputField {
|
"addInputAttr": func(attr string, field *InputField) *InputField {
|
||||||
field.Attributes = append(field.Attributes, template.HTMLAttr(attr))
|
field.Attributes = append(field.Attributes, template.HTMLAttr(attr))
|
||||||
return field
|
return field
|
||||||
|
|
30
po/ca.po
30
po/ca.po
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: numerus\n"
|
"Project-Id-Version: numerus\n"
|
||||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||||
"POT-Creation-Date: 2024-08-10 04:08+0200\n"
|
"POT-Creation-Date: 2024-08-11 03:17+0200\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"
|
||||||
|
@ -217,6 +217,7 @@ msgstr "Descàrrega"
|
||||||
#: web/template/invoices/index.gohtml:74 web/template/switch-company.gohtml:23
|
#: 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/quotes/index.gohtml:74 web/template/contacts/index.gohtml:51
|
||||||
#: web/template/expenses/index.gohtml:76 web/template/products/index.gohtml:48
|
#: web/template/expenses/index.gohtml:76 web/template/products/index.gohtml:48
|
||||||
|
#: web/template/payments/index.gohtml:30
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
msgstr "Accions"
|
msgstr "Accions"
|
||||||
|
@ -238,7 +239,7 @@ msgstr "Accions per la factura %s"
|
||||||
#: web/template/invoices/index.gohtml:139 web/template/invoices/view.gohtml:19
|
#: web/template/invoices/index.gohtml:139 web/template/invoices/view.gohtml:19
|
||||||
#: web/template/quotes/index.gohtml:137 web/template/quotes/view.gohtml:22
|
#: web/template/quotes/index.gohtml:137 web/template/quotes/view.gohtml:22
|
||||||
#: web/template/contacts/index.gohtml:82 web/template/expenses/index.gohtml:143
|
#: web/template/contacts/index.gohtml:82 web/template/expenses/index.gohtml:143
|
||||||
#: web/template/products/index.gohtml:78
|
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:62
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
msgstr "Edita"
|
msgstr "Edita"
|
||||||
|
@ -772,7 +773,20 @@ msgctxt "title"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Descripció"
|
msgstr "Descripció"
|
||||||
|
|
||||||
|
#: web/template/payments/index.gohtml:35
|
||||||
|
msgid "Are you sure you wish to delete this payment?"
|
||||||
|
msgstr "Esteu segur de voler esborrar aquest pagament?"
|
||||||
|
|
||||||
#: web/template/payments/index.gohtml:54
|
#: web/template/payments/index.gohtml:54
|
||||||
|
msgid "Actions for payment %s"
|
||||||
|
msgstr "Accions pel pagament %s"
|
||||||
|
|
||||||
|
#: web/template/payments/index.gohtml:73
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Remove"
|
||||||
|
msgstr "Esborra"
|
||||||
|
|
||||||
|
#: web/template/payments/index.gohtml:83
|
||||||
msgid "No payments added yet."
|
msgid "No payments added yet."
|
||||||
msgstr "No hi ha cap pagament."
|
msgstr "No hi ha cap pagament."
|
||||||
|
|
||||||
|
@ -829,29 +843,29 @@ msgctxt "title"
|
||||||
msgid "VAT number"
|
msgid "VAT number"
|
||||||
msgstr "DNI / NIF"
|
msgstr "DNI / NIF"
|
||||||
|
|
||||||
#: pkg/login.go:37 pkg/company.go:127 pkg/profile.go:40 pkg/contacts.go:276
|
#: pkg/login.go:38 pkg/company.go:127 pkg/profile.go:40 pkg/contacts.go:276
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Correu-e"
|
msgstr "Correu-e"
|
||||||
|
|
||||||
#: pkg/login.go:48 pkg/profile.go:49
|
#: pkg/login.go:49 pkg/profile.go:49
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Contrasenya"
|
msgstr "Contrasenya"
|
||||||
|
|
||||||
#: pkg/login.go:75 pkg/company.go:283 pkg/profile.go:89
|
#: pkg/login.go:76 pkg/company.go:283 pkg/profile.go:89
|
||||||
msgid "Email can not be empty."
|
msgid "Email can not be empty."
|
||||||
msgstr "No podeu deixar el correu-e en blanc."
|
msgstr "No podeu deixar el correu-e en blanc."
|
||||||
|
|
||||||
#: pkg/login.go:76 pkg/company.go:284 pkg/profile.go:90 pkg/contacts.go:420
|
#: pkg/login.go:77 pkg/company.go:284 pkg/profile.go:90 pkg/contacts.go:420
|
||||||
msgid "This value is not a valid email. It should be like name@domain.com."
|
msgid "This value is not a valid email. It should be like name@domain.com."
|
||||||
msgstr "Aquest valor no és un correu-e vàlid. Hauria de ser similar a nom@domini.cat."
|
msgstr "Aquest valor no és un correu-e vàlid. Hauria de ser similar a nom@domini.cat."
|
||||||
|
|
||||||
#: pkg/login.go:78
|
#: pkg/login.go:79
|
||||||
msgid "Password can not be empty."
|
msgid "Password can not be empty."
|
||||||
msgstr "No podeu deixar la contrasenya en blanc."
|
msgstr "No podeu deixar la contrasenya en blanc."
|
||||||
|
|
||||||
#: pkg/login.go:114
|
#: pkg/login.go:115
|
||||||
msgid "Invalid user or password."
|
msgid "Invalid user or password."
|
||||||
msgstr "Nom d’usuari o contrasenya incorrectes."
|
msgstr "Nom d’usuari o contrasenya incorrectes."
|
||||||
|
|
||||||
|
|
30
po/es.po
30
po/es.po
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: numerus\n"
|
"Project-Id-Version: numerus\n"
|
||||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||||
"POT-Creation-Date: 2024-08-10 04:08+0200\n"
|
"POT-Creation-Date: 2024-08-11 03:17+0200\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"
|
||||||
|
@ -217,6 +217,7 @@ msgstr "Descargar"
|
||||||
#: web/template/invoices/index.gohtml:74 web/template/switch-company.gohtml:23
|
#: 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/quotes/index.gohtml:74 web/template/contacts/index.gohtml:51
|
||||||
#: web/template/expenses/index.gohtml:76 web/template/products/index.gohtml:48
|
#: web/template/expenses/index.gohtml:76 web/template/products/index.gohtml:48
|
||||||
|
#: web/template/payments/index.gohtml:30
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
msgstr "Acciones"
|
msgstr "Acciones"
|
||||||
|
@ -238,7 +239,7 @@ msgstr "Acciones para la factura %s"
|
||||||
#: web/template/invoices/index.gohtml:139 web/template/invoices/view.gohtml:19
|
#: web/template/invoices/index.gohtml:139 web/template/invoices/view.gohtml:19
|
||||||
#: web/template/quotes/index.gohtml:137 web/template/quotes/view.gohtml:22
|
#: web/template/quotes/index.gohtml:137 web/template/quotes/view.gohtml:22
|
||||||
#: web/template/contacts/index.gohtml:82 web/template/expenses/index.gohtml:143
|
#: web/template/contacts/index.gohtml:82 web/template/expenses/index.gohtml:143
|
||||||
#: web/template/products/index.gohtml:78
|
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:62
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
msgstr "Editar"
|
msgstr "Editar"
|
||||||
|
@ -772,7 +773,20 @@ msgctxt "title"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Descripción"
|
msgstr "Descripción"
|
||||||
|
|
||||||
|
#: web/template/payments/index.gohtml:35
|
||||||
|
msgid "Are you sure you wish to delete this payment?"
|
||||||
|
msgstr "¿Estáis seguro de querer borrar este pago?"
|
||||||
|
|
||||||
#: web/template/payments/index.gohtml:54
|
#: web/template/payments/index.gohtml:54
|
||||||
|
msgid "Actions for payment %s"
|
||||||
|
msgstr "Acciones para el pago %s"
|
||||||
|
|
||||||
|
#: web/template/payments/index.gohtml:73
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Remove"
|
||||||
|
msgstr "Borrar"
|
||||||
|
|
||||||
|
#: web/template/payments/index.gohtml:83
|
||||||
msgid "No payments added yet."
|
msgid "No payments added yet."
|
||||||
msgstr "No hay pagos."
|
msgstr "No hay pagos."
|
||||||
|
|
||||||
|
@ -829,29 +843,29 @@ msgctxt "title"
|
||||||
msgid "VAT number"
|
msgid "VAT number"
|
||||||
msgstr "DNI / NIF"
|
msgstr "DNI / NIF"
|
||||||
|
|
||||||
#: pkg/login.go:37 pkg/company.go:127 pkg/profile.go:40 pkg/contacts.go:276
|
#: pkg/login.go:38 pkg/company.go:127 pkg/profile.go:40 pkg/contacts.go:276
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Correo-e"
|
msgstr "Correo-e"
|
||||||
|
|
||||||
#: pkg/login.go:48 pkg/profile.go:49
|
#: pkg/login.go:49 pkg/profile.go:49
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Contraseña"
|
msgstr "Contraseña"
|
||||||
|
|
||||||
#: pkg/login.go:75 pkg/company.go:283 pkg/profile.go:89
|
#: pkg/login.go:76 pkg/company.go:283 pkg/profile.go:89
|
||||||
msgid "Email can not be empty."
|
msgid "Email can not be empty."
|
||||||
msgstr "No podéis dejar el correo-e en blanco."
|
msgstr "No podéis dejar el correo-e en blanco."
|
||||||
|
|
||||||
#: pkg/login.go:76 pkg/company.go:284 pkg/profile.go:90 pkg/contacts.go:420
|
#: pkg/login.go:77 pkg/company.go:284 pkg/profile.go:90 pkg/contacts.go:420
|
||||||
msgid "This value is not a valid email. It should be like name@domain.com."
|
msgid "This value is not a valid email. It should be like name@domain.com."
|
||||||
msgstr "Este valor no es un correo-e válido. Tiene que ser parecido a nombre@dominio.es."
|
msgstr "Este valor no es un correo-e válido. Tiene que ser parecido a nombre@dominio.es."
|
||||||
|
|
||||||
#: pkg/login.go:78
|
#: pkg/login.go:79
|
||||||
msgid "Password can not be empty."
|
msgid "Password can not be empty."
|
||||||
msgstr "No podéis dejar la contraseña en blanco."
|
msgstr "No podéis dejar la contraseña en blanco."
|
||||||
|
|
||||||
#: pkg/login.go:114
|
#: pkg/login.go:115
|
||||||
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."
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Revert numerus:remove_payment from pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
drop function if exists numerus.remove_payment(uuid);
|
||||||
|
|
||||||
|
commit;
|
|
@ -152,3 +152,4 @@ available_expense_status [available_expense_status@v2] 2024-08-04T05:24:08Z jord
|
||||||
update_expense_payment_status [roles schema_numerus expense payment expense_payment available_expense_status available_payment_status] 2024-08-04T06:36:00Z jordi fita mas <jordi@tandem.blog> # Add function to update payment and expense status
|
update_expense_payment_status [roles schema_numerus expense payment expense_payment available_expense_status available_payment_status] 2024-08-04T06:36:00Z jordi fita mas <jordi@tandem.blog> # Add function to update payment and expense status
|
||||||
add_payment [roles schema_numerus payment expense_payment company currency parse_price tag_name update_expense_payment_status] 2024-08-04T03:16:55Z jordi fita mas <jordi@tandem.blog> # Add function to insert new payments
|
add_payment [roles schema_numerus payment expense_payment company currency parse_price tag_name update_expense_payment_status] 2024-08-04T03:16:55Z jordi fita mas <jordi@tandem.blog> # Add function to insert new payments
|
||||||
edit_payment [roles schema_numerus payment expense_payment currency parse_price tag_name update_expense_payment_status] 2024-08-04T03:31:45Z jordi fita mas <jordi@tandem.blog> # Add function to update payments
|
edit_payment [roles schema_numerus payment expense_payment currency parse_price tag_name update_expense_payment_status] 2024-08-04T03:31:45Z jordi fita mas <jordi@tandem.blog> # Add function to update payments
|
||||||
|
remove_payment [roles schema_numerus expense_payment payment extension_pgcrypto update_expense_payment_status] 2024-08-11T00:30:58Z jordi fita mas <jordi@tandem.blog> # Add function to remove payments
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
-- Test remove_payment
|
||||||
|
set client_min_messages to warning;
|
||||||
|
create extension if not exists pgtap;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select plan(15);
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
select has_function('numerus', 'remove_payment', array['uuid']);
|
||||||
|
select function_lang_is('numerus', 'remove_payment', array['uuid'], 'plpgsql');
|
||||||
|
select function_returns('numerus', 'remove_payment', array['uuid'], 'void');
|
||||||
|
select isnt_definer('numerus', 'remove_payment', array['uuid']);
|
||||||
|
select volatility_is('numerus', 'remove_payment', array['uuid'], 'volatile');
|
||||||
|
|
||||||
|
select function_privs_are('numerus', 'remove_payment', array ['uuid'], 'guest', array []::text[]);
|
||||||
|
select function_privs_are('numerus', 'remove_payment', array ['uuid'], 'invoicer', array ['EXECUTE']);
|
||||||
|
select function_privs_are('numerus', 'remove_payment', array ['uuid'], 'admin', array ['EXECUTE']);
|
||||||
|
select function_privs_are('numerus', 'remove_payment', array ['uuid'], 'authenticator', array []::text[]);
|
||||||
|
|
||||||
|
|
||||||
|
set client_min_messages to warning;
|
||||||
|
truncate expense_payment cascade;
|
||||||
|
truncate payment cascade;
|
||||||
|
truncate expense cascade;
|
||||||
|
truncate contact cascade;
|
||||||
|
truncate payment_account cascade;
|
||||||
|
truncate payment_method cascade;
|
||||||
|
truncate company cascade;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
|
||||||
|
set constraints "company_default_payment_method_id_fkey" deferred;
|
||||||
|
|
||||||
|
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code, default_payment_method_id)
|
||||||
|
values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 111)
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into payment_method (payment_method_id, company_id, name, instructions)
|
||||||
|
values (111, 1, 'cash', 'cash')
|
||||||
|
;
|
||||||
|
|
||||||
|
set constraints "company_default_payment_method_id_fkey" immediate;
|
||||||
|
|
||||||
|
insert into contact (contact_id, company_id, name)
|
||||||
|
values ( 9, 1, 'Customer 1')
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into expense (expense_id, company_id, invoice_number, contact_id, invoice_date, amount, currency_code, expense_status)
|
||||||
|
values (13, 1, 'INV001', 9, '2011-01-11', 111, 'EUR', 'paid')
|
||||||
|
, (14, 1, 'INV002', 9, '2022-02-22', 222, 'EUR', 'paid')
|
||||||
|
, (15, 1, 'INV003', 9, '2022-02-22', 333, 'EUR', 'partial')
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into payment_account (payment_account_id, company_id, payment_account_type, name)
|
||||||
|
values (11, 1, 'cash', 'Cash 1')
|
||||||
|
, (12, 1, 'cash', 'Cash 2')
|
||||||
|
, (13, 1, 'other', 'Other')
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into payment (payment_id, company_id, slug, description, payment_date, payment_account_id, amount, currency_code, payment_status, tags)
|
||||||
|
values (16, 1, '7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'Payment INV001', '2023-05-04', 12, 111, 'EUR', 'complete', '{tag1}')
|
||||||
|
, (17, 1, 'b57b980b-247b-4be4-a0b7-03a7819c53ae', 'First INV002', '2023-05-05', 13, 100, 'EUR', 'partial', '{tag2}')
|
||||||
|
, (18, 1, '3bdad7a8-4a1e-4ae0-b5c6-015e51ee0502', 'Second INV002', '2023-05-06', 13, 122, 'EUR', 'partial', '{tag1,tag3}')
|
||||||
|
, (19, 1, '5a524bee-8311-4d13-9adf-ef6310b26990', 'Partial INV003', '2023-05-07', 11, 123, 'EUR', 'partial', '{}')
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into expense_payment (expense_id, payment_id)
|
||||||
|
values (13, 16)
|
||||||
|
, (14, 17)
|
||||||
|
, (14, 18)
|
||||||
|
, (15, 19)
|
||||||
|
;
|
||||||
|
|
||||||
|
select lives_ok(
|
||||||
|
$$ select remove_payment('7ac3ae0e-b0c1-4206-a19b-0be20835edd4') $$,
|
||||||
|
'Should be able to remove a complete payment'
|
||||||
|
);
|
||||||
|
|
||||||
|
select lives_ok(
|
||||||
|
$$ select remove_payment('5a524bee-8311-4d13-9adf-ef6310b26990') $$,
|
||||||
|
'Should be able to remove a partial payment, '
|
||||||
|
);
|
||||||
|
|
||||||
|
select lives_ok(
|
||||||
|
$$ select remove_payment('b57b980b-247b-4be4-a0b7-03a7819c53ae') $$,
|
||||||
|
'Should be able to remove a partial payment, leaving the expense’s other partial payment'
|
||||||
|
);
|
||||||
|
|
||||||
|
select bag_eq(
|
||||||
|
$$ select description, payment_date::text, payment_account_id, amount, payment_status, tags::text from payment $$,
|
||||||
|
$$ values ('Second INV002', '2023-05-06', 13, 122, 'partial', '{tag1,tag3}')
|
||||||
|
$$,
|
||||||
|
'Should have deleted all given payments'
|
||||||
|
);
|
||||||
|
|
||||||
|
select bag_eq(
|
||||||
|
$$ select expense_id, payment_id from expense_payment$$,
|
||||||
|
$$ values (14, 18)
|
||||||
|
$$,
|
||||||
|
'Should have deleted all related expenses’ payments'
|
||||||
|
);
|
||||||
|
|
||||||
|
select bag_eq(
|
||||||
|
$$ select expense_id, expense_status from expense $$,
|
||||||
|
$$ values (13, 'pending')
|
||||||
|
, (14, 'partial')
|
||||||
|
, (15, 'pending')
|
||||||
|
$$,
|
||||||
|
'Should have updated expenses too'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
select *
|
||||||
|
from finish();
|
||||||
|
|
||||||
|
rollback;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Verify numerus:remove_payment on pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select has_function_privilege('numerus.remove_payment(uuid)', 'execute');
|
||||||
|
|
||||||
|
rollback;
|
|
@ -27,10 +27,12 @@
|
||||||
<th>{{( pgettext "Status" "title" )}}</th>
|
<th>{{( pgettext "Status" "title" )}}</th>
|
||||||
<th>{{( pgettext "Tags" "title" )}}</th>
|
<th>{{( pgettext "Tags" "title" )}}</th>
|
||||||
<th class="numeric">{{( pgettext "Total" "title" )}}</th>
|
<th class="numeric">{{( pgettext "Total" "title" )}}</th>
|
||||||
|
<th>{{( pgettext "Actions" "title" )}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ with .Payments }}
|
{{ with .Payments }}
|
||||||
|
{{ $confirm := (gettext "Are you sure you wish to delete this payment?")}}
|
||||||
{{- range $payment := . }}
|
{{- range $payment := . }}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ .PaymentDate|formatDate }}</td>
|
<td>{{ .PaymentDate|formatDate }}</td>
|
||||||
|
@ -47,6 +49,33 @@
|
||||||
{{- end }}
|
{{- end }}
|
||||||
</td>
|
</td>
|
||||||
<td class="numeric">{{ .Total | formatPrice }}</td>
|
<td class="numeric">{{ .Total | formatPrice }}</td>
|
||||||
|
<td class="actions">
|
||||||
|
<details class="menu">
|
||||||
|
{{- $label := .Description | printf (gettext "Actions for payment %s") -}}
|
||||||
|
<summary aria-label="{{ $label }}"><i class="ri-more-line"></i></summary>
|
||||||
|
<ul role="menu" class="action-menu">
|
||||||
|
<li role="presentation">
|
||||||
|
<a role="menuitem" href="{{ companyURI "/payments"}}/{{ .Slug }}"
|
||||||
|
data-hx-target="main" data-hx-boost="true"
|
||||||
|
>
|
||||||
|
<i class="ri-edit-line"></i>
|
||||||
|
{{( pgettext "Edit" "action" )}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li role="presentation">
|
||||||
|
<button role="menuitem"
|
||||||
|
data-hx-delete="{{ companyURI "/payments"}}/{{ .Slug }}"
|
||||||
|
data-hx-confirm="{{ $confirm }}"
|
||||||
|
data-hx-headers='{ {{ csrfHeader }} }'
|
||||||
|
data-hx-target="main"
|
||||||
|
>
|
||||||
|
<i class="ri-delete-back-2-line"></i>
|
||||||
|
{{( pgettext "Remove" "action" )}}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
|
|
Loading…
Reference in New Issue