Subversion Repositories SmartDukaan

Rev

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

package com.spice.profitmandi.web.controller;

import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
import com.spice.profitmandi.common.model.CustomRetailer;
import com.spice.profitmandi.common.model.ProfitMandiConstants;
import com.spice.profitmandi.common.util.Utils;
import com.spice.profitmandi.dao.entity.auth.AuthUser;
import com.spice.profitmandi.dao.entity.cs.*;
import com.spice.profitmandi.dao.entity.cs.Position;
import com.spice.profitmandi.dao.entity.dtr.Document;
import com.spice.profitmandi.dao.entity.cs.TicketReadStatus.UserType;
import com.spice.profitmandi.dao.entity.fofo.ActivityType;
import com.spice.profitmandi.dao.enumuration.cs.EscalationType;
import com.spice.profitmandi.dao.enumuration.cs.TicketStatus;
import com.spice.profitmandi.dao.model.CreatePositionModel;
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
import com.spice.profitmandi.dao.repository.cs.*;
import com.spice.profitmandi.dao.repository.dtr.DocumentRepository;
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
import com.spice.profitmandi.service.authentication.RoleManager;
import com.spice.profitmandi.service.user.RetailerService;
import com.spice.profitmandi.web.model.LoginDetails;
import com.spice.profitmandi.web.util.CookiesProcessor;
import com.spice.profitmandi.web.util.MVCResponseSender;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.swing.SortOrder;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Controller
@Transactional(rollbackFor = Throwable.class)
public class CsController {

    private static final Logger LOGGER = LogManager.getLogger(CsController.class);
    private static final String ACTIVITY_SUBJECT = "Message related ticketId#%s";
    private static final String PARTNER_RESOLVED_TICKET_MAIL = "Dear Partner , we have resolved your ticket # %s , request to kindly accept the same. In case you still have any concerns regarding the same pls click on %s so that we can help you.Regards\nSmartdukaan";
    private static final String PARTNER_REOPEN = "Dear Partner , Your ticket # %s has been re-opened as per your confirmation & we are committed to resolve it on priority.Regards\nSmartdukaan";
    private static final String INTERNAL_REOPEN_MAIL = "Team, Pls note that the Ticket Id %s has been re-opened by %s , pls respond on priority";
    private static final String INTERNAL_REOPEN_ACTIVITY_MESSAGE = "Hi,My ticket is not resolved yet,so I have reopened it";

    @Autowired
    JavaMailSender mailSender;

    @Autowired
    private CsService csService;

    @Autowired
    private CookiesProcessor cookiesProcessor;

    @Autowired
    private TicketCategoryRepository ticketCategoryRepository;

    @Autowired
    private TicketSubCategoryRepository ticketSubCategoryRepository;

    @Autowired
    private RegionRepository regionRepository;

    @Autowired
    private RetailerService retailerService;

    @Autowired
    private MVCResponseSender mvcResponseSender;

    @Autowired
    private AuthRepository authRepository;

    @Autowired
    private PositionRepository positionRepository;

    @Autowired
    private TicketRepository ticketRepository;

    @Autowired
    private RoleManager roleManager;

    @Autowired
    private ActivityRepository activityRepository;

    @Autowired
    private ActivityAttachmentRepository activityAttachmentRepository;

    @Autowired
    private TicketAssignedRepository ticketAssignedRepository;

    @Autowired
    private PartnerRegionRepository partnerRegionRepository;

    @Autowired
    PartnerPositionRepository partnerPositionRepository;

    @Autowired
    FofoStoreRepository fofoStoreRepository;

    @Autowired
    DocumentRepository documentRepository;

    @GetMapping(value = "/cs/createCategory")
    public String getCreateCategory(HttpServletRequest request, Model model) {
        List<TicketCategory> ticketCategories = ticketCategoryRepository.selectAll();
        model.addAttribute("ticketCategories", ticketCategories);
        return "create-ticket-category";
    }

    @PostMapping(value = "/cs/createCategory")
    public String createCategory(HttpServletRequest request,
                                 @RequestParam(name = "name") String name,
                                 @RequestParam(name = "categoryType") int categoryType,
                                 @RequestParam(name = "description") String description,
                                 Model model) throws ProfitMandiBusinessException {
        TicketCategory ticketCategory = ticketCategoryRepository.selectByName(name);
        if (ticketCategory != null) {
            throw new ProfitMandiBusinessException("name", name, "already exists!");
        }

        ticketCategory = new TicketCategory();
        ticketCategory.setName(name);
        ticketCategory.setDescription(description);

        ticketCategory.setCategoryType(categoryType == 1);
        ticketCategoryRepository.persist(ticketCategory);
        return "create-ticket-category";
    }


    @GetMapping(value = "/cs/createSubCategory")
    public String getCreateSubCategory(HttpServletRequest request, Model model) {
        List<TicketCategory> ticketCategories = ticketCategoryRepository.selectAll();
        model.addAttribute("ticketCategories", ticketCategories);
        return "create-ticket-sub-category";
    }

    @GetMapping(value = "/cs/getSubCategoryByCategoryId")
    public String getSubCategoryByCategoryId(HttpServletRequest request, @RequestParam(name = "ticketCategoryId", defaultValue = "") int ticketCategoryId, Model model) {
        List<TicketSubCategory> ticketSubCategories = ticketSubCategoryRepository.selectAll(ticketCategoryId);
        TicketCategory ticketCategory = ticketCategoryRepository.selectById(ticketCategoryId);
        LOGGER.info("ticketSubCategories {}", ticketSubCategories);
        LOGGER.info("ticketCategory {}", ticketCategory);
        model.addAttribute("ticketSubCategories", ticketSubCategories);
        model.addAttribute("ticketCategory", ticketCategory);
        return "ticket-sub-category";
    }

    @PostMapping(value = "/cs/createSubCategory")
    public String createSubCategory(HttpServletRequest request, @RequestParam(name = "categoryId", defaultValue = "0") int categoryId, @RequestParam(name = "name") String name, @RequestParam(name = "description") String description, Model model) throws ProfitMandiBusinessException {

        TicketSubCategory ticketSubCategory = ticketSubCategoryRepository.selectTicketSubCategory(categoryId, name);
        if (ticketSubCategory != null) {
            throw new ProfitMandiBusinessException("name & categoryId", name + "  " + categoryId, "already exists!");
        }

        ticketSubCategory = new TicketSubCategory();
        ticketSubCategory.setCategoryId(categoryId);
        ticketSubCategory.setName(name);
        ticketSubCategory.setDescription(description);
        ticketSubCategoryRepository.persist(ticketSubCategory);
        return "create-ticket-sub-category";
    }

    @GetMapping(value = "/cs/createRegion")
    public String createRegion(HttpServletRequest request, Model model) {
        List<Region> regions = regionRepository.selectAll();
        model.addAttribute("regions", regions);
        return "create-region";
    }

    @PostMapping(value = "/cs/createRegion")
    public String createRegion(HttpServletRequest request, @RequestParam(name = "name") String name, @RequestParam(name = "description") String description, Model model) throws Exception {
        Region region = regionRepository.selectByName(name);
        if (region != null) {
            throw new ProfitMandiBusinessException("name", name, "already exists!");
        }
        region = new Region();
        region.setName(name);
        region.setDescription(description);
        regionRepository.persist(region);
        model.addAttribute("response1", mvcResponseSender.createResponseString(true));
        return "response";
    }

    @GetMapping(value = "/cs/getPartners")
    public String getPartners(HttpServletRequest request, @RequestParam(name = "regionId", defaultValue = "0") int regionId, Model model) throws ProfitMandiBusinessException {
        List<Integer> fofoIds = fofoStoreRepository.selectAll().stream().map(x -> x.getId()).collect(Collectors.toList());
        List<Integer> addedfofoIds = partnerRegionRepository.selectByRegionId(regionId).stream().map(x -> x.getFofoId()).collect(Collectors.toList());

        Map<Integer, CustomRetailer> customRetailerMap = retailerService.getAllFofoRetailers();
        Map<Integer, CustomRetailer> fofoRetailers = fofoIds.stream().map(x -> customRetailerMap.get(x)).filter(x -> x != null).collect(Collectors.toList()).stream().collect(Collectors.toMap(x -> x.getPartnerId(), x -> x));
        model.addAttribute("fofoRetailers", fofoRetailers);
        model.addAttribute("addedfofoIds", addedfofoIds);
        return "added-region-partners";
    }

    @GetMapping(value = "/cs/getPartnersByRegion")
    public String getPartnersByRegion(HttpServletRequest request, @RequestParam(name = "regionId", defaultValue = "0") int regionId, Model model) throws ProfitMandiBusinessException {
        List<Integer> fofoIds = null;
        fofoIds = partnerRegionRepository.selectByRegionId(regionId).stream().map(x -> x.getFofoId()).collect(Collectors.toList());

        if (fofoIds.contains(0)) {
            fofoIds = fofoStoreRepository.selectAll().stream().filter(x -> x.isActive()).collect(Collectors.toList()).stream().map(x -> x.getId()).collect(Collectors.toList());

        }
        Map<Integer, CustomRetailer> customRetailerMap = retailerService.getAllFofoRetailers();

        Map<Integer, CustomRetailer> fofoRetailers = fofoIds.stream().map(x -> customRetailerMap.get(x)).filter(x -> x != null).collect(Collectors.toList()).stream().collect(Collectors.toMap(x -> x.getPartnerId(), x -> x));
        model.addAttribute("fofoRetailers", fofoRetailers);
        return "added-subregion-partners";
    }

    @GetMapping(value = "/cs/createPartnerRegion")
    public String createPartnerRegion(HttpServletRequest request, Model model) {
        List<Region> regions = regionRepository.selectAll();
        model.addAttribute("regions", regions);
        return "create-partner-region";
    }

    @PostMapping(value = "/cs/createPartnerRegion")
    public String createPartnerRegion(HttpServletRequest request, @RequestParam(name = "regionId") int regionId, @RequestBody List<Integer> selectedFofoIds, Model model) throws Exception {
        partnerRegionRepository.delete(regionId);
        LOGGER.info("successfully removed");
        LOGGER.info(selectedFofoIds.size());
        csService.addPartnerToRegion(regionId, selectedFofoIds);
        model.addAttribute("response1", mvcResponseSender.createResponseString(true));
        return "response";
    }

