File

src/portfolio/portfolio.service.ts

Index

Properties
Methods

Methods

Private Async generatePasteSettlementsInfo
generatePasteSettlementsInfo(settledOrdersGraph: ApolloQueryResult<IGetAllSettledOrdersQuery>, funds: FundWithInstrument[])
Parameters :
Name Type Optional
settledOrdersGraph ApolloQueryResult<IGetAllSettledOrdersQuery> No
funds FundWithInstrument[] No
Private Async getAdminPastSettlements
getAdminPastSettlements(user: UserJwtPayload, filterPaginationDto?: FilterPaginationDto)
Parameters :
Name Type Optional
user UserJwtPayload No
filterPaginationDto FilterPaginationDto Yes
Public Async getCurrentSettlements
getCurrentSettlements(user: UserJwtPayload, filterPaginationDto?: FilterPaginationDto)
Parameters :
Name Type Optional
user UserJwtPayload No
filterPaginationDto FilterPaginationDto Yes
Public Async getPasteSettlements
getPasteSettlements(user: UserJwtPayload, filterPaginationDto?: FilterPaginationDto)
Parameters :
Name Type Optional
user UserJwtPayload No
filterPaginationDto FilterPaginationDto Yes
Private Async getUserPastSettlements
getUserPastSettlements(user: UserJwtPayload, filterPaginationDto?: FilterPaginationDto)
Parameters :
Name Type Optional
user UserJwtPayload No
filterPaginationDto FilterPaginationDto Yes

Properties

Private Readonly fundService
Type : FundService
Decorators :
@Inject(FundService)
Private Readonly userService
Type : UserService
Decorators :
@Inject(UserService)
import { ApolloQueryResult } from "@apollo/client";
import {
  Inject,
  Injectable,
  ServiceUnavailableException,
} from "@nestjs/common";
import {
  decodeBytes32String,
  encodeBytes32String,
  formatEther,
  formatUnits,
} from "ethers";
import {
  EndUserRoles,
  IGetAllSettledOrdersQuery,
  IGetLatestNavsByInstrumentsIdsQuery,
  INavQuery,
  UserJwtPayload,
} from "src/common/interfaces";
import {
  apolloClient,
  getAllAdminSettledOrdersQuery,
  getAllDealerSettledOrdersQuery,
  getAllFundAdminSettledOrdersQuery,
  getAllInvestorSettledOrdersQuery,
  getLatestNavsByInstrumentsIdsQuery,
} from "src/common/provider";
import {
  FundWithInstrument,
  InstrumentSubgraphAndDB,
} from "src/security/fund.interface";
import { FundService } from "src/security/fund.service";
import { User } from "src/user/schemas/user.schema";
import { UserService } from "src/user/user.service";
import { IPastSettlement, ICurrentHolding } from "./portfolio.interface";
import { DEFAULT_USER_ADDRESS } from "src/common/constants";
import { FilterPaginationDto } from "src/shared/dto/filter-pagination.dto";

@Injectable()
export class PortfolioService {
  @Inject(FundService)
  private readonly fundService: FundService;
  @Inject(UserService)
  private readonly userService: UserService;

  public async getPasteSettlements(
    user: UserJwtPayload,
    filterPaginationDto?: FilterPaginationDto,
  ): Promise<IPastSettlement[]> {
    if (
      user.role === EndUserRoles.dealer ||
      user.role === EndUserRoles.investor
    ) {
      return await this.getUserPastSettlements(user, filterPaginationDto);
    }
    return await this.getAdminPastSettlements(user, filterPaginationDto);
  }
  private async getUserPastSettlements(
    user: UserJwtPayload,
    filterPaginationDto?: FilterPaginationDto,
  ): Promise<IPastSettlement[]> {
    try {
      const userDb = await this.userService.findUserByProperty({
        email: user.email,
        ...filterPaginationDto,
      });
      const fundsId = userDb.subscribedFunds.map((subscribedFund) =>
        subscribedFund.toString(),
      );
      const { limit, skip, orderDirection } = filterPaginationDto ?? {};
      const [settledOrdersGraph, funds]: [
        ApolloQueryResult<IGetAllSettledOrdersQuery>,
        FundWithInstrument[],
      ] = await Promise.all([
        apolloClient.query({
          query:
            user.role === EndUserRoles.dealer
              ? getAllDealerSettledOrdersQuery
              : getAllInvestorSettledOrdersQuery,
          variables: {
            userId: encodeBytes32String(userDb._id.toString()),
            fundsId: fundsId.map((fundId) =>
              encodeBytes32String(fundId.toString()),
            ),
            first: limit,
            skip,
            orderDirection,
          },
        }),
        this.fundService.findByIdsAndInstrument({ ids: fundsId }),
      ]);

      return this.generatePasteSettlementsInfo(settledOrdersGraph, funds);
    } catch (error) {
      console.error(error);
      throw new ServiceUnavailableException(error);
    }
  }

