Index: main/kofacustom.dspg/trunk/src/kofacustom/dspg/interfaces.py
===================================================================
--- main/kofacustom.dspg/trunk/src/kofacustom/dspg/interfaces.py	(revision 15287)
+++ main/kofacustom.dspg/trunk/src/kofacustom/dspg/interfaces.py	(revision 15288)
@@ -410,4 +410,9 @@
         )
 
+    lsfp_penalty_fee = schema.Float(
+        title = _(u'HND/ND FT Fresh Student Late School Fee Payment Penalty Fee'),
+        default = 0.0,
+        required = True,
+        )
 
     def getSessionString():
Index: main/kofacustom.dspg/trunk/src/kofacustom/dspg/locales/en/LC_MESSAGES/waeup.kofa.po
===================================================================
--- main/kofacustom.dspg/trunk/src/kofacustom/dspg/locales/en/LC_MESSAGES/waeup.kofa.po	(revision 15287)
+++ main/kofacustom.dspg/trunk/src/kofacustom/dspg/locales/en/LC_MESSAGES/waeup.kofa.po	(revision 15288)
@@ -40,29 +40,31 @@
 
 msgid "Next Matriculation Number Integer"
-msgstr "Next ND Matriculation Number Integer"
+msgstr "Next ND FT Matriculation Number Integer"
 
 msgid "Integer used for constructing the next matriculation number"
 msgstr ""
-"Integer used for constructing the next matriculation number of ND students"
+"Integer used for constructing the next matriculation number of ND FT students"
 
 msgid "Next Matriculation Number Integer 2"
+msgstr "Next HND FT Matriculation Number Integer"
+
+msgid "Next Matriculation Number Integer 3"
 msgstr "Next ND PT Matriculation Number Integer"
 
-msgid "Next Matriculation Number Integer 3"
-msgstr "Next HND Matriculation Number Integer"
-
 msgid "Next Matriculation Number Integer 4"
-msgstr "Next HND PT Matriculation Number Integer"
+msgstr "Next HND PT Matriculation Number Integer "
 
 msgid "2nd integer used for constructing the next matriculation number"
 msgstr ""
-"Integer used for constructing the next matriculation number of ND PT students"
+"Integer used for constructing the next matriculation number of HND FT "
+"students"
 
 msgid "3rd integer used for constructing the next matriculation number"
 msgstr ""
-"Integer used for constructing the next matriculation number of HND students"
+"Integer used for constructing the next matriculation number of ND PT "
+"students (start value 10001)"
 
 msgid "4th integer used for constructing the next matriculation number"
 msgstr ""
 "Integer used for constructing the next matriculation number of HND PT "
-"students"
+"students (start value 10001)"
Index: main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/browser.py
===================================================================
--- main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/browser.py	(revision 15287)
+++ main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/browser.py	(revision 15288)
@@ -23,6 +23,6 @@
 from waeup.kofa.interfaces import REQUESTED, IExtFileStore, IKofaUtils
 from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
-from waeup.kofa.browser.layout import KofaEditFormPage
-from waeup.kofa.browser.layout import action, jsaction
+from waeup.kofa.browser.layout import (
+    KofaEditFormPage, UtilityView, action, jsaction)
 from waeup.kofa.students.browser import (
     StudyLevelEditFormPage, StudyLevelDisplayFormPage,
@@ -31,5 +31,6 @@
     ExportPDFTranscriptSlip, BedTicketAddPage,
     msave, emit_lock_message)
-from waeup.kofa.students.interfaces import IStudentsUtils, ICourseTicket
+from waeup.kofa.students.interfaces import (
+    IStudentsUtils, ICourseTicket, IStudent)
 from waeup.kofa.students.workflow import FORBIDDEN_POSTGRAD_TRANS
 from kofacustom.nigeria.students.browser import (
@@ -42,8 +43,7 @@
     NigeriaAccommodationManageFormPage,
     )
-
 from kofacustom.dspg.students.interfaces import (
     ICustomStudentOnlinePayment, ICustomStudentStudyCourse,
-    ICustomStudentStudyLevel)
+    ICustomStudentStudyLevel, ICustomStudent)
 from kofacustom.dspg.interfaces import MessageFactory as _
 
