Index: /main/waeup.kofa/trunk/src/waeup/kofa/browser/batchprocessing.txt
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/browser/batchprocessing.txt	(revision 9417)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/browser/batchprocessing.txt	(revision 9418)
@@ -98,5 +98,6 @@
     'Applicant Processor', 'Applicants Container Processor',
     'CertificateCourse Processor', 'Certificate Processor',
-    'Course Processor', 'CourseTicket Processor',
+    'Course Processor', 'Course Result Processor (special processor)',
+    'CourseTicket Processor',
     'Department Processor', 'Faculty Processor',
     'Hostel Processor',
@@ -105,5 +106,5 @@
     'StudentStudyLevel Processor',
     'User Processor',
-    'Verdict Processor (update only)']
+    'Verdict Processor (special processor, update only)']
 
     >>> importerselect.getControl('Faculty Processor').selected = True
Index: /main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py	(revision 9417)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py	(revision 9418)
@@ -720,5 +720,5 @@
 
 class StudentVerdictProcessor(StudentStudyCourseProcessor):
-    """A batch processor for verdicts.
+    """A special batch processor for verdicts.
 
     Import verdicts and perform workflow transitions.
@@ -728,5 +728,5 @@
     grok.name(util_name)
 
-    name = u'Verdict Processor (update only)'
+    name = u'Verdict Processor (special processor, update only)'
     iface = IStudentVerdictUpdate
     factory_name = 'waeup.StudentStudyCourse'
@@ -793,2 +793,78 @@
         notify(grok.ObjectModifiedEvent(obj.__parent__))
         return
+
+class CourseResultProcessor(CourseTicketProcessor):
+    """A special batch processor for course results objects.
+
+    Import course results, compares session and creates study level.
+    """
+    grok.implements(IBatchProcessor)
+    grok.provides(IBatchProcessor)
+    grok.context(Interface)
+    util_name = 'courseresultprocessor'
+    grok.name(util_name)
+
+    name = u'Course Result Processor (special processor)'
+    iface = ICourseTicket
+    factory_name = 'waeup.CourseTicket'
+
+    location_fields = []
+    additional_fields = ['level', 'code']
+    additional_headers = ['level', 'code']
+
+    def getParent(self, row, site):
+        student = self._getStudent(row, site)
+        if student is None:
+            return None
+        return student['studycourse'].get(row['level'])
+
+    def getEntry(self, row, site):
+        level = self.getParent(row, site)
+        if level is None:
+            return None
+        return level.get(row['code'])
+
+    def updateEntry(self, obj, row, site):
+        """Update obj to the values given in row.
+        """
+        items_changed = super(CourseTicketProcessor, self).updateEntry(
+            obj, row, site)
+        parent = self.getParent(row, site)
+        student = self.getParent(row, site).__parent__.__parent__
+        student.__parent__.logger.info(
+            '%s - Course ticket in %s updated: %s'
+            % (student.student_id,  parent.level, items_changed))
+        return
+
+    def addEntry(self, obj, row, site):
+        parent = self.getParent(row, site)
+        catalog = getUtility(ICatalog, name='courses_catalog')
+        entries = list(catalog.searchResults(code=(row['code'],row['code'])))
+        obj.fcode = entries[0].__parent__.__parent__.__parent__.code
+        obj.dcode = entries[0].__parent__.__parent__.code
+        obj.title = entries[0].title
+        if getattr(obj, 'credits', None) is None:
+            obj.credits = entries[0].credits
+        if getattr(obj, 'passmark', None) is None:
+            obj.passmark = entries[0].passmark
+        obj.semester = entries[0].semester
+        parent[row['code']] = obj
+        return
+
+    def delEntry(self, row, site):
+        raise NotImplementedError('method not implemented')
+
+    def checkConversion(self, row, mode='ignore'):
+        """Validates all values in row.
+        """
+        errs, inv_errs, conv_dict = super(
+            CourseTicketProcessor, self).checkConversion(row, mode=mode)
+
+        # We have to check if course really exists.
+        # This is not done by the converter.
+        catalog = getUtility(ICatalog, name='courses_catalog')
+        entries = catalog.searchResults(code=(row['code'],row['code']))
+        if len(entries) == 0:
+            errs.append(('code','non-existent'))
+            return errs, inv_errs, conv_dict
+        return errs, inv_errs, conv_dict
