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

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

Replace maxCreditsExceeded by warnCreditsOOR.

  • Property svn:keywords set to Id
File size: 22.6 KB
RevLine 
[7419]1## $Id: utils.py 14585 2017-02-24 09:24:09Z 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
[14538]82            elif level_obj.gpa_params[1] < 30:
[14537]83                # credits taken below limit
84                return 0
[14160]85            failed_courses = level_obj.passed_params[4]
[14411]86            not_taken_courses = level_obj.passed_params[5]
[14160]87            if '_m' in failed_courses:
88                return 0
[14441]89            if len(not_taken_courses) \
[14506]90                and not not_taken_courses == 'Nil':
[14411]91                return 0
[14464]92        elif level_obj.gpa_params[1] < 30:
93            # credits taken below limit
94            return 0
[14487]95        if level_obj.level_verdict in ('FRNS', 'NER', 'NYV'):
[14464]96            return 0
[14160]97        # use gpa_boundaries above
[14462]98        return self.getClassFromCGPA(
99            level_obj.cumulative_params[0], level_obj.student)[0]
[14160]100
[13359]101    def increaseMatricInteger(self, student):
102        """Increase counter for matric numbers.
103        This counter can be a centrally stored attribute or an attribute of
104        faculties, departments or certificates. In the base package the counter
105        is as an attribute of the site configuration container.
106        """
107        if student.current_mode in ('ug_pt', 'de_pt'):
108            grok.getSite()['configuration'].next_matric_integer += 1
109            return
[13609]110        elif student.is_postgrad:
111            grok.getSite()['configuration'].next_matric_integer_3 += 1
112            return
[13793]113        elif student.current_mode in ('dp_ft',):
114            grok.getSite()['configuration'].next_matric_integer_4 += 1
115            return
[13359]116        grok.getSite()['configuration'].next_matric_integer_2 += 1
117        return
118
[13749]119    def _concessionalPaymentMade(self, student):
120        if len(student['payments']):
121            for ticket in student['payments'].values():
122                if ticket.p_state == 'paid' and \
123                    ticket.p_category == 'concessional':
124                    return True
125        return False
126
[11596]127    def constructMatricNumber(self, student):
[11593]128        faccode = student.faccode
129        depcode = student.depcode
[13609]130        certcode = student.certcode
[13664]131        degree = getattr(
132            getattr(student.get('studycourse', None), 'certificate', None),
133                'degree', None)
[11593]134        year = unicode(student.entry_session)[2:]
[13359]135        if not student.state in (PAID, ) or not student.is_fresh or \
136            student.current_mode == 'found':
137            return _('Matriculation number cannot be set.'), None
[13755]138        #if student.current_mode not in ('mug_ft', 'mde_ft') and \
139        #    not self._concessionalPaymentMade(student):
140        #    return _('Matriculation number cannot be set.'), None
[13571]141        if student.is_postgrad:
[13609]142            next_integer = grok.getSite()['configuration'].next_matric_integer_3
[13664]143            if not degree or next_integer == 0:
[13609]144                return _('Matriculation number cannot be set.'), None
[13846]145            if student.faccode in ('IOE'):
146                return None, "AAU/SPS/%s/%s/%s/%05d" % (
147                    faccode, year, degree, next_integer)
[13609]148            return None, "AAU/SPS/%s/%s/%s/%s/%05d" % (
[13664]149                faccode, depcode, year, degree, next_integer)
[13359]150        if student.current_mode in ('ug_pt', 'de_pt'):
151            next_integer = grok.getSite()['configuration'].next_matric_integer
152            if next_integer == 0:
153                return _('Matriculation number cannot be set.'), None
[12975]154            return None, "PTP/%s/%s/%s/%05d" % (
155                faccode, depcode, year, next_integer)
[13793]156        if student.current_mode in ('dp_ft',):
157            next_integer = grok.getSite()['configuration'].next_matric_integer_4
158            if next_integer == 0:
159                return _('Matriculation number cannot be set.'), None
160            return None, "IOE/DIP/%s/%05d" % (year, next_integer)
[13359]161        next_integer = grok.getSite()['configuration'].next_matric_integer_2
162        if next_integer == 0:
163            return _('Matriculation number cannot be set.'), None
164        if student.faccode in ('FBM', 'FCS'):
165            return None, "CMS/%s/%s/%s/%05d" % (
166                faccode, depcode, year, next_integer)
167        return None, "%s/%s/%s/%05d" % (faccode, depcode, year, next_integer)
[12975]168
[8270]169    def getReturningData(self, student):
170        """ This method defines what happens after school fee payment
[8319]171        of returning students depending on the student's senate verdict.
[8270]172        """
[8319]173        prev_level = student['studycourse'].current_level
174        cur_verdict = student['studycourse'].current_verdict
[14089]175        if cur_verdict in ('A','B','C', 'L','M','N','Z',):
[8319]176            # Successful student
177            new_level = divmod(int(prev_level),100)[0]*100 + 100
[14089]178        #elif cur_verdict == 'C':
179        #    # Student on probation
180        #    new_level = int(prev_level) + 10
[8319]181        else:
182            # Student is somehow in an undefined state.
183            # Level has to be set manually.
184            new_level = prev_level
[8270]185        new_session = student['studycourse'].current_session + 1
186        return new_session, new_level
187
[13454]188    def _isPaymentDisabled(self, p_session, category, student):
189        academic_session = self._getSessionConfiguration(p_session)
[14246]190        if category.startswith('schoolfee'):
191            if 'sf_all' in academic_session.payment_disabled:
192                return True
193            if 'sf_pg' in academic_session.payment_disabled and \
194                student.is_postgrad:
195                return True
[14571]196            if 'sf_ug_pt' in academic_session.payment_disabled and \
197                student.current_mode in ('ug_pt', 'de_pt'):
[14246]198                return True
199            if 'sf_found' in academic_session.payment_disabled and \
200                student.current_mode == 'found':
201                return True
[13794]202        if category.startswith('clearance') and \
203            'cl_regular' in academic_session.payment_disabled and \
204            student.current_mode in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
205            return True
[13454]206        if category == 'hostel_maintenance' and \
207            'maint_all' in academic_session.payment_disabled:
208            return True
209        return False
210
[9154]211    def setPaymentDetails(self, category, student,
212            previous_session=None, previous_level=None):
[8600]213        """Create Payment object and set the payment data of a student for
214        the payment category specified.
215
216        """
[8306]217        details = {}
[8600]218        p_item = u''
219        amount = 0.0
220        error = u''
[9154]221        if previous_session:
[14544]222            if previous_session < student['studycourse'].entry_session:
223                return _('The previous session must not fall below '
224                         'your entry session.'), None
225            if category == 'schoolfee':
226                # School fee is always paid for the following session
227                if previous_session > student['studycourse'].current_session:
228                    return _('This is not a previous session.'), None
229            else:
230                if previous_session > student['studycourse'].current_session - 1:
231                    return _('This is not a previous session.'), None
232            p_session = previous_session
233            p_level = previous_level
234            p_current = False
235        else:
236            p_session = student['studycourse'].current_session
237            p_level = student['studycourse'].current_level
238            p_current = True
[9527]239        academic_session = self._getSessionConfiguration(p_session)
240        if academic_session == None:
[8600]241            return _(u'Session configuration object is not available.'), None
[8677]242        # Determine fee.
[7151]243        if category == 'transfer':
[8600]244            amount = academic_session.transfer_fee
[14296]245        elif category == 'transcript_local':
246            amount = academic_session.transcript_fee_local
247        elif category == 'transcript_inter':
248            amount = academic_session.transcript_fee_inter
[7151]249        elif category == 'bed_allocation':
[8600]250            amount = academic_session.booking_fee
[14378]251        elif category == 'restitution':
[14402]252            if student.entry_session == 2016 \
253                and student.current_session == 2016 \
[14378]254                or student.current_mode != 'ug_ft':
255                return _(u'Restitution fee payment not required.'), None
256            amount = academic_session.restitution_fee
[7151]257        elif category == 'hostel_maintenance':
[13418]258            amount = 0.0
259            bedticket = student['accommodation'].get(
260                str(student.current_session), None)
[13502]261            if bedticket is not None and bedticket.bed is not None:
[13474]262                p_item = bedticket.display_coordinates
[13418]263                if bedticket.bed.__parent__.maint_fee > 0:
264                    amount = bedticket.bed.__parent__.maint_fee
265                else:
266                    # fallback
267                    amount = academic_session.maint_fee
268            else:
[13506]269                return _(u'No bed allocated.'), None
[13636]270        elif student.current_mode == 'found' and category not in (
271            'schoolfee', 'clearance', 'late_registration'):
272            return _('Not allowed.'), None
[13400]273        elif category.startswith('clearance'):
[13594]274            if student.state not in (ADMITTED, CLEARANCE, REQUESTED, CLEARED):
275                return _(u'Acceptance Fee payments not allowed.'), None
[13855]276            if student.current_mode in (
277                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
278                'transfer', 'mug_ft', 'mde_ft') \
279                and category != 'clearance_incl':
280                    return _("Additional fees must be included."), None
[14518]281            if student.current_mode == 'ijmbe':
282                amount = academic_session.clearance_fee_ijmbe
283            elif student.faccode == 'FP':
[11653]284                amount = academic_session.clearance_fee_fp
[13377]285            elif student.current_mode.endswith('_pt'):
[13678]286                if student.is_postgrad:
287                    amount = academic_session.clearance_fee_pg_pt
288                else:
289                    amount = academic_session.clearance_fee_ug_pt
[13466]290            elif student.faccode == 'FCS':
291                # Students in clinical medical sciences pay the medical
292                # acceptance fee
[13377]293                amount = academic_session.clearance_fee_med
[13678]294            elif student.is_postgrad:  # and not part-time
[13853]295                if category != 'clearance':
[13854]296                    return _("No additional fees required."), None
[13526]297                amount = academic_session.clearance_fee_pg
[11653]298            else:
299                amount = academic_session.clearance_fee
[8753]300            p_item = student['studycourse'].certificate.code
[13689]301            if amount in (0.0, None):
302                return _(u'Amount could not be determined.'), None
[14239]303            # Add Matric Gown Fee and Lapel Fee
[13689]304            if category == 'clearance_incl':
[13414]305                amount += gateway_net_amt(academic_session.matric_gown_fee) + \
306                    gateway_net_amt(academic_session.lapel_fee)
[11004]307        elif category == 'late_registration':
[14117]308            if student.is_postgrad:
309                amount = academic_session.late_pg_registration_fee
310            else:
311                amount = academic_session.late_registration_fee
[13400]312        elif category.startswith('schoolfee'):
[8600]313            try:
[8753]314                certificate = student['studycourse'].certificate
315                p_item = certificate.code
[8600]316            except (AttributeError, TypeError):
317                return _('Study course data are incomplete.'), None
[13853]318            if student.is_postgrad and category != 'schoolfee':
[13854]319                return _("No additional fees required."), None
[14544]320            if not previous_session and student.current_mode in (
[13855]321                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
322                'transfer', 'mug_ft', 'mde_ft') \
323                and not category in (
324                'schoolfee_incl', 'schoolfee_1', 'schoolfee_2'):
[14244]325                    return _("You must choose a payment which includes "
[13855]326                             "additional fees."), None
[13780]327            if category in ('schoolfee_1', 'schoolfee_2'):
328                if student.current_mode == 'ug_pt':
329                    return _("Part-time students are not allowed "
330                             "to pay by instalments."), None
[14241]331                if student.entry_session < 2015:
332                    return _("You are not allowed "
333                             "to pay by instalments."), None
[14544]334            if previous_session:
335                # Students can pay for previous sessions in all
336                # workflow states.  Fresh students are excluded by the
337                # update method of the PreviousPaymentAddFormPage.
338                if previous_level == 100:
339                    amount = getattr(certificate, 'school_fee_1', 0.0)
340                else:
341                    if student.entry_session >= 2015:
342                        amount = getattr(certificate, 'school_fee_2', 0.0)
343                    else:
344                        amount = getattr(certificate, 'school_fee_3', 0.0)
345            elif student.state == CLEARED and category != 'schoolfee_2':
[14229]346                amount = getattr(certificate, 'school_fee_1', 0.0)
[13512]347                # Cut school fee by 50%
[14241]348                if category == 'schoolfee_1' and amount:
349                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
350            elif student.is_fresh and category == 'schoolfee_2':
351                amount = getattr(certificate, 'school_fee_1', 0.0)
352                # Cut school fee by 50%
353                if amount:
354                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
[14396]355            elif student.state == RETURNING and category != 'schoolfee_2':
[13482]356                if not student.father_name:
357                    return _("Personal data form is not properly filled."), None
[13374]358                # In case of returning school fee payment the payment session
359                # and level contain the values of the session the student
360                # has paid for.
361                p_session, p_level = self.getReturningData(student)
[8961]362                try:
363                    academic_session = grok.getSite()[
364                        'configuration'][str(p_session)]
365                except KeyError:
366                    return _(u'Session configuration object is not available.'), None
[14241]367                if student.entry_session >= 2015:
[14229]368                    amount = getattr(certificate, 'school_fee_2', 0.0)
[14241]369                    # Cut school fee by 50%
[14396]370                    if category == 'schoolfee_1' and amount:
[14241]371                        amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
[13374]372                else:
[14229]373                    amount = getattr(certificate, 'school_fee_3', 0.0)
[14241]374            elif category == 'schoolfee_2':
375                amount = getattr(certificate, 'school_fee_2', 0.0)
376                # Cut school fee by 50%
377                if amount:
378                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
[8600]379            else:
[8753]380                return _('Wrong state.'), None
[13417]381            if amount in (0.0, None):
382                return _(u'Amount could not be determined.'), None
[14244]383            # Add Student Union Fee , Student Id Card Fee and Welfare Assurance
[13512]384            if category in ('schoolfee_incl', 'schoolfee_1'):
[13414]385                amount += gateway_net_amt(academic_session.welfare_fee) + \
386                    gateway_net_amt(academic_session.union_fee)
[14244]387                if student.entry_session == 2016 \
388                    and student.entry_mode == 'ug_ft' \
389                    and student.state == CLEARED:
390                    amount += gateway_net_amt(academic_session.id_card_fee)
[13534]391            # Add non-indigenous fee and session specific penalty fees
392            if student.is_postgrad:
393                amount += academic_session.penalty_pg
[14246]394                if student.lga and not student.lga.startswith('edo'):
[13534]395                    amount += 20000.0
396            else:
397                amount += academic_session.penalty_ug
[14248]398        elif not student.is_postgrad:
399            fee_name = category + '_fee'
400            amount = getattr(academic_session, fee_name, 0.0)
[8600]401        if amount in (0.0, None):
402            return _(u'Amount could not be determined.'), None
[8677]403        # Create ticket.
[8600]404        for key in student['payments'].keys():
405            ticket = student['payments'][key]
406            if ticket.p_state == 'paid' and\
407               ticket.p_category == category and \
408               ticket.p_item == p_item and \
409               ticket.p_session == p_session:
410                  return _('This type of payment has already been made.'), None
[13786]411            # Additional condition in AAUE
412            if category in ('schoolfee', 'schoolfee_incl', 'schoolfee_1'):
413                if ticket.p_state == 'paid' and \
414                   ticket.p_category in ('schoolfee',
415                                         'schoolfee_incl',
416                                         'schoolfee_1') and \
417                   ticket.p_item == p_item and \
418                   ticket.p_session == p_session:
419                      return _(
420                          'Another school fee payment for this '
421                          'session has already been made.'), None
422
[11455]423        if self._isPaymentDisabled(p_session, category, student):
[13798]424            return _('This category of payments has been disabled.'), None
[8712]425        payment = createObject(u'waeup.StudentOnlinePayment')
[8954]426        timestamp = ("%d" % int(time()*10000))[1:]
[8600]427        payment.p_id = "p%s" % timestamp
428        payment.p_category = category
429        payment.p_item = p_item
430        payment.p_session = p_session
431        payment.p_level = p_level
[9154]432        payment.p_current = p_current
[8600]433        payment.amount_auth = amount
434        return None, payment
[7621]435
[10922]436    def _admissionText(self, student, portal_language):
437        inst_name = grok.getSite()['configuration'].name
438        entry_session = student['studycourse'].entry_session
439        entry_session = academic_sessions_vocab.getTerm(entry_session).title
440        text = trans(_(
[10953]441            'This is to inform you that you have been offered provisional'
442            ' admission into ${a} for the ${b} academic session as follows:',
[10922]443            mapping = {'a': inst_name, 'b': entry_session}),
444            portal_language)
445        return text
446
[14585]447    def warnCreditsOOR(self, studylevel, course=None):
448        if course and studylevel.total_credits + course.credits > 48:
449            return  _('Maximum credits exceeded.')
450        elif studylevel.total_credits > 48:
451            return _('Maximum credits exceeded.')
452        return
[10051]453
[13353]454    def getBedCoordinates(self, bedticket):
455        """Return descriptive bed coordinates.
456        This method can be used to customize the `display_coordinates`
457        property method in order to  display a
458        customary description of the bed space.
459        """
460        bc = bedticket.bed_coordinates.split(',')
461        if len(bc) == 4:
462            return bc[0]
463        return bedticket.bed_coordinates
464
[13415]465    def getAccommodationDetails(self, student):
466        """Determine the accommodation data of a student.
467        """
468        d = {}
469        d['error'] = u''
470        hostels = grok.getSite()['hostels']
471        d['booking_session'] = hostels.accommodation_session
472        d['allowed_states'] = hostels.accommodation_states
473        d['startdate'] = hostels.startdate
474        d['enddate'] = hostels.enddate
475        d['expired'] = hostels.expired
476        # Determine bed type
[13416]477        bt = 'all'
[13415]478        if student.sex == 'f':
479            sex = 'female'
480        else:
481            sex = 'male'
482        special_handling = 'regular'
483        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
484        return d
485
[13753]486    def checkAccommodationRequirements(self, student, acc_details):
[14238]487        msg = super(CustomStudentsUtils, self).checkAccommodationRequirements(
[13753]488            student, acc_details)
[14238]489        if msg:
490            return msg
[13753]491        if student.current_mode not in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
492            return _('You are not eligible to book accommodation.')
493        return
494
[8444]495    # AAUE prefix
496    STUDENT_ID_PREFIX = u'E'
Note: See TracBrowser for help on using the repository browser.