mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 15:35:22 +02:00
remove plaid from tests and replace db function
This commit is contained in:
parent
a33cef920f
commit
024f289ee9
10 changed files with 97 additions and 50 deletions
|
@ -33,16 +33,16 @@ Sentry.init({
|
||||||
|
|
||||||
// Providers and components only relevant to a logged-in user
|
// Providers and components only relevant to a logged-in user
|
||||||
const WithAuth = function ({ children }: PropsWithChildren) {
|
const WithAuth = function ({ children }: PropsWithChildren) {
|
||||||
const { data: session } = useSession()
|
const { data: session, status } = useSession()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!session) {
|
if (!session && status === 'unauthenticated') {
|
||||||
router.push('/login')
|
router.push('/login')
|
||||||
}
|
}
|
||||||
}, [session, router])
|
}, [session, status, router])
|
||||||
|
|
||||||
if (session) {
|
if (session && status === 'authenticated') {
|
||||||
return (
|
return (
|
||||||
<OnboardingGuard>
|
<OnboardingGuard>
|
||||||
<UserAccountContextProvider>
|
<UserAccountContextProvider>
|
||||||
|
|
|
@ -15,17 +15,11 @@ import { startServer, stopServer } from './utils/server'
|
||||||
import { getAxiosClient } from './utils/axios'
|
import { getAxiosClient } from './utils/axios'
|
||||||
import { resetUser } from './utils/user'
|
import { resetUser } from './utils/user'
|
||||||
import { createTestInvestmentAccount } from './utils/account'
|
import { createTestInvestmentAccount } from './utils/account'
|
||||||
import { default as _plaid } from '../lib/plaid'
|
|
||||||
|
|
||||||
jest.mock('../middleware/validate-plaid-jwt.ts')
|
|
||||||
jest.mock('bull')
|
jest.mock('bull')
|
||||||
jest.mock('plaid')
|
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
// For TypeScript support
|
|
||||||
const plaid = jest.mocked(_plaid) // eslint-disable-line
|
|
||||||
|
|
||||||
const authId = '__TEST_USER_ID__'
|
const authId = '__TEST_USER_ID__'
|
||||||
let axios: AxiosInstance
|
let axios: AxiosInstance
|
||||||
let user: User
|
let user: User
|
||||||
|
@ -107,10 +101,6 @@ describe('/v1/accounts API', () => {
|
||||||
mask: null,
|
mask: null,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
syncStatus: 'IDLE',
|
syncStatus: 'IDLE',
|
||||||
plaidType: null,
|
|
||||||
plaidSubtype: null,
|
|
||||||
plaidAccountId: null,
|
|
||||||
plaidLiability: null,
|
|
||||||
currencyCode: 'USD',
|
currencyCode: 'USD',
|
||||||
currentBalance: new Decimal(21_000),
|
currentBalance: new Decimal(21_000),
|
||||||
availableBalance: null,
|
availableBalance: null,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { User } from '@prisma/client'
|
import { InvestmentTransactionCategory, type User } from '@prisma/client'
|
||||||
import { PrismaClient } from '@prisma/client'
|
import { PrismaClient } from '@prisma/client'
|
||||||
import { createLogger, transports } from 'winston'
|
import { createLogger, transports } from 'winston'
|
||||||
import { DateTime } from 'luxon'
|
import { DateTime } from 'luxon'
|
||||||
|
@ -130,7 +130,7 @@ describe('balance sync strategies', () => {
|
||||||
amount: 100,
|
amount: 100,
|
||||||
quantity: 10,
|
quantity: 10,
|
||||||
price: 10,
|
price: 10,
|
||||||
plaidType: 'buy',
|
category: InvestmentTransactionCategory.buy,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: DateTime.fromISO('2023-02-04').toJSDate(),
|
date: DateTime.fromISO('2023-02-04').toJSDate(),
|
||||||
|
@ -139,7 +139,7 @@ describe('balance sync strategies', () => {
|
||||||
amount: -50,
|
amount: -50,
|
||||||
quantity: 5,
|
quantity: 5,
|
||||||
price: 10,
|
price: 10,
|
||||||
plaidType: 'sell',
|
category: InvestmentTransactionCategory.sell,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: DateTime.fromISO('2023-02-04').toJSDate(),
|
date: DateTime.fromISO('2023-02-04').toJSDate(),
|
||||||
|
@ -147,6 +147,7 @@ describe('balance sync strategies', () => {
|
||||||
amount: 50,
|
amount: 50,
|
||||||
quantity: 50,
|
quantity: 50,
|
||||||
price: 1,
|
price: 1,
|
||||||
|
category: InvestmentTransactionCategory.other,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -35,7 +35,7 @@ export async function createTestInvestmentAccount(
|
||||||
join(__dirname, `../test-data/${portfolio}/holdings.csv`)
|
join(__dirname, `../test-data/${portfolio}/holdings.csv`)
|
||||||
)
|
)
|
||||||
|
|
||||||
const [_deleted, ...securities] = await prisma.$transaction([
|
const [, ...securities] = await prisma.$transaction([
|
||||||
prisma.security.deleteMany({
|
prisma.security.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
symbol: {
|
symbol: {
|
||||||
|
@ -99,7 +99,21 @@ export async function createTestInvestmentAccount(
|
||||||
(s) => s.date === it.date && s.ticker === it.ticker
|
(s) => s.date === it.date && s.ticker === it.ticker
|
||||||
)?.price
|
)?.price
|
||||||
|
|
||||||
const isCashFlow = it.type === 'DEPOSIT' || it.type === 'WITHDRAW'
|
function getTransactionCategory(type: string) {
|
||||||
|
switch (type) {
|
||||||
|
case 'BUY':
|
||||||
|
return 'buy'
|
||||||
|
case 'SELL':
|
||||||
|
return 'sell'
|
||||||
|
case 'DIVIDEND':
|
||||||
|
return 'dividend'
|
||||||
|
case 'DEPOSIT':
|
||||||
|
case 'WITHDRAW':
|
||||||
|
return 'transfer'
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
securityId: securities.find((s) => it.ticker === s.symbol)?.id,
|
securityId: securities.find((s) => it.ticker === s.symbol)?.id,
|
||||||
|
@ -108,26 +122,7 @@ export async function createTestInvestmentAccount(
|
||||||
amount: price ? new Prisma.Decimal(price).times(it.qty) : it.qty,
|
amount: price ? new Prisma.Decimal(price).times(it.qty) : it.qty,
|
||||||
quantity: price ? it.qty : 0,
|
quantity: price ? it.qty : 0,
|
||||||
price: price ?? 0,
|
price: price ?? 0,
|
||||||
plaidType:
|
category: getTransactionCategory(it.type),
|
||||||
isCashFlow || it.type === 'DIVIDEND'
|
|
||||||
? 'cash'
|
|
||||||
: it.type === 'BUY'
|
|
||||||
? 'buy'
|
|
||||||
: it.type === 'SELL'
|
|
||||||
? 'sell'
|
|
||||||
: undefined,
|
|
||||||
plaidSubtype:
|
|
||||||
it.type === 'DEPOSIT'
|
|
||||||
? 'deposit'
|
|
||||||
: it.type === 'WITHDRAW'
|
|
||||||
? 'withdrawal'
|
|
||||||
: it.type === 'DIVIDEND'
|
|
||||||
? 'dividend'
|
|
||||||
: it.type === 'BUY'
|
|
||||||
? 'buy'
|
|
||||||
: it.type === 'SELL'
|
|
||||||
? 'sell'
|
|
||||||
: undefined,
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
|
@ -70,8 +70,7 @@ export class InvestmentTransactionBalanceSyncStrategy extends BalanceSyncStrateg
|
||||||
it.account_id = ${pAccountId}
|
it.account_id = ${pAccountId}
|
||||||
AND it.date BETWEEN ${pStart} AND now()
|
AND it.date BETWEEN ${pStart} AND now()
|
||||||
AND ( -- filter for transactions that modify a position
|
AND ( -- filter for transactions that modify a position
|
||||||
it.plaid_type IN ('buy', 'sell', 'transfer')
|
it.category IN ('buy', 'sell', 'transfer')
|
||||||
OR it.finicity_transaction_id IS NOT NULL
|
|
||||||
)
|
)
|
||||||
GROUP BY
|
GROUP BY
|
||||||
1, 2
|
1, 2
|
||||||
|
|
|
@ -243,10 +243,7 @@ export class AccountQueryService implements IAccountQueryService {
|
||||||
AND it.date BETWEEN sd.start_date AND ${pEnd}
|
AND it.date BETWEEN sd.start_date AND ${pEnd}
|
||||||
-- filter for investment_transactions that represent external flows
|
-- filter for investment_transactions that represent external flows
|
||||||
AND (
|
AND (
|
||||||
(it.plaid_type = 'cash' AND it.plaid_subtype IN ('contribution', 'deposit', 'withdrawal'))
|
it.category = 'transfer'
|
||||||
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
|
GROUP BY
|
||||||
1, 2
|
1, 2
|
||||||
|
|
|
@ -747,10 +747,7 @@ export class InsightService implements IInsightService {
|
||||||
WHERE
|
WHERE
|
||||||
it.account_id = ${accountId}
|
it.account_id = ${accountId}
|
||||||
AND (
|
AND (
|
||||||
(it.plaid_type = 'cash' AND it.plaid_subtype IN ('contribution', 'deposit', 'withdrawal'))
|
it.category = 'transfer'
|
||||||
OR (it.plaid_type = 'transfer' AND it.plaid_subtype IN ('transfer', 'send', 'request'))
|
|
||||||
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'))
|
|
||||||
)
|
)
|
||||||
-- Exclude any contributions made prior to the start date since balances will be 0
|
-- Exclude any contributions made prior to the start date since balances will be 0
|
||||||
AND (a.start_date is NULL OR it.date >= a.start_date)
|
AND (a.start_date is NULL OR it.date >= a.start_date)
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
ALTER TABLE
|
||||||
|
"investment_transaction" DROP COLUMN "category";
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
"investment_transaction"
|
||||||
|
ADD
|
||||||
|
COLUMN "category" "InvestmentTransactionCategory" DEFAULT 'other' :: "InvestmentTransactionCategory" NOT NULL;
|
|
@ -0,0 +1,61 @@
|
||||||
|
CREATE OR REPLACE FUNCTION calculate_return_dietz(p_account_id account.id%type, p_start date, p_end date, out percentage numeric, out amount numeric) AS $$
|
||||||
|
DECLARE
|
||||||
|
v_start date := GREATEST(p_start, (SELECT MIN(date) FROM account_balance WHERE account_id = p_account_id));
|
||||||
|
v_end date := p_end;
|
||||||
|
v_days int := v_end - v_start;
|
||||||
|
BEGIN
|
||||||
|
SELECT
|
||||||
|
ROUND((b1.balance - b0.balance - flows.net) / NULLIF(b0.balance + flows.weighted, 0), 4) AS "percentage",
|
||||||
|
b1.balance - b0.balance - flows.net AS "amount"
|
||||||
|
INTO
|
||||||
|
percentage, amount
|
||||||
|
FROM
|
||||||
|
account a
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT
|
||||||
|
COALESCE(SUM(-fw.flow), 0) AS "net",
|
||||||
|
COALESCE(SUM(-fw.flow * fw.weight), 0) AS "weighted"
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
SUM(it.amount) AS flow,
|
||||||
|
(v_days - (it.date - v_start))::numeric / v_days AS weight
|
||||||
|
FROM
|
||||||
|
investment_transaction it
|
||||||
|
WHERE
|
||||||
|
it.account_id = a.id
|
||||||
|
AND it.date BETWEEN v_start AND v_end
|
||||||
|
-- filter for investment_transactions that represent external flows
|
||||||
|
AND (
|
||||||
|
it.category = 'transfer'
|
||||||
|
)
|
||||||
|
GROUP BY
|
||||||
|
it.date
|
||||||
|
) fw
|
||||||
|
) flows ON TRUE
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT
|
||||||
|
ab.balance AS "balance"
|
||||||
|
FROM
|
||||||
|
account_balance ab
|
||||||
|
WHERE
|
||||||
|
ab.account_id = a.id AND ab.date <= v_start
|
||||||
|
ORDER BY
|
||||||
|
ab.date DESC
|
||||||
|
LIMIT 1
|
||||||
|
) b0 ON TRUE
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT
|
||||||
|
COALESCE(ab.balance, a.current_balance) AS "balance"
|
||||||
|
FROM
|
||||||
|
account_balance ab
|
||||||
|
WHERE
|
||||||
|
ab.account_id = a.id AND ab.date <= v_end
|
||||||
|
ORDER BY
|
||||||
|
ab.date DESC
|
||||||
|
LIMIT 1
|
||||||
|
) b1 ON TRUE
|
||||||
|
WHERE
|
||||||
|
a.id = p_account_id;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql STABLE;
|
||||||
|
|
|
@ -250,7 +250,7 @@ model InvestmentTransaction {
|
||||||
currencyCode String @default("USD") @map("currency_code")
|
currencyCode String @default("USD") @map("currency_code")
|
||||||
|
|
||||||
// Derived from provider types
|
// Derived from provider types
|
||||||
category InvestmentTransactionCategory @default(dbgenerated("\nCASE\n WHEN (plaid_type = 'buy'::text) THEN 'buy'::\"InvestmentTransactionCategory\"\n WHEN (plaid_type = 'sell'::text) THEN 'sell'::\"InvestmentTransactionCategory\"\n WHEN (plaid_subtype = ANY (ARRAY['dividend'::text, 'qualified dividend'::text, 'non-qualified dividend'::text])) THEN 'dividend'::\"InvestmentTransactionCategory\"\n WHEN (plaid_subtype = ANY (ARRAY['non-resident tax'::text, 'tax'::text, 'tax withheld'::text])) THEN 'tax'::\"InvestmentTransactionCategory\"\n WHEN ((plaid_type = 'fee'::text) OR (plaid_subtype = ANY (ARRAY['account fee'::text, 'legal fee'::text, 'management fee'::text, 'margin expense'::text, 'transfer fee'::text, 'trust fee'::text]))) THEN 'fee'::\"InvestmentTransactionCategory\"\n WHEN (plaid_type = 'cash'::text) THEN 'transfer'::\"InvestmentTransactionCategory\"\n WHEN (plaid_type = 'cancel'::text) THEN 'cancel'::\"InvestmentTransactionCategory\"\n WHEN (finicity_investment_transaction_type = ANY (ARRAY['purchased'::text, 'purchaseToClose'::text, 'purchaseToCover'::text, 'dividendReinvest'::text, 'reinvestOfIncome'::text])) THEN 'buy'::\"InvestmentTransactionCategory\"\n WHEN (finicity_investment_transaction_type = ANY (ARRAY['sold'::text, 'soldToClose'::text, 'soldToOpen'::text])) THEN 'sell'::\"InvestmentTransactionCategory\"\n WHEN (finicity_investment_transaction_type = 'dividend'::text) THEN 'dividend'::\"InvestmentTransactionCategory\"\n WHEN (finicity_investment_transaction_type = 'tax'::text) THEN 'tax'::\"InvestmentTransactionCategory\"\n WHEN (finicity_investment_transaction_type = 'fee'::text) THEN 'fee'::\"InvestmentTransactionCategory\"\n WHEN (finicity_investment_transaction_type = ANY (ARRAY['transfer'::text, 'contribution'::text, 'deposit'::text, 'income'::text, 'interest'::text])) THEN 'transfer'::\"InvestmentTransactionCategory\"\n WHEN (finicity_investment_transaction_type = 'cancel'::text) THEN 'cancel'::\"InvestmentTransactionCategory\"\n ELSE 'other'::\"InvestmentTransactionCategory\"\nEND"))
|
category InvestmentTransactionCategory @default(other)
|
||||||
|
|
||||||
// plaid data
|
// plaid data
|
||||||
plaidInvestmentTransactionId String? @unique @map("plaid_investment_transaction_id")
|
plaidInvestmentTransactionId String? @unique @map("plaid_investment_transaction_id")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue