Index: main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/interfaces.py
===================================================================
--- main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/interfaces.py	(revision 15127)
+++ main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/interfaces.py	(revision 15128)
@@ -56,5 +56,7 @@
 VALIDATED = 'courses validated'
 GRADUATED = 'graduated'
-TRANSCRIPT = 'transcript requested'
+TRANSREQ = 'transcript requested'
+TRANSVAL = 'transcript validated'
+TRANSREL = 'transcript released'
 
 
@@ -103,5 +105,7 @@
     (_('returning'), RETURNING),
     (_('graduated'), GRADUATED),
-    (_('transcript requested'), TRANSCRIPT),
+    (_('transcript requested'), TRANSREQ),
+    (_('transcript validated'), TRANSVAL),
+    (_('transcript released'), TRANSREL),
     )
 
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 15127)
+++ main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/browser.py	(revision 15128)
@@ -78,5 +78,5 @@
 from waeup.kofa.students.workflow import (
     ADMITTED, PAID, CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED,
-    VALIDATED, GRADUATED, TRANSCRIPT, FORBIDDEN_POSTGRAD_TRANS
+    VALIDATED, GRADUATED, TRANSREQ, TRANSVAL, TRANSREL, FORBIDDEN_POSTGRAD_TRANS
     )
 
@@ -1146,19 +1146,19 @@
         return
 
