diff --git a/.gitignore b/.gitignore
index 694db65d9869500b8ada53accd417f9a8d60ef11..e882e145fcec335b65eaeade223514c9ce1824a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,18 @@
 # Disable virtualenv
 env/*
 
+# Disable migration files
+smash/web/migrations/*
+
+# Folder with db statics (dev mode)
+smash/~/
+
 # Disable python bytecode
 *.pyc
 
 # Disable local developer settings
 local_settings.py
+
+#tmp files
+appointment-import/testFiles/~*
+appointment-import/tmp.sql
diff --git a/appointment-import/.classpath b/appointment-import/.classpath
new file mode 100644
index 0000000000000000000000000000000000000000..f7b62f180179a74fbb8400870e0e56b071065daa
--- /dev/null
+++ b/appointment-import/.classpath
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/appointment-import/.gitignore b/appointment-import/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..b83d22266ac8aa2f8df2edef68082c789727841d
--- /dev/null
+++ b/appointment-import/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/appointment-import/.project b/appointment-import/.project
new file mode 100644
index 0000000000000000000000000000000000000000..eee5e1c7df61a5e6865e9a8584bb8c4c4becfe6a
--- /dev/null
+++ b/appointment-import/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>appointment-import</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+	</natures>
+</projectDescription>
diff --git a/appointment-import/.settings/org.eclipse.core.resources.prefs b/appointment-import/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000000000000000000000000000000000000..04cfa2c1a8566d64dd12fcf7a8e895fe02e1856f
--- /dev/null
+++ b/appointment-import/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,6 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding//src/test/java=UTF-8
+encoding//src/test/resources=UTF-8
+encoding/<project>=UTF-8
diff --git a/appointment-import/.settings/org.eclipse.jdt.core.prefs b/appointment-import/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000000000000000000000000000000000000..bbcbc93486d53c73668b275ea5daf41b66ba9c41
--- /dev/null
+++ b/appointment-import/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/appointment-import/.settings/org.eclipse.m2e.core.prefs b/appointment-import/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000000000000000000000000000000000000..14b697b7bbb0d85e8d8ee19141a2a92d9ce211be
--- /dev/null
+++ b/appointment-import/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/appointment-import/pom.xml b/appointment-import/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a2244ee9c27fc393f0190108de420b7cb272a67c
--- /dev/null
+++ b/appointment-import/pom.xml
@@ -0,0 +1,72 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>smash</groupId>
+  <artifactId>appointment-import</artifactId>
+  <version>0.0.1-SNAPSHOT</version>
+  <packaging>jar</packaging>
+
+  <name>appointment-import</name>
+  <url>http://maven.apache.org</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+<dependency>
+    <groupId>log4j</groupId>
+    <artifactId>log4j</artifactId>
+    <version>1.2.17</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.poi</groupId>
+    <artifactId>poi-ooxml</artifactId>
+    <version>3.15</version>
+</dependency>
+<dependency>
+    <groupId>commons-cli</groupId>
+    <artifactId>commons-cli</artifactId>
+    <version>1.3.1</version>
+</dependency>    
+  </dependencies>
+  
+	<build>
+		<plugins>
+<plugin>
+    <groupId>org.apache.maven.plugins</groupId>
+    <artifactId>maven-compiler-plugin</artifactId>
+    <version>3.5.1</version>
+    <configuration>
+        <source>1.7</source>
+        <target>1.7</target>
+    </configuration>
+</plugin> 		
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-assembly-plugin</artifactId>
+				<configuration>
+        	<source>1.7</source>
+        	<target>1.7</target>
+        	<archive>
+						<manifest>
+             <addClasspath>true</addClasspath>
+          	 <mainClass>smash.appointment.parse.Main</mainClass>
+						</manifest>
+					</archive>
+					<descriptorRefs>
+          <descriptorRef>jar-with-dependencies</descriptorRef>
+        </descriptorRefs>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+	  
+</project>
diff --git a/appointment-import/src/main/java/smash/appointment/parse/AppointmentDao.java b/appointment-import/src/main/java/smash/appointment/parse/AppointmentDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa9a57f9a3fc80456e4bfa307da2375548751569
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/AppointmentDao.java
@@ -0,0 +1,108 @@
+package smash.appointment.parse;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+public class AppointmentDao {
+	Logger												 logger				= Logger.getLogger(AppointmentDao.class);
+	private List<AppointmentEntry> appointments	= new ArrayList<>();
+
+	public void addAppointments(List<AppointmentEntry> appointmentsToAdd) {
+		for (AppointmentEntry appointmentEntry : appointmentsToAdd) {
+			addAppointment(appointmentEntry);
+		}
+	}
+
+	/**
+	 * @return the appointments
+	 * @see #appointments
+	 */
+	public List<AppointmentEntry> getAppointments() {
+		return appointments;
+	}
+
+	/**
+	 * @param appointments
+	 *          the appointments to set
+	 * @see #appointments
+	 */
+	public void setAppointments(List<AppointmentEntry> appointments) {
+		this.appointments = appointments;
+	}
+
+	public List<Visit> getVisits() {
+		List<Visit> result = new ArrayList<>();
+		Map<Subject, List<AppointmentEntry>> subjectAppointments = new HashMap<>();
+		for (AppointmentEntry entry : appointments) {
+			if (subjectAppointments.get(entry.getSubject()) == null) {
+				subjectAppointments.put(entry.getSubject(), new ArrayList<AppointmentEntry>());
+			}
+			subjectAppointments.get(entry.getSubject()).add(entry);
+		}
+		for (Subject subject : subjectAppointments.keySet()) {
+			result.addAll(getVisitsForSubject(subject, subjectAppointments.get(subject)));
+		}
+		return result;
+	}
+
+	private List<Visit> getVisitsForSubject(Subject subject, List<AppointmentEntry> list) {
+		Comparator<AppointmentEntry> comparator = new Comparator<AppointmentEntry>() {
+
+			@Override
+			public int compare(AppointmentEntry o1, AppointmentEntry o2) {
+				String date1 = o1.getDay().substring(0, 10);
+				String date2 = o2.getDay().substring(0, 10);
+				if (date1.compareTo(date2) == 0) {
+					if (o1.getTypes().contains(AppointmentType.LEVEL_A) || o1.getTypes().contains(AppointmentType.LEVEL_A_TQ)) {
+						return -1;
+					} else if (o2.getTypes().contains(AppointmentType.LEVEL_A) || o2.getTypes().contains(AppointmentType.LEVEL_A_TQ)) {
+						return 1;
+					} else {
+						return 0;
+					}
+				} else {
+					return date1.compareTo(date2);
+				}
+			}
+		};
+		Collections.sort(list, comparator);
+
+		List<Visit> result = new ArrayList<>();
+		Visit currentVisit = new Visit(subject);
+		for (AppointmentEntry appointmentEntry : list) {
+			if (appointmentEntry.getTypes().contains(AppointmentType.LEVEL_A) || appointmentEntry.getTypes().contains(AppointmentType.LEVEL_A_TQ)) {
+				if (currentVisit.getAppointments().size() > 0) {
+					result.add(currentVisit);
+				}
+
+				currentVisit = new Visit(subject);
+				currentVisit.addAppointment(appointmentEntry);
+			} else {
+				String date = currentVisit.getLastAppointmentDate();
+				if (date.equals(appointmentEntry.getDay().substring(0, 10))) {
+					currentVisit.getLastAppointment().addTypes(appointmentEntry.getTypes());
+					String source = appointmentEntry.getSource() + "\n"+currentVisit.getLastAppointment().getSource();
+					currentVisit.getLastAppointment().setSource(source);
+				} else {
+					currentVisit.addAppointment(appointmentEntry);
+				}
+			}
+		}
+		if (currentVisit.getAppointments().size() > 0) {
+			result.add(currentVisit);
+		}
+		return result;
+	}
+
+	public void addAppointment(AppointmentEntry appointment) {
+		appointments.add(appointment);
+
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/AppointmentEntry.java b/appointment-import/src/main/java/smash/appointment/parse/AppointmentEntry.java
new file mode 100644
index 0000000000000000000000000000000000000000..b4faf7c678c40b3daa3f6429d2314ce8d11e87ce
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/AppointmentEntry.java
@@ -0,0 +1,147 @@
+package smash.appointment.parse;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class AppointmentEntry {
+	private String								day;
+	private String								time;
+	private String								duration;
+	private Subject								subject;
+	private Set<AppointmentType>	types	= new HashSet<>();
+	private String								source;
+
+	/**
+	 * @return the time
+	 * @see #time
+	 */
+	public String getTime() {
+		if (time == null || time.trim().isEmpty()) {
+			return "09:00";
+		} else {
+			return time;
+		}
+	}
+
+	/**
+	 * @param time
+	 *          the time to set
+	 * @see #time
+	 */
+	public void setTime(String time) {
+		this.time = time;
+	}
+
+	/**
+	 * @return the subject
+	 * @see #subject
+	 */
+	public Subject getSubject() {
+		return subject;
+	}
+
+	/**
+	 * @param subject
+	 *          the subject to set
+	 * @see #subject
+	 */
+	public void setSubject(Subject subject) {
+		this.subject = subject;
+	}
+
+	/**
+	 * @return the type
+	 * @see #type
+	 */
+	public Set<AppointmentType> getTypes() {
+		return types;
+	}
+
+	/**
+	 * @param type
+	 *          the type to set
+	 * @see #type
+	 */
+	public void addType(AppointmentType type) {
+		this.types.add(type);
+	}
+
+	/**
+	 * @return the day
+	 * @see #day
+	 */
+	public String getDay() {
+		return day;
+	}
+
+	/**
+	 * @param day
+	 *          the day to set
+	 * @see #day
+	 */
+	public void setDay(String day) {
+		this.day = day;
+	}
+
+	/**
+	 * @return the source
+	 * @see #source
+	 */
+	public String getSource() {
+		return source;
+	}
+
+	/**
+	 * @param source
+	 *          the source to set
+	 * @see #source
+	 */
+	public void setSource(String source) {
+		this.source = source;
+	}
+
+	@Override
+	public String toString() {
+		return day + " " + time + " " + subject + " " + types + "\t\t[source: " + source + "]";
+	}
+
+	/**
+	 * @return the duration
+	 * @see #duration
+	 */
+	public String getDuration() {
+		if (duration == null || duration.trim().isEmpty()) {
+			int count = 0;
+			for (AppointmentType type : types) {
+				count += type.getTime();
+			}
+			return count + "";
+		}
+		return duration;
+	}
+
+	/**
+	 * @param duration
+	 *          the duration to set
+	 * @see #duration
+	 */
+	public void setDuration(String duration) {
+		this.duration = duration;
+	}
+
+	public void addTypes(AppointmentType[] typesToAdd) {
+		for (AppointmentType appointmentType : typesToAdd) {
+			addType(appointmentType);
+		}
+
+	}
+
+	public void addTypes(Collection<AppointmentType> typesToAdd) {
+		for (AppointmentType appointmentType : typesToAdd) {
+			addType(appointmentType);
+		}
+
+	}
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/AppointmentSqlExporter.java b/appointment-import/src/main/java/smash/appointment/parse/AppointmentSqlExporter.java
new file mode 100644
index 0000000000000000000000000000000000000000..6966c9ec3dcea9a0d39f302eb625c6aeb923005a
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/AppointmentSqlExporter.java
@@ -0,0 +1,36 @@
+package smash.appointment.parse;
+
+public class AppointmentSqlExporter extends SqlExporter {
+
+	public String toSql(AppointmentEntry appointment, boolean isFinished) {
+		StringBuilder result = new StringBuilder("");
+
+		result.append("insert into web_appointment (");
+		result.append("datetime_when, ");
+		result.append("length, ");
+		result.append("is_finished, ");
+		result.append("comment, ");
+		result.append("visit_id) ");
+
+		result.append("values (");
+		result.append(getStringVal(appointment.getDay() + " " + appointment.getTime()+"+00") + ",");
+		result.append(getStringVal(appointment.getDuration()) + ",");
+		result.append(isFinished + ",");
+		result.append(getStringVal(appointment.getSource()) + ",");
+		result.append("(select max(id) from web_visit)");
+		result.append(");");
+
+		for (AppointmentType type : appointment.getTypes()) {
+			result.append("insert into web_appointment_appointment_types (");
+			result.append("appointment_id, ");
+			result.append("appointmenttype_id) ");
+			result.append("values (");
+			result.append("(select max(id) from web_appointment),");
+			result.append("(select id from web_appointmenttype where code=" + getStringVal(type.getAbbreviation()) + ") ");
+			result.append(");");
+		}
+
+		return result.toString();
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/AppointmentType.java b/appointment-import/src/main/java/smash/appointment/parse/AppointmentType.java
new file mode 100644
index 0000000000000000000000000000000000000000..bceb087694fe573ae698e4251a7364557624f402
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/AppointmentType.java
@@ -0,0 +1,29 @@
+package smash.appointment.parse;
+
+public enum AppointmentType {
+	LEVEL_BV(90,"BV"), //
+	LEVEL_BG(30,"BG"), //
+	LEVEL_SB(30,"SB"), //
+
+	LEVEL_A_TQ(120,"A_TQ"), //
+	LEVEL_A(120,"A"), //
+	LEVEL_B(90,"B"), //
+	LEVEL_B_M_POWER(70,"mPower"), //
+	OTHER(60,"OTHER"), //
+	;
+
+	private int time;
+	private String abbreviation;
+	
+	private AppointmentType(int defaultTime, String abbreviation) {
+		this.time=defaultTime;
+		this.abbreviation=abbreviation;
+	}
+	public int getTime() {
+		return time;
+	}
+	public String getAbbreviation() {
+		return abbreviation;
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/AppointmentTypeCollection.java b/appointment-import/src/main/java/smash/appointment/parse/AppointmentTypeCollection.java
new file mode 100644
index 0000000000000000000000000000000000000000..c4fdcc15a17c91f517da687112ce87bff8298940
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/AppointmentTypeCollection.java
@@ -0,0 +1,78 @@
+package smash.appointment.parse;
+
+public enum AppointmentTypeCollection {
+	LEVEL_A_BV_BG(new AppointmentType[] { AppointmentType.LEVEL_A, AppointmentType.LEVEL_BV, AppointmentType.LEVEL_BG }, //
+			new String[] { "BV + BG + neuro level A" }), //
+	LEVEL_A_B(new AppointmentType[] { AppointmentType.LEVEL_A, AppointmentType.LEVEL_B }, //
+			new String[] { "level B + level A neuro" }), //
+	
+	LEVEL_B_BV_SB(new AppointmentType[] { AppointmentType.LEVEL_B, AppointmentType.LEVEL_BV, AppointmentType.LEVEL_SB }, //
+			new String[] { "level B + BV + SB" }), //
+
+	LEVEL_B_BV_BG(new AppointmentType[] { AppointmentType.LEVEL_B, AppointmentType.LEVEL_BV, AppointmentType.LEVEL_BG }, //
+			new String[] { "level B + BV + BG" }), //
+
+	LEVEL_B_BG(new AppointmentType[] { AppointmentType.LEVEL_B, AppointmentType.LEVEL_BG }, //
+			new String[] { "level B + BG" }), //
+
+	LEVEL_B_BV(new AppointmentType[] { AppointmentType.LEVEL_B, AppointmentType.LEVEL_BV }, //
+			new String[] { "level B + BV" }), //
+
+	LEVEL_BV_BG_SB_MPOWER(
+			new AppointmentType[] { AppointmentType.LEVEL_BV, AppointmentType.LEVEL_BG, AppointmentType.LEVEL_SB, AppointmentType.LEVEL_B_M_POWER, }, //
+			new String[] { "evel BV + BG + SB + mPower", "BV + BG + SB + mPower" }), //
+	
+	LEVEL_BG_SB_MPOWER(
+			new AppointmentType[] { AppointmentType.LEVEL_BG, AppointmentType.LEVEL_SB, AppointmentType.LEVEL_B_M_POWER, }, //
+			new String[] { "level BG + SB + mPower" }), //
+	LEVEL_BV_BG_SB(new AppointmentType[] { AppointmentType.LEVEL_BV, AppointmentType.LEVEL_BG, AppointmentType.LEVEL_SB }, //
+			new String[] { "evel BV + BG + SB", "BV + BG + SB" }), //
+	LEVEL_BV_SB(new AppointmentType[] { AppointmentType.LEVEL_BV, AppointmentType.LEVEL_SB }, //
+			new String[] { "evel BV + SB", "BV + SB" }), //
+	LEVEL_BV_BG(new AppointmentType[] { AppointmentType.LEVEL_BV, AppointmentType.LEVEL_BG }, //
+			new String[] { "evel BV + BG", "BV + BG" }), //
+	LEVEL_BG_SB(new AppointmentType[] { AppointmentType.LEVEL_BG, AppointmentType.LEVEL_SB }, //
+			new String[] { "evel BG + SB", "BG + SB" }), //
+	LEVEL_BV(new AppointmentType[] { AppointmentType.LEVEL_BV }, //
+			new String[] { "evel BV", "BV" }), //
+	LEVEL_BG(new AppointmentType[] { AppointmentType.LEVEL_BG }, //
+			new String[] { "evel BG", "BG" }), //
+	LEVEL_SB(new AppointmentType[] { AppointmentType.LEVEL_SB }, //
+			new String[] { "evel SB", "SB" }), //
+
+	LEVEL_A_TQ(new AppointmentType[] { AppointmentType.LEVEL_A_TQ }, //
+			new String[] { "TQ" }), //
+	LEVEL_A(new AppointmentType[] { AppointmentType.LEVEL_A }, //
+			new String[] { "level A" }), //
+	LEVEL_B(new AppointmentType[] { AppointmentType.LEVEL_B }, //
+			new String[] { "evel B" }), //
+	LEVEL_B_M_POWER(new AppointmentType[] { AppointmentType.LEVEL_B_M_POWER }, //
+			new String[] { "mPower" }), //
+	OTHER(new AppointmentType[] {}, //
+			new String[] {}), //
+	;
+	private String[]					queryStrings;
+	private AppointmentType[]	types;
+
+	private AppointmentTypeCollection(AppointmentType[] types, String[] queryStrings) {
+		this.queryStrings = queryStrings;
+		this.types = types;
+	}
+
+	/**
+	 * @return the queryStrings
+	 * @see #queryStrings
+	 */
+	public String[] getQueryStrings() {
+		return queryStrings;
+	}
+
+	/**
+	 * @return the types
+	 * @see #types
+	 */
+	public AppointmentType[] getTypes() {
+		return types;
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/CellParser.java b/appointment-import/src/main/java/smash/appointment/parse/CellParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..3780a8006712edf884e6e5d1ad6f1a5ecd34ca0b
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/CellParser.java
@@ -0,0 +1,162 @@
+package smash.appointment.parse;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+
+public class CellParser {
+	Logger						 logger			 = Logger.getLogger(CellParser.class);
+
+	private SubjectDao subjectDao;
+
+	Pattern						 timePattern = Pattern.compile("^[0-9][0-9]\\:[0-9][0-9]");
+
+	public String extractTime(String content) {
+		String result = null;
+		Matcher matcher = timePattern.matcher(content);
+		if (matcher.find()) {
+			result = matcher.group();
+		}
+		return result;
+	}
+
+	public String removeTime(String content) {
+		Matcher matcher = timePattern.matcher(content);
+		if (matcher.find()) {
+			content = matcher.replaceFirst("").trim();
+		}
+		return content;
+	}
+
+	public AppointmentEntry parseAppointment(String query, String defaultTime) {
+		AppointmentEntry result = new AppointmentEntry();
+
+		String time = extractTime(query);
+		if (time != null) {
+			query = removeTime(query);
+		} else {
+			time = defaultTime;
+		}
+		result.setTime(time);
+
+		Subject subject = extractSubject(query);
+		result.setSubject(subject);
+
+		AppointmentTypeCollection type = extractType(query);
+		if (type == null) {
+			result.addType(AppointmentType.OTHER);
+		} else {
+			result.addTypes(type.getTypes());
+		}
+
+		result.setSource(query);
+		return result;
+	}
+
+	private AppointmentTypeCollection extractType(String query) {
+		
+		String simplifiedQuery = Utils.simplifyString(query);
+
+		AppointmentTypeCollection result = null;
+
+		String usedString = null;
+		for (AppointmentTypeCollection type : AppointmentTypeCollection.values()) {
+			boolean matchFound = false;
+			for (String string : type.getQueryStrings()) {
+				if (!matchFound) {
+					String simplifiedString = Utils.simplifyString(string);
+
+					if (simplifiedQuery.contains(simplifiedString)) {
+						matchFound = true;
+						if (result == null) {
+							result = type;
+							usedString = string;
+						} else {
+							if (string.contains(usedString)) {
+								result = type;
+								usedString = string;
+							} else if (usedString.contains(string)) {
+								// new one is a substring of old
+							} else { // if there is no substring then we might have a problem
+								AppointmentTypeCollection newType = result;
+								if (usedString.length() < string.length()) {
+									usedString = string;
+									newType = type;
+								}
+								logger.warn("More than one type possible for query: " + query + ". Type 1: " + result + ". Type 2: " + type + ". Choosing: " + newType);
+								result = newType;
+							}
+						}
+
+					}
+				}
+			}
+		}
+		return result;
+	}
+
+	private Subject extractSubject(String query) {
+		Subject result = null;
+		String simplifiedQuery = Utils.simplifyString(query);
+
+		SubjectIndexer[] mainIndices = new SubjectIndexer[] { //
+				new NameSurnameIndexer(), //
+				new SurnameNameIndexer(), //
+				new NdNumberIndexer(),//
+		};
+
+		result = getByIndices(query, simplifiedQuery, mainIndices);
+		if (result == null) {
+			SubjectIndexer[] secondaryIndices = new SubjectIndexer[] { //
+					new SurnameIndexer(), //
+			};
+			result = getByIndices(query, simplifiedQuery, secondaryIndices);
+		}
+		return result;
+	}
+
+	private Subject getByIndices(String query, String simplifiedQuery, SubjectIndexer[] mainIndices) {
+		Subject result = null;
+		for (Subject subject : subjectDao.getSubjects()) {
+			boolean matchFound = false;
+			for (SubjectIndexer indexer : mainIndices) {
+				if (!matchFound) {
+					if (indexer.match(subject, simplifiedQuery)) {
+						matchFound = true;
+						if (result == null) {
+							result = subject;
+						} else {
+							Subject newResult = result;
+							if (indexer.isBetter(subject, result)) {
+								newResult = subject;
+							}
+							logger.warn(
+									"[" + indexer.getClass().getSimpleName() + "]" + "More than one subject possible for query: " + query + ". Subject 1: " + result
+											+ ". Subject 2: " + subject + ". Choosing: " + newResult);
+							result = newResult;
+						}
+					}
+				}
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * @return the subjectDao
+	 * @see #subjectDao
+	 */
+	public SubjectDao getSubjectDao() {
+		return subjectDao;
+	}
+
+	/**
+	 * @param subjectDao
+	 *          the subjectDao to set
+	 * @see #subjectDao
+	 */
+	public void setSubjectDao(SubjectDao subjectDao) {
+		this.subjectDao = subjectDao;
+	}
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/LihControlMappingParser.java b/appointment-import/src/main/java/smash/appointment/parse/LihControlMappingParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..3163eed9966d052bea816326d4ae3632ff2406bc
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/LihControlMappingParser.java
@@ -0,0 +1,157 @@
+package smash.appointment.parse;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import org.apache.poi.ss.usermodel.Row;
+
+public class LihControlMappingParser extends SubjectParser {
+
+	@Override
+	protected String parseScreeningNumber(Row row) {
+		String number = getString(row.getCell(0));
+		if (number.trim().isEmpty()) {
+			return "";
+		} else {
+			if (number.length() == 1) {
+				number = "00" + number;
+			} else if (number.length() == 2) {
+				number = "0" + number;
+			}
+			return "L-" + number;
+		}
+	}
+
+	@Override
+	protected String parseName(Row row) {
+		return getString(row.getCell(8));
+	}
+
+	@Override
+	protected String parseSurname(Row row) {
+		return getString(row.getCell(9));
+	}
+
+	@Override
+	protected String parseNdNumber(Row row) {
+		String number = getString(row.getCell(7));
+		
+		if (number.equalsIgnoreCase("PK") ||number.equalsIgnoreCase("CHEM") || number.equalsIgnoreCase("Flying Team")) {
+			number = "";
+		}
+		return number;
+	}
+
+	@Override
+	protected String getSheetName() {
+		return "Sheet1";
+	}
+
+	@Override
+	protected int getInitRow() {
+		return 1;
+	}
+
+	@Override
+	protected String parseBirthDate(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parsemPowerId(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseAddDate(Row row) {
+		return getDate(row.getCell(1), Calendar.getInstance());
+	}
+
+	@Override
+	protected String parseReferal(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseDiagnosisYear(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseMail(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parsePhone3(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parsePhone2(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parsePhone1(Row row) {
+		return getString(row.getCell(10));
+	}
+
+	@Override
+	protected String parseCity(Row row) {
+		return "";
+
+	}
+
+	@Override
+	protected String parseCountry(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseZipCode(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseAddress(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseRemarks(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseDiagnosis(Row row) {
+		return "";
+	}
+
+	@Override
+	protected SubjectType parseType(Row row) {
+		return SubjectType.CONTROL;
+	}
+
+	@Override
+	protected List<String> parseLanguages(Row row) {
+		return new ArrayList<>();
+	}
+
+	@Override
+	protected String parseToBeSeenAt(Row row) {
+		return "L";
+	}
+
+	@Override
+	protected boolean parseDead(Row row) {
+		return false;
+	}
+
+	@Override
+	protected boolean parseResigned(Row row) {
+		return false;
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/LihControlParser.java b/appointment-import/src/main/java/smash/appointment/parse/LihControlParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe44bb6fc33c679718a54aa51af1443565341be6
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/LihControlParser.java
@@ -0,0 +1,209 @@
+package smash.appointment.parse;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import org.apache.poi.ss.usermodel.Row;
+
+public class LihControlParser extends SubjectParser {
+
+	@Override
+	protected String parseScreeningNumber(Row row) {
+		String number = getString(row.getCell(0));
+		if (number.trim().isEmpty()) {
+			return "";
+		} else {
+			return "L-" + number;
+		}
+	}
+
+	@Override
+	protected String parseName(Row row) {
+		return getString(row.getCell(2));
+	}
+
+	@Override
+	protected String parseSurname(Row row) {
+		return getString(row.getCell(1));
+	}
+
+	@Override
+	protected String parseNdNumber(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String getSheetName() {
+		return "Screening log";
+	}
+
+	@Override
+	protected int getInitRow() {
+		return 1;
+	}
+
+	@Override
+	protected String parseBirthDate(Row row) {
+		return getDate(row.getCell(5), null);
+	}
+
+	@Override
+	protected String parsemPowerId(Row row) {
+		return "";
+	}
+
+	private static final SimpleDateFormat	DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
+
+	@Override
+	protected String parseAddDate(Row row) {
+		return DATE_FORMATTER.format(Calendar.getInstance().getTime());
+	}
+
+	@Override
+	protected String parseReferal(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseDiagnosisYear(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseMail(Row row) {
+		return getString(row.getCell(8));
+	}
+
+	@Override
+	protected String parsePhone3(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parsePhone2(Row row) {
+		return getString(row.getCell(7));
+	}
+
+	@Override
+	protected String parsePhone1(Row row) {
+		return getString(row.getCell(6));
+	}
+
+	@Override
+	protected String parseCity(Row row) {
+		return getString(row.getCell(12));
+
+	}
+
+	@Override
+	protected String parseCountry(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseZipCode(Row row) {
+		return getString(row.getCell(11));
+	}
+
+	@Override
+	protected String parseAddress(Row row) {
+		return getString(row.getCell(9)) + ", " + getString(row.getCell(10));
+	}
+
+	@Override
+	protected String parseRemarks(Row row) {
+		List<String> remarks = new ArrayList<>();
+		String info = getString(row.getCell(4));
+		if (!info.trim().isEmpty()) {
+			remarks.add("PD family relation=" + info);
+		}
+		remarks.add(getString(row.getCell(13)));
+		remarks.add(getComments(row.getCell(14)));
+		remarks.add(getComments(row.getCell(15)));
+		remarks.add(getComments(row.getCell(16)));
+		remarks.add(getString(row.getCell(18)));
+		remarks.add(getString(row.getCell(19)));
+		String result = "";
+		for (String string : remarks) {
+			if (!string.trim().isEmpty()) {
+				result += string.trim() + "\n";
+			}
+		}
+		return result;
+	}
+
+	@Override
+	protected String parseDiagnosis(Row row) {
+		return "";
+	}
+
+	@Override
+	protected SubjectType parseType(Row row) {
+		return SubjectType.CONTROL;
+	}
+
+	@Override
+	protected List<String> parseLanguages(Row row) {
+		List<String> result = new ArrayList<>();
+
+		String languages = getString(row.getCell(3));
+		String langAbbreviations[] = new String[] {};
+		if (languages.indexOf(",") >= 0) {
+			langAbbreviations = languages.split(",");
+		} else if (languages.indexOf("+") >= 0) {
+			langAbbreviations = languages.split("\\+");
+		} else {
+			langAbbreviations = languages.split("/");
+		}
+		for (String string : langAbbreviations) {
+			if (!string.trim().isEmpty()) {
+				result.add(getMappedLanguage(string.trim()));
+			}
+		}
+		return result;
+	}
+
+	protected String getMappedLanguage(String abbreviation) {
+		switch (abbreviation.toUpperCase()) {
+			case ("F"):
+				return "French";
+			case ("D"):
+				return "German";
+			case ("GB"):
+				return "English";
+			case ("P"):
+				return "Portuguese";
+			case ("PT"):
+				return "Portuguese";
+			case ("ENG"):
+				return "English";
+			case ("EN"):
+				return "English";
+			case ("FR"):
+				return "French";
+			case ("L"):
+				return "Luxembourgish";
+			case ("LUX"):
+				return "Luxembourgish";
+		}
+		logger.warn("Unknown language abbreviation: " + abbreviation);
+		return "";
+	}
+
+	@Override
+	protected String parseToBeSeenAt(Row row) {
+		return "L";
+	}
+
+	@Override
+	protected boolean parseDead(Row row) {
+		return false;
+	}
+
+	@Override
+	protected boolean parseResigned(Row row) {
+		return false;
+	}
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/Main.java b/appointment-import/src/main/java/smash/appointment/parse/Main.java
new file mode 100644
index 0000000000000000000000000000000000000000..59a12ffb85d8be2c62b77c3c7db03416eae1dea6
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/Main.java
@@ -0,0 +1,157 @@
+package smash.appointment.parse;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.log4j.Logger;
+
+public class Main {
+	private static Logger	logger				 = Logger.getLogger(Main.class);
+
+	SubjectDao						subjectDao		 = new SubjectDao();
+	AppointmentDao				appointmentDao = new AppointmentDao();
+
+	public void run(String[] args) throws Exception {
+
+		Options options = new Options();
+		Option agenda = Option.builder().required().argName("file").hasArg().desc("PRC agenda").longOpt("agenda").build();
+		Option subjects = Option.builder().required().argName("file").hasArg().desc("PRC subjects").longOpt("subjects").build();
+		Option controls = Option.builder().required().argName("file").hasArg().desc("PRC controls").longOpt("controls").build();
+		Option flyingTeam = Option.builder().required().argName("file").hasArg().desc("PRC flying-team").longOpt("flying-team").build();
+		Option lihControls = Option.builder().required().argName("file").hasArg().desc("LIH controls").longOpt("lih-controls").build();
+		Option lihMappingControls = Option.builder().required().argName("file").hasArg().desc("LIH controls mapping").longOpt("lih-mapping").build();
+		Option redCap = Option.builder().required().argName("file").hasArg().desc("RedCap appointments").longOpt("red-cap").build();
+		options.addOption(agenda);
+		options.addOption(subjects);
+		options.addOption(controls);
+		options.addOption(flyingTeam);
+		options.addOption(lihControls);
+		options.addOption(lihMappingControls);
+		options.addOption(redCap);
+
+		CommandLineParser parser = new DefaultParser();
+		try {
+			CommandLine line = parser.parse(options, args);
+
+			String subjectsFile = line.getOptionValue("subjects");
+			for (Subject subject : processPrcSubjects(subjectsFile)) {
+				subjectDao.addSubject(subject, "[" + subjectsFile + ";" + subject.getScreeningNumber() + ";" + subject.getName() + " " + subject.getSurname() + "]");
+			}
+
+			String controlsFile = line.getOptionValue("controls");
+			for (Subject subject : processPrcControls(controlsFile)) {
+				subjectDao.addSubject(subject, "[" + controlsFile + ";" + subject.getScreeningNumber() + ";" + subject.getName() + " " + subject.getSurname() + "]");
+			}
+
+			String flyingTeamFile = line.getOptionValue("flying-team");
+			for (Subject subject : processFlyingTeamControls(flyingTeamFile)) {
+				subjectDao.addSubject(subject, "[" + flyingTeamFile + ";" + subject.getScreeningNumber() + ";" + subject.getName() + " " + subject.getSurname() + "]");
+			}
+
+			String lihMappingControlsFile = line.getOptionValue("lih-mapping");
+			for (Subject subject : processLihMappingControls(lihMappingControlsFile)) {
+				subjectDao.addSubject(
+						subject, "[" + lihMappingControlsFile + ";" + subject.getScreeningNumber() + ";" + subject.getName() + " " + subject.getSurname() + "]");
+			}
+
+			String lihControlsFile = line.getOptionValue("lih-controls");
+			for (Subject subject : processLihControls(lihControlsFile)) {
+				subjectDao
+						.addSubject(subject, "[" + lihControlsFile + ";" + subject.getScreeningNumber() + ";" + subject.getName() + " " + subject.getSurname() + "]");
+			}
+
+			subjectDao.addSubject(Visit.UNKNOWN, "");
+
+			String agendaFile = line.getOptionValue("agenda");
+			Calendar today = Calendar.getInstance();
+			today.set(Calendar.HOUR, 5);
+			appointmentDao.addAppointments(processPrcAppointments(agendaFile, today));
+
+			String redCapFile = line.getOptionValue("red-cap");
+			appointmentDao.addAppointments(processRedCapAppointments(redCapFile));
+
+			System.out.println("delete from web_appointment_appointment_types;");
+			System.out.println("delete from web_subject_languages;");
+			System.out.println("delete from web_appointment;");
+			System.out.println("delete from web_visit;");
+			System.out.println("delete from web_subject;");
+			SubjectSqlExporter subjectSqlExporter = new SubjectSqlExporter();
+			// logger.debug("SUBJECTS: ");
+			for (Subject subject : subjectDao.getSubjects()) {
+				System.out.println(subjectSqlExporter.toSql(subject));
+			}
+
+			VisitSqlExporter visitSqlExporter = new VisitSqlExporter();
+			List<Visit> visits = appointmentDao.getVisits();
+			for (int i = 0; i < visits.size(); i++) {
+				Visit visit = visits.get(i);
+				boolean finished = false;
+				if (i < visits.size() - 1) {
+					if (visit.getSubject().equals(visits.get(i + 1).getSubject())) {
+						finished = true;
+					}
+				}
+				System.out.println(visitSqlExporter.toSql(visit, finished));
+			}
+
+		} catch (ParseException exp) {
+			System.out.println(exp.getMessage());
+			HelpFormatter formatter = new HelpFormatter();
+			formatter.printHelp("java -jar file.jar", options);
+		}
+	}
+
+	private List<AppointmentEntry> processRedCapAppointments(String agendaFile) throws Exception{
+		RedcapParser parser = new RedcapParser();
+		parser.setSubjectDao(subjectDao);
+		return parser.parse(agendaFile);
+	}
+
+	private List<Subject> processLihMappingControls(String lihMappingControlsFile) throws Exception {
+		LihControlMappingParser parser = new LihControlMappingParser();
+		return parser.processExcel(lihMappingControlsFile);
+	}
+
+	private List<Subject> processLihControls(String lihControlsFile) throws Exception {
+		LihControlParser parser = new LihControlParser();
+		return parser.processExcel(lihControlsFile);
+	}
+
+	private List<Subject> processFlyingTeamControls(String flyingTeamFile) throws Exception {
+		PrcFlyingParser parser = new PrcFlyingParser();
+		return parser.processExcel(flyingTeamFile);
+	}
+
+	private List<Subject> processPrcControls(String controlsFile) throws Exception {
+		PrcControlParser parser = new PrcControlParser();
+		return parser.processExcel(controlsFile);
+	}
+
+	private List<AppointmentEntry> processPrcAppointments(String agendaFile, Calendar minDate) throws Exception {
+		XlsxCalendarProcessor processor = new XlsxCalendarProcessor();
+		processor.setSubjectDao(subjectDao);
+		List<AppointmentEntry> entries = processor.processExcel(agendaFile, minDate);
+
+		return entries;
+	}
+
+	public static void main(String[] args) throws Exception {
+		new Main().run(args);
+	}
+
+	private List<Subject> processPrcSubjects(String subjectsFile) throws Exception {
+		PrcSubjectsParser parser = new PrcSubjectsParser();
+		return parser.processExcel(subjectsFile);
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/NameSurnameIndexer.java b/appointment-import/src/main/java/smash/appointment/parse/NameSurnameIndexer.java
new file mode 100644
index 0000000000000000000000000000000000000000..2ed820ad2e1b4609692d2eaf7233254a6784c0fc
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/NameSurnameIndexer.java
@@ -0,0 +1,9 @@
+package smash.appointment.parse;
+
+public class NameSurnameIndexer extends SubjectIndexer {
+
+	public String getIndexedString(Subject subject) {
+		return Utils.simplifyString(subject.getName() + subject.getSurname());
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/NdNumberIndexer.java b/appointment-import/src/main/java/smash/appointment/parse/NdNumberIndexer.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd96a4a1a0149f439d7436689350279fe87d92fb
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/NdNumberIndexer.java
@@ -0,0 +1,9 @@
+package smash.appointment.parse;
+
+public class NdNumberIndexer extends SubjectIndexer {
+
+	public String getIndexedString(Subject subject) {
+		return Utils.simplifyString(subject.getNdNumber());
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/PrcControlParser.java b/appointment-import/src/main/java/smash/appointment/parse/PrcControlParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..e0c6e4f5407e09e29609ac6ae55c76a9a14ecdb4
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/PrcControlParser.java
@@ -0,0 +1,184 @@
+package smash.appointment.parse;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+
+public class PrcControlParser extends SubjectParser {
+
+	@Override
+	protected String parseScreeningNumber(Row row) {
+		String number = getString(row.getCell(16));
+		if (number.trim().isEmpty()) {
+			return "";
+		} else {
+			if (number.length() == 1) {
+				number = "00" + number;
+			} else if (number.length() == 2) {
+				number = "0" + number;
+			}
+			return "P-" + number;
+		}
+	}
+
+	@Override
+	protected String parseName(Row row) {
+		return getString(row.getCell(1));
+	}
+
+	@Override
+	protected String parseSurname(Row row) {
+		return getString(row.getCell(0));
+	}
+
+	@Override
+	protected String parseNdNumber(Row row) {
+		return getString(row.getCell(14));
+	}
+
+	@Override
+	protected String getSheetName() {
+		return "Contrôles";
+	}
+
+	@Override
+	protected int getInitRow() {
+		return 1;
+	}
+
+	@Override
+	protected String parseBirthDate(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parsemPowerId(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseAddDate(Row row) {
+		return getDate(row.getCell(8), Calendar.getInstance());
+	}
+
+	@Override
+	protected String parseReferal(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseDiagnosisYear(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseMail(Row row) {
+		return getString(row.getCell(5));
+	}
+
+	@Override
+	protected String parsePhone3(Row row) {
+		String phones[] = getPhones(row);
+		if (phones.length > 2) {
+			return phones[2].trim();
+		}
+		return "";
+	}
+
+	private String[] getPhones(Row row) {
+		return getString(row.getCell(4)).split("/");
+	}
+
+	@Override
+	protected String parsePhone2(Row row) {
+		String phones[] = getPhones(row);
+		if (phones.length > 1) {
+			return phones[1].trim();
+		}
+		return "";
+	}
+
+	@Override
+	protected String parsePhone1(Row row) {
+		String phones[] = getPhones(row);
+		return phones[0].trim();
+	}
+
+	@Override
+	protected String parseCity(Row row) {
+		String string = getString(row.getCell(3));
+		int spaceIndex = string.indexOf(" ");
+		if (spaceIndex > 0) {
+			return string.substring(spaceIndex).trim();
+		}
+		return string;
+	}
+
+	@Override
+	protected String parseCountry(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseZipCode(Row row) {
+		String string = getString(row.getCell(3));
+		int spaceIndex = string.indexOf(" ");
+		if (spaceIndex > 0) {
+			return string.substring(0,spaceIndex);
+		}
+		return "";
+	}
+
+	@Override
+	protected String parseAddress(Row row) {
+		return getString(row.getCell(2));
+	}
+
+	@Override
+	protected String parseRemarks(Row row) {
+		String remark1 = getString(row.getCell(9));
+		String remark2 = "";
+
+		String result = "";
+		if (!remark1.trim().isEmpty()) {
+			result = result + remark1 + "\n";
+		}
+		if (!remark2.trim().isEmpty()) {
+			result = result + remark2 + "\n";
+		}
+		return result;
+	}
+
+	@Override
+	protected String parseDiagnosis(Row row) {
+		return "";
+	}
+
+	@Override
+	protected SubjectType parseType(Row row) {
+		return SubjectType.CONTROL;
+	}
+
+	@Override
+	protected List<String> parseLanguages(Row row) {
+		return new ArrayList<>();
+	}
+
+	@Override
+	protected String parseToBeSeenAt(Row row) {
+		return "P";
+	}
+	@Override
+	protected boolean parseDead(Row row) {
+		return false;
+	}
+
+	@Override
+	protected boolean parseResigned(Row row) {
+		return false;
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/PrcFlyingParser.java b/appointment-import/src/main/java/smash/appointment/parse/PrcFlyingParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..efb92ca21c1289dc67732144365ba7e10fc08b65
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/PrcFlyingParser.java
@@ -0,0 +1,179 @@
+package smash.appointment.parse;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+
+public class PrcFlyingParser extends SubjectParser {
+
+	@Override
+	protected String parseScreeningNumber(Row row) {
+		String number = getString(row.getCell(25));
+		if (number.trim().isEmpty()) {
+			return "";
+		} else {
+			if (number.length() == 1) {
+				number = "00" + number;
+			} else if (number.length() == 2) {
+				number = "0" + number;
+			}
+			return "P-" + number;
+		}
+	}
+
+	@Override
+	protected String parseName(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseSurname(Row row) {
+		return getString(row.getCell(0));
+	}
+
+	@Override
+	protected String parseNdNumber(Row row) {
+		return getString(row.getCell(23));
+	}
+
+	@Override
+	protected String getSheetName() {
+		return "Leudelange";
+	}
+
+	@Override
+	protected int getInitRow() {
+		return 2;
+	}
+
+	@Override
+	protected String parseBirthDate(Row row) {
+		return parseDateOfBirth(row.getCell(27));
+	}
+
+	@Override
+	protected String parsemPowerId(Row row) {
+		return "";
+	}
+
+	@Override
+	protected String parseAddDate(Row row) {
+		return getDate(row.getCell(11),Calendar.getInstance());
+	}
+
+	@Override
+	protected String parseReferal(Row row) {
+		return getString(row.getCell(10));
+	}
+
+	@Override
+	protected String parseDiagnosisYear(Row row) {
+		return getString(row.getCell(8));
+	}
+
+	@Override
+	protected String parseMail(Row row) {
+		return getString(row.getCell(6));
+	}
+
+	@Override
+	protected String parsePhone3(Row row) {
+		String phones[] = getPhones(row);
+		if (phones.length > 2) {
+			return phones[2].trim();
+		}
+		return "";
+	}
+
+	private String[] getPhones(Row row) {
+		return getString(row.getCell(5)).split("/");
+	}
+
+	@Override
+	protected String parsePhone2(Row row) {
+		String phones[] = getPhones(row);
+		if (phones.length > 1) {
+			return phones[1].trim();
+		}
+		return "";
+	}
+
+	@Override
+	protected String parsePhone1(Row row) {
+		String phones[] = getPhones(row);
+		return phones[0].trim();
+	}
+
+	@Override
+	protected String parseCity(Row row) {
+		return getString(row.getCell(3));
+	}
+
+	@Override
+	protected String parseCountry(Row row) {
+		return getString(row.getCell(4));
+	}
+
+	@Override
+	protected String parseZipCode(Row row) {
+		return getString(row.getCell(2));
+	}
+
+	@Override
+	protected String parseAddress(Row row) {
+		return getString(row.getCell(1));
+	}
+
+	@Override
+	protected String parseRemarks(Row row) {
+		String remark1 = getString(row.getCell(7));
+		String remark2 = getString(row.getCell(18));
+
+		String result = "";
+		if (!remark1.trim().isEmpty()) {
+			result = result + remark1 + "\n";
+		}
+		if (!remark2.trim().isEmpty()) {
+			result = result + remark2 + "\n";
+		}
+		return result;
+	}
+
+	@Override
+	protected String parseDiagnosis(Row row) {
+		return getString(row.getCell(9));
+	}
+
+	@Override
+	protected SubjectType parseType(Row row) {
+		String name = (parseName(row) + " " + parseSurname(row)).toLowerCase();
+		if (name.indexOf("(c)") >= 0 || name.indexOf("©") >= 0) {
+			return SubjectType.CONTROL;
+		} else {
+			return SubjectType.PATIENT;
+		}
+	}
+
+	@Override
+	protected List<String> parseLanguages(Row row) {
+		return new ArrayList<>();
+	}
+	@Override
+	protected String parseToBeSeenAt(Row row) {
+		return "P";
+	}
+
+	@Override
+	protected boolean parseDead(Row row) {
+		return false;
+	}
+
+	@Override
+	protected boolean parseResigned(Row row) {
+		return false;
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/PrcSubjectsParser.java b/appointment-import/src/main/java/smash/appointment/parse/PrcSubjectsParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..b24a61be5c22d4b6af45cc8a0347a598a9fe2108
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/PrcSubjectsParser.java
@@ -0,0 +1,193 @@
+package smash.appointment.parse;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.Row;
+
+public class PrcSubjectsParser extends SubjectParser {
+
+	@Override
+	protected String parseScreeningNumber(Row row) {
+		String number = getString(row.getCell(25));
+		if (number.trim().isEmpty()) {
+			return "";
+		} else {
+			if (number.length() == 1) {
+				number = "00" + number;
+			} else if (number.length() == 2) {
+				number = "0" + number;
+			}
+			return "P-" + number;
+		}
+	}
+
+	@Override
+	protected String parseName(Row row) {
+		return getString(row.getCell(1));
+	}
+
+	@Override
+	protected String parseSurname(Row row) {
+		return getString(row.getCell(0));
+	}
+
+	@Override
+	protected String parseNdNumber(Row row) {
+		return getString(row.getCell(22));
+	}
+
+	@Override
+	protected String getSheetName() {
+		return "Feuil1";
+	}
+
+	@Override
+	protected int getInitRow() {
+		return 1;
+	}
+
+	@Override
+	protected String parseBirthDate(Row row) {
+		return parseDateOfBirth(row.getCell(27));
+	}
+
+	@Override
+	protected String parsemPowerId(Row row) {
+		return getString(row.getCell(23));
+	}
+
+	@Override
+	protected String parseAddDate(Row row) {
+		return getDate(row.getCell(14), Calendar.getInstance());
+	}
+
+	@Override
+	protected String parseReferal(Row row) {
+		return getString(row.getCell(13));
+	}
+
+	@Override
+	protected String parseDiagnosisYear(Row row) {
+		return getString(row.getCell(11));
+	}
+
+	@Override
+	protected String parseMail(Row row) {
+		return getString(row.getCell(10));
+	}
+
+	@Override
+	protected String parsePhone3(Row row) {
+		return getString(row.getCell(9));
+	}
+
+	@Override
+	protected String parsePhone2(Row row) {
+		return getString(row.getCell(8));
+	}
+
+	@Override
+	protected String parsePhone1(Row row) {
+		return getString(row.getCell(7));
+	}
+
+	@Override
+	protected String parseCity(Row row) {
+		return getString(row.getCell(5));
+	}
+
+	@Override
+	protected String parseCountry(Row row) {
+		return getString(row.getCell(6));
+	}
+
+	@Override
+	protected String parseZipCode(Row row) {
+		return getString(row.getCell(4));
+	}
+
+	@Override
+	protected String parseAddress(Row row) {
+		return getString(row.getCell(3));
+	}
+
+	@Override
+	protected String parseRemarks(Row row) {
+		String remark1 = getString(row.getCell(2));
+		String remark2 = getString(row.getCell(20));
+
+		String result = "";
+		if (!remark1.trim().isEmpty()) {
+			result = result + remark1 + "\n";
+		}
+		if (!remark2.trim().isEmpty()) {
+			result = result + remark2 + "\n";
+		}
+		return result;
+	}
+
+	@Override
+	protected String parseDiagnosis(Row row) {
+		return getString(row.getCell(12));
+	}
+
+	@Override
+	protected SubjectType parseType(Row row) {
+		return SubjectType.PATIENT;
+	}
+
+	@Override
+	protected List<String> parseLanguages(Row row) {
+		return new ArrayList<>();
+	}
+
+	@Override
+	protected String parseToBeSeenAt(Row row) {
+		return "P";
+	}
+
+	@Override
+	protected boolean parseDead(Row row) {
+		if (row.getCell(0).getCellStyle().getFillBackgroundColorColor() != null) {
+			return true;
+		}
+		return false;
+	}
+
+	@Override
+	protected boolean parseResigned(Row row) {
+		try {
+			int colorId = row.getSheet().getWorkbook().getFontAt(row.getCell(0).getCellStyle().getFontIndex()).getColor();
+			//special case for black
+			if (colorId == 32767) {
+				return false;
+			}
+			IndexedColors color = IndexedColors.fromInt(colorId);
+			switch (color) {
+				case RED:
+					return true;
+				case SEA_GREEN:
+					return false;
+				case PINK:
+					return false;
+				case PLUM:
+					return false;
+				case BLUE:
+					return false;
+				case BLACK:
+					return false;
+				case GREEN:
+					return false;
+				default:
+					logger.debug("Unknown font color: " + color);
+					return false;
+			}
+		} catch (Exception e) {
+			logger.error("Problem with parsing color for subject: " + parseName(row)+" "+parseSurname(row), e);
+			return false;
+		}
+	}
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/RedcapParser.java b/appointment-import/src/main/java/smash/appointment/parse/RedcapParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ba9e39616c177ea8487d95bd2a959f3964e09de
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/RedcapParser.java
@@ -0,0 +1,209 @@
+package smash.appointment.parse;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+public class RedcapParser {
+	Logger						 logger	= Logger.getLogger(RedcapParser.class);
+
+	private SubjectDao subjectDao;
+
+	public List<AppointmentEntry> parse(String filename) throws FileNotFoundException, IOException {
+		List<AppointmentEntry> result = new ArrayList<>();
+		try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
+			int lineNumber = 0;
+			String line;
+			while ((line = br.readLine()) != null) {
+				lineNumber++;
+				if (lineNumber == 1) {
+					continue;
+				}
+				String tmp[] = line.split("\t", -1);
+
+				String ndNumber = tmp[0];
+				Subject subject = subjectDao.getByNdNumber(ndNumber);
+				if (subject == null) {
+					logger.warn("Cannot find subject with id: " + ndNumber);
+				} else {
+					if (tmp.length < 8) {
+						logger.warn("Invalid row (not enough cells): " + line);
+					} else {
+						subject.addLanguage(tmp[4]);
+						subject.addLanguage(tmp[5]);
+						subject.addLanguage(tmp[6]);
+						subject.addLanguage(tmp[7]);
+						if (!tmp[3].trim().isEmpty()) {
+							subject.setSex(tmp[3]);
+						}
+						if (!tmp[2].trim().isEmpty()) {
+							subject.setBirthDate(tmp[2]);
+						}
+
+						// for now only PRC and flying team subjects
+						if (subject.getToBeSeenAt() == null || subject.getToBeSeenAt().startsWith("P") || subject.getToBeSeenAt().startsWith("F")) {
+							List<AppointmentEntry> subjectAppointments = new ArrayList<>();
+							if (!tmp[9].isEmpty()) {
+								subjectAppointments.add(createEntryLevelA(tmp[9], tmp[10], subject));
+							}
+
+							if (!tmp[13].isEmpty()) {
+								subjectAppointments.add(createEntryLevelB(tmp[13], tmp[14], subject));
+							}
+
+							if (!tmp[17].isEmpty()) {
+								subjectAppointments.add(createEntryLevelBG(tmp[17], tmp[18], subject));
+							}
+
+							if (!tmp[21].isEmpty()) {
+								subjectAppointments.add(createEntryLevelBV(tmp[21], tmp[22], subject));
+							}
+
+							if (!tmp[41].isEmpty()) {
+								subjectAppointments.add(createEntryLevelSB(tmp[41], subject));
+							}
+
+							if (!tmp[45].isEmpty()) {
+								subjectAppointments.add(createEntryLevelMPower(tmp[45], subject));
+							}
+
+							result.addAll(subjectAppointments);
+						}
+					}
+				}
+
+			}
+		}
+		return result;
+
+	}
+
+	private AppointmentEntry createEntryLevelMPower(String from, Subject subject) {
+		AppointmentEntry entry = new AppointmentEntry();
+		String date = getDate(from);
+		entry.setDay(date);
+		entry.setSubject(subject);
+		entry.setSource("Imported from RedCap");
+		entry.addType(AppointmentType.LEVEL_B_M_POWER);
+		return entry;
+	}
+
+	private AppointmentEntry createEntryLevelSB(String from, Subject subject) {
+		AppointmentEntry entry = new AppointmentEntry();
+		String date = getDate(from);
+		entry.setDay(date);
+		entry.setSubject(subject);
+		entry.setSource("Imported from RedCap");
+		entry.addType(AppointmentType.LEVEL_SB);
+		return entry;
+	}
+
+	private AppointmentEntry createEntryLevelBV(String from, String to, Subject subject) {
+		AppointmentEntry entry = new AppointmentEntry();
+		String date = getDate(from);
+		String date2 = getDate(to);
+		if (!date.equals(date2)) {
+			logger.warn("Different start and end date for level BV appointment. Subject: " + subject.getNdNumber());
+		}
+		entry.setDay(date);
+		entry.setSubject(subject);
+		entry.setSource("Imported from RedCap");
+		entry.addType(AppointmentType.LEVEL_BV);
+		return entry;
+	}
+
+	private AppointmentEntry createEntryLevelBG(String from, String to, Subject subject) {
+		AppointmentEntry entry = new AppointmentEntry();
+		String date = getDate(from);
+		String date2 = getDate(to);
+		if (!date.equals(date2)) {
+			logger.warn("Different start and end date for level BG appointment. Subject: " + subject.getNdNumber());
+		}
+		entry.setDay(date);
+		entry.setSubject(subject);
+		entry.setSource("Imported from RedCap");
+		entry.addType(AppointmentType.LEVEL_BG);
+		return entry;
+	}
+
+	private AppointmentEntry createEntryLevelB(String from, String to, Subject subject) {
+		AppointmentEntry entry = new AppointmentEntry();
+		String date = getDate(from);
+		String date2 = getDate(to);
+		if (!date.equals(date2)) {
+			logger.warn("Different start and end date for level B appointment. Subject: " + subject.getNdNumber());
+		}
+		entry.setDay(date);
+		entry.setSubject(subject);
+		entry.setSource("Imported from RedCap");
+		entry.addType(AppointmentType.LEVEL_B);
+		return entry;
+	}
+
+	private AppointmentEntry createEntryLevelA(String from, String to, Subject subject) {
+		AppointmentEntry entry = new AppointmentEntry();
+		String date = getDate(from);
+		String date2 = getDate(to);
+		if (!date.equals(date2)) {
+			logger.warn("Different start and end date for level A appointment. Subject: " + subject.getNdNumber() + "; " + date + ", " + date2);
+		}
+		String time = getTime(from);
+		String duration = getDuration(from, to);
+		entry.setDay(date);
+		entry.setDuration(duration);
+		entry.setSubject(subject);
+		entry.setSource("Imported from RedCap");
+		entry.setTime(time);
+		entry.addType(AppointmentType.LEVEL_A);
+		return entry;
+	}
+
+	private String getDuration(String string, String string2) {
+		if (string2.isEmpty()) {
+			return null;
+		}
+		String time1 = getTime(string);
+		String time2 = getTime(string2);
+
+		DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+		LocalDateTime dateTime1 = LocalDateTime.parse("2014-11-25 " + time1 + ":00", formatter);
+		LocalDateTime dateTime2 = LocalDateTime.parse("2014-11-25 " + time2 + ":00", formatter);
+
+		long diffInMinutes = java.time.Duration.between(dateTime1, dateTime2).toMinutes();
+
+		return diffInMinutes + "";
+	}
+
+	private String getTime(String string) {
+		return string.split(" ")[1];
+	}
+
+	private String getDate(String string) {
+		return string.split(" ")[0];
+	}
+
+	/**
+	 * @return the subjectDao
+	 * @see #subjectDao
+	 */
+	public SubjectDao getSubjectDao() {
+		return subjectDao;
+	}
+
+	/**
+	 * @param subjectDao
+	 *          the subjectDao to set
+	 * @see #subjectDao
+	 */
+	public void setSubjectDao(SubjectDao subjectDao) {
+		this.subjectDao = subjectDao;
+	}
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/SqlExporter.java b/appointment-import/src/main/java/smash/appointment/parse/SqlExporter.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5c19bb25f2b4a06d5d88b9cedcce94ae89d028b
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/SqlExporter.java
@@ -0,0 +1,52 @@
+package smash.appointment.parse;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+public class SqlExporter {
+
+	private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
+
+	public SqlExporter() {
+		super();
+	}
+
+	protected boolean isBefore(AppointmentEntry entry, Calendar minDate) {
+		String entryDate = entry.getDay();
+		String beforeDate = DATE_FORMATTER.format(minDate.getTime());
+		if (entryDate.compareTo(beforeDate) < 0) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	protected String getStringVal(String arg) {
+		if (arg == null) {
+			return "null";
+		} else if (arg.isEmpty()) {
+			return "''";
+		} else {
+			return "'" + arg.replace("'", "''") + "'";
+		}
+	}
+
+	protected String getIntVal(String diagnosisYear) {
+		if (diagnosisYear == null || diagnosisYear.trim().isEmpty()) {
+			return "0";
+		} else {
+			return diagnosisYear;
+		}
+	}
+
+	protected String getDateVal(String arg) {
+		if (arg == null) {
+			return "null";
+		} else if (arg.isEmpty()) {
+			return "null";
+		} else {
+			return "'" + arg + "'";
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/appointment-import/src/main/java/smash/appointment/parse/Subject.java b/appointment-import/src/main/java/smash/appointment/parse/Subject.java
new file mode 100644
index 0000000000000000000000000000000000000000..893fbfd450461c84d13f68ebd85ccb4c3162e942
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/Subject.java
@@ -0,0 +1,569 @@
+package smash.appointment.parse;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+public class Subject {
+	Logger							 logger		 = Logger.getLogger(Subject.class);
+
+	private String			 name;
+	private String			 surname;
+	private String			 ndNumber;
+	private String			 screeningNumber;
+	private String			 sex			 = "";
+	private String			 remarks;
+	private String			 birthDate;
+	private String			 address;
+	private String			 zipCode;
+	private String			 country;
+	private String			 city;
+	private String			 phone1;
+	private String			 phone2;
+	private String			 phone3;
+	private String			 mail;
+	private String			 diagnosisYear;
+	private String			 diagnosis;
+	private String			 referal;
+	private String			 addDate;
+	private String			 mPowerId;
+	private SubjectType	 type;
+	private String			 toBeSeenAt;
+	private boolean			 dead			 = false;
+	private boolean			 resigned	 = false;
+
+	private List<String> languages = new ArrayList<>();
+
+	public Subject(String name, String surname, String ndNumber, String screeningNumber) {
+		this.setName(name);
+		this.setSurname(surname);
+		this.setNdNumber(ndNumber);
+		this.setScreeningNumber(screeningNumber);
+	}
+
+	/**
+	 * @return the name
+	 * @see #name
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @param name
+	 *          the name to set
+	 * @see #name
+	 */
+	public void setName(String name) {
+		if (name != null && name.length() > 50) {
+			logger.warn("Name too long. Trimming: " + name);
+			name = name.substring(0, 50);
+		}
+		this.name = name;
+	}
+
+	/**
+	 * @return the surname
+	 * @see #surname
+	 */
+	public String getSurname() {
+		return surname;
+	}
+
+	/**
+	 * @param surname
+	 *          the surname to set
+	 * @see #surname
+	 */
+	public void setSurname(String surname) {
+		if (surname != null && surname.length() > 50) {
+			logger.warn("Surname too long. Trimming: " + surname);
+			surname = surname.substring(0, 50);
+		}
+		this.surname = surname;
+	}
+
+	/**
+	 * @return the ndNumber
+	 * @see #ndNumber
+	 */
+	public String getNdNumber() {
+		return ndNumber;
+	}
+
+	/**
+	 * @param ndNumber
+	 *          the ndNumber to set
+	 * @see #ndNumber
+	 */
+	public void setNdNumber(String ndNumber) {
+		this.ndNumber = ndNumber;
+	}
+
+	/**
+	 * @return the screeningNumber
+	 * @see #screeningNumber
+	 */
+	public String getScreeningNumber() {
+		return screeningNumber;
+	}
+
+	/**
+	 * @param screeningNumber
+	 *          the screeningNumber to set
+	 * @see #screeningNumber
+	 */
+	public void setScreeningNumber(String screeningNumber) {
+		this.screeningNumber = screeningNumber;
+	}
+
+	@Override
+	public String toString() {
+		return this.getName() + " " + this.getSurname() + " (" + this.getNdNumber() + "; " + this.getScreeningNumber() + ")";
+	}
+
+	public void addLanguage(String string) {
+		if (!string.trim().isEmpty() && !string.equalsIgnoreCase("OTHER")) {
+			if (!languages.contains(string)) {
+				this.languages.add(string);
+			}
+		}
+	}
+
+	/**
+	 * @return the sex
+	 * @see #sex
+	 */
+	public String getSex() {
+		return sex;
+	}
+
+	/**
+	 * @param sex
+	 *          the sex to set
+	 * @see #sex
+	 */
+	public void setSex(String sex) {
+		this.sex = sex;
+	}
+
+	/**
+	 * @return the birthDate
+	 * @see #birthDate
+	 */
+	public String getBirthDate() {
+		return birthDate;
+	}
+
+	/**
+	 * @param birthDate
+	 *          the birthDate to set
+	 * @see #birthDate
+	 */
+	public void setBirthDate(String birthDate) {
+		this.birthDate = birthDate;
+	}
+
+	/**
+	 * @return the languages
+	 * @see #languages
+	 */
+	public List<String> getLanguages() {
+		return languages;
+	}
+
+	/**
+	 * @param languages
+	 *          the languages to set
+	 * @see #languages
+	 */
+	public void setLanguages(List<String> languages) {
+		this.languages = languages;
+	}
+
+	/**
+	 * @return the remarks
+	 * @see #remarks
+	 */
+	public String getRemarks() {
+		return remarks;
+	}
+
+	/**
+	 * @param remarks
+	 *          the remarks to set
+	 * @see #remarks
+	 */
+	public void setRemarks(String remarks) {
+		this.remarks = remarks;
+	}
+
+	/**
+	 * @return the address
+	 * @see #address
+	 */
+	public String getAddress() {
+		return address;
+	}
+
+	/**
+	 * @param address
+	 *          the address to set
+	 * @see #address
+	 */
+	public void setAddress(String address) {
+		this.address = address;
+	}
+
+	/**
+	 * @return the zipCode
+	 * @see #zipCode
+	 */
+	public String getZipCode() {
+		return zipCode;
+	}
+
+	/**
+	 * @param zipCode
+	 *          the zipCode to set
+	 * @see #zipCode
+	 */
+	public void setZipCode(String zipCode) {
+		if (zipCode != null && zipCode.length() > 7) {
+			logger.warn("Surname too long. Ignoring: " + zipCode);
+			this.zipCode = "";
+		} else {
+			this.zipCode = zipCode;
+		}
+	}
+
+	/**
+	 * @return the country
+	 * @see #country
+	 */
+	public String getCountry() {
+		return country;
+	}
+
+	/**
+	 * @param country
+	 *          the country to set
+	 * @see #country
+	 */
+	public void setCountry(String country) {
+		this.country = country;
+	}
+
+	/**
+	 * @return the city
+	 * @see #city
+	 */
+	public String getCity() {
+		return city;
+	}
+
+	/**
+	 * @param city
+	 *          the city to set
+	 * @see #city
+	 */
+	public void setCity(String city) {
+		this.city = city;
+	}
+
+	/**
+	 * @return the phone1
+	 * @see #phone1
+	 */
+	public String getPhone1() {
+		return phone1;
+	}
+
+	/**
+	 * @param phone1
+	 *          the phone1 to set
+	 * @see #phone1
+	 */
+	public void setPhone1(String phone1) {
+		if (phone1 != null && phone1.length() > 20) {
+			logger.warn("Invalid phone. Ignoring: " + phone1);
+		} else {
+			this.phone1 = phone1;
+		}
+	}
+
+	/**
+	 * @return the phone2
+	 * @see #phone2
+	 */
+	public String getPhone2() {
+		return phone2;
+	}
+
+	/**
+	 * @param phone2
+	 *          the phone2 to set
+	 * @see #phone2
+	 */
+	public void setPhone2(String phone2) {
+		if (phone2 != null && phone2.length() > 20) {
+			logger.warn("Invalid phone. Ignoring: " + phone2);
+		} else {
+			this.phone2 = phone2;
+		}
+	}
+
+	/**
+	 * @return the phone3
+	 * @see #phone3
+	 */
+	public String getPhone3() {
+		return phone3;
+	}
+
+	/**
+	 * @param phone3
+	 *          the phone3 to set
+	 * @see #phone3
+	 */
+	public void setPhone3(String phone3) {
+		if (phone3 != null && phone3.length() > 20) {
+			logger.warn("Invalid phone. Ignoring: " + phone3);
+		} else {
+			this.phone3 = phone3;
+		}
+	}
+
+	/**
+	 * @return the mail
+	 * @see #mail
+	 */
+	public String getMail() {
+		return mail;
+	}
+
+	/**
+	 * @param mail
+	 *          the mail to set
+	 * @see #mail
+	 */
+	public void setMail(String mail) {
+		this.mail = mail;
+	}
+
+	/**
+	 * @return the diagnosisYear
+	 * @see #diagnosisYear
+	 */
+	public String getDiagnosisYear() {
+		return diagnosisYear;
+	}
+
+	/**
+	 * @param diagnosisYear
+	 *          the diagnosisYear to set
+	 * @see #diagnosisYear
+	 */
+	public void setDiagnosisYear(String diagnosisYear) {
+		this.diagnosisYear = diagnosisYear;
+	}
+
+	/**
+	 * @return the diagnosis
+	 * @see #diagnosis
+	 */
+	public String getDiagnosis() {
+		return diagnosis;
+	}
+
+	/**
+	 * @param diagnosis
+	 *          the diagnosis to set
+	 * @see #diagnosis
+	 */
+	public void setDiagnosis(String diagnosis) {
+		this.diagnosis = diagnosis;
+	}
+
+	/**
+	 * @return the referal
+	 * @see #referal
+	 */
+	public String getReferal() {
+		return referal;
+	}
+
+	/**
+	 * @param referal
+	 *          the referal to set
+	 * @see #referal
+	 */
+	public void setReferal(String referal) {
+		this.referal = referal;
+	}
+
+	/**
+	 * @return the mPowerId
+	 * @see #mPowerId
+	 */
+	public String getmPowerId() {
+		return mPowerId;
+	}
+
+	/**
+	 * @param mPowerId
+	 *          the mPowerId to set
+	 * @see #mPowerId
+	 */
+	public void setmPowerId(String mPowerId) {
+		this.mPowerId = mPowerId;
+	}
+
+	/**
+	 * @return the addDate
+	 * @see #addDate
+	 */
+	public String getAddDate() {
+		return addDate;
+	}
+
+	/**
+	 * @param addDate
+	 *          the addDate to set
+	 * @see #addDate
+	 */
+	public void setAddDate(String addDate) {
+		this.addDate = addDate;
+	}
+
+	/**
+	 * @return the type
+	 * @see #type
+	 */
+	public SubjectType getType() {
+		return type;
+	}
+
+	/**
+	 * @param type
+	 *          the type to set
+	 * @see #type
+	 */
+	public void setType(SubjectType type) {
+		this.type = type;
+	}
+
+	public void update(Subject subject, String errorPrefix) {
+		setName(getMergedValue("name", this.getName(), subject.getName(), errorPrefix));
+		setSurname(getMergedValue("surname", this.getSurname(), subject.getSurname(), errorPrefix));
+		setNdNumber(getMergedValue("ndNumber", this.getNdNumber(), subject.getNdNumber(), errorPrefix));
+		setScreeningNumber(getMergedValue("screeningNumber", this.getScreeningNumber(), subject.getScreeningNumber(), errorPrefix));
+		setSex(getMergedValue("sex", this.getSex(), subject.getSex(), errorPrefix));
+		setRemarks(getMergedValue("remarks", this.getRemarks(), subject.getRemarks(), errorPrefix));
+		setBirthDate(getMergedValue("birthDate", this.getBirthDate(), subject.getBirthDate(), errorPrefix));
+		setAddress(getMergedValue("address", this.getAddress(), subject.getAddress(), errorPrefix));
+		setZipCode(getMergedValue("zipCode", this.getZipCode(), subject.getZipCode(), errorPrefix));
+		setCountry(getMergedValue("country", this.getCountry(), subject.getCountry(), errorPrefix));
+		setCity(getMergedValue("city", this.getCity(), subject.getCity(), errorPrefix));
+		setPhone1(getMergedValue("phone1", this.getPhone1(), subject.getPhone1(), errorPrefix));
+		setPhone2(getMergedValue("phone2", this.getPhone2(), subject.getPhone2(), errorPrefix));
+		setPhone3(getMergedValue("phone3", this.getPhone3(), subject.getPhone3(), errorPrefix));
+		setMail(getMergedValue("mail", this.getMail(), subject.getMail(), errorPrefix));
+		setDiagnosisYear(getMergedValue("diagnosisYear", this.getDiagnosisYear(), subject.getDiagnosisYear(), errorPrefix));
+		setDiagnosis(getMergedValue("diagnosis", this.getDiagnosis(), subject.getDiagnosis(), errorPrefix));
+		setReferal(getMergedValue("referal", this.getReferal(), subject.getReferal(), errorPrefix));
+		setAddDate(getMergedValue("addDate", this.getAddDate(), subject.getAddDate(), errorPrefix));
+		setmPowerId(getMergedValue("mPowerId", this.getmPowerId(), subject.getmPowerId(), errorPrefix));
+		setType(getMergedValue("type", this.getType(), subject.getType(), errorPrefix));
+		// override only when to be seen by flying team
+		if (subject.getToBeSeenAt().equals("F")) {
+			setToBeSeenAt(subject.getToBeSeenAt());
+		}
+		addLanguages(subject.getLanguages());
+	}
+
+	private SubjectType getMergedValue(String string, SubjectType existingValue, SubjectType newValue, String errorPrefix) {
+		if (existingValue == null) {
+			return newValue;
+		} else if (newValue == null) {
+			return existingValue;
+		} else if (existingValue.equals(newValue)) {
+			return existingValue;
+		} else {
+			logger.warn(errorPrefix + "New " + string + " differs from old one. (new: " + newValue + ", " + existingValue + "). Skipping");
+			return existingValue;
+		}
+	}
+
+	private String getMergedValue(String string, String existingValue, String newValue, String errorPrefix) {
+		if (existingValue == null || existingValue.trim().isEmpty()) {
+			return newValue;
+		} else if (newValue == null || newValue.trim().isEmpty()) {
+			return existingValue;
+		} else if (existingValue.trim().equalsIgnoreCase(newValue.trim())) {
+			return existingValue;
+		} else {
+			logger.warn(errorPrefix + "New " + string + " differs from old one. (new: " + newValue + ", " + existingValue + "). Skipping");
+			return existingValue;
+		}
+	}
+
+	/**
+	 * @return the toBeSeenAt
+	 * @see #toBeSeenAt
+	 */
+	public String getToBeSeenAt() {
+		return toBeSeenAt;
+	}
+
+	/**
+	 * @param toBeSeenAt
+	 *          the toBeSeenAt to set
+	 * @see #toBeSeenAt
+	 */
+	public void setToBeSeenAt(String toBeSeenAt) {
+		this.toBeSeenAt = toBeSeenAt;
+	}
+
+	/**
+	 * @return the dead
+	 * @see #dead
+	 */
+	public boolean isDead() {
+		return dead;
+	}
+
+	/**
+	 * @param dead
+	 *          the dead to set
+	 * @see #dead
+	 */
+	public void setDead(boolean dead) {
+		this.dead = dead;
+	}
+
+	/**
+	 * @return the resigned
+	 * @see #resigned
+	 */
+	public boolean isResigned() {
+		return resigned;
+	}
+
+	/**
+	 * @param resigned
+	 *          the resigned to set
+	 * @see #resigned
+	 */
+	public void setResigned(boolean resigned) {
+		this.resigned = resigned;
+	}
+
+	public void addLanguages(List<String> langs) {
+		for (String string : langs) {
+			addLanguage(string);
+		}
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/SubjectDao.java b/appointment-import/src/main/java/smash/appointment/parse/SubjectDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..38dada1071019e5a5ea1033139c222c598b4aa77
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/SubjectDao.java
@@ -0,0 +1,87 @@
+package smash.appointment.parse;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+public class SubjectDao {
+	Logger								logger	 = Logger.getLogger(SubjectDao.class);
+	private List<Subject>	subjects = new ArrayList<>();
+
+	public void addSubject(Subject subject, String errorPrefix) {
+		if (subject.getNdNumber() != null && !subject.getNdNumber().trim().isEmpty()) {
+			Subject existing = getByNdNumber(subject.getNdNumber());
+			if (existing != null) {
+				if (!existing.getScreeningNumber().equals(subject.getScreeningNumber())) {
+					logger.error(errorPrefix + " Nd number taken by subject: " + existing.getScreeningNumber());
+					return;
+				}
+			}
+		}
+		Subject existing = getByScreeningNumber(subject.getScreeningNumber());
+		if (existing != null) {
+			existing.update(subject, errorPrefix);
+		} else {
+			subjects.add(subject);
+		}
+	}
+
+	private Subject getByScreeningNumber(String screeningNumber) {
+		for (Subject s : subjects) {
+			if (screeningNumber.equals(s.getScreeningNumber())) {
+				return s;
+			}
+		}
+		return null;
+	}
+
+	public void readFile(String filename) throws IOException {
+		try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
+			String line;
+			int lineCount = 1;
+			while ((line = br.readLine()) != null) {
+				String tmp[] = line.split("\t");
+				addSubject(new Subject(tmp[0], tmp[1], tmp[2], tmp[3]), "[" + filename + ":" + lineCount + "]");
+				lineCount++;
+			}
+		}
+	}
+
+	/**
+	 * @return the subjects
+	 * @see #subjects
+	 */
+	public List<Subject> getSubjects() {
+		return subjects;
+	}
+
+	/**
+	 * @param subjects
+	 *          the subjects to set
+	 * @see #subjects
+	 */
+	public void setSubjects(List<Subject> subjects) {
+		this.subjects = subjects;
+	}
+
+	public Subject getByNdNumber(String ndNumber) {
+		ndNumber = Utils.simplifyString(ndNumber);
+		for (Subject s : subjects) {
+			if (ndNumber.equalsIgnoreCase(s.getNdNumber())) {
+				return s;
+			}
+		}
+		return null;
+	}
+
+	public void addSubjects(List<Subject> processPrcSubjects, String errorPrefix) {
+		for (Subject subject : processPrcSubjects) {
+			addSubject(subject, errorPrefix);
+		}
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/SubjectIndexer.java b/appointment-import/src/main/java/smash/appointment/parse/SubjectIndexer.java
new file mode 100644
index 0000000000000000000000000000000000000000..b120fdc9ccb1cf77902c71b1427d8fdde344801d
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/SubjectIndexer.java
@@ -0,0 +1,25 @@
+package smash.appointment.parse;
+
+import org.apache.log4j.Logger;
+
+public abstract class SubjectIndexer {
+	Logger logger = Logger.getLogger(SubjectIndexer.class);
+
+	public abstract String getIndexedString(Subject subject);
+
+	public boolean match(Subject subject, String simplifiedQuery) {
+		String indexedString = getIndexedString(subject);
+		if (indexedString.length() == 0) {
+			return false;
+		}
+		// logger.debug("Check: " + simplifiedQuery + " against: " + indexedString);
+		if (simplifiedQuery.startsWith(indexedString)) {
+			return true;
+		}
+		return false;
+	}
+
+	public boolean isBetter(Subject subject, Subject oldSubject) {
+		return getIndexedString(subject).length() > getIndexedString(oldSubject).length();
+	}
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/SubjectParser.java b/appointment-import/src/main/java/smash/appointment/parse/SubjectParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..6292d3cb961547a986c42b59c3b0d665d2225bd9
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/SubjectParser.java
@@ -0,0 +1,242 @@
+package smash.appointment.parse;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.hssf.usermodel.HSSFDateUtil;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.Comment;
+import org.apache.poi.ss.usermodel.DataFormatter;
+import org.apache.poi.ss.usermodel.RichTextString;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+
+public abstract class SubjectParser {
+	private static final SimpleDateFormat	DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
+	Logger																logger				 = Logger.getLogger(SubjectParser.class);
+	DataFormatter													df						 = new DataFormatter();
+
+	public List<Subject> processExcel(String filename) throws EncryptedDocumentException, InvalidFormatException, IOException, ParseException {
+		List<Subject> result = new ArrayList<>();
+		InputStream inp = new FileInputStream(filename);
+		Workbook workbook = WorkbookFactory.create(inp);
+		Iterator<Sheet> sheetIter = workbook.sheetIterator();
+		while (sheetIter.hasNext()) {
+			Sheet sheet = sheetIter.next();
+			String name = sheet.getSheetName().trim();
+			if (name.trim().toLowerCase().equals(getSheetName().toLowerCase())) {
+				result.addAll(processSheet(sheet));
+			} else {
+				logger.debug(filename + "Skipping sheet: " + name);
+			}
+		}
+		return result;
+	}
+
+	private List<Subject> processSheet(Sheet sheet) {
+		List<Subject> result = new ArrayList<>();
+		int rowCount = sheet.getPhysicalNumberOfRows();
+		for (int rowId = getInitRow(); rowId < rowCount; rowId++) {
+			Row subjectRow = sheet.getRow(rowId);
+			Subject subject = parseSubject(subjectRow);
+			if (subject != null) {
+				result.add(subject);
+			}
+		}
+		return result;
+	}
+
+	private Subject parseSubject(Row row) {
+		String screeningNumber = parseScreeningNumber(row);
+		if (screeningNumber == null || screeningNumber.isEmpty()) {
+			logger.debug("Skipping row: (" + getString(row.getCell(0)) + ", " + getString(row.getCell(1)) + ")");
+			return null;
+		}
+		String name = parseName(row);
+		String surname = parseSurname(row);
+		String ndNumber = parseNdNumber(row);
+		Subject result = new Subject(name, surname, ndNumber, screeningNumber);
+
+		result.setRemarks(parseRemarks(row));
+		result.setAddress(parseAddress(row));
+		result.setZipCode(parseZipCode(row));
+		result.setCity(parseCity(row));
+		result.setCountry(parseCountry(row));
+		result.setPhone1(parsePhone1(row));
+		result.setPhone2(parsePhone2(row));
+		result.setPhone3(parsePhone3(row));
+		result.setMail(parseMail(row));
+		result.setDiagnosisYear(parseDiagnosisYear(row));
+		result.setDiagnosis(parseDiagnosis(row));
+		result.setReferal(parseReferal(row));
+		result.setAddDate(parseAddDate(row));
+		result.setmPowerId(parsemPowerId(row));
+		result.setBirthDate(parseBirthDate(row));
+		result.setType(parseType(row));
+		result.addLanguages(parseLanguages(row));
+		result.setToBeSeenAt(parseToBeSeenAt(row));
+		result.setDead(parseDead(row));
+		result.setResigned(parseResigned(row));
+
+		return result;
+	}
+
+	protected abstract boolean parseDead(Row row);
+
+	protected abstract boolean parseResigned(Row row);
+
+	protected abstract String parseToBeSeenAt(Row row);
+
+	protected abstract List<String> parseLanguages(Row row);
+
+	protected abstract SubjectType parseType(Row row);
+
+	protected abstract String parseBirthDate(Row row);
+
+	protected abstract String parsemPowerId(Row row);
+
+	protected abstract String parseAddDate(Row row);
+
+	protected abstract String parseReferal(Row row);
+
+	protected abstract String parseDiagnosisYear(Row row);
+
+	protected abstract String parseDiagnosis(Row row);
+
+	protected abstract String parseMail(Row row);
+
+	protected abstract String parsePhone3(Row row);
+
+	protected abstract String parsePhone2(Row row);
+
+	protected abstract String parsePhone1(Row row);
+
+	protected abstract String parseCity(Row row);
+
+	protected abstract String parseCountry(Row row);
+
+	protected abstract String parseZipCode(Row row);
+
+	protected abstract String parseAddress(Row row);
+
+	protected abstract String parseRemarks(Row row);
+
+	protected abstract String parseScreeningNumber(Row row);
+
+	protected abstract String parseName(Row row);
+
+	protected abstract String parseSurname(Row row);
+
+	protected abstract String parseNdNumber(Row row);
+
+	protected abstract String getSheetName();
+
+	protected abstract int getInitRow();
+
+	protected String getString(Cell cell) {
+		if (cell == null) {
+			return "";
+		}
+		if (cell.getCellTypeEnum().equals(CellType.STRING)) {
+			return cell.getStringCellValue().trim();
+		}
+		return df.formatCellValue(cell).trim();
+	}
+
+	protected String getComments(Cell cell) {
+		Comment comment = cell.getCellComment();
+		if (comment != null) {
+			RichTextString richTextString = comment.getString();
+			if (richTextString != null) {
+				return richTextString.getString();
+
+			}
+		}
+		return "";
+	}
+
+	protected String getDate(Cell cell, Calendar defaultDate) {
+		String defaultDateString = "";
+		if (defaultDate != null) {
+			defaultDateString = DATE_FORMATTER.format(defaultDate.getTime());
+		}
+		if (cell == null) {
+			return defaultDateString;
+		}
+		String result = null;
+		if (cell.getCellTypeEnum().equals(CellType.STRING)) {
+			result = getString(cell);
+		} else if (HSSFDateUtil.isCellDateFormatted(cell)) {
+			Date date = cell.getDateCellValue();
+			if (date != null) {
+				result = DATE_FORMATTER.format(date);
+			} else {
+				result = defaultDateString;
+			}
+		} else {
+			result = getString(cell);
+		}
+		if (result == null || result.trim().isEmpty()) {
+			result = defaultDateString;
+		}
+		result = fixDate(result, defaultDateString);
+		return result;
+	}
+
+	protected String fixDate(String result) {
+		return fixDate(result, null);
+	}
+
+	protected String fixDate(String result, String defaultDateString) {
+		result = result.replaceAll("\\?", "");
+		result = result.replaceAll("jan", "01");
+		result = result.replaceAll("fev", "02");
+		result = result.replaceAll("mar", "03");
+		result = result.replaceAll("avr", "04");
+		result = result.replaceAll("may", "05");
+		result = result.replaceAll("aou", "08");
+		result = result.replaceAll("cot", "08");
+		result = result.replaceAll("sep", "09");
+		result = result.replaceAll("oct", "10");
+		if (result.length() != 10) {
+			logger.warn("Invalid date. Ignoring: " + result);
+			result = defaultDateString;
+		}
+		if (result.indexOf("/") >= 0) {
+			String tmp[] = result.split("/");
+			result = tmp[2] + "-" + tmp[1] + "-" + tmp[0];
+		}
+		if (result.length() == 10 && result.charAt(2) == '-') {
+			String tmp[] = result.split("-");
+			result = tmp[2] + "-" + tmp[1] + "-" + tmp[0];
+		}
+		return result;
+	}
+
+	protected String parseDateOfBirth(Cell cell) {
+		String date = getString(cell).replaceAll(" ", "");
+		if (date.length() < 8) {
+			return "";
+		}
+		String year = date.substring(0, 4);
+		String month = date.substring(4, 6);
+		String day = date.substring(6, 8);
+		String result = year + "-" + month + "-" + day;
+		return result;
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/SubjectSqlExporter.java b/appointment-import/src/main/java/smash/appointment/parse/SubjectSqlExporter.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3056da5f1bb3d9f72a537e606544f557508480b
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/SubjectSqlExporter.java
@@ -0,0 +1,74 @@
+package smash.appointment.parse;
+
+public class SubjectSqlExporter extends SqlExporter {
+
+	public String toSql(Subject subject) {
+		StringBuilder result = new StringBuilder("");
+
+		result.append("insert into web_subject (");
+		result.append("sex, ");
+		result.append("last_name, ");
+		result.append("first_name, ");
+		result.append("comments,");
+		result.append("address,");
+		result.append("postal_code, ");
+		result.append("city, ");
+		result.append("country, ");
+		result.append("phone_number,");
+		result.append("phone_number_2,");
+		result.append("phone_number_3,");
+		result.append("email,");
+		result.append("year_of_diagnosis,");
+		result.append("diagnosis,");
+		result.append("referral,");
+		result.append("date_added,");
+		result.append("nd_number,");
+		result.append("mpower_id,");
+		result.append("screening_number,");
+		result.append("default_appointment_location,");
+		result.append("type,");
+		result.append("dead,");
+		result.append("resigned,");
+		result.append("date_born) ");
+
+		result.append("values (");
+		result.append(getStringVal(subject.getSex()) + ",");
+		result.append(getStringVal(subject.getSurname()) + ",");
+		result.append(getStringVal(subject.getName()) + ",");
+		result.append(getStringVal(subject.getRemarks()) + ",");
+		result.append(getStringVal(subject.getAddress()) + ",");
+		result.append(getStringVal(subject.getZipCode()) + ",");
+		result.append(getStringVal(subject.getCity()) + ",");
+		result.append(getStringVal(subject.getCountry()) + ",");
+		result.append(getStringVal(subject.getPhone1()) + ",");
+		result.append(getStringVal(subject.getPhone2()) + ",");
+		result.append(getStringVal(subject.getPhone3()) + ",");
+		result.append(getStringVal(subject.getMail()) + ",");
+		result.append(getIntVal(subject.getDiagnosisYear()) + ",");
+		result.append(getStringVal(subject.getDiagnosis()) + ",");
+		result.append(getStringVal(subject.getReferal()) + ",");
+		result.append(getDateVal(subject.getAddDate()) + ",");
+		result.append(getStringVal(subject.getNdNumber()) + ",");
+		result.append(getStringVal(subject.getmPowerId()) + ",");
+		result.append(getStringVal(subject.getScreeningNumber()) + ",");
+		result.append(getStringVal(subject.getToBeSeenAt()) + ",");
+		result.append(getStringVal(subject.getType().toString().substring(0, 1)) + ",");
+		result.append(subject.isDead() + ",");
+		result.append(subject.isResigned() + ",");
+		result.append(getDateVal(subject.getBirthDate()));
+		result.append(");\n");
+
+		for (String langueage : subject.getLanguages()) {
+			result.append("insert into web_subject_languages (");
+			result.append("subject_id, ");
+			result.append("language_id) ");
+			result.append("values (");
+			result.append("(select max(id) from web_subject), ");
+			result.append("(select id from web_language where name=" + getStringVal(langueage) + ") ");
+			result.append(");\n");
+		}
+
+		return result.toString();
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/SubjectType.java b/appointment-import/src/main/java/smash/appointment/parse/SubjectType.java
new file mode 100644
index 0000000000000000000000000000000000000000..a1ef995b0ccebeb8a0500126b8df6cad8f95177d
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/SubjectType.java
@@ -0,0 +1,5 @@
+package smash.appointment.parse;
+
+public enum SubjectType {
+	PATIENT, CONTROL;
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/SurnameIndexer.java b/appointment-import/src/main/java/smash/appointment/parse/SurnameIndexer.java
new file mode 100644
index 0000000000000000000000000000000000000000..10c1a29f3e39236ed33c0c819e6a30d4420de99c
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/SurnameIndexer.java
@@ -0,0 +1,9 @@
+package smash.appointment.parse;
+
+public class SurnameIndexer extends SubjectIndexer {
+
+	public String getIndexedString(Subject subject) {
+		return Utils.simplifyString(subject.getSurname());
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/SurnameNameIndexer.java b/appointment-import/src/main/java/smash/appointment/parse/SurnameNameIndexer.java
new file mode 100644
index 0000000000000000000000000000000000000000..7de6d7c4ef96e23ee9b75a72fa5d32eb1f6b5ad1
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/SurnameNameIndexer.java
@@ -0,0 +1,9 @@
+package smash.appointment.parse;
+
+public class SurnameNameIndexer extends SubjectIndexer {
+
+	public String getIndexedString(Subject subject) {
+		return Utils.simplifyString(subject.getSurname() + subject.getName());
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/Utils.java b/appointment-import/src/main/java/smash/appointment/parse/Utils.java
new file mode 100644
index 0000000000000000000000000000000000000000..916487ba6948c86609d8a4e273f5b4bed574121b
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/Utils.java
@@ -0,0 +1,7 @@
+package smash.appointment.parse;
+
+public class Utils {
+	public static String simplifyString(String query) {
+		return query.replaceAll("[\\s\\-©]", "").toLowerCase().replaceAll("\\(c\\)", "");
+	}
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/Visit.java b/appointment-import/src/main/java/smash/appointment/parse/Visit.java
new file mode 100644
index 0000000000000000000000000000000000000000..2762806afb7da798ed27ad773fbd4571d4885288
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/Visit.java
@@ -0,0 +1,109 @@
+package smash.appointment.parse;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+public class Visit {
+	static Subject UNKNOWN;
+	static {
+		UNKNOWN = new Subject("NOBODY","","","---");
+		UNKNOWN.setType(SubjectType.CONTROL);
+		UNKNOWN.setAddDate("1900-01-01");
+		UNKNOWN.setAddress("");
+		UNKNOWN.setZipCode("");
+		UNKNOWN.setCity("");
+		UNKNOWN.setCountry("");
+		UNKNOWN.setmPowerId("");
+		UNKNOWN.setReferal("");
+		UNKNOWN.setRemarks("");
+		UNKNOWN.setDiagnosis("");
+		UNKNOWN.setToBeSeenAt("P");
+	}
+	Logger												 logger				= Logger.getLogger(Visit.class);
+
+	private Subject								 subject;
+	private List<AppointmentEntry> appointments	= new ArrayList<>();
+
+	public Visit(Subject subject) {
+		if (subject==null) {
+			subject = UNKNOWN;
+		}
+		this.subject = subject;
+	}
+
+	public void addAppointment(AppointmentEntry entry) {
+		appointments.add(entry);
+	}
+
+	/**
+	 * @return the appointments
+	 * @see #appointments
+	 */
+	public List<AppointmentEntry> getAppointments() {
+		return appointments;
+	}
+
+	/**
+	 * @param appointments
+	 *          the appointments to set
+	 * @see #appointments
+	 */
+	public void setAppointments(List<AppointmentEntry> appointments) {
+		this.appointments = appointments;
+	}
+
+	public String getLastAppointmentDate() {
+		if (appointments.size() == 0) {
+			return "1900-01-01";
+		}
+		return getLastAppointment().getDay().substring(0, 10);
+	}
+
+	public AppointmentEntry getLastAppointment() {
+		if (appointments.size() > 0) {
+			return appointments.get(appointments.size() - 1);
+		} else {
+			return null;
+		}
+	}
+
+	public String getStartDate() {
+		if (appointments.size() > 0) {
+			return appointments.get(0).getDay().substring(0, 10);
+		} else {
+			return "1900-01-01";
+		}
+	}
+
+	private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
+
+	public String getEndDate() throws ParseException {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(DATE_FORMATTER.parse(getStartDate()));
+		calendar.add(Calendar.MONTH, 3);
+		return DATE_FORMATTER.format(calendar.getTime());
+	}
+
+	/**
+	 * @return the subject
+	 * @see #subject
+	 */
+	public Subject getSubject() {
+		return subject;
+	}
+
+	/**
+	 * @param subject
+	 *          the subject to set
+	 * @see #subject
+	 */
+	public void setSubject(Subject subject) {
+		this.subject = subject;
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/VisitSqlExporter.java b/appointment-import/src/main/java/smash/appointment/parse/VisitSqlExporter.java
new file mode 100644
index 0000000000000000000000000000000000000000..16077d37622b97edd5c407abebc6e4344112a3b9
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/VisitSqlExporter.java
@@ -0,0 +1,32 @@
+package smash.appointment.parse;
+
+import java.text.ParseException;
+import java.util.Calendar;
+
+public class VisitSqlExporter extends SqlExporter {
+	AppointmentSqlExporter appointmentSqlExporter = new AppointmentSqlExporter(); 
+	public String toSql(Visit visit, boolean isFinished) throws ParseException {
+		StringBuilder result = new StringBuilder("");
+
+		result.append("insert into web_visit (");
+		result.append("subject_id,	 ");
+		result.append("datetime_begin, ");
+		result.append("datetime_end, ");
+		result.append("is_finished)");
+
+		result.append("values (");
+		result.append("(SELECT id from web_subject where screening_number = "+getStringVal(visit.getSubject().getScreeningNumber()) + "),");
+		result.append(getStringVal(visit.getStartDate()) + ",");
+		result.append(getStringVal(visit.getEndDate()) + ",");
+		result.append(isFinished);
+		result.append(");\n");
+		for (AppointmentEntry entry: visit.getAppointments()) {
+			boolean entryFinished= isFinished;
+			if (isBefore(entry, Calendar.getInstance()))
+				entryFinished=true;
+			result.append(appointmentSqlExporter.toSql(entry, entryFinished)+"\n");
+		}
+
+		return result.toString();
+	}
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/XlsxCalendarProcessor.java b/appointment-import/src/main/java/smash/appointment/parse/XlsxCalendarProcessor.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c6e2a1419f1cbfc83e1eaafebedafed8553ca88
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/XlsxCalendarProcessor.java
@@ -0,0 +1,181 @@
+package smash.appointment.parse;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+
+public class XlsxCalendarProcessor {
+	Logger						 logger	= Logger.getLogger(XlsxCalendarProcessor.class);
+
+	private SubjectDao subjectDao;
+
+	public List<AppointmentEntry> processExcel(String filename, Calendar minDate) throws Exception {
+		List<AppointmentEntry> result = new ArrayList<AppointmentEntry>();
+		InputStream inp = new FileInputStream(filename);
+		Workbook workbook = WorkbookFactory.create(inp);
+		Iterator<Sheet> sheetIter = workbook.sheetIterator();
+		while (sheetIter.hasNext()) {
+			Sheet sheet = sheetIter.next();
+			String name = sheet.getSheetName().trim();
+			if (name.contains("2017")) {
+				String monthName = name.split(" ")[0];
+				String monthNumber = parseMonth(monthName);
+				result.addAll(processSheet(sheet, "2017-" + monthNumber));
+			} else {
+				logger.debug("Skipping sheet: " + name);
+			}
+		}
+		if (minDate != null) {
+			Set<AppointmentEntry> toBeRemoved = new HashSet<>();
+
+			for (AppointmentEntry entry : result) {
+				if (isBefore(entry, minDate)) {
+					toBeRemoved.add(entry);
+				}
+			}
+			result.removeAll(toBeRemoved);
+		}
+
+		return result;
+	}
+
+	private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
+
+	private boolean isBefore(AppointmentEntry entry, Calendar minDate) {
+		String entryDate = entry.getDay();
+		String beforeDate = DATE_FORMATTER.format(minDate.getTime());
+		if (entryDate.compareTo(beforeDate) < 0) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	int[]	dayColumns						 = new int[] { 3, 4, 5, 6, 7 };
+
+	int[]	weekStartRows					 = new int[] { 5, 23, 41, 59, 77, 95 };
+
+	int		hourColum							 = 0;
+	int		dayOfMonthRowOffset		 = 0;
+	int		calendarRowStartOffset = 3;
+	int		calendarRowEndOffset	 = 18;
+
+	private List<AppointmentEntry> processSheet(Sheet sheet, String string) {
+		FormulaEvaluator evaluator = sheet.getWorkbook().getCreationHelper().createFormulaEvaluator();
+
+		List<AppointmentEntry> result = new ArrayList<AppointmentEntry>();
+
+		CellParser parser = new CellParser();
+		parser.setSubjectDao(subjectDao);
+
+		for (int weekOffset : weekStartRows) {
+			Row weekRow = sheet.getRow(weekOffset + dayOfMonthRowOffset);
+			for (int dayColumnOffset : dayColumns) {
+				Cell dayCell = weekRow.getCell(dayColumnOffset);
+				String dayOfMonth = "0";
+				evaluator.evaluateInCell(dayCell);
+				if (dayCell.getCellTypeEnum().equals(CellType.NUMERIC)) {
+					dayOfMonth = ((int) dayCell.getNumericCellValue()) + "";
+				} else {
+					dayOfMonth = dayCell.getStringCellValue();
+				}
+				if (dayOfMonth.length() == 1) {
+					dayOfMonth = "0" + dayOfMonth;
+				}
+				if (!dayOfMonth.equals("00")) {
+					String day = string + "-" + dayOfMonth;
+
+					String hour = "08:00";
+					for (int hourOffset = calendarRowStartOffset; hourOffset < calendarRowEndOffset; hourOffset++) {
+						Row hourRow = sheet.getRow(weekOffset + hourOffset);
+
+						Cell hourCell = hourRow.getCell(hourColum);
+						if (hourCell.getCellTypeEnum().equals(CellType.NUMERIC)) {
+
+							SimpleDateFormat formatTime = new SimpleDateFormat("HH:mm");
+							String hourString = formatTime.format(hourCell.getDateCellValue());
+
+							if (isHour(hourString)) {
+								hour = hourString;
+							}
+						}
+
+						Cell queryCell = hourRow.getCell(dayColumnOffset);
+						if (queryCell != null) {
+							if (queryCell.getCellTypeEnum().equals(CellType.STRING)) {
+								String query = queryCell.getStringCellValue();
+
+								if (query != null && !query.isEmpty()) {
+									AppointmentEntry entry = parser.parseAppointment(query, hour);
+									entry.setDay(day);
+
+									result.add(entry);
+								}
+							} else if (!queryCell.getCellTypeEnum().equals(CellType.BLANK)) {
+								logger
+										.warn("Skipping cell: " + queryCell.getAddress() + ", " + queryCell.getSheet().getSheetName() + ", type: " + queryCell.getCellTypeEnum());
+							}
+						}
+					}
+				}
+			}
+
+		}
+		return result;
+	}
+
+	Pattern timePattern = Pattern.compile("^[0-9][0-9]\\:[0-9][0-9]");
+
+	private boolean isHour(String hourString) {
+		Matcher matcher = timePattern.matcher(hourString);
+		return matcher.find();
+	}
+
+	private String parseMonth(String monthName) throws ParseException {
+		Date date = new SimpleDateFormat("MMMM", Locale.ENGLISH).parse(monthName);
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(date);
+		String result = (cal.get(Calendar.MONTH) + 1) + "";
+		if (result.length() == 1) {
+			result = "0" + result;
+		}
+		return result;
+	}
+
+	/**
+	 * @return the subjectDao
+	 * @see #subjectDao
+	 */
+	public SubjectDao getSubjectDao() {
+		return subjectDao;
+	}
+
+	/**
+	 * @param subjectDao
+	 *          the subjectDao to set
+	 * @see #subjectDao
+	 */
+	public void setSubjectDao(SubjectDao subjectDao) {
+		this.subjectDao = subjectDao;
+	}
+}
diff --git a/appointment-import/src/main/resources/log4j.properties b/appointment-import/src/main/resources/log4j.properties
new file mode 100644
index 0000000000000000000000000000000000000000..c3a78a9276a81dd3ed72637dae8f44705769a156
--- /dev/null
+++ b/appointment-import/src/main/resources/log4j.properties
@@ -0,0 +1,8 @@
+#Set root logger 's level and its appender to an appender called CONSOLE which is defined below.
+log4j.rootLogger=fatal, CONSOLE
+
+#Set the behavior of the CONSOLE appender 
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+log4j.appender.CONSOLE.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n
+#log4j.appender.CONSOLE.layout.ConversionPattern=%m%n
diff --git a/appointment-import/src/test/java/smash/appointment/parse/AllTests.java b/appointment-import/src/test/java/smash/appointment/parse/AllTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d18d8096f02dd342ce2227684342276b60de532
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/AllTests.java
@@ -0,0 +1,25 @@
+package smash.appointment.parse;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({ AppointmentDaoTest.class, //
+		CellParserTest.class, //
+		LihControlMappingParserTest.class, //
+		LihControlParserTest.class, //
+		PrcControlParserTest.class, //
+		PrcFlyingParserTest.class, //
+		PrcSubjectsParserTest.class, //
+		RedcapParserTest.class, //
+
+		SubjectDaoTest.class, //
+		SubjectParserTest.class, //
+		VisitTest.class, //
+		XlsxCalendarProcessorTest.class, //
+})
+
+public class AllTests {
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/AppointmentDaoTest.java b/appointment-import/src/test/java/smash/appointment/parse/AppointmentDaoTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..82dba7faaf64b77c6ffda2e6d87bcca7244ce7fe
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/AppointmentDaoTest.java
@@ -0,0 +1,69 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.*;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class AppointmentDaoTest {
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp() throws Exception {
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void getVisits() {
+		Subject subject1 = new Subject("a", "b", "c", "d");
+		AppointmentEntry appointment = new AppointmentEntry();
+		appointment.setSubject(subject1);
+		appointment.setDay("2016-02-02");
+		appointment.addType(AppointmentType.LEVEL_A);
+		
+		AppointmentEntry appointment2 = new AppointmentEntry();
+		appointment2.setSubject(subject1);
+		appointment2.setDay("2016-02-02");
+		appointment2.addType(AppointmentType.LEVEL_B);
+		
+		AppointmentEntry appointment3 = new AppointmentEntry();
+		appointment3.setSubject(subject1);
+		appointment3.setDay("2011-02-02");
+		appointment3.addType(AppointmentType.LEVEL_A);
+		
+		AppointmentDao appointmentDao = new AppointmentDao();
+		appointmentDao.addAppointment(appointment);
+		appointmentDao.addAppointment(appointment2);
+		appointmentDao.addAppointment(appointment3);
+		assertEquals(2, appointmentDao.getVisits().size());
+	}
+
+	@Test
+	public void getVisits2() {
+		Subject subject1 = new Subject("a", "b", "c", "d");
+		Subject subject2 = new Subject("a1", "b1", "c1", "d1");
+		AppointmentEntry appointment = new AppointmentEntry();
+		appointment.setSubject(subject1);
+		appointment.setDay("2016-02-02");
+		appointment.addType(AppointmentType.LEVEL_A);
+		
+		AppointmentEntry appointment3 = new AppointmentEntry();
+		appointment3.setSubject(subject2);
+		appointment3.setDay("2016-02-02");
+		appointment3.addType(AppointmentType.LEVEL_B);
+		
+		AppointmentDao appointmentDao = new AppointmentDao();
+		appointmentDao.addAppointment(appointment);
+		appointmentDao.addAppointment(appointment3);
+		assertEquals(2, appointmentDao.getVisits().size());
+	}
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/CellParseTestCase.java b/appointment-import/src/test/java/smash/appointment/parse/CellParseTestCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..a43dcbc578952867a0cd36fcbd74d1284fbc3466
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/CellParseTestCase.java
@@ -0,0 +1,15 @@
+package smash.appointment.parse;
+
+class CellParseTestCase {
+	String						query;
+	Subject						subject;
+	String						time;
+	AppointmentType[]	types;
+
+	public CellParseTestCase(String query, Subject subject, String time, AppointmentType[] types) {
+		this.query = query;
+		this.subject = subject;
+		this.time = time;
+		this.types = types;
+	}
+};
diff --git a/appointment-import/src/test/java/smash/appointment/parse/CellParserTest.java b/appointment-import/src/test/java/smash/appointment/parse/CellParserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..56ae493787677dad7bf08861061720b5302a161e
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/CellParserTest.java
@@ -0,0 +1,147 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CellParserTest extends TestBase {
+	Logger									logger = Logger.getLogger(CellParserTest.class);
+
+	List<CellParseTestCase>	testCases;
+	CellParser							parser;
+
+	@Before
+	public void setUp() {
+		super.setUp();
+
+		parser = new CellParser();
+		parser.setSubjectDao(subjectDao);
+
+		testCases = new ArrayList<CellParseTestCase>();
+
+		testCases.add(new CellParseTestCase("Piotr Gawron level A FU V3", piotrGawron, null, new AppointmentType[] { AppointmentType.LEVEL_A }));
+		testCases.add(new CellParseTestCase("09:00 Jan Kowalski-Nowak level A", janKowalskiNowak, "09:00", new AppointmentType[] { AppointmentType.LEVEL_A }));
+		testCases.add(
+				new CellParseTestCase(
+						"ND0002 l664574645 (sms)evel BV ©  + SB ©", janKowalskiNowak, null, new AppointmentType[] { AppointmentType.LEVEL_BV, AppointmentType.LEVEL_SB }));
+		testCases.add(new CellParseTestCase("ND0001 654654631 level B ©", piotrGawron, null, new AppointmentType[] { AppointmentType.LEVEL_B }));
+		testCases.add(
+				new CellParseTestCase(
+						"John Doe BV + BG + SB", johnDoe, null, new AppointmentType[] { AppointmentType.LEVEL_BV, AppointmentType.LEVEL_BG, AppointmentType.LEVEL_SB }));
+		testCases.add(new CellParseTestCase("Kowalski-Nowak m-Power", janKowalskiNowak, null, new AppointmentType[] { AppointmentType.LEVEL_B_M_POWER }));
+		testCases.add(new CellParseTestCase("ND0004 Name BV ©", cateKowalsky, null, new AppointmentType[] { AppointmentType.LEVEL_BV }));
+		testCases.add(
+				new CellParseTestCase("ND0004 level BV (c) + SB ©", cateKowalsky, null, new AppointmentType[] { AppointmentType.LEVEL_BV, AppointmentType.LEVEL_SB }));
+		testCases.add(
+				new CellParseTestCase(
+						"Cate Kowalsky level BV + BG + SB + m-Power", cateKowalsky, null,
+						new AppointmentType[] { AppointmentType.LEVEL_BV, AppointmentType.LEVEL_BG, AppointmentType.LEVEL_SB, AppointmentType.LEVEL_B_M_POWER }));
+		testCases.add(new CellParseTestCase("sb name level A", null, null, new AppointmentType[] { AppointmentType.LEVEL_A }));
+		testCases.add(new CellParseTestCase("Andrew Dude level A FU V3", andrewDude, null, new AppointmentType[] { AppointmentType.LEVEL_A }));
+		testCases.add(
+				new CellParseTestCase(
+						"Gawron Piotr BV + BG + neuro level A (FU)", piotrGawron, null,
+						new AppointmentType[] { AppointmentType.LEVEL_A, AppointmentType.LEVEL_BV, AppointmentType.LEVEL_BG }));
+
+		testCases.add(
+				new CellParseTestCase(
+						"Gawron Piotr level B © + level A neuro  ©", piotrGawron, null, new AppointmentType[] { AppointmentType.LEVEL_A, AppointmentType.LEVEL_B }));
+
+		testCases.add(
+				new CellParseTestCase(
+						"Gawron Piotr level B ©  + BV © + SB ©", piotrGawron, null,
+						new AppointmentType[] { AppointmentType.LEVEL_B, AppointmentType.LEVEL_BV, AppointmentType.LEVEL_SB }));
+
+		testCases.add(
+				new CellParseTestCase(
+						"Gawron Piotr level B + BV + BG", piotrGawron, null,
+						new AppointmentType[] { AppointmentType.LEVEL_B, AppointmentType.LEVEL_BV, AppointmentType.LEVEL_BG }));
+
+		testCases.add(
+				new CellParseTestCase("Gawron Piotr level B + BG", piotrGawron, null, new AppointmentType[] { AppointmentType.LEVEL_B, AppointmentType.LEVEL_BG }));
+		testCases.add(
+				new CellParseTestCase(
+						"Gawron Piotr level B © + BV  © + SB ©", piotrGawron, null,
+						new AppointmentType[] { AppointmentType.LEVEL_B, AppointmentType.LEVEL_BV, AppointmentType.LEVEL_SB }));
+		testCases.add(
+				new CellParseTestCase(
+						"Gawron Piotr level BG + SB + M-Power", piotrGawron, null,
+						new AppointmentType[] { AppointmentType.LEVEL_BG, AppointmentType.LEVEL_SB, AppointmentType.LEVEL_B_M_POWER }));
+
+		testCases.add(
+				new CellParseTestCase("Gawron Piotr level B + BV", piotrGawron, null, new AppointmentType[] { AppointmentType.LEVEL_B, AppointmentType.LEVEL_BV }));
+		testCases.add(
+				new CellParseTestCase(
+						"Gawron Piotr level BG + SB + M-Power", piotrGawron, null,
+						new AppointmentType[] { AppointmentType.LEVEL_BG, AppointmentType.LEVEL_SB, AppointmentType.LEVEL_B_M_POWER }));
+
+	}
+
+	@Test
+	public void testExtractTime() {
+		for (CellParseTestCase testCase : testCases) {
+			String result = parser.extractTime(testCase.query);
+			assertEquals("Invalid time parsed from query: " + testCase.query, testCase.time, result);
+		}
+	}
+
+	@Test
+	public void testRemoveTime() {
+		for (CellParseTestCase testCase : testCases) {
+			String result = parser.removeTime(testCase.query);
+			if (testCase.time == null) {
+				assertEquals("query after removing time should be the same for query: " + testCase.query, testCase.query, result);
+			} else {
+				assertFalse(testCase.query.equals(result));
+			}
+		}
+	}
+
+	@Test
+	public void testRemoveTime2() {
+		String result = parser.removeTime("09:00 John Doe level B");
+		assertEquals("John Doe level B", result);
+	}
+
+	@Test
+	public void testExtractAppointment() {
+		String defaultTime = "23:55";
+		for (CellParseTestCase testCase : testCases) {
+			AppointmentEntry appointment = parser.parseAppointment(testCase.query, defaultTime);
+			if (testCase.time != null) {
+				assertEquals("Invalid time parsed from query: " + testCase.query, testCase.time, appointment.getTime());
+			} else {
+				assertEquals("Invalid time parsed from query (default value expected): " + testCase.query, defaultTime, appointment.getTime());
+			}
+			assertEquals("Invalid subject parsed from query: " + testCase.query, testCase.subject, appointment.getSubject());
+			assertTrue(
+					"Invalid type parsed from query: " + testCase.query + " expected: " + Arrays.asList(testCase.types) + "; found: " + appointment.getTypes(),
+					equalTypes(Arrays.asList(testCase.types), appointment.getTypes()));
+		}
+	}
+
+	private boolean equalTypes(List<AppointmentType> types, Collection<AppointmentType> types2) {
+		for (AppointmentType type : types) {
+			if (!types2.contains(type)) {
+				return false;
+			}
+		}
+		for (AppointmentType type : types2) {
+			if (!types.contains(type)) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/LihControlMappingParserTest.java b/appointment-import/src/test/java/smash/appointment/parse/LihControlMappingParserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b281fe89d51262e0f562e36a481c3124b0567d7c
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/LihControlMappingParserTest.java
@@ -0,0 +1,80 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LihControlMappingParserTest extends TestBase {
+	Logger						logger		= Logger.getLogger(LihControlMappingParserTest.class);
+
+	LihControlMappingParser processor	= new LihControlMappingParser();
+
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp()  {
+		super.setUp();
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	private static final SimpleDateFormat	DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
+
+	@Test
+	public void test() throws Exception {
+		List<Subject> entries = processor.processExcel("testFiles/lihControlMappingExample.xlsx");
+		assertTrue(entries.size() > 0);
+
+		Subject subject = entries.get(0);
+		assertEquals("L-001", subject.getScreeningNumber());
+		assertEquals("Piotrek", subject.getName());
+		assertEquals("Gawron", subject.getSurname());
+		assertEquals("", subject.getAddress());
+		assertEquals("", subject.getZipCode());
+		assertEquals("", subject.getCity());
+		assertEquals("", subject.getCountry());
+		assertEquals("343", subject.getPhone1());
+		assertEquals("", subject.getPhone2());
+		assertEquals("", subject.getPhone3());
+		assertEquals("", subject.getMail());
+		assertEquals("", subject.getDiagnosisYear());
+		assertEquals("", subject.getDiagnosis());
+		assertEquals("", subject.getReferal());
+		assertEquals("2015-08-03", subject.getAddDate());
+		assertEquals("ND3333", subject.getNdNumber());
+		assertEquals("", subject.getBirthDate());
+		
+		for (Subject s:entries) {
+			DATE_FORMATTER.parse(s.getAddDate());
+		}
+	}
+
+
+	@Test
+	public void testFixDate() throws Exception {
+		DATE_FORMATTER.parse(processor.fixDate("2016-cot-31"));
+		Date d = DATE_FORMATTER.parse(processor.fixDate("25-11-1951"));
+		assertEquals("1951-11-25",DATE_FORMATTER.format(d));
+		DATE_FORMATTER.parse(processor.fixDate("1957??-10-25"));
+		DATE_FORMATTER.parse(processor.fixDate("2016?-sep-12"));
+		DATE_FORMATTER.parse(processor.fixDate("2016-aou-26"));
+		DATE_FORMATTER.parse(processor.fixDate("2016-avr-14"));
+		DATE_FORMATTER.parse(processor.fixDate("2016-fev-03"));
+		DATE_FORMATTER.parse(processor.fixDate("-","2016-01-01"));
+	}
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/LihControlParserTest.java b/appointment-import/src/test/java/smash/appointment/parse/LihControlParserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..bce5ee61b716e85c661cbae9db0ff0c792362469
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/LihControlParserTest.java
@@ -0,0 +1,68 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LihControlParserTest extends TestBase {
+	Logger						logger		= Logger.getLogger(LihControlParserTest.class);
+
+	LihControlParser processor	= new LihControlParser();
+
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp()  {
+		super.setUp();
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void testParseLang() throws Exception {
+		assertEquals("English",processor.getMappedLanguage("EN"));
+	}
+	@Test
+	public void test() throws Exception {
+		List<Subject> entries = processor.processExcel("testFiles/lihControlExample.xlsx");
+		assertTrue(entries.size() > 0);
+
+		Subject subject = entries.get(0);
+		assertEquals("L-001", subject.getScreeningNumber());
+		assertEquals("Name", subject.getName());
+		assertEquals("Surname", subject.getSurname());
+		assertTrue(subject.getRemarks().contains("001 rdv 01/09/2015 9h jyf"));
+		assertTrue(subject.getRemarks().contains("PD family relation=pd info"));		
+		assertEquals("11, Rue blabla", subject.getAddress());
+		assertEquals("L-3322", subject.getZipCode());
+		assertEquals("Luxembourg", subject.getCity());
+		assertEquals("", subject.getCountry());
+		assertEquals("123456789", subject.getPhone1());
+		assertEquals("321654", subject.getPhone2());
+		assertEquals("", subject.getPhone3());
+		assertEquals("email@pt.lu", subject.getMail());
+		assertEquals("", subject.getDiagnosisYear());
+		assertEquals("", subject.getDiagnosis());
+		assertEquals("", subject.getReferal());
+		assertNotNull(subject.getAddDate());
+		assertEquals("", subject.getNdNumber());
+		assertEquals("1937-01-03", subject.getBirthDate());
+		assertTrue(subject.getRemarks().contains("some other remark"));		
+		assertTrue(subject.getRemarks().contains("at home: NMS + RFQ 1 + RFQ 2 + REM + PDSS: manque une page ds RFQ => Linda pr level b 09/09/15"));		
+		assertTrue(subject.getLanguages().contains("French"));		
+		assertTrue(subject.getLanguages().contains("German"));		
+	}
+
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/PrcControlParserTest.java b/appointment-import/src/test/java/smash/appointment/parse/PrcControlParserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9558de7cbac2412517d9969e30dea5f5617aa457
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/PrcControlParserTest.java
@@ -0,0 +1,59 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PrcControlParserTest extends TestBase {
+	Logger						logger		= Logger.getLogger(PrcSubjectsParserTest.class);
+
+	PrcControlParser processor	= new PrcControlParser();
+
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp()  {
+		super.setUp();
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void test() throws Exception {
+		List<Subject> entries = processor.processExcel("testFiles/prcControlesExample.xlsx");
+		assertTrue(entries.size() > 0);
+
+		Subject subject = entries.get(0);
+		assertEquals("P-333", subject.getScreeningNumber());
+		assertEquals("CCC", subject.getName());
+		assertEquals("BBB", subject.getSurname());
+		assertTrue(subject.getRemarks().contains("note"));
+		assertEquals("DDD", subject.getAddress());
+		assertEquals("L-2423", subject.getZipCode());
+		assertEquals("Luxembourg", subject.getCity());
+		assertEquals("", subject.getCountry());
+		assertEquals("123", subject.getPhone1());
+		assertEquals("456", subject.getPhone2());
+		assertEquals("789", subject.getPhone3());
+		assertEquals("mail@mail.lu", subject.getMail());
+		assertEquals("", subject.getDiagnosisYear());
+		assertEquals("", subject.getDiagnosis());
+		assertEquals("", subject.getReferal());
+		assertEquals("2016-03-29", subject.getAddDate());
+		assertEquals("ND3333", subject.getNdNumber());
+		assertEquals("", subject.getBirthDate());
+	}
+
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/PrcFlyingParserTest.java b/appointment-import/src/test/java/smash/appointment/parse/PrcFlyingParserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ba4224e9e2496171b9f16502c33128f3384021fb
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/PrcFlyingParserTest.java
@@ -0,0 +1,60 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PrcFlyingParserTest extends TestBase {
+	Logger						logger		= Logger.getLogger(PrcSubjectsParserTest.class);
+
+	PrcFlyingParser processor	= new PrcFlyingParser();
+
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp()  {
+		super.setUp();
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void test() throws Exception {
+		List<Subject> entries = processor.processExcel("testFiles/prcFlyingTeam.xlsx");
+		assertTrue(entries.size() > 0);
+
+		Subject subject = entries.get(0);
+		assertEquals("P-222", subject.getScreeningNumber());
+		assertEquals("DOE John", subject.getSurname());
+		assertEquals("", subject.getName());
+		assertTrue(subject.getRemarks().contains("notes"));
+		assertEquals("8, rue des Nowhere", subject.getAddress());
+		assertEquals("F-57100", subject.getZipCode());
+		assertEquals("Paris", subject.getCity());
+		assertEquals("France", subject.getCountry());
+		assertEquals("001234325435", subject.getPhone1());
+		assertEquals("666", subject.getPhone2());
+		assertEquals("777", subject.getPhone3());
+		assertEquals("mail@address.lu", subject.getMail());
+		assertEquals("1998", subject.getDiagnosisYear());
+		assertEquals("BLA", subject.getDiagnosis());
+		assertEquals("DR", subject.getReferal());
+		assertEquals("2016-07-06", subject.getAddDate());
+		assertTrue(subject.getRemarks().contains("Questionnaires OK"));
+		assertEquals("ND2222", subject.getNdNumber());
+		assertEquals("1945-01-02", subject.getBirthDate());
+	}
+
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/PrcSubjectsParserTest.java b/appointment-import/src/test/java/smash/appointment/parse/PrcSubjectsParserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5281b382d80ca1ab4355f6bbc8cf22b031e7669
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/PrcSubjectsParserTest.java
@@ -0,0 +1,80 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PrcSubjectsParserTest extends TestBase {
+	Logger						logger		= Logger.getLogger(PrcSubjectsParserTest.class);
+
+	PrcSubjectsParser	processor	= new PrcSubjectsParser();
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp() {
+		super.setUp();
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void test() throws Exception {
+		List<Subject> entries = processor.processExcel("testFiles/prcSubjectsExample.xlsx");
+		assertTrue(entries.size() > 0);
+
+		Subject subject = entries.get(0);
+		assertEquals("P-111", subject.getScreeningNumber());
+		assertEquals("abcdef", subject.getSurname());
+		assertEquals("Piotr", subject.getName());
+		assertTrue(subject.getRemarks().contains("this is remark"));
+		assertEquals("Jaskolki, 6", subject.getAddress());
+		assertEquals("D-66636", subject.getZipCode());
+		assertEquals("Trier", subject.getCity());
+		assertEquals("Deutschland", subject.getCountry());
+		assertEquals("0049 12 34 556 76", subject.getPhone1());
+		assertEquals("tel2", subject.getPhone2());
+		assertEquals("tel3", subject.getPhone3());
+		assertEquals("piotr.cos@uni.lu", subject.getMail());
+		assertEquals("1999", subject.getDiagnosisYear());
+		assertEquals("unknown", subject.getDiagnosis());
+		assertEquals("PG", subject.getReferal());
+		assertEquals("2015-10-14", subject.getAddDate());
+		assertTrue(subject.getRemarks().contains("Questionnaires OK"));
+		assertEquals("ND1111", subject.getNdNumber());
+		assertEquals("m_id", subject.getmPowerId());
+		assertEquals("1972-01-02", subject.getBirthDate());
+		assertFalse(subject.isDead());
+		assertFalse(subject.isResigned());
+
+		subject = entries.get(1);
+		assertTrue(subject.isDead());
+		assertTrue(subject.isResigned());
+		
+		subject = entries.get(2);
+		assertFalse(subject.isDead());
+		assertFalse(subject.isResigned());
+
+		subject = entries.get(3);
+		assertFalse(subject.isDead());
+		assertFalse(subject.isResigned());
+
+		subject = entries.get(4);
+		assertFalse(subject.isDead());
+		assertTrue(subject.isResigned());
+
+	}
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/RedcapParserTest.java b/appointment-import/src/test/java/smash/appointment/parse/RedcapParserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e912829f7d1345aeedb3b5aa079e1d35c0eff59b
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/RedcapParserTest.java
@@ -0,0 +1,74 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.*;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RedcapParserTest extends TestBase{
+	Logger								logger		= Logger.getLogger(RedcapParserTest.class);
+	RedcapParser parser = new RedcapParser();
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp() {
+		super.setUp();
+		parser.setSubjectDao(subjectDao);
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void testParseFile() throws FileNotFoundException, IOException {
+		List<AppointmentEntry> entries = parser.parse("testFiles/redcap_imp.txt");
+		assertTrue(entries.size() > 0);
+		assertEquals(3, subjectDao.getByNdNumber("ND0001").getLanguages().size());
+
+		int levelACount = 0;
+		int levelBCount = 0;
+		int levelBVCount = 0;
+		int levelBGCount = 0;
+		int levelSBCount = 0;
+		int levelMPowerCount = 0;
+		for (AppointmentEntry appointmentEntry : entries) {
+			if (appointmentEntry.getTypes().contains(AppointmentType.LEVEL_A)) {
+				levelACount++;
+			}
+			if (appointmentEntry.getTypes().contains(AppointmentType.LEVEL_B)) {
+				levelBCount++;
+			}
+			if (appointmentEntry.getTypes().contains(AppointmentType.LEVEL_BG)) {
+				levelBGCount++;
+			}
+			if (appointmentEntry.getTypes().contains(AppointmentType.LEVEL_BV)) {
+				levelBVCount++;
+			}
+			if (appointmentEntry.getTypes().contains(AppointmentType.LEVEL_SB)) {
+				levelSBCount++;
+			}
+			if (appointmentEntry.getTypes().contains(AppointmentType.LEVEL_B_M_POWER)) {
+				levelMPowerCount++;
+			}
+		}
+		assertTrue(levelACount > 0);
+		assertTrue(levelBCount > 0);
+		assertTrue(levelBVCount > 0);
+		assertTrue(levelBGCount > 0);
+		assertTrue(levelSBCount > 0);
+		assertTrue(levelMPowerCount > 0);
+		
+	}
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/SubjectDaoTest.java b/appointment-import/src/test/java/smash/appointment/parse/SubjectDaoTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..66fc18c54fc04749163cbd38d5a4cbc0c29a5e9f
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/SubjectDaoTest.java
@@ -0,0 +1,32 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SubjectDaoTest {
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp() throws Exception {
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void testReadFile() throws Exception {
+		SubjectDao subjectDao = new SubjectDao();
+		subjectDao.readFile("testFiles/subjects.txt");
+		assertEquals(2, subjectDao.getSubjects().size());
+		assertEquals("Piotr", subjectDao.getSubjects().get(0).getName());
+	}
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/SubjectParserTest.java b/appointment-import/src/test/java/smash/appointment/parse/SubjectParserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a9efafaf25f0c43f333bd4afa97c370dddc5c4ce
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/SubjectParserTest.java
@@ -0,0 +1,32 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.*;
+
+import java.util.Calendar;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SubjectParserTest {
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp() throws Exception {
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void testParseDate() {
+		SubjectParser parser = new PrcControlParser();
+		assertEquals("", parser.getDate(null, null));
+	}
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/TestBase.java b/appointment-import/src/test/java/smash/appointment/parse/TestBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..b9663f46e9e6dfe9a395fecd0166b97de4ef75e0
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/TestBase.java
@@ -0,0 +1,25 @@
+package smash.appointment.parse;
+
+public class TestBase {
+	SubjectDao subjectDao;
+
+	Subject		 piotrGawron			= new Subject("Piotr", "Gawron", "ND0001", "1");
+	Subject		 janKowalskiNowak	= new Subject("Jan", "Kowalski-Nowak", "ND0002", "2");
+	Subject		 johnDoe					= new Subject("John", "Doe", "ND0003", "3");
+	Subject		 cateKowalsky			= new Subject("Cate", "Kowalsky", "ND0004", "4");
+	Subject		 andrewDude				= new Subject(" Andrew ", " Dude ", "ND0005", "5");
+	Subject		 unknownDude			= new Subject(" Unknownnnnnnnn ", " Dude ", "ND0006", "6");
+
+	public void setUp() {
+		subjectDao = new SubjectDao();
+
+		subjectDao.addSubject(new Subject("Piotr Marcin", "Gawron", "ND1005", "1005"), null);
+		subjectDao.addSubject(piotrGawron, null);
+		subjectDao.addSubject(janKowalskiNowak, null);
+		subjectDao.addSubject(johnDoe, null);
+		subjectDao.addSubject(cateKowalsky, null);
+		subjectDao.addSubject(andrewDude, null);
+		subjectDao.addSubject(unknownDude, null);
+	}
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/VisitTest.java b/appointment-import/src/test/java/smash/appointment/parse/VisitTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..fd3eb2d963d5699e12d1abc642d67070226e9a3a
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/VisitTest.java
@@ -0,0 +1,48 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.*;
+
+import java.text.ParseException;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class VisitTest {
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp() throws Exception {
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void testGetStartDate() {
+		Subject subject = new Subject("!", "", "4", "5");
+		Visit visit = new Visit(subject);
+		AppointmentEntry entry = new AppointmentEntry();
+		entry.setSubject(subject);
+		entry.setDay("2015-02-01 8:00");
+		visit.addAppointment(entry);
+		assertEquals("2015-02-01", visit.getStartDate());
+	}
+
+	@Test
+	public void testGetEndDate() throws ParseException {
+		Subject subject = new Subject("!", "", "4", "5");
+		Visit visit = new Visit(subject);
+		AppointmentEntry entry = new AppointmentEntry();
+		entry.setSubject(subject);
+		entry.setDay("2015-02-01 8:00");
+		visit.addAppointment(entry);
+		assertEquals("2015-05-01", visit.getEndDate());
+	}
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/XlsxCalendarProcessorTest.java b/appointment-import/src/test/java/smash/appointment/parse/XlsxCalendarProcessorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e23b0a920bfd4ef3d4f0c218a767e502bae523b
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/XlsxCalendarProcessorTest.java
@@ -0,0 +1,41 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.FileNotFoundException;
+import java.util.Calendar;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class XlsxCalendarProcessorTest extends TestBase {
+	Logger								logger		= Logger.getLogger(XlsxCalendarProcessorTest.class);
+
+	XlsxCalendarProcessor	processor	= new XlsxCalendarProcessor();
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp() {
+		super.setUp();
+		processor.setSubjectDao(subjectDao);
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void testReadExcel() throws Exception {
+		List<AppointmentEntry> entries = processor.processExcel("testFiles/calendarExample.xlsx", Calendar.getInstance());
+		assertTrue(entries.size() > 0);
+	}
+
+}
diff --git a/appointment-import/src/test/resources/log4j.properties b/appointment-import/src/test/resources/log4j.properties
new file mode 100644
index 0000000000000000000000000000000000000000..e108a78cf8570a6f5bfc3e00dfaee3c45a1e829e
--- /dev/null
+++ b/appointment-import/src/test/resources/log4j.properties
@@ -0,0 +1,8 @@
+#Set root logger 's level and its appender to an appender called CONSOLE which is defined below.
+log4j.rootLogger=debug, CONSOLE
+
+#Set the behavior of the CONSOLE appender 
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+log4j.appender.CONSOLE.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n
+#log4j.appender.CONSOLE.layout.ConversionPattern=%m%n
diff --git a/appointment-import/testFiles/calendarExample.xlsx b/appointment-import/testFiles/calendarExample.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..b8a0e42b59245f10692d9739ade2a0e8e4cc8b03
Binary files /dev/null and b/appointment-import/testFiles/calendarExample.xlsx differ
diff --git a/appointment-import/testFiles/lihControlExample.xlsx b/appointment-import/testFiles/lihControlExample.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..526e749bdf8cb818d13e98b763afb22b3ec3b071
Binary files /dev/null and b/appointment-import/testFiles/lihControlExample.xlsx differ
diff --git a/appointment-import/testFiles/lihControlMappingExample.xlsx b/appointment-import/testFiles/lihControlMappingExample.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..4d54795013bdb361399cc256f0c51ae913af2cf5
Binary files /dev/null and b/appointment-import/testFiles/lihControlMappingExample.xlsx differ
diff --git a/appointment-import/testFiles/prcControlesExample.xlsx b/appointment-import/testFiles/prcControlesExample.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..2277978b4984e2871171b48d30d06cc32d9ee31e
Binary files /dev/null and b/appointment-import/testFiles/prcControlesExample.xlsx differ
diff --git a/appointment-import/testFiles/prcFlyingTeam.xlsx b/appointment-import/testFiles/prcFlyingTeam.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..56646dc463b43ec98bbf36b20c83cade9069ac44
Binary files /dev/null and b/appointment-import/testFiles/prcFlyingTeam.xlsx differ
diff --git a/appointment-import/testFiles/prcSubjectsExample.xlsx b/appointment-import/testFiles/prcSubjectsExample.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..b728be7a0083e5574a6fc513934de0535fb19065
Binary files /dev/null and b/appointment-import/testFiles/prcSubjectsExample.xlsx differ
diff --git a/appointment-import/testFiles/redcap_imp.txt b/appointment-import/testFiles/redcap_imp.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8065e5c6c17ca5d0d5803ba08831feb0798a0406
--- /dev/null
+++ b/appointment-import/testFiles/redcap_imp.txt
@@ -0,0 +1,8 @@
+Subject ID	Event Name	Date/Time of Birth	Sex	Language: most fluent	Language: second most fluent	Language: third most fluent	Language: fourth most fluent	Basic assessment (Level A)	Basic assessment: start date	Basic assessment: end date	Basic assessment: number of visits	Detailed neuropsychological assessment (Level B)	Detailed neuropsychological assessment: start date	Detailed neuropsychological assessment: end date	Detailed neuropsychological assessment: number of visits	Detailed motor assessment (Level B)	Detailed motor assessment: start date	Detailed motor assessment: end date	Detailed motor assessment: number of visits	Detailed sensory assessment (Level B)	Detailed sensory assessment: start date	Detailed sensory assessment: end date	Detailed sensory assessment: number of visits	PSP (Level B)	PSP: start date	PSP: end date	PSP: number of visits	Blood	Blood: date	Urine	Urine: date	CSF	CSF: date	Imaging	Imaging: date	Stool	Stool: collection acceptance by patient	Stool: collection date proposed	Stool: date collected	Skin biopsy	Skin biopsy: date	Saliva	Saliva: date	mPower	mPower: date
+ND0001	Visit 1	1955-10-24 10:44	M	Polish	German	French		Yes	2015-01-04 10:14	2015-03-04 14:18	1																	Yes	2015-03-04	Yes	2015-03-04									Yes	2016-10-20	Yes	2015-03-04		2015-03-11
+ND0001	Visit 2							Yes	2016-12-12 09:34	2017-01-24 10:46	2																																		
+ND0002	Visit 1	1956-05-22 14:53	M	Polish				Yes	2015-03-04 14:56	2015-03-04 17:43	1	Yes	2015-07-29	2015-07-29	1	No				No				No				Yes	2015-05-13	Yes	2015-05-13	No		No		No				No		Yes	2015-05-13		
+ND0002	Visit 2							Yes	2016-01-18 10:00	2016-01-18 13:10	1									Yes	2016-05-26	2016-05-26	1					Yes	2016-01-18	Yes	2016-01-18					Yes	Yes	2016-05-26	2016-05-26	Yes	2016-05-26	Yes	2016-01-18		
+ND0003	Visit 1	1957-07-15 09:21	F	Polish	German	Rusian	English	Yes	2015-03-11 09:25	2015-03-11 14:58	1	Yes	2015-11-12	2015-11-12	1													Yes	2015-03-11	Yes	2015-03-11											Yes	2015-03-11		
+ND0003	Visit 2							Yes	2016-04-27 11:31	2016-05-11 13:30	2					Yes	2016-06-16	2016-06-16	1	Yes	2016-08-18	2016-08-18	1					Yes	2016-04-27	Yes	2016-04-27					Yes	Yes	2016-04-27	2016-04-28	Yes	2016-07-28	Yes	2016-04-27		
+ND0003	Visit 3
\ No newline at end of file
diff --git a/appointment-import/testFiles/subjects.txt b/appointment-import/testFiles/subjects.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c55594f7ee41d31b0b81aaf1b38b46eb949d5bb4
--- /dev/null
+++ b/appointment-import/testFiles/subjects.txt
@@ -0,0 +1,2 @@
+Piotr	Gawron	ND0001	1
+John	Doe	ND0002	2
diff --git a/smash/web/models.py b/smash/web/models.py
index c5b2915b5b2839bb9cd92bd1defb4f12061592fa..2de5fe18f0332705220e344e9e625f62e3e65a6b 100644
--- a/smash/web/models.py
+++ b/smash/web/models.py
@@ -5,6 +5,7 @@ from django.utils import timezone
 from django.contrib.auth.models import User, AnonymousUser
 import datetime
 
+from datetime import timedelta
 
 def get_current_year():
     return datetime.datetime.now().year
@@ -34,36 +35,50 @@ class Subject(models.Model):
         (SEX_CHOICES_FEMALE,'Female'),
     )
 
-    STATUS_CHOICES_DEAD = 'DEA'
-    STATUS_CHOICES_REJECTED = 'REJ'
-    STATUS_CHOICES_NOT_CONTACTED = 'NCO'
-    STATUS_CHOICES_TEST_GROUP = 'TST'
-    STATUS_CHOICES_LEVEL_A = 'LEA'
-    STATUS_CHOICES_LEVEL_B = 'LEB'
-    STATUS_CHOICES_OPTED_OUT = 'OPT'
+    LOCATION_CHOICES_LIH = 'L'
+    LOCATION_CHOICES = (
+        (LOCATION_CHOICES_LIH,'LIH'),
+        ('P','PRC'),
+        ('F','FLYING TEAM'),
+    )
 
-    STATUS_CHOICES = (
-        (STATUS_CHOICES_NOT_CONTACTED, 'Not contacted'),
-        (STATUS_CHOICES_TEST_GROUP, 'Test group'),
-        (STATUS_CHOICES_LEVEL_A, 'Level A'),
-        (STATUS_CHOICES_LEVEL_B, 'Level B'),
-        (STATUS_CHOICES_OPTED_OUT, 'Opted out'),
-        (STATUS_CHOICES_REJECTED, 'Rejected'),
-        (STATUS_CHOICES_DEAD, 'Dead')
+    SUBJECT_TYPE_CHOICES_CONTROL = 'C'
+    SUBJECT_TYPE_CHOICES = (
+        (SUBJECT_TYPE_CHOICES_CONTROL,'CONTROL'),
+        ('P','PATIENT'),
     )
 
     def mark_as_dead(self):
-        self.status = self.STATUS_CHOICES_DEAD
+        self.dead = True
         self.save()
 
     def mark_as_rejected(self):
-        self.status = self.STATUS_CHOICES_REJECTED
+        self.resigned = True
         self.save()
 
     sex = models.CharField(max_length=1,
         choices=SEX_CHOICES,
         verbose_name='Sex'
     )
+    type = models.CharField(max_length=1,
+        choices=SUBJECT_TYPE_CHOICES,
+        verbose_name='Type'
+    )
+
+    dead = models.BooleanField(
+        verbose_name='Dead',
+        default= False,
+        editable=False
+    )
+    resigned = models.BooleanField(
+        verbose_name='Resigned',
+        default= False,
+        editable=False
+    )
+    default_appointment_location = models.CharField(max_length=1,
+        choices=LOCATION_CHOICES,
+        verbose_name='Default appointment location'
+    )
     first_name = models.CharField(max_length=50,
         verbose_name='First name'
     )
@@ -127,10 +142,6 @@ class Subject(models.Model):
 	    blank=True,
         verbose_name='MPower ID'
     )
-    status = models.CharField(max_length=3, choices=STATUS_CHOICES,
-        verbose_name='Status',
-        default='NCO'
-    )
     comments = models.CharField(max_length=2000,
 	    blank=True,
         verbose_name='Comments'
@@ -155,6 +166,16 @@ class Subject(models.Model):
         verbose_name='Year of diagnosis (YYYY)'
     )
 
+    def latest_visit(self):
+        visits = self.visit_set.all()
+        if len(visits)==0:
+            return None
+        result = visits[0];
+        for visit in visits:
+            if (visit.datetime_begin > result.datetime_begin):
+                result = visit
+        return result
+
     def __str__(self):
         return "%s %s" % (self.first_name, self.last_name)
 
@@ -173,19 +194,6 @@ class Visit(models.Model):
         verbose_name='Visit ends at'
     ) # Deadline before which all appointments need to be scheduled
 
-    TYPE_CHOICES_LEVEL_A_VISIT = 'A'
-    TYPE_CHOICES_LEVEL_B_VISIT = 'B'
-    TYPE_CHOICES_OTHER = 'O'
-
-    TYPE_CHOICES = (
-        (TYPE_CHOICES_LEVEL_A_VISIT, 'Level A visit'),
-        (TYPE_CHOICES_LEVEL_B_VISIT, 'Level B visit'),
-        (TYPE_CHOICES_OTHER, 'Other')
-    )
-    visit_type = models.CharField(max_length=1, choices=TYPE_CHOICES,
-        verbose_name='Visit type',
-        default='O'
-    )
     is_finished = models.BooleanField(
         verbose_name='Has ended',
         default=False
@@ -196,6 +204,35 @@ class Visit(models.Model):
     def __str__(self):
         return "%s %s" % (self.subject.first_name, self.subject.last_name)
 
+    def follow_up_title(self):
+        visits = Visit.objects.filter(subject=self.subject, datetime_begin__lt =self.datetime_begin)
+        if len(visits)==0:
+            return "First visit"
+        else:
+            return "Follow up "+str(len(visits))
+
+    def mark_as_finished(self):
+        self.is_finished = True
+        self.save()
+
+        visit_finished = self.datetime_begin
+
+        appointments = Appointment.objects.filter(visit=self)
+
+        for appointment in appointments:
+            visit_finished = max(visit_finished,appointment.datetime_when)
+
+        time_to_next_visit = datetime.timedelta(days=365)
+        if self.subject.type== Subject.SUBJECT_TYPE_CHOICES_CONTROL:
+            time_to_next_visit = datetime.timedelta(days=365*3+366)
+
+        Visit.objects.create(
+                            subject = self.subject,
+                            datetime_begin = visit_finished+time_to_next_visit,
+                            datetime_end = visit_finished+time_to_next_visit+datetime.timedelta(days=93)
+                            )
+
+
     def end_if_appointments_were_finished(self):
         the_appointments = self.appointment_set.all()
         finished = True
@@ -257,6 +294,11 @@ class Room (models.Model):
 
 
 class AppointmentType (models.Model):
+
+
+    DEFAULT_COLOR = '#cfc600'
+    DEFAULT_FONT_COLOR = '#00000'
+
     required_equipment = models.ManyToManyField(Item,
         verbose_name='Required equipment',
         blank = True
@@ -270,6 +312,18 @@ class AppointmentType (models.Model):
     default_duration = models.IntegerField(
         verbose_name='Default duration (in minutes)'
     )
+    calendar_color_priority = models.IntegerField(
+        verbose_name='Calendar color priority',
+        default=1
+    )
+    calendar_color = models.CharField(max_length=2000,
+        verbose_name='Calendar color',
+        default=DEFAULT_COLOR
+    )
+    calendar_font_color = models.CharField(max_length=2000,
+        verbose_name='Calendar color',
+        default=DEFAULT_FONT_COLOR
+    )
     rest_time = models.IntegerField(
         verbose_name='Suggested rest time'
     )
@@ -434,9 +488,8 @@ class Appointment(models.Model):
         verbose_name='Worker conducting the assessment (if applicable)',
         null=True, blank=True
     )
-    appointment_type = models.ForeignKey(AppointmentType,
-        verbose_name='Appointment type',
-        null=True, blank=True
+    appointment_types = models.ManyToManyField(AppointmentType,
+        verbose_name='Appointment types',
     )
     room = models.ForeignKey(Room,
         verbose_name='Room ID',
@@ -446,6 +499,11 @@ class Appointment(models.Model):
     visit = models.ForeignKey(Visit,
         verbose_name='Visit ID'
     )
+    comment = models.CharField(max_length=1024,
+        verbose_name='Comment',
+        null=True,
+        blank=True
+    )
     datetime_when = models.DateTimeField(
         verbose_name='Appointment on',
         null=True, blank=True
@@ -455,5 +513,43 @@ class Appointment(models.Model):
     )#Potentially redundant; but can be used to manually adjust appointment's length
     is_finished = models.BooleanField(
         verbose_name='Has the appointment ended?',
-        default=False
+        default=False,
+        editable=False
     )
+
+    def mark_as_finished(self):
+        self.is_finished = True
+        self.save()
+
+    def datetime_until(self):
+        if self.datetime_when is None:
+            return None
+        else:
+            return self.datetime_when + timedelta(minutes=max(self.length, 15))
+
+    def color(self):
+        result = AppointmentType.DEFAULT_COLOR
+        priority = 1000000
+        for type in self.appointment_types.all():
+            if type.calendar_color_priority<priority:
+                priority=type.calendar_color_priority
+                result = type.calendar_color
+        return result
+
+    def font_color(self):
+        result = AppointmentType.DEFAULT_FONT_COLOR
+        priority = 1000000
+        for type in self.appointment_types.all():
+            if type.calendar_color_priority<priority:
+                priority=type.calendar_color_priority
+                result = type.calendar_font_color
+        return result
+
+    def title(self):
+        if self.visit.subject.screening_number=="---":
+            return self.comment
+        else:
+            title = self.visit.subject.first_name + " " + self.visit.subject.last_name + " type: "
+            for type in self.appointment_types.all():
+                title += type.code+", "
+            return title
diff --git a/smash/web/static/flags/ARABIC.png b/smash/web/static/flags/ARABIC.png
new file mode 100644
index 0000000000000000000000000000000000000000..a377d4727f6a0fbc4575a22b7f995557602883a4
Binary files /dev/null and b/smash/web/static/flags/ARABIC.png differ
diff --git a/smash/web/static/flags/DK.png b/smash/web/static/flags/DK.png
new file mode 100644
index 0000000000000000000000000000000000000000..a95f38161ab8696b49cc6271e144901d702f046f
Binary files /dev/null and b/smash/web/static/flags/DK.png differ
diff --git a/smash/web/static/flags/ES.png b/smash/web/static/flags/ES.png
new file mode 100644
index 0000000000000000000000000000000000000000..3d4cc208cfcfcaceb63cbe885847a532d6607148
Binary files /dev/null and b/smash/web/static/flags/ES.png differ
diff --git a/smash/web/static/flags/FI.png b/smash/web/static/flags/FI.png
new file mode 100644
index 0000000000000000000000000000000000000000..86d788a476e4ae0deba086210e73aa1d8f42693c
Binary files /dev/null and b/smash/web/static/flags/FI.png differ
diff --git a/smash/web/static/flags/GR.png b/smash/web/static/flags/GR.png
new file mode 100644
index 0000000000000000000000000000000000000000..14cb515ebc0b044efbc4bd511e42fdb65bfd790e
Binary files /dev/null and b/smash/web/static/flags/GR.png differ
diff --git a/smash/web/static/flags/HU.png b/smash/web/static/flags/HU.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf0afeba93a6db0346bf96a3c92314490479cb8e
Binary files /dev/null and b/smash/web/static/flags/HU.png differ
diff --git a/smash/web/static/flags/IT.png b/smash/web/static/flags/IT.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee61e0633bda155c34a9a5c54c5329f302921d0b
Binary files /dev/null and b/smash/web/static/flags/IT.png differ
diff --git a/smash/web/static/flags/NL.png b/smash/web/static/flags/NL.png
new file mode 100644
index 0000000000000000000000000000000000000000..8607b766ea0b14c025b25aeddfd9bbffab4d2773
Binary files /dev/null and b/smash/web/static/flags/NL.png differ
diff --git a/smash/web/static/flags/PL.png b/smash/web/static/flags/PL.png
new file mode 100644
index 0000000000000000000000000000000000000000..f7633f09e0166de0a1ef17d7bd86fb324843e7c1
Binary files /dev/null and b/smash/web/static/flags/PL.png differ
diff --git a/smash/web/static/flags/RO.png b/smash/web/static/flags/RO.png
new file mode 100644
index 0000000000000000000000000000000000000000..80d3bec04ffee83721e9e7f7bf33067b6cd9c40e
Binary files /dev/null and b/smash/web/static/flags/RO.png differ
diff --git a/smash/web/static/flags/SE.png b/smash/web/static/flags/SE.png
new file mode 100644
index 0000000000000000000000000000000000000000..6b32d1d2e4ed46b74c6ac4bf11eba11c67961241
Binary files /dev/null and b/smash/web/static/flags/SE.png differ
diff --git a/smash/web/static/flags/SK.png b/smash/web/static/flags/SK.png
new file mode 100644
index 0000000000000000000000000000000000000000..765ec9971ecafee3247e9d4e58cadb3b65b625b8
Binary files /dev/null and b/smash/web/static/flags/SK.png differ
diff --git a/smash/web/templates/_base.html b/smash/web/templates/_base.html
index 51c5c09345f639344d6e55aa1593da2dd119fae2..3291f0ce7a57198e9098bf34a517d3250c971b01 100644
--- a/smash/web/templates/_base.html
+++ b/smash/web/templates/_base.html
@@ -347,7 +347,7 @@ desired effect
 	{% block footer %}
 		<!-- To the right -->
 		<div class="pull-right hidden-xs">
-		  Version: <strong>preview 0.1.1</strong> (20 Feb 2017)
+		  Version: <strong>preview 0.1.2</strong> (1 Mar 2017)
 		</div>
 
 		<!-- Default to the left -->
diff --git a/smash/web/templates/appointments/add.html b/smash/web/templates/appointments/add.html
index cbfab41313139b6f86ca04f75ecf70884db431f8..ea975643a3f0a4fe536878447e77c41fc709adf8 100644
--- a/smash/web/templates/appointments/add.html
+++ b/smash/web/templates/appointments/add.html
@@ -7,6 +7,10 @@
 	<!-- DataTables -->
   <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}">
 
+  <!-- fullCalendar 2.2.5-->
+  <link rel="stylesheet" href="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.min.css' %}">
+  <link rel="stylesheet" href="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.print.css' %}" media="print">
+
   {% include "includes/datetimepicker.css.html" %}
 {% endblock styles %}
 
@@ -55,8 +59,17 @@
   				</div>
   			{% endfor %}
       </div>
+      <div class="col-md-6">
+    		<div class="box box-primary">
+    			<div class="box-body no-padding">
+    				<div id="calendar"></div>
+    			</div>
+    		</div>
+    	</div>
 		</div><!-- /.box-body -->
 
+
+
 		<div class="box-footer">
 			<div class="col-sm-6">
 				<button type="submit" class="btn btn-block btn-success">Add</button>
@@ -66,6 +79,8 @@
 			</div>
 		</div><!-- /.box-footer -->
 	</form>
+
+
 </div>
 
 {% endblock %}
@@ -78,6 +93,8 @@
 
 	<script src="{% static 'AdminLTE/plugins/datatables/jquery.dataTables.min.js' %}"></script>
 	<script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script>
+  <script src="{% static 'AdminLTE/plugins/moment.js/moment.min.js' %}"></script>
+	<script src="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.min.js' %}"></script>
 	<script>
 		$(function () {
 			$('#table').DataTable({
@@ -88,6 +105,52 @@
 			  "info": true,
 			  "autoWidth": false
 			});
+
+      $('#calendar').fullCalendar({
+				header: {
+					left: 'prev,next today',
+					center: 'title',
+					right: 'month,agendaWeek'
+				},
+				editable: false,
+        dayClick: function(date, jsEvent, view) {
+               var dateString = date.format();
+               if (dateString.indexOf("T")>=0) {
+                 dateString=dateString.replace("T"," ");
+               } else {
+                 dateString=dateString+" 09:00";
+               }
+               document.getElementById("id_datetime_when").value = dateString;
+
+           },
+        eventClick: function(calEvent, jsEvent, view) {
+
+          var dateString = calEvent.start.format();
+          if (dateString.indexOf("T")>0) {
+            dateString=dateString.replace("T"," ");
+          } else {
+            dateString=dateString+" 09:00";
+          }
+          if (dateString.indexOf("+")>=0) {
+            dateString= dateString.substring(0,dateString.indexOf("+"));
+          }
+          document.getElementById("id_datetime_when").value = dateString;
+
+        },
+				events: [
+        {% for appointment in full_appointment_list %}
+          {
+            title: '{{ appointment.title }}',
+            start: '{{ appointment.datetime_when | date:"c" }}',
+            end: '{{ appointment.datetime_until | date:"c" }}',
+            color: '{{ appointment.color }}',
+            subject_id: '{{ appointment.visit.subject.id }}',
+            id: '{{ appointment.id }}'
+          },
+        {% endfor %}
+          ],
+			});
+
 		});
 	</script>
 
diff --git a/smash/web/templates/appointments/edit.html b/smash/web/templates/appointments/edit.html
index 0b53364f4d057564b06d7272fabc28ec384ca9f0..35d60f3c2867725aba99acce1025b07806e50934 100644
--- a/smash/web/templates/appointments/edit.html
+++ b/smash/web/templates/appointments/edit.html
@@ -53,6 +53,9 @@
           {% endif %}
 				</div>
 			{% endfor %}
+      <div class="col-md-6">
+        <a href="{% url 'web.views.appointment_mark' id 'finished' %}" class="btn btn-warning btn-block">Mark as finished</a>
+      </div>
 		</div><!-- /.box-body -->
 
 		<div class="box-footer">
diff --git a/smash/web/templates/appointments/index.html b/smash/web/templates/appointments/index.html
index d743a78d353469e78e931c2df8d476d1fcd2ecfd..c440bab2db3df666951f494dd723c02cae913f32 100644
--- a/smash/web/templates/appointments/index.html
+++ b/smash/web/templates/appointments/index.html
@@ -66,10 +66,9 @@
 			<table id="approaching_table" class="table table-bordered table-striped">
 				<thead>
 					<tr>
-						<th>Subject name</th>
-						<th>Full information</th>
+            <th>Subject</th>
+            <th>Type</th>
 						<th>Date</th>
-						<th>Time</th>
 						<th>Details</th>
 					</tr>
 				</thead>
@@ -78,15 +77,20 @@
       			{% for approach in approaching_list %}
   				<tr>
   					<td>
-  						{{ approach.visit.subject.first_name }} {{ approach.visit.subject.last_name }}
+              {% if approach.visit.subject.screening_number == "---" %}
+                N/A
+              {% else %}
+                {{ approach.visit.subject.first_name }} {{ approach.visit.subject.last_name }}
+              {% endif %}
   					</td>
+            <td>
+              {% for type in approach.appointment_types.all %}
+                {{ type.code }},
+              {% endfor %}
+            </td>
+        			<td>{{ approach.datetime_when | date:"Y-m-d H:i" }}</td>
         			<td>
-        				<a href="{% url 'web.views.subject_details' approach.visit.subject.id %}" type="button" class="btn btn-block btn-default">Subject's details</a>
-        			</td>
-        			<td>{{ approach.datetime_when | date:"d-M-Y" }}</td>
-        			<td>{{ approach.datetime_when | time:"H:i" }}</td>
-        			<td>
-        				<a href="{% url 'web.views.appointment_details' approach.id %}" type="button" class="btn btn-block btn-default">Details</a>
+        				<a href="{% url 'web.views.appointment_edit' approach.id %}" type="button" class="btn btn-block btn-default">Details</a>
         			</td>
   				</tr>
 		      	{% endfor %}
@@ -123,6 +127,7 @@
 			  "lengthChange": false,
 			  "searching": true,
 			  "ordering": true,
+        "order": [[ 2, "asc"]],
 			  "info": true,
 			  "autoWidth": false
 			});
@@ -135,13 +140,14 @@
 				},
 				editable: false,
 				events: [
-        {% for approach in approaching_list %}
+        {% for appointment in full_list %}
           {
-            title: '{{ approach.visit.subject.first_name }} {{ approach.visit.subject.last_name }}',
-            start: '{{ approach.datetime_when | date:"c" }}',
-            color: '#cfc600',
-            subject_id: '{{ approach.visit.subject.id }}',
-            id: '{{ approach.id }}'
+            title: '{{ appointment.title }}',
+            start: '{{ appointment.datetime_when | date:"c" }}',
+            end: '{{ appointment.datetime_until | date:"c" }}',
+            color: '{{ appointment.color }}',
+            subject_id: '{{ appointment.visit.subject.id }}',
+            id: '{{ appointment.id }}'
           },
         {% endfor %}
           ],
diff --git a/smash/web/templates/subjects/index.html b/smash/web/templates/subjects/index.html
index e2955a291d44e766e00a43386cde9de299caa98b..9212f52b7b24dcbabd7f0d3b32fca919732bf600 100644
--- a/smash/web/templates/subjects/index.html
+++ b/smash/web/templates/subjects/index.html
@@ -36,10 +36,11 @@
 				<th>Last name</th>
 				<th>Country</th>
 				<th data-sorter="false" data-filter="false">Languages</th>
-				<th class="filter-select filter-exact" data-placeholder="Select type">Type</th>
+				<th class="filter-select filter-exact" data-placeholder="Select location">Default location</th>
+				<th>Dead</th>
+				<th>Resigned</th>
 				<th>Details</th>
 				<th>Edit</th>
-        <th>Delete</th>
 			</tr>
 		</thead>
 
@@ -60,10 +61,11 @@
         {% endfor %}
         {% endautoescape %}
       </td>
-			<td>{{ subject.get_status_display }}</td>
+			<td>{{ subject.get_default_appointment_location_display }}</td>
+			<td>{% if subject.dead %} YES {% else %} NO {% endif %}	</td>
+			<td>{% if subject.resigned %} YES {% else %} NO {% endif %}	</td>
 			<td><a href="{% url 'web.views.subject_details' subject.id %}" type="button" class="btn btn-block btn-default">Details</a></td>
 			<td><a href="{% url 'web.views.subject_edit' subject.id %}" type="button" class="btn btn-block btn-default">Edit</a></td>
-      <td><a href="{% url 'web.views.subject_delete' subject.id %}" type="button" class="btn btn-block btn-default">Delete</a></td>
 		</tr>
     {% endfor %}
 
diff --git a/smash/web/templates/subjects/visitdetails.html b/smash/web/templates/subjects/visitdetails.html
index 2b6476e6c0bd8c4f22002a409106cf9473951cea..c3432d99fe5f918a15f0e8327ec11ecb9afba38c 100644
--- a/smash/web/templates/subjects/visitdetails.html
+++ b/smash/web/templates/subjects/visitdetails.html
@@ -31,7 +31,7 @@
       {% for element in display %}
           <div class="box box-widget widget-user-2">
             <div class="widget-user-header bg-green">
-              <h3 class="widget-user-username">Visit {{ forloop.counter }}</h3>
+              <h3 class="widget-user-username">{{ element.4 }}</h3>
               <h5 class="widget-user-desc">
                 {% if element.2 %}(Finished)
                 {% else %}(Not finished)
@@ -78,7 +78,13 @@
                   {% for app in element.1 %}
                   <tr>
                     <td>{{ forloop.counter }}</td>
-                    <td>{{ app.appointment_type }}</td>
+                    <td style="background-color:{{app.color}} !important">
+                      <font COLOR="{{app.font_color}}">
+                      {% for type in app.appointment_types.all %}
+                        {{ type.code }},
+                      {% endfor %}
+                      </font>
+                    </td>
                     <td>{{ app.datetime_when | date:"d-M-Y" }}</td>
                     <td>{{ app.datetime_when | time:"H:i" }}</td>
                     <td>{{ app.length }}</td>
@@ -88,7 +94,11 @@
                       {% endif %}
                     </td>
                     <td>
-                      <a href="{% url 'web.views.appointment_edit' app.id %}" type="button" class="btn btn-block btn-default">Edit</a>
+                      {% if app.is_finished %}
+                        FINISHED
+                      {% else %}
+                        <a href="{% url 'web.views.appointment_edit' app.id %}" type="button" class="btn btn-block btn-default">Edit</a>
+                      {% endif %}
                     </td>
                   </tr>
                   {% endfor %}
diff --git a/smash/web/templates/visits/details.html b/smash/web/templates/visits/details.html
index 5462e19d94ba10159d423ff50cbdccbe26422700..7385a5102267d52079bd736eb289a1a6cbeaa865 100644
--- a/smash/web/templates/visits/details.html
+++ b/smash/web/templates/visits/details.html
@@ -11,10 +11,10 @@
 {% endblock styles %}
 
 {% block ui_active_tab %}'visits'{% endblock ui_active_tab %}
-{% block page_header %}Details of the visit{% endblock page_header %}
+{% block page_header %}Details of the visit ({{ visit.follow_up_title }}) {% endblock page_header %}
 {% block page_description %}{% endblock page_description %}
 
-{% block title %}{{ block.super }} - Details of visit {% endblock %}
+{% block title %}{{ block.super }} - Details of visit ({{ visit.follow_up_title }}) {% endblock %}
 
 {% block breadcrumb %}
 {% include "subjects/breadcrumb.html" %}
@@ -29,7 +29,9 @@
 	</div>
 
 	<div class="box-header with-border">
-		<h3 class="box-title">Details of visit</h3>
+		<h3 class="box-title">Details of visit
+
+    </h3>
 	</div>
 
 	<form class="form-horizontal">
@@ -41,7 +43,7 @@
 					</label>
 
 					<div class="col-sm-8">
-						{{ field|add_class:'form-control' }}
+						{{ field|disable|add_class:'form-control' }}
 					</div>
 
           {% if field.errors %}
@@ -57,8 +59,14 @@
           Visit finished
         </label>
         <div class="col-sm-8">
-  				{% if visFinished %}<button type="button" class="btn btn-block btn-danger">YES</button>
-          {% else %}<button type="button" class="btn btn-block btn-success">NO</button>
+  				{% if visFinished %}<div class="btn btn-block">YES</div>
+          {% else %}<div class="btn btn-block">
+            {% if canFinish %}
+              <a href="{% url 'web.views.visit_mark' vid 'finished' %}" class="btn btn-warning btn-block">Mark as finished</a>
+            {% else %}
+              Waiting for appointments to finish.
+            {% endif %}
+            </div>
           {% endif %}
         </div>
 			</div>
@@ -95,7 +103,13 @@
   {% for app in loApp %}
   <tr>
     <td>{{ forloop.counter }}</td>
-    <td>{{ app.appointment_type }}</td>
+    <td style="background-color:{{app.color}} !important">
+      <font COLOR="{{app.font_color}}">
+      {% for type in app.appointment_types.all %}
+        {{ type.code }},
+      {% endfor %}
+      </font>
+    </td>
     <td>{{ app.datetime_when | date:"d-M-Y" }}</td>
     <td>{{ app.datetime_when | time:"H:i" }}</td>
     <td>{{ app.length }}</td>
@@ -105,7 +119,11 @@
       {% endif %}
     </td>
     	<td>
-        <a href="{% url 'web.views.appointment_edit' app.id %}" type="button" class="btn btn-block btn-default">Edit</a>
+        {% if app.is_finished %}
+          FINISHED
+        {% else %}
+          <a href="{% url 'web.views.appointment_edit' app.id %}" type="button" class="btn btn-block btn-default">Edit</a>
+        {% endif %}
       </td>
   </tr>
   {% endfor %}
diff --git a/smash/web/templates/visits/index.html b/smash/web/templates/visits/index.html
index adb835d2f3166d5a462010292d963f30516d77d8..65a47aea2b1bbf053fc0e350164edff192c9ce72 100644
--- a/smash/web/templates/visits/index.html
+++ b/smash/web/templates/visits/index.html
@@ -41,8 +41,7 @@
 					<th>Full information</th>
           <th>Visit begins</th>
           <th>Visit ends</th>
-          <th>Visit type</th>
-          <th>Finished?</th>
+          <th>Finished?</th>
 				</tr>
 			</thead>
   			<tbody>
@@ -58,10 +57,7 @@
 		        	<td>
 		        		{{ visit.datetime_end }}
 		        	</td>
-		        	<td>
-		        		{{ visit.get_visit_type_display }}
-		        	</td>
-		        	<td>
+		        	<td>
                 {% if visit.is_finished %}<button type="button" class="btn btn-block btn-success">YES</button>
                 {% else %}<button type="button" class="btn btn-block btn-danger">NO</button>
                 {% endif %}
diff --git a/smash/web/tests/test_SubjectAddForm.py b/smash/web/tests/test_SubjectAddForm.py
index 41fdb32027d0dfe0f14c13344c43b5bbeb5fbe9c..92b032d07979d0de60fe4ef5c468b6a8175b8e04 100644
--- a/smash/web/tests/test_SubjectAddForm.py
+++ b/smash/web/tests/test_SubjectAddForm.py
@@ -6,8 +6,9 @@ class SubjectAddFormTests(TestCase):
     def setUp(self):
         self.sample_data = {'first_name': 'name',
             'last_name': 'name',
-            'status' : Subject.STATUS_CHOICES_NOT_CONTACTED,
             'sex' : Subject.SEX_CHOICES_MALE,
+            'type' : Subject.SUBJECT_TYPE_CHOICES_CONTROL,
+            'default_appointment_location' : Subject.LOCATION_CHOICES_LIH,
             'country' : 'Luxembourg'
         }
     def test_validation(self):
diff --git a/smash/web/tests/test_SubjectEditForm.py b/smash/web/tests/test_SubjectEditForm.py
index 604c3a0c6b10bceb71c135af9114cf0fe68a406f..223b55954c6b61a5375a4243cb7f4619c1b1d9ee 100644
--- a/smash/web/tests/test_SubjectEditForm.py
+++ b/smash/web/tests/test_SubjectEditForm.py
@@ -8,8 +8,9 @@ class SubjectEditFormTests(TestCase):
     def setUp(self):
         self.sample_data = {'first_name': 'name',
             'last_name': 'name',
-            'status' : Subject.STATUS_CHOICES_NOT_CONTACTED,
             'sex' : Subject.SEX_CHOICES_MALE,
+            'type' : Subject.SUBJECT_TYPE_CHOICES_CONTROL,
+            'default_appointment_location' : Subject.LOCATION_CHOICES_LIH,
             'country' : 'Luxembourg',
             'screening_number' : '123',
             'nd_number' : 'nd_123'
diff --git a/smash/web/tests/test_VisitAddForm.py b/smash/web/tests/test_VisitAddForm.py
index 9a35892b17104ab8d46beaaffbc004890821386e..ae3c4baa3576b9114e3a705c1a6894f11c3985f8 100644
--- a/smash/web/tests/test_VisitAddForm.py
+++ b/smash/web/tests/test_VisitAddForm.py
@@ -8,15 +8,15 @@ class SubjectAddFormTests(TestCase):
     def setUp(self):
         subject_data = {'first_name': 'name',
             'last_name': 'name',
-            'status' : Subject.STATUS_CHOICES_NOT_CONTACTED,
             'sex' : Subject.SEX_CHOICES_MALE,
+            'type' : Subject.SUBJECT_TYPE_CHOICES_CONTROL,
+            'default_appointment_location' : Subject.LOCATION_CHOICES_LIH,
             'country' : 'Luxembourg',
         }
         self.subject = SubjectAddForm(data=subject_data).save()
 
         self.sample_data = {'datetime_begin': "2017-01-01",
             'datetime_end': "2017-02-02",
-            'visit_type': Visit.TYPE_CHOICES_OTHER,
             'subject' : self.subject.id
         }
 
diff --git a/smash/web/urls.py b/smash/web/urls.py
index 4a71af8d219c17bfc38fc7546edd2bc090122305..bf78ef96158d66d42d82d4f585b9269f60fad24f 100644
--- a/smash/web/urls.py
+++ b/smash/web/urls.py
@@ -22,10 +22,12 @@ urlpatterns = [
     url(r'appointments/add/(?P<id>\d+)$', views.appointment_add, name='web.views.appointment_add'),
     url(r'appointments/edit/(?P<id>\d+)$', views.appointment_edit, name='web.views.appointment_edit'),
     url(r'appointments/edit_datetime/(?P<id>\d+)$', views.appointment_edit_datetime, name='web.views.appointment_edit_datetime'),
+    url(r'appointments/mark/(?P<id>\d+)/(?P<as_what>[A-z]+)$', views.appointment_mark, name='web.views.appointment_mark'),
 
     url(r'visits$', views.visits, name='web.views.visits'),
     url(r'visits/details/(?P<id>\d+)$', views.visit_details, name='web.views.visit_details'),
     url(r'visits/add$', views.visit_add, name='web.views.visit_add'),
+    url(r'visit/mark/(?P<id>\d+)/(?P<as_what>[A-z]+)$', views.visit_mark, name='web.views.visit_mark'),
 
     url(r'subjects$', views.subjects, name='web.views.subjects'),
     url(r'subjects/add$', views.subject_add, name='web.views.subject_add'),
diff --git a/smash/web/views.py b/smash/web/views.py
index d48eade117e0ec66c7fda1a861920c1b4da14b7a..99aa99903befe437ab2250e772219adf384e6fb3 100644
--- a/smash/web/views.py
+++ b/smash/web/views.py
@@ -94,10 +94,23 @@ def visit_details(request, id):
 	vid = displayedVisit.id
 	displayedSubject = displayedVisit.subject
 	listOfAppointments = displayedVisit.appointment_set.all()
+	canFinish=True
+	for appointment in listOfAppointments:
+		if not appointment.is_finished:
+			canFinish=False;
 	vform = VisitDetailForm(instance=displayedVisit)
 	sform = SubjectDetailForm(instance=displayedSubject)
 
-	return wrap_response(request, 'visits/details.html', {'vform': vform, 'sform': sform, 'loApp': listOfAppointments, 'visFinished': visFinished, 'vid': vid})
+	return wrap_response(request, 'visits/details.html', {'vform': vform, 'sform': sform, 'loApp': listOfAppointments, 'visFinished': visFinished,'canFinish': canFinish, 'vid': vid, 'visit': displayedVisit})
+
+def visit_mark(request, id, as_what):
+	visit = get_object_or_404(Visit, id=id)
+	if as_what == 'finished':
+		visit.mark_as_finished()
+
+	visit = get_object_or_404(Visit, id=id)
+	return redirect(visit_details, id=visit.subject.latest_visit().id)
+
 
 
 def visit_add(request):
@@ -173,6 +186,12 @@ def subject_mark(request, id, as_what):
 		who.mark_as_rejected()
 	return redirect(subject_edit, id=id)
 
+def appointment_mark(request, id, as_what):
+	appointment = get_object_or_404(Appointment, id=id)
+	if as_what == 'finished':
+		appointment.mark_as_finished()
+	return redirect(visit_details, id=appointment.visit.id)
+
 def subject_visit_details(request, id):
 	locsubject = get_object_or_404(Subject, id=id)
 	visits = locsubject.visit_set.all()
@@ -182,8 +201,9 @@ def subject_visit_details(request, id):
 		print assign
 		finished = vis.is_finished
 		visid = vis.id
+		visit_title = vis.follow_up_title()
 		visform = VisitDetailForm(instance=vis)
-		endlist.append((visform,assign,finished,visid))
+		endlist.append((visform,assign,finished,visid,visit_title))
 
 	#print len(endlist)
 	#print endlist[0]
@@ -301,7 +321,12 @@ def suggest_details(Appointment appoint):
 def appointments(request):
 	futureDate = datetime.datetime.now()+datetime.timedelta(days=93)
 	planning_list = Appointment.objects.filter(datetime_when__isnull=True, visit__datetime_begin__lt = futureDate)
-	approaching_list = Appointment.objects.filter(datetime_when__gt = datetime.datetime.now())
+
+	today = datetime.datetime.now()
+	today_midnight = datetime.datetime(today.year,today.month,today.day)
+	month_ago = today +datetime.timedelta(days=-31)
+	approaching_list = Appointment.objects.filter(datetime_when__gt = today_midnight, is_finished = False).order_by('datetime_when')
+	full_list = Appointment.objects.filter(datetime_when__gt = month_ago).order_by('datetime_when')
 
 
 	for plan in planning_list:
@@ -309,7 +334,8 @@ def appointments(request):
 
 	context = {
 		'planning_list': planning_list,
-		'approaching_list': approaching_list
+		'approaching_list': approaching_list,
+		'full_list': full_list
 	}
 
 	return wrap_response(request, "appointments/index.html",context)
@@ -321,6 +347,10 @@ def appointment_details(request, id):
 
 
 def appointment_add(request, id):
+	today = datetime.datetime.now()
+	today_midnight = datetime.datetime(today.year,today.month,today.day)
+	month_ago = today +datetime.timedelta(days=-31)
+	full_list = Appointment.objects.filter(datetime_when__gt = month_ago).order_by('datetime_when')
 	if request.method == 'POST':
 		form = AppointmentAddForm(request.POST, request.FILES)
 		form.fields['visit'].widget = forms.HiddenInput()
@@ -331,7 +361,7 @@ def appointment_add(request, id):
 		form = AppointmentAddForm(initial={'visit': id})
 		form.fields['visit'].widget = forms.HiddenInput()
 
-	return wrap_response(request, 'appointments/add.html', {'form': form, 'visitID': id})
+	return wrap_response(request, 'appointments/add.html', {'form': form, 'visitID': id, 'full_appointment_list': full_list})
 
 def appointment_edit(request, id):
 	the_appointment = get_object_or_404(Appointment, id=id)
@@ -348,7 +378,7 @@ def appointment_edit(request, id):
 			return redirect(appointments)
 	else:
 		form = AppointmentEditForm(instance=the_appointment)
-	return wrap_response(request, 'appointments/edit.html', {'form': form})
+	return wrap_response(request, 'appointments/edit.html', {'form': form, 'id':id})
 
 
 def appointment_edit_datetime(request, id):