Subversion Repositories SmartDukaan

Rev

Rev 36347 | Rev 36355 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
36342 amit 1
package com.spice.profitmandi.service.offers;
2
 
3
import com.spice.profitmandi.dao.entity.transaction.CronBatch;
4
import com.spice.profitmandi.dao.enumuration.catalog.OfferSchemeType;
5
import com.spice.profitmandi.dao.model.CreateOfferRequest;
36354 amit 6
import com.spice.profitmandi.dao.repository.transaction.CronBatchRepository;
36342 amit 7
import com.spice.profitmandi.service.cron.CronBatchService;
8
import org.apache.logging.log4j.LogManager;
9
import org.apache.logging.log4j.Logger;
10
import org.springframework.beans.factory.annotation.Autowired;
11
import org.springframework.stereotype.Service;
12
 
13
import java.util.LinkedHashMap;
14
import java.util.List;
36347 amit 15
import java.util.Set;
16
import java.util.concurrent.ConcurrentHashMap;
17
import java.util.concurrent.ExecutorService;
18
import java.util.concurrent.Executors;
36342 amit 19
 
20
/**
21
 * Orchestrates offer processing with per-partner transaction isolation and batch tracking.
22
 *
23
 * NO @Transactional on this class or any of its methods — the orchestrator must not
24
 * carry an outer transaction. Each partner's write work runs in REQUIRES_NEW via
25
 * OfferProcessingHelper; batch metadata writes run in REQUIRES_NEW via CronBatchService.
26
 * If the caller is already inside a transaction (e.g., controller class-level @Transactional),
27
 * REQUIRES_NEW will suspend it cleanly.
28
 */
