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

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

"dp_ft" Diploma Full Time should be included in the payment of restitution fee.

  • Property svn:keywords set to Id
File size: 22.9 KB
Line 
1## $Id: utils.py 14660 2017-03-29 20:51:06Z 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                or student.current_mode not in ('ug_ft', 'dp_ft'):
254                return _(u'Restitution fee payment not required.'), None
255            amount = academic_session.restitution_fee
256        elif category == 'hostel_maintenance':
257            amount = 0.0
258            bedticket = student['accommodation'].get(
259                str(student.current_session), None)
260            if bedticket is not None and bedticket.bed is not None:
261                p_item = bedticket.display_coordinates
262                if bedticket.bed.__parent__.maint_fee > 0:
263                    amount = bedticket.bed.__parent__.maint_fee
264                else:
265                    # fallback
266                    amount = academic_session.maint_fee
267            else:
268                return _(u'No bed allocated.'), None
269        elif student.current_mode == 'found' and category not in (
270            'schoolfee', 'clearance', 'late_registration'):
271            return _('Not allowed.'), None
272        elif category.startswith('clearance'):
273            if student.state not in (ADMITTED, CLEARANCE, REQUESTED, CLEARED):
274                return _(u'Acceptance Fee payments not allowed.'), None
275            if student.current_mode in (
276                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
277                'transfer', 'mug_ft', 'mde_ft') \
278                and category != 'clearance_incl':
279                    return _("Additional fees must be included."), None
280            if student.current_mode == 'ijmbe':
281                amount = academic_session.clearance_fee_ijmbe
282            elif student.faccode == 'FP':
283                amount = academic_session.clearance_fee_fp
284            elif student.current_mode.endswith('_pt'):
285                if student.is_postgrad:
286                    amount = academic_session.clearance_fee_pg_pt
287                else:
288                    amount = academic_session.clearance_fee_ug_pt
289            elif student.faccode == 'FCS':
290                # Students in clinical medical sciences pay the medical
291                # acceptance fee
292                amount = academic_session.clearance_fee_med
293            elif student.is_postgrad:  # and not part-time
294                if category != 'clearance':
295                    return _("No additional fees required."), None
296                amount = academic_session.clearance_fee_pg
297            else:
298                amount = academic_session.clearance_fee
299            p_item = student['studycourse'].certificate.code
300            if amount in (0.0, None):
301                return _(u'Amount could not be determined.'), None
302            # Add Matric Gown Fee and Lapel Fee
303            if category == 'clearance_incl':
304                amount += gateway_net_amt(academic_session.matric_gown_fee) + \
305                    gateway_net_amt(academic_session.lapel_fee)
306        elif category == 'late_registration':
307            if student.is_postgrad:
308                amount = academic_session.late_pg_registration_fee
309            else:
310                amount = academic_session.late_registration_fee
311        elif category.startswith('schoolfee'):
312            try:
313                certificate = student['studycourse'].certificate
314                p_item = certificate.code
315            except (AttributeError, TypeError):
316                return _('Study course data are incomplete.'), None
317            if student.is_postgrad and category != 'schoolfee':
318                return _("No additional fees required."), None
319            if not previous_session and student.current_mode in (
320                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
321                'transfer', 'mug_ft', 'mde_ft') \
322                and not category in (
323                'schoolfee_incl', 'schoolfee_1', 'schoolfee_2'):
324                    return _("You must choose a payment which includes "
325                             "additional fees."), None
326            if category in ('schoolfee_1', 'schoolfee_2'):
327                if student.current_mode == 'ug_pt':
328                    return _("Part-time students are not allowed "
329                             "to pay by instalments."), None
330                if student.entry_session < 2015:
331                    return _("You are not allowed "
332                             "to pay by instalments."), None
333            if previous_session:
334                # Students can pay for previous sessions in all
335                # workflow states.  Fresh students are excluded by the
336                # update method of the PreviousPaymentAddFormPage.
337                if previous_level == 100:
338                    amount = getattr(certificate, 'school_fee_1', 0.0)
339                else:
340                    if student.entry_session >= 2015:
341                        amount = getattr(certificate, 'school_fee_2', 0.0)
342                    else:
343                        amount = getattr(certificate, 'school_fee_3', 0.0)
344            elif student.state == CLEARED and category != 'schoolfee_2':
345                amount = getattr(certificate, 'school_fee_1', 0.0)
346                # Cut school fee by 50%
347                if category == 'schoolfee_1' and amount:
348                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
349            elif student.is_fresh and category == 'schoolfee_2':
350                amount = getattr(certificate, 'school_fee_1', 0.0)
351                # Cut school fee by 50%
352                if amount:
353                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
354            elif student.state == RETURNING and category != 'schoolfee_2':
355                if not student.father_name:
356                    return _("Personal data form is not properly filled."), None
357                # In case of returning school fee payment the payment session
358                # and level contain the values of the session the student
359                # has paid for.
360                p_session, p_level = self.getReturningData(student)
361                try:
362                    academic_session = grok.getSite()[
363                        'configuration'][str(p_session)]
364                except KeyError:
365                    return _(u'Session configuration object is not available.'), None
366                if student.entry_session >= 2015:
367                    amount = getattr(certificate, 'school_fee_2', 0.0)
368                    # Cut school fee by 50%
369                    if category == 'schoolfee_1' and amount:
370                        amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
371                else:
372                    amount = getattr(certificate, 'school_fee_3', 0.0)
373            elif category == 'schoolfee_2':
374                amount = getattr(certificate, 'school_fee_2', 0.0)
375                # Cut school fee by 50%
376                if amount:
377                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
378            else:
379                return _('Wrong state.'), None
380            if amount in (0.0, None):
381                return _(u'Amount could not be determined.'), None
382            # Add Student Union Fee , Student Id Card Fee and Welfare Assurance
383            if category in ('schoolfee_incl', 'schoolfee_1'):
384                amount += gateway_net_amt(academic_session.welfare_fee) + \
385                    gateway_net_amt(academic_session.union_fee)
386                if student.entry_session == 2016 \
387                    and student.entry_mode == 'ug_ft' \
388                    and student.state == CLEARED:
389                    amount += gateway_net_amt(academic_session.id_card_fee)
390            # Add non-indigenous fee and session specific penalty fees
391            if student.is_postgrad:
392                amount += academic_session.penalty_pg
393                if student.lga and not student.lga.startswith('edo'):
394                    amount += 20000.0
395            else:
396                amount += academic_session.penalty_ug
397        elif not student.is_postgrad:
398            fee_name = category + '_fee'
399            amount = getattr(academic_session, fee_name, 0.0)
400        if amount in (0.0, None):
401            return _(u'Amount could not be determined.'), None
402        # Create ticket.
403        for key in student['payments'].keys():
404            ticket = student['payments'][key]
405            if ticket.p_state == 'paid' and\
406               ticket.p_category == category and \
407               ticket.p_item == p_item and \
408               ticket.p_session == p_session:
409                  return _('This type of payment has already been made.'), None
410            # Additional condition in AAUE
411            if category in ('schoolfee', 'schoolfee_incl', 'schoolfee_1'):
412                if ticket.p_state == 'paid' and \
413                   ticket.p_category in ('schoolfee',
414                                         'schoolfee_incl',
415                                         'schoolfee_1') and \
416                   ticket.p_item == p_item and \
417                   ticket.p_session == p_session:
418                      return _(
419                          'Another school fee payment for this '
420                          'session has already been made.'), None
421
422        if self._isPaymentDisabled(p_session, category, student):
423            return _('This category of payments has been disabled.'), None
424        payment = createObject(u'waeup.StudentOnlinePayment')
425        timestamp = ("%d" % int(time()*10000))[1:]
426        payment.p_id = "p%s" % timestamp
427        payment.p_category = category
428        payment.p_item = p_item
429        payment.p_session = p_session
430        payment.p_level = p_level
431        payment.p_current = p_current
432        payment.amount_auth = amount
433        return None, payment
434
435    def _admissionText(self, student, portal_language):
436        inst_name = grok.getSite()['configuration'].name
437        entry_session = student['studycourse'].entry_session
438        entry_session = academic_sessions_vocab.getTerm(entry_session).title
439        text = trans(_(
440            'This is to inform you that you have been offered provisional'
441            ' admission into ${a} for the ${b} academic session as follows:',
442            mapping = {'a': inst_name, 'b': entry_session}),
443            portal_language)
444        return text
445
446    def warnCreditsOOR(self, studylevel, course=None):
447        if course and studylevel.total_credits + course.credits > 48:
448            return  _('Maximum credits exceeded.')
449        elif studylevel.total_credits > 48:
450            return _('Maximum credits exceeded.')
451        return
452
453    def getBedCoordinates(self, bedticket):
454        """Return descriptive bed coordinates.
455        This method can be used to customize the `display_coordinates`
456        property method in order to  display a
457        customary description of the bed space.
458        """
459        bc = bedticket.bed_coordinates.split(',')
460        if len(bc) == 4:
461            return bc[0]
462        return bedticket.bed_coordinates
463
464    def getAccommodationDetails(self, student):
465        """Determine the accommodation data of a student.
466        """
467        d = {}
468        d['error'] = u''
469        hostels = grok.getSite()['hostels']
470        d['booking_session'] = hostels.accommodation_session
471        d['allowed_states'] = hostels.accommodation_states
472        d['startdate'] = hostels.startdate
473        d['enddate'] = hostels.enddate
474        d['expired'] = hostels.expired
475        # Determine bed type
476        bt = 'all'
477        if student.sex == 'f':
478            sex = 'female'
479        else:
480            sex = 'male'
481        special_handling = 'regular'
482        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
483        return d
484
485    def checkAccommodationRequirements(self, student, acc_details):
486        msg = super(CustomStudentsUtils, self).checkAccommodationRequirements(
487            student, acc_details)
488        if msg:
489            return msg
490        if student.current_mode not in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
491            return _('You are not eligible to book accommodation.')
492        return
493
494    # AAUE prefix
495    STUDENT_ID_PREFIX = u'E'
496
497    STUDENT_EXPORTER_NAMES = ('students', 'studentstudycourses',
498            'studentstudylevels', 'coursetickets',
499            'studentpayments', 'studentunpaidpayments',
500            'bedtickets', 'paymentsoverview',
501            'studylevelsoverview', 'combocard', 'bursary',
502            'levelreportdata')
Note: See TracBrowser for help on using the repository browser.