package de.dhbw.humbuch.viewmodel;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import com.google.inject.Inject;
import de.davherrmann.mvvm.BasicState;
import de.davherrmann.mvvm.State;
import de.davherrmann.mvvm.annotations.AfterVMBinding;
import de.davherrmann.mvvm.annotations.ProvidesState;
import de.dhbw.humbuch.model.DAO;
import de.dhbw.humbuch.model.entity.BorrowedMaterial;
import de.dhbw.humbuch.model.entity.Dunning;
import de.dhbw.humbuch.model.entity.SchoolYear;
import de.dhbw.humbuch.model.entity.SchoolYear.Term;
import de.dhbw.humbuch.model.entity.Student;
import de.dhbw.humbuch.model.entity.TeachingMaterial;
/**
* Handles all actions for the {@link DunningView} and provides the necessary
* states.
*
* @author Martin Wentzel
* @author Johannes Idelhauser
*
*/
public class DunningViewModel {
public interface Dunnings extends State<Collection<Dunning>> {}
@ProvidesState(Dunnings.class)
public final State<Collection<Dunning>> dunnings = new BasicState<>(Collection.class);
private DAO<Dunning> daoDunning;
private DAO<SchoolYear> daoSchoolYear;
private DAO<Student> daoStudent;
private SchoolYear activeSchoolYear;
private Properties properties;
@Inject
public DunningViewModel(DAO<Dunning> daoDunning, DAO<BorrowedMaterial> daoBorrowedMaterial, DAO<SchoolYear> daoSchoolYear, DAO<Student> daoStudent, Properties properties) {
this.daoDunning = daoDunning;
this.daoSchoolYear = daoSchoolYear;
this.daoStudent = daoStudent;
this.properties = properties;
}
@AfterVMBinding
public void initialiseStates() {
dunnings.set(new ArrayList<Dunning>());
}
public void refresh() {
updateSchoolYear();
checkIfDunningCanBeClosed();
createSecondDunnings();
updateDunningsState();
}
/**
* Checks for every sent dunning if all contained borrowed materials have been returned.
* If all borrowed materials are returned, the dunning is closed.
*/
private void checkIfDunningCanBeClosed() {
List<Dunning> sentDunnings = daoDunning
.findAllWithCriteria(Restrictions.or(
Restrictions.eq("status", Dunning.Status.SENT),
Restrictions.eq("status", Dunning.Status.OPENED)));
//For every sent dunning
for (Dunning dunning : sentDunnings) {
Boolean toBeClosed = true;
//Check if all materials of the dunning have been returned
for(BorrowedMaterial material : dunning.getBorrowedMaterials()) {
if(material.getReturnDate() == null) {
toBeClosed = false;
break;
}
}
//If all materials of the dunning have been returned, close it
if(toBeClosed) {
dunning.setStatus(Dunning.Status.CLOSED);
daoDunning.update(dunning);
}
}
}
/**
* Creates the second dunning for overdue first dunnings. A first dunning is
* overdue, when the dunning is not resolved within a specific time frame.
* This function gets all first dunnings which are in an SENT state. Then it
* checks for each dunning whether it is overdue or not. If so, a second
* dunning is created and the first dunning is closed
*/
private void createSecondDunnings() {
int deadline = getDeadlineSecondDunning();
List<Dunning> sentFirstDunnings = daoDunning.findAllWithCriteria(Restrictions.and(
Restrictions.eq("status", Dunning.Status.SENT),
Restrictions.eq("type", Dunning.Type.TYPE1)));
for (Dunning dunning : sentFirstDunnings) {
Date dateStatusSent = dunning.getStatusDate(Dunning.Status.SENT);
//Check if the current date is after the specific sent-date of the dunning plus period
if (currentDateAfterPeriod(dateStatusSent, deadline)) {
//Create the new set of overdue materials
Set<BorrowedMaterial> overdueMaterials = new HashSet<BorrowedMaterial>();
for (BorrowedMaterial material : dunning.getBorrowedMaterials()) {
//If material of the dunning is still not returned
if (!material.isReturned()) {
overdueMaterials.add(material);
}
}
Dunning newDunning = new Dunning.Builder(
dunning.getStudent()).type(Dunning.Type.TYPE2)
.status(Dunning.Status.OPENED)
.borrowedMaterials(overdueMaterials)
.build();
daoDunning.insert(newDunning);
dunning.setStatus(Dunning.Status.CLOSED);
daoDunning.update(dunning);
}
}
}
private int getDeadlineSecondDunning() {
int deadline = 0;
String deadlineSecondDunning = properties.settings.get().get("dun_secondDunningDeadline");
if (deadlineSecondDunning != null) {
deadline = Integer.parseInt(deadlineSecondDunning);
}
return deadline;
}
private int getDeadlineFirstDunning() {
int deadline = 0;
String deadlineFirstDunning = properties.settings.get().get("dun_firstDunningDeadline");
if (deadlineFirstDunning != null) {
deadline = Integer.parseInt(deadlineFirstDunning);
}
return deadline;
}
/**
* Check if the current date is after the given date plus the given period
* of time
*
* @param date
* The original {@link Date} to check
* @param timePeriod
* Number of days to add to the date
* @return Whether the current date is after the deadline or not
*/
private boolean currentDateAfterPeriod(Date date, int timePeriod) {
Calendar currentDate = Calendar.getInstance();
Calendar originDate = Calendar.getInstance();
originDate.setTime(date);
originDate.add(Calendar.DATE, timePeriod);
if(currentDate.after(originDate)) {
return true;
} else {
return false;
}
}
/**
* Updates the dunnings state
*/
private void updateDunningsState() {
Collection<Dunning> openDunnings = createFirstDunnings();
Collection<Dunning> savedDunnings = daoDunning.findAll();
openDunnings.addAll(savedDunnings);
dunnings.set(openDunnings);
}
/**
* Creates temporary dunnings for overdue borrowed materials. The returned
* {@link Collection} of {@link Dunning}s is not saved to the database.
*
* @return All newly generated dunnings that are not yet sent
*/
private Collection<Dunning> createFirstDunnings() {
List<Student> allStudents = daoStudent.findAll();
Collection<Dunning> dunnings = new ArrayList<Dunning>();
int deadline = getDeadlineFirstDunning();
// Generate temporary key to be able to fill the vaadin table. If no
// temporary id is used, the vaadin table will cause problems because of
// the duplicate IDs.
int id = generateTemporaryId();
for (Student student : allStudents) {
List<BorrowedMaterial> borrowedMaterials = student.getBorrowedMaterials();
Set<BorrowedMaterial> overdueMaterials = new HashSet<BorrowedMaterial>();
if (!borrowedMaterials.isEmpty()) {
//For every borrowed material of the student
for (BorrowedMaterial material : borrowedMaterials) {
//If the material is received and not yet returned
if (material.isReceived() && material.getReturnDate() == null) {
Date dueDate = material.getBorrowUntil();
//If material is manually lended and the current date is after due date + deadline
if (dueDate != null && currentDateAfterPeriod(dueDate, deadline) && !dunningExistsWithMaterial(material)) {
//Add to the dunning
overdueMaterials.add(material);
} else if (dueDate == null) {
//If the materials isn't needed next term and there exists no dunning with the material
if (!isNeededNextTerm(material) && !dunningExistsWithMaterial(material)) {
//Is the deadline over?
Term toTerm = material.getTeachingMaterial().getToTerm();
if (currentDateAfterPeriod(activeSchoolYear.getEndOf(toTerm), deadline)) {
//Add to the dunning
overdueMaterials.add(material);
}
}
}
}
}
}
//If there are overdue materials, create a dunning
if (!overdueMaterials.isEmpty()) {
Dunning newDunning = new Dunning.Builder(student)
.type(Dunning.Type.TYPE1)
.status(Dunning.Status.OPENED)
.borrowedMaterials(overdueMaterials)
.id(id++)
.build();
dunnings.add(newDunning);
}
}
return dunnings;
}
/**
* Generates a temporary key for a dunning by extracting the maximum value
* of the id column from the dunning table in database.
*
* @return Key, that is one greater than the biggest value in the id column
*/
private int generateTemporaryId() {
Dunning a = daoDunning.findSingleWithCriteria(Order.desc("id"),
Restrictions.or(Restrictions.eq("type", Dunning.Type.TYPE1),
Restrictions.eq("type", Dunning.Type.TYPE2)));
int id;
if (a==null) {
id = 0;
} else {
id = a.getId();
}
return ++id;
}
/**
* Checks if there is a dunning in the database for the given {@link BorrowedMaterial}.
* @param material {@link BorrowedMaterial} to check for
* @return Whether there is a dunning for the material in the db or not
*/
private boolean dunningExistsWithMaterial(BorrowedMaterial material) {
List<Dunning> allDunnings = daoDunning.findAll();
for (Dunning dunning : allDunnings) {
if (dunning.getBorrowedMaterials().contains(material)) {
return true;
}
}
return false;
}
/**
* Checks whether a specific borrowed material is needed next term. Thereto
* the according teaching material with its grade and term information is
* retrieved.
*
* @param borrowedMaterial
* the borrowed material which is checked
* @return <code>true</code> if the borrowed materials is needed next term;
* <code>false</code> otherwise
*/
private boolean isNeededNextTerm(BorrowedMaterial borrowedMaterial) {
TeachingMaterial teachingMaterial = borrowedMaterial.getTeachingMaterial();
Integer toGrade = teachingMaterial.getToGrade();
int currentGrade = borrowedMaterial.getStudent().getGrade().getGrade();
Term toTerm = teachingMaterial.getToTerm();
Term currentTerm = activeSchoolYear.getRecentlyActiveTerm();
if (toGrade == null)
return false;
return (toGrade > currentGrade || (toGrade == currentGrade && (toTerm.compareTo(currentTerm) > 0)));
}
/**
* Updates the recently active {@link SchoolYear}
*/
private void updateSchoolYear() {
activeSchoolYear = daoSchoolYear.findSingleWithCriteria(
Order.desc("toDate"),
Restrictions.le("fromDate", new Date()));
if (activeSchoolYear == null) {
activeSchoolYear = new SchoolYear.Builder("now", getDate(
Calendar.AUGUST, 1), getDate(Calendar.JUNE, 31))
.endFirstTerm(getDate(Calendar.JANUARY, 31))
.beginSecondTerm(getDate(Calendar.FEBRUARY, 1)).build();
}
}
/**
* Returns {@link Date} object with the current year and the given month and
* day.
*
* @param month
* @param day
*
* @return {@link Date} object with given information
*/
private Date getDate(int month, int day) {
Calendar calendar = Calendar.getInstance();
calendar.set(calendar.get(Calendar.YEAR), month, day);
return calendar.getTime();
}
/**
* Updates a dunning by saving it to the database
*
* @param dunning the dunning to be updated
*/
public void doUpdateDunning(Dunning dunning) {
daoDunning.update(dunning);
updateDunningsState();
}
}