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

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

Add clearance_fee_ijmbe.

  • Property svn:keywords set to Id
File size: 21.0 KB
RevLine 
[7419]1## $Id: utils.py 14518 2017-02-08 08:18:26Z 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
[14518]265            if student.current_mode == 'ijmbe':
266                amount = academic_session.clearance_fee_ijmbe
267            elif student.faccode == 'FP':
[11653]268                amount = academic_session.clearance_fee_fp
[13377]269            elif student.current_mode.endswith('_pt'):
[13678]270                if student.is_postgrad:
271                    amount = academic_session.clearance_fee_pg_pt
272                else:
273                    amount = academic_session.clearance_fee_ug_pt
[13466]274            elif student.faccode == 'FCS':
275                # Students in clinical medical sciences pay the medical
276                # acceptance fee
[13377]277                amount = academic_session.clearance_fee_med
[13678]278            elif student.is_postgrad:  # and not part-time
[13853]279                if category != 'clearance':
[13854]280                    return _("No additional fees required."), None
[13526]281                amount = academic_session.clearance_fee_pg
[11653]282            else:
283                amount = academic_session.clearance_fee
[8753]284            p_item = student['studycourse'].certificate.code
[13689]285            if amount in (0.0, None):
286                return _(u'Amount could not be determined.'), None
[14239]287            # Add Matric Gown Fee and Lapel Fee
[13689]288            if category == 'clearance_incl':
[13414]289                amount += gateway_net_amt(academic_session.matric_gown_fee) + \
290                    gateway_net_amt(academic_session.lapel_fee)
[11004]291        elif category == 'late_registration':
[14117]292            if student.is_postgrad:
293                amount = academic_session.late_pg_registration_fee
294            else:
295                amount = academic_session.late_registration_fee
[13400]296        elif category.startswith('schoolfee'):
[8600]297            try:
[8753]298                certificate = student['studycourse'].certificate
299                p_item = certificate.code
[8600]300            except (AttributeError, TypeError):
301                return _('Study course data are incomplete.'), None
[13853]302            if student.is_postgrad and category != 'schoolfee':
[13854]303                return _("No additional fees required."), None
[13855]304            if student.current_mode in (
305                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
306                'transfer', 'mug_ft', 'mde_ft') \
307                and not category in (
308                'schoolfee_incl', 'schoolfee_1', 'schoolfee_2'):
[14244]309                    return _("You must choose a payment which includes "
[13855]310                             "additional fees."), None
[13780]311            if category in ('schoolfee_1', 'schoolfee_2'):
312                if student.current_mode == 'ug_pt':
313                    return _("Part-time students are not allowed "
314                             "to pay by instalments."), None
[14241]315                if student.entry_session < 2015:
316                    return _("You are not allowed "
317                             "to pay by instalments."), None
[14396]318            if student.state == CLEARED and category != 'schoolfee_2':
[14229]319                amount = getattr(certificate, 'school_fee_1', 0.0)
[13512]320                # Cut school fee by 50%
[14241]321                if category == 'schoolfee_1' and amount:
322                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
323            elif student.is_fresh and category == 'schoolfee_2':
324                amount = getattr(certificate, 'school_fee_1', 0.0)
325                # Cut school fee by 50%
326                if amount:
327                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
[14396]328            elif student.state == RETURNING and category != 'schoolfee_2':
[13482]329                if not student.father_name:
330                    return _("Personal data form is not properly filled."), None
[13374]331                # In case of returning school fee payment the payment session
332                # and level contain the values of the session the student
333                # has paid for.
334                p_session, p_level = self.getReturningData(student)
[8961]335                try:
336                    academic_session = grok.getSite()[
337                        'configuration'][str(p_session)]
338                except KeyError:
339                    return _(u'Session configuration object is not available.'), None
[14241]340                if student.entry_session >= 2015:
[14229]341                    amount = getattr(certificate, 'school_fee_2', 0.0)
[14241]342                    # Cut school fee by 50%
[14396]343                    if category == 'schoolfee_1' and amount:
[14241]344                        amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
[13374]345                else:
[14229]346                    amount = getattr(certificate, 'school_fee_3', 0.0)
[14241]347            elif category == 'schoolfee_2':
348                amount = getattr(certificate, 'school_fee_2', 0.0)
349                # Cut school fee by 50%
350                if amount:
351                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
[8600]352            else:
[8753]353                return _('Wrong state.'), None
[13417]354            if amount in (0.0, None):
355                return _(u'Amount could not be determined.'), None
[14244]356            # Add Student Union Fee , Student Id Card Fee and Welfare Assurance
[13512]357            if category in ('schoolfee_incl', 'schoolfee_1'):
[13414]358                amount += gateway_net_amt(academic_session.welfare_fee) + \
359                    gateway_net_amt(academic_session.union_fee)
[14244]360                if student.entry_session == 2016 \
361                    and student.entry_mode == 'ug_ft' \
362                    and student.state == CLEARED:
363                    amount += gateway_net_amt(academic_session.id_card_fee)
[13534]364            # Add non-indigenous fee and session specific penalty fees
365            if student.is_postgrad:
366                amount += academic_session.penalty_pg
[14246]367                if student.lga and not student.lga.startswith('edo'):
[13534]368                    amount += 20000.0
369            else:
370                amount += academic_session.penalty_ug
[14248]371        elif not student.is_postgrad:
372            fee_name = category + '_fee'
373            amount = getattr(academic_session, fee_name, 0.0)
[8600]374        if amount in (0.0, None):
375            return _(u'Amount could not be determined.'), None
[8677]376        # Create ticket.
[8600]377        for key in student['payments'].keys():
378            ticket = student['payments'][key]
379            if ticket.p_state == 'paid' and\
380               ticket.p_category == category and \
381               ticket.p_item == p_item and \
382               ticket.p_session == p_session:
383                  return _('This type of payment has already been made.'), None
[13786]384            # Additional condition in AAUE
385            if category in ('schoolfee', 'schoolfee_incl', 'schoolfee_1'):
386                if ticket.p_state == 'paid' and \
387                   ticket.p_category in ('schoolfee',
388                                         'schoolfee_incl',
389                                         'schoolfee_1') and \
390                   ticket.p_item == p_item and \
391                   ticket.p_session == p_session:
392                      return _(
393                          'Another school fee payment for this '
394                          'session has already been made.'), None
395
[11455]396        if self._isPaymentDisabled(p_session, category, student):
[13798]397            return _('This category of payments has been disabled.'), None
[8712]398        payment = createObject(u'waeup.StudentOnlinePayment')
[8954]399        timestamp = ("%d" % int(time()*10000))[1:]
[8600]400        payment.p_id = "p%s" % timestamp
401        payment.p_category = category
402        payment.p_item = p_item
403        payment.p_session = p_session
404        payment.p_level = p_level
[9154]405        payment.p_current = p_current
[8600]406        payment.amount_auth = amount
407        return None, payment
[7621]408
[10922]409    def _admissionText(self, student, portal_language):
410        inst_name = grok.getSite()['configuration'].name
411        entry_session = student['studycourse'].entry_session
412        entry_session = academic_sessions_vocab.getTerm(entry_session).title
413        text = trans(_(
[10953]414            'This is to inform you that you have been offered provisional'
415            ' admission into ${a} for the ${b} academic session as follows:',
[10922]416            mapping = {'a': inst_name, 'b': entry_session}),
417            portal_language)
418        return text
419
[10051]420    def maxCredits(self, studylevel):
421        """Return maximum credits.
422
423        """
424        return 48
425
[13353]426    def getBedCoordinates(self, bedticket):
427        """Return descriptive bed coordinates.
428        This method can be used to customize the `display_coordinates`
429        property method in order to  display a
430        customary description of the bed space.
431        """
432        bc = bedticket.bed_coordinates.split(',')
433        if len(bc) == 4:
434            return bc[0]
435        return bedticket.bed_coordinates
436
[13415]437    def getAccommodationDetails(self, student):
438        """Determine the accommodation data of a student.
439        """
440        d = {}
441        d['error'] = u''
442        hostels = grok.getSite()['hostels']
443        d['booking_session'] = hostels.accommodation_session
444        d['allowed_states'] = hostels.accommodation_states
445        d['startdate'] = hostels.startdate
446        d['enddate'] = hostels.enddate
447        d['expired'] = hostels.expired
448        # Determine bed type
[13416]449        bt = 'all'
[13415]450        if student.sex == 'f':
451            sex = 'female'
452        else:
453            sex = 'male'
454        special_handling = 'regular'
455        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
456        return d
457
[13753]458    def checkAccommodationRequirements(self, student, acc_details):
[14238]459        msg = super(CustomStudentsUtils, self).checkAccommodationRequirements(
[13753]460            student, acc_details)
[14238]461        if msg:
462            return msg
[13753]463        if student.current_mode not in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
464            return _('You are not eligible to book accommodation.')
465        return
466
[8444]467    # AAUE prefix
468    STUDENT_ID_PREFIX = u'E'
Note: See TracBrowser for help on using the repository browser.