"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AvailabilityService = exports.START_PAGINATORS = void 0;
const dayjs_1 = __importDefault(require("dayjs"));
const lodash_1 = __importDefault(require("lodash"));
const mongoose_1 = __importDefault(require("mongoose"));
const decimal_js_1 = __importDefault(require("decimal.js"));
const mongo_helper_1 = require("../helpers/mongo.helper");
const ReceivableAvailability_model_1 = require("../models/ReceivableAvailability.model");
const numbers_helper_1 = require("../helpers/numbers.helper");
const AccountPayableItem_model_1 = require("../models/AccountPayableItem.model");
const collaterals_enum_1 = require("../enums/collaterals.enum");
const ReceivableAvailabilityItem_model_1 = require("../models/ReceivableAvailabilityItem.model");
const collaterals_db_1 = require("../db/collaterals.db");
const _models_1 = require("../models/_models");
const receivables_db_1 = require("../db/receivables.db");
const bbcSheets_db_1 = require("../db/bbcSheets.db");
const CustomerGroup_model_1 = require("../models/CustomerGroup.model");
const partially_paid_restated_1 = require("../queries/receivable/partially-paid-restated");
const MappedGroup_model_1 = __importDefault(require("../models/MappedGroup.model"));
const CustomerAPGroup_model_1 = __importDefault(require("../models/CustomerAPGroup.model"));
const reserve_db_1 = require("../db/reserve.db");
const inventory_availability_results_enum_1 = require("../enums/inventory-availability-results.enum");
exports.START_PAGINATORS = {
    [inventory_availability_results_enum_1.EInventoryAvailabilityResults.UNIQ_ITEMS]: { pageSize: 20, pageIndex: 0 },
    [inventory_availability_results_enum_1.EInventoryAvailabilityResults.NON_UNIQ_ITEMS]: { pageSize: 20, pageIndex: 0 },
    [inventory_availability_results_enum_1.EInventoryAvailabilityResults.NOT_MATCHED_ITEMS]: { pageSize: 20, pageIndex: 0 },
};
class AvailabilityService {
    collateralAdjustmentsService;
    collateralsService;
    equipmentService;
    inventoryAvailabilityService;
    loanStatementService;
    loanTransactionsService;
    manualEntryService;
    reserveService;
    signsService;
    constructor(collateralAdjustmentsService, collateralsService, equipmentService, inventoryAvailabilityService, loanStatementService, loanTransactionsService, manualEntryService, reserveService, signsService) {
        this.collateralAdjustmentsService = collateralAdjustmentsService;
        this.collateralsService = collateralsService;
        this.equipmentService = equipmentService;
        this.inventoryAvailabilityService = inventoryAvailabilityService;
        this.loanStatementService = loanStatementService;
        this.loanTransactionsService = loanTransactionsService;
        this.manualEntryService = manualEntryService;
        this.reserveService = reserveService;
        this.signsService = signsService;
    }
    async recombineGroups(calculationsSettings) {
        const mappedSettings = await Promise.all(Object.values(ReceivableAvailability_model_1.ELimitSettings).map(async (settingsKey) => {
            const customerGroupMapped = await Promise.all(calculationsSettings[settingsKey].map(async (customerGroup) => {
                const idIsValid = (0, mongo_helper_1.isValidId)(customerGroup.customer);
                if (idIsValid) {
                    const foundCustomerGroup = await CustomerGroup_model_1.CustomerGroupModel.findById(customerGroup.customer);
                    if (foundCustomerGroup) {
                        return foundCustomerGroup.items.reduce((innerAcc, item) => ({
                            ...innerAcc,
                            [item]: customerGroup.limit,
                        }), {});
                    }
                    return { [customerGroup.customer]: customerGroup.limit };
                }
                return null;
            }));
            const filtered = customerGroupMapped.filter((group) => !!group);
            return filtered.reduce((lastAcc, item) => ({ ...lastAcc, ...item }), {});
        }));
        const groupedSettings = Object.values(ReceivableAvailability_model_1.ELimitSettings).reduce((acc, settings, index) => ({
            ...acc,
            [settings]: mappedSettings[index],
        }), {});
        Object.values(ReceivableAvailability_model_1.ELimitSettings).forEach((settingsKey) => {
            calculationsSettings[settingsKey].forEach((customer) => {
                const idIsValid = (0, mongo_helper_1.isValidId)(customer.customer);
                if (!idIsValid) {
                    groupedSettings[settingsKey][customer.customer] = customer.limit;
                }
            });
        });
        return groupedSettings;
    }
    async calculateAvailability(bbcDateId, paginatorOptions, user) {
        const calculationsSettings = await this.getCalculationSettings(bbcDateId);
        const allReceivables = await (0, collaterals_db_1.getCollateralDocsByBBC)([bbcDateId], collaterals_enum_1.ECollaterals.RECEIVABLE);
        const receivables = allReceivables.pop();
        const calculation = {
            borrowerId: null,
            bbcDateId: new mongoose_1.default.Types.ObjectId(bbcDateId),
            userCalculatedId: user?.id ? user.id : null,
            calculatedAt: new Date(),
            actual: true,
            settings: calculationsSettings,
            items: [],
            summary: null,
            totalItems: 0,
            useManualInputs: false,
        };
        if (!receivables) {
            return calculation;
        }
        calculation.borrowerId = new mongoose_1.default.Types.ObjectId(receivables.borrowerId.toString());
        const allCustomerGroups = await CustomerGroup_model_1.CustomerGroupModel.find({ borrowerId: receivables.borrowerId }).lean();
        const enrichedEnrichedSettings = await this.recombineGroups(calculationsSettings);
        const getBucket = (bbcDate, item, permittedDays, isExcluded = false) => {
            const invoiceDate = (0, dayjs_1.default)(item.invoiceDate).startOf('day');
            const actualDays = (0, dayjs_1.default)(bbcDate).startOf('day').diff(invoiceDate, 'day');
            const data = {
                bucketCurrent: 0,
                bucket30: 0,
                bucket60: 0,
                permitted60: 0,
                permittedOver: 0,
                creditNotes: 0,
                actualDays,
                permittedDays,
            };
            if (isExcluded) {
                return data;
            }
            if (item.invoiceAmount < 0) {
                data.creditNotes = item.invoiceAmount;
                return data;
            }
            if (actualDays > permittedDays) {
                data.permittedOver = item.invoiceAmount;
                return data;
            }
            if (actualDays > 60 && actualDays <= permittedDays) {
                data.permitted60 = item.invoiceAmount;
                return data;
            }
            if (actualDays > 30) {
                data.bucket60 = item.invoiceAmount;
                return data;
            }
            if (actualDays >= 1) {
                data.bucket30 = item.invoiceAmount;
                return data;
            }
            data.bucketCurrent = item.invoiceAmount;
            return data;
        };
        const getExcludedCustomers = (items) => {
            const uniqueCustomers = Array.from(new Set(items.map((c) => c.customerTitle)));
            return uniqueCustomers.reduce((acc, customer) => {
                if (Object.keys(enrichedEnrichedSettings.excludedCustomers).includes(customer)) {
                    return [...acc, customer];
                }
                return acc;
            }, []);
        };
        const calculateCustomerConcentration = (items, calculationsSettings) => {
            const uniqueCustomers = Array.from(new Set(items.map((c) => c.customerTitle)));
            const uniqueCustomersWithTotal = items.reduce((acc, item) => {
                const preliminaryEligibleBalance = item.preliminaryEligibleBalance;
                const permittedOver = item.permittedOver;
                if (acc[item.customerTitle]) {
                    return {
                        ...acc,
                        [item.customerTitle]: {
                            preliminaryEligibleBalance: new decimal_js_1.default(acc[item.customerTitle].preliminaryEligibleBalance).add(preliminaryEligibleBalance).toNumber(),
                            permittedOver: new decimal_js_1.default(acc[item.customerTitle].permittedOver).add(permittedOver).toNumber(),
                        },
                    };
                }
                return {
                    ...acc,
                    [item.customerTitle]: {
                        preliminaryEligibleBalance: preliminaryEligibleBalance,
                        permittedOver: permittedOver,
                    },
                };
            }, {});
            const uniqueCustomersWithTotalGrouped = Object.entries(uniqueCustomersWithTotal).reduce((acc, [customer, totals]) => {
                if (Object.keys(enrichedEnrichedSettings.excludedCustomers).includes(customer)) {
                    return { INCLUDED: { ...acc.INCLUDED }, EXCLUDED: { ...acc.EXCLUDED, [customer]: totals } };
                }
                return { EXCLUDED: { ...acc.EXCLUDED }, INCLUDED: { ...acc.INCLUDED, [customer]: totals } };
            }, { EXCLUDED: {}, INCLUDED: {} });
            const preliminaryEligibleIncluded = Object
                .values(uniqueCustomersWithTotalGrouped.INCLUDED)
                .reduce((total, c) => {
                return new decimal_js_1.default(total).add(c.preliminaryEligibleBalance).toNumber();
            }, 0);
            const totalPermittedOver = Object
                .values(uniqueCustomersWithTotalGrouped.INCLUDED)
                .reduce((total, c) => {
                return new decimal_js_1.default(total).add(c.permittedOver).toNumber();
            }, 0);
            const totalExcluded = new decimal_js_1.default(preliminaryEligibleIncluded).add(totalPermittedOver).toNumber();
            const INCLUDED = {};
            const updatedCustomers = items.map((c) => {
                if (!excludedCustomers.includes(c.customerTitle)) {
                    INCLUDED[c.customerTitle] = {
                        concentration: new decimal_js_1.default(uniqueCustomersWithTotal[c.customerTitle].preliminaryEligibleBalance).div(preliminaryEligibleIncluded).toNumber(),
                        total: uniqueCustomersWithTotal[c.customerTitle],
                    };
                }
                const permittedConcentrationLimit = enrichedEnrichedSettings.permittedConcentration[c.customerTitle];
                const permittedCustomerConcentration = permittedConcentrationLimit ? permittedConcentrationLimit : calculationsSettings.customerConcentrationLimit;
                let customerConcentration;
                if (calculationsSettings.excludeFromTotal) {
                    customerConcentration = totalExcluded === 0
                        ? 0
                        : new decimal_js_1.default(uniqueCustomersWithTotal[c.customerTitle].preliminaryEligibleBalance).add(uniqueCustomersWithTotal[c.customerTitle].permittedOver).div(totalExcluded).toNumber();
                }
                else {
                    customerConcentration = preliminaryEligibleIncluded === 0
                        ? 0 :
                        new decimal_js_1.default(uniqueCustomersWithTotal[c.customerTitle].preliminaryEligibleBalance).div(preliminaryEligibleIncluded).toNumber();
                }
                return { ...c, customerConcentration, permittedCustomerConcentration };
            });
            const uniqueCustomerConcentration = uniqueCustomers
                .map((customer) => {
                if (excludedCustomers.includes(customer)) {
                    return null;
                }
                const foundRow = updatedCustomers.find((c) => c.customerTitle === customer);
                return {
                    customerTitle: foundRow.customerTitle,
                    customerGroup: foundRow.customerGroup,
                    concentration: foundRow.customerConcentration,
                };
            })
                .filter((customer) => !!customer)
                .filter((customer) => !!customer.customerGroup);
            const groupsConcentration = uniqueCustomerConcentration.reduce((groupAcc, group) => {
                return { ...groupAcc, [group.customerGroup]: (groupAcc[group.customerGroup] ?? 0) + group.concentration };
            }, {});
            updatedCustomers.forEach((customer) => {
                const groupConcentration = groupsConcentration[customer.customerGroup];
                if (groupConcentration) {
                    customer.customerConcentration = groupConcentration;
                }
                if (customer.excludedCustomer) {
                    customer.customerConcentration = 0;
                }
                customer.deductionPercent = customer.customerConcentration === 0
                    ? 0
                    : Math.min(0, customer.permittedCustomerConcentration - customer.customerConcentration) / customer.customerConcentration;
                const deduction = (0, numbers_helper_1.roundToXDigits)(Math.abs(customer.grossAR * customer.deductionPercent));
                customer.deduction = Math.min(deduction, customer.preliminaryEligibleBalance);
                customer.balanceAfterDeduction = customer.preliminaryEligibleBalance - customer.deduction;
            });
            return { updatedCustomers, INCLUDED };
        };
        const groupData = (items, groupedBy = 'crossAge', byGroup = true) => {
            const uniqueCustomers = Array.from(new Set(items.map((c) => byGroup ? c.customer : c.customerTitle)));
            return uniqueCustomers.reduce((acc, customer) => {
                const totals = items.reduce((innerAcc, c) => {
                    if (c[byGroup ? 'customer' : 'customerTitle'] === customer) {
                        const grossAR = (0, numbers_helper_1.roundToXDigits)(innerAcc.grossAR + c.grossAR);
                        const permittedOver = (0, numbers_helper_1.roundToXDigits)(innerAcc.permittedOver + c.permittedOver);
                        const crossAge = grossAR === 0 ? 0 : permittedOver / grossAR;
                        const balanceAfterCrossAge = (0, numbers_helper_1.roundToXDigits)(innerAcc.balanceAfterCrossAge + c.balanceAfterCrossAge);
                        const balanceAfterExclusion = (0, numbers_helper_1.roundToXDigits)(innerAcc.balanceAfterExclusion + c.balanceAfterExclusion);
                        return { grossAR, permittedOver, crossAge, balanceAfterCrossAge, balanceAfterExclusion };
                    }
                    return innerAcc;
                }, { grossAR: 0, permittedOver: 0, crossAge: 0, balanceAfterCrossAge: 0, balanceAfterExclusion: 0 });
                return { ...acc, [customer]: totals[groupedBy] };
            }, {});
        };
        const startDate = (0, dayjs_1.default)(new Date(receivables.bbcDate)).subtract(calculationsSettings.paidDays, 'day').toDate();
        const endDate = new Date(receivables.bbcDate);
        const partiallyPaidData = await (0, partially_paid_restated_1.getPartialPaid)({
            borrowerId: new mongoose_1.default.Types.ObjectId(String(receivables.borrowerId)),
            startDate,
            endDate,
        });
        const excludedCustomers = getExcludedCustomers(receivables.items);
        const allInvoices = [...receivables.items];
        let items = receivables.items.map((invoice) => {
            const permittedDaysLimit = enrichedEnrichedSettings.permittedExtendedARDays[invoice.customerTitle];
            const permittedDays = permittedDaysLimit ? permittedDaysLimit : calculationsSettings.standardARDays;
            const isExcluded = excludedCustomers.includes(invoice.customerTitle);
            const data = {
                ...getBucket(receivables.bbcDate, invoice, permittedDays, isExcluded),
                customer: invoice.customerTitle.trim(),
                invoice: invoice,
                customerTitle: invoice.customerTitle.trim(),
                invoiceId: invoice._id.toString(),
                invoiceNumber: invoice.invoiceNumber,
                invoiceDate: invoice.invoiceDate,
                invoiceAmount: invoice.invoiceAmount,
                grossAR: 0,
                preliminaryEligibleBalance: 0,
                permittedCustomerConcentration: 0,
                customerConcentration: 0,
                deductionPercent: 0,
                deduction: 0,
                balanceAfterDeduction: 0,
                partiallyPaidDifference: false,
                partiallyPaidDeduction: 0,
                balanceAfterPartially: 0,
                excludedCustomer: isExcluded,
                excludedCustomerDeduction: 0,
                balanceAfterExclusion: 0,
                customerGroupId: null,
                customerGroup: null,
                contra: 0,
                contraDeduction: 0,
                balanceAfterContra: 0,
                crossAge: 0,
                crossAgeDeduction: 0,
                balanceAfterCrossAge: 0,
                waivedDeduction: 0,
                finalResult: 0,
                insuredComponent: 0,
                uninsuredComponent: 0,
            };
            const foundCustomerGroup = allCustomerGroups.find((group) => group.items.includes(data.customerTitle));
            if (foundCustomerGroup) {
                data.customer = foundCustomerGroup.groupName;
                data.customerGroupId = foundCustomerGroup.id;
                data.customerGroup = foundCustomerGroup.groupName;
            }
            const foundInvoice = partiallyPaidData.partiallyPaidInvoices.find((pInvoice) => {
                return (pInvoice.invoiceNumber === invoice.invoiceNumber) && (pInvoice.customer === invoice.customerTitle);
            });
            if (foundInvoice) {
                data.partiallyPaidDifference = foundInvoice.difference > 0;
                data.partiallyPaidDeduction = invoice.invoiceAmount;
            }
            if (invoice.originalAmount !== 0 && invoice.originalAmount !== invoice.invoiceAmount) {
                data.partiallyPaidDifference = true;
                data.partiallyPaidDeduction = invoice.invoiceAmount;
            }
            const duplicateInvoices = allInvoices.filter((pInvoice) => {
                return (pInvoice.invoiceNumber === invoice.invoiceNumber) && (pInvoice.customerTitle === invoice.customerTitle);
            });
            if (duplicateInvoices.length > 1) {
                data.partiallyPaidDifference = true;
                data.partiallyPaidDeduction = invoice.invoiceAmount;
            }
            data.grossAR = isExcluded ? invoice.invoiceAmount : (0, numbers_helper_1.roundToXDigits)(data.bucketCurrent + data.bucket30 + data.bucket60 + data.permitted60 + data.permittedOver);
            data.preliminaryEligibleBalance = (0, numbers_helper_1.roundToXDigits)(data.bucketCurrent + data.bucket30 + data.bucket60 + data.permitted60);
            return data;
        });
        const groupedGross = groupData(items, 'grossAR', false);
        ({ updatedCustomers: items } = calculateCustomerConcentration(items, calculationsSettings));
        const bbcSheets = await (0, bbcSheets_db_1.getBBCSheetsByType)([bbcDateId], collaterals_enum_1.ECollaterals.ACCOUNT_PAYABLE);
        const apData = await AccountPayableItem_model_1.AccountPayableItemModel.aggregate([
            {
                $match: { bbcSheetId: { $in: bbcSheets.map((sheet) => sheet._id) } },
            },
            {
                $group: {
                    '_id': '$customerName',
                    'itemSum': {
                        $sum: '$amount',
                    },
                },
            },
        ]);
        const mappedGroups = await MappedGroup_model_1.default
            .find({ borrowerId: receivables.borrowerId })
            .lean();
        const replacedMappedGroups = await Promise.all(mappedGroups.map(async (group) => {
            const groupCopy = { ...group, items: [group.group1], customersAP: [group.group2] };
            switch (group.groupType) {
                case 'contras':
                    if ((0, mongo_helper_1.isValidId)(group.group1)) {
                        const foundGroup = await CustomerGroup_model_1.CustomerGroupModel.findById(group.group1).lean();
                        groupCopy.items = foundGroup ? foundGroup.items.slice() : [];
                    }
                    if ((0, mongo_helper_1.isValidId)(group.group2)) {
                        const foundGroup = await CustomerAPGroup_model_1.default.findById(group.group2).lean();
                        groupCopy.customersAP = foundGroup ? foundGroup.items.slice() : [];
                    }
            }
            return groupCopy;
        }));
        const groupedData = groupData(items);
        items.forEach((customer) => {
            /** --- Excluded customer --- */
            customer.excludedCustomerDeduction = customer.excludedCustomer ? customer.invoiceAmount : 0;
            customer.balanceAfterExclusion = customer.excludedCustomer ? 0 : customer.invoiceAmount;
            if (customer.excludedCustomer) {
                customer.grossAR = 0;
            }
            /** --- Partially paid --- */
            if (customer.balanceAfterDeduction === 0) {
                customer.partiallyPaidDeduction = 0;
            }
            customer.balanceAfterPartially = customer.partiallyPaidDifference ? 0 : customer.balanceAfterDeduction - customer.partiallyPaidDeduction;
        });
        const groupedBalanceAfterPartially = groupData(items, 'balanceAfterPartially', false);
        items.forEach((customer) => {
            /** --- Contra --- */
            customer.balanceAfterContra = customer.balanceAfterPartially;
            const mappedGroupMatches = replacedMappedGroups.filter((mappedGroup) => mappedGroup.items.includes(customer.customerTitle));
            if (mappedGroupMatches && mappedGroupMatches.length) {
                const contra = mappedGroupMatches.reduce((acc, mappedGroup) => {
                    return acc + mappedGroup.customersAP.reduce((innerAcc, customerAC) => {
                        const foundApData = apData.find((data) => data._id === customerAC);
                        if (foundApData) {
                            return foundApData.itemSum + innerAcc;
                        }
                        return innerAcc;
                    }, 0);
                }, 0);
                const contraClearDeduction = contra * calculationsSettings.contraProvision;
                const eligibleBalance = groupedBalanceAfterPartially[customer.customerTitle] !== 0 ? customer.balanceAfterPartially / groupedBalanceAfterPartially[customer.customerTitle] : 0;
                const contraDeduction = (0, numbers_helper_1.roundToXDigits)(eligibleBalance * contraClearDeduction);
                customer.contra = contra;
                customer.contraDeduction = contraDeduction >= customer.balanceAfterPartially ? customer.balanceAfterPartially : contraDeduction;
                customer.balanceAfterContra = customer.balanceAfterPartially - customer.contraDeduction;
            }
            /** --- Cross age --- */
            customer.crossAge = groupedData[customer.customer];
            customer.crossAgeDeduction = customer.crossAge > calculationsSettings.crossAgeLimit ? customer.balanceAfterContra : 0;
            customer.balanceAfterCrossAge = customer.crossAge > calculationsSettings.crossAgeLimit ? 0 : customer.balanceAfterContra;
        });
        /** --- Insurance --- */
        const groupedBalanceAfterCrossAge = groupData(items, 'balanceAfterCrossAge', false);
        items.forEach((customer) => {
            const insured = enrichedEnrichedSettings.insuredCustomers[customer.customerTitle] ?? 0;
            const totalGross = groupedGross[customer.customerTitle] ?? 0;
            if (insured > totalGross) {
                customer.waivedDeduction = calculationsSettings.waiveInsuredCustomers ? new decimal_js_1.default(customer.deduction).add(customer.crossAgeDeduction).toNumber() : 0;
            }
            customer.finalResult = new decimal_js_1.default((calculationsSettings.waiveInsuredCustomers
                ? new decimal_js_1.default(customer.balanceAfterCrossAge).add(customer.waivedDeduction).toNumber()
                : customer.balanceAfterCrossAge)).add(customer.creditNotes).toNumber();
            customer.uninsuredComponent = customer.finalResult;
            if (insured) {
                const insuredAmount = groupedBalanceAfterCrossAge[customer.customerTitle] === 0
                    ? 0
                    : new decimal_js_1.default(insured).div(groupedBalanceAfterCrossAge[customer.customerTitle]).mul(customer.finalResult).toDecimalPlaces(2).toNumber();
                const insuredAmountLimited = insuredAmount > customer.finalResult ? customer.finalResult : insuredAmount;
                const uninsuredAmount = new decimal_js_1.default(customer.finalResult).sub(insuredAmountLimited).toNumber();
                customer.insuredComponent = isNaN(insuredAmountLimited) ? 0 : insuredAmountLimited;
                customer.uninsuredComponent = isNaN(uninsuredAmount) ? 0 : uninsuredAmount;
            }
        });
        calculation.items = items;
        calculation.summary = await this.calculateAvailabilitySummary(calculation);
        calculation.totalItems = await this.collateralsService.countCollateralDocsByBBC([bbcDateId], collaterals_enum_1.ECollaterals.RECEIVABLE);
        await this.saveCalculatedAvailability(calculation);
        return await this.getAvailability(bbcDateId, paginatorOptions, user);
    }
    async getAvailability(bbcDateId, paginatorOptions, user) {
        return await this.foundOrCreateAvailability(bbcDateId, paginatorOptions, user);
    }
    async getAvailabilityItemsWithFilter(bbcDateId, itemsFilter) {
        const filtersMap = {
            creditNotesNegative: 'creditNotes',
        };
        const filterField = filtersMap[itemsFilter] ? filtersMap[itemsFilter] : itemsFilter;
        const emptyResponse = { itemsFilter: filterField, invoiceItems: [], customerItems: [] };
        const allowedFilters = [
            'creditNotes',
            'excludedCustomerDeduction',
            'permittedOver',
            'deduction',
            'partiallyPaidDeduction',
            'contraDeduction',
            'crossAgeDeduction',
            'waivedDeduction',
        ];
        if (!allowedFilters.includes(filterField)) {
            return emptyResponse;
        }
        const foundAvailability = await this.findCalculatedAvailability(bbcDateId);
        if (!foundAvailability) {
            return emptyResponse;
        }
        const items = await ReceivableAvailabilityItem_model_1.ReceivableAvailabilityItemModel.aggregate([
            {
                $match: {
                    'availabilityId': foundAvailability._id,
                    [filterField]: { $ne: 0 },
                },
            }, {
                $sort: {
                    'order': 1,
                },
            }, {
                $lookup: {
                    from: _models_1.MODEL_NAMES.receivableItems,
                    localField: 'invoiceId',
                    foreignField: '_id',
                    as: 'invoice',
                },
            }, {
                $unwind: {
                    path: '$invoice',
                },
            },
        ]);
        const invoiceItems = items
            .map((item) => {
            return {
                invoiceNumber: item.invoice.invoiceNumber,
                invoiceDate: item.invoice.invoiceDate,
                customerTitle: item.invoice.customerTitle,
                invoiceAmount: item.invoice.invoiceAmount,
            };
        })
            .sort((a, b) => new Date(b.invoiceDate).valueOf() - new Date(a.invoiceDate).valueOf());
        const customerItemsRaw = items.reduce((acc, item) => {
            if (!acc[item.invoice.customerTitle]) {
                return {
                    ...acc,
                    [item.invoice.customerTitle]: item[filterField],
                };
            }
            return {
                ...acc,
                [item.invoice.customerTitle]: new decimal_js_1.default(acc[item.invoice.customerTitle]).add(item[filterField]).toNumber(),
            };
        }, {});
        const customerItemsTotal = Object
            .values(customerItemsRaw)
            .reduce((acc, itemValue) => new decimal_js_1.default(acc).add(itemValue).toNumber(), 0);
        const findConcentration = (customerTitle) => {
            const foundItem = items.find((item) => item.invoice.customerTitle === customerTitle);
            if (!foundItem) {
                return 0;
            }
            return new decimal_js_1.default(foundItem.customerConcentration).mul(100).toDP(2).toNumber();
        };
        const customerItems = Object.entries(customerItemsRaw).map(([customerTitle, customerAmount]) => {
            return {
                customerTitle,
                customerConcentration: itemsFilter === 'deduction' ? findConcentration(customerTitle) : null,
                customerAmount,
            };
        });
        const customerItemsWithTotal = customerItems.length > 0
            ? [...customerItems, { customerTitle: 'TOTAL', customerConcentration: null, customerAmount: customerItemsTotal }]
            : customerItems;
        return { itemsFilter: filterField, invoiceItems, customerItems: customerItemsWithTotal };
    }
    async foundOrCreateAvailability(bbcDateId, paginatorOptions, user) {
        const foundAvailability = await this.findCalculatedAvailability(bbcDateId);
        if (foundAvailability) {
            foundAvailability.items = await this.getAvailabilityItems(foundAvailability._id.toString(), paginatorOptions);
            const uniqueCustomers = await this.getUniqueCustomers(foundAvailability._id.toString());
            const bbcDateIdID = new mongoose_1.default.Types.ObjectId(bbcDateId);
            foundAvailability.uniqueCustomers = uniqueCustomers.map((c) => c.customerTitle);
            foundAvailability.totalItems = await this.collateralsService.countCollateralDocsByBBC([bbcDateId], collaterals_enum_1.ECollaterals.RECEIVABLE);
            if (foundAvailability.useManualInputs) {
                const { summary: manualSummary } = await this.manualEntryService.getReceivableManualSummary(bbcDateId);
                foundAvailability.summary = manualSummary;
            }
            else {
                foundAvailability.summary = foundAvailability.summary ? foundAvailability.summary : {};
                const reserves = await (0, reserve_db_1.getReserveDocs)([bbcDateIdID]);
                const ARReserves = reserves.length ? reserves[0].summary?.RECEIVABLES?.amount : 0;
                foundAvailability.summary.ARReserves = ARReserves;
                foundAvailability.summary.ARAvailability = new decimal_js_1.default(foundAvailability.summary.ARAvailability || 0).sub(ARReserves).toNumber();
            }
            return foundAvailability;
        }
        return await this.calculateAvailability(bbcDateId, paginatorOptions, user);
    }
    async findCalculatedAvailability(bbcDateId) {
        const calculatedAvailability = await ReceivableAvailability_model_1.ReceivableAvailabilityModel.aggregate([
            {
                $match: {
                    'bbcDateId': new mongoose_1.default.Types.ObjectId(bbcDateId),
                },
            },
            {
                $lookup: {
                    from: _models_1.MODEL_NAMES.BBCDates,
                    localField: 'bbcDateId',
                    foreignField: '_id',
                    as: 'bbc',
                },
            },
            {
                $unwind: {
                    path: '$bbc',
                },
            },
            {
                $addFields: {
                    'bbcDate': '$bbc.bbcDate',
                },
            },
            {
                $lookup: {
                    from: _models_1.MODEL_NAMES.users,
                    localField: 'userCalculatedId',
                    foreignField: '_id',
                    as: 'userCalculated',
                },
            },
            {
                $unwind: {
                    path: '$userCalculated',
                    preserveNullAndEmptyArrays: true,
                },
            },
            {
                $group: {
                    '_id': '$_id',
                    'bbcDate': { $first: '$bbcDate' },
                    'bbcDateId': { $first: '$bbcDateId' },
                    'userCalculated': { $first: '$userCalculated' },
                    'calculatedAt': { $first: '$calculatedAt' },
                    'actual': { $first: '$actual' },
                    'settings': { $first: '$settings' },
                    'summary': { $first: '$summary' },
                    'useManualInputs': { $first: '$useManualInputs' },
                },
            },
        ]);
        if (calculatedAvailability.length) {
            return (0, mongo_helper_1.normalizeObject)(calculatedAvailability[0], '_id', '_id');
        }
        return null;
    }
    async saveCalculatedAvailabilitySettings(bbcDateId, settings) {
        await ReceivableAvailability_model_1.ReceivableAvailabilityModel.findOneAndUpdate({ bbcDateId: bbcDateId }, {
            actual: false,
            settings: settings,
        }, { upsert: true });
    }
    async saveLimitSettings(bbcDateId, limitSettings) {
        const availability = await this.findCalculatedAvailability(bbcDateId);
        const updatedSettings = { ...availability.settings, ...limitSettings };
        await this.saveCalculatedAvailabilitySettings(bbcDateId, updatedSettings);
    }
    async calculateAvailabilitySummary(newCalculation) {
        const summaryFieldsOpt = {
            invoiceAmount: {
                fieldPath: 'invoiceAmount',
                replacedHeader: 'invoiceAmount',
            },
            excludedCustomerDeduction: {
                fieldPath: 'excludedCustomerDeduction',
                reverse: true,
            },
            creditNotes: {
                fieldPath: 'creditNotes',
                reverse: true,
            },
            grossAR: {
                fieldPath: 'grossAR',
            },
            permittedOver: {
                fieldPath: 'permittedOver',
                reverse: true,
            },
            preliminaryEligibleBalance: {
                fieldPath: 'preliminaryEligibleBalance',
            },
            creditNotesNegative: {
                fieldPath: 'creditNotes',
            },
            deduction: {
                fieldPath: 'deduction',
                reverse: true,
            },
            partiallyPaidDeduction: {
                fieldPath: 'partiallyPaidDeduction',
                reverse: true,
            },
            contraDeduction: {
                fieldPath: 'contraDeduction',
                reverse: true,
            },
            crossAgeDeduction: {
                fieldPath: 'crossAgeDeduction',
                reverse: true,
            },
            balanceAfterCrossAge: {
                fieldPath: 'balanceAfterCrossAge',
            },
            waivedDeduction: {
                fieldPath: 'waivedDeduction',
            },
            finalResult: {
                fieldPath: 'finalResult',
            },
            insuredComponent: {
                fieldPath: 'insuredComponent',
            },
            uninsuredComponent: {
                fieldPath: 'uninsuredComponent',
            },
            insuredAvailability: {
                fieldPath: 'insuredAvailability',
            },
            uninsuredAvailability: {
                fieldPath: 'uninsuredAvailability',
            },
            ARAvailability: {
                fieldPath: 'ARAvailability',
            },
        };
        const summary = Object.entries(summaryFieldsOpt).reduce((acc, [field, desc]) => {
            const total = newCalculation.items.reduce((fieldTotal, customer) => {
                if (desc.fieldPath === 'insuredAvailability') {
                    return new decimal_js_1.default(customer.insuredComponent).mul(newCalculation.settings.insuredARAdvanceRate).add(fieldTotal).toDP(2).toNumber();
                }
                if (desc.fieldPath === 'uninsuredAvailability') {
                    return new decimal_js_1.default(customer.uninsuredComponent).mul(newCalculation.settings.uninsuredARAdvanceRate).add(fieldTotal).toDP(2).toNumber();
                }
                if (desc.fieldPath === 'ARAvailability') {
                    const insuredPart = new decimal_js_1.default(customer.insuredComponent).mul(newCalculation.settings.insuredARAdvanceRate);
                    const uninsuredPart = new decimal_js_1.default(customer.uninsuredComponent).mul(newCalculation.settings.uninsuredARAdvanceRate);
                    return new decimal_js_1.default(fieldTotal).add(insuredPart).add(uninsuredPart).toDP(2).toNumber();
                }
                if (lodash_1.default.get(customer, desc.fieldPath)) {
                    return new decimal_js_1.default(fieldTotal).add(lodash_1.default.get(customer, desc.fieldPath)).toDP(2).toNumber();
                }
                return fieldTotal;
            }, 0);
            return { ...acc, [desc.replacedHeader ?? field]: desc.reverse ? total * -1 : total };
        }, {});
        // TODO quick solution for creditNotesNegative
        summary.balanceAfterCrossAge = summary.balanceAfterCrossAge + summary.creditNotesNegative;
        const insuredComponent = summary.insuredComponent === 0 ? 0 : summary.insuredComponent;
        const uninsuredComponent = new decimal_js_1.default(summary.finalResult).sub(insuredComponent).toNumber();
        summary.insuredComponent = insuredComponent;
        summary.uninsuredComponent = uninsuredComponent;
        const insuredPart = new decimal_js_1.default(insuredComponent).mul(newCalculation.settings.insuredARAdvanceRate);
        const uninsuredPart = new decimal_js_1.default(uninsuredComponent).mul(newCalculation.settings.uninsuredARAdvanceRate);
        summary.ARAvailability = new decimal_js_1.default(insuredPart).add(uninsuredPart).toDP(2).toNumber();
        return summary;
    }
    async saveCalculatedAvailability(newCalculation) {
        const savedAvailability = await ReceivableAvailability_model_1.ReceivableAvailabilityModel.findOneAndReplace({ bbcDateId: newCalculation.bbcDateId }, newCalculation, { upsert: true, new: true });
        await ReceivableAvailabilityItem_model_1.ReceivableAvailabilityItemModel.deleteMany({ availabilityId: savedAvailability._id });
        const orderedItems = newCalculation.items.map((item, order) => ({ ...item, order }));
        await Promise.all(orderedItems.map(async (item) => {
            const newCollateralItem = new ReceivableAvailabilityItem_model_1.ReceivableAvailabilityItemModel({
                availabilityId: savedAvailability._id,
                order: item.order,
                ...item,
            });
            try {
                await newCollateralItem.save();
            }
            catch (e) {
                console.error(e);
            }
        }));
    }
    async getCalculationSettings(bbcDateId) {
        const availability = await ReceivableAvailability_model_1.ReceivableAvailabilityModel.findOne({ bbcDateId: bbcDateId }).lean();
        if (!availability || !availability.settings || lodash_1.default.isEmpty(availability.settings)) {
            return {
                standardARDays: 90,
                customerConcentrationLimit: 0.2,
                crossAgeLimit: 0.25,
                contraProvision: 1.25,
                paidDays: 180,
                insuredARAdvanceRate: 0,
                uninsuredARAdvanceRate: 0,
                excludeFromTotal: true,
                waiveInsuredCustomers: true,
                excludedCustomers: [],
                insuredCustomers: [],
                permittedConcentration: [],
                permittedExtendedARDays: [],
            };
        }
        return availability.settings;
    }
    async getAllSettings(bbcDateId = null) {
        const selectedAvailability = await ReceivableAvailability_model_1.ReceivableAvailabilityModel.findOne({ bbcDateId: bbcDateId }).lean();
        const availabilities = await ReceivableAvailability_model_1.ReceivableAvailabilityModel.aggregate([
            {
                '$match': {
                    '$and': [
                        { 'borrowerId': new mongoose_1.default.Types.ObjectId(selectedAvailability.borrowerId) },
                        { 'bbcDateId': { '$ne': new mongoose_1.default.Types.ObjectId(bbcDateId) } },
                    ],
                },
            },
        ]);
        return availabilities.reduce((acc, a) => {
            const settings = Object.values(ReceivableAvailability_model_1.ELimitSettings).reduce((accSettings, limitSettings) => {
                return { ...accSettings, [limitSettings]: a.settings[limitSettings] };
            }, {});
            return { ...acc, [a.bbcDateId.toString()]: settings };
        }, {});
    }
    async getAvailabilityItems(availabilityId, paginatorOptions) {
        return ReceivableAvailabilityItem_model_1.ReceivableAvailabilityItemModel.aggregate([
            {
                $match: {
                    'availabilityId': new mongoose_1.default.Types.ObjectId(availabilityId),
                },
            },
            {
                $sort: {
                    'order': 1,
                },
            },
            ...(0, collaterals_db_1.ITEMS_PAGINATION)(paginatorOptions),
            {
                $lookup: {
                    from: _models_1.MODEL_NAMES.receivableItems,
                    localField: 'invoiceId',
                    foreignField: '_id',
                    as: 'invoice',
                },
            },
            {
                $unwind: {
                    path: '$invoice',
                },
            },
        ]);
    }
    async getUniqueCustomers(availabilityId) {
        return ReceivableAvailabilityItem_model_1.ReceivableAvailabilityItemModel.aggregate([
            {
                $match: {
                    'availabilityId': new mongoose_1.default.Types.ObjectId(availabilityId),
                },
            },
            {
                $lookup: {
                    from: _models_1.MODEL_NAMES.receivableItems,
                    localField: 'invoiceId',
                    foreignField: '_id',
                    as: 'invoice',
                },
            },
            {
                $unwind: {
                    path: '$invoice',
                },
            },
            {
                $group: {
                    '_id': null,
                    'customerTitle': {
                        $addToSet: '$invoice.customerTitle',
                    },
                },
            },
            {
                $project: {
                    '_id': 0,
                    'customerTitle': 1,
                },
            },
            {
                $unwind: {
                    path: '$customerTitle',
                },
            },
        ]);
    }
    async getAllSummaries(bbcDateId, user) {
        const equipment = await this.equipmentService.getEquipmentForBBC(bbcDateId);
        const reserve = await this.reserveService.getReserve(bbcDateId);
        const loanBalances = await this.loanTransactionsService.getLoanBalanceForBBCDateId(bbcDateId);
        const collateralAdjustmentSummary = await this.collateralAdjustmentsService.getCollateralAdjustmentSummary(bbcDateId);
        const inventoryAvailability = await this.inventoryAvailabilityService.getInventoryAvailability(bbcDateId, exports.START_PAGINATORS);
        const receivableAvailability = await this.getAvailability(bbcDateId, { pageIndex: null, pageSize: null }, user);
        const savedData = await this.signsService.getSavedData(bbcDateId);
        if (!savedData.accruedStatement) {
            savedData.accruedStatement = await this.loanStatementService.calculateAccruedStatementForBBC(bbcDateId);
        }
        return {
            reserve: reserve.summary,
            equipment: equipment.summary,
            inventory: inventoryAvailability.summary,
            receivable: receivableAvailability.summary,
            loanBalances,
            collateralAdjustment: { summary: collateralAdjustmentSummary },
            manualInputs: {
                receivableAvailability: receivableAvailability.useManualInputs,
                inventoryAvailability: inventoryAvailability.useManualInputs,
            },
            ...savedData,
        };
    }
    async isBBCEditable(bbcDateId) {
        const availabilitiesSigns = await this.signsService.getAvailabilitiesSigns([bbcDateId]);
        if (!availabilitiesSigns.length) {
            return true;
        }
        const aSign = availabilitiesSigns[0];
        return aSign.signs.filter((s) => !s.revokedAt).length < aSign.requiredSigns;
    }
    async toggleManualReceivableAvailability(bbcDateId, useManualInputs) {
        await (0, receivables_db_1.setReceivableManualInputsToggle)(bbcDateId, useManualInputs);
    }
}
exports.AvailabilityService = AvailabilityService;
