Index: /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/batching.py
===================================================================
--- /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/batching.py	(revision 15139)
+++ /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/batching.py	(revision 15140)
@@ -528,4 +528,6 @@
         problems.
         """
+        if obj.student.studycourse_locked:
+            return 'Studycourse is locked.'
         certificate = getattr(obj, 'certificate', None)
         entry_session = getattr(obj, 'entry_session', None)
@@ -619,4 +621,11 @@
         return
 
+    def checkUpdateRequirements(self, obj, row, site):
+        """
+        """
+        if obj.student.studycourse_locked:
+            return 'Studylevel is locked.'
+        return None
+
 class CourseTicketProcessor(StudentProcessorBase):
     """The Course Ticket Processor imports course tickets, the subobjects
@@ -714,4 +723,11 @@
             del parent[ticket.code]
         return
+
+    def checkUpdateRequirements(self, obj, row, site):
+        """
+        """
+        if obj.student.studycourse_locked:
+            return 'Studycourse is locked.'
+        return None
 
     def checkConversion(self, row, mode='ignore'):
Index: /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/browser.py
===================================================================
--- /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/browser.py	(revision 15139)
+++ /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/browser.py	(revision 15140)
@@ -996,5 +996,6 @@
 
     def update(self):
-        if not self.context.is_current:
+        if not self.context.is_current \
+            or self.context.student.studycourse_locked:
             emit_lock_message(self)
             return
@@ -1076,5 +1077,5 @@
     """ Page to request transcript by student
     """
-    grok.context(IStudent)
+    grok.context(IStudentTranscript)
     grok.name('request_transcript')
     grok.require('waeup.handleStudent')
@@ -1146,8 +1147,30 @@
         return
 
-class StudentTranscriptReleasesFormPage(KofaEditFormPage):
+class StudentTranscriptValidateView(UtilityView, grok.View):
+    """ View to validate transcript
+    """
+    grok.context(IStudentTranscript)
+    grok.name('validate_transcript')
+    grok.require('waeup.processTranscript')
+    form_fields = grok.AutoFields(IStudentTranscript)
+
+    def update(self, SUBMIT=None):
+        if self.context.student.state != TRANSREQ:
+            self.flash(_('Student is in wrong state.'), type="warning")
+            self.redirect(self.url(self.context))
+            return
+        # Fire transition
+        IWorkflowInfo(self.context).fireTransition('validate_transcript')
+        self.flash(_('Transcript validated.'))
+        self.redirect(self.url(self.context))
+        return
+
+    def render(self):
+        return
+
+class StudentTranscriptReleaseFormPage(KofaEditFormPage):
     """ Page to release transcript
     """
-    grok.context(IStudent)
+    grok.context(IStudentTranscript)
     grok.name('release_transcript')
     grok.require('waeup.processTranscript')
@@ -1155,9 +1178,9 @@
     form_fields = grok.AutoFields(IStudentTranscript)
     label = _('Release transcript')
-    buttonname = _('Save comment and mark as released')
+    buttonname = _('Save comment and release transcript')
     pnav = 4
 
     def update(self, SUBMIT=None):
-        super(StudentTranscriptReleasesFormPage, self).update()
+        super(StudentTranscriptReleaseFormPage, self).update()
         if self.context.state != TRANSVAL:
             self.flash(_('Student is in wrong state.'), type="warning")
@@ -1173,5 +1196,5 @@
         # Fire transition
         IWorkflowInfo(self.context).fireTransition('release_transcript')
-        self.flash(_('Transcript released.'))
+        self.flash(_('Transcript released and final transcript file saved.'))
         comment = self.request.form.get('comment', '').replace('\r', '')
         tz = getattr(queryUtility(IKofaUtils), 'tzinfo', pytz.utc)
@@ -1190,10 +1213,4 @@
         # Show and produce transcript file
         self.redirect(self.url(self.context) + '/studycourse/transcript.pdf')
-
-        #subject = _('Transcript released')
-        #args = {'subject':subject, 'body':comment}
-        #self.redirect(self.url(self.context) +
-        #    '/contactstudent?%s' % urlencode(args))
-
         return
 
