diff --git a/libs/server/features/src/account/account-query.service.ts b/libs/server/features/src/account/account-query.service.ts index 08845d99..f12d5e75 100644 --- a/libs/server/features/src/account/account-query.service.ts +++ b/libs/server/features/src/account/account-query.service.ts @@ -117,32 +117,32 @@ export class AccountQueryService implements IAccountQueryService { } >( sql` - SELECT - h.id, - h.security_id, - s.name, - s.symbol, - s.shares_per_contract, - he.quantity, - he.value, - he.cost_basis, - h.cost_basis_user, - h.cost_basis_provider, - he.cost_basis_per_share, - he.price, - he.price_prev, - he.excluded - FROM - holdings_enriched he - INNER JOIN security s ON s.id = he.security_id - INNER JOIN holding h ON h.id = he.id - WHERE - he.account_id = ${accountId} - ORDER BY - he.excluded ASC, - he.value DESC - OFFSET ${page * pageSize} - LIMIT ${pageSize}; + SELECT + h.id, + h.security_id, + s.name, + s.symbol, + s.shares_per_contract, + he.quantity, + he.value, + he.cost_basis, + h.cost_basis_user, + h.cost_basis_provider, + he.cost_basis_per_share, + he.price, + he.price_prev, + he.excluded + FROM + holdings_enriched he + INNER JOIN security s ON s.id = he.security_id + INNER JOIN holding h ON h.id = he.id + WHERE + he.account_id = ${accountId} + ORDER BY + he.excluded ASC, + he.value DESC + OFFSET ${page * pageSize} + LIMIT ${pageSize}; ` ) @@ -204,7 +204,7 @@ export class AccountQueryService implements IAccountQueryService { ) v ORDER BY v.date ASC - ` + ` ) return rows @@ -220,79 +220,79 @@ export class AccountQueryService implements IAccountQueryService { const { rows } = await this.pg.pool.query( sql` - WITH start_date AS ( - SELECT - a.id AS "account_id", - GREATEST(account_value_start_date(a.id), a.start_date) AS "start_date" - FROM - account a - WHERE - a.id = ANY(${pAccountIds}) - GROUP BY - 1 - ), external_flows AS ( - SELECT - it.account_id, - it.date, - SUM(it.amount) AS "amount" - FROM - investment_transaction it - LEFT JOIN start_date sd ON sd.account_id = it.account_id - WHERE - it.account_id = ANY(${pAccountIds}) - AND it.date BETWEEN sd.start_date AND ${pEnd} - -- filter for investment_transactions that represent external flows - AND ( - (it.plaid_type = 'cash' AND it.plaid_subtype IN ('contribution', 'deposit', 'withdrawal')) - OR (it.plaid_type = 'transfer' AND it.plaid_subtype IN ('transfer')) - OR (it.plaid_type = 'buy' AND it.plaid_subtype IN ('contribution')) - OR (it.finicity_transaction_id IS NOT NULL AND it.finicity_investment_transaction_type IN ('contribution', 'deposit', 'transfer')) - ) - GROUP BY - 1, 2 - ), external_flow_totals AS ( - SELECT - account_id, - SUM(amount) as "amount" - FROM - external_flows - GROUP BY - 1 - ), balances AS ( - SELECT - abg.account_id, - abg.date, - abg.balance, - 0 - SUM(COALESCE(ef.amount, 0)) OVER (PARTITION BY abg.account_id ORDER BY abg.date ASC) AS "contributions_period", - COALESCE(-1 * (eft.amount - coalesce(SUM(ef.amount) OVER (PARTITION BY abg.account_id ORDER BY abg.date DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0)), 0) AS "contributions" - FROM - account_balances_gapfilled( - ${pStart}, - ${pEnd}, - '1d', - ${pAccountIds} - ) abg - LEFT JOIN external_flows ef ON ef.account_id = abg.account_id AND ef.date = abg.date - LEFT JOIN external_flow_totals eft ON eft.account_id = abg.account_id - ) - SELECT - b.account_id, - b.date, - b.balance, - b.contributions, - b.contributions_period, - COALESCE(ROUND((b.balance - b0.balance - b.contributions_period) / COALESCE(NULLIF(b0.balance, 0), NULLIF(b.contributions_period, 0)), 4), 0) AS "rate_of_return" - FROM - balances b - LEFT JOIN ( - SELECT DISTINCT ON (account_id) - account_id, - balance + WITH start_date AS ( + SELECT + a.id AS "account_id", + GREATEST(account_value_start_date(a.id), a.start_date) AS "start_date" FROM - balances - ORDER BY - account_id, date ASC - ) b0 ON b0.account_id = b.account_id + account a + WHERE + a.id = ANY(${pAccountIds}) + GROUP BY + 1 + ), external_flows AS ( + SELECT + it.account_id, + it.date, + SUM(it.amount) AS "amount" + FROM + investment_transaction it + LEFT JOIN start_date sd ON sd.account_id = it.account_id + WHERE + it.account_id = ANY(${pAccountIds}) + AND it.date BETWEEN sd.start_date AND ${pEnd} + -- filter for investment_transactions that represent external flows + AND ( + (it.plaid_type = 'cash' AND it.plaid_subtype IN ('contribution', 'deposit', 'withdrawal')) + OR (it.plaid_type = 'transfer' AND it.plaid_subtype IN ('transfer')) + OR (it.plaid_type = 'buy' AND it.plaid_subtype IN ('contribution')) + OR (it.finicity_transaction_id IS NOT NULL AND it.finicity_investment_transaction_type IN ('contribution', 'deposit', 'transfer')) + ) + GROUP BY + 1, 2 + ), external_flow_totals AS ( + SELECT + account_id, + SUM(amount) as "amount" + FROM + external_flows + GROUP BY + 1 + ), balances AS ( + SELECT + abg.account_id, + abg.date, + abg.balance, + 0 - SUM(COALESCE(ef.amount, 0)) OVER (PARTITION BY abg.account_id ORDER BY abg.date ASC) AS "contributions_period", + COALESCE(-1 * (eft.amount - coalesce(SUM(ef.amount) OVER (PARTITION BY abg.account_id ORDER BY abg.date DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0)), 0) AS "contributions" + FROM + account_balances_gapfilled( + ${pStart}, + ${pEnd}, + '1d', + ${pAccountIds} + ) abg + LEFT JOIN external_flows ef ON ef.account_id = abg.account_id AND ef.date = abg.date + LEFT JOIN external_flow_totals eft ON eft.account_id = abg.account_id + ) + SELECT + b.account_id, + b.date, + b.balance, + b.contributions, + b.contributions_period, + COALESCE(ROUND((b.balance - b0.balance - b.contributions_period) / COALESCE(NULLIF(b0.balance, 0), NULLIF(b.contributions_period, 0)), 4), 0) AS "rate_of_return" + FROM + balances b + LEFT JOIN ( + SELECT DISTINCT ON (account_id) + account_id, + balance + FROM + balances + ORDER BY + account_id, date ASC + ) b0 ON b0.account_id = b.account_id ` ) @@ -313,17 +313,17 @@ export class AccountQueryService implements IAccountQueryService { const { rows } = await this.pg.pool.query( sql` - SELECT - abg.account_id, - abg.date, - abg.balance - FROM - account_balances_gapfilled( - ${pStart}, - ${pEnd}, - ${pInterval}, - ${pAccountIds} - ) abg + SELECT + abg.account_id, + abg.date, + abg.balance + FROM + account_balances_gapfilled( + ${pStart}, + ${pEnd}, + ${pInterval}, + ${pAccountIds} + ) abg ` ) @@ -344,14 +344,14 @@ export class AccountQueryService implements IAccountQueryService { 'accountIds' in id ? id.accountIds : sql`( - SELECT - array_agg(a.id) - FROM - account a - LEFT JOIN account_connection ac ON ac.id = a.account_connection_id - WHERE - (a.user_id = ${id.userId} OR ac.user_id = ${id.userId}) - AND a.is_active + SELECT + array_agg(a.id) + FROM + account a + LEFT JOIN account_connection ac ON ac.id = a.account_connection_id + WHERE + (a.user_id = ${id.userId} OR ac.user_id = ${id.userId}) + AND a.is_active )` const pStart = raw(`'${start}'`) @@ -379,26 +379,26 @@ export class AccountQueryService implements IAccountQueryService { } >( sql` - SELECT - abg.date, - a.category, - a.classification, - SUM(CASE WHEN a.classification = 'asset' THEN abg.balance ELSE -abg.balance END) AS balance - FROM - account_balances_gapfilled( - ${pStart}, - ${pEnd}, - ${pInterval}, - ${pAccountIds} - ) abg - INNER JOIN account a ON a.id = abg.account_id - GROUP BY - GROUPING SETS ( - (abg.date, a.classification, a.category), - (abg.date, a.classification), - (abg.date) - ) - ORDER BY date ASC; + SELECT + abg.date, + a.category, + a.classification, + SUM(CASE WHEN a.classification = 'asset' THEN abg.balance ELSE -abg.balance END) AS balance + FROM + account_balances_gapfilled( + ${pStart}, + ${pEnd}, + ${pInterval}, + ${pAccountIds} + ) abg + INNER JOIN account a ON a.id = abg.account_id + GROUP BY + GROUPING SETS ( + (abg.date, a.classification, a.category), + (abg.date, a.classification), + (abg.date) + ) + ORDER BY date ASC; ` ) @@ -453,14 +453,14 @@ export class AccountQueryService implements IAccountQueryService { 'accountId' in id ? [id.accountId] : sql`( - SELECT - array_agg(a.id) - FROM - account a - LEFT JOIN account_connection ac ON ac.id = a.account_connection_id - WHERE - (a.user_id = ${id.userId} OR ac.user_id = ${id.userId}) - AND a.is_active + SELECT + array_agg(a.id) + FROM + account a + LEFT JOIN account_connection ac ON ac.id = a.account_connection_id + WHERE + (a.user_id = ${id.userId} OR ac.user_id = ${id.userId}) + AND a.is_active )` const pStart = raw(`'${start}'`) @@ -469,58 +469,58 @@ export class AccountQueryService implements IAccountQueryService { const { rows } = await this.pg.pool.query( sql` - WITH account_rollup AS ( + WITH account_rollup AS ( + SELECT + abg.date, + a.classification, + a.category, + a.id, + SUM(abg.balance) AS balance, + CASE GROUPING(abg.date, a.classification, a.category, a.id) + WHEN 3 THEN 'classification' + WHEN 1 THEN 'category' + WHEN 0 THEN 'account' + ELSE NULL + END AS grouping + FROM + account_balances_gapfilled( + ${pStart}, + ${pEnd}, + ${pInterval}, + ${pAccountIds} + ) abg + INNER JOIN account a ON a.id = abg.account_id + GROUP BY + GROUPING SETS ( + (abg.date, a.classification, a.category, a.id), + (abg.date, a.classification, a.category), + (abg.date, a.classification) + ) + ) SELECT - abg.date, - a.classification, - a.category, - a.id, - SUM(abg.balance) AS balance, - CASE GROUPING(abg.date, a.classification, a.category, a.id) - WHEN 3 THEN 'classification' - WHEN 1 THEN 'category' - WHEN 0 THEN 'account' - ELSE NULL - END AS grouping + ar.date, + ar.classification, + ar.category, + ar.id, + ar.balance, + ar.grouping, + CASE + WHEN a.id IS NULL THEN NULL + ELSE json_build_object('id', a.id, 'name', a.name, 'mask', a.mask, 'syncStatus', a.sync_status, 'connection', CASE WHEN ac.id IS NULL THEN NULL ELSE json_build_object('name', ac.name, 'syncStatus', ac.sync_status) END) + END AS account, + ROUND( + CASE ar.grouping + WHEN 'account' THEN COALESCE(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date, ar.classification, ar.category), 0) + WHEN 'category' THEN COALESCE(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date, ar.classification), 0) + WHEN 'classification' THEN COALESCE(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date), 0) + END, 4) AS rollup_pct, + ROUND(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date), 4) AS total_pct FROM - account_balances_gapfilled( - ${pStart}, - ${pEnd}, - ${pInterval}, - ${pAccountIds} - ) abg - INNER JOIN account a ON a.id = abg.account_id - GROUP BY - GROUPING SETS ( - (abg.date, a.classification, a.category, a.id), - (abg.date, a.classification, a.category), - (abg.date, a.classification) - ) - ) - SELECT - ar.date, - ar.classification, - ar.category, - ar.id, - ar.balance, - ar.grouping, - CASE - WHEN a.id IS NULL THEN NULL - ELSE json_build_object('id', a.id, 'name', a.name, 'mask', a.mask, 'syncStatus', a.sync_status, 'connection', CASE WHEN ac.id IS NULL THEN NULL ELSE json_build_object('name', ac.name, 'syncStatus', ac.sync_status) END) - END AS account, - ROUND( - CASE ar.grouping - WHEN 'account' THEN COALESCE(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date, ar.classification, ar.category), 0) - WHEN 'category' THEN COALESCE(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date, ar.classification), 0) - WHEN 'classification' THEN COALESCE(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date), 0) - END, 4) AS rollup_pct, - ROUND(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date), 4) AS total_pct - FROM - account_rollup ar - LEFT JOIN account a ON a.id = ar.id - LEFT JOIN account_connection ac ON ac.id = a.account_connection_id - ORDER BY - ar.classification, ar.category, ar.id, ar.date; + account_rollup ar + LEFT JOIN account a ON a.id = ar.id + LEFT JOIN account_connection ac ON ac.id = a.account_connection_id + ORDER BY + ar.classification, ar.category, ar.id, ar.date; ` )