Index: main/kofacustom.edopoly/trunk/src/kofacustom/edopoly/applicants/applicant.py
===================================================================
--- main/kofacustom.edopoly/trunk/src/kofacustom/edopoly/applicants/applicant.py	(revision 16326)
+++ main/kofacustom.edopoly/trunk/src/kofacustom/edopoly/applicants/applicant.py	(revision 17370)
@@ -27,10 +27,11 @@
 from kofacustom.edopoly.applicants.interfaces import(
     ICustomApplicant, ICustomUGApplicantEdit, ICustomPGApplicantEdit,
-    IPUTMEApplicantEdit, ICustomSpecialApplicant)
+    IPUTMEApplicantEdit, ICustomSpecialApplicant, ITranscriptApplicant)
 
 class CustomApplicant(NigeriaApplicant):
 
     grok.implements(ICustomApplicant, ICustomUGApplicantEdit,
-        ICustomPGApplicantEdit, IPUTMEApplicantEdit, ICustomSpecialApplicant)
+        ICustomPGApplicantEdit, IPUTMEApplicantEdit, ICustomSpecialApplicant,
+        ITranscriptApplicant)
     grok.provides(ICustomApplicant)
 
Index: main/kofacustom.edopoly/trunk/src/kofacustom/edopoly/applicants/browser.py
===================================================================
--- main/kofacustom.edopoly/trunk/src/kofacustom/edopoly/applicants/browser.py	(revision 16326)
+++ main/kofacustom.edopoly/trunk/src/kofacustom/edopoly/applicants/browser.py	(revision 17370)
@@ -51,5 +51,5 @@
     ICustomUGApplicantEdit, ICustomUGApplicant,
     ICustomPGApplicantEdit, ICustomPGApplicant,
-    ICustomApplicant, ICustomSpecialApplicant,)
+    ICustomApplicant, ICustomSpecialApplicant, ITranscriptApplicant)
 
 from kofacustom.edopoly.interfaces import MessageFactory as _
@@ -81,4 +81,16 @@
     'aggregate')
 
