Keep products in invoices/quotes sorted by (roughly) insertion order
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.
This commit is contained in:
parent
58dd69773a
commit
e62cee7e82
|
@ -416,13 +416,63 @@ func mustGetInvoice(ctx context.Context, conn *Conn, company *Company, slug stri
|
||||||
&inv.Total)) {
|
&inv.Total)) {
|
||||||
return nil
|
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)
|
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)
|
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()
|
defer rows.Close()
|
||||||
taxClasses := map[string]bool{}
|
taxClasses := map[string]bool{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
|
@ -430,7 +480,15 @@ func mustGetInvoice(ctx context.Context, conn *Conn, company *Company, slug stri
|
||||||
Taxes: make(map[string]int),
|
Taxes: make(map[string]int),
|
||||||
}
|
}
|
||||||
var taxes [][]string
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
for _, tax := range taxes {
|
for _, tax := range taxes {
|
||||||
|
|
63
pkg/quote.go
63
pkg/quote.go
|
@ -410,13 +410,60 @@ func mustGetQuote(ctx context.Context, conn *Conn, company *Company, slug string
|
||||||
&quo.Total)) {
|
&quo.Total)) {
|
||||||
return nil
|
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)
|
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)
|
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()
|
defer rows.Close()
|
||||||
taxClasses := map[string]bool{}
|
taxClasses := map[string]bool{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
|
@ -424,7 +471,15 @@ func mustGetQuote(ctx context.Context, conn *Conn, company *Company, slug string
|
||||||
Taxes: make(map[string]int),
|
Taxes: make(map[string]int),
|
||||||
}
|
}
|
||||||
var taxes [][]string
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
for _, tax := range taxes {
|
for _, tax := range taxes {
|
||||||
|
|
Loading…
Reference in New Issue