Compare commits
No commits in common. "a30e015639cfd85f1381b3ff822aa83923a9558f" and "f95936c523e444474c475f90eecb436f8f492c78" have entirely different histories.
a30e015639
...
f95936c523
|
@ -4,7 +4,6 @@
|
|||
-- requires: expense
|
||||
-- requires: payment
|
||||
-- requires: expense_payment
|
||||
-- requires: expense_tax_amount
|
||||
-- requires: available_expense_status
|
||||
-- requires: available_payment_status
|
||||
|
||||
|
@ -15,16 +14,15 @@ set search_path to numerus, public;
|
|||
create or replace function update_expense_payment_status(pid integer, eid integer, amount_cents integer) returns void as
|
||||
$$
|
||||
update payment
|
||||
set payment_status = case when expense.amount + coalesce(tax.amount, 0) > amount_cents or exists (select 1 from expense_payment as ep where ep.expense_id = expense.expense_id and payment_id <> pid) then 'partial' else 'complete' end
|
||||
set payment_status = case when expense.amount > amount_cents or exists (select 1 from expense_payment as ep where ep.expense_id = expense.expense_id and payment_id <> pid) then 'partial' else 'complete' end
|
||||
from expense
|
||||
left join ( select expense_id, sum(amount) as amount from expense_tax_amount group by expense_id) as tax using (expense_id)
|
||||
where expense.expense_id = eid
|
||||
and payment_id = pid
|
||||
;
|
||||
|
||||
update expense
|
||||
set expense_status = case
|
||||
when paid_amount >= expense.amount + tax_amount then 'paid'
|
||||
when paid_amount >= expense.amount then 'paid'
|
||||
when paid_amount = 0 then 'pending'
|
||||
else 'partial' end
|
||||
from (
|
||||
|
@ -32,12 +30,7 @@ $$
|
|||
from expense_payment
|
||||
join payment using (payment_id)
|
||||
where expense_payment.expense_id = eid
|
||||
) as payment,
|
||||
(
|
||||
select coalesce (sum(amount), 0) as tax_amount
|
||||
from expense_tax_amount
|
||||
where expense_id = eid
|
||||
) as tax
|
||||
) as payment
|
||||
where expense.expense_id = eid
|
||||
;
|
||||
$$
|
||||
|
|
|
@ -19,10 +19,9 @@ const (
|
|||
|
||||
func servePaymentAccountIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
conn := getConn(r)
|
||||
company := mustGetCompany(r)
|
||||
locale := getLocale(r)
|
||||
|
||||
page := NewPaymentAccountIndexPage(r.Context(), conn, company, locale)
|
||||
page := NewPaymentAccountIndexPage(r.Context(), conn, locale)
|
||||
page.MustRender(w, r)
|
||||
}
|
||||
|
||||
|
@ -30,9 +29,9 @@ type PaymentAccountIndexPage struct {
|
|||
Accounts []*PaymentAccountEntry
|
||||
}
|
||||
|
||||
func NewPaymentAccountIndexPage(ctx context.Context, conn *Conn, company *Company, locale *Locale) *PaymentAccountIndexPage {
|
||||
func NewPaymentAccountIndexPage(ctx context.Context, conn *Conn, locale *Locale) *PaymentAccountIndexPage {
|
||||
return &PaymentAccountIndexPage{
|
||||
Accounts: mustCollectPaymentAccountEntries(ctx, conn, company, locale),
|
||||
Accounts: mustCollectPaymentAccountEntries(ctx, conn, locale),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,7 +50,7 @@ type PaymentAccountEntry struct {
|
|||
ExpirationDate string
|
||||
}
|
||||
|
||||
func mustCollectPaymentAccountEntries(ctx context.Context, conn *Conn, company *Company, locale *Locale) []*PaymentAccountEntry {
|
||||
func mustCollectPaymentAccountEntries(ctx context.Context, conn *Conn, locale *Locale) []*PaymentAccountEntry {
|
||||
rows := conn.MustQuery(ctx, `
|
||||
select payment_account_id
|
||||
, slug
|
||||
|
@ -66,9 +65,8 @@ func mustCollectPaymentAccountEntries(ctx context.Context, conn *Conn, company *
|
|||
left join payment_account_card using (payment_account_id, payment_account_type)
|
||||
join payment_account_type using (payment_account_type)
|
||||
left join payment_account_type_i18n as i18n on payment_account_type.payment_account_type = i18n.payment_account_type and i18n.lang_tag = $1
|
||||
where company_id = $2
|
||||
order by payment_account_id
|
||||
`, locale.Language.String(), company.Id)
|
||||
`, locale.Language.String())
|
||||
defer rows.Close()
|
||||
|
||||
var entries []*PaymentAccountEntry
|
||||
|
|
|
@ -491,11 +491,44 @@ func (form *contactForm) TaxDetails() *CustomerTaxDetails {
|
|||
}
|
||||
|
||||
func ServeEditContactTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
serveTagsEditForm(w, r, params, "/contacts/", "select tags from contact where slug = $1")
|
||||
conn := getConn(r)
|
||||
locale := getLocale(r)
|
||||
company := getCompany(r)
|
||||
slug := params[0].Value
|
||||
if !ValidUuid(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
form := newTagsForm(companyURI(company, "/contacts/"+slug+"/tags"), slug, locale)
|
||||
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from contact where slug = $1`, form.Slug).Scan(form.Tags)) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
mustRenderStandaloneTemplate(w, r, "tags/edit.gohtml", form)
|
||||
}
|
||||
|
||||
func HandleUpdateContactTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
handleUpdateTags(w, r, params, "/contacts/", "update contact set tags = $1 where slug = $2 returning slug")
|
||||
locale := getLocale(r)
|
||||
conn := getConn(r)
|
||||
company := getCompany(r)
|
||||
slug := params[0].Value
|
||||
if !ValidUuid(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
form := newTagsForm(companyURI(company, "/contacts/"+slug+"/tags/edit"), slug, 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 conn.MustGetText(r.Context(), "", "update contact set tags = $1 where slug = $2 returning slug", form.Tags, form.Slug) == "" {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
mustRenderStandaloneTemplate(w, r, "tags/view.gohtml", form)
|
||||
}
|
||||
|
||||
func ServeImportPage(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
|
|
|
@ -568,11 +568,45 @@ func (form *expenseFilterForm) BuildQuery(args []interface{}) (string, []interfa
|
|||
}
|
||||
|
||||
func ServeEditExpenseTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
serveTagsEditForm(w, r, params, "/expenses/", "select tags from expense where slug = $1")
|
||||
conn := getConn(r)
|
||||
locale := getLocale(r)
|
||||
company := getCompany(r)
|
||||
slug := params[0].Value
|
||||
if !ValidUuid(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
form := newTagsForm(companyURI(company, "/expenses/"+slug+"/tags"), slug, locale)
|
||||
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from expense where slug = $1`, form.Slug).Scan(form.Tags)) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
mustRenderStandaloneTemplate(w, r, "tags/edit.gohtml", form)
|
||||
}
|
||||
|
||||
func HandleUpdateExpenseTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
handleUpdateTags(w, r, params, "/expenses/", "update expense set tags = $1 where slug = $2 returning slug")
|
||||
locale := getLocale(r)
|
||||
conn := getConn(r)
|
||||
company := getCompany(r)
|
||||
slug := params[0].Value
|
||||
if !ValidUuid(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
form := newTagsForm(companyURI(company, "/expenses/"+slug+"/tags/edit"), slug, 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 conn.MustGetText(r.Context(), "", "update expense set tags = $1 where slug = $2 returning slug", form.Tags, form.Slug) == "" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
mustRenderStandaloneTemplate(w, r, "tags/view.gohtml", form)
|
||||
}
|
||||
|
||||
func ServeExpenseAttachment(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
|
|
|
@ -1447,12 +1447,70 @@ func handleInvoiceAction(w http.ResponseWriter, r *http.Request, action string,
|
|||
}
|
||||
}
|
||||
|
||||
type tagsForm struct {
|
||||
Action string
|
||||
Slug string
|
||||
Tags *TagsField
|
||||
}
|
||||
|
||||
func newTagsForm(uri string, slug string, locale *Locale) *tagsForm {
|
||||
return &tagsForm{
|
||||
Action: uri,
|
||||
Slug: slug,
|
||||
Tags: &TagsField{
|
||||
Name: "tags-" + slug,
|
||||
Label: pgettext("input", "Tags", locale),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (form *tagsForm) Parse(r *http.Request) error {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
form.Tags.FillValue(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ServeEditInvoiceTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
serveTagsEditForm(w, r, params, "/invoices/", "select tags from invoice where slug = $1")
|
||||
conn := getConn(r)
|
||||
locale := getLocale(r)
|
||||
company := getCompany(r)
|
||||
slug := params[0].Value
|
||||
if !ValidUuid(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
form := newTagsForm(companyURI(company, "/invoices/"+slug+"/tags"), slug, locale)
|
||||
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from invoice where slug = $1`, form.Slug).Scan(form.Tags)) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
mustRenderStandaloneTemplate(w, r, "tags/edit.gohtml", form)
|
||||
}
|
||||
|
||||
func HandleUpdateInvoiceTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
handleUpdateTags(w, r, params, "/invoices/", "update invoice set tags = $1 where slug = $2 returning slug")
|
||||
locale := getLocale(r)
|
||||
conn := getConn(r)
|
||||
company := getCompany(r)
|
||||
slug := params[0].Value
|
||||
if !ValidUuid(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
form := newTagsForm(companyURI(company, "/invoices/"+slug+"/tags/edit"), slug, 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 conn.MustGetText(r.Context(), "", "update invoice set tags = $1 where slug = $2 returning slug", form.Tags, form.Slug) == "" {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
mustRenderStandaloneTemplate(w, r, "tags/view.gohtml", form)
|
||||
}
|
||||
|
||||
func ServeInvoiceAttachment(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
|
|
|
@ -12,10 +12,9 @@ import (
|
|||
|
||||
func servePaymentIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
conn := getConn(r)
|
||||
company := mustGetCompany(r)
|
||||
locale := getLocale(r)
|
||||
|
||||
page := NewPaymentIndexPage(r.Context(), conn, company, locale)
|
||||
page := NewPaymentIndexPage(r.Context(), conn, locale)
|
||||
page.MustRender(w, r)
|
||||
}
|
||||
|
||||
|
@ -23,9 +22,9 @@ type PaymentIndexPage struct {
|
|||
Payments []*PaymentEntry
|
||||
}
|
||||
|
||||
func NewPaymentIndexPage(ctx context.Context, conn *Conn, company *Company, locale *Locale) *PaymentIndexPage {
|
||||
func NewPaymentIndexPage(ctx context.Context, conn *Conn, locale *Locale) *PaymentIndexPage {
|
||||
return &PaymentIndexPage{
|
||||
Payments: mustCollectPaymentEntries(ctx, conn, company, locale),
|
||||
Payments: mustCollectPaymentEntries(ctx, conn, locale),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,8 +37,6 @@ type PaymentEntry struct {
|
|||
Slug string
|
||||
PaymentDate time.Time
|
||||
Description string
|
||||
ExpenseSlug string
|
||||
InvoiceNumber string
|
||||
Total string
|
||||
OriginalFileName string
|
||||
Tags []string
|
||||
|
@ -47,7 +44,7 @@ type PaymentEntry struct {
|
|||
StatusLabel string
|
||||
}
|
||||
|
||||
func mustCollectPaymentEntries(ctx context.Context, conn *Conn, company *Company, locale *Locale) []*PaymentEntry {
|
||||
func mustCollectPaymentEntries(ctx context.Context, conn *Conn, locale *Locale) []*PaymentEntry {
|
||||
rows := conn.MustQuery(ctx, `
|
||||
select payment_id
|
||||
, payment.slug
|
||||
|
@ -58,23 +55,18 @@ func mustCollectPaymentEntries(ctx context.Context, conn *Conn, company *Company
|
|||
, payment.payment_status
|
||||
, psi18n.name
|
||||
, coalesce(attachment.original_filename, '')
|
||||
, coalesce(expense.slug::text, '')
|
||||
, coalesce(expense.invoice_number, '')
|
||||
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)
|
||||
left join expense_payment using (payment_id)
|
||||
left join expense using (expense_id)
|
||||
where payment.company_id = $2
|
||||
order by payment_date desc, total desc
|
||||
`, locale.Language, company.Id)
|
||||
`, locale.Language)
|
||||
defer rows.Close()
|
||||
|
||||
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, &entry.OriginalFileName, &entry.ExpenseSlug, &entry.InvoiceNumber); 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)
|
||||
|
@ -140,7 +132,7 @@ func newPaymentForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
|
|||
Name: "payment_account",
|
||||
Label: pgettext("input", "Account", locale),
|
||||
Required: true,
|
||||
Options: MustGetOptions(ctx, conn, "select payment_account_id::text, name from payment_account where company_id = $1 order by name", company.Id),
|
||||
Options: MustGetOptions(ctx, conn, "select payment_account_id::text, name from payment_account order by name"),
|
||||
},
|
||||
Amount: &InputField{
|
||||
Name: "amount",
|
||||
|
@ -314,11 +306,3 @@ func servePaymentAttachment(w http.ResponseWriter, r *http.Request, params httpr
|
|||
where slug = $1
|
||||
`)
|
||||
}
|
||||
|
||||
func servePaymentTagsEditForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
serveTagsEditForm(w, r, params, "/payments/", "select tags from payment where slug = $1")
|
||||
}
|
||||
|
||||
func handleUpdatePaymentTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
handleUpdateTags(w, r, params, "/payments/", "update payment set tags = $1 where slug = $2 returning slug")
|
||||
}
|
||||
|
|
|
@ -367,9 +367,42 @@ func HandleProductSearch(w http.ResponseWriter, r *http.Request, _ httprouter.Pa
|
|||
}
|
||||
|
||||
func ServeEditProductTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
serveTagsEditForm(w, r, params, "/products/", "select tags from product where slug = $1")
|
||||
conn := getConn(r)
|
||||
locale := getLocale(r)
|
||||
company := getCompany(r)
|
||||
slug := params[0].Value
|
||||
if !ValidUuid(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
form := newTagsForm(companyURI(company, "/products/"+slug+"/tags"), slug, locale)
|
||||
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from product where slug = $1`, form.Slug).Scan(form.Tags)) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
mustRenderStandaloneTemplate(w, r, "tags/edit.gohtml", form)
|
||||
}
|
||||
|
||||
func HandleUpdateProductTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
handleUpdateTags(w, r, params, "/products/", "update product set tags = $1 where slug = $2 returning slug")
|
||||
locale := getLocale(r)
|
||||
conn := getConn(r)
|
||||
company := getCompany(r)
|
||||
slug := params[0].Value
|
||||
if !ValidUuid(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
form := newTagsForm(companyURI(company, "/products/"+slug+"/tags/edit"), slug, 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 conn.MustGetText(r.Context(), "", "update product set tags = $1 where slug = $2 returning slug", form.Tags, form.Slug) == "" {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
mustRenderStandaloneTemplate(w, r, "tags/view.gohtml", form)
|
||||
}
|
||||
|
|
37
pkg/quote.go
37
pkg/quote.go
|
@ -1187,9 +1187,42 @@ func handleQuoteAction(w http.ResponseWriter, r *http.Request, action string, re
|
|||
}
|
||||
|
||||
func ServeEditQuoteTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
serveTagsEditForm(w, r, params, "/quotes/", "select tags from quote where slug = $1")
|
||||
conn := getConn(r)
|
||||
locale := getLocale(r)
|
||||
company := getCompany(r)
|
||||
slug := params[0].Value
|
||||
if !ValidUuid(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
form := newTagsForm(companyURI(company, "/quotes/"+slug+"/tags"), slug, locale)
|
||||
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from quote where slug = $1`, form.Slug).Scan(form.Tags)) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
mustRenderStandaloneTemplate(w, r, "tags/edit.gohtml", form)
|
||||
}
|
||||
|
||||
func HandleUpdateQuoteTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
handleUpdateTags(w, r, params, "/quotes/", "update quote set tags = $1 where slug = $2 returning slug")
|
||||
locale := getLocale(r)
|
||||
conn := getConn(r)
|
||||
company := getCompany(r)
|
||||
slug := params[0].Value
|
||||
if !ValidUuid(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
form := newTagsForm(companyURI(company, "/quotes/"+slug+"/tags/edit"), slug, 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 conn.MustGetText(r.Context(), "", "update quote set tags = $1 where slug = $2 returning slug", form.Tags, form.Slug) == "" {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
mustRenderStandaloneTemplate(w, r, "tags/view.gohtml", form)
|
||||
}
|
||||
|
|
|
@ -67,8 +67,6 @@ func NewRouter(db *Db, demo bool) http.Handler {
|
|||
companyRouter.GET("/payments/:slug", servePaymentForm)
|
||||
companyRouter.PUT("/payments/:slug", handleEditPayment)
|
||||
companyRouter.DELETE("/payments/:slug", handleRemovePayment)
|
||||
companyRouter.PUT("/payments/:slug/tags", handleUpdatePaymentTags)
|
||||
companyRouter.GET("/payments/:slug/tags/edit", servePaymentTagsEditForm)
|
||||
companyRouter.GET("/payments/:slug/download/:filename", servePaymentAttachment)
|
||||
companyRouter.GET("/payment-accounts", servePaymentAccountIndex)
|
||||
companyRouter.POST("/payment-accounts", handleAddPaymentAccount)
|
||||
|
|
73
pkg/tags.go
73
pkg/tags.go
|
@ -1,73 +0,0 @@
|
|||
package pkg
|
||||
|
||||
import (
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func serveTagsEditForm(w http.ResponseWriter, r *http.Request, params httprouter.Params, prefix string, sql string) {
|
||||
conn := getConn(r)
|
||||
locale := getLocale(r)
|
||||
company := getCompany(r)
|
||||
slug := params[0].Value
|
||||
if !ValidUuid(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
form := newTagsForm(companyURI(company, prefix+slug+"/tags"), slug, locale)
|
||||
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), sql, form.Slug).Scan(form.Tags)) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
mustRenderStandaloneTemplate(w, r, "tags/edit.gohtml", form)
|
||||
}
|
||||
|
||||
type tagsForm struct {
|
||||
Action string
|
||||
Slug string
|
||||
Tags *TagsField
|
||||
}
|
||||
|
||||
func newTagsForm(uri string, slug string, locale *Locale) *tagsForm {
|
||||
return &tagsForm{
|
||||
Action: uri,
|
||||
Slug: slug,
|
||||
Tags: &TagsField{
|
||||
Name: "tags-" + slug,
|
||||
Label: pgettext("input", "Tags", locale),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (form *tagsForm) Parse(r *http.Request) error {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
form.Tags.FillValue(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleUpdateTags(w http.ResponseWriter, r *http.Request, params httprouter.Params, prefix string, sql string) {
|
||||
locale := getLocale(r)
|
||||
conn := getConn(r)
|
||||
company := getCompany(r)
|
||||
slug := params[0].Value
|
||||
if !ValidUuid(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
form := newTagsForm(companyURI(company, prefix+slug+"/tags/edit"), slug, 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 conn.MustGetText(r.Context(), "", sql, form.Tags, form.Slug) == "" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
mustRenderStandaloneTemplate(w, r, "tags/view.gohtml", form)
|
||||
}
|
79
po/ca.po
79
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-15 03:58+0200\n"
|
||||
"POT-Creation-Date: 2024-08-14 04:02+0200\n"
|
||||
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||
|
@ -116,7 +116,7 @@ msgstr "Subtotal"
|
|||
#: web/template/quotes/new.gohtml:74 web/template/quotes/view.gohtml:82
|
||||
#: web/template/quotes/view.gohtml:122 web/template/quotes/edit.gohtml:75
|
||||
#: web/template/expenses/new.gohtml:46 web/template/expenses/index.gohtml:74
|
||||
#: web/template/expenses/edit.gohtml:48 web/template/payments/index.gohtml:30
|
||||
#: web/template/expenses/edit.gohtml:48 web/template/payments/index.gohtml:29
|
||||
msgctxt "title"
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
@ -193,14 +193,14 @@ msgid "Customer"
|
|||
msgstr "Client"
|
||||
|
||||
#: web/template/invoices/index.gohtml:70 web/template/quotes/index.gohtml:70
|
||||
#: web/template/expenses/index.gohtml:68 web/template/payments/index.gohtml:28
|
||||
#: web/template/expenses/index.gohtml:68 web/template/payments/index.gohtml:27
|
||||
msgctxt "title"
|
||||
msgid "Status"
|
||||
msgstr "Estat"
|
||||
|
||||
#: web/template/invoices/index.gohtml:71 web/template/quotes/index.gohtml:71
|
||||
#: web/template/contacts/index.gohtml:50 web/template/expenses/index.gohtml:69
|
||||
#: web/template/products/index.gohtml:46 web/template/payments/index.gohtml:29
|
||||
#: web/template/products/index.gohtml:46 web/template/payments/index.gohtml:28
|
||||
msgctxt "title"
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetes"
|
||||
|
@ -212,7 +212,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/payments/index.gohtml:31
|
||||
#: web/template/expenses/index.gohtml:75 web/template/payments/index.gohtml:30
|
||||
msgctxt "title"
|
||||
msgid "Download"
|
||||
msgstr "Descàrrega"
|
||||
|
@ -220,7 +220,7 @@ msgstr "Descàrrega"
|
|||
#: web/template/invoices/index.gohtml:74 web/template/quotes/index.gohtml:74
|
||||
#: web/template/contacts/index.gohtml:51 web/template/expenses/index.gohtml:76
|
||||
#: web/template/company/switch.gohtml:23 web/template/products/index.gohtml:48
|
||||
#: web/template/payments/index.gohtml:32
|
||||
#: web/template/payments/index.gohtml:31
|
||||
msgctxt "title"
|
||||
msgid "Actions"
|
||||
msgstr "Accions"
|
||||
|
@ -242,7 +242,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:121
|
||||
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:77
|
||||
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:71
|
||||
msgctxt "action"
|
||||
msgid "Edit"
|
||||
msgstr "Edita"
|
||||
|
@ -794,25 +794,20 @@ msgctxt "title"
|
|||
msgid "Description"
|
||||
msgstr "Descripció"
|
||||
|
||||
#: web/template/payments/index.gohtml:27
|
||||
msgctxt "title"
|
||||
msgid "Document"
|
||||
msgstr "Document"
|
||||
|
||||
#: web/template/payments/index.gohtml:37
|
||||
#: 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:69
|
||||
#: web/template/payments/index.gohtml:63
|
||||
msgid "Actions for payment %s"
|
||||
msgstr "Accions pel pagament %s"
|
||||
|
||||
#: web/template/payments/index.gohtml:88
|
||||
#: web/template/payments/index.gohtml:82
|
||||
msgctxt "action"
|
||||
msgid "Remove"
|
||||
msgstr "Esborra"
|
||||
|
||||
#: web/template/payments/index.gohtml:98
|
||||
#: web/template/payments/index.gohtml:92
|
||||
msgid "No payments added yet."
|
||||
msgstr "No hi ha cap pagament."
|
||||
|
||||
|
@ -887,14 +882,14 @@ msgstr "No podeu deixar la contrasenya en blanc."
|
|||
msgid "Invalid user or password."
|
||||
msgstr "Nom d’usuari o contrasenya incorrectes."
|
||||
|
||||
#: pkg/products.go:172 pkg/products.go:276 pkg/quote.go:901 pkg/accounts.go:140
|
||||
#: pkg/products.go:172 pkg/products.go:276 pkg/quote.go:901 pkg/accounts.go:138
|
||||
#: pkg/invoices.go:1147 pkg/contacts.go:149 pkg/contacts.go:262
|
||||
msgctxt "input"
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: pkg/products.go:177 pkg/products.go:303 pkg/quote.go:174 pkg/quote.go:708
|
||||
#: pkg/payments.go:162 pkg/expenses.go:335 pkg/expenses.go:485
|
||||
#: pkg/payments.go:154 pkg/expenses.go:335 pkg/expenses.go:485
|
||||
#: pkg/invoices.go:177 pkg/invoices.go:877 pkg/invoices.go:1462
|
||||
#: pkg/contacts.go:154 pkg/contacts.go:362
|
||||
msgctxt "input"
|
||||
|
@ -929,7 +924,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:129
|
||||
#: pkg/products.go:282 pkg/quote.go:915 pkg/payments.go:121
|
||||
#: pkg/invoices.go:1161
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
|
@ -1344,103 +1339,103 @@ 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:135 pkg/expenses.go:304 pkg/invoices.go:866
|
||||
#: pkg/payments.go:127 pkg/expenses.go:304 pkg/invoices.go:866
|
||||
msgctxt "input"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Data de factura"
|
||||
|
||||
#: pkg/payments.go:141
|
||||
#: pkg/payments.go:133
|
||||
msgctxt "input"
|
||||
msgid "Account"
|
||||
msgstr "Compte"
|
||||
|
||||
#: pkg/payments.go:147 pkg/expenses.go:319
|
||||
#: pkg/payments.go:139 pkg/expenses.go:319
|
||||
msgctxt "input"
|
||||
msgid "Amount"
|
||||
msgstr "Import"
|
||||
|
||||
#: pkg/payments.go:157 pkg/expenses.go:330 pkg/invoices.go:888
|
||||
#: pkg/payments.go:149 pkg/expenses.go:330 pkg/invoices.go:888
|
||||
msgctxt "input"
|
||||
msgid "File"
|
||||
msgstr "Fitxer"
|
||||
|
||||
#: pkg/payments.go:169
|
||||
#: pkg/payments.go:161
|
||||
msgid "Select an account."
|
||||
msgstr "Escolliu un compte."
|
||||
|
||||
#: pkg/payments.go:218
|
||||
#: pkg/payments.go:210
|
||||
msgid "Description can not be empty."
|
||||
msgstr "No podeu deixar la descripció en blanc."
|
||||
|
||||
#: pkg/payments.go:219
|
||||
#: 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:220
|
||||
#: 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:221 pkg/expenses.go:372
|
||||
#: pkg/payments.go:213 pkg/expenses.go:372
|
||||
msgid "Amount can not be empty."
|
||||
msgstr "No podeu deixar l’import en blanc."
|
||||
|
||||
#: pkg/payments.go:222 pkg/expenses.go:373
|
||||
#: pkg/payments.go:214 pkg/expenses.go:373
|
||||
msgid "Amount must be a number greater than zero."
|
||||
msgstr "L’import ha de ser un número major a zero."
|
||||
|
||||
#: pkg/accounts.go:131
|
||||
#: pkg/accounts.go:129
|
||||
msgctxt "input"
|
||||
msgid "Type"
|
||||
msgstr "Tipus"
|
||||
|
||||
#: pkg/accounts.go:146 pkg/contacts.go:352
|
||||
#: pkg/accounts.go:144 pkg/contacts.go:352
|
||||
msgctxt "input"
|
||||
msgid "IBAN"
|
||||
msgstr "IBAN"
|
||||
|
||||
#: pkg/accounts.go:152
|
||||
#: pkg/accounts.go:150
|
||||
msgctxt "input"
|
||||
msgid "Card’s last four digits"
|
||||
msgstr "Els quatre darrers dígits de la targeta"
|
||||
|
||||
#: pkg/accounts.go:163
|
||||
#: pkg/accounts.go:161
|
||||
msgctxt "input"
|
||||
msgid "Expiration date"
|
||||
msgstr "Data de caducitat"
|
||||
|
||||
#: pkg/accounts.go:229
|
||||
#: pkg/accounts.go:227
|
||||
msgid "Selected payment account type is not valid."
|
||||
msgstr "Heu seleccionat un tipus de compte de pagament que no és vàlid."
|
||||
|
||||
#: pkg/accounts.go:232
|
||||
#: pkg/accounts.go:230
|
||||
msgid "IBAN can not be empty."
|
||||
msgstr "No podeu deixar l’IBAN en blanc."
|
||||
|
||||
#: pkg/accounts.go:233
|
||||
#: pkg/accounts.go:231
|
||||
msgid "This value is not a valid IBAN."
|
||||
msgstr "Aquest valor no és un IBAN vàlid."
|
||||
|
||||
#: pkg/accounts.go:236
|
||||
#: pkg/accounts.go:234
|
||||
msgid "Last four digits can not be empty."
|
||||
msgstr "No podeu deixar el quatre darrers dígits en blanc."
|
||||
|
||||
#: pkg/accounts.go:237
|
||||
#: pkg/accounts.go:235
|
||||
msgid "You must enter the card’s last four digits"
|
||||
msgstr "Heu d’entrar els quatre darrers dígits de la targeta"
|
||||
|
||||
#: pkg/accounts.go:238
|
||||
#: pkg/accounts.go:236
|
||||
msgid "Last four digits must be a number."
|
||||
msgstr "El quatre darrera dígits han de ser un número."
|
||||
|
||||
#: pkg/accounts.go:241
|
||||
#: pkg/accounts.go:239
|
||||
msgid "Expiration date can not be empty."
|
||||
msgstr "No podeu deixar la data de pagament en blanc."
|
||||
|
||||
#: pkg/accounts.go:243
|
||||
#: pkg/accounts.go:241
|
||||
msgid "Expiration date should be a valid date in format MM/YY (e.g., 08/24)."
|
||||
msgstr "La data de caducitat has de ser vàlida i en format MM/AA (p. ex., 08/24)."
|
||||
|
||||
#: pkg/accounts.go:247
|
||||
#: pkg/accounts.go:245
|
||||
msgid "Payment account name can not be empty."
|
||||
msgstr "No podeu deixar el nom del compte de pagament en blanc."
|
||||
|
||||
|
|
79
po/es.po
79
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-15 03:58+0200\n"
|
||||
"POT-Creation-Date: 2024-08-14 04:02+0200\n"
|
||||
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||
|
@ -116,7 +116,7 @@ msgstr "Subtotal"
|
|||
#: web/template/quotes/new.gohtml:74 web/template/quotes/view.gohtml:82
|
||||
#: web/template/quotes/view.gohtml:122 web/template/quotes/edit.gohtml:75
|
||||
#: web/template/expenses/new.gohtml:46 web/template/expenses/index.gohtml:74
|
||||
#: web/template/expenses/edit.gohtml:48 web/template/payments/index.gohtml:30
|
||||
#: web/template/expenses/edit.gohtml:48 web/template/payments/index.gohtml:29
|
||||
msgctxt "title"
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
@ -193,14 +193,14 @@ msgid "Customer"
|
|||
msgstr "Cliente"
|
||||
|
||||
#: web/template/invoices/index.gohtml:70 web/template/quotes/index.gohtml:70
|
||||
#: web/template/expenses/index.gohtml:68 web/template/payments/index.gohtml:28
|
||||
#: web/template/expenses/index.gohtml:68 web/template/payments/index.gohtml:27
|
||||
msgctxt "title"
|
||||
msgid "Status"
|
||||
msgstr "Estado"
|
||||
|
||||
#: web/template/invoices/index.gohtml:71 web/template/quotes/index.gohtml:71
|
||||
#: web/template/contacts/index.gohtml:50 web/template/expenses/index.gohtml:69
|
||||
#: web/template/products/index.gohtml:46 web/template/payments/index.gohtml:29
|
||||
#: web/template/products/index.gohtml:46 web/template/payments/index.gohtml:28
|
||||
msgctxt "title"
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetes"
|
||||
|
@ -212,7 +212,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/payments/index.gohtml:31
|
||||
#: web/template/expenses/index.gohtml:75 web/template/payments/index.gohtml:30
|
||||
msgctxt "title"
|
||||
msgid "Download"
|
||||
msgstr "Descargar"
|
||||
|
@ -220,7 +220,7 @@ msgstr "Descargar"
|
|||
#: web/template/invoices/index.gohtml:74 web/template/quotes/index.gohtml:74
|
||||
#: web/template/contacts/index.gohtml:51 web/template/expenses/index.gohtml:76
|
||||
#: web/template/company/switch.gohtml:23 web/template/products/index.gohtml:48
|
||||
#: web/template/payments/index.gohtml:32
|
||||
#: web/template/payments/index.gohtml:31
|
||||
msgctxt "title"
|
||||
msgid "Actions"
|
||||
msgstr "Acciones"
|
||||
|
@ -242,7 +242,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:121
|
||||
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:77
|
||||
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:71
|
||||
msgctxt "action"
|
||||
msgid "Edit"
|
||||
msgstr "Editar"
|
||||
|
@ -794,25 +794,20 @@ msgctxt "title"
|
|||
msgid "Description"
|
||||
msgstr "Descripción"
|
||||
|
||||
#: web/template/payments/index.gohtml:27
|
||||
msgctxt "title"
|
||||
msgid "Document"
|
||||
msgstr "Documento"
|
||||
|
||||
#: web/template/payments/index.gohtml:37
|
||||
#: 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:69
|
||||
#: web/template/payments/index.gohtml:63
|
||||
msgid "Actions for payment %s"
|
||||
msgstr "Acciones para el pago %s"
|
||||
|
||||
#: web/template/payments/index.gohtml:88
|
||||
#: web/template/payments/index.gohtml:82
|
||||
msgctxt "action"
|
||||
msgid "Remove"
|
||||
msgstr "Borrar"
|
||||
|
||||
#: web/template/payments/index.gohtml:98
|
||||
#: web/template/payments/index.gohtml:92
|
||||
msgid "No payments added yet."
|
||||
msgstr "No hay pagos."
|
||||
|
||||
|
@ -887,14 +882,14 @@ msgstr "No podéis dejar la contraseña en blanco."
|
|||
msgid "Invalid user or password."
|
||||
msgstr "Nombre de usuario o contraseña inválido."
|
||||
|
||||
#: pkg/products.go:172 pkg/products.go:276 pkg/quote.go:901 pkg/accounts.go:140
|
||||
#: pkg/products.go:172 pkg/products.go:276 pkg/quote.go:901 pkg/accounts.go:138
|
||||
#: pkg/invoices.go:1147 pkg/contacts.go:149 pkg/contacts.go:262
|
||||
msgctxt "input"
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
#: pkg/products.go:177 pkg/products.go:303 pkg/quote.go:174 pkg/quote.go:708
|
||||
#: pkg/payments.go:162 pkg/expenses.go:335 pkg/expenses.go:485
|
||||
#: pkg/payments.go:154 pkg/expenses.go:335 pkg/expenses.go:485
|
||||
#: pkg/invoices.go:177 pkg/invoices.go:877 pkg/invoices.go:1462
|
||||
#: pkg/contacts.go:154 pkg/contacts.go:362
|
||||
msgctxt "input"
|
||||
|
@ -929,7 +924,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:129
|
||||
#: pkg/products.go:282 pkg/quote.go:915 pkg/payments.go:121
|
||||
#: pkg/invoices.go:1161
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
|
@ -1344,103 +1339,103 @@ 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:135 pkg/expenses.go:304 pkg/invoices.go:866
|
||||
#: pkg/payments.go:127 pkg/expenses.go:304 pkg/invoices.go:866
|
||||
msgctxt "input"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Fecha de factura"
|
||||
|
||||
#: pkg/payments.go:141
|
||||
#: pkg/payments.go:133
|
||||
msgctxt "input"
|
||||
msgid "Account"
|
||||
msgstr "Cuenta"
|
||||
|
||||
#: pkg/payments.go:147 pkg/expenses.go:319
|
||||
#: pkg/payments.go:139 pkg/expenses.go:319
|
||||
msgctxt "input"
|
||||
msgid "Amount"
|
||||
msgstr "Importe"
|
||||
|
||||
#: pkg/payments.go:157 pkg/expenses.go:330 pkg/invoices.go:888
|
||||
#: pkg/payments.go:149 pkg/expenses.go:330 pkg/invoices.go:888
|
||||
msgctxt "input"
|
||||
msgid "File"
|
||||
msgstr "Archivo"
|
||||
|
||||
#: pkg/payments.go:169
|
||||
#: pkg/payments.go:161
|
||||
msgid "Select an account."
|
||||
msgstr "Escoged una cuenta."
|
||||
|
||||
#: pkg/payments.go:218
|
||||
#: pkg/payments.go:210
|
||||
msgid "Description can not be empty."
|
||||
msgstr "No podéis dejar la descripción en blanco."
|
||||
|
||||
#: pkg/payments.go:219
|
||||
#: 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:220
|
||||
#: pkg/payments.go:212
|
||||
msgid "Payment date must be a valid date."
|
||||
msgstr "La fecha de pago debe ser válida."
|
||||
|
||||
#: pkg/payments.go:221 pkg/expenses.go:372
|
||||
#: pkg/payments.go:213 pkg/expenses.go:372
|
||||
msgid "Amount can not be empty."
|
||||
msgstr "No podéis dejar el importe en blanco."
|
||||
|
||||
#: pkg/payments.go:222 pkg/expenses.go:373
|
||||
#: pkg/payments.go:214 pkg/expenses.go:373
|
||||
msgid "Amount must be a number greater than zero."
|
||||
msgstr "El importe tiene que ser un número mayor a cero."
|
||||
|
||||
#: pkg/accounts.go:131
|
||||
#: pkg/accounts.go:129
|
||||
msgctxt "input"
|
||||
msgid "Type"
|
||||
msgstr "Tipo"
|
||||
|
||||
#: pkg/accounts.go:146 pkg/contacts.go:352
|
||||
#: pkg/accounts.go:144 pkg/contacts.go:352
|
||||
msgctxt "input"
|
||||
msgid "IBAN"
|
||||
msgstr "IBAN"
|
||||
|
||||
#: pkg/accounts.go:152
|
||||
#: pkg/accounts.go:150
|
||||
msgctxt "input"
|
||||
msgid "Card’s last four digits"
|
||||
msgstr "Últimos cuatro dígitos de la tarjeta"
|
||||
|
||||
#: pkg/accounts.go:163
|
||||
#: pkg/accounts.go:161
|
||||
msgctxt "input"
|
||||
msgid "Expiration date"
|
||||
msgstr "Fecha de caducidad"
|
||||
|
||||
#: pkg/accounts.go:229
|
||||
#: pkg/accounts.go:227
|
||||
msgid "Selected payment account type is not valid."
|
||||
msgstr "Habéis escogido un tipo de cuenta de pago que no es válido."
|
||||
|
||||
#: pkg/accounts.go:232
|
||||
#: pkg/accounts.go:230
|
||||
msgid "IBAN can not be empty."
|
||||
msgstr "No podéis dejar el IBAN en blanco."
|
||||
|
||||
#: pkg/accounts.go:233
|
||||
#: pkg/accounts.go:231
|
||||
msgid "This value is not a valid IBAN."
|
||||
msgstr "Este valor no es un IBAN válido."
|
||||
|
||||
#: pkg/accounts.go:236
|
||||
#: pkg/accounts.go:234
|
||||
msgid "Last four digits can not be empty."
|
||||
msgstr "No podéis dejar los cuatro últimos dígitos en blanco."
|
||||
|
||||
#: pkg/accounts.go:237
|
||||
#: pkg/accounts.go:235
|
||||
msgid "You must enter the card’s last four digits"
|
||||
msgstr "Debéis entrar los cuatro últimos dígitos de la tarjeta"
|
||||
|
||||
#: pkg/accounts.go:238
|
||||
#: pkg/accounts.go:236
|
||||
msgid "Last four digits must be a number."
|
||||
msgstr "Los cuatro últimos dígitos tienen que ser un número."
|
||||
|
||||
#: pkg/accounts.go:241
|
||||
#: pkg/accounts.go:239
|
||||
msgid "Expiration date can not be empty."
|
||||
msgstr "No podéis dejar la fecha de caducidad en blanco."
|
||||
|
||||
#: pkg/accounts.go:243
|
||||
#: pkg/accounts.go:241
|
||||
msgid "Expiration date should be a valid date in format MM/YY (e.g., 08/24)."
|
||||
msgstr "La fecha de caducidad tiene que ser válida y en formato MM/AA (p. ej., 08/24)."
|
||||
|
||||
#: pkg/accounts.go:247
|
||||
#: pkg/accounts.go:245
|
||||
msgid "Payment account name can not be empty."
|
||||
msgstr "No podéis dejar el nombre de la cuenta de pago en blanco."
|
||||
|
||||
|
|
|
@ -149,7 +149,7 @@ available_payment_status [schema_numerus payment_status payment_status_i18n] 202
|
|||
payment [roles schema_numerus company payment_account currency tag_name payment_status extension_pgcrypto] 2024-08-01T01:28:59Z jordi fita mas <jordi@tandem.blog> # Add relation for accounts payable
|
||||
expense_payment [roles schema_numerus expense payment] 2024-08-04T03:44:30Z jordi fita mas <jordi@tandem.blog> # Add relation of expense payments
|
||||
available_expense_status [available_expense_status@v2] 2024-08-04T05:24:08Z jordi fita mas <jordi@tandem.blog> # Add “partial” expense status
|
||||
update_expense_payment_status [roles schema_numerus expense payment expense_payment expense_tax_amount 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
|
||||
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
|
||||
|
|
|
@ -5,7 +5,7 @@ reset client_min_messages;
|
|||
|
||||
begin;
|
||||
|
||||
select plan(20);
|
||||
select plan(17);
|
||||
|
||||
set search_path to numerus, auth, public;
|
||||
|
||||
|
@ -25,11 +25,8 @@ set client_min_messages to warning;
|
|||
truncate expense_payment cascade;
|
||||
truncate payment cascade;
|
||||
truncate payment_account cascade;
|
||||
truncate expense_tax cascade;
|
||||
truncate expense cascade;
|
||||
truncate contact cascade;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate payment_method cascade;
|
||||
truncate company cascade;
|
||||
reset client_min_messages;
|
||||
|
@ -48,16 +45,6 @@ values (111, 1, 'cash', 'cash')
|
|||
|
||||
set constraints "company_default_payment_method_id_fkey" immediate;
|
||||
|
||||
insert into tax_class (tax_class_id, company_id, name)
|
||||
values (11, 1, 'tax')
|
||||
;
|
||||
|
||||
insert into tax (tax_id, company_id, tax_class_id, name, rate)
|
||||
values (2, 1, 11, 'IRPF -15 %', -0.15)
|
||||
, (3, 1, 11, 'IVA 4 %', 0.04)
|
||||
, (4, 1, 11, 'IVA 10 %', 0.10)
|
||||
;
|
||||
|
||||
insert into contact (contact_id, company_id, name)
|
||||
values ( 9, 1, 'Customer 1')
|
||||
, (10, 2, 'Customer 2')
|
||||
|
@ -68,16 +55,6 @@ values (12, 1, 'REF123', 9, '2011-01-11', 111, 'EUR')
|
|||
, (13, 2, 'INV001', 10, '2011-01-11', 111, 'USD')
|
||||
, (14, 2, 'INV002', 10, '2022-02-22', 222, 'USD')
|
||||
, (15, 2, 'INV003', 10, '2022-02-22', 222, 'USD')
|
||||
, (16, 1, 'REF001', 9, '2023-03-03', 10000, 'EUR')
|
||||
, (17, 1, 'REF002', 9, '2023-03-03', 10000, 'EUR')
|
||||
, (18, 1, 'REF003', 9, '2023-03-03', 10000, 'EUR')
|
||||
;
|
||||
|
||||
insert into expense_tax (expense_id, tax_id, tax_rate)
|
||||
values (16, 3, 0.04)
|
||||
, (17, 2, -0.15)
|
||||
, (18, 2, -0.15)
|
||||
, (18, 4, 0.10)
|
||||
;
|
||||
|
||||
insert into payment_account (payment_account_id, company_id, payment_account_type, name)
|
||||
|
@ -110,21 +87,6 @@ select lives_ok(
|
|||
'Should be able to insert a partial payment for the third expense'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_payment(1, 16, '2023-03-06', 11, 'Re: REF001', '103.99', array[]::tag_name[]) $$,
|
||||
'Should be able to pay an expense with taxes'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_payment(1, 17, '2023-03-06', 11, 'Re: REF002', '85', array[]::tag_name[]) $$,
|
||||
'Should be able to pay an expense with negative taxes'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_payment(1, 18, '2023-03-06', 11, 'Re: REF003', '95', array[]::tag_name[]) $$,
|
||||
'Should be able to pay an expense with multiple taxes'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select company_id, description, payment_date::text, payment_account_id, amount, currency_code, payment_status, tags::text, created_at from payment $$,
|
||||
$$ values (1, '“Protection”', '2023-05-02', 11, 1111, 'EUR', 'complete', '{tag1,tag2}', current_timestamp)
|
||||
|
@ -132,9 +94,6 @@ select bag_eq(
|
|||
, (2, 'First payment of INV002', '2023-05-04', 22, 100, 'USD', 'partial', '{}', current_timestamp)
|
||||
, (2, 'Second payment of INV002', '2023-05-05', 22, 122, 'USD', 'partial', '{}', current_timestamp)
|
||||
, (2, 'Partial payment of INV003', '2023-05-06', 22, 111, 'USD', 'partial', '{}', current_timestamp)
|
||||
, (1, 'Re: REF001', '2023-03-06', 11, 10399, 'EUR', 'partial', '{}', current_timestamp)
|
||||
, (1, 'Re: REF002', '2023-03-06', 11, 8500, 'EUR', 'complete', '{}', current_timestamp)
|
||||
, (1, 'Re: REF003', '2023-03-06', 11, 9500, 'EUR', 'complete', '{}', current_timestamp)
|
||||
$$,
|
||||
'Should have created all payments'
|
||||
);
|
||||
|
@ -145,9 +104,6 @@ select bag_eq(
|
|||
, (14, 'First payment of INV002')
|
||||
, (14, 'Second payment of INV002')
|
||||
, (15, 'Partial payment of INV003')
|
||||
, (16, 'Re: REF001')
|
||||
, (17, 'Re: REF002')
|
||||
, (18, 'Re: REF003')
|
||||
$$,
|
||||
'Should have linked all expenses to payments'
|
||||
);
|
||||
|
@ -158,9 +114,6 @@ select bag_eq(
|
|||
, (13, 'paid')
|
||||
, (14, 'paid')
|
||||
, (15, 'partial')
|
||||
, (16, 'partial')
|
||||
, (17, 'paid')
|
||||
, (18, 'paid')
|
||||
$$,
|
||||
'Should have updated the status of expenses'
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@ reset client_min_messages;
|
|||
|
||||
begin;
|
||||
|
||||
select plan(17);
|
||||
select plan(14);
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
|
@ -24,11 +24,8 @@ select function_privs_are('numerus', 'edit_payment', array ['uuid', 'date', 'int
|
|||
set client_min_messages to warning;
|
||||
truncate expense_payment cascade;
|
||||
truncate payment cascade;
|
||||
truncate expense_tax cascade;
|
||||
truncate expense cascade;
|
||||
truncate contact cascade;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate payment_account cascade;
|
||||
truncate payment_method cascade;
|
||||
truncate company cascade;
|
||||
|
@ -47,16 +44,6 @@ values (111, 1, 'cash', 'cash')
|
|||
|
||||
set constraints "company_default_payment_method_id_fkey" immediate;
|
||||
|
||||
insert into tax_class (tax_class_id, company_id, name)
|
||||
values (11, 1, 'tax')
|
||||
;
|
||||
|
||||
insert into tax (tax_id, company_id, tax_class_id, name, rate)
|
||||
values (2, 1, 11, 'IRPF -15 %', -0.15)
|
||||
, (3, 1, 11, 'IVA 4 %', 0.04)
|
||||
, (4, 1, 11, 'IVA 10 %', 0.10)
|
||||
;
|
||||
|
||||
insert into contact (contact_id, company_id, name)
|
||||
values ( 9, 1, 'Customer 1')
|
||||
;
|
||||
|
@ -65,16 +52,6 @@ insert into expense (expense_id, company_id, invoice_number, contact_id, invoice
|
|||
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')
|
||||
, (16, 1, 'REF001', 9, '2023-03-03', 10000, 'EUR', 'paid')
|
||||
, (17, 1, 'REF002', 9, '2023-03-03', 10000, 'EUR', 'paid')
|
||||
, (18, 1, 'REF003', 9, '2023-03-03', 10000, 'EUR', 'paid')
|
||||
;
|
||||
|
||||
insert into expense_tax (expense_id, tax_id, tax_rate)
|
||||
values (16, 3, 0.04)
|
||||
, (17, 2, -0.15)
|
||||
, (18, 2, -0.15)
|
||||
, (18, 4, 0.10)
|
||||
;
|
||||
|
||||
insert into payment_account (payment_account_id, company_id, payment_account_type, name)
|
||||
|
@ -88,9 +65,6 @@ values (16, 1, '7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'Payment INV001', '2023-0
|
|||
, (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', '{}')
|
||||
, (20, 1, '65222c3b-4faa-4be4-b39c-5bd170a943cf', 'Re: REF001', '2023-03-07', 11, 10400, 'EUR', 'complete', '{}')
|
||||
, (21, 1, 'dbb699cf-d1f4-40ff-96cb-8f29e238d51d', 'Re: REF002', '2023-03-07', 11, 8500, 'EUR', 'complete', '{}')
|
||||
, (22, 1, '0756a50f-2957-4661-abd2-e422a848af4e', 'Re: REF003', '2023-03-07', 11, 9500, 'EUR', 'complete', '{}')
|
||||
;
|
||||
|
||||
insert into expense_payment (expense_id, payment_id)
|
||||
|
@ -98,9 +72,6 @@ values (13, 16)
|
|||
, (14, 17)
|
||||
, (14, 18)
|
||||
, (15, 19)
|
||||
, (16, 20)
|
||||
, (17, 21)
|
||||
, (18, 22)
|
||||
;
|
||||
|
||||
select lives_ok(
|
||||
|
@ -118,30 +89,12 @@ select lives_ok(
|
|||
'Should be able to complete a previously partial payment'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select edit_payment('65222c3b-4faa-4be4-b39c-5bd170a943cf', '2023-03-10', 11, 'Re: REF001', '103.99', array[]::tag_name[]) $$,
|
||||
'Should be able to make partial a payment with tax.'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select edit_payment('dbb699cf-d1f4-40ff-96cb-8f29e238d51d', '2023-03-10', 11, 'Re: REF002', '84.99', array[]::tag_name[]) $$,
|
||||
'Should be able to make partial a payment with negative tax.'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select edit_payment('0756a50f-2957-4661-abd2-e422a848af4e', '2023-03-10', 11, 'Re: REF003', '94.99', array[]::tag_name[]) $$,
|
||||
'Should be able to make partial a payment with multiple taxe.'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select description, payment_date::text, payment_account_id, amount, payment_status, tags::text from payment $$,
|
||||
$$ values ('Partial INV001', '2023-05-06', 13, 100, 'partial', '{tag1}')
|
||||
, ('First INV002', '2023-05-07', 12, 50, 'partial', '{tag1,tag3}')
|
||||
, ('Second INV002', '2023-05-06', 13, 122, 'partial', '{tag1,tag3}')
|
||||
, ('Complete INV003', '2023-05-01', 11, 333, 'complete', '{}')
|
||||
, ('Re: REF001', '2023-03-10', 11, 10399, 'partial', '{}')
|
||||
, ('Re: REF002', '2023-03-10', 11, 8499, 'partial', '{}')
|
||||
, ('Re: REF003', '2023-03-10', 11, 9499, 'partial', '{}')
|
||||
$$,
|
||||
'Should have updated all payments'
|
||||
);
|
||||
|
@ -151,9 +104,6 @@ select bag_eq(
|
|||
$$ values (13, 'partial')
|
||||
, (14, 'partial')
|
||||
, (15, 'paid')
|
||||
, (16, 'partial')
|
||||
, (17, 'partial')
|
||||
, (18, 'partial')
|
||||
$$,
|
||||
'Should have updated expenses too'
|
||||
);
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
<tr>
|
||||
<th>{{( pgettext "Payment Date" "title" )}}</th>
|
||||
<th>{{( pgettext "Description" "title" )}}</th>
|
||||
<th>{{( pgettext "Document" "title" )}}</th>
|
||||
<th>{{( pgettext "Status" "title" )}}</th>
|
||||
<th>{{( pgettext "Tags" "title" )}}</th>
|
||||
<th class="numeric">{{( pgettext "Total" "title" )}}</th>
|
||||
|
@ -39,11 +38,6 @@
|
|||
<tr>
|
||||
<td>{{ .PaymentDate|formatDate }}</td>
|
||||
<td><a href="{{ companyURI "/payments/"}}{{ .Slug }}">{{ .Description }}</a></td>
|
||||
<td>
|
||||
{{- if .InvoiceNumber -}}
|
||||
<a href="{{ companyURI "/expenses/"}}{{ .ExpenseSlug }}">{{ .InvoiceNumber }}</a>
|
||||
{{- end -}}
|
||||
</td>
|
||||
<td class="payment-status-{{ .Status }}">{{ .StatusLabel }}</td>
|
||||
<td
|
||||
data-hx-get="{{companyURI "/payments/"}}{{ .Slug }}/tags/edit"
|
||||
|
@ -95,7 +89,7 @@
|
|||
{{- end }}
|
||||
{{ else }}
|
||||
<tr>
|
||||
<td colspan="8">{{( gettext "No payments added yet." )}}</td>
|
||||
<td colspan="5">{{( gettext "No payments added yet." )}}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
|
|
Loading…
Reference in New Issue