+TRANS_OMIT_FIELDS = ('suspended',)
+
+TRANS_SHORT_OMIT_FIELDS = TRANS_OMIT_FIELDS
+
+TRANS_OMIT_EDIT_FIELDS = TRANS_OMIT_FIELDS + ('applicant_id', )
+
+TRANS_SHORT_OMIT_EDIT_FIELDS = TRANS_SHORT_OMIT_FIELDS + ('applicant_id', )
+
+TRANS_OMIT_PDF_FIELDS = TRANS_OMIT_FIELDS + ('locked', )
+
+TRANS_SHORT_OMIT_PDF_FIELDS = TRANS_SHORT_OMIT_FIELDS + ('locked', )
+
 class CustomApplicantDisplayFormPage(NigeriaApplicantDisplayFormPage):
     """A display view for applicant data.
@@ -89,4 +101,18 @@
     def form_fields(self):
         target = getattr(self.context.__parent__, 'prefix', None)
+        if target is not None and target == 'tscf':
+            form_fields = grok.AutoFields(ITranscriptApplicant)
+            for field in TRANS_OMIT_FIELDS:
+                form_fields = form_fields.omit(field)
+            form_fields['dispatch_address'].custom_widget = BytesDisplayWidget
+            #form_fields['perm_address'].custom_widget = BytesDisplayWidget
+            return form_fields
+        if target is not None and target == 'tscs':
+            form_fields = grok.AutoFields(ITranscriptApplicant)
+            for field in TRANS_SHORT_OMIT_FIELDS:
+                form_fields = form_fields.omit(field)
+            form_fields['dispatch_address'].custom_widget = BytesDisplayWidget
+            #form_fields['perm_address'].custom_widget = BytesDisplayWidget
+            return form_fields
         if self.context.special:
             return grok.AutoFields(ICustomSpecialApplicant)
@@ -223,9 +249,19 @@
     @property
     def form_fields(self):
+        target = getattr(self.context.__parent__, 'prefix', None)
+        if target is not None and target == 'tscf':
+            form_fields = grok.AutoFields(ITranscriptApplicant)
+            for field in TRANS_OMIT_EDIT_FIELDS:
+                form_fields = form_fields.omit(field)
+            return form_fields
+        if target is not None and target == 'tscs':
+            form_fields = grok.AutoFields(ITranscriptApplicant)
+            for field in TRANS_SHORT_OMIT_EDIT_FIELDS:
+                form_fields = form_fields.omit(field)
+            return form_fields
         if self.context.special:
             form_fields = grok.AutoFields(ICustomSpecialApplicant)
             form_fields['applicant_id'].for_display = True
             return form_fields
-        target = getattr(self.context.__parent__, 'prefix', None)
         form_fields = grok.AutoFields(ICustomUGApplicant)
         for field in UG_OMIT_MANAGE_FIELDS:
@@ -241,4 +277,26 @@
     @property
     def form_fields(self):
+        target = getattr(self.context.__parent__, 'prefix', None)
+        state = IWorkflowState(self.context).getState()
+        if target is not None and target == 'tscf':
+            form_fields = grok.AutoFields(ITranscriptApplicant)
+            form_fields['courier_tno'].for_display = True
+            form_fields['proc_date'].for_display = True
+            for field in TRANS_OMIT_EDIT_FIELDS:
+                form_fields = form_fields.omit(field)
+            if state == PAID:
+                form_fields['order'].for_display = True
+            form_fields = form_fields.omit('locked')
+            return form_fields
+        if target is not None and target == 'tscs':
+            form_fields = grok.AutoFields(ITranscriptApplicant)
+            form_fields['courier_tno'].for_display = True
+            form_fields['proc_date'].for_display = True
+            for field in TRANS_SHORT_OMIT_EDIT_FIELDS:
+                form_fields = form_fields.omit(field)
+            form_fields = form_fields.omit('locked')
+            if state == PAID:
+                form_fields['order'].for_display = True
+            return form_fields
         if self.context.special:
             form_fields = grok.AutoFields(ICustomSpecialApplicant).omit(
@@ -246,5 +304,4 @@
             form_fields['applicant_id'].for_display = True
             return form_fields
-        target = getattr(self.context.__parent__, 'prefix', None)
         form_fields = grok.AutoFields(ICustomUGApplicantEdit)
         for field in UG_OMIT_EDIT_FIELDS:
Index: main/kofacustom.edopoly/trunk/src/kofacustom/edopoly/applicants/interfaces.py
===================================================================
--- main/kofacustom.edopoly/trunk/src/kofacustom/edopoly/applicants/interfaces.py	(revision 16326)
+++ main/kofacustom.edopoly/trunk/src/kofacustom/edopoly/applicants/interfaces.py	(revision 17370)
@@ -1,2 +1,3 @@
+# -*- coding: utf-8 -*-
 ## $Id$
 ##
@@ -20,4 +21,8 @@
 
 from zope import schema
+from zope.interface import invariant, Invalid
+from zope.component import getUtility
+from zope.catalog.interfaces import ICatalog
+from zc.sourcefactory.basic import BasicSourceFactory
 from waeup.kofa.applicants.interfaces import (
     IApplicantBaseData,
@@ -25,6 +30,8 @@
 from waeup.kofa.schoolgrades import ResultEntryField
 from waeup.kofa.interfaces import (
-    SimpleKofaVocabulary, academic_sessions_vocab, validate_email)
-from waeup.kofa.schema import FormattedDate, TextLineChoice
+    SimpleKofaVocabulary, IKofaObject,
+    academic_sessions_vocab, validate_email)
+from waeup.kofa.university.vocabularies import StudyModeSource
+from waeup.kofa.schema import FormattedDate, TextLineChoice, PhoneNumber
 from waeup.kofa.students.vocabularies import nats_vocab, GenderSource
 from waeup.kofa.applicants.interfaces import (
@@ -41,4 +48,68 @@
 from kofacustom.edopoly.payments.interfaces import ICustomOnlinePayment
 
+class TranscriptCertificateSource(CertificateSource):
+    """Include Department and Faculty in Title.
+    """
+    def getValues(self, context):
+        catalog = getUtility(ICatalog, name='certificates_catalog')
+        resultset = catalog.searchResults(code=(None, None))
+        resultlist = sorted(resultset, key=lambda
+            value: value.__parent__.__parent__.__parent__.code +
+            value.__parent__.__parent__.code +
+            value.code)
+        return resultlist
+
+    def getTitle(self, context, value):
+        """
+        """
+        try: title = "%s / %s / %s (%s)" % (
+            value.__parent__.__parent__.__parent__.title,
+            value.__parent__.__parent__.title,
+            value.title, value.code)
+        except AttributeError:
+            title = "NA / %s (%s)" % (value.title, value.code)
+        return title
+
+DESTINATION_COST = {
+    #'none': ('To the moon', 1000000000.0, 1),
+    'nigeria': ('Within Nigeria', 20000.0, 1),
+    'africa': ('Within Africa ', 30000.0, 2),
+    'inter': ('International', 35000.0, 3),
+    'cert_nigeria': ('Certified Copy - Within Nigeria', 13000.0, 4),
+    'cert_africa': ('Certified Copy - Within Africa', 23000.0, 5),
+    'cert_inter': ('Certified Copy - International', 28000.0, 6),
+    }
+
+class DestinationCostSource(BasicSourceFactory):
+    """A source that delivers continents and shipment costs.
+    """
+    def getValues(self):
+        sorted_items = sorted(DESTINATION_COST.items(),
+                              key=lambda element: element[1][2])
+        return [item[0] for item in sorted_items]
+
+    def getToken(self, value):
+        return value
+
+    def getTitle(self, value):
+        return u"%s (₦ %s)" % (
+            DESTINATION_COST[value][0],
+            DESTINATION_COST[value][1])
+
+class OrderSource(BasicSourceFactory):
+    """
+    """
+    def getValues(self):
+        return ['o', 'c']
+
+    def getToken(self, value):
+        return value
+
+    def getTitle(self, value):
+        if value == 'o':
+            return _('Original')
+        if value == 'c':
+            return _('Certified True Copy')
+
 class ICustomUGApplicant(IApplicantBaseData):
     """An undergraduate applicant.
