Subversion Repositories SmartDukaan

Rev

Rev 33916 | Blame | Compare with Previous | Last modification | View Log | RSS feed

package com.spice.profitmandi.service.catalog;

import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
import com.spice.profitmandi.common.model.ComboCancellationResult;
import com.spice.profitmandi.common.model.ComboCancellationResult.ComboLinkedOrder;
import com.spice.profitmandi.dao.entity.catalog.*;
import com.spice.profitmandi.dao.entity.fofo.FofoStore;
import com.spice.profitmandi.dao.entity.transaction.Order;
import in.shop2020.model.v1.order.OrderStatus;
import com.spice.profitmandi.dao.model.UserCart;
import com.spice.profitmandi.dao.repository.catalog.*;
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
import com.spice.profitmandi.dao.repository.user.CartLineRepository;
import com.spice.profitmandi.dao.repository.user.UserRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

@Component
public class ComboServiceImpl implements ComboService {
    @Autowired
    CartLineRepository cartLineRepository;
    @Autowired
    ItemRepository itemRepository;
    @Autowired
    CatalogRepository catalogRepository;

    @Autowired
    UserRepository userRepository;

    @Autowired
    FofoStoreRepository fofoStoreRepository;

    @Autowired
    ComboModelRepository comboModelRepository;
    @Autowired
    ComboOptionRepository comboOptionRepository;
    @Autowired
    ComboMappedModelRepository comboMappedModelRepository;

    @Autowired
    OrderRepository orderRepository;

    private static final Logger LOGGER = LogManager.getLogger(ComboServiceImpl.class);

    @Override
    public boolean validateCombo(UserCart userCart) throws ProfitMandiBusinessException {
        Map<Integer, Integer> itemQtyMap = cartLineRepository.selectAllByCart(userCart.getCartId()).stream().collect(Collectors.toMap(x -> x.getItemId(), x -> x.getQuantity()));
        LOGGER.info("itemQtyMap - {}", itemQtyMap);
        List<Item> items = itemRepository.selectByIds(itemQtyMap.keySet());
        Set<Integer> focModelSet = items.stream().filter(x -> x.getBrand().equals("FOC")).map(x -> x.getCatalogItemId()).collect(Collectors.toSet());
        Map<Integer, Integer> catalogIdCartQtyMap = items.stream().collect(Collectors.groupingBy(x -> x.getCatalogItemId(), Collectors.summingInt(x -> itemQtyMap.get(x.getId()))));


        FofoStore fofoStore = fofoStoreRepository.selectByRetailerId(userCart.getUserId());

        List<ComboModel> comboModelList = comboModelRepository.selectByCatalogIdsAndWarehouseId(new ArrayList<>(catalogIdCartQtyMap.keySet()), fofoStore.getWarehouseId());
        if(comboModelList.size() == 0) {
            return false;
        }
        Map<Integer, List<ComboModel>> comboMap = comboModelList.stream().collect(Collectors.groupingBy(x -> x.getCatalogId()));

        for (Map.Entry<Integer, List<ComboModel>> comboEntry : comboMap.entrySet()) {
            int catalogId = comboEntry.getKey();
            List<ComboModel> comboModels = comboEntry.getValue();
            ComboModel filteredModel = comboModels.stream().filter(x -> x.getQty() == catalogIdCartQtyMap.get(catalogId)).findAny().orElse(null);
            if (filteredModel == null) {
                throw new ProfitMandiBusinessException("Combo Quantity mismatched", "Combo Quantity mismatched", "Combo Quantity mismatched");
            }
            List<ComboOption> comboOptions = comboOptionRepository.selectByComboId(filteredModel.getId());
            List<ComboMappedModel> comboMappedModels = comboMappedModelRepository.selectByComboOptionId(comboOptions.get(0).getId());
            for (ComboMappedModel comboMappedModel : comboMappedModels) {
                int secondaryCatalogId = comboMappedModel.getComboCatalogId();
                int requiredQty = comboMappedModel.getComboQty();
                LOGGER.info("catalogIdCartQtyMap.get(secondaryCatalogId) - {}", catalogIdCartQtyMap.get(secondaryCatalogId));
                if (!catalogIdCartQtyMap.containsKey(secondaryCatalogId) || requiredQty > catalogIdCartQtyMap.get(secondaryCatalogId)) {
                    Catalog catalog = catalogRepository.selectCatalogById(catalogId);
                    throw new ProfitMandiBusinessException("Missing required Qty for Combo", "", "Missing required qty for Combo - " + catalog.getDescription());
                } else {
                    catalogIdCartQtyMap.put(secondaryCatalogId, catalogIdCartQtyMap.get(secondaryCatalogId) - requiredQty);
                }
            }
        }
        for (int focModel : focModelSet) {
            int qtyLeft = catalogIdCartQtyMap.get(focModel);
            if (qtyLeft != 0) {
                throw new ProfitMandiBusinessException("FOC items cant be billed above Combo Qty", "FOC", "FOC items cant be billed above Combo Qty");
            }
        }
        return true;
    }