@@ -1202,5 +1219,5 @@
     for this page. It's an emergency page.
     """
-    grok.context(IStudent)
+    grok.context(IStudentTranscript)
     grok.name('manage_transcript_request')
     grok.require('waeup.processTranscript')
@@ -1308,5 +1325,5 @@
                          ]
 
-        return students_utils.renderPDFTranscript(
+        pdfstream = students_utils.renderPDFTranscript(
             self, 'transcript.pdf',
             self.context.student, studentview,
@@ -1317,4 +1334,8 @@
             save_file=self._save_file(),
             )
+        if not pdfstream:
+            self.redirect(self.url(self.context.student))
+            return
+        return pdfstream
 
 class StudentTransferFormPage(KofaAddFormPage):
@@ -1494,5 +1515,6 @@
 
     def update(self, ADD=None, course=None):
-        if not self.context.__parent__.is_current:
+        if not self.context.__parent__.is_current \
+            or self.context.student.studycourse_locked:
             emit_lock_message(self)
             return
@@ -1566,5 +1588,5 @@
             emit_lock_message(self)
             return
-        if str(self.context.__parent__.current_level) != self.context.__name__:
+        if str(self.context.student.current_level) != self.context.__name__:
             self.flash(_('This is not the student\'s current level.'),
                        type="danger")
@@ -1662,5 +1684,6 @@
 
     def update(self):
-        if not self.context.__parent__.is_current:
+        if not self.context.__parent__.is_current \
+            or self.context.student.studycourse_locked:
             emit_lock_message(self)
             return
@@ -1714,4 +1737,12 @@
     pnav = 4
     grok.template('courseticketmanagepage')
+
+    def update(self):
+        if not self.context.__parent__.__parent__.is_current \
+            or self.context.student.studycourse_locked:
+            emit_lock_message(self)
+            return
+        super(CourseTicketManageFormPage, self).update()
+        return
 
     @property
@@ -2642,5 +2673,6 @@
 
     def update(self):
-        if not self.context.is_current:
+        if not self.context.is_current \
+            or self.context.student.studycourse_locked:
             emit_lock_message(self)
             return
Index: /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/fileviewlets.py
===================================================================
--- /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/fileviewlets.py	(revision 15139)
+++ /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/fileviewlets.py	(revision 15140)
@@ -153,4 +153,11 @@
 
 
+class FinalTranscriptImage(StudentImage):
+    """Renders final transcript.
+    """
+    grok.name('final_transcript')
+    download_name = u'final_transcript'
+
+
 class BirthCertificateImage(StudentImage):
     """Renders birth certificate scan.
Index: /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/student.py
===================================================================
--- /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/student.py	(revision 15139)
+++ /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/student.py	(revision 15140)
@@ -38,5 +38,5 @@
     IKofaUtils, registration_states_vocab, IExtFileStore,
     CREATED, ADMITTED, CLEARANCE, PAID, REGISTERED, VALIDATED, RETURNING,