29
@Service
30
public class OfferBatchService {
31
 
32
    private static final Logger LOGGER = LogManager.getLogger(OfferBatchService.class);
33
 
36347 amit 34
    private static final ExecutorService BATCH_EXECUTOR = Executors.newFixedThreadPool(3, r -> {
35
        Thread t = new Thread(r, "offer-batch-worker");
36
        t.setDaemon(true);
37
        return t;
38
    });
39
 
40
    private final Set<Integer> inFlightOfferIds = ConcurrentHashMap.newKeySet();
41
 
36342 amit 42
    @Autowired
43
    private OfferService offerService;
44
 
45
    @Autowired
46
    private OfferProcessingHelper offerProcessingHelper;
47
 
48
    @Autowired
49
    private CronBatchService cronBatchService;
50
 
36354 amit 51
    @Autowired
52
    private CronBatchRepository cronBatchRepository;
53
 
36347 amit 54
    /**
55
     * Fire-and-forget: schedule the batch on a background worker and return immediately.
56
     * Prevents duplicate concurrent runs for the same offerId at the JVM level.
57
     * Used by HTTP controllers; cron CLI keeps using the sync processOfferWithBatch.
58
     */
59
    public String submitBatchAsync(int offerId) {
60
        if (!inFlightOfferIds.add(offerId)) {
61
            LOGGER.info("Offer {} batch submit ignored — already in flight", offerId);
62
            return "Offer " + offerId + " is already being processed. Check the batch summary for progress.";
63
        }
64
        BATCH_EXECUTOR.submit(() -> {
65
            try {
66
                processOfferWithBatch(offerId);
67
            } catch (Exception e) {
68
                LOGGER.error("Offer {} batch processing failed: {}", offerId, e.getMessage(), e);
69
            } finally {
70
                inFlightOfferIds.remove(offerId);
71
            }
72
        });
73
        LOGGER.info("Offer {} batch submitted to background worker", offerId);
74
        return "Offer " + offerId + " submitted for processing. Check the batch summary for progress.";
75
    }
76
 
36342 amit 77
    public void processOfferWithBatch(int offerId) throws Exception {
36354 amit 78
        List<CronBatch> running = cronBatchRepository.selectRunningForOffer(offerId);
79
        if (!running.isEmpty()) {
80
            CronBatch existing = running.get(0);
81
            LOGGER.info("Offer {} already running (batch {}, started {}); skipping duplicate run",
82
                    offerId, existing.getId(), existing.getStartedAt());
83
            return;
84
        }
85
 
36342 amit 86
        CreateOfferRequest createOfferRequest = offerProcessingHelper.loadOfferRequest(offerId);
87
        if (createOfferRequest == null) {
88
            return;
89
        }
90
        LOGGER.info("Processing offer {} (type={}) with batch tracking", offerId, createOfferRequest.getSchemeType());
91
 
92
        if (createOfferRequest.getSchemeType().equals(OfferSchemeType.ACTIVATION)) {
93
            processActivationOfferWithBatch(offerId, createOfferRequest);
94
        } else if (createOfferRequest.getSchemeType().equals(OfferSchemeType.SELLIN)) {
95
            processSellinOfferWithBatch(offerId, createOfferRequest);
96
        } else {
97
            LOGGER.warn("Offer {} has unsupported scheme type: {}", offerId, createOfferRequest.getSchemeType());
98
        }
99
    }
100
 
101
    private void processActivationOfferWithBatch(int offerId, CreateOfferRequest createOfferRequest) throws Exception {
102
        List<OfferPartnerPayoutData> partnerPayouts;
103
        try {
104
            partnerPayouts = offerService.calculateOfferPayouts(createOfferRequest);
105
        } catch (Exception e) {
106
            LOGGER.error("Failed to calculate activation payouts for offer {}: {}", offerId, e.getMessage());
107
            return;
108
        }
109
 
110
        if (partnerPayouts.isEmpty()) {
111
            LOGGER.info("No eligible partners for activation offer {}", offerId);
112
            return;
113
        }
114
 
115
        LinkedHashMap<Integer, String> fofoIdPartnerNameMap = new LinkedHashMap<>();
116
        for (OfferPartnerPayoutData data : partnerPayouts) {
117
            fofoIdPartnerNameMap.put(data.getFofoId(), "fofo-" + data.getFofoId());
118
        }
119
 
120
        CronBatch batch = cronBatchService.createBatch("processActivationOffer-" + offerId, fofoIdPartnerNameMap);
121
 
122
        for (OfferPartnerPayoutData data : partnerPayouts) {
123
            try {
124
                offerProcessingHelper.processPartnerOfferPayout(
125
                        batch.getId(), data.getFofoId(), offerId, createOfferRequest.getDescription(),
126
                        data.getLineItemValueMap(), data.getItemCriteriaPayout(), data.getCriteriaId(),
127
                        data.getSerialNumberPaid(), data.getAgeingSummaryModelsMap(),
128
                        data.getEligiblePayoutValue(), data.isDiscount());
129
            } catch (Exception e) {
130
                LOGGER.error("Activation offer {} failed for fofoId={}: {}", offerId, data.getFofoId(), e.getMessage());
131
                cronBatchService.markItemFailed(batch.getId(), data.getFofoId(), e.getMessage());
132
            }
133
        }
134
 
135
        cronBatchService.finalizeBatch(batch.getId());
136
    }
137
 
138
    private void processSellinOfferWithBatch(int offerId, CreateOfferRequest createOfferRequest) throws Exception {
139
        List<SellinPartnerPayoutData> partnerPayouts;
140
        try {
141
            partnerPayouts = offerService.calculateSellinPayouts(createOfferRequest);
142
        } catch (Exception e) {
143
            LOGGER.error("Failed to calculate sellin payouts for offer {}: {}", offerId, e.getMessage());
144
            return;
145
        }
146
 
147
        if (partnerPayouts.isEmpty()) {
148
            LOGGER.info("No eligible partners for sellin offer {}", offerId);
149
            return;
150
        }
151
 
152
        LinkedHashMap<Integer, String> fofoIdPartnerNameMap = new LinkedHashMap<>();
153
        for (SellinPartnerPayoutData data : partnerPayouts) {
154
            fofoIdPartnerNameMap.put(data.getFofoId(), "fofo-" + data.getFofoId());
155
        }
156
 
157
        CronBatch batch = cronBatchService.createBatch("processSellinOffer-" + offerId, fofoIdPartnerNameMap);
158
 
159
        for (SellinPartnerPayoutData data : partnerPayouts) {
160
            try {
161
                offerProcessingHelper.processPartnerSellinPayout(
162
                        batch.getId(), data.getFofoId(), offerId, createOfferRequest.getDescription(),
163
                        data.getSerialNumberInventoryItemMap(), data.getItemCriteriaPayout(), data.getCriteriaId(),
164
                        data.getSerialNumberPaid(), data.getEligiblePayoutValue(), data.isDiscount());
165
            } catch (Exception e) {
166
                LOGGER.error("Sellin offer {} failed for fofoId={}: {}", offerId, data.getFofoId(), e.getMessage());
167
                cronBatchService.markItemFailed(batch.getId(), data.getFofoId(), e.getMessage());
168
            }
169
        }
170
 
171
        cronBatchService.finalizeBatch(batch.getId());
172
    }
173
}