From effa84fe14571025b7bbab1bb3a247380614a8b4 Mon Sep 17 00:00:00 2001 From: kennethcheng Date: Tue, 28 Apr 2026 01:54:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(api):=20=E5=AF=A6=E8=A3=9D=20portfolio=20?= =?UTF-8?q?=E5=8C=AF=E7=8E=87=E5=85=9C=E5=BA=95=E9=82=8F=E8=BC=AF=EF=BC=8C?= =?UTF-8?q?=E4=B8=A6=E5=9C=A8=20transaction=20=E9=8C=84=E5=85=A5=E5=B1=A4?= =?UTF-8?q?=E8=87=AA=E5=8B=95=E6=94=94=E6=88=AA=E5=AF=AB=E5=85=A5=E6=AD=B7?= =?UTF-8?q?=E5=8F=B2=E5=8C=AF=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/actions/portfolio.ts | 37 ++++++++++++++++++++++++++----------- src/actions/transaction.ts | 20 +++++++++++++++++--- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/actions/portfolio.ts b/src/actions/portfolio.ts index 7cc9741..027b8a7 100644 --- a/src/actions/portfolio.ts +++ b/src/actions/portfolio.ts @@ -73,12 +73,13 @@ function calculateCnyValueFromPrice( } export async function getPortfolioPositions(): Promise { - const allTransactions = await db + const allTransactions = await db .select({ txType: transactions.txType, quantity: transactions.quantity, price: transactions.price, exchangeRate: transactions.exchangeRate, + txCurrency: transactions.txCurrency, assetId: transactions.assetId, assetSymbol: assets.symbol, assetType: assets.type, @@ -89,6 +90,14 @@ export async function getPortfolioPositions(): Promise { .leftJoin(assets, eq(assets.id, transactions.assetId)) .orderBy(desc(transactions.executedAt)); + const rates = await db.select({ + fromCurrency: exchangeRates.fromCurrency, + toCurrency: exchangeRates.toCurrency, + rate: exchangeRates.rate, + }).from(exchangeRates); + + const rateMap = buildRateMap(rates); + const holdings = new Map { holding.quantity = holding.quantity.plus(new Big(tx.quantity)); const costPerUnit = new Big(tx.quantity).times(new Big(tx.price)); holding.totalCostNative = holding.totalCostNative.plus(costPerUnit); - const costCny = costPerUnit.times(new Big(tx.exchangeRate || '1')); + let appliedRate = tx.exchangeRate; + if ((!appliedRate || appliedRate === '1' || appliedRate === '1.00000000') && tx.txCurrency !== 'CNY') { + const fallbackRate = getRate(rateMap, tx.txCurrency, 'CNY'); + if (fallbackRate) { + appliedRate = fallbackRate; + } + } + const costCny = costPerUnit.times(new Big(appliedRate || '1')); holding.totalCostCny = holding.totalCostCny.plus(costCny); } else if (tx.txType === 'SELL') { holding.quantity = holding.quantity.minus(new Big(tx.quantity)); const sellCostPerUnit = new Big(tx.quantity).times(new Big(tx.price)); holding.totalCostNative = holding.totalCostNative.minus(sellCostPerUnit); - const sellCostCny = sellCostPerUnit.times(new Big(tx.exchangeRate || '1')); + let appliedRate = tx.exchangeRate; + if ((!appliedRate || appliedRate === '1' || appliedRate === '1.00000000') && tx.txCurrency !== 'CNY') { + const fallbackRate = getRate(rateMap, tx.txCurrency, 'CNY'); + if (fallbackRate) { + appliedRate = fallbackRate; + } + } + const sellCostCny = sellCostPerUnit.times(new Big(appliedRate || '1')); holding.totalCostCny = holding.totalCostCny.minus(sellCostCny); } else if (tx.txType === 'AIRDROP') { holding.quantity = holding.quantity.plus(new Big(tx.quantity)); @@ -142,14 +165,6 @@ export async function getPortfolioPositions(): Promise { } } - const rates = await db.select({ - fromCurrency: exchangeRates.fromCurrency, - toCurrency: exchangeRates.toCurrency, - rate: exchangeRates.rate, - }).from(exchangeRates); - - const rateMap = buildRateMap(rates); - const result: Position[] = []; let totalCnyValue = new Big('0'); let totalPnlCny = new Big('0'); diff --git a/src/actions/transaction.ts b/src/actions/transaction.ts index 24ac31f..6d49a44 100644 --- a/src/actions/transaction.ts +++ b/src/actions/transaction.ts @@ -1,9 +1,9 @@ 'use server'; import { db } from '@/db'; -import { transactions, transactionTypeEnum } from '@/db/schema'; +import { transactions, transactionTypeEnum, exchangeRates } from '@/db/schema'; import { z } from 'zod'; -import { eq, desc } from 'drizzle-orm'; +import { eq, desc, and } from 'drizzle-orm'; const createTransactionSchema = z.object({ assetId: z.string().uuid(), @@ -23,7 +23,21 @@ export async function createTransaction(params: z.infer