From 1bb6870f264902c82989798126347b849b091276 Mon Sep 17 00:00:00 2001 From: jordi fita mas Date: Fri, 7 Jul 2023 10:39:22 +0200 Subject: [PATCH] Keep products in invoices/quotes sorted by (roughly) insertion order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There was no explicit `order by` in the queries that list the products of quotes and invoices, so PostgreSQL was free to use any order it wanted. In this case, since was am grouping first by name, the result was sorted by product name. This is not an issue in most cases, albeit a bit rude to the user, except for when the products *have* to in the same order the user entered them, because they are monthly fees or something like that, that must be ordered by month _number_, not by their _name_; the user will usually input them in the correct order they want them on the invoice or quote. Sorting by *_product_id does *not* guarantee that they will always be in insertion order, because the sequence can “wrap”, but i think i am going to have bigger problems at that point. Closes #63 --- pkg/invoices.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++--- pkg/quote.go | 63 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 121 insertions(+), 8 deletions(-) diff --git a/pkg/invoices.go b/pkg/invoices.go index a6120e2..d11ba96 100644 --- a/pkg/invoices.go +++ b/pkg/invoices.go @@ -416,13 +416,63 @@ func mustGetInvoice(ctx context.Context, conn *Conn, company *Company, slug stri &inv.Total)) { return nil } - if err := conn.QueryRow(ctx, "select business_name, vatin, phone, email, address, city, province, postal_code, legal_disclaimer from company where company_id = $1", company.Id).Scan(&inv.Invoicer.Name, &inv.Invoicer.VATIN, &inv.Invoicer.Phone, &inv.Invoicer.Email, &inv.Invoicer.Address, &inv.Invoicer.City, &inv.Invoicer.Province, &inv.Invoicer.PostalCode, &inv.LegalDisclaimer); err != nil { + if err := conn.QueryRow(ctx, ` + select business_name + , vatin + , phone + , email + , address + , city + , province + , postal_code + , legal_disclaimer + from company + where company_id = $1 + `, company.Id).Scan( + &inv.Invoicer.Name, + &inv.Invoicer.VATIN, + &inv.Invoicer.Phone, + &inv.Invoicer.Email, + &inv.Invoicer.Address, + &inv.Invoicer.City, + &inv.Invoicer.Province, + &inv.Invoicer.PostalCode, + &inv.LegalDisclaimer); err != nil { panic(err) } - if err := conn.QueryRow(ctx, "select array_agg(array[name, to_price(amount, $2)]) from invoice_tax_amount join tax using (tax_id) where invoice_id = $1", invoiceId, decimalDigits).Scan(&inv.Taxes); err != nil { + if err := conn.QueryRow(ctx, ` + select array_agg(array[name, to_price(amount, $2)]) + from invoice_tax_amount + join tax using (tax_id) + where invoice_id = $1 + `, invoiceId, decimalDigits).Scan(&inv.Taxes); err != nil { panic(err) } - rows := conn.MustQuery(ctx, "select invoice_product.name, description, to_price(price, $2), (discount_rate * 100)::integer, quantity, to_price(subtotal, $2), to_price(total, $2), array_agg(array[tax_class.name, (tax_rate * 100)::integer::text]) filter (where tax_rate is not null) from invoice_product join invoice_product_amount using (invoice_product_id) left join invoice_product_tax using (invoice_product_id) left join tax using (tax_id) left join tax_class using (tax_class_id) where invoice_id = $1 group by invoice_product.name, description, discount_rate, price, quantity, subtotal, total", invoiceId, decimalDigits) + rows := conn.MustQuery(ctx, ` + select invoice_product.name + , description + , to_price(price, $2) + , (discount_rate * 100)::integer + , quantity + , to_price(subtotal, $2) + , to_price(total, $2) + , array_agg(array[tax_class.name, (tax_rate * 100)::integer::text]) filter (where tax_rate is not null) + from invoice_product + join invoice_product_amount using (invoice_product_id) + left join invoice_product_tax using (invoice_product_id) + left join tax using (tax_id) + left join tax_class using (tax_class_id) + where invoice_id = $1 + group by invoice_product_id + , invoice_product.name + , description + , discount_rate + , price + , quantity + , subtotal + , total + order by invoice_product_id + `, invoiceId, decimalDigits) defer rows.Close() taxClasses := map[string]bool{} for rows.Next() { @@ -430,7 +480,15 @@ func mustGetInvoice(ctx context.Context, conn *Conn, company *Company, slug stri Taxes: make(map[string]int), } var taxes [][]string - if err := rows.Scan(&product.Name, &product.Description, &product.Price, &product.Discount, &product.Quantity, &product.Subtotal, &product.Total, &taxes); err != nil { + if err := rows.Scan( + &product.Name, + &product.Description, + &product.Price, + &product.Discount, + &product.Quantity, + &product.Subtotal, + &product.Total, + &taxes); err != nil { panic(err) } for _, tax := range taxes { diff --git a/pkg/quote.go b/pkg/quote.go index e7fd893..27354cb 100644 --- a/pkg/quote.go +++ b/pkg/quote.go @@ -410,13 +410,60 @@ func mustGetQuote(ctx context.Context, conn *Conn, company *Company, slug string &quo.Total)) { return nil } - if err := conn.QueryRow(ctx, "select business_name, vatin, phone, email, address, city, province, postal_code, legal_disclaimer from company where company_id = $1", company.Id).Scan(&quo.Quoter.Name, &quo.Quoter.VATIN, &quo.Quoter.Phone, &quo.Quoter.Email, &quo.Quoter.Address, &quo.Quoter.City, &quo.Quoter.Province, &quo.Quoter.PostalCode, &quo.LegalDisclaimer); err != nil { + if err := conn.QueryRow(ctx, ` + select business_name + , vatin, phone + , email + , address + , city + , province + , postal_code + , legal_disclaimer + from company + where company_id = $1 +`, company.Id).Scan( + &quo.Quoter.Name, + &quo.Quoter.VATIN, + &quo.Quoter.Phone, + &quo.Quoter.Email, + &quo.Quoter.Address, + &quo.Quoter.City, + &quo.Quoter.Province, + &quo.Quoter.PostalCode, + &quo.LegalDisclaimer); err != nil { panic(err) } - if err := conn.QueryRow(ctx, "select array_agg(array[name, to_price(amount, $2)]) from quote_tax_amount join tax using (tax_id) where quote_id = $1", quoteId, decimalDigits).Scan(&quo.Taxes); err != nil { + if err := conn.QueryRow(ctx, ` + select array_agg(array[name, to_price(amount, $2)]) from quote_tax_amount + join tax using (tax_id) + where quote_id = $1 + `, quoteId, decimalDigits).Scan(&quo.Taxes); err != nil { panic(err) } - rows := conn.MustQuery(ctx, "select quote_product.name, description, to_price(price, $2), (discount_rate * 100)::integer, quantity, to_price(subtotal, $2), to_price(total, $2), array_agg(array[tax_class.name, (tax_rate * 100)::integer::text]) filter (where tax_rate is not null) from quote_product join quote_product_amount using (quote_product_id) left join quote_product_tax using (quote_product_id) left join tax using (tax_id) left join tax_class using (tax_class_id) where quote_id = $1 group by quote_product.name, description, discount_rate, price, quantity, subtotal, total", quoteId, decimalDigits) + rows := conn.MustQuery(ctx, ` + select quote_product.name + , description + , to_price(price, $2) + , (discount_rate * 100)::integer + , quantity, to_price(subtotal, $2) + , to_price(total, $2) + , array_agg(array[tax_class.name, (tax_rate * 100)::integer::text]) filter (where tax_rate is not null) + from quote_product + join quote_product_amount using (quote_product_id) + left join quote_product_tax using (quote_product_id) + left join tax using (tax_id) + left join tax_class using (tax_class_id) + where quote_id = $1 + group by quote_product_id + , quote_product.name + , description + , discount_rate + , price + , quantity + , subtotal + , total + order by quote_product_id + `, quoteId, decimalDigits) defer rows.Close() taxClasses := map[string]bool{} for rows.Next() { @@ -424,7 +471,15 @@ func mustGetQuote(ctx context.Context, conn *Conn, company *Company, slug string Taxes: make(map[string]int), } var taxes [][]string - if err := rows.Scan(&product.Name, &product.Description, &product.Price, &product.Discount, &product.Quantity, &product.Subtotal, &product.Total, &taxes); err != nil { + if err := rows.Scan( + &product.Name, + &product.Description, + &product.Price, + &product.Discount, + &product.Quantity, + &product.Subtotal, + &product.Total, + &taxes); err != nil { panic(err) } for _, tax := range taxes {