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

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

ivama: Any student with less than 30 units in summary of result should not proceed no matter the GPA/CGPA.

  • Property svn:keywords set to Id
File size: 20.9 KB
Line 
1## $Id: utils.py 14464 2017-01-26 11:03: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##
18import grok
19from time import time
20from zope.component import createObject
21from waeup.kofa.interfaces import (
22    ADMITTED, CLEARANCE, REQUESTED, CLEARED, RETURNING, PAID,
23    academic_sessions_vocab)
24from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
25from waeup.kofa.accesscodes import create_accesscode
26from waeup.kofa.students.utils import trans
27from waeup.aaue.interswitch.browser import gateway_net_amt, GATEWAY_AMT
28from waeup.aaue.interfaces import MessageFactory as _
29
30class CustomStudentsUtils(NigeriaStudentsUtils):
31    """A collection of customized methods.
32
33    """
34
35    PORTRAIT_CHANGE_STATES = (ADMITTED,)
36
37    gpa_boundaries = ((1, 'FRNS / NEOR / NEOV'),
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
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
70    def getDegreeClassNumber(self, level_obj):
71        """Get degree class number (used for SessionResultsPresentation
72        reports).
73        """
74        certificate = getattr(level_obj.__parent__,'certificate', None)
75        end_level = getattr(certificate, 'end_level', None)
76        if end_level and level_obj.level >= end_level:
77            if level_obj.level > end_level:
78                # spill-over level
79                if self.gpa_params[1] == 0:
80                    # no credits taken
81                    return 0
82            failed_courses = level_obj.passed_params[4]
83            not_taken_courses = level_obj.passed_params[5]
84            if '_m' in failed_courses:
85                return 0
86            if len(not_taken_courses) \
87                and not not_taken_courses == 'NIL':
88                return 0
89        elif level_obj.gpa_params[1] < 30:
90            # credits taken below limit
91            return 0
92        if level_obj.level_verdict in ('FRNS', 'NEOR', 'NEOV'):
93            return 0
94        # use gpa_boundaries above
95        return self.getClassFromCGPA(
96            level_obj.cumulative_params[0], level_obj.student)[0]
97
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
107        elif student.is_postgrad:
108            grok.getSite()['configuration'].next_matric_integer_3 += 1
109            return
110        elif student.current_mode in ('dp_ft',):
111            grok.getSite()['configuration'].next_matric_integer_4 += 1
112            return
113        grok.getSite()['configuration'].next_matric_integer_2 += 1
114        return
115
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
124    def constructMatricNumber(self, student):
125        faccode = student.faccode
126        depcode = student.depcode
127        certcode = student.certcode
128        degree = getattr(
129            getattr(student.get('studycourse', None), 'certificate', None),
130                'degree', None)
131        year = unicode(student.entry_session)[2:]
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
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
138        if student.is_postgrad:
139            next_integer = grok.getSite()['configuration'].next_matric_integer_3
140            if not degree or next_integer == 0:
141                return _('Matriculation number cannot be set.'), None
142            if student.faccode in ('IOE'):
143                return None, "AAU/SPS/%s/%s/%s/%05d" % (
144                    faccode, year, degree, next_integer)
145            return None, "AAU/SPS/%s/%s/%s/%s/%05d" % (
146                faccode, depcode, year, degree, next_integer)
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
151            return None, "PTP/%s/%s/%s/%05d" % (
152                faccode, depcode, year, next_integer)
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)
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)
165
166    def getReturningData(self, student):
167        """ This method defines what happens after school fee payment
168        of returning students depending on the student's senate verdict.
169        """
170        prev_level = student['studycourse'].current_level
171        cur_verdict = student['studycourse'].current_verdict
172        if cur_verdict in ('A','B','C', 'L','M','N','Z',):
173            # Successful student
174            new_level = divmod(int(prev_level),100)[0]*100 + 100
175        #elif cur_verdict == 'C':
176        #    # Student on probation
177        #    new_level = int(prev_level) + 10
178        else:
179            # Student is somehow in an undefined state.
180            # Level has to be set manually.
181            new_level = prev_level
182        new_session = student['studycourse'].current_session + 1
183        return new_session, new_level
184
185    def _isPaymentDisabled(self, p_session, category, student):
186        academic_session = self._getSessionConfiguration(p_session)
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
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
203        if category == 'hostel_maintenance' and \
204            'maint_all' in academic_session.payment_disabled:
205            return True
206        return False
207
208    def setPaymentDetails(self, category, student,
209            previous_session=None, previous_level=None):
210        """Create Payment object and set the payment data of a student for
211        the payment category specified.
212
213        """
214        details = {}
215        p_item = u''
216        amount = 0.0
217        error = u''
218        if previous_session:
219            return _('Previous session payment not yet implemented.'), None
220        p_session = student['studycourse'].current_session
221        p_level = student['studycourse'].current_level
222        p_current = True
223        academic_session = self._getSessionConfiguration(p_session)
224        if academic_session == None:
225            return _(u'Session configuration object is not available.'), None
226        # Determine fee.
227        if category == 'transfer':
228            amount = academic_session.transfer_fee
229        elif category == 'transcript_local':
230            amount = academic_session.transcript_fee_local
231        elif category == 'transcript_inter':
232            amount = academic_session.transcript_fee_inter
233        elif category == 'bed_allocation':
234            amount = academic_session.booking_fee
235        elif category == 'restitution':
236            if student.entry_session == 2016 \
237                and student.current_session == 2016 \
238                or student.current_mode != 'ug_ft':
239                return _(u'Restitution fee payment not required.'), None
240            amount = academic_session.restitution_fee
241        elif category == 'hostel_maintenance':
242            amount = 0.0
243            bedticket = student['accommodation'].get(
244                str(student.current_session), None)
245            if bedticket is not None and bedticket.bed is not None:
246                p_item = bedticket.display_coordinates
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:
253                return _(u'No bed allocated.'), None
254        elif student.current_mode == 'found' and category not in (
255            'schoolfee', 'clearance', 'late_registration'):
256            return _('Not allowed.'), None
257        elif category.startswith('clearance'):
258            if student.state not in (ADMITTED, CLEARANCE, REQUESTED, CLEARED):
259                return _(u'Acceptance Fee payments not allowed.'), None
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
265            if student.faccode == 'FP':  # includes IJMBE
266                amount = academic_session.clearance_fee_fp
267            elif student.current_mode.endswith('_pt'):
268                if student.is_postgrad:
269                    amount = academic_session.clearance_fee_pg_pt
270                else:
271                    amount = academic_session.clearance_fee_ug_pt
272            elif student.faccode == 'FCS':
273                # Students in clinical medical sciences pay the medical
274                # acceptance fee
275                amount = academic_session.clearance_fee_med
276            elif student.is_postgrad:  # and not part-time
277                if category != 'clearance':
278                    return _("No additional fees required."), None
279                amount = academic_session.clearance_fee_pg
280            else:
281                amount = academic_session.clearance_fee
282            p_item = student['studycourse'].certificate.code
283            if amount in (0.0, None):
284                return _(u'Amount could not be determined.'), None
285            # Add Matric Gown Fee and Lapel Fee
286            if category == 'clearance_incl':
287                amount += gateway_net_amt(academic_session.matric_gown_fee) + \
288                    gateway_net_amt(academic_session.lapel_fee)
289        elif category == 'late_registration':
290            if student.is_postgrad:
291                amount = academic_session.late_pg_registration_fee
292            else:
293                amount = academic_session.late_registration_fee
294        elif category.startswith('schoolfee'):
295            try:
296                certificate = student['studycourse'].certificate
297                p_item = certificate.code
298            except (AttributeError, TypeError):
299                return _('Study course data are incomplete.'), None
300            if student.is_postgrad and category != 'schoolfee':
301                return _("No additional fees required."), None
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'):
307                    return _("You must choose a payment which includes "
308                             "additional fees."), None
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
313                if student.entry_session < 2015:
314                    return _("You are not allowed "
315                             "to pay by instalments."), None
316            if student.state == CLEARED and category != 'schoolfee_2':
317                amount = getattr(certificate, 'school_fee_1', 0.0)
318                # Cut school fee by 50%
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
326            elif student.state == RETURNING and category != 'schoolfee_2':
327                if not student.father_name:
328                    return _("Personal data form is not properly filled."), None
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)
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
338                if student.entry_session >= 2015:
339                    amount = getattr(certificate, 'school_fee_2', 0.0)
340                    # Cut school fee by 50%
341                    if category == 'schoolfee_1' and amount:
342                        amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
343                else:
344                    amount = getattr(certificate, 'school_fee_3', 0.0)
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
350            else:
351                return _('Wrong state.'), None
352            if amount in (0.0, None):
353                return _(u'Amount could not be determined.'), None
354            # Add Student Union Fee , Student Id Card Fee and Welfare Assurance
355            if category in ('schoolfee_incl', 'schoolfee_1'):
356                amount += gateway_net_amt(academic_session.welfare_fee) + \
357                    gateway_net_amt(academic_session.union_fee)
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)
362            # Add non-indigenous fee and session specific penalty fees
363            if student.is_postgrad:
364                amount += academic_session.penalty_pg
365                if student.lga and not student.lga.startswith('edo'):
366                    amount += 20000.0
367            else:
368                amount += academic_session.penalty_ug
369        elif not student.is_postgrad:
370            fee_name = category + '_fee'
371            amount = getattr(academic_session, fee_name, 0.0)
372        if amount in (0.0, None):
373            return _(u'Amount could not be determined.'), None
374        # Create ticket.
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
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
394        if self._isPaymentDisabled(p_session, category, student):
395            return _('This category of payments has been disabled.'), None
396        payment = createObject(u'waeup.StudentOnlinePayment')
397        timestamp = ("%d" % int(time()*10000))[1:]
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
403        payment.p_current = p_current
404        payment.amount_auth = amount
405        return None, payment
406
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(_(
412            'This is to inform you that you have been offered provisional'
413            ' admission into ${a} for the ${b} academic session as follows:',
414            mapping = {'a': inst_name, 'b': entry_session}),
415            portal_language)
416        return text
417
418    def maxCredits(self, studylevel):
419        """Return maximum credits.
420
421        """
422        return 48
423
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
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
447        bt = 'all'
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
456    def checkAccommodationRequirements(self, student, acc_details):
457        msg = super(CustomStudentsUtils, self).checkAccommodationRequirements(
458            student, acc_details)
459        if msg:
460            return msg
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
465    # AAUE prefix
466    STUDENT_ID_PREFIX = u'E'
Note: See TracBrowser for help on using the repository browser.