@@ -218,4 +289,198 @@
         )
 
+class ITranscriptApplicant(IKofaObject):
+    """A transcript applicant.
+    """
+
+    suspended = schema.Bool(
+        title = _(u'Account suspended'),
+        default = False,
+        required = False,
+        )
+
+    locked = schema.Bool(
+        title = _(u'Form locked'),
+        default = False,
+        required = False,
+        )
+
+    applicant_id = schema.TextLine(
+        title = _(u'Transcript Application Id'),
+        required = False,
+        readonly = False,
+        )
+
+    student_id = schema.TextLine(
+        title = _(u'Kofa Student Id'),
+        required = False,
+        readonly = False,
+        )
+
+    matric_number = schema.TextLine(
+        title = _(u'Matriculation Number'),
+        readonly = False,
+        required = True,
+        )
+
+    firstname = schema.TextLine(
+        title = _(u'First Name in School'),
+        required = True,
+        )
+
+    middlename = schema.TextLine(
+        title = _(u'Middle Name in School'),
+        required = False,
+        )
+
+    lastname = schema.TextLine(
+        title = _(u'Surname in School'),
+        required = True,
+        )
+
+    date_of_birth = FormattedDate(
+        title = _(u'Date of Birth'),
+        required = False,
+        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
+        show_year = True,
+        )
+
+    sex = schema.Choice(
+        title = _(u'Gender'),
+        source = GenderSource(),
+        required = True,
+        )
+
+    #nationality = schema.Choice(
+    #    vocabulary = nats_vocab,
+    #    title = _(u'Nationality'),
+    #    required = False,
+    #    )
+
+    email = schema.ASCIILine(
+        title = _(u'Email Address'),
+        required = True,
+        constraint=validate_email,
+        )
+
+    phone = PhoneNumber(
+        title = _(u'Phone'),
+        description = u'',
+        required = False,
+        )
+
+    #perm_address = schema.Text(
+    #    title = _(u'Current Local Address'),
+    #    required = False,
+    #    readonly = False,
+    #    )
+
+    collected = schema.Bool(
+        title = _(u'Have you collected transcript before?'),
+        default = False,
+        required = False,
+        )
+
+    entry_session = schema.Choice(
+        title = _(u'Academic Session of Entry'),
+        source = academic_sessions_vocab,
+        required = False,
+        readonly = False,
+        )
+
+    end_session = schema.Choice(
+        title = _(u'Academic Session of Graduation'),
+        source = academic_sessions_vocab,
+        required = False,
+        readonly = False,
+        )
+
+    entry_mode = schema.Choice(
+        title = _(u'Mode of Entry'),
+        source = StudyModeSource(),
+        required = False,
+        readonly = False,
+        )
+
+    course_studied = schema.Choice(
+        title = _(u'Course of Study'),
+        source = TranscriptCertificateSource(),
+        description = u'Faculty / Department / Course',
+        required = True,
+        readonly = False,
+        )
+
+    course_changed = schema.Choice(
+        title = _(u'Change of Study Course / Transfer'),
+        description = u'If yes, select previous course of study.',
+        source = TranscriptCertificateSource(),
+        readonly = False,
+        required = False,
+        )
+
+    spillover_level = schema.Choice(
+        title = _(u'Spill-over'),
+        description = u'Any spill-over? If yes, select session of spill-over.',
+        source = academic_sessions_vocab,
+        required = False,
+        readonly = False,
+        )
+
+    purpose = schema.TextLine(
+        title = _(u'Transcript Purpose'),
+        required = False,
+        )
+
+    order = schema.Choice(
+        source = OrderSource(),
+        title = _(u'Type of Order'),
+        required = False,
+        )
+
+    dispatch_address = schema.Text(
+        title = _(u'Recipient Body'),
+        description = u'Addresses (including email address and phone number) '
+                       'to which transcripts should be posted. '
+                       'All addresses must involve same courier charges.',
+        required = True,
+        readonly = False,
+        )
+
+    charge = schema.Choice(
+        title = _(u'Transcript Charge'),
+        source = DestinationCostSource(),
+        required = False,
+        readonly = False,
+        )
+
+    no_copies = schema.Choice(
+        title = _(u'Number of Copies'),
+        description = u'Must correspond with the number of dispatch addresses above.',
+        values=[1, 2, 3, 4],
+        required = False,
+        readonly = False,
+        default = 1,
+        )
+
+    courier_tno = schema.TextLine(
+        title = _(u'Courier Tracking Number'),
+        required = False,
+        )
+
+    proc_date = FormattedDate(
+        title = _(u'Processing Date'),
+        required = False,
+        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
+        show_year = True,
+        )
+
+    @invariant
+    def type_of_order(applicant):
+        if not applicant.collected and applicant.order != 'o':
+            raise Invalid(_("If you haven't collected transcript before, type of order must be 'original'."))
+        if applicant.order == 'o' and applicant.charge.startswith('cert_'):
+            raise Invalid(_("You've selected the wrong transcript charge."))
+        if applicant.order == 'c' and not applicant.charge.startswith('cert_'):
+            raise Invalid(_("You've selected the wrong transcript charge."))
+
 class ICustomPGApplicant(INigeriaPGApplicant):
     """A postgraduate applicant.
@@ -230,5 +495,6 @@
     """
 
-class ICustomApplicant(ICustomUGApplicant, ICustomPGApplicant):
+class ICustomApplicant(ICustomUGApplicant, ICustomPGApplicant,
+    ITranscriptApplicant):
     """An interface for both types of applicants.
 
Index: main/kofacustom.edopoly/trunk/src/kofacustom/edopoly/applicants/utils.py
===================================================================
--- main/kofacustom.edopoly/trunk/src/kofacustom/edopoly/applicants/utils.py	(revision 16326)
+++ main/kofacustom.edopoly/trunk/src/kofacustom/edopoly/applicants/utils.py	(revision 17370)
@@ -45,4 +45,6 @@
         'hnde': ['HND Part-Time Evening Programmes', 'HNDE'],
         'nde': ['ND Part-Time Evening Programmes', 'NDE'],
+        'tscf': ['Transcript (without student record)', 'TRF'],
+        'tscs': ['Transcript (with student record)', 'TRS'],
         }
 
