Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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
}