Use billable time for Teamspace comparisons

This commit is contained in:
2026-08-05 17:06:34 +02:00
parent 76bc438b4e
commit 2aec561f3c
8 changed files with 169 additions and 130 deletions
+18 -12
View File
@@ -549,7 +549,9 @@ async function getPeriodOverview(config: PeriodConfig, period: ParsedPeriod, use
COUNT(s.id)::int AS session_count,
COUNT(*) FILTER (WHERE s.recurring_billing_id IS NOT NULL)::int AS recurring_session_count,
COUNT(*) FILTER (WHERE s.recurring_billing_id IS NULL)::int AS manual_session_count,
COALESCE(SUM(s.rounded_minutes), 0)::int AS total_minutes,
COALESCE(SUM(s.rounded_minutes) FILTER (WHERE s.billing_status = 'billed'), 0)::int AS total_minutes,
COALESCE(SUM(s.rounded_minutes) FILTER (WHERE s.billing_status = 'non_billable'), 0)::int AS non_billable_minutes,
COALESCE(SUM(s.rounded_minutes) FILTER (WHERE s.billing_status IS NULL), 0)::int AS open_minutes,
COUNT(*) FILTER (WHERE s.billing_status IS NULL)::int AS open_count,
COUNT(*) FILTER (WHERE s.billing_status = 'billed')::int AS billed_count,
COUNT(*) FILTER (WHERE s.billing_status = 'non_billable')::int AS non_billable_count,
@@ -604,8 +606,8 @@ async function getPeriodOverview(config: PeriodConfig, period: ParsedPeriod, use
`
SELECT
${bucketExpression} AS bucket_key,
COUNT(*)::int AS session_count,
COALESCE(SUM(s.rounded_minutes), 0)::int AS total_minutes
COUNT(*) FILTER (WHERE s.billing_status = 'billed')::int AS session_count,
COALESCE(SUM(s.rounded_minutes) FILTER (WHERE s.billing_status = 'billed'), 0)::int AS total_minutes
FROM sessions s
WHERE s.started_at >= $1::timestamptz
AND s.started_at < $2::timestamptz
@@ -628,6 +630,7 @@ async function getPeriodOverview(config: PeriodConfig, period: ParsedPeriod, use
WHERE s.ticket_id = tdb.ticket_id
AND s.user_id = tdb.user_id
AND (s.started_at AT TIME ZONE 'Europe/Berlin')::date = tdb.day
AND s.billing_status = 'billed'
);
`,
[userId, period.start, period.endIso]
@@ -651,6 +654,8 @@ async function getPeriodOverview(config: PeriodConfig, period: ParsedPeriod, use
sessions: totalSessions,
minutes: totalMinutes,
crmBilledMinutes: crmBillingResult.rows[0]?.crm_billed_minutes ?? 0,
nonBillableMinutes: tickets.reduce((sum: number, ticket: any) => sum + ticket.non_billable_minutes, 0),
openMinutes: tickets.reduce((sum: number, ticket: any) => sum + ticket.open_minutes, 0),
openSessions: openSessions.length
},
tickets,
@@ -690,9 +695,10 @@ async function getStatisticsOverview(period: ParsedPeriod, userId: string) {
ticket_id,
ticket_number,
day,
SUM(rounded_minutes)::int AS tracked_minutes
SUM(rounded_minutes) FILTER (WHERE billing_status = 'billed')::int AS tracked_minutes
FROM session_base
GROUP BY ticket_id, ticket_number, day
HAVING SUM(rounded_minutes) FILTER (WHERE billing_status = 'billed') > 0
),
ticket_day_billing AS (
SELECT
@@ -723,7 +729,7 @@ async function getStatisticsOverview(period: ParsedPeriod, userId: string) {
sb.ticket_number,
sb.day,
COUNT(*)::int AS session_count,
SUM(sb.rounded_minutes)::int AS total_minutes,
COALESCE(SUM(sb.rounded_minutes) FILTER (WHERE sb.billing_status = 'billed'), 0)::int AS total_minutes,
SUM(sb.rounded_minutes) FILTER (WHERE sb.billing_status = 'billed')::int AS billed_minutes,
SUM(sb.rounded_minutes) FILTER (WHERE sb.billing_status = 'non_billable')::int AS non_billable_minutes,
SUM(sb.rounded_minutes) FILTER (WHERE sb.billing_status IS NULL)::int AS open_minutes,
@@ -764,19 +770,19 @@ async function getStatisticsOverview(period: ParsedPeriod, userId: string) {
SELECT
COUNT(DISTINCT ticket_id)::int AS tickets,
COUNT(*)::int AS sessions,
COALESCE(SUM(rounded_minutes), 0)::int AS minutes,
COALESCE(SUM(rounded_minutes) FILTER (WHERE billing_status = 'billed'), 0)::int AS minutes,
COALESCE(SUM(rounded_minutes) FILTER (WHERE billing_status = 'billed'), 0)::int AS billed_minutes,
COALESCE(SUM(rounded_minutes) FILTER (WHERE billing_status = 'non_billable'), 0)::int AS non_billable_minutes,
COALESCE(SUM(rounded_minutes) FILTER (WHERE billing_status IS NULL), 0)::int AS open_minutes,
COUNT(*) FILTER (WHERE billing_status = 'billed')::int AS billed_sessions,
COUNT(*) FILTER (WHERE billing_status = 'non_billable')::int AS non_billable_sessions,
COUNT(*) FILTER (WHERE billing_status IS NULL)::int AS open_sessions,
COALESCE(SUM(rounded_minutes) FILTER (WHERE recurring_billing_id IS NOT NULL), 0)::int AS recurring_minutes,
COALESCE(SUM(rounded_minutes) FILTER (WHERE recurring_billing_id IS NULL), 0)::int AS manual_minutes,
COALESCE(SUM(rounded_minutes) FILTER (WHERE recurring_billing_id IS NOT NULL AND billing_status = 'billed'), 0)::int AS recurring_minutes,
COALESCE(SUM(rounded_minutes) FILTER (WHERE recurring_billing_id IS NULL AND billing_status = 'billed'), 0)::int AS manual_minutes,
COUNT(*) FILTER (WHERE recurring_billing_id IS NOT NULL)::int AS recurring_sessions,
COUNT(*) FILTER (WHERE recurring_billing_id IS NULL)::int AS manual_sessions,
COALESCE(ROUND(AVG(rounded_minutes)), 0)::int AS average_session_minutes,
COUNT(DISTINCT day)::int AS active_days,
COALESCE(ROUND(AVG(rounded_minutes) FILTER (WHERE billing_status = 'billed')), 0)::int AS average_session_minutes,
COUNT(DISTINCT day) FILTER (WHERE billing_status = 'billed')::int AS active_days,
COALESCE((SELECT SUM(crm_billed_minutes)::int FROM ticket_day_billing), 0)::int AS crm_billed_minutes
FROM session_base;
`,
@@ -871,9 +877,9 @@ async function getStatisticsOverview(period: ParsedPeriod, userId: string) {
ticket_number,
MIN(organization_id)::text AS organization_id,
MIN(organization_name) AS organization_name,
COUNT(DISTINCT day)::int AS active_days,
COUNT(DISTINCT day) FILTER (WHERE billing_status = 'billed')::int AS active_days,
COUNT(*)::int AS sessions,
COALESCE(SUM(rounded_minutes), 0)::int AS total_minutes,
COALESCE(SUM(rounded_minutes) FILTER (WHERE billing_status = 'billed'), 0)::int AS total_minutes,
COALESCE(SUM(rounded_minutes) FILTER (WHERE billing_status = 'billed'), 0)::int AS billed_minutes,
COALESCE(SUM(rounded_minutes) FILTER (WHERE billing_status = 'non_billable'), 0)::int AS non_billable_minutes,
COALESCE(SUM(rounded_minutes) FILTER (WHERE billing_status IS NULL), 0)::int AS open_minutes,