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

Last change on this file since 14518 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
Line 
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##
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            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', 'NER', 'NYV'):
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.current_mode == 'ijmbe':
266                amount = academic_session.clearance_fee_ijmbe
267            elif student.faccode == 'FP':
268                amount = academic_session.clearance_fee_fp
269            elif student.current_mode.endswith('_pt'):
270                if student.is_postgrad:
271                    amount = academic_session.clearance_fee_pg_pt
272                else:
273                    amount = academic_session.clearance_fee_ug_pt
274            elif student.faccode == 'FCS':
275                # Students in clinical medical sciences pay the medical
276                # acceptance fee
277                amount = academic_session.clearance_fee_med
278            elif student.is_postgrad:  # and not part-time
279                if category != 'clearance':
280                    return _("No additional fees required."), None
281                amount = academic_session.clearance_fee_pg
282            else:
283                amount = academic_session.clearance_fee
284            p_item = student['studycourse'].certificate.code
285            if amount in (0.0, None):
286                return _(u'Amount could not be determined.'), None
287            # Add Matric Gown Fee and Lapel Fee
288            if category == 'clearance_incl':
289                amount += gateway_net_amt(academic_session.matric_gown_fee) + \
290                    gateway_net_amt(academic_session.lapel_fee)
291        elif category == 'late_registration':
292            if student.is_postgrad:
293                amount = academic_session.late_pg_registration_fee
294            else:
295                amount = academic_session.late_registration_fee
296        elif category.startswith('schoolfee'):
297            try:
298                certificate = student['studycourse'].certificate
299                p_item = certificate.code
300            except (AttributeError, TypeError):
301                return _('Study course data are incomplete.'), None
302            if student.is_postgrad and category != 'schoolfee':
303                return _("No additional fees required."), None
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'):
309                    return _("You must choose a payment which includes "
310                             "additional fees."), None
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
315                if student.entry_session < 2015:
316                    return _("You are not allowed "
317                             "to pay by instalments."), None
318            if student.state == CLEARED and category != 'schoolfee_2':
319                amount = getattr(certificate, 'school_fee_1', 0.0)
320                # Cut school fee by 50%
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
328            elif student.state == RETURNING and category != 'schoolfee_2':
329                if not student.father_name:
330                    return _("Personal data form is not properly filled."), None
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)
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
340                if student.entry_session >= 2015:
341                    amount = getattr(certificate, 'school_fee_2', 0.0)
342                    # Cut school fee by 50%
343                    if category == 'schoolfee_1' and amount:
344                        amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
345                else:
346                    amount = getattr(certificate, 'school_fee_3', 0.0)
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
352            else:
353                return _('Wrong state.'), None
354            if amount in (0.0, None):
355                return _(u'Amount could not be determined.'), None
356            # Add Student Union Fee , Student Id Card Fee and Welfare Assurance
357            if category in ('schoolfee_incl', 'schoolfee_1'):
358                amount += gateway_net_amt(academic_session.welfare_fee) + \
359                    gateway_net_amt(academic_session.union_fee)
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)
364            # Add non-indigenous fee and session specific penalty fees
365            if student.is_postgrad:
366                amount += academic_session.penalty_pg
367                if student.lga and not student.lga.startswith('edo'):
368                    amount += 20000.0
369            else:
370                amount += academic_session.penalty_ug
371        elif not student.is_postgrad:
372            fee_name = category + '_fee'
373            amount = getattr(academic_session, fee_name, 0.0)
374        if amount in (0.0, None):
375            return _(u'Amount could not be determined.'), None
376        # Create ticket.
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
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
396        if self._isPaymentDisabled(p_session, category, student):
397            return _('This category of payments has been disabled.'), None
398        payment = createObject(u'waeup.StudentOnlinePayment')
399        timestamp = ("%d" % int(time()*10000))[1:]
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
405        payment.p_current = p_current
406        payment.amount_auth = amount
407        return None, payment
408
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(_(
414            'This is to inform you that you have been offered provisional'
415            ' admission into ${a} for the ${b} academic session as follows:',
416            mapping = {'a': inst_name, 'b': entry_session}),
417            portal_language)
418        return text
419
420    def maxCredits(self, studylevel):
421        """Return maximum credits.
422
423        """
424        return 48
425
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
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
449        bt = 'all'
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
458    def checkAccommodationRequirements(self, student, acc_details):
459        msg = super(CustomStudentsUtils, self).checkAccommodationRequirements(
460            student, acc_details)
461        if msg:
462            return msg
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
467    # AAUE prefix
468    STUDENT_ID_PREFIX = u'E'
Note: See TracBrowser for help on using the repository browser.