    @GetMapping(value = "/cs/getPosition")
    public String getPosition(HttpServletRequest request, @RequestParam int positionId, Model model) throws ProfitMandiBusinessException {

        Position position = positionRepository.selectById(positionId);

        List<CustomRetailer> positionIdCustomRetailer = csService.getPositionCustomRetailerMap(Arrays.asList(position)).get(position.getId());

        Map<Integer, CustomRetailer> regionRetailerMap = csService.getRegionPartners(Arrays.asList(position)).get(position.getRegionId()).stream().collect(Collectors.toMap(x -> x.getPartnerId(), x -> x));

        model.addAttribute("position", position);
        model.addAttribute("regionRetailerMap", regionRetailerMap);
        model.addAttribute("positionIdCustomRetailer", positionIdCustomRetailer);

        return "position-partner";
    }

    @GetMapping(value = "/cs/createPosition")
    public String createPosition(HttpServletRequest request, @RequestParam(name = "offset", defaultValue = "0") int offset, @RequestParam(name = "limit", defaultValue = "10") int limit, Model model) {
        List<AuthUser> authUsers = authRepository.selectAllActiveUser();
        List<TicketCategory> ticketCategories = ticketCategoryRepository.selectAll();
        List<Region> regions = regionRepository.selectAll();
        model.addAttribute("escalationTypes", EscalationType.values());
        model.addAttribute("authUsers", authUsers);
        model.addAttribute("ticketCategories", ticketCategories);
        model.addAttribute("regions", regions);

        List<Position> positions = positionRepository.selectAllPosition();
        LOGGER.info("positions" + positions);

        Map<Integer, AuthUser> authUserIdAndAuthUserMap = csService.getAuthUserIdAndAuthUserMapUsingPositions(positions);
        Map<Integer, TicketCategory> categoryIdAndCategoryMap = csService.getCategoryIdAndCategoryUsingPositions(positions);
        Map<Integer, Region> regionIdAndRegionMap = csService.getRegionIdAndRegionMap(positions);

//          Map<Integer, List<CustomRetailer>> positionIdAndpartnerRegionMap = csService
//                      .getpositionIdAndpartnerRegionMap(positions);

//           Map<Integer, List<CustomRetailer>> addedpositionIdAndCustomRetailerMap = csService
//                              .getPositionCustomRetailerMap(positions);
//              LOGGER.info("fofoIdAndCustomRetailerMap" + addedpositionIdAndCustomRetailerMap);

        model.addAttribute("start", offset + 1);

        model.addAttribute("positions", positions);
        model.addAttribute("authUserIdAndAuthUserMap", authUserIdAndAuthUserMap);
        model.addAttribute("categoryIdAndCategoryMap", categoryIdAndCategoryMap);
        model.addAttribute("regionIdAndRegionMap", regionIdAndRegionMap);
        // model.addAttribute("positionIdAndCustomRetailerMap",
        // addedpositionIdAndCustomRetailerMap);
        // model.addAttribute("positionIdAndpartnerRegionMap",
// positionIdAndpartnerRegionMap);

        return "create-position";
    }

    @GetMapping(value = "/cs/position-paginated")
    public String positionPaginated(HttpServletRequest request, @RequestParam(name = "offset", defaultValue = "0") int offset, @RequestParam(name = "limit", defaultValue = "10") int limit, Model model) {

        List<Position> positions = positionRepository.selectAll(offset, limit);
        Map<Integer, AuthUser> authUserIdAndAuthUserMap = csService.getAuthUserIdAndAuthUserMapUsingPositions(positions);
        Map<Integer, TicketCategory> categoryIdAndCategoryMap = csService.getCategoryIdAndCategoryUsingPositions(positions);
        Map<Integer, Region> regionIdAndRegionMap = csService.getRegionIdAndRegionMap(positions);
        /*
         * Map<Integer, List<CustomRetailer>> positionIdAndpartnerRegionMap = csService
         * .getpositionIdAndpartnerRegionMap(positions);
         * 
         * Map<Integer, List<CustomRetailer>> addedpositionIdAndCustomRetailerMap =
         * csService .getPositionCustomRetailerMap(positions);
         */

        model.addAttribute("positions", positions);
        model.addAttribute("authUserIdAndAuthUserMap", authUserIdAndAuthUserMap);
        model.addAttribute("categoryIdAndCategoryMap", categoryIdAndCategoryMap);
        model.addAttribute("regionIdAndRegionMap", regionIdAndRegionMap);
        // model.addAttribute("positionIdAndCustomRetailerMap",
        // addedpositionIdAndCustomRetailerMap);
        // model.addAttribute("positionIdAndpartnerRegionMap",
        // positionIdAndpartnerRegionMap);

        return "position-paginated";
    }

    @PostMapping(value = "/cs/createPosition")
    public String createPosition(HttpServletRequest request, @RequestBody CreatePositionModel createPositionModel, Model model) throws Exception {

        LOGGER.info("partnerPosition" + createPositionModel.isTicketAssigned());

        // Validate authUserId exists
        int authUserId = createPositionModel.getAuthUserId();
        if (authUserId <= 0 || authRepository.selectById(authUserId) == null) {
            throw new ProfitMandiBusinessException("Position", authUserId, "Invalid authUserId");
        }

        // Validate categoryId exists (0 means "All Categories")
        int categoryId = createPositionModel.getCategoryId();
        if (categoryId > 0 && ticketCategoryRepository.selectById(categoryId) == null) {
            throw new ProfitMandiBusinessException("Position", categoryId, "Invalid categoryId");
        }

        // Validate regionId exists (0 or 5 typically means "All Partners/Regions")
        int regionId = createPositionModel.getRegionId();
        if (regionId > 0 && regionId != 5 && regionRepository.selectById(regionId) == null) {
            throw new ProfitMandiBusinessException("Position", regionId, "Invalid regionId");
        }

        // Validate fofoIds exist (0 means "All Partners")
        List<Integer> fofoIds = createPositionModel.getFofoIds();
        if (fofoIds == null || fofoIds.isEmpty()) {
            throw new ProfitMandiBusinessException("Position", 0, "At least one partner must be specified");
        }
        boolean hasAllPartners = fofoIds.contains(0);
        if (hasAllPartners && fofoIds.size() > 1) {
            throw new ProfitMandiBusinessException("Position", 0, "Cannot mix 'All Partners' (0) with specific partner IDs");
        }
        if (!hasAllPartners) {
            Map<Integer, CustomRetailer> validRetailers = retailerService.getFofoRetailers(false);
            for (int fofoId : fofoIds) {
                if (!validRetailers.containsKey(fofoId)) {
                    throw new ProfitMandiBusinessException("Position", fofoId, "Invalid fofoId");
                }
            }
        }

        Position position = positionRepository.selectPosition(authUserId, categoryId, regionId, createPositionModel.getEscalationType());
        if (position == null) {
            position = new Position();
            position.setAuthUserId(authUserId);
            position.setCategoryId(categoryId);
            position.setEscalationType(createPositionModel.getEscalationType());
            position.setRegionId(regionId);
            position.setCreateTimestamp(LocalDateTime.now());
            position.setTicketAssignee(createPositionModel.isTicketAssigned());
            positionRepository.persist(position);

            if (fofoIds != null) {
                for (int fofoId : fofoIds) {
                    PartnerPosition partnerPosition = new PartnerPosition();
                    partnerPosition.setFofoId(fofoId);
                    partnerPosition.setRegionId(regionId);
                    partnerPosition.setPositionId(position.getId());
                    partnerPositionRepository.persist(partnerPosition);
                    LOGGER.info("partnerPosition" + partnerPosition);
                }
            }

            model.addAttribute("response1", mvcResponseSender.createResponseString(true));
        } else {
            throw new ProfitMandiBusinessException("Position", authUserId, "already exists!");
        }
        return "response";
    }

    @PostMapping(value = "/cs/updatePartnerPosition")
    public String updatePartnerPosition(HttpServletRequest request, @RequestParam(name = "regionId") int regionId, @RequestBody List<Integer> selectedFofoIds, @RequestParam(name = "positionId") int positionId, Model model) throws Exception {

        // Validate positionId exists
        Position position = positionRepository.selectById(positionId);
        if (position == null) {
            throw new ProfitMandiBusinessException("Position", positionId, "Position not found");
        }

        // Validate regionId exists (0 or 5 typically means "All Partners/Regions")
        if (regionId > 0 && regionId != 5 && regionRepository.selectById(regionId) == null) {
            throw new ProfitMandiBusinessException("Position", regionId, "Invalid regionId");
        }

        // Validate fofoIds exist (0 means "All Partners")
        if (selectedFofoIds == null || selectedFofoIds.isEmpty()) {
            throw new ProfitMandiBusinessException("Position", 0, "At least one partner must be specified");
        }
        boolean hasAllPartners = selectedFofoIds.contains(0);
        if (hasAllPartners && selectedFofoIds.size() > 1) {
            throw new ProfitMandiBusinessException("Position", 0, "Cannot mix 'All Partners' (0) with specific partner IDs");
        }
        if (!hasAllPartners) {
            // Use direct database query instead of cached retailerService to include newly added partners
            Set<Integer> validFofoIds = fofoStoreRepository.selectAll().stream()
                    .map(fs -> fs.getId())
                    .collect(Collectors.toSet());
            for (int fofoId : selectedFofoIds) {
                if (!validFofoIds.contains(fofoId)) {
                    throw new ProfitMandiBusinessException("Position", fofoId, "Invalid fofoId");
                }
            }
        }

        partnerPositionRepository.delete(positionId);
        if (selectedFofoIds != null) {
            for (int fofoId : selectedFofoIds) {
                PartnerPosition partnerPosition = new PartnerPosition();
                partnerPosition.setFofoId(fofoId);
                partnerPosition.setRegionId(regionId);
                partnerPosition.setPositionId(positionId);
                partnerPositionRepository.persist(partnerPosition);
            }
        }

        model.addAttribute("response1", mvcResponseSender.createResponseString(true));

        return "response";
    }

    @GetMapping(value = "/cs/createTicket")
    public String createTicket(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        boolean isAdmin = roleManager.isAdmin(loginDetails.getRoleIds());
        boolean isCrmUser = false;

        List<TicketCategory> ticketCategories = csService.getAllTicketCategotyFromSubCategory();

        if (isAdmin) {
            AuthUser currentAuthUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
            isCrmUser = positionRepository.hasCategory(currentAuthUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);

            // CRM users should not see Sales and RBM categories
            if (isCrmUser) {
                ticketCategories = ticketCategories.stream()
                        .filter(tc -> tc.getId() != ProfitMandiConstants.TICKET_CATEGORY_SALES
                                && tc.getId() != ProfitMandiConstants.TICKET_CATEGORY_RBM)
                        .collect(Collectors.toList());
            }
        } else {
            // For partners/customers: hide categories where ALL subcategories are invisible
            ticketCategories = ticketCategories.stream()
                    .filter(category -> {
                        List<TicketSubCategory> subs = ticketSubCategoryRepository.selectAll(category.getId());
                        // Keep category if at least one subcategory is visible
                        return subs.stream().anyMatch(TicketSubCategory::isVisibility);
                    })
                    .collect(Collectors.toList());
        }

        model.addAttribute("roleType", isAdmin);
        model.addAttribute("ticketCategories", ticketCategories);
        model.addAttribute("isCrmUser", isCrmUser);
        return "create-ticket";
    }

