Compare commits

..

2 Commits

Author SHA1 Message Date
jordi fita mas f1534e6cd2 Add the date of the last payment/collection to ODS export
Requested by Clara, that she wanted to know that date for internal
processes.  We agreed on adding only the most recent payment/collection
date, instead of adding all of them, for multiple payments/collections,
and she can know whether that date is for a partial or a complete
payment/collection with the status column.
2024-10-03 14:06:15 +02:00
jordi fita mas cfd7a0c701 Make <main> use all the available space in <body>
Otherwise, in a table with only two or three rows, since <main> does
not reach the bottom, opening a menu will trigger the scroll due to
overflow.
2024-10-03 13:58:59 +02:00
4 changed files with 77 additions and 12 deletions

View File

@ -663,9 +663,10 @@ func HandleBatchExpenseAction(w http.ResponseWriter, r *http.Request, _ httprout
} }
entries := mustCollectExpenseEntries(r.Context(), conn, locale, filters) entries := mustCollectExpenseEntries(r.Context(), conn, locale, filters)
vatin := mustCollectExpenseEntriesVATIN(r.Context(), conn, entries) vatin := mustCollectExpenseEntriesVATIN(r.Context(), conn, entries)
lastPaymentDate := mustCollectExpenseEntriesLastPaymentDate(r.Context(), conn, entries)
taxes := mustCollectExpenseEntriesTaxes(r.Context(), conn, entries) taxes := mustCollectExpenseEntriesTaxes(r.Context(), conn, entries)
taxColumns := mustCollectTaxColumns(r.Context(), conn, company) taxColumns := mustCollectTaxColumns(r.Context(), conn, company)
ods := mustWriteExpensesOds(entries, vatin, taxes, taxColumns, locale, company) ods := mustWriteExpensesOds(entries, vatin, lastPaymentDate, taxes, taxColumns, locale, company)
writeOdsResponse(w, ods, gettext("expenses.ods", locale)) writeOdsResponse(w, ods, gettext("expenses.ods", locale))
default: default:
http.Error(w, gettext("Invalid action", locale), http.StatusBadRequest) http.Error(w, gettext("Invalid action", locale), http.StatusBadRequest)
@ -700,6 +701,20 @@ func mustCollectExpenseEntriesVATIN(ctx context.Context, conn *Conn, entries []*
`) `)
} }
func mustCollectExpenseEntriesLastPaymentDate(ctx context.Context, conn *Conn, entries []*ExpenseEntry) map[int]time.Time {
ids := mustMakeIDArray(entries, func(entry *ExpenseEntry) int {
return entry.ID
})
return mustMakeDateMap(ctx, conn, ids, `
select expense_id
, max(payment_date)
from expense_payment
join payment using (payment_id)
where expense_id = any ($1)
group by expense_id
`)
}
func handleRemoveExpense(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func handleRemoveExpense(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
slug := params[0].Value slug := params[0].Value
if !ValidUuid(slug) { if !ValidUuid(slug) {

View File

@ -673,9 +673,10 @@ func HandleBatchInvoiceAction(w http.ResponseWriter, r *http.Request, _ httprout
} }
entries := mustCollectInvoiceEntries(r.Context(), conn, locale, filters) entries := mustCollectInvoiceEntries(r.Context(), conn, locale, filters)
vatin := mustCollectInvoiceEntriesVATIN(r.Context(), conn, entries) vatin := mustCollectInvoiceEntriesVATIN(r.Context(), conn, entries)
lastCollectionDate := mustCollectInvoiceEntriesLastCollectionDate(r.Context(), conn, entries)
taxes := mustCollectInvoiceEntriesTaxes(r.Context(), conn, entries) taxes := mustCollectInvoiceEntriesTaxes(r.Context(), conn, entries)
taxColumns := mustCollectTaxColumns(r.Context(), conn, company) taxColumns := mustCollectTaxColumns(r.Context(), conn, company)
ods := mustWriteInvoicesOds(entries, vatin, taxes, taxColumns, locale, company) ods := mustWriteInvoicesOds(entries, vatin, lastCollectionDate, taxes, taxColumns, locale, company)
writeOdsResponse(w, ods, gettext("invoices.ods", locale)) writeOdsResponse(w, ods, gettext("invoices.ods", locale))
default: default:
http.Error(w, gettext("Invalid action", locale), http.StatusBadRequest) http.Error(w, gettext("Invalid action", locale), http.StatusBadRequest)
@ -806,6 +807,43 @@ func mustMakeVATINMap(ctx context.Context, conn *Conn, ids *pgtype.Int4Array, sq
return vatin return vatin
} }
func mustCollectInvoiceEntriesLastCollectionDate(ctx context.Context, conn *Conn, entries []*InvoiceEntry) map[int]time.Time {
ids := mustMakeIDArray(entries, func(entry *InvoiceEntry) int {
return entry.ID
})
return mustMakeDateMap(ctx, conn, ids, `
select invoice_id
, max(collection_date)
from invoice_collection
join collection using (collection_id)
where invoice_id = any ($1)
group by invoice_id
`)
}
func mustMakeDateMap(ctx context.Context, conn *Conn, ids *pgtype.Int4Array, sql string) map[int]time.Time {
rows, err := conn.Query(ctx, sql, ids)
if err != nil {
panic(err)
}
defer rows.Close()
dates := make(map[int]time.Time)
for rows.Next() {
var entryID int
var date time.Time
if err := rows.Scan(&entryID, &date); err != nil {
panic(err)
}
dates[entryID] = date
}
if rows.Err() != nil {
panic(rows.Err())
}
return dates
}
func mustWriteInvoicesPdf(r *http.Request, slugs []string) []byte { func mustWriteInvoicesPdf(r *http.Request, slugs []string) []byte {
conn := getConn(r) conn := getConn(r)
company := mustGetCompany(r) company := mustGetCompany(r)

View File

@ -53,15 +53,16 @@ func extractTaxIDs(taxColumns map[int]string) []int {
return taxIDs return taxIDs
} }
func mustWriteInvoicesOds(invoices []*InvoiceEntry, vatin map[int]string, taxes map[int]taxMap, taxColumns map[int]string, locale *Locale, company *Company) []byte { func mustWriteInvoicesOds(invoices []*InvoiceEntry, vatin map[int]string, lastCollectionDate map[int]time.Time, taxes map[int]taxMap, taxColumns map[int]string, locale *Locale, company *Company) []byte {
taxIDs := extractTaxIDs(taxColumns) taxIDs := extractTaxIDs(taxColumns)
columns := make([]string, 7+len(taxIDs)) columns := make([]string, 8+len(taxIDs))
columns[0] = "Date" columns[0] = "Date"
columns[1] = "Invoice Num." columns[1] = "Invoice Num."
columns[2] = "Customer" columns[2] = "Customer"
columns[3] = pgettext("title", "VAT number", locale) columns[3] = pgettext("title", "VAT number", locale)
columns[4] = "Status" columns[4] = "Payment Date"
i := 5 columns[5] = "Status"
i := 6
for _, taxID := range taxIDs { for _, taxID := range taxIDs {
columns[i] = taxColumns[taxID] columns[i] = taxColumns[taxID]
i++ i++
@ -73,6 +74,11 @@ func mustWriteInvoicesOds(invoices []*InvoiceEntry, vatin map[int]string, taxes
writeCellString(sb, invoice.Number) writeCellString(sb, invoice.Number)
writeCellString(sb, invoice.CustomerName) writeCellString(sb, invoice.CustomerName)
writeCellString(sb, vatin[invoice.ID]) writeCellString(sb, vatin[invoice.ID])
if date, ok := lastCollectionDate[invoice.ID]; ok {
writeCellDate(sb, date)
} else {
writeCellString(sb, "")
}
writeCellString(sb, invoice.StatusLabel) writeCellString(sb, invoice.StatusLabel)
writeTaxes(sb, taxes[invoice.ID], taxIDs, locale, company) writeTaxes(sb, taxes[invoice.ID], taxIDs, locale, company)
writeCellFloat(sb, invoice.Total, locale, company) writeCellFloat(sb, invoice.Total, locale, company)
@ -99,16 +105,17 @@ func mustWriteQuotesOds(quotes []*QuoteEntry, locale *Locale, company *Company)
}) })
} }
func mustWriteExpensesOds(expenses []*ExpenseEntry, vatin map[int]string, taxes map[int]taxMap, taxColumns map[int]string, locale *Locale, company *Company) []byte { func mustWriteExpensesOds(expenses []*ExpenseEntry, vatin map[int]string, lastPaymentDate map[int]time.Time, taxes map[int]taxMap, taxColumns map[int]string, locale *Locale, company *Company) []byte {
taxIDs := extractTaxIDs(taxColumns) taxIDs := extractTaxIDs(taxColumns)
columns := make([]string, 8+len(taxIDs)) columns := make([]string, 9+len(taxIDs))
columns[0] = "Contact" columns[0] = "Contact"
columns[1] = pgettext("title", "VAT number", locale) columns[1] = pgettext("title", "VAT number", locale)
columns[2] = "Invoice Date" columns[2] = "Invoice Date"
columns[3] = "Invoice Number" columns[3] = "Invoice Number"
columns[4] = "Status" columns[4] = "Payment Date"
columns[5] = "Amount" columns[5] = "Status"
i := 6 columns[6] = "Amount"
i := 7
for _, taxID := range taxIDs { for _, taxID := range taxIDs {
columns[i] = taxColumns[taxID] columns[i] = taxColumns[taxID]
i++ i++
@ -116,11 +123,15 @@ func mustWriteExpensesOds(expenses []*ExpenseEntry, vatin map[int]string, taxes
columns[i] = "Total" columns[i] = "Total"
columns[i+1] = "Tags" columns[i+1] = "Tags"
return mustWriteTableOds(expenses, columns, locale, func(sb *strings.Builder, expense *ExpenseEntry) { return mustWriteTableOds(expenses, columns, locale, func(sb *strings.Builder, expense *ExpenseEntry) {
writeCellString(sb, expense.InvoicerName) writeCellString(sb, expense.InvoicerName)
writeCellString(sb, vatin[expense.ID]) writeCellString(sb, vatin[expense.ID])
writeCellDate(sb, expense.InvoiceDate) writeCellDate(sb, expense.InvoiceDate)
writeCellString(sb, expense.InvoiceNumber) writeCellString(sb, expense.InvoiceNumber)
if date, ok := lastPaymentDate[expense.ID]; ok {
writeCellDate(sb, date)
} else {
writeCellString(sb, "")
}
writeCellString(sb, expense.StatusLabel) writeCellString(sb, expense.StatusLabel)
writeCellFloat(sb, expense.Amount, locale, company) writeCellFloat(sb, expense.Amount, locale, company)
writeTaxes(sb, taxes[expense.ID], taxIDs, locale, company) writeTaxes(sb, taxes[expense.ID], taxIDs, locale, company)

View File

@ -314,6 +314,7 @@ header nav a[aria-current] {
main { main {
padding: 2rem 3rem; padding: 2rem 3rem;
overflow-y: scroll; overflow-y: scroll;
flex: 1;
} }
.input { .input {