-    GRADUATED)
+    GRADUATED, TRANSVAL, TRANSREL)
 from waeup.kofa.students.accommodation import StudentAccommodation
 from waeup.kofa.students.interfaces import (
@@ -239,4 +239,8 @@
     def transcript_enabled(self):
         return True
+
+    @property
+    def studycourse_locked(self):
+        return self.state in (GRADUATED, TRANSREL, TRANSVAL)
 
     @property
Index: /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/tests/test_browser.py
===================================================================
--- /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/tests/test_browser.py	(revision 15139)
+++ /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/tests/test_browser.py	(revision 15140)
@@ -2151,13 +2151,10 @@
         self.browser.getLink("K1000000").click()
         self.assertFalse('Release transcript request' in self.browser.contents)
-        # ToDo: Somebody has to validate the transcript
-
-
-
-        IWorkflowState(self.student).setState('transcript validated')
-
-
-
-        self.browser.open(self.student_path)
+        # Officer has to validate the transcript
+        self.browser.getLink("Validate transcript").click()
+
+
+
+
         self.assertTrue('Release transcript' in self.browser.contents)
         # Now the transcript can be released.
@@ -2168,5 +2165,5 @@
         self.browser.getControl(name="comment").value = (
             'Hello,\nYour transcript has been sent to the address provided.')
-        self.browser.getControl("Save comment and mark as released").click()
+        self.browser.getControl("Save comment and release transcript").click()
         self.assertTrue(
             'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
@@ -2178,5 +2175,5 @@
         logcontent = open(logfile).read()
         self.assertTrue(
-            'mrtranscript - students.browser.StudentTranscriptReleasesFormPage - '
+            'mrtranscript - students.browser.StudentTranscriptReleaseFormPage - '
             'K1000000 - comment: Hello,<br>'
             'Your transcript has been sent to the address provided'
@@ -2186,5 +2183,5 @@
         storage = getUtility(IExtFileStore)
         file_id = IFileStoreNameChooser(
-            self.student).chooseName(attr='transcript.pdf')
+            self.student).chooseName(attr='final_transcript.pdf')
         pdf = storage.getFile(file_id).read()
         self.assertTrue(len(pdf) > 0)
Index: /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/utils.py
===================================================================
--- /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/utils.py	(revision 15139)
+++ /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/utils.py	(revision 15140)
@@ -860,5 +860,5 @@
         file_store = getUtility(IExtFileStore)
         file_id = IFileStoreNameChooser(student).chooseName(
-            attr="transcript.pdf")
+            attr="final_transcript.pdf")
         file_store.createFile(file_id, StringIO(transcript))
         return
@@ -979,4 +979,5 @@
         if save_file:
             self._saveTranscriptPDF(student, pdf_stream)
+            return
         return pdf_stream
 
Index: /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/viewlets.py
===================================================================
--- /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/viewlets.py	(revision 15139)
+++ /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/viewlets.py	(revision 15140)
@@ -32,9 +32,9 @@
     OnlinePaymentDisplayFormPage, BedTicketDisplayFormPage,
     StudentClearanceEditFormPage, StudentPersonalEditFormPage,
-    PaymentsManageFormPage, StudyCourseTranscriptPage, EditScoresPage)
+    PaymentsManageFormPage, StudyCourseTranscriptPage, EditScoresPage,)
 from waeup.kofa.students.interfaces import (
     IStudentsContainer, IStudent, IStudentStudyCourse, IStudentStudyLevel,
     ICourseTicket, IStudentOnlinePayment, IBedTicket,
-    IStudentPaymentsContainer, IStudentsUtils
+    IStudentPaymentsContainer, IStudentsUtils, IStudentTranscript
     )
 from waeup.kofa.students.workflow import (
@@ -145,5 +145,5 @@
 class StudentManageTranscriptLink(StudentManageLink):
     grok.order(9)
-    link = 'transcript.pdf'
+    link = 'final_transcript'
     text = _(u'Final Transcript')
 
@@ -155,5 +155,5 @@
             text = translate(
                 self.text, 'waeup.kofa', target_language=lang)
-            url = self.view.url(self.context.student['studycourse'], self.link)
+            url = self.view.url(self.context.student, self.link)
             return u'<li><a href="%s">%s</a></li>' % (
                 url, text)
@@ -762,5 +762,5 @@
 class RequestTranscriptActionButton(ManageActionButton):
     grok.order(8)
-    grok.context(IStudent)
+    grok.context(IStudentTranscript)
     grok.view(StudentBaseDisplayFormPage)
     grok.require('waeup.handleStudent')
@@ -776,7 +776,30 @@
 
 
+class ValidateTranscriptActionButton(ManageActionButton):
+    grok.order(9)
+    grok.context(IStudentTranscript)
+    grok.view(StudentBaseDisplayFormPage)
+    grok.require('waeup.processTranscript')
+    text = _('Validate transcript')
+    target = 'validate_transcript'
+    icon = 'actionicon_transcript.png'
+
+    @property
+    def target_url(self):
+        if self.context.state != TRANSREQ:
+            return ''
+        return self.view.url(self.view.context, self.target)
+
+    @property
+    def onclick(self):
+        return "return window.confirm(%s);" % _(
+            "'Course results cannot be changed after transcript "
+            "validation. \\n\\n"
+            "You really want to validate the transcript?'")
+
+
 class ReleaseTranscriptActionButton(ManageActionButton):
     grok.order(9)
-    grok.context(IStudent)
+    grok.context(IStudentTranscript)
     grok.view(StudentBaseDisplayFormPage)
     grok.require('waeup.processTranscript')
Index: /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/workflow.py
===================================================================
--- /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/workflow.py	(revision 15139)
+++ /main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/workflow.py	(revision 15140)
@@ -209,5 +209,5 @@
     Transition(
         transition_id = 'validate_transcript',
-        title = _('Transcript validated'),
+        title = _('Validate transcript'),
         msg = _("Transcript validated"),
         source = TRANSREQ,
@@ -216,8 +216,15 @@
     Transition(
         transition_id = 'release_transcript',
-        title = _('Transcript released'),
+        title = _('Release transcript'),
         msg = _("Transcript released"),
         source = TRANSVAL,
         destination = TRANSREL),
+
+    Transition(
+        transition_id = 'reset11',
+        title = _('Reset to graduated'),
+        msg = _("Transcript process reset"),
+        source = TRANSREL,
+        destination = GRADUATED),
     )
 
