"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BorrowerSummaryService = void 0;
const mongoose_1 = __importDefault(require("mongoose"));
const dayjs_1 = __importDefault(require("dayjs"));
const decimal_js_1 = __importDefault(require("decimal.js"));
const LoanTransaction_model_1 = require("../models/LoanTransaction.model");
const date_helper_1 = require("../helpers/date.helper");
const numbers_helper_1 = require("../helpers/numbers.helper");
const BorrowerCompliance_model_1 = require("../models/BorrowerCompliance.model");
const ComplianceItem_model_1 = require("../models/ComplianceItem.model");
const bbcSheets_db_1 = require("../db/bbcSheets.db");
const collaterals_enum_1 = require("../enums/collaterals.enum");
const loan_types_enum_1 = require("../enums/loan-types.enum");
const receivables_db_1 = require("../db/receivables.db");
const bbcDates_db_1 = require("../db/bbcDates.db");
const BorrowerSummary_model_1 = require("../models/BorrowerSummary.model");
const inventories_1 = require("../db/inventories");
class BorrowerSummaryService {
    availabilityService;
    borrowersDB;
    borrowerService;
    inventoryService;
    loanChargesService;
    loanTransactionsService;
    reportsService;
    constructor(availabilityService, borrowersDB, borrowerService, inventoryService, loanChargesService, loanTransactionsService, reportsService) {
        this.availabilityService = availabilityService;
        this.borrowersDB = borrowersDB;
        this.borrowerService = borrowerService;
        this.inventoryService = inventoryService;
        this.loanChargesService = loanChargesService;
        this.loanTransactionsService = loanTransactionsService;
        this.reportsService = reportsService;
    }
    async getBorrowerSummary(borrowerId) {
        return BorrowerSummary_model_1.BorrowerSummary.findOne({ borrowerId });
    }
    async getHighLevelTable(borrowerId, useSignedBBC) {
        const extraInfo = await this.borrowerService.getExtraInfo(borrowerId);
        const lastBBCDate = useSignedBBC ? extraInfo.lastSignedBBCDate : extraInfo.lastBBCDate;
        const loanBalances = {
            [loan_types_enum_1.ELoanTypes.REVOLVER]: 0,
            [loan_types_enum_1.ELoanTypes.TERM]: 0,
        };
        const products = await this.loanChargesService.getLoanProducts(borrowerId);
        await Promise.all(products.map(async (p) => {
            const lastTransaction = await this.loanTransactionsService.getLastTransactionForDate(p._id.toString(), (0, dayjs_1.default)(lastBBCDate).utcOffset(0).endOf('day').toDate());
            if (lastTransaction) {
                loanBalances[loan_types_enum_1.ELoanTypes[p.type]] = new decimal_js_1.default(loanBalances[loan_types_enum_1.ELoanTypes[p.type]]).add(lastTransaction.balance).toNumber();
            }
        }));
        if (!useSignedBBC) {
            return {
                title: 'Loan summary - Current',
                data: [
                    ['Loan Balance - REVOLVER', (0, numbers_helper_1.formatNumbers)(loanBalances[loan_types_enum_1.ELoanTypes.REVOLVER])],
                    ['Loan Balance - TERM', (0, numbers_helper_1.formatNumbers)(loanBalances[loan_types_enum_1.ELoanTypes.TERM])],
                ],
            };
        }
        const allProductsData = await this.reportsService.generateBankPortfolioSummary([borrowerId], lastBBCDate, {
            useSignedBBC,
            useStoredBalance: false,
        });
        const collateralRevolver = allProductsData.reduce((acc, product) => {
            return new decimal_js_1.default(acc).add(product.totalCollateral).toNumber();
        }, 0);
        const collateralTerm = allProductsData.reduce((acc, product) => {
            return new decimal_js_1.default(acc).add(product.equipmentCollateral).toNumber();
        }, 0);
        return {
            title: `Summary of Last Approved BBC - ${(0, dayjs_1.default)(lastBBCDate).format(date_helper_1.defaultDateFormat)}`,
            data: [
                ['Loan Balance - REVOLVER', (0, numbers_helper_1.formatNumbers)(loanBalances[loan_types_enum_1.ELoanTypes.REVOLVER])],
                ['Loan Balance - TERM', (0, numbers_helper_1.formatNumbers)(loanBalances[loan_types_enum_1.ELoanTypes.TERM])],
                ['Collateral - REVOLVER', (0, numbers_helper_1.formatNumbers)(collateralRevolver)],
                ['Collateral - TERM', (0, numbers_helper_1.formatNumbers)(collateralTerm)],
                ['Available to borrow', (0, numbers_helper_1.formatNumbers)(new decimal_js_1.default(collateralRevolver).sub(loanBalances[loan_types_enum_1.ELoanTypes.REVOLVER]).toNumber())],
            ],
        };
    }
    async getComplianceItemsTable(borrowerId) {
        const complianceBorrower = await BorrowerCompliance_model_1.BorrowerCompliance
            .findOne({ borrower: borrowerId })
            .populate('items.item')
            .lean();
        if (!complianceBorrower) {
            return [];
        }
        const outstandingItems = [];
        const newItems = [];
        complianceBorrower.items.map((item) => {
            item.instances.forEach((i) => {
                if (i.status === ComplianceItem_model_1.EComplianceItemStatus.NEW) {
                    newItems.push([
                        `${item.item.name} (${(0, dayjs_1.default)(i.nextDate).format('MMM.D, YYYY')})`,
                        (0, dayjs_1.default)(i.nextDate).format(date_helper_1.defaultDateFormat),
                    ]);
                }
                else if (i.status === ComplianceItem_model_1.EComplianceItemStatus.REJECTED) {
                    outstandingItems.push([
                        `${item.item.name} (${(0, dayjs_1.default)(i.nextDate).format('MMM.D, YYYY')})`,
                        (0, dayjs_1.default)().diff(i.nextDate, 'day'),
                    ]);
                }
            });
        });
        return [...outstandingItems, ...newItems];
    }
    async getTop5SKUsData(borrowerId) {
        const lastBBC = await (0, bbcDates_db_1.getLatestSignedBBCDateDoc)(borrowerId);
        if (lastBBC) {
            const bbcGroups = await (0, bbcSheets_db_1.getBBCSheetsByTypeGroupedByBBC)([lastBBC._id.toString()], collaterals_enum_1.ECollaterals.INVENTORY);
            const sheetIds = [];
            bbcGroups.forEach((bbcGroup) => {
                bbcGroup.sheets.forEach((sheet) => {
                    sheetIds.push(sheet._id.toString());
                });
            });
            return await (0, inventories_1.getInventoryItemsSKUAndValue)(sheetIds);
        }
        return [];
    }
    async getTop5SKUs(borrowerId) {
        const lastBBC = await (0, bbcDates_db_1.getLatestSignedBBCDateDoc)(borrowerId);
        if (!lastBBC) {
            return [['no signed BBC', '']];
        }
        const SKUs = await this.getTop5SKUsData(borrowerId);
        if (SKUs.length === 0) {
            return [['MANUALLY ENTERED', '']];
        }
        return SKUs.slice(0, 5).map((SKU) => [SKU._id, (0, numbers_helper_1.formatNumbers)(SKU.totalAmount)]);
    }
    async getInventoryTurn(borrowerId) {
        const startPeriod = (0, dayjs_1.default)().utcOffset(0).startOf('day').subtract(150, 'day').toDate().toISOString();
        const endPeriod = (0, dayjs_1.default)().utcOffset(0).endOf('day').toDate().toISOString();
        const allSkuValues = await this.inventoryService.getInventoryTurn(borrowerId, startPeriod, endPeriod);
        const values = allSkuValues.tableByUnit.length > 0
            ? allSkuValues.tableByUnit.sort((a, b) => a.current < b.current ? 1 : -1).slice(0, 5)
            : [];
        return values.map((value) => [value.sku, (0, numbers_helper_1.formatNumbers)(value.current)]);
    }
    async getTop5CustomersConcentrationData(borrowerId) {
        const bbc = await (0, bbcDates_db_1.getBBCDatesByBorrower)(borrowerId, -1);
        if (bbc.length > 0) {
            const lastBBC = bbc[0];
            const bbcGroups = await (0, bbcSheets_db_1.getBBCSheetsByTypeGroupedByBBC)([lastBBC._id.toString()], collaterals_enum_1.ECollaterals.RECEIVABLE);
            const sheetIds = [];
            bbcGroups.forEach((bbcGroup) => {
                bbcGroup.sheets.forEach((sheet) => {
                    sheetIds.push(sheet._id.toString());
                });
            });
            return await (0, receivables_db_1.getReceivableItemsCustomerAndValue)(sheetIds);
        }
        return [];
    }
    async getTop5CustomersConcentration(borrowerId) {
        const bbc = await (0, bbcDates_db_1.getBBCDatesByBorrower)(borrowerId, -1);
        if (!bbc) {
            return [['no BBC', '']];
        }
        const customers = await this.getTop5CustomersConcentrationData(borrowerId);
        if (customers.length === 0) {
            return [['MANUALLY ENTERED', '']];
        }
        const totalAmount = customers.reduce((acc, customer) => new decimal_js_1.default(acc).add(customer.totalAmount).toNumber(), 0);
        return customers
            .slice(0, 5)
            .map((customer) => {
            return [customer._id, `${(0, numbers_helper_1.formatNumbers)(new decimal_js_1.default(customer.totalAmount).div(totalAmount).mul(100).toDP(2).toNumber())}%`];
        });
    }
    async getAgedCustomers(borrowerId) {
        const lastSignedBBC = await (0, bbcDates_db_1.getLatestSignedBBCDateDoc)(borrowerId);
        if (lastSignedBBC) {
            const { customerItems } = await this.availabilityService.getAvailabilityItemsWithFilter(lastSignedBBC._id.toString(), 'crossAgeDeduction');
            return customerItems
                .sort((a, b) => a.customerAmount < b.customerAmount ? 1 : -1)
                .map((c) => [c.customerTitle, (0, numbers_helper_1.formatNumbers)(c.customerAmount)]);
        }
        return [['no BBC', '']];
    }
    async getLast5TransactionsTable(borrowerId) {
        const products = await this.loanChargesService.getLoanProducts(borrowerId);
        const startDate = (0, dayjs_1.default)().utcOffset(0).subtract(5, 'day').startOf('day').toDate();
        const transactions = await LoanTransaction_model_1.LoanTransaction.aggregate([
            {
                '$match': {
                    'productId': { '$in': products.map((p) => p._id) },
                    'date': { $gte: startDate },
                },
            }, {
                '$sort': {
                    'date': -1,
                },
            }, {
                '$limit': 5,
            },
        ]);
        return transactions.map((t) => {
            return [
                (0, dayjs_1.default)(t.date).format(date_helper_1.defaultDateFormat),
                t.reference,
                (0, numbers_helper_1.formatNumbers)(t.amount),
            ];
        }, []);
    }
    async getChartData(borrowerId) {
        const products = await this.loanChargesService.getLoanProducts(borrowerId);
        const revolverProduct = products.find((product) => product.type === loan_types_enum_1.ELoanTypes.REVOLVER && (0, date_helper_1.isProductActive)(product.startDate, product.deactivationDate, new Date()));
        const chartData = [];
        if (revolverProduct) {
            const lastSignedBBC = await (0, bbcDates_db_1.getLatestSignedBBCDateDoc)(borrowerId);
            if (lastSignedBBC) {
                const periodEnd = lastSignedBBC.bbcDate;
                const periodStart = (0, dayjs_1.default)(lastSignedBBC.bbcDate).utcOffset(0).subtract(6, 'month').toDate();
                const BBCs = await (0, bbcDates_db_1.getBBCDatesForPeriod)(borrowerId, periodStart, periodEnd);
                await Promise.all(BBCs.map(async (bbc) => {
                    if (bbc) {
                        const lastTransaction = await this.loanTransactionsService.getLastTransactionForDate(revolverProduct._id.toString(), (0, dayjs_1.default)(bbc.bbcDate).utcOffset(0).endOf('day').toDate());
                        const allProductsData = await this.reportsService.generateBankPortfolioSummary([borrowerId], bbc.bbcDate, {
                            useSignedBBC: false,
                            useStoredBalance: false,
                        });
                        const revolverData = allProductsData.find((p) => p.productType === loan_types_enum_1.ELoanTypes.REVOLVER);
                        chartData.push({
                            bbc: (0, date_helper_1.convertDateToFormat)(bbc.bbcDate),
                            revolverCollateralTotal: revolverData ? revolverData.totalCollateral : 0,
                            revolverLoanBalance: lastTransaction ? lastTransaction.balance : 0,
                        });
                    }
                }));
            }
        }
        return {
            data: chartData.sort((a, b) => new Date(a.bbc).valueOf() - new Date(b.bbc).valueOf()),
            series: [
                {
                    type: 'bar',
                    xKey: 'bbc',
                    yKey: 'revolverCollateralTotal',
                    yName: 'Revolver Collateral Total',
                },
                {
                    type: 'bar',
                    xKey: 'bbc',
                    yKey: 'revolverLoanBalance',
                    yName: 'Revolver Loan Balance',
                },
            ],
        };
    }
    async updateBorrowerSummary(borrowerId) {
        const newSummary = {
            borrowerId: new mongoose_1.default.Types.ObjectId(borrowerId),
            lastCalculatedAt: new Date(),
            tableData: {
                [BorrowerSummary_model_1.ETableNames.HIGH_LEVEL]: await this.getHighLevelTable(borrowerId, false),
                [BorrowerSummary_model_1.ETableNames.HIGH_LEVEL_APPROVED]: await this.getHighLevelTable(borrowerId, true),
                [BorrowerSummary_model_1.ETableNames.COMPLIANCE_ITEMS]: {
                    title: 'Compliance Items',
                    data: await this.getComplianceItemsTable(borrowerId),
                },
                [BorrowerSummary_model_1.ETableNames.TOP_5_SKUS]: {
                    title: 'Top 5 SKUs',
                    data: await this.getTop5SKUs(borrowerId),
                },
                [BorrowerSummary_model_1.ETableNames.LAST_5_DAYS_TRANSACTIONS]: {
                    title: 'Last 5 days transactions',
                    data: await this.getLast5TransactionsTable(borrowerId),
                },
                [BorrowerSummary_model_1.ETableNames.INVENTORY_TURN]: {
                    title: 'Inventory turn (last 90 days)',
                    data: await this.getInventoryTurn(borrowerId),
                },
                [BorrowerSummary_model_1.ETableNames.TOP_5_CUSTOMERS_CONCENTRATION]: {
                    title: 'Top 5 Customers Concentration',
                    data: await this.getTop5CustomersConcentration(borrowerId),
                },
                [BorrowerSummary_model_1.ETableNames.AGED_OUT_CUSTOMERS]: {
                    title: 'Aged out customers',
                    data: await this.getAgedCustomers(borrowerId),
                },
            },
            chartData: {
                [BorrowerSummary_model_1.EChartNames.LAST_6_MONTH]: await this.getChartData(borrowerId),
            },
        };
        await BorrowerSummary_model_1.BorrowerSummary.findOneAndUpdate({ borrowerId }, newSummary, { upsert: true });
        console.log(`calculation finished - ${borrowerId}`);
    }
    async recalculateAllActive() {
        const borrowers = await this.borrowersDB.getActiveBorrowers();
        let i = 1;
        for (const borrower of borrowers) {
            try {
                await this.updateBorrowerSummary(borrower._id.toString());
            }
            catch (e) {
                console.error(e);
            }
            i = i + 1;
        }
    }
}
exports.BorrowerSummaryService = BorrowerSummaryService;