-class StudentTranscriptRequestProcessFormPage(KofaEditFormPage):
-    """ Page to process transcript requests
+class StudentTranscriptReleasesFormPage(KofaEditFormPage):
+    """ Page to release transcript
     """
     grok.context(IStudent)
-    grok.name('process_transcript_request')
+    grok.name('release_transcript')
     grok.require('waeup.viewTranscript')
-    grok.template('transcriptprocess')
+    grok.template('transcriptrelease')
     form_fields = grok.AutoFields(IStudentTranscript)
-    label = _('Process transcript request')
-    buttonname = _('Save comment and mark as processed')
+    label = _('Release transcript')
+    buttonname = _('Save comment and mark as released')
     pnav = 4
 
     def update(self, SUBMIT=None):
-        super(StudentTranscriptRequestProcessFormPage, self).update()
-        if self.context.state != TRANSCRIPT:
+        super(StudentTranscriptReleasesFormPage, self).update()
+        if self.context.state != TRANSVAL:
             self.flash(_('Student is in wrong state.'), type="warning")
             self.redirect(self.url(self.context))
@@ -1171,6 +1171,7 @@
         if SUBMIT is None:
             return
-        IWorkflowInfo(self.context).fireTransition('process_transcript')
-        self.flash(_('Transcript request processed.'))
+        # Fire transition
+        IWorkflowInfo(self.context).fireTransition('release_transcript')
+        self.flash(_('Transcript released.'))
         comment = self.request.form.get('comment', '').replace('\r', '')
         tz = getattr(queryUtility(IKofaUtils), 'tzinfo', pytz.utc)
@@ -1187,8 +1188,12 @@
         self.context.writeLogMessage(
             self, 'comment: %s' % comment.replace('\n', '<br>'))
-        subject = _('Transcript processed')
-        args = {'subject':subject, 'body':comment}
-        self.redirect(self.url(self.context) +
-            '/contactstudent?%s' % urlencode(args))
+        # 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
 
@@ -1276,4 +1281,9 @@
     def _signatures(self):
         return None
+
+    def _save_file(self):
+        if self.context.student.state == TRANSREL:
+            return True
+        return False
 
     def render(self):
@@ -1304,4 +1314,5 @@
             signatures=self._signatures(),
             sigs_in_footer=self._sigsInFooter(),
+            save_file=self._save_file(),
             )
 
Index: main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/browser_templates/transcriptprocess.pt
===================================================================
--- main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/browser_templates/transcriptprocess.pt	(revision 15127)
+++ 	(revision )
@@ -1,45 +1,0 @@
-<form method="POST" i18n:domain="waeup.kofa">
-
-  <ul class="tabs nav nav-tabs" data-tabs="tabs">
-    <li class="active">
-      <a href="#tab1" data-toggle="tab">
-        <span i18n:translate="">Process request</span>
-      </a>
-    </li>
-    <li>
-      <a href="#tab2" data-toggle="tab">
-        <span i18n:translate="">View correspondence</span>
-      </a>
-    </li>
-  </ul>
-
-  <div class="tab-content">
-    <div id="tab1" class="active tab-pane">
-
-      <table class="form-table">
-        <tbody>
-          <tr>
-            <td i18n:translate="">
-            Comment:
-            </td>
-            <td>
-            <textarea cols="60" name="comment" rows="6"></textarea>
-            </td>
-          </tr>
-          <tr>
-            <td colspan="2">
-              <br />
-              <input type="submit" name="SUBMIT" class="btn btn-primary"
-                     tal:attributes="value view/buttonname" />
-            </td>
-          </tr>
-        </tbody>
-      </table>
-    </div>
-
-    <div id="tab2" class="tab-pane">
-      <p tal:content="structure view/correspondence"></p>
-    </div>
-  </div>
-
-</form>
Index: main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/browser_templates/transcriptrelease.pt
===================================================================
--- main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/browser_templates/transcriptrelease.pt	(revision 15128)
+++ main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/browser_templates/transcriptrelease.pt	(revision 15128)
@@ -0,0 +1,45 @@
+<form method="POST" i18n:domain="waeup.kofa">
+
+  <ul class="tabs nav nav-tabs" data-tabs="tabs">
+    <li class="active">
+      <a href="#tab1" data-toggle="tab">
+        <span i18n:translate="">Process request</span>
+      </a>
+    </li>
+    <li>
+      <a href="#tab2" data-toggle="tab">
+        <span i18n:translate="">View correspondence</span>
+      </a>
+    </li>
+  </ul>
+
+  <div class="tab-content">
+    <div id="tab1" class="active tab-pane">
+
+      <table class="form-table">
+        <tbody>
+          <tr>
+            <td i18n:translate="">
+            Comment:
+            </td>
+            <td>
+            <textarea cols="60" name="comment" rows="6"></textarea>
+            </td>
+          </tr>
+          <tr>
+            <td colspan="2">
+              <br />
+              <input type="submit" name="SUBMIT" class="btn btn-primary"
+                     tal:attributes="value view/buttonname" />
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <div id="tab2" class="tab-pane">
+      <p tal:content="structure view/correspondence"></p>
+    </div>
+  </div>
+
+</form>
Index: main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/reports/tests/test_student_statistics.py
===================================================================
--- main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/reports/tests/test_student_statistics.py	(revision 15127)
+++ main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/reports/tests/test_student_statistics.py	(revision 15128)
@@ -21,5 +21,6 @@
               'clearance requested', 'cleared', 'school fee paid',
               'courses registered', 'courses validated', 'returning',
-              'graduated', 'transcript requested', 'Total')
+              'graduated', 'transcript requested', 'transcript validated', 
+              'transcript released', 'Total')
 
     def test_iface(self):
@@ -41,12 +42,12 @@
             ((u'fac1', u'Total',),
              self.states,
-             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
-              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
+             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
+              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
         self.assertEqual(
             result2,
             ((u'fac1', u'Total'),
              self.states,
-             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
-              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
+             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
         return
 
@@ -62,14 +63,14 @@
             ((u'fac1', u'fac2', u'Total'),
              self.states,
-             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
-              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
-              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
+             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
         self.assertEqual(
             result2,
             ((u'fac1', u'fac2', u'Total'),
              self.states,
-             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
-              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
-              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
+             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
         return
 
@@ -83,14 +84,14 @@
             ((u'fac1/dep1', u'fac2/dep2', u'Total'),
              self.states,
-             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
-              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
-              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
-        self.assertEqual(
-            result2,
-            ((u'fac1/dep1', u'fac2/dep2', u'Total'),
-             self.states,
-             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
-              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
-              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
+             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
+        self.assertEqual(
+            result2,
+            ((u'fac1/dep1', u'fac2/dep2', u'Total'),
+             self.states,
+             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
         return
 
@@ -104,14 +105,14 @@
             ((u'fac1/dep1', u'fac2/dep2', u'Total'),
              self.states,
-             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
-              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
-              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
-        self.assertEqual(
-            result2,
-            ((u'fac1/dep1', u'fac2/dep2', u'Total'),
-             self.states,
-             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
-              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
-              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
+             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
+        self.assertEqual(
+            result2,
+            ((u'fac1/dep1', u'fac2/dep2', u'Total'),
+             self.states,
+             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
         return
 
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 15127)
+++ main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/tests/test_browser.py	(revision 15128)
@@ -41,4 +41,5 @@
 from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
 from waeup.kofa.app import University
+from waeup.kofa.interfaces import IFileStoreNameChooser, IExtFileStore
 from waeup.kofa.payments.interfaces import IPayer
 from waeup.kofa.students.payments import StudentOnlinePayment
@@ -2119,5 +2120,5 @@
         print "Sample PDF transcript.pdf written to %s" % path
 
-    def test_process_transcript_request(self):
+    def test_release_transcript(self):
         IWorkflowState(self.student).setState('transcript requested')
         notify(grok.ObjectModifiedEvent(self.student))
@@ -2143,5 +2144,5 @@
             '...<div>Transcript Officer</div>...',
             self.browser.contents)
-        # Officer can search for students in state 'transcripr requested'
+        # Officer can search for students in state 'transcript requested'
         self.browser.open(self.container_path)
         self.browser.getControl(name="searchtype").value = ['transcript']
@@ -2149,5 +2150,17 @@
         self.assertTrue('Anna Tester' in self.browser.contents)
         self.browser.getLink("K1000000").click()
-        self.browser.getLink("Manage transcript request").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)
+        self.assertTrue('Release transcript' in self.browser.contents)
+        # Now the transcript can be released.
+        self.browser.getLink("Release transcript").click()
         self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
         'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
@@ -2155,5 +2168,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 processed").click()
+        self.browser.getControl("Save comment and mark as released").click()
         self.assertTrue(
             'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
@@ -2165,8 +2178,27 @@
         logcontent = open(logfile).read()
         self.assertTrue(
-            'mrtranscript - students.browser.StudentTranscriptRequestProcessFormPage - '
+            'mrtranscript - students.browser.StudentTranscriptReleasesFormPage - '
             'K1000000 - comment: Hello,<br>'
             'Your transcript has been sent to the address provided'
             in logcontent)
+        # File has been stored in the file system
+        # Check if transcript exists in the file system and is a PDF file
+        storage = getUtility(IExtFileStore)
+        file_id = IFileStoreNameChooser(
+            self.student).chooseName(attr='transcript.pdf')
+        pdf = storage.getFile(file_id).read()
+        self.assertTrue(len(pdf) > 0)
+        self.assertEqual(pdf[:8], '%PDF-1.4')
+        # Copy the file to samples_dir
+        path = os.path.join(samples_dir(), 'final_transcript.pdf')
+        open(path, 'wb').write(pdf)
+        print "Sample PDF final_transcript.pdf written to %s" % path
+        # Check if there is an transcript pdf link in UI
+        self.browser.open(self.student_path)
+        self.assertTrue('Final Transcript' in self.browser.contents)
+        self.browser.getLink("Final Transcript").click()
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertEqual(self.browser.headers['Content-Type'],
+                         'application/pdf')
 
 class StudentUITests(StudentsFullSetup):
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 15127)
+++ main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/utils.py	(revision 15128)
@@ -20,4 +20,5 @@
 import grok
 from time import time
+from cStringIO import StringIO
 from reportlab.lib import colors
 from reportlab.lib.units import cm
@@ -33,5 +34,5 @@
 from waeup.kofa.interfaces import (
     IExtFileStore, IKofaUtils, RETURNING, PAID, CLEARED,
-    academic_sessions_vocab)
+    academic_sessions_vocab, IFileStoreNameChooser)
 from waeup.kofa.interfaces import MessageFactory as _
 from waeup.kofa.students.interfaces import IStudentsUtils
@@ -854,4 +855,13 @@
             level_obj.cumulative_params[0], level_obj.student)[0]
 
+    def _saveTranscriptPDF(self, student, transcript):
+        """Create a transcript PDF file and store it in student folder.
+        """
+        file_store = getUtility(IExtFileStore)
+        file_id = IFileStoreNameChooser(student).chooseName(
+            attr="transcript.pdf")
+        file_store.createFile(file_id, StringIO(transcript))
+        return
+
     def renderPDFTranscript(self, view, filename='transcript.pdf',
                   student=None,
@@ -861,5 +871,6 @@
                   omit_fields=(),
                   tableheader=None,
-                  no_passport=False):
+                  no_passport=False,
+                  save_file=False):
         """Render pdf slip of a transcripts.
         """
@@ -966,4 +977,6 @@
             view.flash(_('Error in image file.'))
             return view.redirect(view.url(view.context))
+        if save_file:
+            self._saveTranscriptPDF(student, pdf_stream)
         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 15127)
+++ main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/viewlets.py	(revision 15128)
@@ -40,5 +40,5 @@
 from waeup.kofa.students.workflow import (
     ADMITTED, PAID, REQUESTED, CLEARED, REGISTERED, VALIDATED, GRADUATED,
-    TRANSCRIPT)
+    TRANSREQ, TRANSVAL)
 from waeup.kofa.university.interfaces import ICourse
 
@@ -96,5 +96,4 @@
         return ''
 
-
 class StudentManageBaseLink(StudentManageLink):
     grok.order(2)
@@ -142,4 +141,22 @@
     link = 'history'
     text = _(u'History')
+
+
+class StudentManageTranscriptLink(StudentManageLink):
+    grok.order(9)
+    link = 'transcript.pdf'
+    text = _(u'Final Transcript')
+
+    def render(self):
+        slip = getUtility(IExtFileStore).getFileByContext(
+            self.context.student, attr=self.link)
+        if slip:
+            lang = self.request.cookies.get('kofa.language')
+            text = translate(
+                self.text, 'waeup.kofa', target_language=lang)
+            url = self.view.url(self.context.student['studycourse'], self.link)
+            return u'<li><a href="%s">%s</a></li>' % (
+                url, text)
+        return ''
 
 
@@ -759,16 +776,16 @@
 
 
-class ProcessTranscriptRequestActionButton(ManageActionButton):
+class ReleaseTranscriptActionButton(ManageActionButton):
     grok.order(9)
     grok.context(IStudent)
     grok.view(StudentBaseDisplayFormPage)
     grok.require('waeup.viewTranscript')
-    text = _('Manage transcript request')
-    target = 'process_transcript_request'
+    text = _('Release transcript')
+    target = 'release_transcript'
     icon = 'actionicon_transcript.png'
 
     @property
     def target_url(self):
-        if self.context.state != TRANSCRIPT:
+        if self.context.state != TRANSVAL:
             return ''
         return self.view.url(self.view.context, self.target)
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 15127)
+++ main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/workflow.py	(revision 15128)
@@ -9,5 +9,5 @@
     IObjectHistory, IKofaWorkflowInfo, IKofaUtils,
     CREATED, ADMITTED, CLEARANCE, REQUESTED, CLEARED, PAID, RETURNING,
-    REGISTERED, VALIDATED, GRADUATED, TRANSCRIPT)
+    REGISTERED, VALIDATED, GRADUATED, TRANSREQ, TRANSVAL, TRANSREL)
 from waeup.kofa.interfaces import MessageFactory as _
 from waeup.kofa.workflow import KofaWorkflow, KofaWorkflowInfo
@@ -198,12 +198,26 @@
         msg = _("Transcript requested"),
         source = GRADUATED,
-        destination = TRANSCRIPT),
-
-    Transition(
-        transition_id = 'process_transcript',
-        title = _('Transcript processed'),
-        msg = _("Transcript processed"),
-        source = TRANSCRIPT,
+        destination = TRANSREQ),
+
+    Transition(
+        transition_id = 'reset10',
+        title = _('Reject transcript request'),
+        msg = _("Transcript request rejected"),
+        source = TRANSREQ,
         destination = GRADUATED),
+
+    Transition(
+        transition_id = 'validate_transcript',
+        title = _('Transcript validated'),
+        msg = _("Transcript validated"),
+        source = TRANSREQ,
+        destination = TRANSVAL),
+
+    Transition(
+        transition_id = 'release_transcript',
+        title = _('Transcript released'),
+        msg = _("Transcript released"),
+        source = TRANSVAL,
+        destination = TRANSREL),
     )
 
