package crmdna.program;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.cmd.Query;
import crmdna.client.Client;
import crmdna.common.DateUtils;
import crmdna.common.Utils;
import crmdna.common.Utils.Currency;
import crmdna.common.api.APIException;
import crmdna.common.api.APIResponse.Status;
import crmdna.group.Group;
import crmdna.programtype.ProgramType;
import crmdna.registration.Registration;
import crmdna.registration.RegistrationSummaryProp;
import crmdna.sequence.Sequence;
import crmdna.sequence.Sequence.SequenceType;
import crmdna.teacher.Teacher;
import crmdna.user.User;
import crmdna.user.User.GroupLevelPrivilege;
import crmdna.user.UserCore;
import crmdna.user.UserHelper;
import crmdna.venue.Venue;
import java.util.*;
import static crmdna.common.AssertUtils.ensure;
import static crmdna.common.OfyService.ofy;
public class Program {
public static ProgramProp create(String client, long groupId, long programTypeId, long venueId,
long teacherId, int startYYYYMMDD, int endYYYYMMDD, int numBatches, String description,
double fees, Currency ccy, String login) {
Client.ensureValid(client);
User.ensureGroupLevelPrivilege(client, groupId, login, GroupLevelPrivilege.UPDATE_PROGRAM);
Group.safeGet(client, groupId);
ProgramType.safeGet(client, programTypeId);
Venue.safeGet(client, venueId);
Teacher.safeGet(client, teacherId);
ProgramEntity programEntity = new ProgramEntity();
programEntity.programTypeId = programTypeId;
programEntity.venueId = venueId;
programEntity.groupId = groupId;
programEntity.teacherId = teacherId;
programEntity.startYYYYMMDD = startYYYYMMDD;
programEntity.endYYYYMMDD = endYYYYMMDD;
programEntity.numBatches = numBatches;
programEntity.description = description;
programEntity.fee = fees;
programEntity.ccy = ccy;
ensureValid(programEntity);
ensureNotPresentInDB(client, programEntity);
safeAddToMemcache(client, programEntity);
programEntity.programId = Sequence.getNext(client, SequenceType.PROGRAM);
ofy(client).save().entity(programEntity).now();
return programEntity.toProp(client);
}
public static ProgramProp setSpecialInstruction(final String client, final long programId,
final String specialInstruction) {
Client.ensureValid(client);
ProgramEntity programEntity = safeGet(client, programId);
// can specify null to preserve the existing special instruction
if (null == specialInstruction)
return programEntity.toProp(client);
programEntity.specialInstruction = specialInstruction;
// Note: there is a minute change of race condition when saving
ofy(client).save().entity(programEntity).now();
return programEntity.toProp(client);
}
public static ProgramProp setSessionTimings(final String client, long programId,
List<String> batch1SessionTimings, List<String> batch2SessionTimings,
List<String> batch3SessionTimings, List<String> batch4SessionTimings,
List<String> batch5SessionTimings) {
Client.ensureValid(client);
// Note: There is a tiny chance of race condition when multiple requests
// update
// program entity in the same time though it is likely to be extremely
// rare as program entities
// are not updated frequently. Currently we are unable to wrap this in
// an objectify transaction
// as loading program entity touches multiple entities and we hit the
// cross group transaction entity limit
ProgramEntity programEntity = safeGet(client, programId);
if (programEntity.numBatches == 1) {
batch2SessionTimings = null;
batch3SessionTimings = null;
batch4SessionTimings = null;
batch5SessionTimings = null;
} else if (programEntity.numBatches == 2) {
batch3SessionTimings = null;
batch4SessionTimings = null;
batch5SessionTimings = null;
} else if (programEntity.numBatches == 3) {
batch4SessionTimings = null;
batch5SessionTimings = null;
} else if (programEntity.numBatches == 4) {
batch5SessionTimings = null;
}
boolean changed = false;
// can specify null to preserve existing value
if ((batch1SessionTimings != null) && !batch1SessionTimings.isEmpty()) {
programEntity.batch1SessionTimings = batch1SessionTimings;
changed = true;
}
if ((batch2SessionTimings != null) && !batch2SessionTimings.isEmpty()) {
programEntity.batch2SessionTimings = batch2SessionTimings;
changed = true;
}
if ((batch3SessionTimings != null) && !batch3SessionTimings.isEmpty()) {
programEntity.batch3SessionTimings = batch3SessionTimings;
changed = true;
}
if ((batch4SessionTimings != null) && !batch4SessionTimings.isEmpty()) {
programEntity.batch4SessionTimings = batch4SessionTimings;
changed = true;
}
if ((batch5SessionTimings != null) && batch5SessionTimings.isEmpty()) {
programEntity.batch5SessionTimings = batch5SessionTimings;
changed = true;
}
if (!changed)
return programEntity.toProp(client);
ofy(client).save().entity(programEntity).now();
return programEntity.toProp(client);
}
public static ProgramProp setMaxParticipants(final String client, long programId,
Integer maxParticipants) {
Client.ensureValid(client);
ProgramEntity programEntity = safeGet(client, programId);
if (maxParticipants != null) {
programEntity.maxParticipants = maxParticipants;
ofy(client).save().entity(programEntity).now();
}
return programEntity.toProp(client);
}
public static ProgramProp setDisabled(final String client, long programId, Boolean disabled) {
Client.ensureValid(client);
ProgramEntity programEntity = safeGet(client, programId);
if (disabled != null) {
programEntity.disabled = disabled;
ofy(client).save().entity(programEntity).now();
}
return programEntity.toProp(client);
}
public static Map<Long, ProgramProp> getProps(String client, Iterable<Long> programIds) {
Client.ensureValid(client);
Map<Long, ProgramEntity> programEntities = getEntities(client, programIds);
Map<Long, ProgramProp> programProps = new HashMap<>();
for (Long programId : programEntities.keySet()) {
ProgramEntity programEntity = programEntities.get(programId);
if (programEntity != null)
programProps.put(programId, programEntity.toProp(client));
}
return programProps;
}
public static Map<Long, ProgramEntity> getEntities(String client, Iterable<Long> programIds) {
Client.ensureValid(client);
Map<Long, ProgramEntity> map = ofy(client).load().type(ProgramEntity.class).ids(programIds);
return map;
}
public static ProgramProp update(String client, long programId, Long newVenueId,
Long newTeacherId, Integer newStartYYYYMMDD, Integer newEndYYYYMMDD, Integer newNumBatches,
String newDescription, Double fees, Currency ccy, String login) {
// warning: this method will modify session cache if an exception is
// thrown
Client.ensureValid(client);
ProgramEntity programEntity = safeGet(client, programId);
ProgramProp programProp = programEntity.toProp(client);
User.ensureGroupLevelPrivilege(client, programProp.groupProp.groupId, login,
GroupLevelPrivilege.UPDATE_PROGRAM);
boolean checkUnique = false;
if (null != newVenueId) {
if (newVenueId != programProp.venueProp.venueId) {
Venue.safeGet(client, newVenueId);
programEntity.venueId = newVenueId;
checkUnique = true;
}
}
if (null != newTeacherId) {
if (newTeacherId != programProp.teacherProp.teacherId) {
Teacher.safeGet(client, newTeacherId);
programEntity.teacherId = newTeacherId;
}
}
if (null != newStartYYYYMMDD) {
if (newStartYYYYMMDD != programProp.startYYYYMMDD) {
DateUtils.ensureFormatYYYYMMDD(newStartYYYYMMDD);
programEntity.startYYYYMMDD = newStartYYYYMMDD;
checkUnique = true;
}
}
if (null != newEndYYYYMMDD) {
if (newEndYYYYMMDD != programProp.endYYYYMMDD) {
DateUtils.ensureFormatYYYYMMDD(newEndYYYYMMDD);
programEntity.endYYYYMMDD = newEndYYYYMMDD;
checkUnique = true;
}
}
if (null != newNumBatches) {
programEntity.numBatches = newNumBatches;
}
if (null != newDescription)
programEntity.description = newDescription;
if (null != fees)
programEntity.fee = fees;
if (null != ccy)
programEntity.ccy = ccy;
ensureValid(programEntity);
if (checkUnique) {
ensureNotPresentInDB(client, programEntity);
safeAddToMemcache(client, programEntity);
}
ofy(client).save().entity(programEntity).now();
return programEntity.toProp(client);
}
private static void ensureValid(ProgramEntity programEntity) {
DateUtils.ensureFormatYYYYMMDD(programEntity.startYYYYMMDD);
DateUtils.ensureFormatYYYYMMDD(programEntity.endYYYYMMDD);
if (programEntity.startYYYYMMDD > programEntity.endYYYYMMDD)
throw new APIException().status(Status.ERROR_RESOURCE_INCORRECT).message(
"Start date [" + programEntity.startYYYYMMDD + "] is greater than end date ["
+ programEntity.endYYYYMMDD + "]");
if (programEntity.numBatches < 1)
throw new APIException().status(Status.ERROR_RESOURCE_INCORRECT).message(
"Invalid number of batches [" + programEntity.numBatches
+ "]. numBatches should be positive");
if ((programEntity.programTypeId == 0) || (programEntity.venueId == 0)
|| (programEntity.teacherId == 0) || (programEntity.groupId == 0))
throw new APIException().status(Status.ERROR_RESOURCE_INCORRECT).message(
"Either program type or venue or teacher or group not populated");
Utils.ensureNonNegative(programEntity.fee);
if (programEntity.fee != 0)
if (programEntity.ccy == null)
Utils.throwIncorrectSpecException("Ccy should be specified");
}
public static ProgramEntity safeGet(String client, long programId) {
Client.ensureValid(client);
ProgramEntity entity = ofy(client).load().type(ProgramEntity.class).id(programId).now();
if (null == entity)
throw new APIException().status(Status.ERROR_RESOURCE_NOT_FOUND).message(
"There is no program with id [" + programId + "]");
return entity;
}
public static ProgramEntity get(String client, long programId) {
Client.ensureValid(client);
ProgramEntity entity = ofy(client).load().type(ProgramEntity.class).id(programId).now();
return entity;
}
public static List<ProgramProp> query(String client, Integer startYYYYMMDD, Integer endYYYYMMDD,
Set<Long> programTypeIds, Set<Long> groupIds, Long venueId, Integer maxResultSize) {
Client.ensureValid(client);
Query<ProgramEntity> q = ofy(client).load().type(ProgramEntity.class);
if (null != startYYYYMMDD)
DateUtils.ensureFormatYYYYMMDD(startYYYYMMDD);
if (null != endYYYYMMDD)
DateUtils.ensureFormatYYYYMMDD(endYYYYMMDD);
if ((null != programTypeIds) && (programTypeIds.size() != 0)) {
q = q.filter("programTypeId in", programTypeIds);
}
if ((null != groupIds) && (groupIds.size() != 0)) {
q = q.filter("groupId in", groupIds);
}
if (null != venueId) {
Venue.safeGet(client, venueId);
q = q.filter("venueId", venueId);
}
// don't limit the query as this will mess up the sort order.
// get all results, sort and then limit the data that is returned
List<ProgramEntity> entities = q.list();
List<ProgramProp> props = new ArrayList<>();
for (ProgramEntity entity : entities) {
// discard entities that are after end date (if specified)
if ((null != endYYYYMMDD) && (entity.endYYYYMMDD > endYYYYMMDD))
continue;
if ((null != startYYYYMMDD) && (entity.startYYYYMMDD < startYYYYMMDD))
continue;
RegistrationSummaryProp registrationSummaryProp =
Registration.getSummary(client, entity.programId, User.SUPER_USER);
ProgramProp programProp = entity.toProp(client);
programProp.isRegistrationLimitReached =
(registrationSummaryProp.numCompleted >= entity.maxParticipants);
props.add(programProp);
}
Collections.sort(props);
if ((maxResultSize != null) && (props.size() > maxResultSize))
props = props.subList(0, maxResultSize);
return props;
}
static List<ProgramProp> getOngoingPrograms(String client, int dateYYYYMMDD, String login) {
Client.ensureValid(client);
User.ensureValidUser(client, login);
DateUtils.ensureFormatYYYYMMDD(dateYYYYMMDD);
Set<Long> groupIds =
UserHelper.getGroupIdsWithPrivilage(client, login, GroupLevelPrivilege.CHECK_IN);
// remove groupId 0 as it is not a valid group
groupIds.remove((long) 0);
// if user does not have write access to any group just return
if (0 == groupIds.size())
return new ArrayList<>();
List<ProgramProp> programProps = query(client, null, null, null, groupIds, null, null);
List<ProgramProp> ongoing = new ArrayList<>();
for (ProgramProp programProp : programProps) {
if ((dateYYYYMMDD >= programProp.startYYYYMMDD) && (dateYYYYMMDD <= programProp.endYYYYMMDD))
ongoing.add(programProp);
}
return ongoing;
}
public static List<SessionProp> getOngoingSessions(String client, int dateYYYYMMDD, String login) {
List<ProgramProp> programProps = getOngoingPrograms(client, dateYYYYMMDD, login);
List<SessionProp> sessionProps = new ArrayList<>();
for (ProgramProp programProp : programProps) {
sessionProps.addAll(programProp.getSessions(dateYYYYMMDD));
}
return sessionProps;
}
private static String getUniqueKey(String namespace, ProgramEntity programEntity) {
return namespace + "_" + programEntity.programTypeId + "_"
+ programEntity.venueId + "_" + programEntity.startYYYYMMDD + "_"
+ programEntity.endYYYYMMDD;
}
private static void ensureNotPresentInDB(String namespace, ProgramEntity programEntity) {
List<Key<ProgramEntity>> keys =
ofy(namespace).load().type(ProgramEntity.class)
.filter("programTypeId", programEntity.programTypeId)
.filter("venueId", programEntity.venueId)
.filter("startYYYYMMDD", programEntity.startYYYYMMDD)
.filter("endYYYYMMDD", programEntity.endYYYYMMDD).keys().list();
if (keys.size() != 0)
throw new APIException()
.status(Status.ERROR_RESOURCE_ALREADY_EXISTS)
.message(
"There is already a program with the same start date, end date, venue, program type and batch.");
}
private static void safeAddToMemcache(String namespace, ProgramEntity programEntity) {
String key = getUniqueKey(namespace, programEntity);
long val = MemcacheServiceFactory.getMemcacheService().increment(key, 1, (long) 0);
if (val != 1)
throw new APIException()
.status(Status.ERROR_RESOURCE_ALREADY_EXISTS)
.message(
"There is already a program with the same start date, end date, venue, program type and batch. ");
}
// this should be removed after bhairavi data migration is complete
public static void resaveAll(String client, String login) {
Client.ensureValid(client);
ensure(UserCore.isSuperUser(login), "Allowed only for super user");
List<ProgramEntity> all = ofy(client).load().type(ProgramEntity.class).list();
ofy(client).save().entities(all).now();
}
}