  private async getAdminPastSettlements(
    user: UserJwtPayload,
    filterPaginationDto?: FilterPaginationDto,
  ): Promise<IPastSettlement[]> {
    try {
      const { limit, skip, orderDirection } = filterPaginationDto ?? {};

      if (user.role === "admin") {
        const [settledOrdersGraph, funds]: [
          ApolloQueryResult<IGetAllSettledOrdersQuery>,
          FundWithInstrument[],
        ] = await Promise.all([
          apolloClient.query({
            query: getAllAdminSettledOrdersQuery,
            variables: { first: limit, skip: skip * limit, orderDirection },
          }),
          this.fundService.findFundsByFilter(filterPaginationDto) as Promise<
            FundWithInstrument[]
          >,
        ]);
        return this.generatePasteSettlementsInfo(settledOrdersGraph, funds);
      }
      const fundAdmin = await this.userService.findUserByProperty({
        email: user.email,
      });
      const fundsId = fundAdmin.subscribedFunds.map((subscribedFund) =>
        subscribedFund.toString(),
      );

      const [settledOrdersGraph, funds]: [
        ApolloQueryResult<IGetAllSettledOrdersQuery>,
        FundWithInstrument[],
      ] = await Promise.all([
        apolloClient.query({
          query: getAllFundAdminSettledOrdersQuery,
          variables: {
            fundsId: fundsId.map((fundId) =>
              encodeBytes32String(fundId.toString()),
            ),
            first: limit,
            skip,
            orderDirection,
          },
        }),
        this.fundService.findByIdsAndInstrument({
          ids: fundsId,
        }),
      ]);
      return await this.generatePasteSettlementsInfo(settledOrdersGraph, funds);
    } catch (error) {
      console.error(error);
      throw new ServiceUnavailableException(error);
    }
  }

  private async generatePasteSettlementsInfo(
    settledOrdersGraph: ApolloQueryResult<IGetAllSettledOrdersQuery>,
    funds: FundWithInstrument[],
  ): Promise<IPastSettlement[]> {
    try {
      const fundByEncodedId: Record<string, FundWithInstrument> = {};
      const instrumentByEncodedId: Record<string, InstrumentSubgraphAndDB> = {};
      const investorById: Record<string, User> = {};
      const investorIds: string[] = [];
      settledOrdersGraph.data.orders.forEach((order) => {
        investorIds.push(decodeBytes32String(order.onBehalf.id));
      });
      const investors = await this.userService.findByIds(investorIds);
      investors.forEach((investor) => {
        investorById[investor._id.toString()] = investor;
      });
      funds.forEach((fund) => {
        fundByEncodedId[encodeBytes32String(fund._id.toString())] = fund;
        fund.instruments.forEach((instrument) => {
          instrumentByEncodedId[instrument.subgraphInstrument.id] = instrument;
        });
      });

      const settledOrders: IPastSettlement[] = [];
      settledOrdersGraph.data.orders.forEach((order) => {
        const decodedInvestorId = decodeBytes32String(order.onBehalf.id);
        const settlementInfo = {
          name: investorById[decodedInvestorId].name,
          address:
            investorById[decodedInvestorId].address || DEFAULT_USER_ADDRESS,
          investorId: decodedInvestorId,
          fund: fundByEncodedId[order.orderBook.fund.id].title,
          fundId: fundByEncodedId[order.orderBook.fund.id]._id,
          shareClass:
            instrumentByEncodedId[order.orderBook.instrument.id].dbInstrument
              .name,
          shareId:
            instrumentByEncodedId[
              order.orderBook.instrument.id
            ].dbInstrument._id.toString(),
          settlementType: order.orderBook.type,
        };
        if (order.type === "Subscription") {
          settledOrders.push({
            ...settlementInfo,
            shares: `${
              Number(formatUnits(order.amount, 6)) *
              Number(formatUnits(order.settledOnNavPerShare, 6))
            }`,
            navPerShare: formatUnits(order.settledOnNavPerShare, 6),
            settlementDate: order.settleTime,
            amountInFiat: formatUnits(order.amount, 6),
            settlementHash: order.settledTxHash,
          });
        } else {
          settledOrders.push({
            ...settlementInfo,
            shares: formatEther(order.amount),
            settlementDate: order.settleTime,
            navPerShare: formatUnits(order.settledOnNavPerShare, 6),
            amountInFiat: `${
              Number(formatEther(order.amount)) *
              Number(formatUnits(order.settledOnNavPerShare, 6))
            }`,
            settlementHash: order.settledTxHash,
          });
        }
      });

      return settledOrders;
    } catch (error) {
      console.error(error);
      throw new ServiceUnavailableException(error);
    }
  }

