| 37764 |
amit |
1 |
package com.spice.profitmandi.service.user;
|
|
|
2 |
|
|
|
3 |
import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
|
|
|
4 |
import com.spice.profitmandi.common.model.CustomRetailer;
|
|
|
5 |
import com.spice.profitmandi.common.model.ProfitMandiConstants;
|
|
|
6 |
import com.spice.profitmandi.common.util.Utils;
|
|
|
7 |
import com.spice.profitmandi.dao.entity.auth.AuthUser;
|
|
|
8 |
import com.spice.profitmandi.dao.entity.cs.Position;
|
|
|
9 |
import com.spice.profitmandi.dao.entity.dtr.Document;
|
|
|
10 |
import com.spice.profitmandi.dao.entity.fofo.FofoStore;
|
|
|
11 |
import com.spice.profitmandi.dao.entity.fofo.StoreClosure;
|
|
|
12 |
import com.spice.profitmandi.dao.enumuration.cs.EscalationType;
|
|
|
13 |
import com.spice.profitmandi.dao.enumuration.fofo.StoreClosureReason;
|
|
|
14 |
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
|
|
|
15 |
import com.spice.profitmandi.dao.repository.cs.CsService;
|
|
|
16 |
import com.spice.profitmandi.dao.repository.cs.PositionRepository;
|
|
|
17 |
import com.spice.profitmandi.dao.repository.dtr.DocumentRepository;
|
|
|
18 |
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
|
|
|
19 |
import com.spice.profitmandi.dao.repository.fofo.StoreClosureRepository;
|
|
|
20 |
import com.spice.profitmandi.service.mail.MailOutboxService;
|
|
|
21 |
import org.apache.logging.log4j.LogManager;
|
|
|
22 |
import org.apache.logging.log4j.Logger;
|
|
|
23 |
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
24 |
import org.springframework.core.io.FileSystemResource;
|
|
|
25 |
import org.springframework.stereotype.Service;
|
|
|
26 |
|
|
|
27 |
import java.io.File;
|
|
|
28 |
import java.time.LocalDateTime;
|
|
|
29 |
import java.time.format.DateTimeFormatter;
|
|
|
30 |
import java.util.*;
|
|
|
31 |
import java.util.stream.Collectors;
|
|
|
32 |
|
|
|
33 |
/**
|
|
|
34 |
* Permanent partner store closure. A closure needs a reason, a remark and the approval mail
|
|
|
35 |
* (uploaded via /document-upload), and queues a closure report with the approval attached to the partner's
|
|
|
36 |
* Sales L2, the top three Sales levels, the top Accounts level and the closer.
|
|
|
37 |
*/
|
|
|
38 |
@Service
|
|
|
39 |
public class StoreClosureService {
|
|
|
40 |
|
|
|
41 |
private static final Logger LOGGER = LogManager.getLogger(StoreClosureService.class);
|
|
|
42 |
|
|
|
43 |
|
|
|
44 |
private static final DateTimeFormatter REPORT_TIME_FORMAT = DateTimeFormatter.ofPattern("dd-MMM-yyyy hh:mm a");
|
|
|
45 |
|
|
|
46 |
@Autowired
|
|
|
47 |
private FofoStoreRepository fofoStoreRepository;
|
|
|
48 |
|
|
|
49 |
@Autowired
|
|
|
50 |
private StoreClosureRepository storeClosureRepository;
|
|
|
51 |
|
|
|
52 |
@Autowired
|
|
|
53 |
private DocumentRepository documentRepository;
|
|
|
54 |
|
|
|
55 |
@Autowired
|
|
|
56 |
private PositionRepository positionRepository;
|
|
|
57 |
|
|
|
58 |
@Autowired
|
|
|
59 |
private AuthRepository authRepository;
|
|
|
60 |
|
|
|
61 |
@Autowired
|
|
|
62 |
private CsService csService;
|
|
|
63 |
|
|
|
64 |
@Autowired
|
|
|
65 |
private RetailerService retailerService;
|
|
|
66 |
|
|
|
67 |
@Autowired
|
|
|
68 |
private MailOutboxService mailOutboxService;
|
|
|
69 |
|
|
|
70 |
public void closeStore(int fofoId, String storeCode, StoreClosureReason reason, String remark,
|
|
|
71 |
int approvalDocumentId, String closedBy) throws ProfitMandiBusinessException {
|
|
|
72 |
StoreAccess.check(StoreAccess.CLOSE, closedBy, "close store");
|
|
|
73 |
FofoStore fofoStore = fofoStoreRepository.selectByRetailerId(fofoId);
|
|
|
74 |
if (fofoStore.isClosed()) {
|
|
|
75 |
throw new ProfitMandiBusinessException("Store Closed", "Store Already Closed",
|
|
|
76 |
"This store is already permanently closed");
|
|
|
77 |
}
|
|
|
78 |
if (storeCode == null || !storeCode.equals(fofoStore.getCode())) {
|
|
|
79 |
throw new ProfitMandiBusinessException("Invalid Store Code", "Store Code Mismatch",
|
|
|
80 |
"Please type the correct store code to confirm closure");
|
|
|
81 |
}
|
|
|
82 |
if (reason == null) {
|
|
|
83 |
throw new ProfitMandiBusinessException("Closure Reason", "Reason Missing",
|
|
|
84 |
"Please select the reason for closing the store");
|
|
|
85 |
}
|
|
|
86 |
if (remark == null || remark.trim().isEmpty()) {
|
|
|
87 |
throw new ProfitMandiBusinessException("Closure Remark", "Remark Missing",
|
|
|
88 |
"Please describe why the store is being closed");
|
|
|
89 |
}
|
|
|
90 |
if (remark.trim().length() > 1000) {
|
|
|
91 |
throw new ProfitMandiBusinessException("Closure Remark", "Remark Too Long",
|
|
|
92 |
"Please keep the remark within 1000 characters");
|
|
|
93 |
}
|
|
|
94 |
Document approvalDocument = approvalDocumentId > 0 ? documentRepository.selectById(approvalDocumentId) : null;
|
|
|
95 |
if (approvalDocument == null) {
|
|
|
96 |
throw new ProfitMandiBusinessException("Approval Mail", "Approval Missing",
|
|
|
97 |
"Please attach the approval mail for this closure");
|
|
|
98 |
}
|
|
|
99 |
File approvalFile = new File(approvalDocument.getPath(), approvalDocument.getName());
|
|
|
100 |
if (!approvalFile.isFile()) {
|
|
|
101 |
throw new ProfitMandiBusinessException("Approval Mail", "Approval File Missing",
|
|
|
102 |
"The uploaded approval mail could not be found, please upload it again");
|
|
|
103 |
}
|
|
|
104 |
|
|
|
105 |
LocalDateTime now = LocalDateTime.now();
|
|
|
106 |
fofoStore.setActive(false);
|
|
|
107 |
fofoStore.setClosed(true);
|
|
|
108 |
fofoStore.setClosedTimestamp(now);
|
|
|
109 |
|
|
|
110 |
StoreClosure storeClosure = new StoreClosure();
|
|
|
111 |
storeClosure.setFofoId(fofoId);
|
|
|
112 |
storeClosure.setReason(reason);
|
|
|
113 |
storeClosure.setRemark(remark.trim());
|
|
|
114 |
storeClosure.setApprovalDocumentId(approvalDocumentId);
|
|
|
115 |
storeClosure.setClosedBy(closedBy);
|
|
|
116 |
storeClosure.setCreateTimestamp(now);
|
|
|
117 |
storeClosureRepository.persist(storeClosure);
|
|
|
118 |
LOGGER.info("Store {} closed permanently by {} - {}", fofoStore.getCode(), closedBy, storeClosure);
|
|
|
119 |
|
|
|
120 |
sendClosureReport(fofoStore, storeClosure, approvalDocument, approvalFile);
|
|
|
121 |
}
|
|
|
122 |
|
|
|
123 |
private void sendClosureReport(FofoStore fofoStore, StoreClosure storeClosure, Document approvalDocument,
|
|
|
124 |
File approvalFile) throws ProfitMandiBusinessException {
|
|
|
125 |
CustomRetailer retailer = retailerService.getFofoRetailer(fofoStore.getId());
|
|
|
126 |
|
|
|
127 |
List<AuthUser> salesL2 = csService.getAuthUsersByPartnerIdAndCategory(fofoStore.getId(),
|
|
|
128 |
ProfitMandiConstants.TICKET_CATEGORY_SALES, EscalationType.L2);
|
|
|
129 |
Set<String> recipients = new LinkedHashSet<>();
|
|
|
130 |
salesL2.stream().map(AuthUser::getEmailId).filter(Objects::nonNull).forEach(recipients::add);
|
|
|
131 |
recipients.addAll(topLevelEmails(ProfitMandiConstants.TICKET_CATEGORY_SALES, 3));
|
|
|
132 |
recipients.addAll(topLevelEmails(ProfitMandiConstants.TICKET_CATEGORY_ACCOUNTS, 1));
|
|
|
133 |
recipients.add(storeClosure.getClosedBy());
|
|
|
134 |
|
|
|
135 |
String businessName = retailer.getBusinessName();
|
|
|
136 |
String city = retailer.getAddress() == null ? "-" : retailer.getAddress().getCity() + ", " + retailer.getAddress().getState();
|
|
|
137 |
String subject = "Store Closure Report - " + fofoStore.getCode() + " - " + businessName;
|
|
|
138 |
StringBuilder body = new StringBuilder();
|
|
|
139 |
body.append("Dear Team,\n\nThe following partner store has been permanently closed.\n\n");
|
|
|
140 |
body.append("Partner : ").append(businessName).append(" (Partner Id ").append(fofoStore.getId()).append(")\n");
|
|
|
141 |
body.append("Store Code : ").append(fofoStore.getCode()).append("\n");
|
|
|
142 |
body.append("GSTIN : ").append(Optional.ofNullable(retailer.getGstNumber()).orElse("-")).append("\n");
|
|
|
143 |
body.append("Location : ").append(city).append("\n");
|
|
|
144 |
body.append("Sales L2 : ").append(salesL2.isEmpty() ? "-" :
|
|
|
145 |
salesL2.stream().map(AuthUser::getFullName).collect(Collectors.joining(", "))).append("\n");
|
|
|
146 |
body.append("Closed By : ").append(storeClosure.getClosedBy()).append("\n");
|
|
|
147 |
body.append("Closed On : ").append(storeClosure.getCreateTimestamp().format(REPORT_TIME_FORMAT)).append("\n");
|
|
|
148 |
body.append("Reason : ").append(storeClosure.getReason().getLabel()).append("\n");
|
|
|
149 |
body.append("Remark : ").append(storeClosure.getRemark()).append("\n\n");
|
|
|
150 |
body.append("The approval mail for this closure is attached.\n\nRegards,\nSmartDukaan");
|
|
|
151 |
|
|
|
152 |
String attachmentName = approvalDocument.getDisplayName() != null ? approvalDocument.getDisplayName() : approvalDocument.getName();
|
|
|
153 |
mailOutboxService.queueMailWithAttachments(recipients.toArray(new String[0]), null, subject, body.toString(),
|
|
|
154 |
"StoreClosureService.sendClosureReport", new Utils.Attachment(attachmentName, new FileSystemResource(approvalFile)));
|
|
|
155 |
}
|
|
|
156 |
|
|
|
157 |
/**
|
|
|
158 |
* Emails of active auth users holding the highest {@code levels} distinct escalation levels held by active users
|
|
|
159 |
* in a position category, resolved live so role changes in cs.position flow through without a code change.
|
|
|
160 |
*/
|
|
|
161 |
private Set<String> topLevelEmails(int categoryId, int levels) {
|
|
|
162 |
List<Position> positions = positionRepository.selectPositionByCategoryId(categoryId).stream()
|
|
|
163 |
.filter(x -> x.getAuthUserId() > 0 && x.getEscalationType() != null).collect(Collectors.toList());
|
|
|
164 |
if (positions.isEmpty()) {
|
|
|
165 |
return new HashSet<>();
|
|
|
166 |
}
|
|
|
167 |
Map<Integer, AuthUser> activeUsers = authRepository.selectByIds(positions.stream().map(Position::getAuthUserId)
|
|
|
168 |
.distinct().collect(Collectors.toList())).stream().filter(x -> Boolean.TRUE.equals(x.getActive()))
|
|
|
169 |
.collect(Collectors.toMap(AuthUser::getId, x -> x));
|
|
|
170 |
List<Position> activePositions = positions.stream().filter(x -> activeUsers.containsKey(x.getAuthUserId()))
|
|
|
171 |
.collect(Collectors.toList());
|
|
|
172 |
List<EscalationType> topLevels = activePositions.stream().map(Position::getEscalationType).distinct()
|
|
|
173 |
.sorted(Comparator.reverseOrder()).limit(levels).collect(Collectors.toList());
|
|
|
174 |
return activePositions.stream().filter(x -> topLevels.contains(x.getEscalationType()))
|
|
|
175 |
.map(x -> activeUsers.get(x.getAuthUserId()).getEmailId()).filter(Objects::nonNull)
|
|
|
176 |
.map(String::toLowerCase).collect(Collectors.toSet());
|
|
|
177 |
}
|
|
|
178 |
}
|