Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 10456)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 10458)
@@ -20,4 +20,5 @@
 import sys
 import grok
+import pytz
 from urllib import urlencode
 from datetime import datetime
@@ -64,5 +65,5 @@
 from waeup.kofa.university.vocabularies import course_levels
 from waeup.kofa.utils.batching import VirtualExportJobContainer
-from waeup.kofa.utils.helpers import get_current_principal, to_timezone
+from waeup.kofa.utils.helpers import get_current_principal, to_timezone, now
 from waeup.kofa.widgets.restwidget import ReSTDisplayWidget
 from waeup.kofa.students.interfaces import (
@@ -74,5 +75,5 @@
     ICourseTicket, ICourseTicketAdd, IStudentPaymentsContainer,
     IStudentOnlinePayment, IStudentPreviousPayment, IStudentBalancePayment,
-    IBedTicket, IStudentsUtils, IStudentRequestPW
+    IBedTicket, IStudentsUtils, IStudentRequestPW, IStudentTranscript
     )
 from waeup.kofa.students.catalog import search, StudentQueryResultItem
@@ -82,5 +83,5 @@
 from waeup.kofa.students.workflow import (CREATED, ADMITTED, PAID,
     CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, VALIDATED,
-    FORBIDDEN_POSTGRAD_TRANS)
+    GRADUATED, TRANSCRIPT, FORBIDDEN_POSTGRAD_TRANS)
 
 
@@ -1061,4 +1062,119 @@
                 self,'removed: %s' % ', '.join(deleted))
         self.redirect(self.url(self.context, u'@@manage')+'?tab2')
