this question will be a bit of a coding challenge perhaps, as I am not sure what exactly has gone wrong in the 400-odd lines of the class in question (though I suspect it will be related to either compareSalesWithCampaigns(), getMaxCampaignBonusForDate(), isProductEligibleForCampaign(), getCampaignBonus(), getCampaignBonusForDate() or isCampaignDateValid() ).
in short, we have a bug in a java program the company was hoping to use to track bonus (free) products that it gives customers (the company is a baby products distributor, and they sell to pharmacies through warehouses). The problem is that warehouses sometimes sell products with extra bonuses which are not in the official campaigns list for that month, and so when they send us back their sales data, the company needs to track this and inform them of cuts.
The issue in the code, which functions as part of a wider program which standardises and analyses the data in question, occurs when a campaign which has minimum quantity requirements, and a certain pharmacy (identified by the “PHARMACY GLN NUMBER” unique tag) satisfies minimum order quantity for the first product, but not the second one. That campaign in question, which is represented by one line in a bonuses.csv file, should not be valid for the first product sale either. in line 10 in the below example/test input file, this is the only situation in which the code fails – it should cut 3 of the 6 bonuses, but it does not.
This is the code so far:
package mf_app.BonusCalculate_3;
import com.opencsv.CSVReader;
import com.opencsv.CSVWriter;
import tocsv.UniversalConverter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
public class BonusTracker {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
private static final String[] dateFormats = {
"dd/MM/yyyy", "d/M/yyyy", "dd/M/yyyy", "d/MM/yyyy", // Original formats with slash as separator
"dd.MM.yyyy", "d.M.yyyy", "dd.M.yyyy", "d.MM.yyyy", "dd.MM.yy", // Formats with dot as separator
"MM/dd/yyyy", "M/d/yyyy", "MM/d/yyyy", "M/dd/yyyy", // Additional US-style formats with slash as separator
"MM/dd/yy"
};
private static final NumberFormat US_FORMAT = NumberFormat.getInstance(Locale.US);
private static final NumberFormat EU_FORMAT = NumberFormat.getInstance(Locale.GERMANY);
private static List<Map<String, String>> campaignData; // Added campaignData as class-level variable
public static void main(String[] args) {
String bonusesDirectoryPath = "./data/0_UTILITIES/BONUSES";
String salesFilePath = "./data/5_1_CALCULATIONS_1/CALCULATED_1.csv";
String reportFilePath = "./data/5_2_MF_CHECKED/MF_CHECKED.csv";
try {
String campaignsFilePath = findAndConvertBonusesFile(bonusesDirectoryPath);
if (campaignsFilePath == null) {
System.err.println("No bonuses CSV or Excel file found in the directory: " + bonusesDirectoryPath);
System.exit(1);
}
ParsedCSV salesParsedCSV = parseCSV(salesFilePath);
ParsedCSV campaignsParsedCSV = parseCSV(campaignsFilePath);
campaignData = campaignsParsedCSV.data;
List<Map<String, String>> discrepancies = compareSalesWithCampaigns(salesParsedCSV.data, campaignData);
System.out.println("Total Discrepancies Found: " + discrepancies.size());
generateReport(discrepancies, reportFilePath, salesParsedCSV.headers);
System.out.println("Discrepancies reported: " + discrepancies.size());
System.out.println("Report generated successfully: " + reportFilePath);
} catch (IOException | ParseException e) {
e.printStackTrace();
System.exit(1); // Exit with error status
} catch (Error err) {
// This block catches OutOfMemoryError and other serious errors
System.err.println("A severe error occurred: " + err.getMessage());
if (err instanceof OutOfMemoryError) {
System.err.println("The application ran out of memory. Consider increasing the heap size or optimizing memory usage.");
} else {
System.err.println("Please check the application's environment and configuration.");
}
System.exit(2); // Exit with a specific error status for this type of failure
}
}
private static String findAndConvertBonusesFile(String directoryPath) throws IOException {
File directory = new File(directoryPath);
if (directory.isDirectory()) {
// Check for Excel file first
File[] excelFiles = directory.listFiles((dir, name) -> name.toLowerCase().endsWith(".xlsx"));
if (excelFiles != null && excelFiles.length > 0) {
System.out.println("excel not found!");
// Convert the first Excel file found to CSV
String excelFilePath = excelFiles[0].getPath();
String csvFilePath = directoryPath + "/" + excelFiles[0].getName().replace(".xlsx", ".csv");
UniversalConverter.convertFiles(directoryPath, directoryPath);
return csvFilePath;
}
// If no Excel file, check for CSV file
File[] csvFiles = directory.listFiles((dir, name) -> name.toLowerCase().endsWith(".csv"));
if (csvFiles != null && csvFiles.length > 0) {
System.out.println("csv not found!");
return csvFiles[0].getPath();
}
}
return null;
}
// static ParsedCSV class:
static class ParsedCSV {
List<String> headers;
List<Map<String, String>> data;
public ParsedCSV(List<String> headers, List<Map<String, String>> data) {
this.headers = headers;
this.data = data;
}
}
private static ParsedCSV parseCSV(String filePath) throws IOException {
List<Map<String, String>> data = new ArrayList<>();
List<String> headersList = new ArrayList<>();
try (CSVReader reader = new CSVReader(new FileReader(filePath))) {
String[] headers = reader.readNext();
if (headers != null) {
for (int i = 0; i < headers.length; i++) {
headers[i] = headers[i].replaceAll("[^\x20-\x7E]", "").trim();
headersList.add(headers[i]);
}
}
String[] line;
while ((line = reader.readNext()) != null) {
Map<String, String> row = new HashMap<>();
for (int i = 0; i < headers.length; i++) {
row.put(headers[i], line[i]);
}
data.add(row);
}
}
return new ParsedCSV(headersList, data);
}
private static List<Map<String, String>> compareSalesWithCampaigns(List<Map<String, String>> salesData, List<Map<String, String>> campaignData) throws ParseException {
List<Map<String, String>> discrepancies = new ArrayList<>();
for (Map<String, String> sale : salesData) {
boolean noSale = false;
boolean saleNoBonus = false;
String productBarcode = sale.get("BARCODE"); // Use the barcode instead of the product name
int saleQuantity = 0;
int saleBonus = 0;
try {
saleQuantity = parseEuropeanInteger(sale.get("QUANTITY"));
} catch (NumberFormatException e) {
noSale = true;
saleQuantity = 0;
}
if(!noSale) {
try {
saleBonus = parseEuropeanInteger(sale.get("BONUS"));
} catch (NumberFormatException e) {
noSale = true;
saleBonus = 0;
}
if (saleBonus == 0) {
saleNoBonus = true;
}
}
//int saleQuantity = parseEuropeanInteger(sale.get("QUANTITY"));
System.out.println("the bonus is: " + sale.get("BONUS"));
//int saleBonus = parseEuropeanInteger(sale.get("BONUS"));
double salePrice = parsePrice(sale.get("PRICE"));
Date saleDate = parseDate(sale.get("DATE"), false);
Map<String, String> bestCampaign = null;
int bestCampaignBonus = 0;
boolean priceMismatch = false;
//System.out.println("Processing sale: " + sale); // Debug statement
for (Map<String, String> campaign : campaignData) {
boolean[] eligibilityAndMismatch = isProductEligibleForCampaign(campaign, productBarcode, saleQuantity, salePrice); // Pass product barcode
boolean isEligible = eligibilityAndMismatch[0];
boolean currentPriceMismatch = eligibilityAndMismatch[1];
if (isCampaignDateValid(campaign, saleDate) && isEligible) {
int campaignBonus = getCampaignBonus(campaign, saleQuantity);
//System.out.println("Campaign: " + campaign + " Bonus: " + campaignBonus); // Debug statement
if (campaignBonus > bestCampaignBonus && campaignBonus <= saleBonus) {
bestCampaignBonus = campaignBonus;
bestCampaign = campaign;
priceMismatch = currentPriceMismatch;
}
}
}
System.out.println("Best Campaign: " + bestCampaign + " With Bonus: " + bestCampaignBonus);
int autoApproved = bestCampaignBonus;
int autoCut = 0;
if (saleBonus > 0 || autoApproved > 0) {
autoCut = saleBonus - autoApproved;
}
//System.out.println("Sale: " + sale.get("PRODUCT NAME") + ", Auto Approved: " + autoApproved + ", Auto Cut: " + autoCut); // Debug statement
Map<String, String> discrepancyRecord = new HashMap<>(sale);
discrepancyRecord.put("AUTO APPROVED", String.valueOf(autoApproved));
discrepancyRecord.put("AUTO CUT", String.valueOf(autoCut));
if (noSale) {
discrepancyRecord.put("NOTE", "NO SALE");
}
else if (saleNoBonus) {
discrepancyRecord.put("NOTE", "NO BONUS");
}
else if (priceMismatch) {
discrepancyRecord.put("PRICE HIGHLIGHT", "!!");
//discrepancyRecord.put("NOTE", "Campaign found but price mismatch");
discrepancyRecord.put("NOTE", ("Price mismatch, but: " + calculateNote(bestCampaign, saleQuantity, autoApproved)));
} else {
discrepancyRecord.put("HIGHLIGHT", autoCut > 0 ? "!" : "");
discrepancyRecord.put("NOTE", calculateNote(bestCampaign, saleQuantity, autoApproved));
}
discrepancies.add(discrepancyRecord);
}
return discrepancies;
}
private static String calculateNote(Map<String, String> bestCampaign, int saleQuantity, int autoApprovedBonus) throws ParseException {
if (bestCampaign == null) {
// Handle the case where no eligible campaign is found
return "Rejected: No matching campaign found";
}
// Get the date of the sale from the bestCampaign
String saleDateString = bestCampaign.get("ValidFrom");
String saleDateStrings = bestCampaign.get("ValidUntil");
if (saleDateString == null || saleDateString.isEmpty()) {
// Handle the case where the sale date is missing
// System.out.println("DEBUG: Sale date is missing for the sale. Best Campaign: " + bestCampaign);
return "Note: Sale date is missing.";
}
// System.out.println("DEBUG: Sale date string: " + saleDateString);
// Parse the sale date
Date saleDate;
try {
saleDate = dateFormat.parse(saleDateString);
saleDate = dateFormat.parse(saleDateStrings);
// System.out.println("DEBUG: Parsed sale date: " + saleDate);
} catch (ParseException e) {
// Handle parsing exception
e.printStackTrace();
return "oups; this is not parseable as a date..."; // Return empty note in case of error
}
// Calculate the maximum campaign bonus for the given date
int maxCampaignBonus = getMaxCampaignBonusForDate(saleDate);
// System.out.println("DEBUG: Maximum campaign bonus for sale date: " + maxCampaignBonus);
// Calculate auto cut and auto approve amounts
int autoCut = Math.max(0, autoApprovedBonus - maxCampaignBonus);
int autoApproved = autoApprovedBonus - autoCut;
/*
System.out.println("DEBUG: Auto Approved: " + autoApproved);
System.out.println("DEBUG: Auto Cut: " + autoCut);*/
// Construct the note with auto cut and auto approve amounts
StringBuilder note = new StringBuilder();
note.append("Auto Approved: ").append(autoApproved).append(". ");
if (autoCut > 0) {
note.append("Auto Cut: ").append(autoCut).append(". ");
}
return note.toString();
}
private static int getMaxCampaignBonusForDate(Date saleDate) throws ParseException {
int maxCampaignBonus = 0;
// Iterate through all campaigns and calculate the maximum campaign bonus for the given date
for (Map<String, String> campaign : campaignData) {
if (isCampaignDateValid(campaign, saleDate)) {
int campaignBonus = getCampaignBonusForDate(campaign);
maxCampaignBonus = Math.max(maxCampaignBonus, campaignBonus);
}
}
return maxCampaignBonus;
}
private static boolean[] isProductEligibleForCampaign(Map<String, String> campaign, String productBarcode, int saleQuantity, double salePrice) {
boolean[] result = new boolean[2]; // [eligible, priceMismatch]
for (int i = 1; i <= 3; i++) {
String campaignProductBarcode = campaign.get("PRODUCT_" + i + "_BARCODE");
if (!campaignProductBarcode.isEmpty() && campaignProductBarcode.equals(productBarcode)) {
int minOrderQuantity = Integer.parseInt(campaign.getOrDefault("PRODUCT_" + i + "_MINIMUM_QUANTITY", "0"));
double campaignPrice = Double.parseDouble(campaign.getOrDefault("PRODUCT_" + i + "_PRICE", "0"));
if (saleQuantity >= minOrderQuantity) {
result[0] = true; // Eligible based on quantity
result[1] = (salePrice != campaignPrice); // Price mismatch check
return result;
}
}
}
result[0] = false; // Not eligible
return result;
}
private static int getCampaignBonus(Map<String, String> campaign, int totalSaleQuantity) {
int maxCampaignBonus = 0;
// Check if the combined campaign for all products meets the minimum order quantity
String campaignQtyStr1 = campaign.getOrDefault("PRODUCT_1_MINIMUM_QUANTITY", "0");
int campaignQty1 = campaignQtyStr1.isEmpty() ? 0 : Integer.parseInt(campaignQtyStr1);
String campaignQtyStr2 = campaign.getOrDefault("PRODUCT_2_MINIMUM_QUANTITY", "0");
int campaignQty2 = campaignQtyStr2.isEmpty() ? 0 : Integer.parseInt(campaignQtyStr2);
String campaignQtyStr3 = campaign.getOrDefault("PRODUCT_3_MINIMUM_QUANTITY", "0");
int campaignQty3 = campaignQtyStr3.isEmpty() ? 0 : Integer.parseInt(campaignQtyStr3);
// Check if all products involved in the campaign meet their criteria
boolean isValidCampaign1 = totalSaleQuantity >= campaignQty1;
boolean isValidCampaign2 = totalSaleQuantity >= campaignQty2;
boolean isValidCampaign3 = totalSaleQuantity >= campaignQty3;
// Set the maximum campaign bonus based on valid combinations
if (isValidCampaign1 && isValidCampaign2 && isValidCampaign3) {
String campaignBonusStr1 = campaign.getOrDefault("PRODUCT_1_MF", "0");
int campaignBonus1 = campaignBonusStr1.isEmpty() ? 0 : Integer.parseInt(campaignBonusStr1);
String campaignBonusStr2 = campaign.getOrDefault("PRODUCT_2_MF", "0");
int campaignBonus2 = campaignBonusStr2.isEmpty() ? 0 : Integer.parseInt(campaignBonusStr2);
String campaignBonusStr3 = campaign.getOrDefault("PRODUCT_3_MF", "0");
int campaignBonus3 = campaignBonusStr3.isEmpty() ? 0 : Integer.parseInt(campaignBonusStr3);
maxCampaignBonus = Math.max(Math.max(campaignBonus1, campaignBonus2), campaignBonus3);
} else if (isValidCampaign1 && isValidCampaign2) {
maxCampaignBonus = Math.max(campaign.getOrDefault("PRODUCT_1_MF", "0").isEmpty() ? 0 : Integer.parseInt(campaign.getOrDefault("PRODUCT_1_MF", "0")), campaign.getOrDefault("PRODUCT_2_MF", "0").isEmpty() ? 0 : Integer.parseInt(campaign.getOrDefault("PRODUCT_2_MF", "0")));
} else if (isValidCampaign1 && isValidCampaign3) {
maxCampaignBonus = Math.max(campaign.getOrDefault("PRODUCT_1_MF", "0").isEmpty() ? 0 : Integer.parseInt(campaign.getOrDefault("PRODUCT_1_MF", "0")), campaign.getOrDefault("PRODUCT_3_MF", "0").isEmpty() ? 0 : Integer.parseInt(campaign.getOrDefault("PRODUCT_3_MF", "0")));
} else if (isValidCampaign2 && isValidCampaign3) {
maxCampaignBonus = Math.max(campaign.getOrDefault("PRODUCT_2_MF", "0").isEmpty() ? 0 : Integer.parseInt(campaign.getOrDefault("PRODUCT_2_MF", "0")), campaign.getOrDefault("PRODUCT_3_MF", "0").isEmpty() ? 0 : Integer.parseInt(campaign.getOrDefault("PRODUCT_3_MF", "0")));
} else if (isValidCampaign1) {
maxCampaignBonus = campaign.getOrDefault("PRODUCT_1_MF", "0").isEmpty() ? 0 : Integer.parseInt(campaign.getOrDefault("PRODUCT_1_MF", "0"));
} else if (isValidCampaign2) {
maxCampaignBonus = campaign.getOrDefault("PRODUCT_2_MF", "0").isEmpty() ? 0 : Integer.parseInt(campaign.getOrDefault("PRODUCT_2_MF", "0"));
} else if (isValidCampaign3) {
maxCampaignBonus = campaign.getOrDefault("PRODUCT_3_MF", "0").isEmpty() ? 0 : Integer.parseInt(campaign.getOrDefault("PRODUCT_3_MF", "0"));
}
return maxCampaignBonus;
}
private static int getCampaignBonusForDate(Map<String, String> campaign) {
// Extract the campaign bonus for the given date from the campaign map
String campaignBonusStr = campaign.get("PRODUCT_1_MF"); // Assuming bonus for product 1 is used to represent campaign bonus
int campaignBonus = Integer.parseInt(campaignBonusStr.isEmpty() ? "0" : campaignBonusStr);
// You can add additional logic here if the campaign bonus depends on specific conditions or criteria
return campaignBonus;
}
private static boolean isCampaignDateValid(Map<String, String> campaign, Date saleDate) throws ParseException {
// If saleDate is null, set it to the default value of 01/01/2000
if (saleDate == null) {
saleDate = new SimpleDateFormat("dd/MM/yyyy").parse("01/01/2000");
}
String validFromStr = campaign.getOrDefault("ValidFrom", "");
String validUntilStr = campaign.getOrDefault("ValidUntil", "");
Date validFrom = validFromStr.isEmpty() ? new Date(Long.MIN_VALUE) : parseDate(validFromStr, true);
Date validUntil = validUntilStr.isEmpty() ? new Date(Long.MAX_VALUE) : parseDate(validUntilStr, true);
return !saleDate.before(validFrom) && !saleDate.after(validUntil);
}
// Refactor parseDate to minimize unnecessary parsing attempts
private static Date parseDate(String dateString, Boolean isQuiet) throws ParseException {
if (dateString == null || dateString.trim().isEmpty()) {
return null; // Or a more appropriate default date
}
ParseException lastException = null;
for (String format : dateFormats) {
try {
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
dateFormat.setLenient(false); // Ensure strict parsing
return dateFormat.parse(dateString);
} catch (ParseException e) {
lastException = e;
}
}
if (lastException != null) {
throw lastException;
}
return null; // This should never happen if dateFormats is comprehensive
}
private static double parsePrice(String priceString) throws ParseException {
if (priceString == null || priceString.trim().isEmpty()) {
return 0.0; // Return zero or another appropriate default value
}
// Try parsing in US format
try {
Number number = US_FORMAT.parse(priceString);
return number.doubleValue();
} catch (ParseException e) {
// Try parsing in EU format
try {
Number number = EU_FORMAT.parse(priceString);
return number.doubleValue();
} catch (ParseException euException) {
throw euException; // Throw the exception from the EU format attempt
}
}
}
public static int parseEuropeanInteger(String value) {
try {
// Remove commas before parsing
return Integer.parseInt(value.replaceAll(",", ""));
} catch (NumberFormatException e) {
throw new NumberFormatException("Unable to parse the integer: " + value);
}
}
private static void generateReport(List<Map<String, String>> discrepancies, String filePath, List<String> additionalHeaders) throws IOException {
try (CSVWriter writer = new CSVWriter(new FileWriter(filePath))) {
// Predefined headers
List<String> predefinedHeaders = Arrays.asList(
"PRODUCT NAME", "BARCODE", "PHARMACY GLN NUMBER", "PHARMACY NAME", "DATE", "ORDER NO",
"TOWN", "PROVINCE", "QUANTITY", "BONUS", "PRICE", "WAREHOUSE",
"AUTO APPROVED", "AUTO CUT", "MANUAL APPROVED", "HIGHLIGHT", "PRICE HIGHLIGHT", "NOTE"
);
// Combine predefined headers with additional headers, avoiding duplicates
Set<String> combinedHeadersSet = new LinkedHashSet<>(predefinedHeaders);
combinedHeadersSet.addAll(additionalHeaders);
List<String> combinedHeaders = new ArrayList<>(combinedHeadersSet);
// Write headers to CSV
writer.writeNext(combinedHeaders.toArray(new String[0]));
// Write data records to CSV
for (Map<String, String> discrepancy : discrepancies) {
List<String> record = new ArrayList<>();
for (String header : combinedHeaders) {
record.add(discrepancy.getOrDefault(header, ""));
}
writer.writeNext(record.toArray(new String[0]));
}
}
}
}
this is a sample bonus campaigns CSV file (“MF” is company shorthand for a specific type of bonus):
sample bonus campaigns CSV file
this is a sample sales data CSV file to be checked:
sample sales data CSV file
this is the output the program is supposed to get:
enter image description here
this is the output it is currently generating (notice line 10!!!):
enter image description here
and these are explanations of the cuts and approvals:
enter image description here