    @GetMapping(value = "/cs/getSubCategoriesByCategoryId")
    public String getSubCategoriesByCategoryId(HttpServletRequest request, @RequestParam(name = "categoryId", defaultValue = "0") int categoryId, Model model) throws ProfitMandiBusinessException {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        boolean isAdmin = roleManager.isAdmin(loginDetails.getRoleIds());
        boolean isCrmUser = false;

        if (isAdmin) {
            AuthUser currentAuthUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
            isCrmUser = positionRepository.hasCategory(currentAuthUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);
        }

        List<TicketSubCategory> ticketSubCategories = ticketSubCategoryRepository.selectAllVisible(categoryId);

        // CRM users should not see invisible subcategories
        if (isCrmUser) {
            ticketSubCategories = ticketSubCategories.stream()
                    .filter(TicketSubCategory::isVisibility)
                    .collect(Collectors.toList());
        }

        LOGGER.info(ticketSubCategories);
        model.addAttribute("ticketSubCategories", ticketSubCategories);
        return "ticket-sub-categories";
    }


    @GetMapping(value = "/cs/getEscalationTypeByCategoryId")
    public String getEscalationTypeByCategoryId(HttpServletRequest request, @RequestParam(name = "categoryId", defaultValue = "0") int categoryId, @RequestParam(name = "authId", defaultValue = "0") int authId, Model model) {
        List<Position> positions = positionRepository.selectPositionbyCategoryIdAndAuthId(categoryId, authId);
        List<EscalationType> escalationTypes = new ArrayList<>();

        if (!positions.isEmpty()) {
            escalationTypes = positions.stream().map(x -> x.getEscalationType()).distinct().collect(Collectors.toList());
        }

        LOGGER.info("escalationTypes {}", escalationTypes);

        model.addAttribute("escalationTypes", escalationTypes);
        return "ticket-escalationtype";
    }


    @GetMapping(value = "/cs/getCategoriesByAuthId")
    public String getCategoriesByAuthId(HttpServletRequest request, @RequestParam(name = "authId", defaultValue = "0") int authId, Model model) {


        List<Position> positions = positionRepository.selectPositionByAuthId(authId);

        LOGGER.info("positions {}", positions);

        List<TicketCategory> ticketCategories = new ArrayList<TicketCategory>();

        if (!positions.isEmpty()) {

            List<Integer> categoryIds = positions.stream().map(x -> x.getCategoryId()).collect(Collectors.toList());
            ticketCategories = ticketCategoryRepository.selectAll(categoryIds);
        }
        LOGGER.info("ticketCategories {}", ticketCategories);
        model.addAttribute("ticketCategories", ticketCategories);
        return "ticket-categories";
    }

    @PostMapping(value = "/cs/createTicket")
    public String createTicket(HttpServletRequest request, @RequestParam(name = "categoryId") int categoryId, @RequestParam(name = "subCategoryId") int subCategoryId, @RequestParam(name = "message") String message, Model model) throws Exception {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        List<Ticket> tickets = ticketRepository.selectAllResolvedMarkedTicketByCreator(loginDetails.getFofoId());
        if (tickets.size() > 3 || tickets.size() == 3) {
            model.addAttribute("response1", mvcResponseSender.createResponseString(false));
        } else {
            csService.createTicket(loginDetails.getFofoId(), categoryId, subCategoryId, message);
            model.addAttribute("response1", mvcResponseSender.createResponseString(true));
        }
        return "response";
    }

    @GetMapping(value = "/cs/myticket")
    public String getTicket(HttpServletRequest request,
                            @RequestParam(name = "orderby", defaultValue = "DESCENDING") SortOrder sortOrder,
                            @RequestParam(name = "ticketStatus", defaultValue = "OPENED") TicketStatus ticketStatus,
                            @RequestParam(name = "ticketSearchType", defaultValue = "") TicketSearchType ticketSearchType,
                            @RequestParam(name = "searchTerm", defaultValue = "0") int searchTerm,
                            @RequestParam(name = "page", defaultValue = "0") int page,
                            @RequestParam(name = "pageSize", defaultValue = "25") int pageSize,
                            @RequestParam(name = "search", required = false) String searchText,
                            Model model) throws ProfitMandiBusinessException {
        populateMyTicketModel(request, sortOrder, ticketStatus, ticketSearchType, searchTerm, page, pageSize, searchText, model);
        return "ticket";
    }

    @GetMapping(value = "/cs/myticket-content")
    public String getTicketContent(HttpServletRequest request,
                                   @RequestParam(name = "orderby", defaultValue = "DESCENDING") SortOrder sortOrder,
                                   @RequestParam(name = "ticketStatus", defaultValue = "OPENED") TicketStatus ticketStatus,
                                   @RequestParam(name = "ticketSearchType", defaultValue = "") TicketSearchType ticketSearchType,
                                   @RequestParam(name = "searchTerm", defaultValue = "0") int searchTerm,
                                   @RequestParam(name = "page", defaultValue = "0") int page,
                                   @RequestParam(name = "pageSize", defaultValue = "25") int pageSize,
                                   @RequestParam(name = "search", required = false) String searchText,
                                   Model model) throws ProfitMandiBusinessException {
        populateMyTicketModel(request, sortOrder, ticketStatus, ticketSearchType, searchTerm, page, pageSize, searchText, model);
        return "ticket-content";
    }

    private void populateMyTicketModel(HttpServletRequest request, SortOrder sortOrder, TicketStatus ticketStatus,
                                       TicketSearchType ticketSearchType, int searchTerm, int page, int pageSize,
                                       String searchText, Model model) throws ProfitMandiBusinessException {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        List<Ticket> tickets = null;
        List<TicketAssigned> ticketAssigneds = null;
        long totalRecords = 0;
        Map<Integer, AuthUser> authUserIdAndAuthUserMap = null;
        boolean isAdmin = roleManager.isAdmin(new HashSet<>(loginDetails.getRoleIds()));
        AuthUser currentAuthUser = isAdmin ? authRepository.selectByEmailOrMobile(loginDetails.getEmailId()) : null;

        boolean isCrmUser = isAdmin && currentAuthUser != null && positionRepository.hasCategory(currentAuthUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);
        boolean isSalesUser = isAdmin && currentAuthUser != null && positionRepository.hasCategory(currentAuthUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_SALES);
        boolean isRbmUser = isAdmin && currentAuthUser != null && positionRepository.hasCategory(currentAuthUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_RBM);

        List<Integer> userCategoryIds = isAdmin && currentAuthUser != null
                ? positionRepository.selectCategoryIdsByAuthUserId(currentAuthUser.getId())
                : Collections.emptyList();

        int offset = page * pageSize;

        if (isAdmin) {
            int authUserId = currentAuthUser.getId();

            if (isCrmUser) {
                Optional<Boolean> resolvedOpt = ticketStatus.equals(TicketStatus.RESOLVED) ? Optional.empty() : Optional.of(TicketStatus.CLOSED.equals(ticketStatus));
                tickets = ticketRepository.selectAllTicketsPaginated(
                        resolvedOpt, sortOrder, ticketSearchType, searchTerm, searchText,
                        userCategoryIds, offset, pageSize);
                totalRecords = ticketRepository.selectAllTicketsPaginatedCount(
                        resolvedOpt, ticketSearchType, searchTerm, searchText, userCategoryIds);
            } else {
                Optional<Boolean> resolvedOpt = ticketStatus.equals(TicketStatus.RESOLVED) ? Optional.empty() : Optional.of(TicketStatus.CLOSED.equals(ticketStatus));
                tickets = ticketRepository.selectAllByAssigneePaginated(
                        authUserId, resolvedOpt, sortOrder, ticketSearchType, searchTerm, searchText,
                        offset, pageSize);
                totalRecords = ticketRepository.selectAllByAssigneePaginatedCount(
                        authUserId, resolvedOpt, ticketSearchType, searchTerm, searchText);
            }

            if (tickets.size() > 0) {
                ticketAssigneds = ticketAssignedRepository.selectByTicketIds(tickets.stream().map(x -> x.getId()).collect(Collectors.toList()));
                authUserIdAndAuthUserMap = csService.getAuthUserIdAndAuthUserMap(ticketAssigneds);
                Map<Integer, CustomRetailer> fofoIdsAndCustomRetailer = csService.getPartnerByFofoIds(tickets);
                model.addAttribute("fofoIdsAndCustomRetailer", fofoIdsAndCustomRetailer);
            }

        } else {
            tickets = ticketRepository.selectAllByCreator(loginDetails.getFofoId(), Optional.of(TicketStatus.OPENED.equals(ticketStatus)), sortOrder);
            totalRecords = ticketRepository.selectAllCountByCreator(loginDetails.getFofoId(), Optional.of(TicketStatus.OPENED.equals(ticketStatus)));
        }
        authUserIdAndAuthUserMap = csService.getTicketIdAndAuthUserMapUsingTickets(tickets);
        if (authUserIdAndAuthUserMap == null) {
            authUserIdAndAuthUserMap = new HashMap<>();
        }

        int totalPages = (int) Math.ceil((double) totalRecords / pageSize);
        if (totalPages == 0) totalPages = 1;
        int startRecord = totalRecords > 0 ? offset + 1 : 0;
        int endRecord = (int) Math.min(offset + pageSize, totalRecords);

        model.addAttribute("size", totalRecords);
        model.addAttribute("totalRecords", totalRecords);
        model.addAttribute("currentPage", page);
        model.addAttribute("pageSize", pageSize);
        model.addAttribute("totalPages", totalPages);
        model.addAttribute("startRecord", startRecord);
        model.addAttribute("endRecord", endRecord);
        model.addAttribute("searchText", searchText != null ? searchText : "");
        model.addAttribute("currentPageDisplay", page + 1);
        model.addAttribute("prevPage", page - 1);
        model.addAttribute("nextPage", page + 1);
        model.addAttribute("lastPage", totalPages - 1);

        model.addAttribute("roleType", isAdmin);
        model.addAttribute("isCrmUser", isCrmUser);
        model.addAttribute("isSalesUser", isSalesUser);
        model.addAttribute("salesCategoryId", ProfitMandiConstants.TICKET_CATEGORY_SALES);
        model.addAttribute("isRbmUser", isRbmUser);
        model.addAttribute("rbmCategoryId", ProfitMandiConstants.TICKET_CATEGORY_RBM);
        model.addAttribute("userCategoryIds", userCategoryIds);

        List<Integer> subCategoryIds = tickets.stream().map(x -> x.getSubCategoryId()).collect(Collectors.toList());
        Map<Integer, TicketSubCategory> subCategoryIdAndSubCategoryMap = csService.getSubCategoryIdAndSubCategoryMap(subCategoryIds);
        if (subCategoryIdAndSubCategoryMap == null) {
            subCategoryIdAndSubCategoryMap = new HashMap<>();
        }

        Map<Integer, TicketCategory> subCategoryIdAndCategoryMap = csService.getSubCategoryIdAndCategoryMap(subCategoryIds);
        if (subCategoryIdAndCategoryMap == null) {
            subCategoryIdAndCategoryMap = new HashMap<>();
        }

        List<Integer> ticketIds = tickets.stream().map(x -> x.getId()).collect(Collectors.toList());

        Map<Integer, List<Activity>> activityMap = new HashMap<>();
        Map<Integer, List<Activity>> activityMapWithActivityId = new HashMap<>();
        Map<Integer, AuthUser> authUserMap = new HashMap<>();

        if (!ticketIds.isEmpty()) {
            List<Activity> allActivities = activityRepository.selectAll(ticketIds);
            activityMap = allActivities.stream().collect(Collectors.groupingBy(Activity::getTicketId));
            activityMapWithActivityId = allActivities.stream().collect(Collectors.groupingBy(Activity::getId));

            Set<Integer> activityCreatorIds = allActivities.stream()
                    .map(Activity::getCreatedBy)
                    .filter(id -> id > 0)
                    .collect(Collectors.toSet());
            if (!activityCreatorIds.isEmpty()) {
                authUserMap = authRepository.selectByIds(new ArrayList<>(activityCreatorIds))
                        .stream().collect(Collectors.toMap(AuthUser::getId, x -> x));
            }
        }

        int currentUserId;
        UserType currentUserType;
        if (isAdmin) {
            currentUserId = currentAuthUser.getId();
            currentUserType = UserType.AUTH_USER;
        } else {
            currentUserId = loginDetails.getFofoId();
            currentUserType = UserType.PARTNER;
        }
        Map<Integer, Boolean> unreadMap = csService.getUnreadStatusForTickets(tickets, currentUserId, currentUserType);
        Map<Integer, Activity> lastActivityMap = csService.getLastActivitiesForTickets(ticketIds);
        Map<Integer, Activity> lastMessageMap = csService.getLastMessageActivitiesForTickets(ticketIds);
        model.addAttribute("unreadMap", unreadMap);
        model.addAttribute("lastActivityMap", lastActivityMap);
        model.addAttribute("lastMessageMap", lastMessageMap);

        model.addAttribute("tickets", tickets);
        model.addAttribute("resolved", ActivityType.RESOLVED);
        model.addAttribute("resolved-accepted", ActivityType.RESOLVED_ACCEPTED);
        model.addAttribute("resolved-rejected", ActivityType.RESOLVED_REJECTED);
        model.addAttribute("authUserIdAndAuthUserMap", authUserIdAndAuthUserMap);
        model.addAttribute("subCategoryIdAndSubCategoryMap", subCategoryIdAndSubCategoryMap);

        model.addAttribute("subCategoryIdAndCategoryMap", subCategoryIdAndCategoryMap);
        model.addAttribute("activityMap", activityMap);
        model.addAttribute("authUserMap", authUserMap);
        model.addAttribute("activityMapWithActivityId", activityMapWithActivityId);

        model.addAttribute("ticketStatusValues", TicketStatus.values());
        model.addAttribute("orderByValues", SortOrder.values());
        model.addAttribute("selectedticketStatus", ticketStatus);
        model.addAttribute("selectedorderby", sortOrder);
        model.addAttribute("ticketSearchTypes", TicketSearchType.values());
        model.addAttribute("ticketSearchType", ticketSearchType);
        model.addAttribute("searchTerm", searchTerm);
    }


