Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
37125 vikas 1
package com.spice.profitmandi.service;
2
 
3
import com.spice.profitmandi.common.model.ProfitMandiConstants;
4
import com.spice.profitmandi.dao.entity.auth.AuthUser;
5
import com.spice.profitmandi.dao.entity.cs.Position;
6
import com.spice.profitmandi.dao.entity.cs.Region;
7
import com.spice.profitmandi.dao.entity.user.Lead;
8
import com.spice.profitmandi.dao.enumuration.cs.EscalationType;
9
import com.spice.profitmandi.dao.enumuration.dtr.LeadStage;
10
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
11
import com.spice.profitmandi.dao.repository.cs.PositionRepository;
12
import com.spice.profitmandi.dao.repository.cs.RegionRepository;
13
import org.apache.logging.log4j.LogManager;
14
import org.apache.logging.log4j.Logger;
15
import org.springframework.beans.factory.annotation.Autowired;
16
import org.springframework.stereotype.Service;
17
 
18
import java.time.LocalDateTime;
19
import java.util.Arrays;
20
import java.util.List;
21
 
22
/**
23
 * LMS auto-assignment engine (SOP §10) + LMS-id generation + SLA helpers.
24
 *
25
 * Uses the EXISTING mapping: a region is picked from {@code cs.region} and the owning BM/RSM is
26
 * resolved live from {@code cs.position} (category = SALES, escalation = L4, region_id). Unmapped
27
 * regions land in the HOLD queue so nothing breaks while the position mapping is completed.
28
 *
29
 * Reused by lead create + the Create-form preview endpoint.
30
 */
