Add attachments to payments
This commit is contained in:
parent
58cef8c00b
commit
c95f172499
|
@ -0,0 +1,30 @@
|
|||
-- Deploy numerus:attach_to_payment to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_numerus
|
||||
-- requires: payment
|
||||
-- requires: payment_attachment
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
create or replace function attach_to_payment(payment_slug uuid, original_filename text, mime_type text, content bytea) returns void as
|
||||
$$
|
||||
insert into payment_attachment (payment_id, original_filename, mime_type, content)
|
||||
select payment_id, original_filename, mime_type, content
|
||||
from payment
|
||||
where slug = payment_slug
|
||||
on conflict (payment_id) do update
|
||||
set original_filename = excluded.original_filename
|
||||
, mime_type = excluded.mime_type
|
||||
, content = excluded.content
|
||||
;
|
||||
$$
|
||||
language sql
|
||||
;
|
||||
|
||||
revoke execute on function attach_to_payment(uuid, text, text, bytea) from public;
|
||||
grant execute on function attach_to_payment(uuid, text, text, bytea) to invoicer;
|
||||
grant execute on function attach_to_payment(uuid, text, text, bytea) to admin;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,33 @@
|
|||
-- Deploy numerus:payment_attachment to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_numerus
|
||||
-- requires: payment
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
create table payment_attachment (
|
||||
payment_id integer primary key references payment,
|
||||
original_filename text not null,
|
||||
mime_type text not null,
|
||||
content bytea not null
|
||||
);
|
||||
|
||||
grant select, insert, update, delete on table payment_attachment to invoicer;
|
||||
grant select, insert, update, delete on table payment_attachment to admin;
|
||||
|
||||
alter table payment_attachment enable row level security;
|
||||
|
||||
create policy company_policy
|
||||
on payment_attachment
|
||||
using (
|
||||
exists(
|
||||
select 1
|
||||
from payment
|
||||
where payment.payment_id = payment_attachment.payment_id
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
commit;
|
|
@ -3,6 +3,7 @@
|
|||
-- requires: schema_numerus
|
||||
-- requires: expense_payment
|
||||
-- requires: payment
|
||||
-- requires: payment_attachment
|
||||
-- requires: update_expense_payment_status
|
||||
|
||||
begin;
|
||||
|
@ -25,6 +26,7 @@ begin
|
|||
perform update_expense_payment_status(null, eid, 0);
|
||||
end if;
|
||||
|
||||
delete from payment_attachment where payment_id = pid;
|
||||
delete from payment where payment_id = pid;
|
||||
end
|
||||
$$
|
||||
|
|
|
@ -33,14 +33,15 @@ func (page *PaymentIndexPage) MustRender(w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
|
||||
type PaymentEntry struct {
|
||||
ID int
|
||||
Slug string
|
||||
PaymentDate time.Time
|
||||
Description string
|
||||
Total string
|
||||
Tags []string
|
||||
Status string
|
||||
StatusLabel string
|
||||
ID int
|
||||
Slug string
|
||||
PaymentDate time.Time
|
||||
Description string
|
||||
Total string
|
||||
OriginalFileName string
|
||||
Tags []string
|
||||
Status string
|
||||
StatusLabel string
|
||||
}
|
||||
|
||||
func mustCollectPaymentEntries(ctx context.Context, conn *Conn, locale *Locale) []*PaymentEntry {
|
||||
|
@ -53,9 +54,11 @@ func mustCollectPaymentEntries(ctx context.Context, conn *Conn, locale *Locale)
|
|||
, payment.tags
|
||||
, payment.payment_status
|
||||
, psi18n.name
|
||||
, coalesce(attachment.original_filename, '')
|
||||
from payment
|
||||
join payment_status_i18n psi18n on payment.payment_status = psi18n.payment_status and psi18n.lang_tag = $1
|
||||
join currency using (currency_code)
|
||||
left join payment_attachment as attachment using (payment_id)
|
||||
order by payment_date desc, total desc
|
||||
`, locale.Language)
|
||||
defer rows.Close()
|
||||
|
@ -63,7 +66,7 @@ func mustCollectPaymentEntries(ctx context.Context, conn *Conn, locale *Locale)
|
|||
var entries []*PaymentEntry
|
||||
for rows.Next() {
|
||||
entry := &PaymentEntry{}
|
||||
if err := rows.Scan(&entry.ID, &entry.Slug, &entry.PaymentDate, &entry.Description, &entry.Total, &entry.Tags, &entry.Status, &entry.StatusLabel); err != nil {
|
||||
if err := rows.Scan(&entry.ID, &entry.Slug, &entry.PaymentDate, &entry.Description, &entry.Total, &entry.Tags, &entry.Status, &entry.StatusLabel, &entry.OriginalFileName); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
|
@ -105,6 +108,7 @@ type PaymentForm struct {
|
|||
PaymentDate *InputField
|
||||
PaymentAccount *SelectField
|
||||
Amount *InputField
|
||||
File *FileField
|
||||
Tags *TagsField
|
||||
}
|
||||
|
||||
|
@ -140,6 +144,11 @@ func newPaymentForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
|
|||
template.HTMLAttr(fmt.Sprintf(`step="%v"`, company.MinCents())),
|
||||
},
|
||||
},
|
||||
File: &FileField{
|
||||
Name: "file",
|
||||
Label: pgettext("input", "File", locale),
|
||||
MaxSize: 1 << 20,
|
||||
},
|
||||
Tags: &TagsField{
|
||||
Name: "tags",
|
||||
Label: pgettext("input", "Tags", locale),
|
||||
|
@ -182,13 +191,16 @@ func (f *PaymentForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug
|
|||
}
|
||||
|
||||
func (f *PaymentForm) Parse(r *http.Request) error {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
if err := r.ParseMultipartForm(f.File.MaxSize); err != nil {
|
||||
return err
|
||||
}
|
||||
f.Description.FillValue(r)
|
||||
f.PaymentDate.FillValue(r)
|
||||
f.PaymentAccount.FillValue(r)
|
||||
f.Amount.FillValue(r)
|
||||
if err := f.File.FillValue(r); err != nil {
|
||||
return err
|
||||
}
|
||||
f.Tags.FillValue(r)
|
||||
return nil
|
||||
}
|
||||
|
@ -224,7 +236,10 @@ func handleAddPayment(w http.ResponseWriter, r *http.Request, _ httprouter.Param
|
|||
form.MustRender(w, r)
|
||||
return
|
||||
}
|
||||
conn.MustExec(r.Context(), "select add_payment($1, $2, $3, $4, $5, $6, $7)", company.Id, nil, form.PaymentDate, form.PaymentAccount, form.Description, form.Amount, form.Tags)
|
||||
slug := conn.MustGetText(r.Context(), "", "select add_payment($1, $2, $3, $4, $5, $6, $7)", company.Id, nil, form.PaymentDate, form.PaymentAccount, form.Description, form.Amount, form.Tags)
|
||||
if len(form.File.Content) > 0 {
|
||||
conn.MustQuery(r.Context(), "select attach_to_payment($1, $2, $3, $4)", slug, form.File.OriginalFileName, form.File.ContentType, form.File.Content)
|
||||
}
|
||||
htmxRedirect(w, r, companyURI(company, "/payments"))
|
||||
}
|
||||
|
||||
|
@ -257,6 +272,9 @@ func handleEditPayment(w http.ResponseWriter, r *http.Request, params httprouter
|
|||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if len(form.File.Content) > 0 {
|
||||
conn.MustQuery(r.Context(), "select attach_to_payment($1, $2, $3, $4)", form.Slug, form.File.OriginalFileName, form.File.ContentType, form.File.Content)
|
||||
}
|
||||
htmxRedirect(w, r, companyURI(company, "/payments"))
|
||||
}
|
||||
|
||||
|
@ -278,3 +296,13 @@ func handleRemovePayment(w http.ResponseWriter, r *http.Request, params httprout
|
|||
company := mustGetCompany(r)
|
||||
htmxRedirect(w, r, companyURI(company, "/payments"))
|
||||
}
|
||||
|
||||
func servePaymentAttachment(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
serveAttachment(w, r, params, `
|
||||
select mime_type
|
||||
, content
|
||||
from payment
|
||||
join payment_attachment using (payment_id)
|
||||
where slug = $1
|
||||
`)
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ func NewRouter(db *Db, demo bool) http.Handler {
|
|||
companyRouter.GET("/payments/:slug", servePaymentForm)
|
||||
companyRouter.PUT("/payments/:slug", handleEditPayment)
|
||||
companyRouter.DELETE("/payments/:slug", handleRemovePayment)
|
||||
companyRouter.GET("/payments/:slug/download/:filename", servePaymentAttachment)
|
||||
companyRouter.GET("/payment-accounts", servePaymentAccountIndex)
|
||||
companyRouter.POST("/payment-accounts", handleAddPaymentAccount)
|
||||
companyRouter.GET("/payment-accounts/:slug", servePaymentAccountForm)
|
||||
|
|
58
po/ca.po
58
po/ca.po
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: numerus\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2024-08-11 03:17+0200\n"
|
||||
"POT-Creation-Date: 2024-08-12 00:06+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"
|
||||
|
@ -121,7 +121,7 @@ msgstr "Total"
|
|||
#: web/template/invoices/new.gohtml:92 web/template/invoices/edit.gohtml:93
|
||||
#: web/template/quotes/new.gohtml:92 web/template/quotes/edit.gohtml:93
|
||||
#: web/template/expenses/new.gohtml:57 web/template/expenses/edit.gohtml:59
|
||||
#: web/template/payments/edit.gohtml:35
|
||||
#: web/template/payments/edit.gohtml:37
|
||||
#: web/template/payments/accounts/edit.gohtml:38
|
||||
msgctxt "action"
|
||||
msgid "Update"
|
||||
|
@ -132,7 +132,7 @@ msgstr "Actualitza"
|
|||
#: web/template/contacts/new.gohtml:49 web/template/contacts/edit.gohtml:53
|
||||
#: web/template/expenses/new.gohtml:60 web/template/expenses/edit.gohtml:62
|
||||
#: web/template/products/new.gohtml:30 web/template/products/edit.gohtml:36
|
||||
#: web/template/payments/new.gohtml:33
|
||||
#: web/template/payments/new.gohtml:35
|
||||
#: web/template/payments/accounts/new.gohtml:41
|
||||
msgctxt "action"
|
||||
msgid "Save"
|
||||
|
@ -209,7 +209,7 @@ msgid "Amount"
|
|||
msgstr "Import"
|
||||
|
||||
#: web/template/invoices/index.gohtml:73 web/template/quotes/index.gohtml:73
|
||||
#: web/template/expenses/index.gohtml:75
|
||||
#: web/template/expenses/index.gohtml:75 web/template/payments/index.gohtml:30
|
||||
msgctxt "title"
|
||||
msgid "Download"
|
||||
msgstr "Descàrrega"
|
||||
|
@ -217,7 +217,7 @@ 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/payments/index.gohtml:30
|
||||
#: web/template/payments/index.gohtml:31
|
||||
msgctxt "title"
|
||||
msgid "Actions"
|
||||
msgstr "Accions"
|
||||
|
@ -239,7 +239,7 @@ msgstr "Accions per la factura %s"
|
|||
#: 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/contacts/index.gohtml:82 web/template/expenses/index.gohtml:143
|
||||
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:62
|
||||
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:71
|
||||
msgctxt "action"
|
||||
msgid "Edit"
|
||||
msgstr "Edita"
|
||||
|
@ -773,20 +773,20 @@ msgctxt "title"
|
|||
msgid "Description"
|
||||
msgstr "Descripció"
|
||||
|
||||
#: web/template/payments/index.gohtml:35
|
||||
#: web/template/payments/index.gohtml:36
|
||||
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:63
|
||||
msgid "Actions for payment %s"
|
||||
msgstr "Accions pel pagament %s"
|
||||
|
||||
#: web/template/payments/index.gohtml:73
|
||||
#: web/template/payments/index.gohtml:82
|
||||
msgctxt "action"
|
||||
msgid "Remove"
|
||||
msgstr "Esborra"
|
||||
|
||||
#: web/template/payments/index.gohtml:83
|
||||
#: web/template/payments/index.gohtml:92
|
||||
msgid "No payments added yet."
|
||||
msgstr "No hi ha cap pagament."
|
||||
|
||||
|
@ -876,7 +876,7 @@ msgid "Name"
|
|||
msgstr "Nom"
|
||||
|
||||
#: pkg/products.go:177 pkg/products.go:303 pkg/quote.go:174 pkg/quote.go:708
|
||||
#: pkg/payments.go:145 pkg/expenses.go:343 pkg/expenses.go:510
|
||||
#: pkg/payments.go:154 pkg/expenses.go:343 pkg/expenses.go:510
|
||||
#: pkg/invoices.go:177 pkg/invoices.go:877 pkg/invoices.go:1462
|
||||
#: pkg/contacts.go:154 pkg/contacts.go:362
|
||||
msgctxt "input"
|
||||
|
@ -911,7 +911,7 @@ msgstr "Qualsevol"
|
|||
msgid "Invoices must have at least one of the specified labels."
|
||||
msgstr "Les factures han de tenir com a mínim una de les etiquetes."
|
||||
|
||||
#: pkg/products.go:282 pkg/quote.go:915 pkg/payments.go:117
|
||||
#: pkg/products.go:282 pkg/quote.go:915 pkg/payments.go:121
|
||||
#: pkg/invoices.go:1161
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
|
@ -1205,8 +1205,8 @@ msgstr "pressuposts.zip"
|
|||
msgid "quotations.ods"
|
||||
msgstr "pressuposts.ods"
|
||||
|
||||
#: pkg/quote.go:634 pkg/quote.go:1176 pkg/quote.go:1184 pkg/expenses.go:719
|
||||
#: pkg/expenses.go:749 pkg/invoices.go:684 pkg/invoices.go:1437
|
||||
#: pkg/quote.go:634 pkg/quote.go:1176 pkg/quote.go:1184 pkg/expenses.go:704
|
||||
#: pkg/expenses.go:734 pkg/invoices.go:684 pkg/invoices.go:1437
|
||||
#: pkg/invoices.go:1445
|
||||
msgid "Invalid action"
|
||||
msgstr "Acció invàlida."
|
||||
|
@ -1326,42 +1326,47 @@ msgstr "La confirmació no és igual a la contrasenya."
|
|||
msgid "Selected language is not valid."
|
||||
msgstr "Heu seleccionat un idioma que no és vàlid."
|
||||
|
||||
#: pkg/payments.go:123 pkg/expenses.go:305 pkg/invoices.go:866
|
||||
#: pkg/payments.go:127 pkg/expenses.go:305 pkg/invoices.go:866
|
||||
msgctxt "input"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Data de factura"
|
||||
|
||||
#: pkg/payments.go:129
|
||||
#: pkg/payments.go:133
|
||||
msgctxt "input"
|
||||
msgid "Account"
|
||||
msgstr "Compte"
|
||||
|
||||
#: pkg/payments.go:135 pkg/expenses.go:320
|
||||
#: pkg/payments.go:139 pkg/expenses.go:320
|
||||
msgctxt "input"
|
||||
msgid "Amount"
|
||||
msgstr "Import"
|
||||
|
||||
#: pkg/payments.go:152
|
||||
#: pkg/payments.go:149 pkg/expenses.go:331 pkg/invoices.go:888
|
||||
msgctxt "input"
|
||||
msgid "File"
|
||||
msgstr "Fitxer"
|
||||
|
||||
#: pkg/payments.go:161
|
||||
msgid "Select an account."
|
||||
msgstr "Escolliu un compte."
|
||||
|
||||
#: pkg/payments.go:198
|
||||
#: pkg/payments.go:210
|
||||
msgid "Description can not be empty."
|
||||
msgstr "No podeu deixar la descripció en blanc."
|
||||
|
||||
#: pkg/payments.go:199
|
||||
#: pkg/payments.go:211
|
||||
msgid "Selected payment account is not valid."
|
||||
msgstr "Heu seleccionat un compte de pagament que no és vàlid."
|
||||
|
||||
#: pkg/payments.go:200
|
||||
#: pkg/payments.go:212
|
||||
msgid "Payment date must be a valid date."
|
||||
msgstr "La data de pagament ha de ser vàlida."
|
||||
|
||||
#: pkg/payments.go:201 pkg/expenses.go:381
|
||||
#: pkg/payments.go:213 pkg/expenses.go:381
|
||||
msgid "Amount can not be empty."
|
||||
msgstr "No podeu deixar l’import en blanc."
|
||||
|
||||
#: pkg/payments.go:202 pkg/expenses.go:382
|
||||
#: pkg/payments.go:214 pkg/expenses.go:382
|
||||
msgid "Amount must be a number greater than zero."
|
||||
msgstr "L’import ha de ser un número major a zero."
|
||||
|
||||
|
@ -1470,11 +1475,6 @@ msgctxt "input"
|
|||
msgid "Invoice number"
|
||||
msgstr "Número de factura"
|
||||
|
||||
#: pkg/expenses.go:331 pkg/invoices.go:888
|
||||
msgctxt "input"
|
||||
msgid "File"
|
||||
msgstr "Fitxer"
|
||||
|
||||
#: pkg/expenses.go:337 pkg/expenses.go:514
|
||||
msgctxt "input"
|
||||
msgid "Expense Status"
|
||||
|
@ -1501,7 +1501,7 @@ msgctxt "input"
|
|||
msgid "Invoice Number"
|
||||
msgstr "Número de factura"
|
||||
|
||||
#: pkg/expenses.go:747
|
||||
#: pkg/expenses.go:732
|
||||
msgid "expenses.ods"
|
||||
msgstr "despeses.ods"
|
||||
|
||||
|
|
58
po/es.po
58
po/es.po
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: numerus\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2024-08-11 03:17+0200\n"
|
||||
"POT-Creation-Date: 2024-08-12 00:06+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"
|
||||
|
@ -121,7 +121,7 @@ msgstr "Total"
|
|||
#: web/template/invoices/new.gohtml:92 web/template/invoices/edit.gohtml:93
|
||||
#: web/template/quotes/new.gohtml:92 web/template/quotes/edit.gohtml:93
|
||||
#: web/template/expenses/new.gohtml:57 web/template/expenses/edit.gohtml:59
|
||||
#: web/template/payments/edit.gohtml:35
|
||||
#: web/template/payments/edit.gohtml:37
|
||||
#: web/template/payments/accounts/edit.gohtml:38
|
||||
msgctxt "action"
|
||||
msgid "Update"
|
||||
|
@ -132,7 +132,7 @@ msgstr "Actualizar"
|
|||
#: web/template/contacts/new.gohtml:49 web/template/contacts/edit.gohtml:53
|
||||
#: web/template/expenses/new.gohtml:60 web/template/expenses/edit.gohtml:62
|
||||
#: web/template/products/new.gohtml:30 web/template/products/edit.gohtml:36
|
||||
#: web/template/payments/new.gohtml:33
|
||||
#: web/template/payments/new.gohtml:35
|
||||
#: web/template/payments/accounts/new.gohtml:41
|
||||
msgctxt "action"
|
||||
msgid "Save"
|
||||
|
@ -209,7 +209,7 @@ msgid "Amount"
|
|||
msgstr "Importe"
|
||||
|
||||
#: web/template/invoices/index.gohtml:73 web/template/quotes/index.gohtml:73
|
||||
#: web/template/expenses/index.gohtml:75
|
||||
#: web/template/expenses/index.gohtml:75 web/template/payments/index.gohtml:30
|
||||
msgctxt "title"
|
||||
msgid "Download"
|
||||
msgstr "Descargar"
|
||||
|
@ -217,7 +217,7 @@ 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/payments/index.gohtml:30
|
||||
#: web/template/payments/index.gohtml:31
|
||||
msgctxt "title"
|
||||
msgid "Actions"
|
||||
msgstr "Acciones"
|
||||
|
@ -239,7 +239,7 @@ msgstr "Acciones para la factura %s"
|
|||
#: 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/contacts/index.gohtml:82 web/template/expenses/index.gohtml:143
|
||||
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:62
|
||||
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:71
|
||||
msgctxt "action"
|
||||
msgid "Edit"
|
||||
msgstr "Editar"
|
||||
|
@ -773,20 +773,20 @@ msgctxt "title"
|
|||
msgid "Description"
|
||||
msgstr "Descripción"
|
||||
|
||||
#: web/template/payments/index.gohtml:35
|
||||
#: web/template/payments/index.gohtml:36
|
||||
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:63
|
||||
msgid "Actions for payment %s"
|
||||
msgstr "Acciones para el pago %s"
|
||||
|
||||
#: web/template/payments/index.gohtml:73
|
||||
#: web/template/payments/index.gohtml:82
|
||||
msgctxt "action"
|
||||
msgid "Remove"
|
||||
msgstr "Borrar"
|
||||
|
||||
#: web/template/payments/index.gohtml:83
|
||||
#: web/template/payments/index.gohtml:92
|
||||
msgid "No payments added yet."
|
||||
msgstr "No hay pagos."
|
||||
|
||||
|
@ -876,7 +876,7 @@ msgid "Name"
|
|||
msgstr "Nombre"
|
||||
|
||||
#: pkg/products.go:177 pkg/products.go:303 pkg/quote.go:174 pkg/quote.go:708
|
||||
#: pkg/payments.go:145 pkg/expenses.go:343 pkg/expenses.go:510
|
||||
#: pkg/payments.go:154 pkg/expenses.go:343 pkg/expenses.go:510
|
||||
#: pkg/invoices.go:177 pkg/invoices.go:877 pkg/invoices.go:1462
|
||||
#: pkg/contacts.go:154 pkg/contacts.go:362
|
||||
msgctxt "input"
|
||||
|
@ -911,7 +911,7 @@ msgstr "Cualquiera"
|
|||
msgid "Invoices must have at least one of the specified labels."
|
||||
msgstr "Las facturas deben tener como mínimo una de las etiquetas."
|
||||
|
||||
#: pkg/products.go:282 pkg/quote.go:915 pkg/payments.go:117
|
||||
#: pkg/products.go:282 pkg/quote.go:915 pkg/payments.go:121
|
||||
#: pkg/invoices.go:1161
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
|
@ -1205,8 +1205,8 @@ msgstr "presupuestos.zip"
|
|||
msgid "quotations.ods"
|
||||
msgstr "presupuestos.ods"
|
||||
|
||||
#: pkg/quote.go:634 pkg/quote.go:1176 pkg/quote.go:1184 pkg/expenses.go:719
|
||||
#: pkg/expenses.go:749 pkg/invoices.go:684 pkg/invoices.go:1437
|
||||
#: pkg/quote.go:634 pkg/quote.go:1176 pkg/quote.go:1184 pkg/expenses.go:704
|
||||
#: pkg/expenses.go:734 pkg/invoices.go:684 pkg/invoices.go:1437
|
||||
#: pkg/invoices.go:1445
|
||||
msgid "Invalid action"
|
||||
msgstr "Acción inválida."
|
||||
|
@ -1326,42 +1326,47 @@ msgstr "La confirmación no corresponde con la contraseña."
|
|||
msgid "Selected language is not valid."
|
||||
msgstr "Habéis escogido un idioma que no es válido."
|
||||
|
||||
#: pkg/payments.go:123 pkg/expenses.go:305 pkg/invoices.go:866
|
||||
#: pkg/payments.go:127 pkg/expenses.go:305 pkg/invoices.go:866
|
||||
msgctxt "input"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Fecha de factura"
|
||||
|
||||
#: pkg/payments.go:129
|
||||
#: pkg/payments.go:133
|
||||
msgctxt "input"
|
||||
msgid "Account"
|
||||
msgstr "Cuenta"
|
||||
|
||||
#: pkg/payments.go:135 pkg/expenses.go:320
|
||||
#: pkg/payments.go:139 pkg/expenses.go:320
|
||||
msgctxt "input"
|
||||
msgid "Amount"
|
||||
msgstr "Importe"
|
||||
|
||||
#: pkg/payments.go:152
|
||||
#: pkg/payments.go:149 pkg/expenses.go:331 pkg/invoices.go:888
|
||||
msgctxt "input"
|
||||
msgid "File"
|
||||
msgstr "Archivo"
|
||||
|
||||
#: pkg/payments.go:161
|
||||
msgid "Select an account."
|
||||
msgstr "Escoged una cuenta."
|
||||
|
||||
#: pkg/payments.go:198
|
||||
#: pkg/payments.go:210
|
||||
msgid "Description can not be empty."
|
||||
msgstr "No podéis dejar la descripción en blanco."
|
||||
|
||||
#: pkg/payments.go:199
|
||||
#: pkg/payments.go:211
|
||||
msgid "Selected payment account is not valid."
|
||||
msgstr "Habéis escogido una cuenta de pago que no es válida."
|
||||
|
||||
#: pkg/payments.go:200
|
||||
#: pkg/payments.go:212
|
||||
msgid "Payment date must be a valid date."
|
||||
msgstr "La fecha de pago debe ser válida."
|
||||
|
||||
#: pkg/payments.go:201 pkg/expenses.go:381
|
||||
#: pkg/payments.go:213 pkg/expenses.go:381
|
||||
msgid "Amount can not be empty."
|
||||
msgstr "No podéis dejar el importe en blanco."
|
||||
|
||||
#: pkg/payments.go:202 pkg/expenses.go:382
|
||||
#: pkg/payments.go:214 pkg/expenses.go:382
|
||||
msgid "Amount must be a number greater than zero."
|
||||
msgstr "El importe tiene que ser un número mayor a cero."
|
||||
|
||||
|
@ -1470,11 +1475,6 @@ msgctxt "input"
|
|||
msgid "Invoice number"
|
||||
msgstr "Número de factura"
|
||||
|
||||
#: pkg/expenses.go:331 pkg/invoices.go:888
|
||||
msgctxt "input"
|
||||
msgid "File"
|
||||
msgstr "Archivo"
|
||||
|
||||
#: pkg/expenses.go:337 pkg/expenses.go:514
|
||||
msgctxt "input"
|
||||
msgid "Expense Status"
|
||||
|
@ -1501,7 +1501,7 @@ msgctxt "input"
|
|||
msgid "Invoice Number"
|
||||
msgstr "Número de factura"
|
||||
|
||||
#: pkg/expenses.go:747
|
||||
#: pkg/expenses.go:732
|
||||
msgid "expenses.ods"
|
||||
msgstr "gastos.ods"
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:attach_to_payment from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop function if exists numerus.attach_to_payment(uuid, text, text, bytea);
|
||||
|
||||
commit;
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:payment_attachment from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop table if exists numerus.payment_attachment;
|
||||
|
||||
commit;
|
|
@ -152,4 +152,6 @@ 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
|
||||
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
|
||||
payment_attachment [roles schema_numerus payment] 2024-08-11T21:01:50Z jordi fita mas <jordi@tandem.blog> # Add relation of payment attachments
|
||||
attach_to_payment [roles schema_numerus payment payment_attachment] 2024-08-11T21:33:36Z jordi fita mas <jordi@tandem.blog> # Add function to attach files to payments
|
||||
remove_payment [roles schema_numerus expense_payment payment payment_attachment update_expense_payment_status] 2024-08-11T00:30:58Z jordi fita mas <jordi@tandem.blog> # Add function to remove payments
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
-- Test attach_to_payment
|
||||
set client_min_messages to warning;
|
||||
create extension if not exists pgtap;
|
||||
reset client_min_messages;
|
||||
|
||||
begin;
|
||||
|
||||
select plan(12);
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
select has_function('numerus', 'attach_to_payment', array['uuid', 'text', 'text', 'bytea']);
|
||||
select function_lang_is('numerus', 'attach_to_payment', array['uuid', 'text', 'text', 'bytea'], 'sql');
|
||||
select function_returns('numerus', 'attach_to_payment', array['uuid', 'text', 'text', 'bytea'], 'void');
|
||||
select isnt_definer('numerus', 'attach_to_payment', array['uuid', 'text', 'text', 'bytea']);
|
||||
select volatility_is('numerus', 'attach_to_payment', array['uuid', 'text', 'text', 'bytea'], 'volatile');
|
||||
select function_privs_are('numerus', 'attach_to_payment', array ['uuid', 'text', 'text', 'bytea'], 'guest', array []::text[]);
|
||||
select function_privs_are('numerus', 'attach_to_payment', array ['uuid', 'text', 'text', 'bytea'], 'invoicer', array ['EXECUTE']);
|
||||
select function_privs_are('numerus', 'attach_to_payment', array ['uuid', 'text', 'text', 'bytea'], 'admin', array ['EXECUTE']);
|
||||
select function_privs_are('numerus', 'attach_to_payment', array ['uuid', 'text', 'text', 'bytea'], 'authenticator', array []::text[]);
|
||||
|
||||
|
||||
set client_min_messages to warning;
|
||||
truncate payment_attachment cascade;
|
||||
truncate payment 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 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)
|
||||
values (16, 1, '7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'Payment 1', '2023-05-04', 12, 111, 'EUR', 'complete')
|
||||
, (17, 1, 'b57b980b-247b-4be4-a0b7-03a7819c53ae', 'Payment 2', '2023-05-05', 13, 100, 'EUR', 'partial')
|
||||
;
|
||||
|
||||
insert into payment_attachment (payment_id, original_filename, mime_type, content)
|
||||
values (17, 'something.txt', 'text/plain', convert_to('Once upon a time…', 'UTF-8'))
|
||||
;
|
||||
|
||||
select lives_ok(
|
||||
$$ select attach_to_payment('7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'payment.txt', 'text/plain', convert_to('To pay 42 €', 'UTF-8')) $$,
|
||||
'Should be able to attach a document to the first payment'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select attach_to_payment('b57b980b-247b-4be4-a0b7-03a7819c53ae', 'payment.html', 'text/html', convert_to('<html><p>To pay 42 €</p></html>', 'UTF-8')) $$,
|
||||
'Should be able to replate the second payment’s attachment with a new document'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select payment_id, original_filename, mime_type, convert_from(content, 'UTF-8') from payment_attachment $$,
|
||||
$$ values (16, 'payment.txt', 'text/plain', 'To pay 42 €')
|
||||
, (17, 'payment.html', 'text/html', '<html><p>To pay 42 €</p></html>')
|
||||
$$,
|
||||
'Should have attached all documents'
|
||||
);
|
||||
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,131 @@
|
|||
-- Test payment_attachment
|
||||
set client_min_messages to warning;
|
||||
create extension if not exists pgtap;
|
||||
reset client_min_messages;
|
||||
|
||||
begin;
|
||||
|
||||
select plan(29);
|
||||
|
||||
set search_path to numerus, auth, public;
|
||||
|
||||
select has_table('payment_attachment');
|
||||
select has_pk('payment_attachment');
|
||||
select table_privs_are('payment_attachment', 'guest', array []::text[]);
|
||||
select table_privs_are('payment_attachment', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||
select table_privs_are('payment_attachment', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||
select table_privs_are('payment_attachment', 'authenticator', array []::text[]);
|
||||
|
||||
select has_column('payment_attachment', 'payment_id');
|
||||
select col_is_pk('payment_attachment', 'payment_id');
|
||||
select col_is_fk('payment_attachment', 'payment_id');
|
||||
select fk_ok('payment_attachment', 'payment_id', 'payment', 'payment_id');
|
||||
select col_type_is('payment_attachment', 'payment_id', 'integer');
|
||||
select col_not_null('payment_attachment', 'payment_id');
|
||||
select col_hasnt_default('payment_attachment', 'payment_id');
|
||||
|
||||
select has_column('payment_attachment', 'original_filename');
|
||||
select col_type_is('payment_attachment', 'original_filename', 'text');
|
||||
select col_not_null('payment_attachment', 'original_filename');
|
||||
select col_hasnt_default('payment_attachment', 'original_filename');
|
||||
|
||||
select has_column('payment_attachment', 'mime_type');
|
||||
select col_type_is('payment_attachment', 'mime_type', 'text');
|
||||
select col_not_null('payment_attachment', 'mime_type');
|
||||
select col_hasnt_default('payment_attachment', 'mime_type');
|
||||
|
||||
select has_column('payment_attachment', 'content');
|
||||
select col_type_is('payment_attachment', 'content', 'bytea');
|
||||
select col_not_null('payment_attachment', 'content');
|
||||
select col_hasnt_default('payment_attachment', 'content');
|
||||
|
||||
|
||||
set client_min_messages to warning;
|
||||
truncate payment_attachment cascade;
|
||||
truncate payment cascade;
|
||||
truncate company_user cascade;
|
||||
truncate payment_method 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')
|
||||
;
|
||||
|
||||
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 (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 222)
|
||||
, (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD', 444)
|
||||
;
|
||||
|
||||
insert into payment_method (payment_method_id, company_id, name, instructions)
|
||||
values (444, 4, 'cash', 'cash')
|
||||
, (222, 2, 'cash', 'cash')
|
||||
;
|
||||
|
||||
set constraints "company_default_payment_method_id_fkey" immediate;
|
||||
|
||||
insert into company_user (company_id, user_id)
|
||||
values (2, 1)
|
||||
, (4, 5)
|
||||
;
|
||||
|
||||
insert into payment_account(payment_account_id, company_id, payment_account_type, name)
|
||||
values (8, 2, 'other', 'Other 2')
|
||||
, (9, 4, 'other', 'Other 4')
|
||||
;
|
||||
|
||||
insert into payment (payment_id, company_id, description, payment_account_id, payment_date, amount, currency_code)
|
||||
values (13, 2, 'Payment 2', 8, '2011-01-11', 111, 'EUR')
|
||||
, (14, 4, 'Payment 4', 9, '2022-02-22', 222, 'EUR')
|
||||
;
|
||||
|
||||
insert into payment_attachment (payment_id, original_filename, mime_type, content)
|
||||
values (13, 'payment.txt', 'text/plain', convert_to('Payment 42', 'UTF8'))
|
||||
, (14, 'payment.html', 'text/html', convert_to('<html>Payment <em>42</em></html>', 'UTF8'))
|
||||
;
|
||||
|
||||
prepare payment_attachment_data as
|
||||
select payment_id, original_filename
|
||||
from payment_attachment
|
||||
order by payment_id, original_filename;
|
||||
|
||||
set role invoicer;
|
||||
select is_empty('payment_attachment_data', 'Should show no data when cookie is not set yet');
|
||||
reset role;
|
||||
|
||||
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog');
|
||||
select bag_eq(
|
||||
'payment_attachment_data',
|
||||
$$ values (13, 'payment.txt')
|
||||
$$,
|
||||
'Should only list payment attachmements of the companies where demo@tandem.blog is user of'
|
||||
);
|
||||
reset role;
|
||||
|
||||
select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog');
|
||||
select bag_eq(
|
||||
'payment_attachment_data',
|
||||
$$ values (14, 'payment.html')
|
||||
$$,
|
||||
'Should only list payment attachmements of the companies where admin@tandem.blog is user of'
|
||||
);
|
||||
reset role;
|
||||
|
||||
select set_cookie('not-a-cookie');
|
||||
select throws_ok(
|
||||
'payment_attachment_data',
|
||||
'42501', 'permission denied for table payment_attachment',
|
||||
'Should not allow select to guest users'
|
||||
);
|
||||
reset role;
|
||||
|
||||
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
||||
|
|
@ -5,7 +5,7 @@ reset client_min_messages;
|
|||
|
||||
begin;
|
||||
|
||||
select plan(15);
|
||||
select plan(16);
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
|
@ -22,7 +22,8 @@ select function_privs_are('numerus', 'remove_payment', array ['uuid'], 'authenti
|
|||
|
||||
|
||||
set client_min_messages to warning;
|
||||
truncate expense_payment cascade;
|
||||
truncate expense_payment;
|
||||
truncate payment_attachment;
|
||||
truncate payment cascade;
|
||||
truncate expense cascade;
|
||||
truncate contact cascade;
|
||||
|
@ -74,6 +75,12 @@ values (13, 16)
|
|||
, (15, 19)
|
||||
;
|
||||
|
||||
insert into payment_attachment (payment_id, original_fileName, mime_type, content)
|
||||
values (16, 'payment.txt', 'text/plain', convert_to('Pay 42', 'UTF-8'))
|
||||
, (18, 'empty.html', 'text/html', convert_to('empty', 'UTF-8'))
|
||||
, (19, 'payment.html', 'text/html', convert_to('<html> PAY <em>42</em></html>', 'UTF-8'))
|
||||
;
|
||||
|
||||
select lives_ok(
|
||||
$$ select remove_payment('7ac3ae0e-b0c1-4206-a19b-0be20835edd4') $$,
|
||||
'Should be able to remove a complete payment'
|
||||
|
@ -103,6 +110,12 @@ select bag_eq(
|
|||
'Should have deleted all related expenses’ payments'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select payment_id, original_filename from payment_attachment $$,
|
||||
$$ values (18, 'empty.html') $$,
|
||||
'Should have deleted all related attachments'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select expense_id, expense_status from expense $$,
|
||||
$$ values (13, 'pending')
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
-- Verify numerus:attach_to_payment on pg
|
||||
|
||||
begin;
|
||||
|
||||
select has_function_privilege('numerus.attach_to_payment(uuid, text, text, bytea)', 'execute');
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,15 @@
|
|||
-- Verify numerus:payment_attachment on pg
|
||||
|
||||
begin;
|
||||
|
||||
select payment_id
|
||||
, original_filename
|
||||
, mime_type
|
||||
, content
|
||||
from numerus.payment_attachment
|
||||
where false;
|
||||
|
||||
select 1 / count(*) from pg_class where oid = 'numerus.payment_attachment'::regclass and relrowsecurity;
|
||||
select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.payment_attachment'::regclass;
|
||||
|
||||
rollback;
|
|
@ -19,6 +19,7 @@
|
|||
<section data-hx-target="main">
|
||||
<h2>{{ template "title" . }}</h2>
|
||||
<form method="POST" action="{{ companyURI "/payments/" }}{{ .Slug }}"
|
||||
enctype="multipart/form-data"
|
||||
data-hx-swap="innerHTML show:false"
|
||||
data-hx-boost="true"
|
||||
>
|
||||
|
@ -30,6 +31,7 @@
|
|||
{{ template "input-field" .PaymentDate }}
|
||||
{{ template "input-field" .Amount }}
|
||||
{{ template "tags-field" .Tags }}
|
||||
{{ template "file-field" .File }}
|
||||
|
||||
<footer>
|
||||
<button class="primary" type="submit">{{( pgettext "Update" "action" )}}</button>
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
<th>{{( pgettext "Status" "title" )}}</th>
|
||||
<th>{{( pgettext "Tags" "title" )}}</th>
|
||||
<th class="numeric">{{( pgettext "Total" "title" )}}</th>
|
||||
<th>{{( pgettext "Download" "title" )}}</th>
|
||||
<th>{{( pgettext "Actions" "title" )}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -49,6 +50,14 @@
|
|||
{{- end }}
|
||||
</td>
|
||||
<td class="numeric">{{ .Total | formatPrice }}</td>
|
||||
<td class="invoice-download">
|
||||
{{ if .OriginalFileName }}
|
||||
<a href="{{ companyURI "/payments/"}}{{ .Slug }}/download/{{.OriginalFileName}}"
|
||||
title="{{( pgettext "Download payment attachment" "action" )}}"
|
||||
aria-label="{{( pgettext "Download payment attachment" "action" )}}"><i
|
||||
class="ri-download-line"></i></a>
|
||||
{{ end }}
|
||||
</td>
|
||||
<td class="actions">
|
||||
<details class="menu">
|
||||
{{- $label := .Description | printf (gettext "Actions for payment %s") -}}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<section data-hx-target="main">
|
||||
<h2>{{ template "title" . }}</h2>
|
||||
<form method="POST" action="{{ companyURI "/payments" }}"
|
||||
enctype="multipart/form-data"
|
||||
data-hx-swap="innerHTML show:false"
|
||||
data-hx-boost="true">
|
||||
{{ csrfToken }}
|
||||
|
@ -28,6 +29,7 @@
|
|||
{{ template "input-field" .PaymentDate }}
|
||||
{{ template "input-field" .Amount }}
|
||||
{{ template "tags-field" .Tags }}
|
||||
{{ template "file-field" .File }}
|
||||
|
||||
<footer>
|
||||
<button class="primary" type="submit">{{( pgettext "Save" "action" )}}</button>
|
||||
|
|
Loading…
Reference in New Issue