feat(db): 新增 portfolio_snapshots 表与每日快照记录 Action
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
'use server';
|
||||
|
||||
import { db } from '@/db';
|
||||
import { portfolioSnapshots } from '@/db/schema';
|
||||
import { getPortfolioPositions } from './portfolio';
|
||||
import { eq, sql } from 'drizzle-orm';
|
||||
|
||||
function getTodayInShanghai(): string {
|
||||
const now = new Date();
|
||||
const utcStr = now.toLocaleString('en-US', { timeZone: 'UTC' });
|
||||
const utcDate = new Date(utcStr);
|
||||
const shanghaiOffset = 8 * 60 * 60 * 1000;
|
||||
const shanghaiDate = new Date(utcDate.getTime() + shanghaiOffset);
|
||||
const year = shanghaiDate.getFullYear();
|
||||
const month = String(shanghaiDate.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(shanghaiDate.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
export async function recordDailySnapshot() {
|
||||
const positions = await getPortfolioPositions();
|
||||
|
||||
const totalValueCny = positions.reduce(
|
||||
(sum, pos) => sum.plus(pos.cnyValue || '0'),
|
||||
0 as number | string
|
||||
);
|
||||
|
||||
const totalCostCny = positions.reduce(
|
||||
(sum, pos) => sum.plus(pos.totalCostCny || '0'),
|
||||
0 as number | string
|
||||
);
|
||||
|
||||
const dateStr = getTodayInShanghai();
|
||||
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(portfolioSnapshots)
|
||||
.where(eq(portfolioSnapshots.date, dateStr))
|
||||
.limit(1);
|
||||
|
||||
const now = new Date();
|
||||
|
||||
if (existing.length > 0) {
|
||||
await db
|
||||
.update(portfolioSnapshots)
|
||||
.set({
|
||||
totalValueCny: String(totalValueCny),
|
||||
totalCostCny: String(totalCostCny),
|
||||
updatedAt: now,
|
||||
})
|
||||
.where(eq(portfolioSnapshots.date, dateStr));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: 'updated',
|
||||
date: dateStr,
|
||||
totalValueCny: String(totalValueCny),
|
||||
totalCostCny: String(totalCostCny),
|
||||
};
|
||||
}
|
||||
|
||||
await db
|
||||
.insert(portfolioSnapshots)
|
||||
.values({
|
||||
date: dateStr,
|
||||
totalValueCny: String(totalValueCny),
|
||||
totalCostCny: String(totalCostCny),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: 'inserted',
|
||||
date: dateStr,
|
||||
totalValueCny: String(totalValueCny),
|
||||
totalCostCny: String(totalCostCny),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getSnapshots(params?: {
|
||||
limit?: number;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}) {
|
||||
const { limit = 365, startDate, endDate } = params || {};
|
||||
|
||||
let query = db
|
||||
.select()
|
||||
.from(portfolioSnapshots)
|
||||
.orderBy(sql`"${date}" DESC`);
|
||||
|
||||
if (startDate) {
|
||||
query = query.where(sql`"${date}" >= ${startDate}`);
|
||||
}
|
||||
if (endDate) {
|
||||
query = query.where(sql`"${date}" <= ${endDate}`);
|
||||
}
|
||||
|
||||
const snapshots = await query.limit(limit);
|
||||
|
||||
return snapshots.reverse();
|
||||
}
|
||||
Reference in New Issue
Block a user