Короче, у меня есть ошибка в Java-программе, которую компания надеялась использовать для отслеживания бонусных (бесплатных) продуктов, которые она предоставляет клиентам (компания является дистрибьютором детских товаров, и они продают в аптеки через склады). Проблема в том, что склады иногда продают товары с дополнительными бонусами, которых нет в официальном списке кампаний за этот месяц, и поэтому, когда они отправляют нам обратно данные о своих продажах, компания должна это отслеживать и информировать их о сокращениях.
Проблема в коде, который функционирует как часть более широкой программы, которая стандартизирует и анализирует рассматриваемые данные, возникает, когда кампания, которая имеет минимальные требования к количеству, и определенная аптека (обозначенная как «АПТЕКА» Уникальный тег GLN NUMBER) соответствует минимальному объему заказа для первого продукта, но не для второго. Эта кампания, о которой идет речь, которая представлена одной строкой в файле Bones.csv, также не должна быть действительна для первой продажи продукта. в строке 10 входного файла примера/теста ниже это единственная ситуация, в которой код дает сбой — он должен сократить 3 из 6 бонусов, но этого не происходит.
Это код на данный момент (Я сильно подозреваю, что виноват либо метод CompareSalesWithCampaigns(), либо метод getCampaignBonus() для минимально воспроизводимых примеров, но остальная часть программы предоставлена и здесь для справки):
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 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 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 headers;
List data;
public ParsedCSV(List headers, List data) {
this.headers = headers;
this.data = data;
}
}
private static ParsedCSV parseCSV(String filePath) throws IOException {
List data = new ArrayList();
List 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 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 compareSalesWithCampaigns(List salesData, List campaignData) throws ParseException {
List discrepancies = new ArrayList();
for (Map 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 bestCampaign = null;
int bestCampaignBonus = 0;
boolean priceMismatch = false;
//System.out.println("Processing sale: " + sale); // Debug statement
for (Map 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 0 || autoApproved > 0) {
autoCut = saleBonus - autoApproved;
}
//System.out.println("Sale: " + sale.get("PRODUCT NAME") + ", Auto Approved: " + autoApproved + ", Auto Cut: " + autoCut); // Debug statement
Map 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 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 campaign : campaignData) {
if (isCampaignDateValid(campaign, saleDate)) {
int campaignBonus = getCampaignBonusForDate(campaign);
maxCampaignBonus = Math.max(maxCampaignBonus, campaignBonus);
}
}
return maxCampaignBonus;
}
private static boolean[] isProductEligibleForCampaign(Map campaign, String productBarcode, int saleQuantity, double salePrice) {
boolean[] result = new boolean[2]; // [eligible, priceMismatch]
for (int i = 1; i = 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 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 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 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 discrepancies, String filePath, List additionalHeaders) throws IOException {
try (CSVWriter writer = new CSVWriter(new FileWriter(filePath))) {
// Predefined headers
List 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 combinedHeadersSet = new LinkedHashSet(predefinedHeaders);
combinedHeadersSet.addAll(additionalHeaders);
List combinedHeaders = new ArrayList(combinedHeadersSet);
// Write headers to CSV
writer.writeNext(combinedHeaders.toArray(new String[0]));
// Write data records to CSV
for (Map discrepancy : discrepancies) {
List record = new ArrayList();
for (String header : combinedHeaders) {
record.add(discrepancy.getOrDefault(header, ""));
}
writer.writeNext(record.toArray(new String[0]));
}
}
}
}
это образец CSV-файла бонусных кампаний («MF» — это сокращение компании для определенного типа бонусов, а не что-то вульгарное):
образец CSV-файла бонусных кампаний
Короче, у меня есть ошибка в Java-программе, которую компания надеялась использовать для отслеживания бонусных (бесплатных) продуктов, которые она предоставляет клиентам (компания является дистрибьютором детских товаров, и они продают в аптеки через склады). Проблема в том, что склады иногда продают товары с дополнительными бонусами, которых нет в официальном списке кампаний за этот месяц, и поэтому, когда они отправляют нам обратно данные о своих продажах, компания должна это отслеживать и информировать их о сокращениях. Проблема в коде, который функционирует как часть более широкой программы, которая стандартизирует и анализирует рассматриваемые данные, возникает, когда кампания, которая имеет минимальные требования к количеству, и определенная аптека (обозначенная как «АПТЕКА» Уникальный тег GLN NUMBER) соответствует минимальному объему заказа для первого продукта, но не для второго. Эта кампания, о которой идет речь, которая представлена одной строкой в файле Bones.csv, также не должна быть действительна для первой продажи продукта. в строке 10 входного файла примера/теста ниже это единственная ситуация, в которой код дает сбой — он должен сократить 3 из 6 бонусов, но этого не происходит. Это код на данный момент ([b]Я сильно подозреваю, что виноват либо метод CompareSalesWithCampaigns(), либо метод getCampaignBonus()[/b] для минимально воспроизводимых примеров, но остальная часть программы предоставлена и здесь для справки): [code]package mf_app.BonusCalculate_3;
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 campaignData; // Added campaignData as class-level variable
List 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 headers; List data;
public ParsedCSV(List headers, List data) { this.headers = headers; this.data = data; } }
private static ParsedCSV parseCSV(String filePath) throws IOException { List data = new ArrayList(); List 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 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 compareSalesWithCampaigns(List salesData, List campaignData) throws ParseException { List discrepancies = new ArrayList();
for (Map sale : salesData) { boolean noSale = false; boolean saleNoBonus = false;
String productBarcode = sale.get("BARCODE"); // Use the barcode instead of the product name
Map 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 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 campaign : campaignData) { if (isCampaignDateValid(campaign, saleDate)) { int campaignBonus = getCampaignBonusForDate(campaign); maxCampaignBonus = Math.max(maxCampaignBonus, campaignBonus); } } return maxCampaignBonus; }
private static boolean[] isProductEligibleForCampaign(Map campaign, String productBarcode, int saleQuantity, double salePrice) { boolean[] result = new boolean[2]; // [eligible, priceMismatch] for (int i = 1; i = 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 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);
private static int getCampaignBonusForDate(Map 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 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); } }
// Combine predefined headers with additional headers, avoiding duplicates Set combinedHeadersSet = new LinkedHashSet(predefinedHeaders); combinedHeadersSet.addAll(additionalHeaders); List combinedHeaders = new ArrayList(combinedHeadersSet);
// Write headers to CSV writer.writeNext(combinedHeaders.toArray(new String[0]));
// Write data records to CSV for (Map discrepancy : discrepancies) { List record = new ArrayList(); for (String header : combinedHeaders) { record.add(discrepancy.getOrDefault(header, "")); } writer.writeNext(record.toArray(new String[0])); } } }
} [/code] это образец CSV-файла бонусных кампаний («MF» — это сокращение компании для определенного типа бонусов, а не что-то вульгарное): образец CSV-файла бонусных кампаний [code]PRODUCT_1_NAME,PRODUCT_1_BARCODE,PRODUCT_1_MINIMUM_QUANTITY,PRODUCT_1_MF,PRODUCT_1_PRICE,PRODUCT_2_NAME,PRODUCT_2_BARCODE,PRODUCT_2_MINIMUM_QUANTITY,PRODUCT_2_MF,PRODUCT_2_PRICE,PRODUCT_3_NAME,PRODUCT_3_BARCODE,PRODUCT_3_MINIMUM_QUANTITY,PRODUCT_3_MF,PRODUCT_3_PRICE,ValidFrom,ValidUntil A,1,10,2,1000,,,,,,,,,,,01/01/2024,01/02/2024 A,1,10,3,1000,,,,,,,,,,,01/01/2024,01/02/2024 A,1,10,5,1000,,,,,,,,,,,10/01/2024,15/02/2024 A,1,10,6,1000,B,2,3,1,1500,,,,,,01/01/2024,01/02/2024 B,2,100,50,1500,,,,,,,,,,,01/01/2024,01/02/2024 B,2,10,3,1500,,,,,,,,,,,01/01/2024,01/02/2024 [/code] это образец CSV-файла с данными о продажах, который необходимо проверить: пример CSV-файла с данными о продажах [code] PRODUCT NAME,BARCODE,PHARMACY GLN NUMBER,PHARMACY NAME,DATE,ORDER NO,TOWN,PROVINCE,QUANTITY,BONUS,PRICE A,1,100,Pharma 1,02/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,2,1000 A,1,200,Pharma 2,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,3,1000 A,1,300,Pharma 3,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,11,3,1000 A,1,400,Pharma 4,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,20,6,1000 A,1,500,Pharma 5,04/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,5,1000 A,1,600,Pharma 6,10/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,5,1000 A,1,700,Pharma 7,04/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,6,1000 B,2,700,Pharma 7,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,3,1,1500 A,1,800,Pharma 8,04/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,6,1000 B,2,800,Pharma 8,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,2,0,1500 B,2,900,Pharma 9,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,105,50,1500 B,2,1000,Pharma 10,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,20,10,1500 A,1,1100,Pharma 11,02/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,2,800 C,3,1200,Pharma 12,02/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,5,1,35 [/code] это вывод, который должна получить программа: вывод, который должна получить программа [code] PRODUCT NAME,BARCODE,PHARMACY GLN NUMBER,PHARMACY NAME,DATE,ORDER NO,TOWN,PROVINCE,QUANTITY,BONUS,PRICE,WAREHOUSE,AUTO APPROVED,AUTO CUT,MANUAL APPROVE,HIGHLIGHT,NOTE A,1,100,Pharma 1,02/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,2,1000,xxxxxxx,2,,,,Auto Approved: 2. A,1,200,Pharma 2,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,3,1000,xxxxxxx,3,,,,Auto Approved: 3. A,1,300,Pharma 3,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,11,3,1000,xxxxxxx,3,,,,Auto Approved: 3. A,1,400,Pharma 4,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,20,6,1000,xxxxxxx,6,,,,Auto Approved: 6. A,1,500,Pharma 5,04/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,5,1000,xxxxxxx,3,2,,!,Auto Approved: 3. A,1,600,Pharma 6,10/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,5,1000,xxxxxxx,5,,,,Auto Approved: 5. A,1,700,Pharma 7,04/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,6,1000,xxxxxxx,6,,,,Auto Approved: 6. B,2,700,Pharma 7,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,3,1,1500,xxxxxxx,1,,,,Auto Approved: 1. A,1,800,Pharma 8,04/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,6,1000,xxxxxxx,3,3,,!,Auto Approved: 3. B,2,800,Pharma 8,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,2,0,1500,xxxxxxx,0,,,,NO BONUS B,2,900,Pharma 9,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,105,50,1500,xxxxxxx,50,,,,Auto Approved: 50. B,2,1000,Pharma 10,03/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,20,10,1500,xxxxxxx,6,4,,!,Auto Approved: 6. A,1,1100,Pharma 11,02/01/2024,xxxxxxx,xxxxxxx,xxxxxxx,10,2,800,xxxxxxx,2,,,!!,"Price mismatch, but: Auto Approved: 2. " [/code] это вывод, который он генерирует в данный момент (обратите внимание на строку 10!!!): вывод, который он генерирует в данный момент [code]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 A,1,100,Pharma 1,02.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,10,2,1000,TO_CHECK_EXAMPLE,2,0,,,,Auto Approved: 2. A,1,200,Pharma 2,03.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,10,3,1000,TO_CHECK_EXAMPLE,3,0,,,,Auto Approved: 3. A,1,300,Pharma 3,03.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,11,3,1000,TO_CHECK_EXAMPLE,3,0,,,,Auto Approved: 3. A,1,400,Pharma 4,03.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,20,6,1000,TO_CHECK_EXAMPLE,6,0,,,,Auto Approved: 6. A,1,500,Pharma 5,04.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,10,5,1000,TO_CHECK_EXAMPLE,3,2,,!,,Auto Approved: 3. A,1,600,Pharma 6,10.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,10,5,1000,TO_CHECK_EXAMPLE,5,0,,,,Auto Approved: 5. A,1,700,Pharma 7,04.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,10,6,1000,TO_CHECK_EXAMPLE,6,0,,,,Auto Approved: 6. B,2,700,Pharma 7,03.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,3,1,1500,TO_CHECK_EXAMPLE,1,0,,,,Auto Approved: 1. A,1,800,Pharma 8,04.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,10,6,1000,TO_CHECK_EXAMPLE,6,0,,,,Auto Approved: 6. B,2,800,Pharma 8,03.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,2,0,1500,TO_CHECK_EXAMPLE,0,0,,,,NO BONUS B,2,900,Pharma 9,03.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,105,50,1500,TO_CHECK_EXAMPLE,50,0,,,,Auto Approved: 50. B,2,1000,Pharma 10,03.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,20,10,1500,TO_CHECK_EXAMPLE,6,4,,!,,Auto Approved: 6. A,1,1100,Pharma 11,02.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,10,2,800,TO_CHECK_EXAMPLE,2,0,,,!!,"Price mismatch, but: Auto Approved: 2. " C,3,1200,Pharma 12,02.01.2024,xxxxxxx,xxxxxxx,xxxxxxx,5,1,35,TO_CHECK_EXAMPLE,0,1,,!,,Rejected: No matching campaign found [/code] а это пояснения сокращений и утверждений: пояснения сокращений и согласований