"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.sortItem = void 0;
const typeorm_1 = require("typeorm");
const sales_base_1 = require("@things-factory/sales-base");
const warehouse_base_1 = require("@things-factory/warehouse-base");
/**
 * Calculates the available quantity that can still be sorted for an order inventory
 */
function getAvailableQty(oi) {
    const available = Math.min(oi.releaseQty, oi.pickedQty) - oi.sortedQty;
    return available > 0 ? available : 0;
}
/**
 * Creates or updates an order tote item with the given quantity
 */
async function upsertOrderToteItem(tx, domain, user, orderTote, orderInventory, qtyToAdd) {
    var _a;
    const orderToteItemRepo = tx.getRepository(sales_base_1.OrderToteItem);
    const existingItem = (await orderToteItemRepo.findOne({
        where: {
            domain,
            orderTote,
            orderInventory,
            orderProduct: orderInventory.orderProduct
        }
    }));
    if (!existingItem) {
        await orderToteItemRepo.save({
            domain,
            name: sales_base_1.OrderNoGenerator.orderToteItem(),
            orderTote,
            orderInventory,
            orderProduct: orderInventory.orderProduct,
            qty: qtyToAdd,
            updater: user
        });
    }
    else {
        await orderToteItemRepo.update(existingItem.id, {
            qty: Number((_a = existingItem.qty) !== null && _a !== void 0 ? _a : 0) + qtyToAdd,
            updater: user,
            updatedAt: new Date()
        });
    }
}
exports.sortItem = {
    async sortItem(_, { releaseGoodNo, productBarcode, qty, worksheetDetails, toteNo }, context) {
        const { domain, user, tx } = context.state;
        // Single query to get order inventories based on either releaseGood name or binLocation
        const orderInventories = await tx
            .getRepository(sales_base_1.OrderInventory)
            .createQueryBuilder('orderInventory')
            .innerJoinAndSelect('orderInventory.productDetail', 'productDetail')
            .innerJoinAndSelect('productDetail.productBarcodes', 'productBarcode')
            .leftJoinAndSelect('orderInventory.orderProduct', 'orderProduct')
            .leftJoinAndSelect('orderInventory.releaseGood', 'releaseGood')
            .leftJoinAndSelect('releaseGood.bizplace', 'bizplace')
            .leftJoinAndSelect('orderInventory.binLocation', 'binLocation')
            .where('orderInventory.domain_id = :domainId', { domainId: domain.id })
            .andWhere('orderInventory.status IN (:...statuses)', {
            statuses: [sales_base_1.ORDER_INVENTORY_STATUS.SORTING, sales_base_1.ORDER_INVENTORY_STATUS.LOADING]
        })
            .andWhere('productBarcode.gtin = :gtin', { gtin: productBarcode })
            .andWhere(new typeorm_1.Brackets(qb => {
            qb.where('orderInventory.sortedQty < orderInventory.pickedQty').andWhere('orderInventory.sortedQty < orderInventory.releaseQty');
        }))
            .andWhere(new typeorm_1.Brackets(qb => {
            qb.where('releaseGood.name = :releaseGoodNo', { releaseGoodNo }).orWhere('binLocation.name = :releaseGoodNo', { releaseGoodNo });
        }))
            .getMany();
        if (!orderInventories.length)
            throw new Error('No matching product barcode found');
        let resolvedReleaseGood = null;
        let resolvedOrderTote = null;
        if (toteNo) {
            // Ensure we can identify a single release order when scanning by BIN
            const uniqueReleaseGoodIds = [...new Set(orderInventories.map(oi => oi.releaseGoodId).filter(Boolean))];
            if (uniqueReleaseGoodIds.length > 1) {
                throw new Error('Multiple release orders detected. Please select a release order before tote scanning.');
            }
            resolvedReleaseGood = await tx.getRepository(sales_base_1.ReleaseGood).findOne({
                where: { domain, id: uniqueReleaseGoodIds[0] },
                relations: ['bizplace']
            });
            if (!resolvedReleaseGood) {
                throw new Error('Release good not found for tote scanning');
            }
            resolvedOrderTote = await tx.getRepository(sales_base_1.OrderTote).findOne({
                where: { domain, name: toteNo, releaseGood: resolvedReleaseGood }
            });
            if (!resolvedOrderTote) {
                // Query for valid totes (NOT dispatched or damaged)
                const foundTote = await tx.getRepository(warehouse_base_1.Tote).findOne({
                    where: {
                        bizplace: resolvedReleaseGood.bizplace,
                        name: toteNo,
                        deletedAt: (0, typeorm_1.IsNull)(),
                        status: (0, typeorm_1.Not)((0, typeorm_1.In)([warehouse_base_1.TOTE_STATUS.DISPATCHED, warehouse_base_1.TOTE_STATUS.DAMAGED]))
                    }
                });
                resolvedOrderTote = await tx.getRepository(sales_base_1.OrderTote).save({
                    name: toteNo,
                    domain,
                    releaseGood: resolvedReleaseGood,
                    tote: foundTote !== null && foundTote !== void 0 ? foundTote : null,
                    creator: user,
                    updater: user
                });
            }
            else if (resolvedOrderTote.closedDate) {
                throw new Error('Tote has been sealed, please try another tote!');
            }
        }
        // Calculate total available quantity using helper function
        const totalAvailableQty = orderInventories.reduce((sum, oi) => sum + getAvailableQty(oi), 0);
        if (qty > totalAvailableQty) {
            throw new Error(`Sort qty (${qty}) exceeds total available quantity (${totalAvailableQty})`);
        }
        // Distribute the sorted quantity across order inventories
        let remainingQty = qty;
        for (const orderInventory of orderInventories) {
            if (remainingQty <= 0)
                break;
            const availableQty = getAvailableQty(orderInventory);
            if (availableQty <= 0)
                continue;
            const qtyToAdd = Math.min(remainingQty, availableQty);
            // Update the order inventory
            await tx.getRepository(sales_base_1.OrderInventory).update(orderInventory.id, {
                sortedQty: orderInventory.sortedQty + qtyToAdd,
                status: sales_base_1.ORDER_INVENTORY_STATUS.LOADING,
                updatedAt: new Date(),
                updater: user
            });
            // Create / update order tote items to reflect tote allocation during QC
            if (resolvedOrderTote) {
                await upsertOrderToteItem(tx, domain, user, resolvedOrderTote, orderInventory, qtyToAdd);
            }
            remainingQty -= qtyToAdd;
        }
        return true;
    }
};
//# sourceMappingURL=sort-item.js.map