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)