    // Combo cancellation rules (linkage resolved via the catalog combo definition, NOT just shared transaction):
    //   secondary is FOC      -> cancel main: auto-cancel the FOC (silent);  cancel FOC: standalone OK, main stays
    //   secondary is non-FOC  -> cancel main: standalone OK, secondary stays; cancel secondary: force-cancel main (confirm)
    @Override
    public ComboCancellationResult checkOrderCancellationForCombo(List<Order> orders) {
        ComboCancellationResult result = ComboCancellationResult.noCombo();
        if (orders == null || orders.isEmpty()) {
            return result;
        }

        Set<Integer> cancelledIds = orders.stream().map(Order::getId).collect(Collectors.toSet());

        Map<Integer, List<Order>> cancelledByTransaction = orders.stream()
                .filter(o -> o.getTransactionId() != null)
                .collect(Collectors.groupingBy(Order::getTransactionId));

        Set<Integer> autoCancelFocIds = new HashSet<>();
        Set<Integer> linkedMainIds = new HashSet<>();

        for (Map.Entry<Integer, List<Order>> entry : cancelledByTransaction.entrySet()) {
            List<Order> cancelledInTransaction = entry.getValue();
            List<Order> siblings = orderRepository.selectAllByTransactionId(entry.getKey());
            if (siblings == null || siblings.isEmpty()) {
                continue;
            }

            Integer warehouseId = resolveWarehouseId(siblings);
            if (warehouseId == null) {
                continue;
            }

            Map<Integer, Integer> orderIdToCatalogId = resolveOrderCatalogIds(siblings);

            // main-catalog -> secondary-catalogs, from the combo definition for this warehouse
            Map<Integer, Set<Integer>> mainToSecondaries =
                    buildComboCatalogMap(new ArrayList<>(new HashSet<>(orderIdToCatalogId.values())), warehouseId);
            if (mainToSecondaries.isEmpty()) {
                continue;
            }

            // inverse: secondary-catalog -> main-catalogs
            Map<Integer, Set<Integer>> secondaryToMains = new HashMap<>();
            for (Map.Entry<Integer, Set<Integer>> e : mainToSecondaries.entrySet()) {
                for (Integer secondaryCatalog : e.getValue()) {
                    secondaryToMains.computeIfAbsent(secondaryCatalog, k -> new HashSet<>()).add(e.getKey());
                }
            }

            // siblings indexed by their catalog id
            Map<Integer, List<Order>> siblingsByCatalog = new HashMap<>();
            for (Order sibling : siblings) {
                Integer catalogId = orderIdToCatalogId.get(sibling.getId());
                if (catalogId != null) {
                    siblingsByCatalog.computeIfAbsent(catalogId, k -> new ArrayList<>()).add(sibling);
                }
            }

            // mains that will be cancelled = user-selected mains + mains forced by a non-FOC secondary cancel
            Set<Integer> mainOrderIdsToCancel = new HashSet<>();
            for (Order cancelled : cancelledInTransaction) {
                Integer catalogId = orderIdToCatalogId.get(cancelled.getId());
                if (catalogId != null && mainToSecondaries.containsKey(catalogId)) {
                    mainOrderIdsToCancel.add(cancelled.getId());
                }
            }

            // Pass 1: a non-FOC secondary being cancelled mandatorily cancels its main(s) -> ask for confirmation
            for (Order cancelled : cancelledInTransaction) {
                Integer catalogId = orderIdToCatalogId.get(cancelled.getId());
                if (catalogId == null || !secondaryToMains.containsKey(catalogId) || isFoc(cancelled)) {
                    continue;
                }
                for (Integer mainCatalog : secondaryToMains.get(catalogId)) {
                    for (Order mainOrder : siblingsByCatalog.getOrDefault(mainCatalog, Collections.emptyList())) {
                        if (cancelledIds.contains(mainOrder.getId())
                                || !OrderStatus.SUBMITTED_FOR_PROCESSING.equals(mainOrder.getStatus())) {
                            continue;
                        }
                        mainOrderIdsToCancel.add(mainOrder.getId());
                        if (linkedMainIds.add(mainOrder.getId())) {
                            result.setConfirmationRequired(true);
                            result.getLinkedMainOrders().add(new ComboLinkedOrder(
                                    mainOrder.getId(),
                                    mainOrder.getLineItem().getBrand() + " " + mainOrder.getLineItem().getModelName() + " " + mainOrder.getLineItem().getModelNumber(),
                                    mainOrder.getLineItem().getQuantity()
                            ));
                        }
                    }
                }
            }

            // Pass 2: every main being cancelled auto-cancels its FOC secondaries (the free gift follows the main)
            for (Integer mainOrderId : mainOrderIdsToCancel) {
                Integer mainCatalog = orderIdToCatalogId.get(mainOrderId);
                if (mainCatalog == null) {
                    continue;
                }
                for (Integer secondaryCatalog : mainToSecondaries.getOrDefault(mainCatalog, Collections.emptySet())) {
                    for (Order secondary : siblingsByCatalog.getOrDefault(secondaryCatalog, Collections.emptyList())) {
                        if (!cancelledIds.contains(secondary.getId())
                                && OrderStatus.SUBMITTED_FOR_PROCESSING.equals(secondary.getStatus())
                                && isFoc(secondary)) {
                            autoCancelFocIds.add(secondary.getId());
                        }
                    }
                }
            }
        }

        result.getAutoCancelFocOrderIds().addAll(autoCancelFocIds);
        return result;
    }

