moex-vibe/apps/backend/src/modules/tbank/services/broker-operation-sync.service.ts

107 lines
3.2 KiB
TypeScript

import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import type { BrokerOperation } from '../types/broker.types';
import { BrokerOperationsService } from './broker-operations.service';
type BrokerOperationSyncRange = {
from: string;
to: string;
};
@Injectable()
export class BrokerOperationSyncService {
constructor(
private readonly operationsService: BrokerOperationsService,
private readonly prisma: PrismaService,
) {}
async syncAccount(
accountId: string,
range: BrokerOperationSyncRange,
): Promise<{ upserted: number }> {
let cursor: string | undefined;
let lastCursor: string | null = null;
let upserted = 0;
do {
const page = await this.operationsService.getOperations(accountId, {
from: range.from,
to: range.to,
cursor,
limit: 1000,
state: 'OPERATION_STATE_EXECUTED',
});
for (const operation of page.data.items) {
await this.upsertOperation(accountId, operation);
upserted++;
}
const nextCursor = page.data.nextCursor ?? undefined;
if (page.data.hasNext && !nextCursor) {
throw new InternalServerErrorException('T-Bank returned hasNext without nextCursor');
}
if (nextCursor) {
lastCursor = nextCursor;
}
cursor = nextCursor;
if (!page.data.hasNext) {
break;
}
} while (cursor);
await this.prisma.brokerOperationSyncState.upsert({
where: { accountId },
create: {
accountId,
lastCursor,
lastSyncedFrom: new Date(range.from),
lastSyncedTo: new Date(range.to),
},
update: {
lastCursor,
lastSyncedFrom: new Date(range.from),
lastSyncedTo: new Date(range.to),
syncedAt: new Date(),
},
});
return { upserted };
}
private async upsertOperation(accountId: string, operation: BrokerOperation): Promise<void> {
const cursor =
operation.cursor || `${operation.id || 'operation'}:${operation.date || 'no-date'}`;
const data = {
accountId,
cursor,
operationId: operation.id,
parentOperationId: operation.parentOperationId,
date: operation.date ? new Date(operation.date) : null,
type: operation.type,
category: operation.category,
state: operation.state,
instrumentUid: operation.instrumentUid,
figi: operation.figi,
ticker: operation.ticker,
classCode: operation.classCode,
payment: operation.payment ? JSON.stringify(operation.payment) : null,
price: operation.price ? JSON.stringify(operation.price) : null,
commission: operation.commission ? JSON.stringify(operation.commission) : null,
yield: operation.yield ? JSON.stringify(operation.yield) : null,
accruedInt: operation.accruedInt ? JSON.stringify(operation.accruedInt) : null,
quantity: operation.quantity,
quantityDone: operation.quantityDone,
raw: JSON.stringify(operation),
};
await this.prisma.brokerOperation.upsert({
where: { accountId_cursor: { accountId, cursor } },
create: data,
update: data,
});
}
}