@@ -176,2 +176,59 @@
     """
     with_hostel_selection = True
+
+class StudentGetMatricNumberPage(UtilityView, grok.View):
+    """ Construct and set the matriculation number.
+    """
+    grok.context(IStudent)
+    grok.name('get_matric_number')
+    grok.require('waeup.viewStudent')
+
+    def update(self):
+        students_utils = getUtility(IStudentsUtils)
+        msg, mnumber = students_utils.setMatricNumber(self.context)
+        if msg:
+            self.flash(msg, type="danger")
+        else:
+            self.flash(_('Matriculation number %s assigned.' % mnumber))
+            self.context.writeLogMessage(self, '%s assigned' % mnumber)
+        self.redirect(self.url(self.context))
+        return
+
+    def render(self):
+        return
+
+class ExportPDFMatricNumberSlip(UtilityView, grok.View):
+    """Deliver a PDF notification slip.
+    """
+    grok.context(ICustomStudent)
+    grok.name('matric_number_slip.pdf')
+    grok.require('waeup.viewStudent')
+    prefix = 'form'
+
+    form_fields = grok.AutoFields(ICustomStudent).select(
+        'student_id', 'matric_number')
+    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
+
+    @property
+    def title(self):
+        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
+        return translate(_('Matriculation Number'), 'waeup.kofa',
+            target_language=portal_language)
+
+    @property
+    def label(self):
+        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
+        return translate(_('Matriculation Number Slip\n'),
+            target_language=portal_language) \
+            + ' %s' % self.context.display_fullname
+
+    def render(self):
+        if self.context.state not in (PAID,) or not self.context.is_fresh \
+            or not self.context.matric_number:
+            self.flash('Not allowed.', type="danger")
+            self.redirect(self.url(self.context))
+            return
+        students_utils = getUtility(IStudentsUtils)
+        return students_utils.renderPDFAdmissionLetter(self,
+            self.context.student, omit_fields=self.omit_fields,
+            pre_text=pre_text, post_text='')
Index: main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/tests/test_utils.py
===================================================================
--- main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/tests/test_utils.py	(revision 15288)
+++ main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/tests/test_utils.py	(revision 15288)
@@ -0,0 +1,63 @@
+## $Id$
+##
+## Copyright (C) 2019 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import grok
+from hurry.workflow.interfaces import IWorkflowState
+from zope.component import getUtility, createObject
+from waeup.kofa.students.studylevel import StudentStudyLevel
+from waeup.kofa.students.tests.test_browser import StudentsFullSetup
+from waeup.kofa.students.interfaces import IStudentsUtils
+from waeup.kofa.students.studylevel import StudentStudyLevel
+from kofacustom.dspg.testing import FunctionalLayer
+
+class StudentsUtilsTests(StudentsFullSetup):
+
+    layer = FunctionalLayer
+
+    def test_setMatricNumber(self):
+        IWorkflowState(self.student).setState('school fee paid')
+        site = grok.getSite()
+        utils = getUtility(IStudentsUtils)
+        site['configuration'].next_matric_integer = 1
+        site['configuration'].next_matric_integer_2 = 1
+        site['configuration'].next_matric_integer_3 = 10001
+        site['configuration'].next_matric_integer_4 = 10001
+        self.student.matric_number = None
+        # nd_ft
+        self.certificate.study_mode ='nd_ft'
+        msg, mnumber = utils.setMatricNumber(self.student)
+        self.assertEqual(self.student.matric_number, 'dep1/ND/04/00001')
+        self.assertEqual(msg, None)
+        # hnd_ft
+        self.student.matric_number = None
+        self.certificate.study_mode ='hnd_ft'
+        msg, mnumber = utils.setMatricNumber(self.student)
+        self.assertEqual(self.student.matric_number, 'dep1/HND/04/00001')
+        self.assertEqual(msg, None)
+        # nd_pt
+        self.student.matric_number = None
+        self.certificate.study_mode ='nd_pt'
+        msg, mnumber = utils.setMatricNumber(self.student)
+        self.assertEqual(self.student.matric_number, 'dep1/ND/04/10001')
+        self.assertEqual(msg, None)
+        # hnd_pt
+        self.student.matric_number = None
+        self.certificate.study_mode ='hnd_pt'
+        msg, mnumber = utils.setMatricNumber(self.student)
+        self.assertEqual(self.student.matric_number, 'dep1/HND/04/10001')
+        self.assertEqual(msg, None)
+        return
Index: main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/utils.py
===================================================================
--- main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/utils.py	(revision 15287)
+++ main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/utils.py	(revision 15288)
@@ -43,4 +43,15 @@
                 if ticket.p_state == 'paid' and \
                     ticket.p_category == 'dep_sug' and \
+                    ticket.p_session == session:
+                    return True
+        return False
+
+    def _lsfp_penalty_paymentMade(self, student, session):
+        if student.current_mode not in ('hnd_ft', 'nd_ft'):
+            return True
+        if len(student['payments']):
+            for ticket in student['payments'].values():
+                if ticket.p_state == 'paid' and \
+                    ticket.p_category == 'lsfp_penalty' and \
                     ticket.p_session == session:
                     return True
@@ -143,4 +154,8 @@
                     return _('You have to pay NADESU/SA/SUG Dues first.'), None
                 if student.state == CLEARED:
+                    penalty = getattr(academic_session, 'lsfp_penalty_fee')
+                    if  penalty and not self._lsfp_penalty_paymentMade(
+                        student, student.current_session):
+                        return _('You have to pay late school fee payment penalty first.'), None
                     if local(student):
                         amount = getattr(certificate, 'school_fee_1', 0.0)
@@ -258,2 +273,46 @@
     #: passport pictures is allowed.
     PORTRAIT_CHANGE_STATES = (ADMITTED, RETURNING,)
+
+    def constructMatricNumber(self, student):
+        #faccode = student.faccode
+        depcode = student.depcode
+        #certcode = student.certcode
+        year = unicode(student.entry_session)[2:]
+        if not student.state in (PAID, ) or not student.is_fresh:
+            return _('Matriculation number cannot be set.'), None
+
+        # ACC/ND/17/00001
+        if student.current_mode == 'nd_ft':
+            next_integer = grok.getSite()['configuration'].next_matric_integer
+            if next_integer == 0:
+                return _('Matriculation number cannot be set.'), None
+            return None, "%s/ND/%s/%05d" % (depcode, year, next_integer)
+
+        # ACC/HND/17/00001
+        if student.current_mode == 'hnd_ft':
+            next_integer = grok.getSite()['configuration'].next_matric_integer_2
+            if next_integer == 0:
+                return _('Matriculation number cannot be set.'), None
+            return None, "%s/HND/%s/%05d" % (depcode, year, next_integer)
+
+        # PT students get the same prefixes but their counter should start
+        # with 10001. This is inconsistent because after 9999 ft students
+        # the ft and pt number ranges will overlap. Issoufou pointed this
+        # out but the director said:
+        # "when the counter gets to 9999 for ft, it can start counting
+        # along where pt is, at that moment."
+
+        # ACC/ND/17/10001
+        if student.current_mode in ('nd_pt, nd_we'):
+            next_integer = grok.getSite()['configuration'].next_matric_integer_3
+            if next_integer == 0:
+                return _('Matriculation number cannot be set.'), None
+            return None, "%s/ND/%s/%05d" % (depcode, year, next_integer)
+        # ACC/HND/17/10001
+        if student.current_mode in ('hnd_pt, hnd_we'):
+            next_integer = grok.getSite()['configuration'].next_matric_integer_4
+            if next_integer == 0:
+                return _('Matriculation number cannot be set.'), None
+            return None, "%s/HND/%s/%05d" % (depcode, year, next_integer)
+
+        return _('Matriculation number cannot be set.'), None
Index: main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/viewlets.py
===================================================================
--- main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/viewlets.py	(revision 15287)
+++ main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/viewlets.py	(revision 15288)
@@ -21,14 +21,50 @@
 from waeup.kofa.interfaces import REQUESTED
 from waeup.kofa.browser.viewlets import ManageActionButton
-from waeup.kofa.students.interfaces import IStudentsUtils
-from kofacustom.dspg.students.interfaces import (
-    ICustomStudentStudyCourse, ICustomStudentStudyLevel)
+from waeup.kofa.students.interfaces import IStudentsUtils, IStudent
 from waeup.kofa.students.fileviewlets import (
     StudentFileDisplay, StudentFileUpload, StudentImage)
 from waeup.kofa.students.browser import (
     ExportPDFClearanceSlip, StudyCourseDisplayFormPage,
-    StudyLevelDisplayFormPage)
+    StudyLevelDisplayFormPage, StudentBaseDisplayFormPage)
+from kofacustom.dspg.students.interfaces import (
+    ICustomStudentStudyCourse, ICustomStudentStudyLevel)
 
 from kofacustom.nigeria.interfaces import MessageFactory as _
+
+class GetMatricNumberActionButton(ManageActionButton):
+    grok.order(10)
+    grok.context(IStudent)
+    grok.view(StudentBaseDisplayFormPage)
+    grok.require('waeup.viewStudent')
+    #grok.require('waeup.manageStudent')
+    icon = 'actionicon_count.png'
+    text = _('Get Matriculation Number')
+
+    @property
+    def target_url(self):
+        students_utils = getUtility(IStudentsUtils)
+        if self.context.matric_number:
+            return ''
+        error, matric_number = students_utils.constructMatricNumber(
+            self.context)
+        if error:
+            return ''
+        return self.view.url(self.view.context, 'get_matric_number')
+
+class MatricNumberSlipActionButton(ManageActionButton):
+    grok.order(10)
+    grok.context(IStudent)
+    grok.view(StudentBaseDisplayFormPage)
+    grok.require('waeup.viewStudent')
+    icon = 'actionicon_pdf.png'
+    text = _('Download matriculation number slip')
+    target = 'matric_number_slip.pdf'
+
+    @property
+    def target_url(self):
+        if self.context.state not in (PAID,) or not self.context.is_fresh \
+            or not self.context.matric_number:
+            return ''
+        return self.view.url(self.view.context, self.target)
 
 # ND Result
Index: main/kofacustom.dspg/trunk/src/kofacustom/dspg/utils/utils.py
===================================================================
--- main/kofacustom.dspg/trunk/src/kofacustom/dspg/utils/utils.py	(revision 15287)
+++ main/kofacustom.dspg/trunk/src/kofacustom/dspg/utils/utils.py	(revision 15288)
@@ -124,4 +124,5 @@
         'pom':'POM Pack',
         'hrm':'HRM Pack',
+        'lsfp_penalty':'HND/ND FT Fresh Student Late School Fee Payment Penalty',
         }
 
@@ -188,4 +189,5 @@
         'pom':'POM Pack',
         'hrm':'HRM Pack',
+        'lsfp_penalty':'HND/ND FT Fresh Student Late School Fee Payment Penalty',
         }
 
