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

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

Catch traceback.

  • Property svn:keywords set to Id
File size: 21.2 KB
Line 
1## $Id: utils.py 14538 2017-02-13 06:20:08Z 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 / NER / NYV'),
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 level_obj.gpa_params[1] == 0:
80                    # no credits taken
81                    return 0
82            elif level_obj.gpa_params[1] < 30:
83                # credits taken below limit
84                return 0
85            failed_courses = level_obj.passed_params[4]
86            not_taken_courses = level_obj.passed_params[5]
87            if '_m' in failed_courses:
88                return 0
89            if len(not_taken_courses) \
90                and not not_taken_courses == 'Nil':
91                return 0
92        elif level_obj.gpa_params[1] < 30:
93            # credits taken below limit
94            return 0
95        if level_obj.level_verdict in ('FRNS', 'NER', 'NYV'):
96            return 0
97        # use gpa_boundaries above
98        return self.getClassFromCGPA(
99            level_obj.cumulative_params[0], level_obj.student)[0]
100
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
110        elif student.is_postgrad:
111            grok.getSite()['configuration'].next_matric_integer_3 += 1
112            return
113        elif student.current_mode in ('dp_ft',):
114            grok.getSite()['configuration'].next_matric_integer_4 += 1
115            return
116        grok.getSite()['configuration'].next_matric_integer_2 += 1
117        return
118
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
127    def constructMatricNumber(self, student):
128        faccode = student.faccode
129        depcode = student.depcode
130        certcode = student.certcode
131        degree = getattr(
132            getattr(student.get('studycourse', None), 'certificate', None),
133                'degree', None)
134        year = unicode(student.entry_session)[2:]
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
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
141        if student.is_postgrad:
142            next_integer = grok.getSite()['configuration'].next_matric_integer_3
143            if not degree or next_integer == 0:
144                return _('Matriculation number cannot be set.'), None
145            if student.faccode in ('IOE'):
146                return None, "AAU/SPS/%s/%s/%s/%05d" % (
147                    faccode, year, degree, next_integer)
148            return None, "AAU/SPS/%s/%s/%s/%s/%05d" % (
149                faccode, depcode, year, degree, next_integer)
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
154            return None, "PTP/%s/%s/%s/%05d" % (
155                faccode, depcode, year, next_integer)
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)
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)
168
169    def getReturningData(self, student):
170        """ This method defines what happens after school fee payment
171        of returning students depending on the student's senate verdict.
172        """
173        prev_level = student['studycourse'].current_level
174        cur_verdict = student['studycourse'].current_verdict
175        if cur_verdict in ('A','B','C', 'L','M','N','Z',):
176            # Successful student
177            new_level = divmod(int(prev_level),100)[0]*100 + 100
178        #elif cur_verdict == 'C':
179        #    # Student on probation
180        #    new_level = int(prev_level) + 10
181        else:
182            # Student is somehow in an undefined state.
183            # Level has to be set manually.
184            new_level = prev_level
185        new_session = student['studycourse'].current_session + 1
186        return new_session, new_level
187
188    def _isPaymentDisabled(self, p_session, category, student):
189        academic_session = self._getSessionConfiguration(p_session)
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
196            if 'sf_pt' in academic_session.payment_disabled and \
197                student.current_mode.endswith('_pt'):
198                return True
199            if 'sf_found' in academic_session.payment_disabled and \
200                student.current_mode == 'found':
201                return True
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
206        if category == 'hostel_maintenance' and \
207            'maint_all' in academic_session.payment_disabled:
208            return True
209        return False
210
211    def setPaymentDetails(self, category, student,
212            previous_session=None, previous_level=None):
213        """Create Payment object and set the payment data of a student for
214        the payment category specified.
215
216        """
217        details = {}
218        p_item = u''
219        amount = 0.0
220        error = u''
221        if previous_session:
222            return _('Previous session payment not yet implemented.'), None
223        p_session = student['studycourse'].current_session
224        p_level = student['studycourse'].current_level
225        p_current = True
226        academic_session = self._getSessionConfiguration(p_session)
227        if academic_session == None:
228            return _(u'Session configuration object is not available.'), None
229        # Determine fee.
230        if category == 'transfer':
231            amount = academic_session.transfer_fee
232        elif category == 'transcript_local':
233            amount = academic_session.transcript_fee_local
234        elif category == 'transcript_inter':
235            amount = academic_session.transcript_fee_inter
236        elif category == 'bed_allocation':
237            amount = academic_session.booking_fee
238        elif category == 'restitution':
239            if student.entry_session == 2016 \
240                and student.current_session == 2016 \
241                or student.current_mode != 'ug_ft':
242                return _(u'Restitution fee payment not required.'), None
243            amount = academic_session.restitution_fee
244        elif category == 'hostel_maintenance':
245            amount = 0.0
246            bedticket = student['accommodation'].get(
247                str(student.current_session), None)
248            if bedticket is not None and bedticket.bed is not None:
249                p_item = bedticket.display_coordinates
250                if bedticket.bed.__parent__.maint_fee > 0:
251                    amount = bedticket.bed.__parent__.maint_fee
252                else:
253                    # fallback
254                    amount = academic_session.maint_fee
255            else:
256                return _(u'No bed allocated.'), None
257        elif student.current_mode == 'found' and category not in (
258            'schoolfee', 'clearance', 'late_registration'):
259            return _('Not allowed.'), None
260        elif category.startswith('clearance'):
261            if student.state not in (ADMITTED, CLEARANCE, REQUESTED, CLEARED):
262                return _(u'Acceptance Fee payments not allowed.'), None
263            if student.current_mode in (
264                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
265                'transfer', 'mug_ft', 'mde_ft') \
266                and category != 'clearance_incl':
267                    return _("Additional fees must be included."), None
268            if student.current_mode == 'ijmbe':
269                amount = academic_session.clearance_fee_ijmbe
270            elif student.faccode == 'FP':
271                amount = academic_session.clearance_fee_fp
272            elif student.current_mode.endswith('_pt'):
273                if student.is_postgrad:
274                    amount = academic_session.clearance_fee_pg_pt
275                else:
276                    amount = academic_session.clearance_fee_ug_pt
277            elif student.faccode == 'FCS':
278                # Students in clinical medical sciences pay the medical
279                # acceptance fee
280                amount = academic_session.clearance_fee_med
281            elif student.is_postgrad:  # and not part-time
282                if category != 'clearance':
283                    return _("No additional fees required."), None
284                amount = academic_session.clearance_fee_pg
285            else:
286                amount = academic_session.clearance_fee
287            p_item = student['studycourse'].certificate.code
288            if amount in (0.0, None):
289                return _(u'Amount could not be determined.'), None
290            # Add Matric Gown Fee and Lapel Fee
291            if category == 'clearance_incl':
292                amount += gateway_net_amt(academic_session.matric_gown_fee) + \
293                    gateway_net_amt(academic_session.lapel_fee)
294        elif category == 'late_registration':
295            if student.is_postgrad:
296                amount = academic_session.late_pg_registration_fee
297            else:
298                amount = academic_session.late_registration_fee
299        elif category.startswith('schoolfee'):
300            try:
301                certificate = student['studycourse'].certificate
302                p_item = certificate.code
303            except (AttributeError, TypeError):
304                return _('Study course data are incomplete.'), None
305            if student.is_postgrad and category != 'schoolfee':
306                return _("No additional fees required."), None
307            if student.current_mode in (
308                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
309                'transfer', 'mug_ft', 'mde_ft') \
310                and not category in (
311                'schoolfee_incl', 'schoolfee_1', 'schoolfee_2'):
312                    return _("You must choose a payment which includes "
313                             "additional fees."), None
314            if category in ('schoolfee_1', 'schoolfee_2'):
315                if student.current_mode == 'ug_pt':
316                    return _("Part-time students are not allowed "
317                             "to pay by instalments."), None
318                if student.entry_session < 2015:
319                    return _("You are not allowed "
320                             "to pay by instalments."), None
321            if student.state == CLEARED and category != 'schoolfee_2':
322                amount = getattr(certificate, 'school_fee_1', 0.0)
323                # Cut school fee by 50%
324                if category == 'schoolfee_1' and amount:
325                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
326            elif student.is_fresh and category == 'schoolfee_2':
327                amount = getattr(certificate, 'school_fee_1', 0.0)
328                # Cut school fee by 50%
329                if amount:
330                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
331            elif student.state == RETURNING and category != 'schoolfee_2':
332                if not student.father_name:
333                    return _("Personal data form is not properly filled."), None
334                # In case of returning school fee payment the payment session
335                # and level contain the values of the session the student
336                # has paid for.
337                p_session, p_level = self.getReturningData(student)
338                try:
339                    academic_session = grok.getSite()[
340                        'configuration'][str(p_session)]
341                except KeyError:
342                    return _(u'Session configuration object is not available.'), None
343                if student.entry_session >= 2015:
344                    amount = getattr(certificate, 'school_fee_2', 0.0)
345                    # Cut school fee by 50%
346                    if category == 'schoolfee_1' and amount:
347                        amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
348                else:
349                    amount = getattr(certificate, 'school_fee_3', 0.0)
350            elif category == 'schoolfee_2':
351                amount = getattr(certificate, 'school_fee_2', 0.0)
352                # Cut school fee by 50%
353                if amount:
354                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
355            else:
356                return _('Wrong state.'), None
357            if amount in (0.0, None):
358                return _(u'Amount could not be determined.'), None
359            # Add Student Union Fee , Student Id Card Fee and Welfare Assurance
360            if category in ('schoolfee_incl', 'schoolfee_1'):
361                amount += gateway_net_amt(academic_session.welfare_fee) + \
362                    gateway_net_amt(academic_session.union_fee)
363                if student.entry_session == 2016 \
364                    and student.entry_mode == 'ug_ft' \
365                    and student.state == CLEARED:
366                    amount += gateway_net_amt(academic_session.id_card_fee)
367            # Add non-indigenous fee and session specific penalty fees
368            if student.is_postgrad:
369                amount += academic_session.penalty_pg
370                if student.lga and not student.lga.startswith('edo'):
371                    amount += 20000.0
372            else:
373                amount += academic_session.penalty_ug
374        elif not student.is_postgrad:
375            fee_name = category + '_fee'
376            amount = getattr(academic_session, fee_name, 0.0)
377        if amount in (0.0, None):
378            return _(u'Amount could not be determined.'), None
379        # Create ticket.
380        for key in student['payments'].keys():
381            ticket = student['payments'][key]
382            if ticket.p_state == 'paid' and\
383               ticket.p_category == category and \
384               ticket.p_item == p_item and \
385               ticket.p_session == p_session:
386                  return _('This type of payment has already been made.'), None
387            # Additional condition in AAUE
388            if category in ('schoolfee', 'schoolfee_incl', 'schoolfee_1'):
389                if ticket.p_state == 'paid' and \
390                   ticket.p_category in ('schoolfee',
391                                         'schoolfee_incl',
392                                         'schoolfee_1') and \
393                   ticket.p_item == p_item and \
394                   ticket.p_session == p_session:
395                      return _(
396                          'Another school fee payment for this '
397                          'session has already been made.'), None
398
399        if self._isPaymentDisabled(p_session, category, student):
400            return _('This category of payments has been disabled.'), None
401        payment = createObject(u'waeup.StudentOnlinePayment')
402        timestamp = ("%d" % int(time()*10000))[1:]
403        payment.p_id = "p%s" % timestamp
404        payment.p_category = category
405        payment.p_item = p_item
406        payment.p_session = p_session
407        payment.p_level = p_level
408        payment.p_current = p_current
409        payment.amount_auth = amount
410        return None, payment
411
412    def _admissionText(self, student, portal_language):
413        inst_name = grok.getSite()['configuration'].name
414        entry_session = student['studycourse'].entry_session
415        entry_session = academic_sessions_vocab.getTerm(entry_session).title
416        text = trans(_(
417            'This is to inform you that you have been offered provisional'
418            ' admission into ${a} for the ${b} academic session as follows:',
419            mapping = {'a': inst_name, 'b': entry_session}),
420            portal_language)
421        return text
422
423    def maxCredits(self, studylevel):
424        """Return maximum credits.
425
426        """
427        return 48
428
429    def getBedCoordinates(self, bedticket):
430        """Return descriptive bed coordinates.
431        This method can be used to customize the `display_coordinates`
432        property method in order to  display a
433        customary description of the bed space.
434        """
435        bc = bedticket.bed_coordinates.split(',')
436        if len(bc) == 4:
437            return bc[0]
438        return bedticket.bed_coordinates
439
440    def getAccommodationDetails(self, student):
441        """Determine the accommodation data of a student.
442        """
443        d = {}
444        d['error'] = u''
445        hostels = grok.getSite()['hostels']
446        d['booking_session'] = hostels.accommodation_session
447        d['allowed_states'] = hostels.accommodation_states
448        d['startdate'] = hostels.startdate
449        d['enddate'] = hostels.enddate
450        d['expired'] = hostels.expired
451        # Determine bed type
452        bt = 'all'
453        if student.sex == 'f':
454            sex = 'female'
455        else:
456            sex = 'male'
457        special_handling = 'regular'
458        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
459        return d
460
461    def checkAccommodationRequirements(self, student, acc_details):
462        msg = super(CustomStudentsUtils, self).checkAccommodationRequirements(
463            student, acc_details)
464        if msg:
465            return msg
466        if student.current_mode not in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
467            return _('You are not eligible to book accommodation.')
468        return
469
470    # AAUE prefix
471    STUDENT_ID_PREFIX = u'E'
Note: See TracBrowser for help on using the repository browser.