diff --git a/pom.xml b/pom.xml index c463be91..a42534bf 100644 --- a/pom.xml +++ b/pom.xml @@ -245,6 +245,12 @@ reactor-netty + + net.objecthunter + exp4j + 0.4.8 + + diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 9010e157..f6d9ac42 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -407,5 +407,6 @@ public class GepafinConstant { public static final String ASSIGNED_APPLICATION_STATUS_UPDATED_SUCCESSFULLY = "assigned.application.status.updated.successfully"; public static final String REQUIRED_REQUESTED_AMOUNT_MSG = "validation.required.requested.amount"; + public static final String FORMULA_AMOUNT_NOT_MATCHED="formula.amount.not.matches.requested.amount"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 794e7796..f43a030f 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -31,6 +31,7 @@ import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundExceptio import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.h2.util.IOUtils; +import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -49,11 +50,16 @@ import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.text.MessageFormat; +import java.text.NumberFormat; +import java.text.ParseException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -94,29 +100,29 @@ public class ApplicationDao { @Autowired private FlowDataRepository flowDataRepository; - + @Autowired private UserCompanyDelegationRepository userCompanyDelegationRepository; - + @Autowired private Validator validator; - + @Autowired private CompanyService companyService; @Autowired private S3PathConfig s3PathConfig; - + @Autowired private SystemEmailTemplatesService systemEmailTemplatesService; @Autowired private AssignedApplicationsRepository assignedApplicationsRepository; - + @Value("${default_System_Receiver_Email}") private String defaultSystemReceiverEmail; - + @Value("${rinaldo_email}") private String rinaldoEmail; - + @Value("${carlo_email}") private String carloEmail; @@ -125,37 +131,37 @@ public class ApplicationDao { @Autowired private AmazonS3Service amazonS3Service; - + @Autowired private ApplicationSignedDocumentRepository applicationSignedDocumentRepository; - + // @Value("${aws.s3.url.folder.signed.document}") // private String signedDocumentS3Folder; - + @Value("${default.hub.uuid}") private String defaultHubUuid; - + @Autowired private UserService userService; @Autowired private S3PathConfig s3ConfigBean; - + @Autowired private ProtocolDao protocolDao; - + @Autowired private HubService hubService; @Autowired private EmailNotificationDao emailNotificationDao; - + @Autowired private FormDao formDao; @Autowired private EmailLogDao emailLogDao; - + @Autowired private UserWithCompanyRepository userWithCompanyRepository; @@ -183,6 +189,7 @@ public class ApplicationDao { @Autowired private ApplicationEvaluationRepository applicationEvaluationRepository; + public ApplicationResponseBean createApplication(HttpServletRequest request, ApplicationRequestBean applicationRequestBean, Long formId, Long applicationId) { FormEntity formEntity = formService.validateForm(formId); // callService.validatePublishedCall(formEntity.getCall().getId()); @@ -258,7 +265,7 @@ public class ApplicationDao { // List contentResponseBeans = Utils.convertJsonStringToList( // applicationFormEntity.getForm().getContent(), ContentResponseBean.class); - + List contentResponseBeans = formDao.convertFormEntityToFormResponseBean(applicationFormEntity.getForm()).getContent(); for (ApplicationFormFieldEntity applicationFormFieldEntity : applicationFormFieldEntities) { @@ -309,7 +316,7 @@ public class ApplicationDao { ); } ApplicationEntity oldApplicationDataEntity = Utils.getClonedEntityForData(applicationEntity); - + validator.validateUserWithCompany(request, applicationEntity.getCompanyId()); applicationEntity.setIsDeleted(true); applicationEntity = applicationRepository.save(applicationEntity); @@ -356,9 +363,9 @@ public class ApplicationDao { // // return applicationResponses; // } - + public List getAllApplications(UserEntity userEntity, Long callId, Long companyId,List statusList) { - + log.info("Fetching applications for RoleType: {}", userEntity.getRoleEntity().getRoleType()); Specification spec = search(userEntity, callId, companyId,statusList); @@ -482,15 +489,18 @@ public class ApplicationDao { public List createOrUpdateMultipleFormFields(List formFieldResponseBeans, ApplicationFormEntity applicationFormEntity, FormEntity formEntity) { + FieldValidator fieldValidator = FieldValidator.create(); List existingFields = applicationFormFieldRepository.findByApplicationFormId(applicationFormEntity.getId()); - return formFieldResponseBeans.stream().map(requestBean -> createOrUpdateApplicationFormField(requestBean, applicationFormEntity, existingFields, formEntity)) + List applicationFormFieldEntities=formFieldResponseBeans.stream().map(requestBean -> createOrUpdateApplicationFormField(requestBean, applicationFormEntity, existingFields, formEntity,fieldValidator)) .collect(Collectors.toList()); + fieldValidator.validate(); + return applicationFormFieldEntities; } public ApplicationFormFieldEntity createOrUpdateApplicationFormField(ApplicationFormFieldRequestBean applicationFormFieldRequestBean, - ApplicationFormEntity applicationFormEntity, List applicationFormFieldEntities, FormEntity formEntity) { + ApplicationFormEntity applicationFormEntity, List applicationFormFieldEntities, FormEntity formEntity,FieldValidator fieldValidator) { ApplicationFormFieldEntity applicationFormFieldEntity = new ApplicationFormFieldEntity(); @@ -506,7 +516,7 @@ public class ApplicationDao { .filter(setting -> "isRequestedAmount".equals(setting.getName()) && Boolean.TRUE.equals(setting.getValue())) .findFirst() .ifPresent(setting -> { - + Object fieldValue = applicationFormFieldRequestBean.getFieldValue(); if(fieldValue!=null) { try { @@ -538,6 +548,7 @@ public class ApplicationDao { } } } + calculationProcessForFormula(applicationFormEntity,contentResponseBeans,applicationFormFieldRequestBean,fieldValidator); Utils.setIfUpdated(applicationFormFieldEntity::getFieldId, applicationFormFieldEntity::setFieldId, applicationFormFieldRequestBean.getFieldId()); if (applicationFormFieldRequestBean.getFieldValue() != null) { @@ -560,7 +571,6 @@ public class ApplicationDao { VersionHistoryRequest.builder().request(request).actionType(actionType).oldData(oldApplicationFormFieldData).newData(applicationFormField).build()); log.info("Version history logged for action: {}, Field ID: {}", actionType, applicationFormFieldEntity.getFieldId()); - return applicationFormField; } @@ -818,7 +828,7 @@ public class ApplicationDao { if(formApplicationResponse.getContent() != null && formApplicationResponse.getFormFields() != null) { formApplicationResponses.add(formApplicationResponse); } - + } public FormApplicationResponse processForm(FormEntity formEntity, ApplicationEntity applicationEntity) { @@ -954,7 +964,7 @@ public class ApplicationDao { return (int) Math.round(progress); } public void validateFormFields(ApplicationRequestBean request, FormEntity formEntity) { - + // List contentResponseBeans=Utils.convertJsonStringToList(formEntity.getContent(),ContentResponseBean.class); List contentResponseBeans=formDao.convertFormEntityToFormResponseBean(formEntity).getContent(); @@ -1034,7 +1044,7 @@ public class ApplicationDao { SystemEmailTemplateResponse systemEmailTemplateResponse = systemEmailTemplatesService .retrieveTemplateByTypeAndCall(SystemEmailTemplatesEntityTypeEnum.APPLICATION_SUBMISSION_TO_USER_AND_COMPANY, hub, null); - + // Create the map for subject placeholders Map subjectPlaceholders = new HashMap<>(); subjectPlaceholders.put("{{call_name}}", call.getName()); @@ -1211,17 +1221,17 @@ public class ApplicationDao { } public ApplicationSignedDocumentResponse getSignedDocument(HttpServletRequest request, Long applicationId) { - + ApplicationEntity applicationEntity = validateApplication(applicationId); // validator.validateUserWithCompany(request, applicationEntity.getCompanyId()); - + if (validator.checkIsPreInstructor()) { ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluationByApplicationId(applicationId); validator.validatePreInstructor(request, applicationEvaluationEntity.getUserId()); } else { validator.validateUserId(request, applicationEntity.getUserId()); } - + ApplicationSignedDocumentEntity applicationSignedDocument = applicationSignedDocumentRepository .findByApplicationIdAndStatus(applicationId, ApplicationSignedDocumentStatusEnum.ACTIVE.getValue()); if(applicationSignedDocument == null) { @@ -1230,11 +1240,11 @@ public class ApplicationDao { } return convertApplicationSignedDocumentToApplicationSignedDocumentResponse(applicationSignedDocument); } - + public void deleteSignedDocument(HttpServletRequest request, Long applicationId) { ApplicationEntity applicationEntity = validateApplication(applicationId); validator.validateUserWithCompany(request, applicationEntity.getCompanyId()); - + ApplicationSignedDocumentEntity applicationSignedDocument = applicationSignedDocumentRepository .findByApplicationIdAndStatus(applicationId, ApplicationSignedDocumentStatusEnum.ACTIVE.getValue()); //cloned entity for old data @@ -1539,5 +1549,135 @@ public class ApplicationDao { } } + public void calculationProcessForFormula(ApplicationFormEntity applicationFormEntity, List contentResponseBeans, ApplicationFormFieldRequestBean applicationFormFieldRequestBean,FieldValidator fieldValidator) { + List formulaValue = new ArrayList<>(); + String formulaValueOpt=null; + String label=null; + for (ContentResponseBean contentResponseBean:contentResponseBeans){ + if(contentResponseBean.getId().equals(applicationFormFieldRequestBean.getFieldId())){ + for (SettingResponseBean settingResponseBean:contentResponseBean.getSettings()){ + if (settingResponseBean.getName().equals("label")){ + label= String.valueOf(settingResponseBean.getValue()); + } + if(settingResponseBean.getName().equals("formula")){ + String value= (String) settingResponseBean.getValue(); + formulaValueOpt=value; + formulaValue=Utils.extractValues(value); + } + } + } + } + Map mappedFormulaValue = new HashMap<>(); + Object fieldValue = applicationFormFieldRequestBean.getFieldValue(); + if (formulaValueOpt != null && fieldValue==null) { + fieldValue=0; + } + + for (ContentResponseBean contentResponseBean : contentResponseBeans) { + String contentId = contentResponseBean.getId(); + + // Extract variable values once per contentResponseBean to avoid repeated stream operations + Set variableValues = contentResponseBean.getSettings().stream() + .filter(setting -> "variable".equals(setting.getName())) + .flatMap(setting -> { + Object value = setting.getValue(); // Get the raw value + if (value instanceof String) { + return Stream.of((String) value); // Handle single String case + } else if (value instanceof List) { + return ((List) value).stream() + .filter(item -> item instanceof String) // Ensure it's a String + .map(item -> (String) item); // Convert to String + } else { + return Stream.empty(); // Ignore unexpected types + } + }) + .collect(Collectors.toSet()); // Collect into a Set for uniqueness + + for (String formula : formulaValue) { + if (variableValues.contains(formula)) { // O(1) lookup instead of O(n) + mappedFormulaValue.put(formula, contentId); + } + } + } + Map updatedMappedFormulaValue = new HashMap<>(); + + for (Map.Entry entry : mappedFormulaValue.entrySet()) { + String variable = entry.getKey(); + String contentId = entry.getValue(); + + // Repository call using contentId + Optional optionalEntity = applicationFormFieldRepository.findByApplicationFormIdAndFieldId(applicationFormEntity.getId(),contentId); + // If entity is found, extract fieldValue and fieldId + optionalEntity.ifPresent(entity -> { + String entityFieldValue = entity.getFieldValue(); // Assuming getter method exists + String fieldId = entity.getFieldId(); // Assuming getter method exists + String tableType = contentResponseBeans.stream() + .filter(content -> content.getId().equals(fieldId)) // Match Content ID with fieldId + .flatMap(content -> content.getSettings().stream()) // Extract settings + .filter(setting -> "criteria_table_columns".equals(setting.getName())) // Match name + .map(setting -> setting.getName()) // Return the name of the setting + .findFirst() // Get the first match + .orElse(null); // Default to null if no match + + if(tableType!=null){ + JSONObject jsonObject = new JSONObject(entityFieldValue); + + // Extract the value of total + entityFieldValue = jsonObject.getString("total"); + + } + + updatedMappedFormulaValue.put(fieldId, entityFieldValue); + }); + } + if(formulaValueOpt==null || formulaValueOpt.isEmpty()){ + return; + } + double finalValue = evaluateFormula(formulaValueOpt, mappedFormulaValue, updatedMappedFormulaValue); + + fieldValidator.formulaValidation(fieldValue, finalValue, label); + } + + + public static double evaluateFormula(String formula, Map mappedFormulaValue, Map updatedMappedFormulaValue) { + // Step 1: Extract all placeholders (variables) like {rest}, {another_var}, etc. + Pattern pattern = Pattern.compile("\\{(.*?)\\}"); + Matcher matcher = pattern.matcher(formula); + List variables = new ArrayList<>(); + + while (matcher.find()) { + variables.add(matcher.group(1)); // Extract variable names inside the curly braces + } + + // Step 2: Replace placeholders with corresponding fieldValue + Map variableValues = new HashMap<>(); + + for (String variable : variables) { + String fieldId = mappedFormulaValue.get(variable); + if (fieldId != null && updatedMappedFormulaValue.containsKey(fieldId)) { + String fieldValueStr = updatedMappedFormulaValue.get(fieldId); + try { + double fieldValue = Double.parseDouble(fieldValueStr); // Assuming fieldValue is numeric + variableValues.put(variable, fieldValue); + } catch (NumberFormatException e) { + // Handle invalid number format gracefully (e.g., log an error or default to 0) + variableValues.put(variable, 0.0); + } + } + } + + // Step 3: Replace variables in the formula with their corresponding values + String expression = formula; + for (String variable : variables) { + Double value = variableValues.get(variable); + if (value != null) { + // Replace {variable} with its corresponding value in the formula + expression = expression.replace("{" + variable + "}", String.valueOf(value)); + } + } + + // Step 4: Evaluate the mathematical expression + return Utils.evaluateExpression(expression); + } } diff --git a/src/main/java/net/gepafin/tendermanagement/util/FieldValidator.java b/src/main/java/net/gepafin/tendermanagement/util/FieldValidator.java index 9fb108ae..1d335735 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/FieldValidator.java +++ b/src/main/java/net/gepafin/tendermanagement/util/FieldValidator.java @@ -2,6 +2,10 @@ package net.gepafin.tendermanagement.util; import java.text.MessageFormat; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -9,12 +13,18 @@ import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.dao.FormDao; import net.gepafin.tendermanagement.dao.VatCheckDao; +import net.gepafin.tendermanagement.entities.ApplicationFormEntity; +import net.gepafin.tendermanagement.entities.ApplicationFormFieldEntity; +import net.gepafin.tendermanagement.model.request.ApplicationFormFieldRequestBean; import net.gepafin.tendermanagement.model.request.ContentRequestBean; import net.gepafin.tendermanagement.model.response.ContentResponseBean; import net.gepafin.tendermanagement.model.response.SettingResponseBean; +import net.gepafin.tendermanagement.repositories.ApplicationFormFieldRepository; +import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException; import net.gepafin.tendermanagement.web.rest.api.errors.Status; import net.gepafin.tendermanagement.web.rest.api.errors.ValidationException; import org.apache.commons.lang3.StringUtils; +import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; @@ -232,5 +242,17 @@ public class FieldValidator { // Now contentRequestBean is populated with the data from the JSON return contentRequestBean; } - + public FieldValidator formulaValidation(Object fieldValue, double finalValue, String label) { + if (fieldValue != null) { + try { + double fieldValueAsDouble = Double.parseDouble(fieldValue.toString()); // Convert fieldValue to double + if (Double.compare(finalValue, fieldValueAsDouble) != 0) { // Compare doubles safely + errors.add(MessageFormat.format(Translator.toLocale(GepafinConstant.FORMULA_AMOUNT_NOT_MATCHED), label)); + } + } catch (NumberFormatException e) { + throw new CustomValidationException(Status.BAD_REQUEST, "Invalid field value: " + fieldValue); + } + } + return this; + } } diff --git a/src/main/java/net/gepafin/tendermanagement/util/Utils.java b/src/main/java/net/gepafin/tendermanagement/util/Utils.java index 2f063173..68e8c8b0 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java @@ -11,6 +11,7 @@ import java.text.SimpleDateFormat; import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -27,6 +28,8 @@ import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.model.request.GlobalFilters; +import net.objecthunter.exp4j.Expression; +import net.objecthunter.exp4j.ExpressionBuilder; import org.apache.commons.collections4.MapUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,6 +57,9 @@ import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; import static org.apache.commons.lang3.StringUtils.isEmpty; @@ -753,6 +759,23 @@ public class Utils { private static Map defaultErrorResponse() { return Collections.singletonMap("message", Translator.toLocale(GepafinConstant.INVALID_VATNUMBER)); } + public static List extractValues(String input) { + List extractedValues = new ArrayList<>(); + Pattern pattern = Pattern.compile("\\{(.*?)\\}"); // Regex to match {value} + Matcher matcher = pattern.matcher(input); - + while (matcher.find()) { + extractedValues.add(matcher.group(1)); // Extract value inside {} + } + return extractedValues; + } + public static double evaluateExpression(String expression) { + try { + Expression exp = new ExpressionBuilder(expression).build(); + return exp.evaluate(); + } catch (Exception e) { + e.printStackTrace(); + return Double.NaN; // Return NaN if the expression is invalid + } + } } \ No newline at end of file diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index 6e058a9f..ab9b1d8c 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -367,5 +367,6 @@ either.applicationId.or.assignedApplicationId.must.be.provided=Either applicatio assigned.application.status.updated.successfully=Assigned application status updated successfully. validation.required.requested.amount=The Requested Amount configuration should be mandatory. +formula.amount.not.matches.requested.amount= The {0} does not matches to calculated amount. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index 624c7dfe..c7f83aed 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -358,6 +358,5 @@ either.applicationId.or.assignedApplicationId.must.be.provided = "� necessario assigned.application.status.updated.successfully=Stato dell'applicazione assegnata aggiornato con successo. validation.required.requested.amount=La configurazione dell'importo richiesto � obbligatoria. - - +formula.amount.not.matches.requested.amount=Il {0} non corrisponde all'importo calcolato.