+        return
+
+class StudentRequestTranscriptPage(KofaPage):
+    """ Page to transcript by student
+    """
+    grok.context(IStudent)
+    grok.name('request_transcript')
+    grok.require('waeup.handleStudent')
+    grok.template('requesttranscript')
+    label = _('Request transcript')
+    ac_prefix = 'TSC'
+    notice = ''
+    pnav = 4
+    buttonname = _('Request transcript now')
+    with_ac = True
+
+    def update(self, SUBMIT=None):
+        if not self.context.state == GRADUATED:
+            self.flash(_("Wrong state"))
+            self.redirect(self.url(self.context))
+            return
+        if self.with_ac:
+            self.ac_series = self.request.form.get('ac_series', None)
+            self.ac_number = self.request.form.get('ac_number', None)
+        if self.context.transcript_comment is not None:
+            self.correspondence = self.context.transcript_comment.replace(
+                '\n', '<br>')
+        else:
+            self.correspondence = ''
+        if SUBMIT is None:
+            return
+        if self.with_ac:
+            pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
+            code = get_access_code(pin)
+            if not code:
+                self.flash(_('Activation code is invalid.'))
+                return
+            if code.state == USED:
+                self.flash(_('Activation code has already been used.'))
+                return
+            # Mark pin as used (this also fires a pin related transition)
+            # and fire transition start_clearance
+            comment = _(u"invalidated")
+            # Here we know that the ac is in state initialized so we do not
+            # expect an exception, but the owner might be different
+            if not invalidate_accesscode(pin, comment, self.context.student_id):
+                self.flash(_('You are not the owner of this access code.'))
+                return
+            self.context.clr_code = pin
+        IWorkflowInfo(self.context).fireTransition('request_transcript')
+        comment = self.request.form.get('comment', '').replace('\r', '')
+        address = self.request.form.get('address', '').replace('\r', '')
+        tz = getattr(queryUtility(IKofaUtils), 'tzinfo', pytz.utc)
+        today = now(tz).strftime('%d/%m/%Y %H:%M:%S %Z')
+        old_transcript_comment = self.context.transcript_comment
+        if old_transcript_comment == None:
+            old_transcript_comment = ''
+        self.context.transcript_comment = '''On %s %s wrote:
+
+%s
+
+Dispatch Address:
+%s
+
+%s''' % (today, self.request.principal.id, comment, address,
+         old_transcript_comment)
+        self.context.writeLogMessage(
+            self, 'comment: %s' % comment.replace('\n', '<br>'))
+        self.flash(_('Transcript processing has been started.'))
+        self.redirect(self.url(self.context))
+        return
+
+class StudentRequestTranscriptManageFormPage(KofaEditFormPage):
+    """ Page to edit personal data by student
+    """
+    grok.context(IStudent)
+    grok.name('manage_transcript_request')
+    grok.require('waeup.manageStudent')
+    form_fields = grok.AutoFields(IStudentTranscript)
+    label = _('Manage transcript request')
+    pnav = 4
+
+    def update(self):
+        super(StudentRequestTranscriptManageFormPage, self).update()
+        if self.context.state != TRANSCRIPT:
+            self.flash(_("Wrong state"))
+            self.redirect(self.url(self.context))
+        return
+
+    #@action(_('Save'), style='primary')
+    #def save(self, **data):
+    #    msave(self, **data)
+    #    return
+
+    @action(_('Save comment and mark as processed'), style='primary')
+    def process(self, **data):
+        if self.context.state == TRANSCRIPT:
+            IWorkflowInfo(self.context).fireTransition('process_transcript')
+            message = _('Transcript request processed.')
+            self.flash(message)
+        else:
+            self.flash(_('Student is in wrong state.'))
+            self.redirect(self.url(self.context))
+            return
+        self.applyData(self.context, **data)
+        comment = data['transcript_comment']
+        if comment:
+            self.context.writeLogMessage(
+                self, 'comment: %s' %
+                comment.replace('\r\n', '<br>'))
+            args = {'subject':message, 'body':comment}
+        else:
+            args = {'subject':message,}
+        self.redirect(self.url(self.context) +
+            '/contactstudent?%s' % urlencode(args))
         return
 
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/requesttranscript.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/requesttranscript.pt	(revision 10458)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/requesttranscript.pt	(revision 10458)
@@ -0,0 +1,57 @@
+<form method="POST" i18n:domain="waeup.kofa">
+  <table class="form-table" tal:condition="view/with_ac">
+    <tbody>
+      <tr>
+        <td i18n:translate="">
+        Comment:
+        </td>
+        <td>
+        <textarea cols="60" name="comment" rows="6"></textarea>
+        </td>
+      </tr>
+      <tr>
+        <td i18n:translate="">
+        Dispatch Address:
+        </td>
+        <td>
+        <textarea cols="60" name="address" rows="6"></textarea>
+        </td>
+      </tr>
+      <tr>
+        <td i18n:translate="">
+        Activation Code:
+        </td>
+        <td>
+        <span tal:replace="view/ac_prefix">PIN Prefix</span> -
+        <input name="ac_series" type="text" class="span1" maxlength="3"
+               value="" tal:attributes="value view/ac_series"/> -
+        <input name="ac_number" type="text" class="span3" maxlength="10"
+               value="" tal:attributes="value view/ac_number" />
+        <font color="red"><span tal:replace="view/notice">Notice</span>
+        </font>
+        </td>
+      </tr>
+      <tr>
+        <td>&nbsp;</td>
+        <td i18n:translate="">
+        The activation code can be found on the payment slip.
+        </td>
+      </tr>
+      <tr>
+        <td colspan="2">
+          <input type="submit" name="SUBMIT" class="btn primary"
+                 tal:attributes="value view/buttonname" />
+        </td>
+      </tr>
+    </tbody>
+  </table>
+
+  <span tal:omit-tag="" tal:condition="not: view/with_ac">
+    <input type="submit" name="SUBMIT" class="btn primary"
+           tal:attributes="value view/buttonname" />
+  </span>
+
+  <h1 i18n:translate="">Correspondence</h1>
+  <p tal:content="structure view/correspondence"></p>
+
+</form>
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 10456)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 10458)
@@ -336,9 +336,7 @@
     """
 
-    transcript_address = schema.Text(
-        title = _(u'Address'),
-        required = False,
-        description = _(
-            u'Enter address the transcript has been shipped to.'),
+    transcript_comment = schema.Text(
+        title = _(u'Comment'),
+        required = False,
         )
 
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_export.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_export.py	(revision 10456)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_export.py	(revision 10458)
@@ -141,5 +141,5 @@
             'nationality,officer_comment,perm_address,personal_updated,'
             'phone,reg_number,sex,student_id,suspended,suspended_comment,'
-            'transcript_address,password,state,history,certcode,is_postgrad,'
+            'transcript_comment,password,state,history,certcode,is_postgrad,'
             'current_level,current_session\r\n'
             'my adm code,0,my clr code,'
@@ -163,5 +163,5 @@
             'nationality,officer_comment,perm_address,personal_updated,'
             'phone,reg_number,sex,student_id,suspended,suspended_comment,'
-            'transcript_address,password,state,history,certcode,'
+            'transcript_comment,password,state,history,certcode,'
             'is_postgrad,current_level,current_session\r\n'
             'my adm code,0,my clr code,1981-02-04#,anna@sample.com,,'
@@ -183,5 +183,5 @@
             'nationality,officer_comment,perm_address,personal_updated,'
             'phone,reg_number,sex,student_id,suspended,suspended_comment,'
-            'transcript_address,password,state,history,certcode,'
+            'transcript_comment,password,state,history,certcode,'
             'is_postgrad,current_level,current_session\r\n'
             'my adm code,0,my clr code,1981-02-04#,anna@sample.com,,'
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/viewlets.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/viewlets.py	(revision 10456)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/viewlets.py	(revision 10458)
@@ -33,5 +33,5 @@
 from waeup.kofa.students.workflow import (
     ADMITTED, PAID, REQUESTED, RETURNING, CLEARED, REGISTERED,
-    VALIDATED)
+    VALIDATED, GRADUATED, TRANSCRIPT)
 from waeup.kofa.students.browser import (
     clearance_disabled_message,
@@ -675,4 +675,34 @@
     @property
     def target_url(self):
+        return self.view.url(self.view.context, self.target)
+
+class RequestTranscriptActionButton(ManageActionButton):
+    grok.order(8)
+    grok.context(IStudent)
+    grok.view(StudentBaseDisplayFormPage)
+    grok.require('waeup.handleStudent')
+    text = _('Request transcript')
+    target = 'request_transcript'
+    icon = 'actionicon_transcript.png'
+
+    @property
+    def target_url(self):
+        if self.context.state != GRADUATED:
+            return ''
+        return self.view.url(self.view.context, self.target)
+
+class ManageTranscriptRequestActionButton(ManageActionButton):
+    grok.order(9)
+    grok.context(IStudent)
+    grok.view(StudentBaseDisplayFormPage)
+    grok.require('waeup.manageStudent')
+    text = _('Manage transcript request')
+    target = 'manage_transcript_request'
+    icon = 'actionicon_transcript.png'
+
+    @property
+    def target_url(self):
+        if self.context.state != TRANSCRIPT:
+            return ''
         return self.view.url(self.view.context, self.target)
 
