source: main/waeup.aaue/trunk/src/waeup/aaue/students/utils.py @ 14506

Last change on this file since 14506 was 14506, checked in by Henrik Bettermann, 8 years ago

Replace 'NIL' by 'Nil'.

  • Property svn:keywords set to Id
File size: 20.9 KB
RevLine 
[7419]1## $Id: utils.py 14506 2017-02-05 08:01:42Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
[7151]18import grok
[8600]19from time import time
20from zope.component import createObject
[10922]21from waeup.kofa.interfaces import (
[13594]22    ADMITTED, CLEARANCE, REQUESTED, CLEARED, RETURNING, PAID,
23    academic_sessions_vocab)
[8823]24from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
[8247]25from waeup.kofa.accesscodes import create_accesscode
[10922]26from waeup.kofa.students.utils import trans
[13720]27from waeup.aaue.interswitch.browser import gateway_net_amt, GATEWAY_AMT
[8444]28from waeup.aaue.interfaces import MessageFactory as _
[6902]29
[8823]30class CustomStudentsUtils(NigeriaStudentsUtils):
[7151]31    """A collection of customized methods.
32
33    """
34
[14242]35    PORTRAIT_CHANGE_STATES = (ADMITTED,)
[13348]36
[14487]37    gpa_boundaries = ((1, 'FRNS / NER / NYV'),
[10641]38                      (1.5, 'Pass'),
39                      (2.4, '3rd Class Honours'),
40                      (3.5, '2nd Class Honours Lower Division'),
41                      (4.5, '2nd Class Honours Upper Division'),
42                      (5, '1st Class Honours'))
43
[14462]44    def getClassFromCGPA(self, gpa, student):
45        if gpa < self.gpa_boundaries[0][0]:
46            # FRNS
47            return 0, self.gpa_boundaries[0][1]
48        if student.entry_session < 2013:
49            if gpa < self.gpa_boundaries[1][0]:
50                # Pass
51                return 1, self.gpa_boundaries[1][1]
52        else:
53            if gpa < self.gpa_boundaries[1][0]:
54                # FRNS (Pass degree has been phased out in 2013)
55                return 0, self.gpa_boundaries[0][1]
56        if gpa < self.gpa_boundaries[2][0]:
57            # 3rd
58            return 2, self.gpa_boundaries[2][1]
59        if gpa < self.gpa_boundaries[3][0]:
60            # 2nd L
61            return 3, self.gpa_boundaries[3][1]
62        if gpa < self.gpa_boundaries[4][0]:
63            # 2nd U
64            return 4, self.gpa_boundaries[4][1]
65        if gpa <= self.gpa_boundaries[5][0]:
66            # 1st
67            return 5, self.gpa_boundaries[5][1]
68        return 'N/A'
69
[14160]70    def getDegreeClassNumber(self, level_obj):
[14415]71        """Get degree class number (used for SessionResultsPresentation
72        reports).
73        """
[14459]74        certificate = getattr(level_obj.__parent__,'certificate', None)
[14160]75        end_level = getattr(certificate, 'end_level', None)
[14459]76        if end_level and level_obj.level >= end_level:
[14464]77            if level_obj.level > end_level:
78                # spill-over level
[14476]79                if level_obj.gpa_params[1] == 0:
[14464]80                    # no credits taken
81                    return 0
[14160]82            failed_courses = level_obj.passed_params[4]
[14411]83            not_taken_courses = level_obj.passed_params[5]
[14160]84            if '_m' in failed_courses:
85                return 0
[14441]86            if len(not_taken_courses) \
[14506]87                and not not_taken_courses == 'Nil':
[14411]88                return 0
[14464]89        elif level_obj.gpa_params[1] < 30:
90            # credits taken below limit
91            return 0
[14487]92        if level_obj.level_verdict in ('FRNS', 'NER', 'NYV'):
[14464]93            return 0
[14160]94        # use gpa_boundaries above
[14462]95        return self.getClassFromCGPA(
96            level_obj.cumulative_params[0], level_obj.student)[0]
[14160]97
[13359]98    def increaseMatricInteger(self, student):
99        """Increase counter for matric numbers.
100        This counter can be a centrally stored attribute or an attribute of
101        faculties, departments or certificates. In the base package the counter
102        is as an attribute of the site configuration container.
103        """
104        if student.current_mode in ('ug_pt', 'de_pt'):
105            grok.getSite()['configuration'].next_matric_integer += 1
106            return
[13609]107        elif student.is_postgrad:
108            grok.getSite()['configuration'].next_matric_integer_3 += 1
109            return
[13793]110        elif student.current_mode in ('dp_ft',):
111            grok.getSite()['configuration'].next_matric_integer_4 += 1
112            return
[13359]113        grok.getSite()['configuration'].next_matric_integer_2 += 1
114        return
115
[13749]116    def _concessionalPaymentMade(self, student):
117        if len(student['payments']):
118            for ticket in student['payments'].values():
119                if ticket.p_state == 'paid' and \
120                    ticket.p_category == 'concessional':
121                    return True
122        return False
123
[11596]124    def constructMatricNumber(self, student):
[11593]125        faccode = student.faccode
126        depcode = student.depcode
[13609]127        certcode = student.certcode
[13664]128        degree = getattr(
129            getattr(student.get('studycourse', None), 'certificate', None),
130                'degree', None)
[11593]131        year = unicode(student.entry_session)[2:]
[13359]132        if not student.state in (PAID, ) or not student.is_fresh or \
133            student.current_mode == 'found':
134            return _('Matriculation number cannot be set.'), None
[13755]135        #if student.current_mode not in ('mug_ft', 'mde_ft') and \
136        #    not self._concessionalPaymentMade(student):
137        #    return _('Matriculation number cannot be set.'), None
[13571]138        if student.is_postgrad:
[13609]139            next_integer = grok.getSite()['configuration'].next_matric_integer_3
[13664]140            if not degree or next_integer == 0:
[13609]141                return _('Matriculation number cannot be set.'), None
[13846]142            if student.faccode in ('IOE'):
143                return None, "AAU/SPS/%s/%s/%s/%05d" % (
144                    faccode, year, degree, next_integer)
[13609]145            return None, "AAU/SPS/%s/%s/%s/%s/%05d" % (
[13664]146                faccode, depcode, year, degree, next_integer)
[13359]147        if student.current_mode in ('ug_pt', 'de_pt'):
148            next_integer = grok.getSite()['configuration'].next_matric_integer
149            if next_integer == 0:
150                return _('Matriculation number cannot be set.'), None
[12975]151            return None, "PTP/%s/%s/%s/%05d" % (
152                faccode, depcode, year, next_integer)
[13793]153        if student.current_mode in ('dp_ft',):
154            next_integer = grok.getSite()['configuration'].next_matric_integer_4
155            if next_integer == 0:
156                return _('Matriculation number cannot be set.'), None
157            return None, "IOE/DIP/%s/%05d" % (year, next_integer)
[13359]158        next_integer = grok.getSite()['configuration'].next_matric_integer_2
159        if next_integer == 0:
160            return _('Matriculation number cannot be set.'), None
161        if student.faccode in ('FBM', 'FCS'):
162            return None, "CMS/%s/%s/%s/%05d" % (
163                faccode, depcode, year, next_integer)
164        return None, "%s/%s/%s/%05d" % (faccode, depcode, year, next_integer)
[12975]165
[8270]166    def getReturningData(self, student):
167        """ This method defines what happens after school fee payment
[8319]168        of returning students depending on the student's senate verdict.
[8270]169        """
[8319]170        prev_level = student['studycourse'].current_level
171        cur_verdict = student['studycourse'].current_verdict
[14089]172        if cur_verdict in ('A','B','C', 'L','M','N','Z',):
[8319]173            # Successful student
174            new_level = divmod(int(prev_level),100)[0]*100 + 100
[14089]175        #elif cur_verdict == 'C':
176        #    # Student on probation
177        #    new_level = int(prev_level) + 10
[8319]178        else:
179            # Student is somehow in an undefined state.
180            # Level has to be set manually.
181            new_level = prev_level
[8270]182        new_session = student['studycourse'].current_session + 1
183        return new_session, new_level
184
[13454]185    def _isPaymentDisabled(self, p_session, category, student):
186        academic_session = self._getSessionConfiguration(p_session)
[14246]187        if category.startswith('schoolfee'):
188            if 'sf_all' in academic_session.payment_disabled:
189                return True
190            if 'sf_pg' in academic_session.payment_disabled and \
191                student.is_postgrad:
192                return True
193            if 'sf_pt' in academic_session.payment_disabled and \
194                student.current_mode.endswith('_pt'):
195                return True
196            if 'sf_found' in academic_session.payment_disabled and \
197                student.current_mode == 'found':
198                return True
[13794]199        if category.startswith('clearance') and \
200            'cl_regular' in academic_session.payment_disabled and \
201            student.current_mode in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
202            return True
[13454]203        if category == 'hostel_maintenance' and \
204            'maint_all' in academic_session.payment_disabled:
205            return True
206        return False
207
[9154]208    def setPaymentDetails(self, category, student,
209            previous_session=None, previous_level=None):
[8600]210        """Create Payment object and set the payment data of a student for
211        the payment category specified.
212
213        """
[8306]214        details = {}
[8600]215        p_item = u''
216        amount = 0.0
217        error = u''
[9154]218        if previous_session:
219            return _('Previous session payment not yet implemented.'), None
[8600]220        p_session = student['studycourse'].current_session
221        p_level = student['studycourse'].current_level
[9154]222        p_current = True
[9527]223        academic_session = self._getSessionConfiguration(p_session)
224        if academic_session == None:
[8600]225            return _(u'Session configuration object is not available.'), None
[8677]226        # Determine fee.
[7151]227        if category == 'transfer':
[8600]228            amount = academic_session.transfer_fee
[14296]229        elif category == 'transcript_local':
230            amount = academic_session.transcript_fee_local
231        elif category == 'transcript_inter':
232            amount = academic_session.transcript_fee_inter
[7151]233        elif category == 'bed_allocation':
[8600]234            amount = academic_session.booking_fee
[14378]235        elif category == 'restitution':
[14402]236            if student.entry_session == 2016 \
237                and student.current_session == 2016 \
[14378]238                or student.current_mode != 'ug_ft':
239                return _(u'Restitution fee payment not required.'), None
240            amount = academic_session.restitution_fee
[7151]241        elif category == 'hostel_maintenance':
[13418]242            amount = 0.0
243            bedticket = student['accommodation'].get(
244                str(student.current_session), None)
[13502]245            if bedticket is not None and bedticket.bed is not None:
[13474]246                p_item = bedticket.display_coordinates
[13418]247                if bedticket.bed.__parent__.maint_fee > 0:
248                    amount = bedticket.bed.__parent__.maint_fee
249                else:
250                    # fallback
251                    amount = academic_session.maint_fee
252            else:
[13506]253                return _(u'No bed allocated.'), None
[13636]254        elif student.current_mode == 'found' and category not in (
255            'schoolfee', 'clearance', 'late_registration'):
256            return _('Not allowed.'), None
[13400]257        elif category.startswith('clearance'):
[13594]258            if student.state not in (ADMITTED, CLEARANCE, REQUESTED, CLEARED):
259                return _(u'Acceptance Fee payments not allowed.'), None
[13855]260            if student.current_mode in (
261                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
262                'transfer', 'mug_ft', 'mde_ft') \
263                and category != 'clearance_incl':
264                    return _("Additional fees must be included."), None
[14411]265            if student.faccode == 'FP':  # includes IJMBE
[11653]266                amount = academic_session.clearance_fee_fp
[13377]267            elif student.current_mode.endswith('_pt'):
[13678]268                if student.is_postgrad:
269                    amount = academic_session.clearance_fee_pg_pt
270                else:
271                    amount = academic_session.clearance_fee_ug_pt
[13466]272            elif student.faccode == 'FCS':
273                # Students in clinical medical sciences pay the medical
274                # acceptance fee
[13377]275                amount = academic_session.clearance_fee_med
[13678]276            elif student.is_postgrad:  # and not part-time
[13853]277                if category != 'clearance':
[13854]278                    return _("No additional fees required."), None
[13526]279                amount = academic_session.clearance_fee_pg
[11653]280            else:
281                amount = academic_session.clearance_fee
[8753]282            p_item = student['studycourse'].certificate.code
[13689]283            if amount in (0.0, None):
284                return _(u'Amount could not be determined.'), None
[14239]285            # Add Matric Gown Fee and Lapel Fee
[13689]286            if category == 'clearance_incl':
[13414]287                amount += gateway_net_amt(academic_session.matric_gown_fee) + \
288                    gateway_net_amt(academic_session.lapel_fee)
[11004]289        elif category == 'late_registration':
[14117]290            if student.is_postgrad:
291                amount = academic_session.late_pg_registration_fee
292            else:
293                amount = academic_session.late_registration_fee
[13400]294        elif category.startswith('schoolfee'):
[8600]295            try:
[8753]296                certificate = student['studycourse'].certificate
297                p_item = certificate.code
[8600]298            except (AttributeError, TypeError):
299                return _('Study course data are incomplete.'), None
[13853]300            if student.is_postgrad and category != 'schoolfee':
[13854]301                return _("No additional fees required."), None
[13855]302            if student.current_mode in (
303                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
304                'transfer', 'mug_ft', 'mde_ft') \
305                and not category in (
306                'schoolfee_incl', 'schoolfee_1', 'schoolfee_2'):
[14244]307                    return _("You must choose a payment which includes "
[13855]308                             "additional fees."), None
[13780]309            if category in ('schoolfee_1', 'schoolfee_2'):
310                if student.current_mode == 'ug_pt':
311                    return _("Part-time students are not allowed "
312                             "to pay by instalments."), None
[14241]313                if student.entry_session < 2015:
314                    return _("You are not allowed "
315                             "to pay by instalments."), None
[14396]316            if student.state == CLEARED and category != 'schoolfee_2':
[14229]317                amount = getattr(certificate, 'school_fee_1', 0.0)
[13512]318                # Cut school fee by 50%
[14241]319                if category == 'schoolfee_1' and amount:
320                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
321            elif student.is_fresh and category == 'schoolfee_2':
322                amount = getattr(certificate, 'school_fee_1', 0.0)
323                # Cut school fee by 50%
324                if amount:
325                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
[14396]326            elif student.state == RETURNING and category != 'schoolfee_2':
[13482]327                if not student.father_name:
328                    return _("Personal data form is not properly filled."), None
[13374]329                # In case of returning school fee payment the payment session
330                # and level contain the values of the session the student
331                # has paid for.
332                p_session, p_level = self.getReturningData(student)
[8961]333                try:
334                    academic_session = grok.getSite()[
335                        'configuration'][str(p_session)]
336                except KeyError:
337                    return _(u'Session configuration object is not available.'), None
[14241]338                if student.entry_session >= 2015:
[14229]339                    amount = getattr(certificate, 'school_fee_2', 0.0)
[14241]340                    # Cut school fee by 50%
[14396]341                    if category == 'schoolfee_1' and amount:
[14241]342                        amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
[13374]343                else:
[14229]344                    amount = getattr(certificate, 'school_fee_3', 0.0)
[14241]345            elif category == 'schoolfee_2':
346                amount = getattr(certificate, 'school_fee_2', 0.0)
347                # Cut school fee by 50%
348                if amount:
349                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
[8600]350            else:
[8753]351                return _('Wrong state.'), None
[13417]352            if amount in (0.0, None):
353                return _(u'Amount could not be determined.'), None
[14244]354            # Add Student Union Fee , Student Id Card Fee and Welfare Assurance
[13512]355            if category in ('schoolfee_incl', 'schoolfee_1'):
[13414]356                amount += gateway_net_amt(academic_session.welfare_fee) + \
357                    gateway_net_amt(academic_session.union_fee)
[14244]358                if student.entry_session == 2016 \
359                    and student.entry_mode == 'ug_ft' \
360                    and student.state == CLEARED:
361                    amount += gateway_net_amt(academic_session.id_card_fee)
[13534]362            # Add non-indigenous fee and session specific penalty fees
363            if student.is_postgrad:
364                amount += academic_session.penalty_pg
[14246]365                if student.lga and not student.lga.startswith('edo'):
[13534]366                    amount += 20000.0
367            else:
368                amount += academic_session.penalty_ug
[14248]369        elif not student.is_postgrad:
370            fee_name = category + '_fee'
371            amount = getattr(academic_session, fee_name, 0.0)
[8600]372        if amount in (0.0, None):
373            return _(u'Amount could not be determined.'), None
[8677]374        # Create ticket.
[8600]375        for key in student['payments'].keys():
376            ticket = student['payments'][key]
377            if ticket.p_state == 'paid' and\
378               ticket.p_category == category and \
379               ticket.p_item == p_item and \
380               ticket.p_session == p_session:
381                  return _('This type of payment has already been made.'), None
[13786]382            # Additional condition in AAUE
383            if category in ('schoolfee', 'schoolfee_incl', 'schoolfee_1'):
384                if ticket.p_state == 'paid' and \
385                   ticket.p_category in ('schoolfee',
386                                         'schoolfee_incl',
387                                         'schoolfee_1') and \
388                   ticket.p_item == p_item and \
389                   ticket.p_session == p_session:
390                      return _(
391                          'Another school fee payment for this '
392                          'session has already been made.'), None
393
[11455]394        if self._isPaymentDisabled(p_session, category, student):
[13798]395            return _('This category of payments has been disabled.'), None
[8712]396        payment = createObject(u'waeup.StudentOnlinePayment')
[8954]397        timestamp = ("%d" % int(time()*10000))[1:]
[8600]398        payment.p_id = "p%s" % timestamp
399        payment.p_category = category
400        payment.p_item = p_item
401        payment.p_session = p_session
402        payment.p_level = p_level
[9154]403        payment.p_current = p_current
[8600]404        payment.amount_auth = amount
405        return None, payment
[7621]406
[10922]407    def _admissionText(self, student, portal_language):
408        inst_name = grok.getSite()['configuration'].name
409        entry_session = student['studycourse'].entry_session
410        entry_session = academic_sessions_vocab.getTerm(entry_session).title
411        text = trans(_(
[10953]412            'This is to inform you that you have been offered provisional'
413            ' admission into ${a} for the ${b} academic session as follows:',
[10922]414            mapping = {'a': inst_name, 'b': entry_session}),
415            portal_language)
416        return text
417
[10051]418    def maxCredits(self, studylevel):
419        """Return maximum credits.
420
421        """
422        return 48
423
[13353]424    def getBedCoordinates(self, bedticket):
425        """Return descriptive bed coordinates.
426        This method can be used to customize the `display_coordinates`
427        property method in order to  display a
428        customary description of the bed space.
429        """
430        bc = bedticket.bed_coordinates.split(',')
431        if len(bc) == 4:
432            return bc[0]
433        return bedticket.bed_coordinates
434
[13415]435    def getAccommodationDetails(self, student):
436        """Determine the accommodation data of a student.
437        """
438        d = {}
439        d['error'] = u''
440        hostels = grok.getSite()['hostels']
441        d['booking_session'] = hostels.accommodation_session
442        d['allowed_states'] = hostels.accommodation_states
443        d['startdate'] = hostels.startdate
444        d['enddate'] = hostels.enddate
445        d['expired'] = hostels.expired
446        # Determine bed type
[13416]447        bt = 'all'
[13415]448        if student.sex == 'f':
449            sex = 'female'
450        else:
451            sex = 'male'
452        special_handling = 'regular'
453        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
454        return d
455
[13753]456    def checkAccommodationRequirements(self, student, acc_details):
[14238]457        msg = super(CustomStudentsUtils, self).checkAccommodationRequirements(
[13753]458            student, acc_details)
[14238]459        if msg:
460            return msg
[13753]461        if student.current_mode not in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
462            return _('You are not eligible to book accommodation.')
463        return
464
[8444]465    # AAUE prefix
466    STUDENT_ID_PREFIX = u'E'
Note: See TracBrowser for help on using the repository browser.