dal/models.js

/**
 * Creates models for transer of data and act as an abstraction layer for Business Logic
 * @module Models
 */

const { ReplyError } = require('redis');
const backend = require('./backend')

const MAGIC_RETURNS_PRICE = 100

/**
 * The Trade class is used as a container for handing Trade related Operations
 * @export @final
 */
exports.Trade = class {
    /**
     * @param  {string} user user who did this trade
     * @param  {TradeType} type Type of the trade ('B'|'S')
     * @param  {string} symbol The ticker symol for this trade
     * @param  {number} price The average amount per share for the trade
     * @param  {number} shares The number of shares traded
     */
    constructor(user,type,symbol,price,shares){
        this.user = user
        this.type = type;
        this.symbol = symbol
        this.price = price,
        this.shares = shares
        /** Stores amount in the Fixed point representation */
        this.amount = price*shares*100
    }
    /**
     * The order method executes an order for this constructor, it internally 
     * uses 2 separate calls depending on wether the trade was a `BUY` or `SELL`
     */
    async order() {
        let r = null
        if (this.type == 'B')
            r = await backend.buySecurity(this.user,this.symbol,this.amount,this.shares);
        else
            r = await backend.sellSecurity(this.user,this.symbol,this.amount,this.shares);
        
        r.totalCost = (r.totalCost/100).toFixed(2)
        r.averagePrice = (r.averagePrice/100).toFixed(2)
        return r
    }

    /**
     * Given a TradeId it gives all the fields of trade to the backend, 
     * for this method the fields are optional and supports any combination 
     * of missing fields, it's strongly consistent and atomic and will raise
     * errors on bad updates.
     * @param {number} tradeId 
     */
    async update(tradeId) {
        let r = await backend.updateSecurity(tradeId,this.user,this.symbol,this.price * 100 || this.price,this.shares,this.type)
        r.totalCost = (r.totalCost/100).toFixed(2)
        r.averagePrice = (r.averagePrice/100).toFixed(2)
        return r
    }

    /**
     * Delete's a trade given it's concistent, it uses the same script of update
     * and is anologus to updating the trades value to (BUY,0 stocks for 0 price)
     * @param {number} tradeId 
     */
    async delete(tradeId) {
        let r = await backend.updateSecurity(tradeId,this.user,this.symbol,this.price * 100 || this.price,this.shares,this.type,1)
        r.totalCost = (r.totalCost/100).toFixed(2)
        r.averagePrice = (r.averagePrice/100).toFixed(2)
        return r
    }
}

/**
 * This class is used to manage the fetching of symbol wise trades for a user
 * @export @final
 */
exports.TradeList = class {
    /**
     * @param  {string} user
     */
     constructor(user){
        this.user = user
    }

    /** Gets the list of for the current user and groups them by symbol */
    async get() {
        let trades = await backend.getAllTrades(this.user)
        let tradesMap = {}
        trades.forEach(trade => {
            if(!tradesMap[trade.symbol]) {
                tradesMap[trade.symbol] = []
            }
            trade.tradeType = trade.tradeType === 'B' ? "BUY" : "SELL"
            trade.price = (trade.amount/trade.quantity/100).toFixed(2)
            delete trade.amount
            tradesMap[trade.symbol].push(trade)
        })
        
        return Object.entries(tradesMap).map(([k,v]) => {
            return {
                symbol: k,
                trades: v
            }
        })
    }
}


/**
 * It fetches the portfolio of a user and returns 
 * @export @final
 */
exports.Portfolio = class {

    /**
     * @param  {string} user 
     */
    constructor(user){
        this.user = user
    }

    /** Gets the portfolio for all the symbols traded by the user */
    async get() {
        let items = await backend.getPortfolio(this.user)
        items.forEach(trade => {
            trade.price = (trade.price/100).toFixed(2)
            trade.amount = (trade.amount/100).toFixed(2)
        })

        items.sort((a,b) => {
            if (a.tickerSymbol < b.tickerSymbol)
                return -1
            else if (a.tickerSymbol > b.tickerSymbol)
                return 1
            return 0
        })
        return items
    }

    /** Uses the portfolio data and calculates the cumulative returns over all the stocks */
    async getReturns() {
        let portfolioData = await this.get()
        let returns = 0
        portfolioData.forEach(item => {
            returns += (MAGIC_RETURNS_PRICE - parseFloat(item.price)) * item.shares
        })
        returns = returns.toFixed(2)
        return {returns}
    }
}