  public async getCurrentSettlements(
    user: UserJwtPayload,
    filterPaginationDto?: FilterPaginationDto,
  ): Promise<ICurrentHolding[]> {
    const userPasteSettlements = await this.getPasteSettlements(
      user,
      filterPaginationDto,
    );

    const instrumentsIds = Array.from(
      new Set(
        userPasteSettlements.map((settlement) =>
          encodeBytes32String(settlement.shareId),
        ),
      ),
    );

    const latestNavsSubgraph: ApolloQueryResult<IGetLatestNavsByInstrumentsIdsQuery> =
      await apolloClient.query({
        query: getLatestNavsByInstrumentsIdsQuery,
        variables: {
          ids: instrumentsIds,
        },
      });

    const navByInstrumentId: Record<string, Partial<INavQuery>> = {};
    latestNavsSubgraph.data.instruments.forEach((instrument) => {
      navByInstrumentId[decodeBytes32String(instrument.id)] = {
        ...instrument.navs[0],
        navPerShare: formatUnits(instrument.navs[0].navPerShare, 6),
      };
    });

    const groupSettlementsInvestorFundShareId: Record<
      string, // investorId-fundId-shareId
      ICurrentHolding
    > = {};

    userPasteSettlements.forEach((settlement) => {
      const settlementId = `${settlement.investorId}-${settlement.fundId}-${settlement.shareId}`;
      const groupedSettlements =
        groupSettlementsInvestorFundShareId[settlementId];
      const currentShares =
        settlement.settlementType === "Redemption"
          ? Number(settlement.shares) * -1
          : Number(settlement.shares);
      if (groupedSettlements) {
        const sharesHeld = groupedSettlements.sharesHeld + currentShares;
        const currentValue =
          groupedSettlements.currentValue +
          currentShares *
            Number(navByInstrumentId[settlement.shareId].navPerShare);

        groupSettlementsInvestorFundShareId[settlementId] = {
          ...groupedSettlements,
          sharesHeld,
          currentValue,
          settlements: [...groupedSettlements.settlements, settlement],
        };
      } else {
        const { name, address, investorId, fund, shareClass, fundId, shareId } =
          settlement;
        groupSettlementsInvestorFundShareId[settlementId] = {
          name,
          address,
          investorId,
          fund,
          shareClass,
          fundId,
          shareId,
          sharesHeld: currentShares,
          currentValue:
            currentShares *
            Number(navByInstrumentId[settlement.shareId].navPerShare),
          auditedNav: Number(navByInstrumentId[settlement.shareId].navPerShare),
          navDate: navByInstrumentId[settlement.shareId].timestamp,
          settlements: [settlement],
        };
      }
    });
    return Object.values(groupSettlementsInvestorFundShareId).flat();
  }
}

results matching ""

    No results matching ""