    @GetMapping(value = "/cs/getActivities")
    public String getActivity(HttpServletRequest request, @RequestParam(name = "ticketId", defaultValue = "0") int ticketId, Model model) throws Exception {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        Ticket ticket = ticketRepository.selectById(ticketId);

        if (ticket == null) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "Ticket not found");
        }

        // Authorization check: verify user has access to this ticket
        boolean isAdmin = roleManager.isAdmin(loginDetails.getRoleIds());
        if (!isAdmin) {
            // Partners can only view their own tickets
            if (ticket.getFofoId() != loginDetails.getFofoId()) {
                throw new ProfitMandiBusinessException("Ticket", ticketId, "You do not have permission to view this ticket");
            }
        } else {
            // Admins must be assigned to the ticket OR be CRM user OR be in the escalation chain
            AuthUser currentAuthUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
            boolean isCrmUser = positionRepository.hasCategory(currentAuthUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);

            if (!isCrmUser) {
                // Check if user is assigned or in escalation chain
                List<TicketAssigned> assignments = ticketAssignedRepository.selectByTicketIds(Arrays.asList(ticketId));
                boolean isAssigned = assignments.stream().anyMatch(ta -> ta.getAssineeId() == currentAuthUser.getId() || ta.getManagerId() == currentAuthUser.getId());
                boolean isInEscalationChain = ticket.getL1AuthUser() == currentAuthUser.getId() ||
                        ticket.getL2AuthUser() == currentAuthUser.getId() ||
                        ticket.getL3AuthUser() == currentAuthUser.getId() ||
                        ticket.getL4AuthUser() == currentAuthUser.getId() ||
                        ticket.getL5AuthUser() == currentAuthUser.getId();

                if (!isAssigned && !isInEscalationChain) {
                    throw new ProfitMandiBusinessException("Ticket", ticketId, "You do not have permission to view this ticket");
                }
            }
        }

        List<Activity> allactivities = activityRepository.selectAll(ticketId);

        // Batch fetch all documents for attachments (fix N+1 query)
        List<Integer> documentIds = allactivities.stream()
                .flatMap(a -> a.getActivityAttachment().stream())
                .map(ActivityAttachment::getDocumentId)
                .distinct()
                .collect(Collectors.toList());

        Map<Integer, Document> documentMap = Collections.emptyMap();
        if (!documentIds.isEmpty()) {
            documentMap = documentRepository.selectByIds(documentIds).stream()
                    .collect(Collectors.toMap(Document::getId, d -> d));
        }

        // Set document names transiently (do not persist during GET)
        for (Activity activity : allactivities) {
            for (ActivityAttachment attachment : activity.getActivityAttachment()) {
                Document document = documentMap.get(attachment.getDocumentId());
                if (document != null) {
                    attachment.setDocumentName(document.getDisplayName());
                }
            }
        }
        List<Activity> activities = null;
        if (isAdmin) {
            Set<Integer> authUserIds = allactivities.stream().map(x -> x.getCreatedBy()).collect(Collectors.toSet());
            List<AuthUser> users = authRepository.selectByIds(new ArrayList<>(authUserIds));
            Map<Integer, String> authUserNameMap = users.stream().collect(Collectors.toMap(AuthUser::getId, x -> x.getFirstName() + " " + x.getLastName()));
            allactivities.stream().forEach(x -> x.setName(authUserNameMap.get(x.getCreatedBy())));
            activities = allactivities;
        } else {
            activities = allactivities.stream().filter(x -> ActivityType.PARTNER_ACTIVITIES.contains(x.getType())).collect(Collectors.toList());
        }
        if (activities == null) {
            throw new ProfitMandiBusinessException("Activity", ticketId, "No Activity Found");
        }
        model.addAttribute("response1", mvcResponseSender.createResponseString(activities));
        return "response";

    }

    @PostMapping(value = "/cs/createActivity")
    public String createActivity(HttpServletRequest request, @RequestParam(name = "ticketId", defaultValue = "0") int ticketId, @RequestParam(name = "assigneeId", defaultValue = "0") int assigneeId, @RequestParam(name = "internal", defaultValue = "true") boolean internal, @RequestParam(name = "message", defaultValue = "") String message, @RequestBody List<Integer> documentIds,

                                 Model model) throws Exception {

        LOGGER.info("documentIds" + documentIds);
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        Ticket ticket = ticketRepository.selectById(ticketId);

        if (ticket == null) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "Ticket not found");
        }

        // Authorization check: verify user has access to add activity to this ticket
        boolean isAdmin = roleManager.isAdmin(loginDetails.getRoleIds());
        if (!isAdmin) {
            // Partners can only add activity to their own tickets
            if (ticket.getFofoId() != loginDetails.getFofoId()) {
                throw new ProfitMandiBusinessException("Ticket", ticketId, "You do not have permission to add activity to this ticket");
            }
        } else {
            // Admins must be assigned to the ticket OR be CRM user OR be in the escalation chain
            AuthUser currentAuthUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
            boolean isCrmUser = positionRepository.hasCategory(currentAuthUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);

            if (!isCrmUser) {
                List<TicketAssigned> assignments = ticketAssignedRepository.selectByTicketIds(Arrays.asList(ticketId));
                boolean isAssigned = assignments.stream().anyMatch(ta -> ta.getAssineeId() == currentAuthUser.getId() || ta.getManagerId() == currentAuthUser.getId());
                boolean isInEscalationChain = ticket.getL1AuthUser() == currentAuthUser.getId() ||
                        ticket.getL2AuthUser() == currentAuthUser.getId() ||
                        ticket.getL3AuthUser() == currentAuthUser.getId() ||
                        ticket.getL4AuthUser() == currentAuthUser.getId() ||
                        ticket.getL5AuthUser() == currentAuthUser.getId();

                if (!isAssigned && !isInEscalationChain) {
                    throw new ProfitMandiBusinessException("Ticket", ticketId, "You do not have permission to add activity to this ticket");
                }
            }
        }
        List<TicketAssigned> ticketAssignedList = ticketAssignedRepository.selectByTicketIds(Arrays.asList(ticketId));
        List<Integer> authUserIds = ticketAssignedList.stream().map(x -> x.getAssineeId()).collect(Collectors.toList());
        authUserIds.add(ticketAssignedList.get(ticketAssignedList.size() - 1).getManagerId());
        Map<Integer, AuthUser> authUsersMap = authRepository.selectByIds(authUserIds).stream().collect(Collectors.toMap(x -> x.getId(), x -> x));
        if (ticket.getCloseTimestamp() == null) {
            Activity activity = new Activity();
            activity.setCreatedBy(0);
            activity.setCreateTimestamp(LocalDateTime.now());
            String subject = null;
            String mailMessage = null;
            activity.setMessage(message);
            if (!roleManager.isAdmin(new HashSet<>(loginDetails.getRoleIds()))) {
                CustomRetailer customRetailer = retailerService.getFofoRetailers(true).get(loginDetails.getFofoId());
                activity.setType(ActivityType.COMMUNICATION_IN);
                subject = String.format("Ticket Update #%s by franchisee %s", ticket.getId(), customRetailer.getBusinessName() + "(" + customRetailer.getCode() + ")");
                mailMessage = String.format("Franchisee message - %s", message);
            } else {
                AuthUser authUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
                activity.setCreatedBy(authUser.getId());
                authUsersMap.remove(authUser.getId());
                subject = String.format("Ticket Update #%s by %s", ticket.getId(), authUser.getName());
                mailMessage = String.format("%s's message - %s", authUser.getFirstName(), message);
                // Only CRM users can send external communications
                boolean isCrmUser = positionRepository.hasCategory(authUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);
                if (internal || !isCrmUser) {
                    activity.setType(ActivityType.COMMUNICATION_INTERNAL);
                } else {
                    String updatedBy = "SD Team";
                    CustomRetailer customRetailer = retailerService.getFofoRetailers(false).get(ticket.getFofoId());
                    subject = String.format("Ticket Update #%s by %s", ticket.getId(), updatedBy);
                    String partnerMessage = String.format("%s's message - %s", updatedBy, message);
                    this.activityRelatedMail(customRetailer.getEmail(), null, "subject", partnerMessage);
                    activity.setType(ActivityType.COMMUNICATION_OUT);
                }
            }
            activityRepository.persist(activity);

            for (Integer documentId : documentIds) {
                ActivityAttachment activityAttachment = new ActivityAttachment();
                activityAttachment.setActivityId(activity.getId());
                activityAttachment.setDocumentId(documentId);
                activityAttachmentRepository.persist(activityAttachment);
            }

            csService.addActivity(ticket, activity);
            AuthUser authUser = authUsersMap.remove(authUserIds.get(0));
            if (authUser == null) {
                authUser = authUsersMap.remove(authUserIds.get(1));
            }
            model.addAttribute("response1", mvcResponseSender.createResponseString(authUser));
            String[] cc = authUsersMap.entrySet().stream().map(x -> x.getValue().getEmailId()).toArray(String[]::new);
            this.activityRelatedMail(authUser.getEmailId(), cc, subject, mailMessage);
        } else {
            throw new ProfitMandiBusinessException("Ticket", ticket.getId(), "Already closed ticket");
        }
        return "response";
    }

    private void activityRelatedMail(String to, String[] cc, String subject, String message) throws ProfitMandiBusinessException {
        try {
            Utils.sendMailWithAttachments(mailSender, to, cc, subject, message, null);
        } catch (Exception e) {
            throw new ProfitMandiBusinessException("Ticket Activity", to, "Could not send ticket activity mail");
        }
    }

    @PostMapping(value = "/cs/closeTicket")
    public String closeTicket(HttpServletRequest request, @RequestParam(name = "ticketId", defaultValue = "0") int ticketId, @RequestParam(name = "happyCode") String happyCode, Model model) throws Exception {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        Ticket ticket = ticketRepository.selectById(ticketId);

        if (ticket == null) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "Ticket not found");
        }

        // Authorization check: only the ticket owner (partner) can close the ticket
        boolean isAdmin = roleManager.isAdmin(loginDetails.getRoleIds());
        if (isAdmin) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "Only partners can close tickets using happy code");
        }

        // Verify the partner owns this ticket
        if (ticket.getFofoId() != loginDetails.getFofoId()) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "You do not have permission to close this ticket");
        }

        if (ticket.getHappyCode().equals(happyCode)) {
            ticket.setCloseTimestamp(LocalDateTime.now());
            ticketRepository.persist(ticket);
            model.addAttribute("response1", mvcResponseSender.createResponseString(true));
        } else {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "Happy Code can't match");
        }
        return "response";
    }

    @GetMapping(value = "/cs/myPartyTicketTicket")
    public String getMyPartyTicketTicket(HttpServletRequest request, @RequestParam(name = "orderby", defaultValue = "DESCENDING") SortOrder sortOrder, @RequestParam(name = "ticketStatus", defaultValue = "OPENED") TicketStatus ticketStatus, @RequestParam(name = "ticketSearchType", defaultValue = "") TicketSearchType ticketSearchType, @RequestParam(name = "searchTerm", defaultValue = "0") int searchTerm, Model model) throws ProfitMandiBusinessException {
        populateMyPartnerTicketModel(request, sortOrder, ticketStatus, ticketSearchType, searchTerm, model);
        return "my-partner-tickets";
    }

    @GetMapping(value = "/cs/myPartyTicket-content")
    public String getMyPartyTicketContent(HttpServletRequest request,
                                          @RequestParam(name = "orderby", defaultValue = "DESCENDING") SortOrder sortOrder,
                                          @RequestParam(name = "ticketStatus", defaultValue = "OPENED") TicketStatus ticketStatus,
                                          @RequestParam(name = "ticketSearchType", defaultValue = "") TicketSearchType ticketSearchType,
                                          @RequestParam(name = "searchTerm", defaultValue = "0") int searchTerm,
                                          Model model) throws ProfitMandiBusinessException {
        populateMyPartnerTicketModel(request, sortOrder, ticketStatus, ticketSearchType, searchTerm, model);
        return "my-partner-tickets-content";
    }

    private void populateMyPartnerTicketModel(HttpServletRequest request, SortOrder sortOrder, TicketStatus ticketStatus,
                                              TicketSearchType ticketSearchType, int searchTerm, Model model) throws ProfitMandiBusinessException {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        AuthUser authUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
        List<Ticket> tickets = new ArrayList<>();
        Map<String, Set<Integer>> storeGuyMap = csService.getAuthUserPartnerIdMapping();
        Set<Integer> fofoIds = storeGuyMap.get(authUser.getEmailId());

        Map<Integer, List<AuthUser>> authUserListMap = null;
        if (fofoIds != null && !fofoIds.isEmpty()) {
            List<Ticket> allPartnerTickets = ticketRepository.selectAllOpenTickets(new ArrayList<>(fofoIds));
            if (allPartnerTickets != null && !allPartnerTickets.isEmpty()) {
                tickets = allPartnerTickets.stream()
                        .filter(ticket -> ticket.getLastActivity() == null ||
                                ticket.getLastActivity() != ActivityType.RESOLVED)
                        .collect(Collectors.toList());
            }
        }

        authUserListMap = csService.getAssignedAuthList(tickets);

        if (tickets.size() > 0) {
            Map<Integer, CustomRetailer> fofoIdsAndCustomRetailer = csService.getPartnerByFofoIds(tickets);
            model.addAttribute("fofoIdsAndCustomRetailer", fofoIdsAndCustomRetailer);
        }

        // Pagination for partner tickets (in-memory since selectAllOpenTickets doesn't support DB-level pagination)
        long totalRecords = tickets.size();
        int totalPages = totalRecords > 0 ? (int) Math.ceil((double) totalRecords / 25) : 1;
        model.addAttribute("totalRecords", totalRecords);
        model.addAttribute("currentPage", 0);
        model.addAttribute("pageSize", 25);
        model.addAttribute("totalPages", totalPages);
        model.addAttribute("startRecord", totalRecords > 0 ? 1 : 0);
        model.addAttribute("endRecord", totalRecords);
        model.addAttribute("currentPageDisplay", 1);
        model.addAttribute("prevPage", 0);
        model.addAttribute("nextPage", 1);
        model.addAttribute("lastPage", totalPages - 1);

        model.addAttribute("size", tickets.size());
        model.addAttribute("tickets", tickets);

        List<Integer> subCategoryIds = tickets.stream().map(x -> x.getSubCategoryId()).collect(Collectors.toList());
        Map<Integer, TicketSubCategory> subCategoryIdAndSubCategoryMap = csService.getSubCategoryIdAndSubCategoryMap(subCategoryIds);
        if (subCategoryIdAndSubCategoryMap == null) {
            subCategoryIdAndSubCategoryMap = new HashMap<>();
        }

        Map<Integer, TicketCategory> subCategoryIdAndCategoryMap = csService.getSubCategoryIdAndCategoryMap(subCategoryIds);
        if (subCategoryIdAndCategoryMap == null) {
            subCategoryIdAndCategoryMap = new HashMap<>();
        }

        List<Integer> ticketIds = tickets.stream().map(x -> x.getId()).collect(Collectors.toList());
        Map<Integer, List<Activity>> activityMap = new HashMap<>();
        Map<Integer, List<Activity>> activityMapWithActivityId = new HashMap<>();
        Map<Integer, AuthUser> authUserMap = new HashMap<>();

        if (!ticketIds.isEmpty()) {
            List<Activity> allActivities = activityRepository.selectAll(ticketIds);
            activityMap = allActivities.stream().collect(Collectors.groupingBy(Activity::getTicketId));
            activityMapWithActivityId = allActivities.stream().collect(Collectors.groupingBy(Activity::getId));

            Set<Integer> activityCreatorIds = allActivities.stream()
                    .map(Activity::getCreatedBy)
                    .filter(id -> id > 0)
                    .collect(Collectors.toSet());
            if (!activityCreatorIds.isEmpty()) {
                authUserMap = authRepository.selectByIds(new ArrayList<>(activityCreatorIds))
                        .stream().collect(Collectors.toMap(AuthUser::getId, x -> x));
            }
        }

        Map<Integer, Boolean> unreadMap = csService.getUnreadStatusForTickets(tickets, authUser.getId(), UserType.AUTH_USER);
        List<Integer> partnerTicketIds = tickets.stream().map(Ticket::getId).collect(Collectors.toList());
        Map<Integer, Activity> lastActivityMap = csService.getLastActivitiesForTickets(partnerTicketIds);
        Map<Integer, Activity> lastMessageMap = csService.getLastMessageActivitiesForTickets(partnerTicketIds);
        model.addAttribute("unreadMap", unreadMap);
        model.addAttribute("lastActivityMap", lastActivityMap);
        model.addAttribute("lastMessageMap", lastMessageMap);

        model.addAttribute("ticketStatusValues", TicketStatus.values());
        model.addAttribute("orderByValues", SortOrder.values());
        model.addAttribute("selectedticketStatus", ticketStatus);
        model.addAttribute("selectedorderby", sortOrder);
        model.addAttribute("ticketSearchTypes", TicketSearchType.values());
        model.addAttribute("ticketSearchType", ticketSearchType);
        model.addAttribute("searchTerm", searchTerm);
        model.addAttribute("authUserListMap", authUserListMap != null ? authUserListMap : new HashMap<>());
        model.addAttribute("subCategoryIdAndSubCategoryMap", subCategoryIdAndSubCategoryMap);

        model.addAttribute("subCategoryIdAndCategoryMap", subCategoryIdAndCategoryMap);

        model.addAttribute("activityMap", activityMap);
        model.addAttribute("authUserMap", authUserMap);
        model.addAttribute("activityMapWithActivityId", activityMapWithActivityId);
        boolean isCrmUser = positionRepository.hasCategory(authUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);
        model.addAttribute("isCrmUser", isCrmUser);
    }

    @GetMapping(value = "/cs/managerTicket")
    public String getManagerTickets(HttpServletRequest request,
                                    @RequestParam(name = "orderby", defaultValue = "DESCENDING") SortOrder sortOrder,
                                    @RequestParam(name = "ticketStatus", defaultValue = "OPENED") TicketStatus ticketStatus,
                                    @RequestParam(name = "ticketSearchType", defaultValue = "") TicketSearchType ticketSearchType,
                                    @RequestParam(name = "searchTerm", defaultValue = "0") int searchTerm,
                                    @RequestParam(name = "page", defaultValue = "0") int page,
                                    @RequestParam(name = "pageSize", defaultValue = "25") int pageSize,
                                    @RequestParam(name = "search", required = false) String searchText,
                                    Model model) throws ProfitMandiBusinessException {
        populateManagerTicketModel(request, sortOrder, ticketStatus, ticketSearchType, searchTerm, page, pageSize, searchText, model);
        return "managerTicket";
    }

    @GetMapping(value = "/cs/managerTicket-content")
    public String getManagerTicketContent(HttpServletRequest request,
                                          @RequestParam(name = "orderby", defaultValue = "DESCENDING") SortOrder sortOrder,
                                          @RequestParam(name = "ticketStatus", defaultValue = "OPENED") TicketStatus ticketStatus,
                                          @RequestParam(name = "ticketSearchType", defaultValue = "") TicketSearchType ticketSearchType,
                                          @RequestParam(name = "searchTerm", defaultValue = "0") int searchTerm,
                                          @RequestParam(name = "page", defaultValue = "0") int page,
                                          @RequestParam(name = "pageSize", defaultValue = "25") int pageSize,
                                          @RequestParam(name = "search", required = false) String searchText,
                                          Model model) throws ProfitMandiBusinessException {
        populateManagerTicketModel(request, sortOrder, ticketStatus, ticketSearchType, searchTerm, page, pageSize, searchText, model);
        return "managerTicket-content";
    }

    private void populateManagerTicketModel(HttpServletRequest request, SortOrder sortOrder, TicketStatus ticketStatus,
                                            TicketSearchType ticketSearchType, int searchTerm, int page, int pageSize,
                                            String searchText, Model model) throws ProfitMandiBusinessException {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        long totalRecords = 0;
        AuthUser authUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());

        // Block L1-only users from accessing manager tickets
        List<Position> positions = positionRepository.selectAllByAuthUserId(authUser.getId());
        boolean isAboveL1 = positions.stream().anyMatch(pos -> pos.getEscalationType() != EscalationType.L1);
        if (!isAboveL1) {
            throw new ProfitMandiBusinessException("ManagerTicket", 0, "Access denied: requires at least L2 position");
        }

        boolean isCrmUser = positionRepository.hasCategory(authUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);

        List<Integer> userCategoryIds = positionRepository.selectCategoryIdsByAuthUserId(authUser.getId());

        List<Ticket> tickets = null;
        Map<Integer, List<AuthUser>> authUserListMap = null;

        int offset = page * pageSize;
        Optional<Boolean> resolvedOpt = ticketStatus.equals(TicketStatus.RESOLVED) ? Optional.empty() : Optional.of(TicketStatus.CLOSED.equals(ticketStatus));

        if (isCrmUser) {
            tickets = ticketRepository.selectAllTicketsPaginated(
                    resolvedOpt, sortOrder, ticketSearchType, searchTerm, searchText,
                    userCategoryIds, offset, pageSize);
            totalRecords = ticketRepository.selectAllTicketsPaginatedCount(
                    resolvedOpt, ticketSearchType, searchTerm, searchText, userCategoryIds);
        } else {
            tickets = ticketRepository.selectAllManagerTicketPaginated(
                    authUser.getId(), sortOrder, resolvedOpt, ticketSearchType, searchTerm, searchText,
                    offset, pageSize);
            totalRecords = ticketRepository.selectAllCountByManagerTicketPaginated(
                    authUser.getId(), resolvedOpt, ticketSearchType, searchTerm, searchText);
        }
        authUserListMap = csService.getAssignedAuthList(tickets);

        if (tickets.size() > 0) {
            Map<Integer, CustomRetailer> fofoIdsAndCustomRetailer = csService.getPartnerByFofoIds(tickets);
            model.addAttribute("fofoIdsAndCustomRetailer", fofoIdsAndCustomRetailer);
        }

        int totalPages = (int) Math.ceil((double) totalRecords / pageSize);
        if (totalPages == 0) totalPages = 1;
        int startRecord = totalRecords > 0 ? offset + 1 : 0;
        int endRecord = (int) Math.min(offset + pageSize, totalRecords);

        model.addAttribute("size", totalRecords);
        model.addAttribute("totalRecords", totalRecords);
        model.addAttribute("currentPage", page);
        model.addAttribute("pageSize", pageSize);
        model.addAttribute("totalPages", totalPages);
        model.addAttribute("startRecord", startRecord);
        model.addAttribute("endRecord", endRecord);
        model.addAttribute("searchText", searchText != null ? searchText : "");
        model.addAttribute("currentPageDisplay", page + 1);
        model.addAttribute("prevPage", page - 1);
        model.addAttribute("nextPage", page + 1);
        model.addAttribute("lastPage", totalPages - 1);
        model.addAttribute("tickets", tickets);

        List<Integer> subCategoryIds = tickets.stream().map(x -> x.getSubCategoryId()).collect(Collectors.toList());
        Map<Integer, TicketSubCategory> subCategoryIdAndSubCategoryMap = csService.getSubCategoryIdAndSubCategoryMap(subCategoryIds);
        if (subCategoryIdAndSubCategoryMap == null) {
            subCategoryIdAndSubCategoryMap = new HashMap<>();
        }

        Map<Integer, TicketCategory> subCategoryIdAndCategoryMap = csService.getSubCategoryIdAndCategoryMap(subCategoryIds);
        if (subCategoryIdAndCategoryMap == null) {
            subCategoryIdAndCategoryMap = new HashMap<>();
        }

        List<Integer> ticketIds = tickets.stream().map(x -> x.getId()).collect(Collectors.toList());
        Map<Integer, List<Activity>> activityMap = new HashMap<>();
        Map<Integer, List<Activity>> activityMapWithActivityId = new HashMap<>();
        Map<Integer, AuthUser> authUserMap = new HashMap<>();

        if (!ticketIds.isEmpty()) {
            List<Activity> allActivities = activityRepository.selectAll(ticketIds);
            activityMap = allActivities.stream().collect(Collectors.groupingBy(Activity::getTicketId));
            activityMapWithActivityId = allActivities.stream().collect(Collectors.groupingBy(Activity::getId));

            Set<Integer> activityCreatorIds = allActivities.stream()
                    .map(Activity::getCreatedBy)
                    .filter(id -> id > 0)
                    .collect(Collectors.toSet());
            if (!activityCreatorIds.isEmpty()) {
                authUserMap = authRepository.selectByIds(new ArrayList<>(activityCreatorIds))
                        .stream().collect(Collectors.toMap(AuthUser::getId, x -> x));
            }
        }

        Map<Integer, Boolean> unreadMap = csService.getUnreadStatusForTickets(tickets, authUser.getId(), UserType.AUTH_USER);
        List<Integer> managerTicketIds = tickets.stream().map(Ticket::getId).collect(Collectors.toList());
        Map<Integer, Activity> lastActivityMap = csService.getLastActivitiesForTickets(managerTicketIds);
        Map<Integer, Activity> lastMessageMap = csService.getLastMessageActivitiesForTickets(managerTicketIds);
        model.addAttribute("unreadMap", unreadMap);
        model.addAttribute("lastActivityMap", lastActivityMap);
        model.addAttribute("lastMessageMap", lastMessageMap);

        model.addAttribute("ticketStatusValues", TicketStatus.values());
        model.addAttribute("orderByValues", SortOrder.values());
        model.addAttribute("selectedticketStatus", ticketStatus);
        model.addAttribute("selectedorderby", sortOrder);
        model.addAttribute("ticketSearchTypes", TicketSearchType.values());
        model.addAttribute("ticketSearchType", ticketSearchType);
        model.addAttribute("searchTerm", searchTerm);
        model.addAttribute("authUserListMap", authUserListMap);
        model.addAttribute("subCategoryIdAndSubCategoryMap", subCategoryIdAndSubCategoryMap);

        model.addAttribute("subCategoryIdAndCategoryMap", subCategoryIdAndCategoryMap);

        model.addAttribute("activityMap", activityMap);
        model.addAttribute("authUserMap", authUserMap);
        model.addAttribute("activityMapWithActivityId", activityMapWithActivityId);
        model.addAttribute("isCrmUser", isCrmUser);
    }


    @GetMapping(value = "/cs/edit-ticket")
    public String getEditTicket(HttpServletRequest request, @RequestParam(name = "ticketId", defaultValue = "0") int ticketId, Model model) throws ProfitMandiBusinessException {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        AuthUser currentAuthUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
        boolean isCrmUser = positionRepository.hasCategory(currentAuthUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);

        Ticket ticket = ticketRepository.selectById(ticketId);
        if (ticket == null) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "Ticket not found");
        }

        TicketSubCategory ticketSubCategory = ticketSubCategoryRepository.selectById(ticket.getSubCategoryId());
        int ticketCategoryId = ticketSubCategory.getCategoryId();
        boolean isVisibleSubCategory = ticketSubCategory.isVisibility();

        // Check permissions
        boolean hasPositionInCategory = positionRepository.hasCategory(currentAuthUser.getId(), ticketCategoryId);
        List<TicketAssigned> assignments = ticketAssignedRepository.selectByTicketIds(Arrays.asList(ticketId));
        boolean isAssigned = assignments.stream().anyMatch(ta -> ta.getAssineeId() == currentAuthUser.getId() || ta.getManagerId() == currentAuthUser.getId());
        boolean isInEscalationChain = ticket.getL1AuthUser() == currentAuthUser.getId() ||
                ticket.getL2AuthUser() == currentAuthUser.getId() ||
                ticket.getL3AuthUser() == currentAuthUser.getId() ||
                ticket.getL4AuthUser() == currentAuthUser.getId() ||
                ticket.getL5AuthUser() == currentAuthUser.getId();

        boolean canEdit = (isCrmUser && isVisibleSubCategory)
                || hasPositionInCategory
                || isAssigned
                || isInEscalationChain;

        if (!canEdit) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "You do not have permission to edit this ticket");
        }

        List<TicketCategory> ticketCategories = csService.getAllTicketCategotyFromSubCategory();
        List<TicketSubCategory> ticketSubCategories = ticketSubCategoryRepository.selectAll(ticketSubCategory.getCategoryId());
        List<AuthUser> authUsers = authRepository.selectAllActiveUser();

        // CRM users should not see invisible subcategories
        if (isCrmUser) {
            ticketSubCategories = ticketSubCategories.stream()
                    .filter(TicketSubCategory::isVisibility)
                    .collect(Collectors.toList());
        }

        // Get partner name for modal title
        String partnerName = retailerService.getFofoRetailer(ticket.getFofoId()).getBusinessName();

        model.addAttribute("ticket", ticket);
        model.addAttribute("ticketCategories", ticketCategories);
        model.addAttribute("ticketSubCategories", ticketSubCategories);
        model.addAttribute("ticketSubCategory", ticketSubCategory);
        model.addAttribute("authUsers", authUsers);
        model.addAttribute("isCrmUser", isCrmUser);
        model.addAttribute("partnerName", partnerName);
        return "edit-ticket-modal";
    }

    @GetMapping(value = "/cs/edit-partner-ticket")
    public String getEditPartnerTicket(HttpServletRequest request, @RequestParam(name = "ticketId", defaultValue = "0") int ticketId, Model model) throws ProfitMandiBusinessException {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        AuthUser currentAuthUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
        boolean isCrmUser = positionRepository.hasCategory(currentAuthUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);

        Ticket ticket = ticketRepository.selectById(ticketId);
        List<TicketCategory> ticketCategories = csService.getAllTicketCategotyFromSubCategory();
        TicketSubCategory ticketSubCategory = ticketSubCategoryRepository.selectById(ticket.getSubCategoryId());
        List<TicketSubCategory> ticketSubCategories = ticketSubCategoryRepository.selectAll(ticketSubCategory.getCategoryId());
        List<AuthUser> authUsers = authRepository.selectAllActiveUser();

        // CRM users should not see invisible subcategories
        if (isCrmUser) {
            ticketSubCategories = ticketSubCategories.stream()
                    .filter(TicketSubCategory::isVisibility)
                    .collect(Collectors.toList());
        }

        // Get partner name for modal title
        String partnerName = retailerService.getFofoRetailer(ticket.getFofoId()).getBusinessName();

        model.addAttribute("ticket", ticket);
        model.addAttribute("ticketCategories", ticketCategories);
        model.addAttribute("ticketSubCategories", ticketSubCategories);
        model.addAttribute("ticketSubCategory", ticketSubCategory);
        model.addAttribute("authUsers", authUsers);
        model.addAttribute("isCrmUser", isCrmUser);
        model.addAttribute("partnerName", partnerName);
        return "edit-ticket-partner-modal";
    }

    @PostMapping(value = "/cs/edit-ticket")
    public String editTicket(HttpServletRequest request, @RequestParam(name = "ticketId", defaultValue = "0") int ticketId, @RequestParam(name = "subCategoryId", defaultValue = "0") int subCategoryId, @RequestParam(name = "categoryId", defaultValue = "0") int categoryId, @RequestParam(name = "authUserId", defaultValue = "0") int authUserId, @RequestParam(name = "escalationType", defaultValue = "L1") EscalationType escalationType, Model model) throws Exception {
        LOGGER.info("Ticket Id {}, CategoryId {}, SubCategory Id {} authUserId {}", ticketId, categoryId, subCategoryId, authUserId);
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);

        // Only admins can edit tickets
        if (!roleManager.isAdmin(loginDetails.getRoleIds())) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "Only admins can edit tickets");
        }

        Ticket ticket = ticketRepository.selectById(ticketId);
        if (ticket == null) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "Ticket not found");
        }

        // Verify admin has access to this ticket
        AuthUser currentAuthUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
        boolean isCrmUser = positionRepository.hasCategory(currentAuthUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);

        // Get ticket's subcategory to check visibility
        TicketSubCategory ticketSubCategory = ticketSubCategoryRepository.selectById(ticket.getSubCategoryId());
        int ticketCategoryId = ticketSubCategory.getCategoryId();
        boolean isVisibleSubCategory = ticketSubCategory.isVisibility();

        // Check if user has position in ticket's category
        boolean hasPositionInCategory = positionRepository.hasCategory(currentAuthUser.getId(), ticketCategoryId);

        // Check if assigned or in escalation chain
        List<TicketAssigned> assignments = ticketAssignedRepository.selectByTicketIds(Arrays.asList(ticketId));
        boolean isAssigned = assignments.stream().anyMatch(ta -> ta.getAssineeId() == currentAuthUser.getId() || ta.getManagerId() == currentAuthUser.getId());
        boolean isInEscalationChain = ticket.getL1AuthUser() == currentAuthUser.getId() ||
                ticket.getL2AuthUser() == currentAuthUser.getId() ||
                ticket.getL3AuthUser() == currentAuthUser.getId() ||
                ticket.getL4AuthUser() == currentAuthUser.getId() ||
                ticket.getL5AuthUser() == currentAuthUser.getId();

        // Permission rules:
        // 1. CRM user AND ticket has visible subcategory, OR
        // 2. Has position in ticket's category (can edit even invisible), OR
        // 3. Is assigned to ticket, OR
        // 4. Is in escalation chain
        boolean canEdit = (isCrmUser && isVisibleSubCategory)
                || hasPositionInCategory
                || isAssigned
                || isInEscalationChain;

        if (!canEdit) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "You do not have permission to edit this ticket");
        }

        csService.updateTicket(categoryId, subCategoryId, ticket, authUserId, escalationType);
        model.addAttribute("response1", mvcResponseSender.createResponseString(true));
        return "response";

    }

    @PostMapping(value = "/cs/edit-partner-ticket")
    public String editPartnerTicket(HttpServletRequest request, @RequestParam(name = "ticketId", defaultValue = "0") int ticketId, @RequestParam(name = "subCategoryId", defaultValue = "0") int subCategoryId, @RequestParam(name = "categoryId", defaultValue = "0") int categoryId, @RequestParam(name = "authUserId", defaultValue = "0") int authUserId, @RequestParam(name = "escalationType", defaultValue = "L1") EscalationType escalationType, Model model) throws Exception {
        LOGGER.info("Ticket Id {}, CategoryId {}, SubCategory Id {} authUserId {}", ticketId, categoryId, subCategoryId, authUserId);
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);

        // Only admins can edit partner tickets
        if (!roleManager.isAdmin(loginDetails.getRoleIds())) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "Only admins can edit partner tickets");
        }

        Ticket ticket = ticketRepository.selectById(ticketId);
        if (ticket == null) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "Ticket not found");
        }

        // Verify admin has access (must be in Sales, ABM, or RBM position for the partner)
        AuthUser currentAuthUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
        Map<String, Set<Integer>> authUserPartnerMap = csService.getAuthUserPartnerIdMapping();
        Set<Integer> allowedPartnerIds = authUserPartnerMap.get(currentAuthUser.getEmailId());

        if (allowedPartnerIds == null || !allowedPartnerIds.contains(ticket.getFofoId())) {
            // Also allow CRM users
            boolean isCrmUser = positionRepository.hasCategory(currentAuthUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);
            if (!isCrmUser) {
                throw new ProfitMandiBusinessException("Ticket", ticketId, "You do not have permission to edit this partner's ticket");
            }
        }

        csService.updateTicket(categoryId, subCategoryId, ticket, authUserId, escalationType);
        model.addAttribute("response1", mvcResponseSender.createResponseString(true));
        return "response";

    }

    @PostMapping(value = "/cs/changeTicketAssignee")
    public String changeTicketAssignee(HttpServletRequest request, @RequestParam(name = "positionId", defaultValue = "0") int positionId, Model model) throws Exception {
        Position position = positionRepository.selectById(positionId);
        if (position.isTicketAssignee()) {
            position.setTicketAssignee(false);
        } else {
            position.setTicketAssignee(true);
        }
        model.addAttribute("response1", mvcResponseSender.createResponseString(true));
        return "response";
    }


    @DeleteMapping(value = "/cs/removePosition")
    public String removePosition(HttpServletRequest request, @RequestParam(name = "positionId", defaultValue = "0") int positionId, Model model) throws Exception {
        positionRepository.delete(positionId);
        model.addAttribute("response1", mvcResponseSender.createResponseString(true));
        return "response";
    }

    @PostMapping(value = "/cs/create-last-activity")
    public String createlastActivity(HttpServletRequest request, @RequestParam(name = "ticketId") int ticketId, @RequestParam(name = "lastactivity") ActivityType lastActivity, Model model) throws Exception {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        Ticket ticket = ticketRepository.selectById(ticketId);

        if (ticket == null) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "Ticket not found");
        }

        // Authorization check for partners: can only update their own tickets
        boolean isAdmin = roleManager.isAdmin(loginDetails.getRoleIds());
        if (!isAdmin && ticket.getFofoId() != loginDetails.getFofoId()) {
            throw new ProfitMandiBusinessException("Ticket", ticketId, "You do not have permission to update this ticket");
        }

        Activity activity = new Activity();
        String subject = String.format(ACTIVITY_SUBJECT, ticket.getId());
        if (isAdmin) {
            // CRM team members can resolve any ticket
            // Sales team members can resolve their own Sales category tickets
            // RBM team members can resolve their own RBM category tickets
            AuthUser authUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
            boolean isCrmUser = positionRepository.hasCategory(authUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);
            boolean isSalesUser = positionRepository.hasCategory(authUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_SALES);
            boolean isRbmUser = positionRepository.hasCategory(authUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_RBM);

            // Check if ticket belongs to Sales or RBM category
            TicketSubCategory ticketSubCategory = ticketSubCategoryRepository.selectById(ticket.getSubCategoryId());
            boolean isSalesTicket = ticketSubCategory != null && ticketSubCategory.getCategoryId() == ProfitMandiConstants.TICKET_CATEGORY_SALES;
            boolean isRbmTicket = ticketSubCategory != null && ticketSubCategory.getCategoryId() == ProfitMandiConstants.TICKET_CATEGORY_RBM;

            // Allow if CRM user OR (Sales user AND Sales ticket) OR (RBM user AND RBM ticket)
            if (!isCrmUser && !(isSalesUser && isSalesTicket) && !(isRbmUser && isRbmTicket)) {
                throw new ProfitMandiBusinessException("Ticket", ticketId, "Only CRM, Sales (for Sales tickets), or RBM (for RBM tickets) team can mark tickets as resolved");
            }
            ticket.setLastActivity(lastActivity);
            String to = retailerService.getFofoRetailer(ticket.getFofoId()).getEmail();
            String message = String.format(PARTNER_RESOLVED_TICKET_MAIL, ticketId, "REOPEN");
            activity.setMessage(message);
            activity.setCreatedBy(authUser.getId());
            activity.setTicketId(ticketId);
            activity.setCreateTimestamp(LocalDateTime.now());
            activity.setType(ActivityType.COMMUNICATION_OUT);
            this.activityRelatedMail(to, null, subject, message);
        } else {
            if (ActivityType.RESOLVED_ACCEPTED == lastActivity) {
                ticket.setLastActivity(lastActivity);
                ticket.setCloseTimestamp(LocalDateTime.now());
                activity.setMessage(ActivityType.RESOLVED_ACCEPTED.toString());
                activity.setCreatedBy(0);
                activity.setTicketId(ticketId);
                activity.setType(ActivityType.COMMUNICATION_IN);
                activity.setCreateTimestamp(LocalDateTime.now());
            } else {
                String message = String.format(INTERNAL_REOPEN_MAIL, ticketId, retailerService.getFofoRetailer(loginDetails.getFofoId()).getBusinessName());
                String to = authRepository.selectById(ticket.getL1AuthUser()).getEmailId();
                String[] ccTo = authRepository.selectByIds(Arrays.asList(ticket.getL2AuthUser(), ticket.getL3AuthUser(), ticket.getL4AuthUser(), ticket.getL5AuthUser())).stream().map(x -> x.getEmailId()).toArray(String[]::new);
                ticket.setLastActivity(lastActivity);
                ticket.setUpdateTimestamp(LocalDateTime.now());
                ticketAssignedRepository.deleteByTicketId(ticketId);
                TicketAssigned ticketAssigned = new TicketAssigned();
                ticketAssigned.setAssineeId(ticket.getL1AuthUser());
                ticketAssigned.setTicketId(ticketId);
                ticketAssignedRepository.persist(ticketAssigned);
                activity.setMessage(INTERNAL_REOPEN_ACTIVITY_MESSAGE);
                activity.setCreatedBy(0);
                activity.setTicketId(ticketId);
                activity.setType(ActivityType.COMMUNICATION_IN);
                activity.setCreateTimestamp(LocalDateTime.now());
                this.activityRelatedMail(to, ccTo, subject, message);
                this.activityRelatedMail(retailerService.getFofoRetailer(loginDetails.getFofoId()).getEmail(), null, subject, String.format(PARTNER_REOPEN, ticketId));
            }

        }
        activityRepository.persist(activity);
        model.addAttribute("response1", mvcResponseSender.createResponseString(true));
        return "response";
    }





    @PostMapping(value = "/partner-position/update")
    public String positionUpdated(Model model, @RequestBody List<PartnerPositonUpdateModel> partnerPositionUpdateModels)
            throws Exception {

        Map<Integer, List<String>> positionIdsToAddMap = partnerPositionUpdateModels.stream().filter(x->x.getPositionIdTo()!=0).collect(Collectors.groupingBy(x->x.getPositionIdTo(),
                Collectors.mapping(x->x.getStoreCode(), Collectors.toList())));

        Map<Integer, List<String>> positionIdsToRemoveMap = partnerPositionUpdateModels.stream().filter(x->x.getPositionIdFrom()!=0).collect(Collectors.groupingBy(x->x.getPositionIdFrom(),
                Collectors.mapping(x->x.getStoreCode(), Collectors.toList())));

        List<Integer> positionIdsToUpdate = new ArrayList<>();
        positionIdsToUpdate.addAll(positionIdsToAddMap.keySet());
        positionIdsToUpdate.addAll(positionIdsToRemoveMap.keySet());

        Map<Integer, Position> positionsToUpdateMap =  positionRepository.selectByIds(positionIdsToUpdate).stream().collect(Collectors.toMap(x->x.getId(), x->x));
        List<Integer> invalidPositionIds = positionsToUpdateMap.values().stream().filter(x-> x.getCategoryId()!= ProfitMandiConstants.TICKET_CATEGORY_RBM
                && x.getCategoryId() != ProfitMandiConstants.TICKET_CATEGORY_SALES && x.getCategoryId() != ProfitMandiConstants.TICKET_CATEGORY_ABM).map(x -> x.getId()).collect(Collectors.toList());
        if(invalidPositionIds.size() > 0) {
            String message = "Non RBM/Sales/ABM are not allowed - " + invalidPositionIds;
            throw new ProfitMandiBusinessException(message, message, message);
        }

        for (Map.Entry<Integer, List<String>> positionIdStoreMapEntry : positionIdsToAddMap.entrySet()) {
            int positionId = positionIdStoreMapEntry.getKey();
            Position position = positionsToUpdateMap.get(positionId);
            LOGGER.info("positionId - {}, Position - {}", positionId, position);
            List<String> storeCodesToAdd = positionIdStoreMapEntry.getValue();
            List<Integer> retailerIdsToAdd = fofoStoreRepository.selectByStoreCodes(storeCodesToAdd).stream().map(x->x.getId()).collect(Collectors.toList());
            Map<Integer, PartnerPosition> partnerPositionsMapByFofoId  = partnerPositionRepository
                    .selectByRegionIdAndPostionId(Arrays.asList(position.getRegionId())
                            ,Arrays.asList(positionId)).stream().collect(Collectors.toMap(x->x.getFofoId(),x->x));
            for (Integer retailerIdToAdd : retailerIdsToAdd) {
                if (!partnerPositionsMapByFofoId.containsKey(retailerIdToAdd)) {
                    PartnerPosition partnerPositionNew = new PartnerPosition();
                    partnerPositionNew.setPositionId(positionId);
                    partnerPositionNew.setFofoId(retailerIdToAdd);
                    partnerPositionNew.setRegionId(position.getRegionId());
                    partnerPositionRepository.persist(partnerPositionNew);
                }
            }
        }

        for (Map.Entry<Integer, List<String>> positionIdStoreMapEntry : positionIdsToRemoveMap.entrySet()) {

            int positionId = positionIdStoreMapEntry.getKey();
            Position position = positionsToUpdateMap.get(positionId);
            List<String> storeCodesToRemove = positionIdStoreMapEntry.getValue();
            List<Integer> retailerIdsToRemove = fofoStoreRepository.selectByStoreCodes(storeCodesToRemove).stream().map(x->x.getId()).collect(Collectors.toList());
            Map<Integer, PartnerPosition> partnerPositionsMapByFofoId  = partnerPositionRepository
                    .selectByRegionIdAndPostionId(Arrays.asList(position.getRegionId()),Arrays.asList(positionId)).stream().collect(Collectors.toMap(x->x.getFofoId(),x->x));
            for (Integer retailerIdToRemove : retailerIdsToRemove) {
                if (partnerPositionsMapByFofoId.containsKey(retailerIdToRemove)) {
                   PartnerPosition partnerPositionToRemove =  partnerPositionsMapByFofoId.get(retailerIdToRemove);
                   partnerPositionRepository.delete(partnerPositionToRemove);
                }
            }
        }



        /*partnerPositionUpdateModels.str

        Map<Integer, Position> positionIdMap = positionsToUpdate.stream().collect(Collectors.toMap(x->x.getId(), x->x));
        for (PartnerPositonUpdateModel partnerPositionUpdateModel : partnerPositionUpdateModels) {
            FofoStore fofoStore = fofoStoreRepository.selectByStoreCode(partnerPositionUpdateModel.getStoreCode());
            Position positionFrom = positionIdMap.get(partnerPositionUpdateModel.getPositionIdFrom());
            Position positionTo = positionIdMap.get(partnerPositionUpdateModel.getPositionIdTo());
            if(positionFrom != null) {
                partnerPositionRepository.selectByRegionIdAndPostionId(Arrays.)
              int regionId = positionFrom.getRegionId()
            }
            if(positionTo != null) {

            }
        }*/
        model.addAttribute("response1", mvcResponseSender.createResponseString(true));
        return "response";

    }

    @PostMapping(value = "/cs/markTicketRead")
    @ResponseBody
    public Map<String, Object> markTicketRead(HttpServletRequest request,
                                              @RequestParam(name = "ticketId") int ticketId,
                                              Model model) throws ProfitMandiBusinessException {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        boolean isAdmin = roleManager.isAdmin(loginDetails.getRoleIds());
        Map<String, Object> response = new HashMap<>();

        if (isAdmin) {
            AuthUser authUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
            csService.markTicketAsRead(ticketId, authUser.getId(), UserType.AUTH_USER);
        } else {
            csService.markTicketAsRead(ticketId, loginDetails.getFofoId(), UserType.PARTNER);
        }

        response.put("success", true);
        return response;
    }

    @GetMapping(value = "/cs/unreadCount")
    @ResponseBody
    public Map<String, Object> getUnreadCount(HttpServletRequest request) throws ProfitMandiBusinessException {
        LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
        boolean isAdmin = roleManager.isAdmin(loginDetails.getRoleIds());
        Map<String, Object> response = new HashMap<>();

        List<Ticket> tickets;
        int userId;
        UserType userType;

        if (isAdmin) {
            AuthUser authUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
            userId = authUser.getId();
            userType = UserType.AUTH_USER;
            boolean isCrmUser = positionRepository.hasCategory(authUser.getId(), ProfitMandiConstants.TICKET_CATEGORY_CRM);
            List<Integer> userCategoryIds = positionRepository.selectCategoryIdsByAuthUserId(authUser.getId());
            if (isCrmUser) {
                tickets = ticketRepository.selectAllTicketsPaginated(
                        Optional.of(false), null, null, 0, null,
                        userCategoryIds, 0, 1000);
            } else {
                tickets = ticketRepository.selectAllByAssigneePaginated(
                        authUser.getId(), Optional.of(false), null, null, 0, null,
                        0, 1000);
            }
        } else {
            userId = loginDetails.getFofoId();
            userType = UserType.PARTNER;
            tickets = ticketRepository.selectAllByCreator(loginDetails.getFofoId(), Optional.of(true), null);
        }

        Map<Integer, Boolean> unreadMap = csService.getUnreadStatusForTickets(tickets, userId, userType);
        long unreadCount = unreadMap.values().stream().filter(v -> v).count();

        response.put("success", true);
        response.put("unreadCount", unreadCount);
        return response;
    }

}