fix(api): 實裝 portfolio 匯率兜底邏輯,並在 transaction 錄入層自動攔截寫入歷史匯率
This commit is contained in:
parent
67ceb63b08
commit
effa84fe14
@ -79,6 +79,7 @@ export async function getPortfolioPositions(): Promise<Position[]> {
|
||||
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<Position[]> {
|
||||
.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<string, {
|
||||
assetId: string;
|
||||
symbol: string;
|
||||
@ -123,13 +132,27 @@ export async function getPortfolioPositions(): Promise<Position[]> {
|
||||
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<Position[]> {
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
@ -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<typeof createTransaction
|
||||
}
|
||||
|
||||
try {
|
||||
const [transaction] = await db.insert(transactions).values(validation.data).returning();
|
||||
const data = { ...validation.data };
|
||||
if (data.txCurrency !== 'CNY' && (!data.exchangeRate || data.exchangeRate === '1' || data.exchangeRate === '1.00000000')) {
|
||||
const [latestRate] = await db
|
||||
.select({ rate: exchangeRates.rate })
|
||||
.from(exchangeRates)
|
||||
.where(and(
|
||||
eq(exchangeRates.fromCurrency, data.txCurrency),
|
||||
eq(exchangeRates.toCurrency, 'CNY')
|
||||
))
|
||||
.limit(1);
|
||||
if (latestRate) {
|
||||
data.exchangeRate = latestRate.rate;
|
||||
}
|
||||
}
|
||||
const [transaction] = await db.insert(transactions).values(data).returning();
|
||||
return { success: true, data: transaction };
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user