2023-05-16 12:56:49 +00:00
|
|
|
package pkg
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/julienschmidt/httprouter"
|
|
|
|
"net/http"
|
|
|
|
)
|
|
|
|
|
2023-05-17 10:05:30 +00:00
|
|
|
const (
|
|
|
|
YearPeriod = "year"
|
|
|
|
QuarterPeriod = "quarter"
|
|
|
|
MonthPeriod = "month"
|
|
|
|
YesteryearPeriod = "yesteryear"
|
|
|
|
)
|
|
|
|
|
2023-05-16 12:56:49 +00:00
|
|
|
type DashboardPage struct {
|
|
|
|
Sales string
|
|
|
|
Income string
|
|
|
|
Expenses string
|
|
|
|
VAT string
|
|
|
|
IRPF string
|
|
|
|
NetIncome string
|
2023-05-17 10:05:30 +00:00
|
|
|
Filters *dashboardFilterForm
|
2023-05-16 12:56:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func ServeDashboard(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
|
|
company := mustGetCompany(r)
|
2023-05-17 10:05:30 +00:00
|
|
|
locale := getLocale(r)
|
|
|
|
filters := newDashboardFilterForm(locale, company)
|
|
|
|
if err := filters.Parse(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
periodStart := "30 DAYS"
|
|
|
|
periodEnd := "0 DAYS"
|
|
|
|
switch filters.Period.Selected {
|
|
|
|
case YearPeriod:
|
|
|
|
periodStart = "1 YEAR"
|
|
|
|
case QuarterPeriod:
|
|
|
|
periodStart = "3 MONTHS"
|
|
|
|
case YesteryearPeriod:
|
|
|
|
periodStart = "2 YEARS"
|
|
|
|
periodEnd = "1 YEAR"
|
|
|
|
case "":
|
|
|
|
filters.Period.Selected = MonthPeriod
|
|
|
|
}
|
2023-05-16 12:56:49 +00:00
|
|
|
conn := getConn(r)
|
|
|
|
rows := conn.MustQuery(r.Context(), `
|
|
|
|
select to_price(0, decimal_digits) as sales
|
2023-05-16 13:14:20 +00:00
|
|
|
, to_price(coalesce(invoice.total, 0), decimal_digits) as income
|
|
|
|
, to_price(coalesce(expense.total, 0), decimal_digits) as expenses
|
|
|
|
, to_price(coalesce(tax.vat, 0), decimal_digits) as vat
|
|
|
|
, to_price(coalesce(tax.irpf, 0), decimal_digits) as irpf
|
2023-05-16 13:24:14 +00:00
|
|
|
, to_price(coalesce(invoice.total, 0) - coalesce(expense.total, 0) - coalesce(tax.vat, 0) + coalesce(tax.irpf, 0), decimal_digits) as net_income
|
2023-05-16 12:56:49 +00:00
|
|
|
from company
|
|
|
|
left join (
|
|
|
|
select company_id, sum(total)::integer as total
|
|
|
|
from invoice
|
|
|
|
join invoice_amount using (invoice_id)
|
2023-05-17 10:05:30 +00:00
|
|
|
where invoice_date between CURRENT_DATE - $2::interval and CURRENT_DATE - $3::interval
|
2023-05-16 12:56:49 +00:00
|
|
|
group by company_id
|
|
|
|
) as invoice using (company_id)
|
|
|
|
left join (
|
|
|
|
select company_id, sum(amount)::integer as total
|
|
|
|
from expense
|
2023-05-17 10:05:30 +00:00
|
|
|
where invoice_date between CURRENT_DATE - $2::interval and CURRENT_DATE - $3::interval
|
2023-05-16 12:56:49 +00:00
|
|
|
group by company_id
|
|
|
|
) as expense using (company_id)
|
|
|
|
left join (
|
|
|
|
select invoice.company_id
|
|
|
|
, sum(case when tax_class.name = 'IVA' then invoice_tax_amount.amount else 0 end)::integer as vat
|
|
|
|
, sum(case when tax_class.name = 'IRPF' then invoice_tax_amount.amount else 0 end)::integer as irpf
|
|
|
|
from invoice
|
|
|
|
join invoice_tax_amount using (invoice_id)
|
|
|
|
join tax using (tax_id)
|
|
|
|
join tax_class using (tax_class_id)
|
2023-05-17 10:05:30 +00:00
|
|
|
where invoice_date between CURRENT_DATE - $2::interval and CURRENT_DATE - $3::interval
|
2023-05-16 12:56:49 +00:00
|
|
|
group by invoice.company_id
|
|
|
|
) as tax using (company_id)
|
|
|
|
join currency using (currency_code)
|
|
|
|
where company_id = $1
|
2023-05-17 10:05:30 +00:00
|
|
|
`, company.Id, periodStart, periodEnd)
|
2023-05-16 12:56:49 +00:00
|
|
|
defer rows.Close()
|
|
|
|
|
2023-05-17 10:05:30 +00:00
|
|
|
dashboard := &DashboardPage{
|
|
|
|
Filters: filters,
|
|
|
|
}
|
2023-05-16 12:56:49 +00:00
|
|
|
for rows.Next() {
|
|
|
|
if err := rows.Scan(&dashboard.Sales, &dashboard.Income, &dashboard.Expenses, &dashboard.VAT, &dashboard.IRPF, &dashboard.NetIncome); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if rows.Err() != nil {
|
|
|
|
panic(rows.Err())
|
|
|
|
}
|
|
|
|
mustRenderMainTemplate(w, r, "dashboard.gohtml", dashboard)
|
|
|
|
}
|
2023-05-17 10:05:30 +00:00
|
|
|
|
|
|
|
type dashboardFilterForm struct {
|
|
|
|
locale *Locale
|
|
|
|
company *Company
|
|
|
|
Period *RadioField
|
|
|
|
}
|
|
|
|
|
|
|
|
func newDashboardFilterForm(locale *Locale, company *Company) *dashboardFilterForm {
|
|
|
|
return &dashboardFilterForm{
|
|
|
|
locale: locale,
|
|
|
|
company: company,
|
|
|
|
Period: &RadioField{
|
|
|
|
Name: "period",
|
|
|
|
Selected: MonthPeriod,
|
|
|
|
Label: pgettext("input", "Period", locale),
|
|
|
|
Options: []*RadioOption{
|
|
|
|
{
|
|
|
|
Label: pgettext("period option", "Year", locale),
|
|
|
|
Value: YearPeriod,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Label: pgettext("period option", "Quarter", locale),
|
|
|
|
Value: QuarterPeriod,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Label: pgettext("period option", "Month", locale),
|
|
|
|
Value: MonthPeriod,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Label: pgettext("period option", "Previous Year", locale),
|
|
|
|
Value: YesteryearPeriod,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (form *dashboardFilterForm) Parse(r *http.Request) error {
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
form.Period.FillValue(r)
|
|
|
|
return nil
|
|
|
|
}
|