Files
backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts
T
ksv741 afdb6b15fd
Backend verify / commits (pull_request) Successful in 1m28s
Backend verify / security (pull_request) Successful in 1m7s
Backend verify / verify (pull_request) Successful in 6m6s
refactor: replace deep imports with native node aliases (WealthLedger/backend#165)
2026-11-04 22:58:36 +03:00

123 lines
3.6 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import type { TbankBond } from '#src/integrations/tbank/tbank.api';
import { formatDecimal } from '#src/common/decimal';
import { decimal } from '#src/integrations/tbank/decimal';
import { CatalogDiscoveryService } from '../../application/catalog-discovery.service';
import { timestamp } from '../../application/instrument-market';
import {
BondDto,
BondResourceDto,
BondSearchResourceDto,
} from '../presentation/bond.dto';
type IdentifiedBond = TbankBond & { uid: string };
import { ISO_DATE_LENGTH, ISO_DATE_START } from '#src/common/time';
import { money } from '../infrastructure/bond-money.mapper';
const MIN_LOT_EXCLUSIVE = 0;
@Injectable()
export class BondCatalogService {
constructor(private readonly discovery: CatalogDiscoveryService) {}
async search(
userId: string,
query: string,
observe = false,
): Promise<BondSearchResourceDto> {
const result = await this.discovery.search(
userId,
{ instrumentType: 'bond', query },
observe,
);
return {
...result,
data:
result.data?.map((item) => {
const short = toBond(item);
const detail = item.detail
? toBond({ ...item.detail, uid: item.uid })
: short;
const last = item.price;
return {
...detail,
classCode: detail.classCode ?? short.classCode,
isin: detail.isin ?? short.isin,
apiTradeAvailable: short.apiTradeAvailable,
pricePercent: last?.price
? formatDecimal(decimal(last.price))
: null,
priceAt: timestamp(last?.time),
};
}) ?? null,
};
}
async get(
userId: string,
uid: string,
observe = false,
): Promise<BondResourceDto> {
const result = await this.discovery.detail(userId, 'bond', uid, observe);
return {
...result,
data: result.data
? { ...toBond(result.data), slug: result.data.slug }
: null,
};
}
async getByTicker(
userId: string,
ticker: string,
observe = false,
): Promise<BondResourceDto> {
const result = await this.discovery.byTicker(
userId,
'bond',
ticker,
observe,
);
return {
...result,
data: result.data
? { ...toBond(result.data), slug: result.data.slug }
: null,
};
}
}
function toBond(bond: IdentifiedBond): BondDto {
return {
instrumentUid: bond.uid,
slug: bond.uid,
name: bond.name?.trim() || bond.ticker || bond.uid,
ticker: bond.ticker?.trim() || null,
isin: bond.isin?.trim() || null,
classCode: bond.classCode?.trim() || null,
realExchange: bond.realExchange || null,
apiTradeAvailable: bond.apiTradeAvailableFlag ?? null,
logoName: bond.brand?.logoName?.trim() || null,
currency: bond.currency?.toUpperCase() || null,
bondType: bond.bondType || null,
sector: bond.sector || null,
lot: bond.lot && bond.lot > MIN_LOT_EXCLUSIVE ? bond.lot : null,
historyFrom: timestamp(bond.first_1dayCandleDate),
nominal: money(bond.nominal),
initialNominal: money(bond.initialNominal),
aci: money(bond.aciValue),
maturityDate: timestamp(bond.maturityDate)?.slice(ISO_DATE_START, ISO_DATE_LENGTH) ?? null,
callDate: timestamp(bond.callDate)?.slice(ISO_DATE_START, ISO_DATE_LENGTH) ?? null,
couponQuantityPerYear: bond.couponQuantityPerYear ?? null,
floatingCoupon: bond.floatingCouponFlag ?? null,
perpetual: bond.perpetualFlag ?? null,
amortization: bond.amortizationFlag ?? null,
subordinated: bond.subordinatedFlag ?? null,
riskLevel: bond.riskLevel || null,
};
}