fix: implement rounding for monetary values across fund and user transactions
Build Images and Deploy / Update-PROD-Stack (push) Failing after 33s
Build Images and Deploy / Update-PROD-Stack (push) Failing after 33s
This commit is contained in:
@@ -3,7 +3,7 @@ import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { z } from 'zod'
|
||||
import { calcFundNav } from '@/lib/pricing'
|
||||
import { calcFundNav, round2 } from '@/lib/pricing'
|
||||
|
||||
const patchSchema = z.object({
|
||||
addManagerUsername: z.string().optional(),
|
||||
@@ -58,7 +58,7 @@ export async function PATCH(
|
||||
}
|
||||
|
||||
if (typeof balance === 'number') {
|
||||
await prisma.user.update({ where: { id: fund.userId }, data: { balance } })
|
||||
await prisma.user.update({ where: { id: fund.userId }, data: { balance: round2(balance) } })
|
||||
}
|
||||
|
||||
const updated = await prisma.hedgeFund.findUnique({
|
||||
@@ -112,14 +112,14 @@ export async function DELETE(
|
||||
const portfolioValue = fund.user.positions.reduce((sum, p) => {
|
||||
const val = p.positionType === 'LONG'
|
||||
? p.shares * p.hashtag.currentPrice
|
||||
: p.avgBuyPrice * p.shares - (p.hashtag.currentPrice - p.avgBuyPrice) * p.shares
|
||||
: (2 * p.avgBuyPrice - p.hashtag.currentPrice) * p.shares
|
||||
return sum + val
|
||||
}, 0)
|
||||
const nav = calcFundNav(fund.user.balance + portfolioValue, fund.sharesOutstanding)
|
||||
|
||||
// Pay out each investor at current NAV before wiping records
|
||||
for (const inv of fund.investments) {
|
||||
const payout = Math.max(0, inv.shares * nav)
|
||||
const payout = Math.max(0, round2(inv.shares * nav))
|
||||
if (payout > 0) {
|
||||
await prisma.user.update({ where: { id: inv.userId }, data: { balance: { increment: payout } } })
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { calcFundNav } from '@/lib/pricing'
|
||||
import { calcFundNav, round2 } from '@/lib/pricing'
|
||||
|
||||
const STARTING_BALANCE = 2000
|
||||
|
||||
@@ -106,7 +106,7 @@ export async function POST(
|
||||
}, 0)
|
||||
const fundTotalValue = inv.fund.user.balance + fundPortfolioValue
|
||||
const nav = calcFundNav(fundTotalValue, inv.fund.sharesOutstanding)
|
||||
const payout = Math.max(0, inv.shares * nav)
|
||||
const payout = Math.max(0, round2(inv.shares * nav))
|
||||
return [
|
||||
prisma.hedgeFund.update({
|
||||
where: { id: inv.fundId },
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { z } from 'zod'
|
||||
import { calcFundNav } from '@/lib/pricing'
|
||||
import { calcFundNav, round2 } from '@/lib/pricing'
|
||||
|
||||
const schema = z.object({
|
||||
balance: z.number().min(0).optional(),
|
||||
@@ -92,13 +92,13 @@ export async function DELETE(
|
||||
const portfolioValue = fund.user.positions.reduce((sum, p) => {
|
||||
const val = p.positionType === 'LONG'
|
||||
? p.shares * p.hashtag.currentPrice
|
||||
: p.avgBuyPrice * p.shares - (p.hashtag.currentPrice - p.avgBuyPrice) * p.shares
|
||||
: (2 * p.avgBuyPrice - p.hashtag.currentPrice) * p.shares
|
||||
return sum + val
|
||||
}, 0)
|
||||
const nav = calcFundNav(fund.user.balance + portfolioValue, fund.sharesOutstanding)
|
||||
const payout = inv.shares * nav
|
||||
const payout = round2(Math.max(0, inv.shares * nav))
|
||||
await prisma.$transaction([
|
||||
prisma.user.update({ where: { id: fund.userId }, data: { balance: { decrement: payout } } }),
|
||||
prisma.user.update({ where: { id: fund.userId }, data: { balance: { decrement: round2(Math.max(0, payout)) } } }),
|
||||
prisma.hedgeFund.update({ where: { id: fund.id }, data: { sharesOutstanding: { decrement: inv.shares } } }),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { calcFundNav } from '@/lib/pricing'
|
||||
import { calcFundNav, round2 } from '@/lib/pricing'
|
||||
|
||||
export async function POST(req: NextRequest, { params }: { params: { slug: string } }) {
|
||||
const session = await getServerSession(authOptions)
|
||||
@@ -10,7 +10,7 @@ export async function POST(req: NextRequest, { params }: { params: { slug: strin
|
||||
|
||||
const slug = decodeURIComponent(params.slug).toLowerCase()
|
||||
const body = await req.json()
|
||||
const amount = Number(body.amount)
|
||||
const amount = round2(Number(body.amount))
|
||||
|
||||
if (!amount || amount < 1) {
|
||||
return NextResponse.json({ error: 'Minimum investment is $1' }, { status: 400 })
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { calcFundNav } from '@/lib/pricing'
|
||||
import { calcFundNav, round2 } from '@/lib/pricing'
|
||||
|
||||
export async function POST(req: NextRequest, { params }: { params: { slug: string } }) {
|
||||
const session = await getServerSession(authOptions)
|
||||
@@ -50,7 +50,7 @@ export async function POST(req: NextRequest, { params }: { params: { slug: strin
|
||||
}, 0)
|
||||
const totalValue = fund.user.balance + portfolioValue
|
||||
const nav = calcFundNav(totalValue, fund.sharesOutstanding)
|
||||
const payout = sharesToRedeem * nav
|
||||
const payout = round2(sharesToRedeem * nav)
|
||||
|
||||
if (fund.user.balance < payout) {
|
||||
return NextResponse.json({ error: 'Fund has insufficient cash to redeem. Try a smaller amount.' }, { status: 400 })
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { round2 } from '@/lib/pricing'
|
||||
import { GRID_SIZE, PRIZE_MAP } from '@/lib/lottery'
|
||||
|
||||
function buildPrizes(): number[] {
|
||||
@@ -68,7 +69,7 @@ export async function POST(req: NextRequest) {
|
||||
prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
balance: { increment: winAmount },
|
||||
balance: { increment: round2(winAmount) },
|
||||
lastLotteryAt: now,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { calcTrade } from '@/lib/pricing'
|
||||
import { calcTrade, round2 } from '@/lib/pricing'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
import { z } from 'zod'
|
||||
|
||||
@@ -105,7 +105,7 @@ export async function POST(req: NextRequest) {
|
||||
// Update user balance
|
||||
await tx.user.update({
|
||||
where: { id: user.id },
|
||||
data: { balance: { increment: balanceDelta } },
|
||||
data: { balance: { increment: round2(balanceDelta) } },
|
||||
})
|
||||
|
||||
// Update / create position
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { calcFundNav } from '@/lib/pricing'
|
||||
import { calcFundNav, round2 } from '@/lib/pricing'
|
||||
|
||||
const STARTING_BALANCE = 2000
|
||||
|
||||
@@ -108,7 +108,7 @@ export async function POST(req: NextRequest) {
|
||||
}, 0)
|
||||
const fundTotalValue = inv.fund.user.balance + fundPortfolioValue
|
||||
const nav = calcFundNav(fundTotalValue, inv.fund.sharesOutstanding)
|
||||
const payout = Math.max(0, inv.shares * nav)
|
||||
const payout = Math.max(0, round2(inv.shares * nav))
|
||||
return [
|
||||
prisma.hedgeFund.update({
|
||||
where: { id: inv.fundId },
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { calcFundNav } from '@/lib/pricing'
|
||||
import { calcFundNav, round2 } from '@/lib/pricing'
|
||||
|
||||
const USERNAME_RE = /^[a-z0-9_]{3,20}$/ // validated after toLowerCase
|
||||
|
||||
@@ -135,11 +135,11 @@ export async function DELETE() {
|
||||
const portfolioValue = fund.user.positions.reduce((sum, p) => {
|
||||
const val = p.positionType === 'LONG'
|
||||
? p.shares * p.hashtag.currentPrice
|
||||
: p.avgBuyPrice * p.shares - (p.hashtag.currentPrice - p.avgBuyPrice) * p.shares
|
||||
: (2 * p.avgBuyPrice - p.hashtag.currentPrice) * p.shares
|
||||
return sum + val
|
||||
}, 0)
|
||||
const nav = calcFundNav(fund.user.balance + portfolioValue, fund.sharesOutstanding)
|
||||
const payout = inv.shares * nav
|
||||
const payout = round2(Math.max(0, inv.shares * nav))
|
||||
await prisma.$transaction([
|
||||
prisma.user.update({ where: { id: fund.userId }, data: { balance: { decrement: payout } } }),
|
||||
prisma.hedgeFund.update({ where: { id: fund.id }, data: { sharesOutstanding: { decrement: inv.shares } } }),
|
||||
|
||||
@@ -39,6 +39,9 @@ export function getBalanceTier(balance: number): BalanceTier {
|
||||
return { level: 1, pointsPerDay: 1, nextThreshold: 10_000 }
|
||||
}
|
||||
|
||||
/** Round a dollar amount to 2 decimal places for DB storage. */
|
||||
export const round2 = (n: number) => Math.round(n * 100) / 100
|
||||
|
||||
/** Calculate NAV (net asset value) per fund share. Returns 1.00 if no shares outstanding. */
|
||||
export function calcFundNav(totalValue: number, sharesOutstanding: number): number {
|
||||
if (sharesOutstanding <= 0) return 1.00
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ async function forceClosePositions(hashtagId: string, price: number, tag: string
|
||||
await prisma.$transaction([
|
||||
prisma.user.update({
|
||||
where: { id: pos.userId },
|
||||
data: { balance: { increment: balanceDelta } },
|
||||
data: { balance: { increment: round2(balanceDelta) } },
|
||||
}),
|
||||
prisma.position.update({
|
||||
where: { id: pos.id },
|
||||
|
||||
Reference in New Issue
Block a user