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

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

IJMBE Preparation are not suppose have matric number

  • Property svn:keywords set to Id
File size: 22.9 KB
Line 
1## $Id: utils.py 14615 2017-03-09 09:34:56Z 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 in ('found', 'ijmbe'):
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_ug_pt' in academic_session.payment_disabled and \
197                student.current_mode in ('ug_pt', 'de_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            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
239        academic_session = self._getSessionConfiguration(p_session)
240        if academic_session == None:
241            return _(u'Session configuration object is not available.'), None
242        # Determine fee.
243        if category == 'transfer':
244            amount = academic_session.transfer_fee
245        elif category == 'transcript_local':
246            amount = academic_session.transcript_fee_local
247        elif category == 'transcript_inter':
248            amount = academic_session.transcript_fee_inter
249        elif category == 'bed_allocation':
250            amount = academic_session.booking_fee
251        elif category == 'restitution':
252            if student.entry_session == 2016 \
253                and student.current_session == 2016 \
254                or student.current_mode != 'ug_ft':
255                return _(u'Restitution fee payment not required.'), None
256            amount = academic_session.restitution_fee
257        elif category == 'hostel_maintenance':
258            amount = 0.0
259            bedticket = student['accommodation'].get(
260                str(student.current_session), None)
261            if bedticket is not None and bedticket.bed is not None:
262                p_item = bedticket.display_coordinates
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:
269                return _(u'No bed allocated.'), None
270        elif student.current_mode == 'found' and category not in (
271            'schoolfee', 'clearance', 'late_registration'):
272            return _('Not allowed.'), None
273        elif category.startswith('clearance'):
274            if student.state not in (ADMITTED, CLEARANCE, REQUESTED, CLEARED):
275                return _(u'Acceptance Fee payments not allowed.'), None
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
281            if student.current_mode == 'ijmbe':
282                amount = academic_session.clearance_fee_ijmbe
283            elif student.faccode == 'FP':
284                amount = academic_session.clearance_fee_fp
285            elif student.current_mode.endswith('_pt'):
286                if student.is_postgrad:
287                    amount = academic_session.clearance_fee_pg_pt
288                else:
289                    amount = academic_session.clearance_fee_ug_pt
290            elif student.faccode == 'FCS':
291                # Students in clinical medical sciences pay the medical
292                # acceptance fee
293                amount = academic_session.clearance_fee_med
294            elif student.is_postgrad:  # and not part-time
295                if category != 'clearance':
296                    return _("No additional fees required."), None
297                amount = academic_session.clearance_fee_pg
298            else:
299                amount = academic_session.clearance_fee
300            p_item = student['studycourse'].certificate.code
301            if amount in (0.0, None):
302                return _(u'Amount could not be determined.'), None
303            # Add Matric Gown Fee and Lapel Fee
304            if category == 'clearance_incl':
305                amount += gateway_net_amt(academic_session.matric_gown_fee) + \
306                    gateway_net_amt(academic_session.lapel_fee)
307        elif category == 'late_registration':
308            if student.is_postgrad:
309                amount = academic_session.late_pg_registration_fee
310            else:
311                amount = academic_session.late_registration_fee
312        elif category.startswith('schoolfee'):
313            try:
314                certificate = student['studycourse'].certificate
315                p_item = certificate.code
316            except (AttributeError, TypeError):
317                return _('Study course data are incomplete.'), None
318            if student.is_postgrad and category != 'schoolfee':
319                return _("No additional fees required."), None
320            if not previous_session and student.current_mode in (
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'):
325                    return _("You must choose a payment which includes "
326                             "additional fees."), None
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
331                if student.entry_session < 2015:
332                    return _("You are not allowed "
333                             "to pay by instalments."), None
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':
346                amount = getattr(certificate, 'school_fee_1', 0.0)
347                # Cut school fee by 50%
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
355            elif student.state == RETURNING and category != 'schoolfee_2':
356                if not student.father_name:
357                    return _("Personal data form is not properly filled."), None
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)
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
367                if student.entry_session >= 2015:
368                    amount = getattr(certificate, 'school_fee_2', 0.0)
369                    # Cut school fee by 50%
370                    if category == 'schoolfee_1' and amount:
371                        amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
372                else:
373                    amount = getattr(certificate, 'school_fee_3', 0.0)
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
379            else:
380                return _('Wrong state.'), None
381            if amount in (0.0, None):
382                return _(u'Amount could not be determined.'), None
383            # Add Student Union Fee , Student Id Card Fee and Welfare Assurance
384            if category in ('schoolfee_incl', 'schoolfee_1'):
385                amount += gateway_net_amt(academic_session.welfare_fee) + \
386                    gateway_net_amt(academic_session.union_fee)
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)
391            # Add non-indigenous fee and session specific penalty fees
392            if student.is_postgrad:
393                amount += academic_session.penalty_pg
394                if student.lga and not student.lga.startswith('edo'):
395                    amount += 20000.0
396            else:
397                amount += academic_session.penalty_ug
398        elif not student.is_postgrad:
399            fee_name = category + '_fee'
400            amount = getattr(academic_session, fee_name, 0.0)
401        if amount in (0.0, None):
402            return _(u'Amount could not be determined.'), None
403        # Create ticket.
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
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
423        if self._isPaymentDisabled(p_session, category, student):
424            return _('This category of payments has been disabled.'), None
425        payment = createObject(u'waeup.StudentOnlinePayment')
426        timestamp = ("%d" % int(time()*10000))[1:]
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
432        payment.p_current = p_current
433        payment.amount_auth = amount
434        return None, payment
435
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(_(
441            'This is to inform you that you have been offered provisional'
442            ' admission into ${a} for the ${b} academic session as follows:',
443            mapping = {'a': inst_name, 'b': entry_session}),
444            portal_language)
445        return text
446
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
453
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
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
477        bt = 'all'
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
486    def checkAccommodationRequirements(self, student, acc_details):
487        msg = super(CustomStudentsUtils, self).checkAccommodationRequirements(
488            student, acc_details)
489        if msg:
490            return msg
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
495    # AAUE prefix
496    STUDENT_ID_PREFIX = u'E'
497
498    STUDENT_EXPORTER_NAMES = ('students', 'studentstudycourses',
499            'studentstudylevels', 'coursetickets',
500            'studentpayments', 'studentunpaidpayments',
501            'bedtickets', 'paymentsoverview',
502            'studylevelsoverview', 'combocard', 'bursary',
503            'levelreportdata')
Note: See TracBrowser for help on using the repository browser.