Subversion Repositories SmartDukaan

Rev

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