From 08e94903ea8d785c672ddcf82e6f7df31b5854c8 Mon Sep 17 00:00:00 2001 From: SaipradeepR <53404427+SaipradeepR@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:13:12 +0530 Subject: [PATCH] 4.8.16 dev (#160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * KB-5718 | DEV|Assessment |BE| Capturing the starttime and endtime of the Batches while creating the batch for Assessment 1. Added a custom logic processStartEndDate that is exceuted if the starttime and entime provided from the ui request body is not null. * KB-5718 | DEV|Assessment |BE| Capturing the starttime and endtime of the Batches while creating the batch for Assessment 1. Fixed the time issue. * KB-5718 | DEV|Assessment |BE| Capturing the starttime and endtime of the Batches while creating the batch for Assessment 1. Added the time stamp to elastic search & the collection object while saving. * KB-5718 | DEV|Assessment |BE| Capturing the starttime and endtime of the Batches while creating the batch for Assessment 1.Add 5hrs30mins to the current time stamp, made it configurable based on flag. 2.Add values to string constants. * KB-5718 | DEV|Assessment |BE| Capturing the starttime and endtime of the Batches while creating the batch for Assessment. (#155) 1. Created a new variable primaryCategory to check if it is a standalone assessment. 2. Added a new validation to check if its a standalone assessment primary category. 3. So the new if case checks the entire timestamp from cassandra db . * Revert " KB-5718 | DEV|Assessment |BE| Capturing the starttime and endtime of…" (#157) This reverts commit 5171d6f6f9a050758fd06347b657703c1fba9a8b. * private api added for the enrolment list. (#159) * private api added for the mentoring competecies for the user * changes according to review comments * changes according to review comments * KB-5718 | DEV|Assessment |BE| Capturing the starttime and endtime of the Batches while creating the batch for Assessment. (#158) * KB-5718 | DEV|Assessment |BE| Capturing the starttime and endtime of the Batches while creating the batch for Assessment. 1.Created a new variable primaryCategory to check if it is a standalone assessment. 2. Added a new validation to check if its a standalone assessment primary category. 3. So the new if case checks the entire timestamp from cassandra db . * KB-5718 | DEV|Assessment |BE| Capturing the starttime and endtime of the Batches while creating the batch for Assessment. 1. Add a custom logic to check the enrollment date is future wrt current time stamp. * KB-5718 | DEV|Assessment |BE| Capturing the starttime and endtime of the Batches while creating the batch for Assessment. 1. Added the logic for admin enrolling of user for program api also. --------- Co-authored-by: tarentomaheshvakkund <139739142+tarentomaheshvakkund@users.noreply.github.com> Co-authored-by: shankaragoudab <140387294+shankaragoudab@users.noreply.github.com> --- .../dao/impl/CourseBatchDaoImpl.java | 64 +++++++++++++++++-- .../sunbird/learner/util/CourseBatchUtil.java | 4 ++ .../models/course/batch/CourseBatch.java | 18 ++++++ .../CourseBatchManagementActor.java | 2 + .../enrolments/CourseEnrolmentActor.scala | 56 +++++++++++++++- .../sunbird/common/models/util/JsonKey.java | 4 ++ .../resources/externalresource.properties | 1 + .../CourseEnrollmentController.java | 13 +++- service/app/util/RequestInterceptor.java | 1 + service/conf/routes | 1 + 10 files changed, 155 insertions(+), 9 deletions(-) diff --git a/course-mw/course-actors-common/src/main/java/org/sunbird/learner/actors/coursebatch/dao/impl/CourseBatchDaoImpl.java b/course-mw/course-actors-common/src/main/java/org/sunbird/learner/actors/coursebatch/dao/impl/CourseBatchDaoImpl.java index 11d416f23..b96920b5b 100644 --- a/course-mw/course-actors-common/src/main/java/org/sunbird/learner/actors/coursebatch/dao/impl/CourseBatchDaoImpl.java +++ b/course-mw/course-actors-common/src/main/java/org/sunbird/learner/actors/coursebatch/dao/impl/CourseBatchDaoImpl.java @@ -16,9 +16,12 @@ import org.sunbird.learner.util.Util; import org.sunbird.models.course.batch.CourseBatch; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import org.sunbird.common.models.util.ProjectUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class CourseBatchDaoImpl implements CourseBatchDao { private CassandraOperation cassandraOperation = ServiceFactory.getInstance(); @@ -27,13 +30,27 @@ public class CourseBatchDaoImpl implements CourseBatchDao { CassandraPropertyReader.getInstance(); private ObjectMapper mapper = new ObjectMapper(); private String dateFormat = "yyyy-MM-dd"; - + private static final Logger log = LoggerFactory.getLogger(CourseBatchDaoImpl.class); @Override public Response create(RequestContext requestContext, CourseBatch courseBatch) { Map map = CourseBatchUtil.cassandraCourseMapping(courseBatch, dateFormat); map = CassandraUtil.changeCassandraColumnMapping(map); CassandraUtil.convertMaptoJsonString(map, JsonKey.BATCH_ATTRIBUTES_KEY); + if(map.get(JsonKey.START_TIME) != null) { + String dateType=JsonKey.START_DATE_BATCH ; + String timeType=JsonKey.START_TIME; + processStartEndDate(map, timeType, dateType); + map.remove(JsonKey.START_TIME); + courseBatch.setStartDate((Date)map.get(dateType)); + } + if(map.get(JsonKey.END_TIME) != null) { + String dateType=JsonKey.END_DATE_BATCH; + String timeType=JsonKey.END_TIME; + processStartEndDate(map, timeType, dateType); + map.remove(JsonKey.END_TIME); + courseBatch.setEndDate((Date)map.get(dateType)); + } return cassandraOperation.insertRecord( requestContext, courseBatchDb.getKeySpace(), courseBatchDb.getTableName(), map); } @@ -150,4 +167,43 @@ public CourseBatch readFirstAvailableBatch(String courseId, RequestContext reque ResponseCode.CLIENT_ERROR.getResponseCode()); } } + + + /** + * This method processes the start and end date by merging the time part from the given map + * into the date part from the given map and setting it to the specified timezone. + * + * @param map The map containing the time and date information. + * @param timeType The key in the map for the time string. + * @param dateType The key in the map for the date object. + */ + private static void processStartEndDate(Map map, String timeType, String dateType) { + String timeStr = (String) map.get(timeType); + SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss"); + Date time; + try { + time = timeFormat.parse(timeStr); + } catch (ParseException e) { + log.error("Failed to parse time string: " + timeStr, e); + return; + } + Calendar calendar = Calendar.getInstance(); + calendar.setTime((Date) map.get(dateType)); + Calendar timeCalendar = Calendar.getInstance(); + timeCalendar.setTime(time); + String timeZone = ProjectUtil.getConfigValue(JsonKey.SUNBIRD_TIMEZONE); + TimeZone tz = TimeZone.getTimeZone(timeZone); + calendar.setTimeZone(tz); + log.info("Merging time part {} into date {} with timezone {}", timeStr, map.get(dateType), timeZone); + calendar.set(Calendar.HOUR_OF_DAY, timeCalendar.get(Calendar.HOUR_OF_DAY)); + calendar.set(Calendar.MINUTE, timeCalendar.get(Calendar.MINUTE)); + calendar.set(Calendar.SECOND, timeCalendar.get(Calendar.SECOND)); + if(ProjectUtil.getConfigValue(JsonKey.ADD_EXTRA_HOURS_MINS).equalsIgnoreCase("true")){ + calendar.add(Calendar.HOUR_OF_DAY, 5); + calendar.add(Calendar.MINUTE, 30); + log.info("Added 5hours 30mins to the start_date and end_date"); + } + map.put(dateType, calendar.getTime()); + log.info("Updated date in map with key {}: {}", dateType, calendar.getTime()); + } } diff --git a/course-mw/course-actors-common/src/main/java/org/sunbird/learner/util/CourseBatchUtil.java b/course-mw/course-actors-common/src/main/java/org/sunbird/learner/util/CourseBatchUtil.java index 1650a4b95..4c011aa44 100644 --- a/course-mw/course-actors-common/src/main/java/org/sunbird/learner/util/CourseBatchUtil.java +++ b/course-mw/course-actors-common/src/main/java/org/sunbird/learner/util/CourseBatchUtil.java @@ -185,6 +185,10 @@ public static Map esCourseMapping(CourseBatch courseBatch, Strin dateFormat.setTimeZone(TimeZone.getTimeZone(ProjectUtil.getConfigValue(JsonKey.SUNBIRD_TIMEZONE))); dateTimeFormat.setTimeZone(TimeZone.getTimeZone(ProjectUtil.getConfigValue(JsonKey.SUNBIRD_TIMEZONE))); Map esCourseMap = mapper.convertValue(courseBatch, Map.class); + if (courseBatch.getStartTime() != null && courseBatch.getEndTime() != null) { + esCourseMap.put(JsonKey.START_TIME, courseBatch.getStartDate()); + esCourseMap.put(JsonKey.END_TIME, courseBatch.getEndDate()); + } changeInDateFormat.forEach(key -> { if (null != esCourseMap.get(key)) esCourseMap.put(key, dateTimeFormat.format(esCourseMap.get(key))); diff --git a/course-mw/course-actors-common/src/main/java/org/sunbird/models/course/batch/CourseBatch.java b/course-mw/course-actors-common/src/main/java/org/sunbird/models/course/batch/CourseBatch.java index 035faec08..ce09ebcde 100644 --- a/course-mw/course-actors-common/src/main/java/org/sunbird/models/course/batch/CourseBatch.java +++ b/course-mw/course-actors-common/src/main/java/org/sunbird/models/course/batch/CourseBatch.java @@ -42,9 +42,27 @@ public class CourseBatch implements Serializable { private String hashTagId; private List mentors; private String name; + private String startTime; + private String endTime; private Integer status; + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + private Map certTemplates; private Map batchAttributes; diff --git a/course-mw/course-actors/src/main/java/org/sunbird/learner/actors/coursebatch/CourseBatchManagementActor.java b/course-mw/course-actors/src/main/java/org/sunbird/learner/actors/coursebatch/CourseBatchManagementActor.java index 61843a97c..44db9e1de 100644 --- a/course-mw/course-actors/src/main/java/org/sunbird/learner/actors/coursebatch/CourseBatchManagementActor.java +++ b/course-mw/course-actors/src/main/java/org/sunbird/learner/actors/coursebatch/CourseBatchManagementActor.java @@ -722,6 +722,8 @@ private void updateCollection(RequestContext requestContext, Map data.put("createdFor", courseBatch.getOrDefault(JsonKey.COURSE_CREATED_FOR, new ArrayList<>())); data.put("startDate", courseBatch.getOrDefault(JsonKey.START_DATE, "")); data.put("endDate", courseBatch.getOrDefault(JsonKey.END_DATE, null)); + data.put("startTime", courseBatch.getOrDefault(JsonKey.START_TIME, null)); + data.put("endTime", courseBatch.getOrDefault(JsonKey.END_TIME, null)); data.put("enrollmentType", courseBatch.getOrDefault(JsonKey.ENROLLMENT_TYPE, "")); data.put("status", courseBatch.getOrDefault(JsonKey.STATUS, "")); data.put("batchAttributes", courseBatch.getOrDefault(CourseJsonKey.BATCH_ATTRIBUTES, new HashMap())); diff --git a/course-mw/enrolment-actor/src/main/scala/org/sunbird/enrolments/CourseEnrolmentActor.scala b/course-mw/enrolment-actor/src/main/scala/org/sunbird/enrolments/CourseEnrolmentActor.scala index 761186532..f040f3a0c 100644 --- a/course-mw/enrolment-actor/src/main/scala/org/sunbird/enrolments/CourseEnrolmentActor.scala +++ b/course-mw/enrolment-actor/src/main/scala/org/sunbird/enrolments/CourseEnrolmentActor.scala @@ -5,7 +5,7 @@ import java.text.{MessageFormat, SimpleDateFormat} import java.time.format.DateTimeFormatter import java.time.{LocalDate, LocalDateTime, LocalTime, Month, ZoneId} import java.util -import java.util.{Collections, Comparator, Date, UUID} +import java.util.{Calendar, Collections, Comparator, Date, TimeZone, UUID} import akka.actor.ActorRef import com.fasterxml.jackson.databind.ObjectMapper import org.sunbird.common.models.util.JsonKey @@ -579,6 +579,12 @@ class CourseEnrolmentActor @Inject()(@Named("course-batch-notification-actor") c } } val batchUserData: BatchUser = batchUserDao.read(request.getRequestContext, batchId, userId) + val primaryCategory=contentData.get(JsonKey.PRIMARYCATEGORY).asInstanceOf[String] + if(primaryCategory.equalsIgnoreCase(JsonKey.STANDALONE_ASSESSMENT)) { + validateEnrolmentV2(batchData, enrolmentData, true,primaryCategory) + }else{ + validateEnrolment(batchData, enrolmentData, true) + } validateEnrolment(batchData, enrolmentData, true) getCoursesForProgramAndEnrol(request, programId, userId, batchId) val dataBatch: util.Map[String, AnyRef] = createBatchUserMapping(batchId, userId, batchUserData) @@ -785,7 +791,12 @@ class CourseEnrolmentActor @Inject()(@Named("course-batch-notification-actor") c } } val batchUserData: BatchUser = batchUserDao.read(request.getRequestContext, batchId, userId) - validateEnrolment(batchData, enrolmentData, true) + val primaryCategory=contentData.get(JsonKey.PRIMARYCATEGORY).asInstanceOf[String] + if(primaryCategory.equalsIgnoreCase(JsonKey.STANDALONE_ASSESSMENT)) { + validateEnrolmentV2(batchData, enrolmentData, true,primaryCategory) + }else{ + validateEnrolment(batchData, enrolmentData, true) + } getCoursesForProgramAndEnrol(request, programId, userId, batchId) val dataBatch: util.Map[String, AnyRef] = createBatchUserMapping(batchId, userId, batchUserData) val data: java.util.Map[String, AnyRef] = createUserEnrolmentMap(userId, programId, batchId, enrolmentData, request.getContext.getOrDefault(JsonKey.REQUEST_ID, "").asInstanceOf[String]) @@ -817,6 +828,47 @@ class CourseEnrolmentActor @Inject()(@Named("course-batch-notification-actor") c } sender().tell(resp, self) } + + /** + * Validates enrolment based on various conditions. + * + * @param batchData CourseBatch object containing batch details. + * @param enrolmentData UserCourses object containing user's enrolment details. + * @param isEnrol Boolean indicating whether user is attempting to enrol or not. + * @param primaryCategory Primary category of the course. + */ + def validateEnrolmentV2(batchData: CourseBatch, enrolmentData: UserCourses, isEnrol: Boolean,primaryCategory: String): Unit = { + if(null == batchData) + ProjectCommonException.throwClientErrorException(ResponseCode.invalidCourseBatchId, ResponseCode.invalidCourseBatchId.getErrorMessage) + + if(!(EnrolmentType.inviteOnly.getVal.equalsIgnoreCase(batchData.getEnrollmentType) || + EnrolmentType.open.getVal.equalsIgnoreCase(batchData.getEnrollmentType))) + ProjectCommonException.throwClientErrorException(ResponseCode.enrollmentTypeValidation, ResponseCode.enrollmentTypeValidation.getErrorMessage) + + if((2 == batchData.getStatus) || (null != batchData.getEndDate && LocalDateTime.now().isAfter(LocalDate.parse(DATE_FORMAT.format(batchData.getEndDate), DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MAX)))) + ProjectCommonException.throwClientErrorException(ResponseCode.courseBatchAlreadyCompleted, ResponseCode.courseBatchAlreadyCompleted.getErrorMessage) + + if(primaryCategory.equalsIgnoreCase(JsonKey.STANDALONE_ASSESSMENT) && isEnrol && null != batchData.getEnrollmentEndDate && + isFutureDate(batchData.getEnrollmentEndDate)) + ProjectCommonException.throwClientErrorException(ResponseCode.courseBatchEnrollmentDateEnded, ResponseCode.courseBatchEnrollmentDateEnded.getErrorMessage) + + if(isEnrol && null != enrolmentData && enrolmentData.isActive) ProjectCommonException.throwClientErrorException(ResponseCode.userAlreadyEnrolledCourse, ResponseCode.userAlreadyEnrolledCourse.getErrorMessage) + if(!isEnrol && (null == enrolmentData || !enrolmentData.isActive)) ProjectCommonException.throwClientErrorException(ResponseCode.userNotEnrolledCourse, ResponseCode.userNotEnrolledCourse.getErrorMessage) + if(!isEnrol && ProjectUtil.ProgressStatus.COMPLETED.getValue == enrolmentData.getStatus) ProjectCommonException.throwClientErrorException(ResponseCode.courseBatchAlreadyCompleted, ResponseCode.courseBatchAlreadyCompleted.getErrorMessage) + } + + /** + * Checks if the given enrollment date is a future date compared to the current date/time. + * + * @param enrollmentEndDate The date to be checked for being in the future. + * @return `true` if the enrollment end date is in the future; `false` otherwise. + */ + def isFutureDate(enrollmentEndDate: Date): Boolean = { + val inputCal = Calendar.getInstance(TimeZone.getTimeZone(ProjectUtil.getConfigValue(JsonKey.SUNBIRD_TIMEZONE))); + inputCal.setTime(enrollmentEndDate) + val currentCal = Calendar.getInstance(TimeZone.getTimeZone(ProjectUtil.getConfigValue(JsonKey.SUNBIRD_TIMEZONE))); + currentCal.after(inputCal) + } } diff --git a/course-mw/sunbird-util/sunbird-platform-core/common-util/src/main/java/org/sunbird/common/models/util/JsonKey.java b/course-mw/sunbird-util/sunbird-platform-core/common-util/src/main/java/org/sunbird/common/models/util/JsonKey.java index 41f51b2ed..1c9221c1a 100644 --- a/course-mw/sunbird-util/sunbird-platform-core/common-util/src/main/java/org/sunbird/common/models/util/JsonKey.java +++ b/course-mw/sunbird-util/sunbird-platform-core/common-util/src/main/java/org/sunbird/common/models/util/JsonKey.java @@ -1139,5 +1139,9 @@ public final class JsonKey { public static final String LRC_PROGRESS_DETAILS = "lrcProgressDetails"; public static final String USERS_COUNT = "system.count(userid)"; public static final String COURSE_ENROLL_ALLOWED_PRIMARY_CATEGORY = "course_enroll_allowed_primary_category"; + public static final String START_DATE_BATCH = "start_date"; + public static final String END_DATE_BATCH = "end_date"; + public static final String ADD_EXTRA_HOURS_MINS = "addExtraHrsAndMins.start_date_end_date"; + public static final String STANDALONE_ASSESSMENT ="Standalone Assessment"; private JsonKey() {} } diff --git a/course-mw/sunbird-util/sunbird-platform-core/common-util/src/main/resources/externalresource.properties b/course-mw/sunbird-util/sunbird-platform-core/common-util/src/main/resources/externalresource.properties index 8d8ceeb7b..bd832b787 100644 --- a/course-mw/sunbird-util/sunbird-platform-core/common-util/src/main/resources/externalresource.properties +++ b/course-mw/sunbird-util/sunbird-platform-core/common-util/src/main/resources/externalresource.properties @@ -224,3 +224,4 @@ content_bucket=/content-store/content static_host_url=https://static.karmayogiprod.nic.in profile_update_url=/app/user-profile/details course_enroll_allowed_primary_category=Course,Blended Program,Standalone Assessment +addExtraHrsAndMins.start_date_end_date=true diff --git a/service/app/controllers/courseenrollment/CourseEnrollmentController.java b/service/app/controllers/courseenrollment/CourseEnrollmentController.java index 5d8b8842e..f2e27a7e9 100644 --- a/service/app/controllers/courseenrollment/CourseEnrollmentController.java +++ b/service/app/controllers/courseenrollment/CourseEnrollmentController.java @@ -30,7 +30,7 @@ public class CourseEnrollmentController extends BaseController { private CourseEnrollmentRequestValidator validator = new CourseEnrollmentRequestValidator(); - public CompletionStage getEnrolledCourses(String uid, Http.Request httpRequest,String version) { + public CompletionStage getEnrolledCourses(String uid, Http.Request httpRequest,String version,boolean isPrivate) { return handleRequest(courseEnrolmentActor, "listEnrol", httpRequest.body().asJson(), (req) -> { @@ -42,6 +42,9 @@ public CompletionStage getEnrolledCourses(String uid, Http.Request httpR queryParams.put("fields", fields.toArray(new String[0])); } String userId = (String) request.getContext().getOrDefault(JsonKey.REQUESTED_FOR, request.getContext().get(JsonKey.REQUESTED_BY)); + if(isPrivate){ + userId = uid; + } validator.validateRequestedBy(userId); request.getContext().put(JsonKey.USER_ID, userId); request.getRequest().put(JsonKey.USER_ID, userId); @@ -264,10 +267,14 @@ public CompletionStage adminGetUserEnrolledCourses(Http.Request httpRequ } public CompletionStage getEnrolledCourses_v1(String uid, Http.Request httpRequest) { - return getEnrolledCourses(uid,httpRequest,"v1"); + return getEnrolledCourses(uid,httpRequest,"v1", false); } public CompletionStage getEnrolledCourses_v2(String uid, Http.Request httpRequest) { - return getEnrolledCourses(uid, httpRequest, "v2"); + return getEnrolledCourses(uid, httpRequest, "v2", false ); + } + + public CompletionStage privateGetUserEnrolledCourses_v3(String uid, Http.Request httpRequest) { + return getEnrolledCourses(uid, httpRequest, "v2", true); } public CompletionStage enrollProgram(Http.Request httpRequest, Boolean batchType) { diff --git a/service/app/util/RequestInterceptor.java b/service/app/util/RequestInterceptor.java index eb807d6a2..7f1b221ae 100644 --- a/service/app/util/RequestInterceptor.java +++ b/service/app/util/RequestInterceptor.java @@ -46,6 +46,7 @@ private RequestInterceptor() {} apiHeaderIgnoreMap.put("/v1/course/admin/enroll", var); apiHeaderIgnoreMap.put("/v1/course/admin/unenroll", var); apiHeaderIgnoreMap.put("/v1/activate-started/course/batches/status", var); + apiHeaderIgnoreMap.put("/private/v3/user/courses/list/:uid", var); } /** diff --git a/service/conf/routes b/service/conf/routes index 061727db3..6e7f5f505 100644 --- a/service/conf/routes +++ b/service/conf/routes @@ -15,6 +15,7 @@ DELETE /v1/cache/clear/:mapName @controllers.cache.CacheController.clearCache(ma # Course Management APIs GET /v1/user/courses/list/:uid @controllers.courseenrollment.CourseEnrollmentController.getEnrolledCourses_v1(uid:String, request: play.mvc.Http.Request) GET /v2/user/courses/list/:uid @controllers.courseenrollment.CourseEnrollmentController.getEnrolledCourses_v2(uid:String, request: play.mvc.Http.Request) +GET /private/v3/user/courses/list/:uid @controllers.courseenrollment.CourseEnrollmentController.privateGetUserEnrolledCourses_v3(uid:String, request: play.mvc.Http.Request) GET /private/v1/user/courses/list/:uid @controllers.courseenrollment.CourseEnrollmentController.privateGetEnrolledCourses(uid:String, request: play.mvc.Http.Request) POST /v2/user/courses/list @controllers.courseenrollment.CourseEnrollmentController.getUserEnrolledCourses(request: play.mvc.Http.Request) POST /v2/user/courses/admin/list @controllers.courseenrollment.CourseEnrollmentController.adminGetUserEnrolledCourses(request: play.mvc.Http.Request)