Subversion Repositories SmartDukaan

Rev

Blame | Last modification | View Log | RSS feed

package com.spice.profitmandi.service;

import com.spice.profitmandi.common.model.ProfitMandiConstants;
import com.spice.profitmandi.dao.entity.auth.AuthUser;
import com.spice.profitmandi.dao.entity.cs.Position;
import com.spice.profitmandi.dao.entity.cs.Region;
import com.spice.profitmandi.dao.entity.user.Lead;
import com.spice.profitmandi.dao.enumuration.cs.EscalationType;
import com.spice.profitmandi.dao.enumuration.dtr.LeadStage;
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
import com.spice.profitmandi.dao.repository.cs.PositionRepository;
import com.spice.profitmandi.dao.repository.cs.RegionRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

/**
 * LMS auto-assignment engine (SOP §10) + LMS-id generation + SLA helpers.
 *
 * Uses the EXISTING mapping: a region is picked from {@code cs.region} and the owning BM/RSM is
 * resolved live from {@code cs.position} (category = SALES, escalation = L4, region_id). Unmapped
 * regions land in the HOLD queue so nothing breaks while the position mapping is completed.
 *
 * Reused by lead create + the Create-form preview endpoint.
 */
@Service
public class LmsAssignmentService {

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

    /** First-contact SLA window (SOP §11): reminder at T+3h, escalation at T+5h. Due = created + 5h. */
    public static final int SLA_HOURS = 5;
    /** DROPPED after this many NOT_REACHABLE attempts (SOP §8, "config"). */
    public static final int MAX_UNREACHABLE = 3;

    private static final int SALES = ProfitMandiConstants.TICKET_CATEGORY_SALES;

    /** BM/RSM tiers to try, highest-relevant first. L4 ≈ BM; fall back up/down if a region lacks L4. */
    private static final List<EscalationType> BM_TIERS =
            Arrays.asList(EscalationType.L4, EscalationType.L5, EscalationType.L3);

    @Autowired
    private RegionRepository regionRepository;

    @Autowired
    private PositionRepository positionRepository;

    @Autowired
    private AuthRepository authRepository;

    // ---- Region resolution ----------------------------------------------------------------

    /** Result of resolving a lead to a region + its owning BM. */
    public static class Assignment {
        public Integer regionId;          // cs.region id (null if not picked / not found)
        public String regionCode;         // cs.region.region_code (fallback: derived from name) — LMS id prefix
        public AuthUser bm;               // owning BM/RSM (null → HOLD)
        public String assignmentStatus;   // ASSIGNED | HOLD
    }

    /** Resolve a picked {@code cs.region} id → its code + owning BM/RSM (via cs.position). */
    public Assignment resolve(Integer regionId) {
        Assignment a = new Assignment();
        if (regionId != null && regionId > 0) {
            Region region = regionRepository.selectById(regionId);
            if (region != null) {
                a.regionId = region.getId();
                a.regionCode = codeOf(region);
                a.bm = resolveBm(region.getId());
            }
        }
        a.assignmentStatus = a.bm != null ? "ASSIGNED" : "HOLD";
        return a;
    }

    /** region_code if set, otherwise a short slug of the region name (for the LMS id prefix). */
    private String codeOf(Region region) {
        if (region.getRegionCode() != null && !region.getRegionCode().trim().isEmpty()) {
            return region.getRegionCode().trim().toUpperCase();
        }
        String name = region.getName() == null ? "" : region.getName().replaceAll("[^A-Za-z]", "");
        if (name.isEmpty()) {
            return "GEN";
        }
        return name.substring(0, Math.min(3, name.length())).toUpperCase();
    }

    /** First active BM/RSM (SALES position) for a region, trying L4 → L5 → L3. Null → HOLD. */
    public AuthUser resolveBm(Integer regionId) {
        if (regionId == null) {
            return null;
        }
        for (EscalationType tier : BM_TIERS) {
            List<Position> positions = positionRepository.selectPositionbyCategoryIdAndEscalationType(SALES, tier, regionId);
            if (positions == null) {
                continue;
            }
            for (Position p : positions) {
                AuthUser u = authRepository.selectById(p.getAuthUserId());
                if (u != null && Boolean.TRUE.equals(u.isActive())) {
                    return u;
                }
            }
        }
        return null;
    }

    // ---- Apply assignment to a lead -------------------------------------------------------

    /**
     * Stamp region + owner + stage + SLA onto a freshly-built lead (SOP §9), using the lead's
     * picked {@code regionId}. Path A owner = BM (BM maps an ASM later); Path B owner = creator
     * (assignTo already set). A manual assignTo override (assignTo &gt; 0) is preserved. Legacy
     * status is synced to the stage. Does NOT persist — caller persists (then {@link #generateLmsCode}).
     */
    public Assignment assign(Lead lead, String path) {
        String creationPath = (path != null && path.trim().equalsIgnoreCase("B")) ? "B" : "A";
        lead.setCreationPath(creationPath);

        Assignment a = resolve(lead.getRegionId());
        lead.setRegionId(a.regionId);
        lead.setRegionCode(a.regionCode);
        lead.setAssignmentStatus(a.assignmentStatus);

        if (a.bm != null) {
            lead.setOwnerBmId(a.bm.getId());
            if ("A".equals(creationPath) && lead.getAssignTo() <= 0) {
                lead.setAssignTo(a.bm.getId());
            }
        }

        lead.setStage(LeadStage.ASSIGNED);
        lead.setStatus(LeadStage.ASSIGNED.toLegacyStatus());
        lead.setUnreachableCount(0);

        LocalDateTime base = lead.getCreatedTimestamp() != null ? lead.getCreatedTimestamp() : LocalDateTime.now();
        lead.setFirstContactDue(base.plusHours(SLA_HOURS));
        return a;
    }

    // ---- LMS id ---------------------------------------------------------------------------

    /** LMS-&lt;REGION&gt;-&lt;YY&gt;-&lt;6-digit id&gt;, e.g. LMS-UPW-26-000123. Call after the lead has an id. */
    public String generateLmsCode(Lead lead) {
        String region = (lead.getRegionCode() != null && !lead.getRegionCode().isEmpty())
                ? lead.getRegionCode() : "GEN";
        LocalDateTime created = lead.getCreatedTimestamp() != null ? lead.getCreatedTimestamp() : LocalDateTime.now();
        int yy = created.getYear() % 100;
        return String.format("LMS-%s-%02d-%06d", region, yy, lead.getId());
    }

    // ---- SLA ------------------------------------------------------------------------------

    /** MET / BREACHED / AT_RISK / RUNNING / NA — for the record SLA card. */
    public String slaState(Lead lead) {
        LocalDateTime due = lead.getFirstContactDue();
        if (lead.getFirstContactedAt() != null) {
            if (due == null) {
                return "MET";
            }
            return !lead.getFirstContactedAt().isAfter(due) ? "MET" : "BREACHED";
        }
        if (due == null) {
            return "NA";
        }
        LocalDateTime now = LocalDateTime.now();
        if (now.isAfter(due)) {
            return "BREACHED";
        }
        if (now.isAfter(due.minusHours(1))) {
            return "AT_RISK";
        }
        return "RUNNING";
    }
}