Subversion Repositories SmartDukaan

Rev

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