31
@Service
32
public class LmsAssignmentService {
33
 
34
    private static final Logger LOGGER = LogManager.getLogger(LmsAssignmentService.class);
35
 
36
    /** First-contact SLA window (SOP §11): reminder at T+3h, escalation at T+5h. Due = created + 5h. */
37
    public static final int SLA_HOURS = 5;
38
    /** DROPPED after this many NOT_REACHABLE attempts (SOP §8, "config"). */
39
    public static final int MAX_UNREACHABLE = 3;
40
 
41
    private static final int SALES = ProfitMandiConstants.TICKET_CATEGORY_SALES;
42
 
43
    /** BM/RSM tiers to try, highest-relevant first. L4 ≈ BM; fall back up/down if a region lacks L4. */
44
    private static final List<EscalationType> BM_TIERS =
45
            Arrays.asList(EscalationType.L4, EscalationType.L5, EscalationType.L3);
46
 
47
    @Autowired
48
    private RegionRepository regionRepository;
49
 
50
    @Autowired
51
    private PositionRepository positionRepository;
52
 
53
    @Autowired
54
    private AuthRepository authRepository;
55
 
56
    // ---- Region resolution ----------------------------------------------------------------
57
 
58
    /** Result of resolving a lead to a region + its owning BM. */
59
    public static class Assignment {
60
        public Integer regionId;          // cs.region id (null if not picked / not found)
61
        public String regionCode;         // cs.region.region_code (fallback: derived from name) — LMS id prefix
62
        public AuthUser bm;               // owning BM/RSM (null → HOLD)
63
        public String assignmentStatus;   // ASSIGNED | HOLD
64
    }
65
 
66
    /** Resolve a picked {@code cs.region} id → its code + owning BM/RSM (via cs.position). */
67
    public Assignment resolve(Integer regionId) {
68
        Assignment a = new Assignment();
69
        if (regionId != null && regionId > 0) {
70
            Region region = regionRepository.selectById(regionId);
71
            if (region != null) {
72
                a.regionId = region.getId();
73
                a.regionCode = codeOf(region);
74
                a.bm = resolveBm(region.getId());
75
            }
76
        }
77
        a.assignmentStatus = a.bm != null ? "ASSIGNED" : "HOLD";
78
        return a;
79
    }
80
 
81
    /** region_code if set, otherwise a short slug of the region name (for the LMS id prefix). */
82
    private String codeOf(Region region) {
83
        if (region.getRegionCode() != null && !region.getRegionCode().trim().isEmpty()) {
84
            return region.getRegionCode().trim().toUpperCase();
85
        }
86
        String name = region.getName() == null ? "" : region.getName().replaceAll("[^A-Za-z]", "");
87
        if (name.isEmpty()) {
88
            return "GEN";
89
        }
90
        return name.substring(0, Math.min(3, name.length())).toUpperCase();
91
    }
92
 
93
    /** First active BM/RSM (SALES position) for a region, trying L4 → L5 → L3. Null → HOLD. */
94
    public AuthUser resolveBm(Integer regionId) {
95
        if (regionId == null) {
96
            return null;
97
        }
98
        for (EscalationType tier : BM_TIERS) {
99
            List<Position> positions = positionRepository.selectPositionbyCategoryIdAndEscalationType(SALES, tier, regionId);
100
            if (positions == null) {
101
                continue;
102
            }
103
            for (Position p : positions) {
104
                AuthUser u = authRepository.selectById(p.getAuthUserId());
105
                if (u != null && Boolean.TRUE.equals(u.isActive())) {
106
                    return u;
107
                }
108
            }
109
        }
110
        return null;
111
    }
112
 
113
    // ---- Apply assignment to a lead -------------------------------------------------------
114
 
115
    /**
116
     * Stamp region + owner + stage + SLA onto a freshly-built lead (SOP §9), using the lead's
117
     * picked {@code regionId}. Path A owner = BM (BM maps an ASM later); Path B owner = creator
118
     * (assignTo already set). A manual assignTo override (assignTo &gt; 0) is preserved. Legacy
119
     * status is synced to the stage. Does NOT persist — caller persists (then {@link #generateLmsCode}).
120
     */
121
    public Assignment assign(Lead lead, String path) {
122
        String creationPath = (path != null && path.trim().equalsIgnoreCase("B")) ? "B" : "A";
123
        lead.setCreationPath(creationPath);
124
 
125
        Assignment a = resolve(lead.getRegionId());
126
        lead.setRegionId(a.regionId);
127
        lead.setRegionCode(a.regionCode);
128
        lead.setAssignmentStatus(a.assignmentStatus);
129
 
130
        if (a.bm != null) {
131
            lead.setOwnerBmId(a.bm.getId());
132
            if ("A".equals(creationPath) && lead.getAssignTo() <= 0) {
133
                lead.setAssignTo(a.bm.getId());
134
            }
135
        }
136
 
137
        lead.setStage(LeadStage.ASSIGNED);
138
        lead.setStatus(LeadStage.ASSIGNED.toLegacyStatus());
139
        lead.setUnreachableCount(0);
140
 
141
        LocalDateTime base = lead.getCreatedTimestamp() != null ? lead.getCreatedTimestamp() : LocalDateTime.now();
142
        lead.setFirstContactDue(base.plusHours(SLA_HOURS));
143
        return a;
144
    }
145
 
146
    // ---- LMS id ---------------------------------------------------------------------------
147
 
148
    /** 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. */
149
    public String generateLmsCode(Lead lead) {
150
        String region = (lead.getRegionCode() != null && !lead.getRegionCode().isEmpty())
151
                ? lead.getRegionCode() : "GEN";
152
        LocalDateTime created = lead.getCreatedTimestamp() != null ? lead.getCreatedTimestamp() : LocalDateTime.now();
153
        int yy = created.getYear() % 100;
154
        return String.format("LMS-%s-%02d-%06d", region, yy, lead.getId());
155
    }
156
 
157
    // ---- SLA ------------------------------------------------------------------------------
158
 
159
    /** MET / BREACHED / AT_RISK / RUNNING / NA — for the record SLA card. */
160
    public String slaState(Lead lead) {
161
        LocalDateTime due = lead.getFirstContactDue();
162
        if (lead.getFirstContactedAt() != null) {
163
            if (due == null) {
164
                return "MET";
165
            }
166
            return !lead.getFirstContactedAt().isAfter(due) ? "MET" : "BREACHED";
167
        }
168
        if (due == null) {
169
            return "NA";
170
        }
171
        LocalDateTime now = LocalDateTime.now();
172
        if (now.isAfter(due)) {
173
            return "BREACHED";
174
        }
175
        if (now.isAfter(due.minusHours(1))) {
176
            return "AT_RISK";
177
        }
178
        return "RUNNING";
179
    }
180
}