    private boolean isFoc(Order order) {
        return order.getLineItem() != null && "FOC".equals(order.getLineItem().getBrand());
    }

    private Integer resolveWarehouseId(List<Order> siblings) {
        for (Order order : siblings) {
            if (order.getRetailerId() == null) {
                continue;
            }
            try {
                FofoStore store = fofoStoreRepository.selectByRetailerId(order.getRetailerId());
                if (store != null) {
                    return store.getWarehouseId();
                }
            } catch (Exception e) {
                LOGGER.warn("Combo cancel: unable to resolve warehouse for retailer {} - {}", order.getRetailerId(), e.getMessage());
            }
        }
        return null;
    }

    private Map<Integer, Integer> resolveOrderCatalogIds(List<Order> siblings) {
        Set<Integer> itemIds = siblings.stream()
                .filter(o -> o.getLineItem() != null && o.getLineItem().getItemId() != null)
                .map(o -> o.getLineItem().getItemId())
                .collect(Collectors.toSet());

        Map<Integer, Integer> itemIdToCatalogId = new HashMap<>();
        if (!itemIds.isEmpty()) {
            try {
                for (Item item : itemRepository.selectByIds(itemIds)) {
                    itemIdToCatalogId.put(item.getId(), item.getCatalogItemId());
                }
            } catch (Exception e) {
                LOGGER.warn("Combo cancel: unable to resolve items - {}", e.getMessage());
            }
        }

        Map<Integer, Integer> orderIdToCatalogId = new HashMap<>();
        for (Order order : siblings) {
            if (order.getLineItem() == null || order.getLineItem().getItemId() == null) {
                continue;
            }
            Integer catalogId = itemIdToCatalogId.get(order.getLineItem().getItemId());
            if (catalogId != null) {
                orderIdToCatalogId.put(order.getId(), catalogId);
            }
        }
        return orderIdToCatalogId;
    }

    private Map<Integer, Set<Integer>> buildComboCatalogMap(List<Integer> catalogIds, int warehouseId) {
        Map<Integer, Set<Integer>> mainToSecondaries = new HashMap<>();
        if (catalogIds.isEmpty()) {
            return mainToSecondaries;
        }
        List<ComboModel> comboModels = comboModelRepository.selectByCatalogIdsAndWarehouseId(catalogIds, warehouseId);
        for (ComboModel comboModel : comboModels) {
            for (ComboOption comboOption : comboOptionRepository.selectByComboId(comboModel.getId())) {
                for (ComboMappedModel mappedModel : comboMappedModelRepository.selectByComboOptionId(comboOption.getId())) {
                    mainToSecondaries
                            .computeIfAbsent(comboModel.getCatalogId(), k -> new HashSet<>())
                            .add(mappedModel.getComboCatalogId());
                }
            }
        }
        return mainToSecondaries;
    }
}