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

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

'NIL' is not an outstanding course.

  • Property svn:keywords set to Id
File size: 19.8 KB
Line 
1## $Id: utils.py 14441 2017-01-23 10:43:58Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import grok
19from time import time
20from zope.component import createObject
21from waeup.kofa.interfaces import (
22    ADMITTED, CLEARANCE, REQUESTED, CLEARED, RETURNING, PAID,
23    academic_sessions_vocab)
24from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
25from waeup.kofa.accesscodes import create_accesscode
26from waeup.kofa.students.utils import trans
27from waeup.aaue.interswitch.browser import gateway_net_amt, GATEWAY_AMT
28from waeup.aaue.interfaces import MessageFactory as _
29
30class CustomStudentsUtils(NigeriaStudentsUtils):
31    """A collection of customized methods.
32
33    """
34
35    PORTRAIT_CHANGE_STATES = (ADMITTED,)
36
37    gpa_boundaries = ((1, 'FRNS / NEOR / NEOV'),
38                      (1.5, 'Pass'),
39                      (2.4, '3rd Class Honours'),
40                      (3.5, '2nd Class Honours Lower Division'),
41                      (4.5, '2nd Class Honours Upper Division'),
42                      (5, '1st Class Honours'))
43
44    def getDegreeClassNumber(self, level_obj):
45        """Get degree class number (used for SessionResultsPresentation
46        reports).
47        """
48        if level_obj.gpa_params[1] == 0:
49            # No credits weighted
50            return 0
51        if level_obj.level_verdict in ('FRNS', 'NEOR', 'NEOV'):
52            return 0
53        certificate = getattr(level_obj.__parent__,'certificate',None)
54        end_level = getattr(certificate, 'end_level', None)
55        if end_level and level_obj.student.current_level >= end_level:
56            failed_courses = level_obj.passed_params[4]
57            not_taken_courses = level_obj.passed_params[5]
58            if '_m' in failed_courses:
59                return 0
60            if len(not_taken_courses) \
61                and not not_taken_courses == 'NIL':
62                return 0
63        # use gpa_boundaries above
64        return self.getClassFromCGPA(level_obj.cumulative_params[0])[0]
65
66    def increaseMatricInteger(self, student):
67        """Increase counter for matric numbers.
68        This counter can be a centrally stored attribute or an attribute of
69        faculties, departments or certificates. In the base package the counter
70        is as an attribute of the site configuration container.
71        """
72        if student.current_mode in ('ug_pt', 'de_pt'):
73            grok.getSite()['configuration'].next_matric_integer += 1
74            return
75        elif student.is_postgrad:
76            grok.getSite()['configuration'].next_matric_integer_3 += 1
77            return
78        elif student.current_mode in ('dp_ft',):
79            grok.getSite()['configuration'].next_matric_integer_4 += 1
80            return
81        grok.getSite()['configuration'].next_matric_integer_2 += 1
82        return
83
84    def _concessionalPaymentMade(self, student):
85        if len(student['payments']):
86            for ticket in student['payments'].values():
87                if ticket.p_state == 'paid' and \
88                    ticket.p_category == 'concessional':
89                    return True
90        return False
91
92    def constructMatricNumber(self, student):
93        faccode = student.faccode
94        depcode = student.depcode
95        certcode = student.certcode
96        degree = getattr(
97            getattr(student.get('studycourse', None), 'certificate', None),
98                'degree', None)
99        year = unicode(student.entry_session)[2:]
100        if not student.state in (PAID, ) or not student.is_fresh or \
101            student.current_mode == 'found':
102            return _('Matriculation number cannot be set.'), None
103        #if student.current_mode not in ('mug_ft', 'mde_ft') and \
104        #    not self._concessionalPaymentMade(student):
105        #    return _('Matriculation number cannot be set.'), None
106        if student.is_postgrad:
107            next_integer = grok.getSite()['configuration'].next_matric_integer_3
108            if not degree or next_integer == 0:
109                return _('Matriculation number cannot be set.'), None
110            if student.faccode in ('IOE'):
111                return None, "AAU/SPS/%s/%s/%s/%05d" % (
112                    faccode, year, degree, next_integer)
113            return None, "AAU/SPS/%s/%s/%s/%s/%05d" % (
114                faccode, depcode, year, degree, next_integer)
115        if student.current_mode in ('ug_pt', 'de_pt'):
116            next_integer = grok.getSite()['configuration'].next_matric_integer
117            if next_integer == 0:
118                return _('Matriculation number cannot be set.'), None
119            return None, "PTP/%s/%s/%s/%05d" % (
120                faccode, depcode, year, next_integer)
121        if student.current_mode in ('dp_ft',):
122            next_integer = grok.getSite()['configuration'].next_matric_integer_4
123            if next_integer == 0:
124                return _('Matriculation number cannot be set.'), None
125            return None, "IOE/DIP/%s/%05d" % (year, next_integer)
126        next_integer = grok.getSite()['configuration'].next_matric_integer_2
127        if next_integer == 0:
128            return _('Matriculation number cannot be set.'), None
129        if student.faccode in ('FBM', 'FCS'):
130            return None, "CMS/%s/%s/%s/%05d" % (
131                faccode, depcode, year, next_integer)
132        return None, "%s/%s/%s/%05d" % (faccode, depcode, year, next_integer)
133
134    def getReturningData(self, student):
135        """ This method defines what happens after school fee payment
136        of returning students depending on the student's senate verdict.
137        """
138        prev_level = student['studycourse'].current_level
139        cur_verdict = student['studycourse'].current_verdict
140        if cur_verdict in ('A','B','C', 'L','M','N','Z',):
141            # Successful student
142            new_level = divmod(int(prev_level),100)[0]*100 + 100
143        #elif cur_verdict == 'C':
144        #    # Student on probation
145        #    new_level = int(prev_level) + 10
146        else:
147            # Student is somehow in an undefined state.
148            # Level has to be set manually.
149            new_level = prev_level
150        new_session = student['studycourse'].current_session + 1
151        return new_session, new_level
152
153    def _isPaymentDisabled(self, p_session, category, student):
154        academic_session = self._getSessionConfiguration(p_session)
155        if category.startswith('schoolfee'):
156            if 'sf_all' in academic_session.payment_disabled:
157                return True
158            if 'sf_pg' in academic_session.payment_disabled and \
159                student.is_postgrad:
160                return True
161            if 'sf_pt' in academic_session.payment_disabled and \
162                student.current_mode.endswith('_pt'):
163                return True
164            if 'sf_found' in academic_session.payment_disabled and \
165                student.current_mode == 'found':
166                return True
167        if category.startswith('clearance') and \
168            'cl_regular' in academic_session.payment_disabled and \
169            student.current_mode in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
170            return True
171        if category == 'hostel_maintenance' and \
172            'maint_all' in academic_session.payment_disabled:
173            return True
174        return False
175
176    def setPaymentDetails(self, category, student,
177            previous_session=None, previous_level=None):
178        """Create Payment object and set the payment data of a student for
179        the payment category specified.
180
181        """
182        details = {}
183        p_item = u''
184        amount = 0.0
185        error = u''
186        if previous_session:
187            return _('Previous session payment not yet implemented.'), None
188        p_session = student['studycourse'].current_session
189        p_level = student['studycourse'].current_level
190        p_current = True
191        academic_session = self._getSessionConfiguration(p_session)
192        if academic_session == None:
193            return _(u'Session configuration object is not available.'), None
194        # Determine fee.
195        if category == 'transfer':
196            amount = academic_session.transfer_fee
197        elif category == 'transcript_local':
198            amount = academic_session.transcript_fee_local
199        elif category == 'transcript_inter':
200            amount = academic_session.transcript_fee_inter
201        elif category == 'bed_allocation':
202            amount = academic_session.booking_fee
203        elif category == 'restitution':
204            if student.entry_session == 2016 \
205                and student.current_session == 2016 \
206                or student.current_mode != 'ug_ft':
207                return _(u'Restitution fee payment not required.'), None
208            amount = academic_session.restitution_fee
209        elif category == 'hostel_maintenance':
210            amount = 0.0
211            bedticket = student['accommodation'].get(
212                str(student.current_session), None)
213            if bedticket is not None and bedticket.bed is not None:
214                p_item = bedticket.display_coordinates
215                if bedticket.bed.__parent__.maint_fee > 0:
216                    amount = bedticket.bed.__parent__.maint_fee
217                else:
218                    # fallback
219                    amount = academic_session.maint_fee
220            else:
221                return _(u'No bed allocated.'), None
222        elif student.current_mode == 'found' and category not in (
223            'schoolfee', 'clearance', 'late_registration'):
224            return _('Not allowed.'), None
225        elif category.startswith('clearance'):
226            if student.state not in (ADMITTED, CLEARANCE, REQUESTED, CLEARED):
227                return _(u'Acceptance Fee payments not allowed.'), None
228            if student.current_mode in (
229                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
230                'transfer', 'mug_ft', 'mde_ft') \
231                and category != 'clearance_incl':
232                    return _("Additional fees must be included."), None
233            if student.faccode == 'FP':  # includes IJMBE
234                amount = academic_session.clearance_fee_fp
235            elif student.current_mode.endswith('_pt'):
236                if student.is_postgrad:
237                    amount = academic_session.clearance_fee_pg_pt
238                else:
239                    amount = academic_session.clearance_fee_ug_pt
240            elif student.faccode == 'FCS':
241                # Students in clinical medical sciences pay the medical
242                # acceptance fee
243                amount = academic_session.clearance_fee_med
244            elif student.is_postgrad:  # and not part-time
245                if category != 'clearance':
246                    return _("No additional fees required."), None
247                amount = academic_session.clearance_fee_pg
248            else:
249                amount = academic_session.clearance_fee
250            p_item = student['studycourse'].certificate.code
251            if amount in (0.0, None):
252                return _(u'Amount could not be determined.'), None
253            # Add Matric Gown Fee and Lapel Fee
254            if category == 'clearance_incl':
255                amount += gateway_net_amt(academic_session.matric_gown_fee) + \
256                    gateway_net_amt(academic_session.lapel_fee)
257        elif category == 'late_registration':
258            if student.is_postgrad:
259                amount = academic_session.late_pg_registration_fee
260            else:
261                amount = academic_session.late_registration_fee
262        elif category.startswith('schoolfee'):
263            try:
264                certificate = student['studycourse'].certificate
265                p_item = certificate.code
266            except (AttributeError, TypeError):
267                return _('Study course data are incomplete.'), None
268            if student.is_postgrad and category != 'schoolfee':
269                return _("No additional fees required."), None
270            if student.current_mode in (
271                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
272                'transfer', 'mug_ft', 'mde_ft') \
273                and not category in (
274                'schoolfee_incl', 'schoolfee_1', 'schoolfee_2'):
275                    return _("You must choose a payment which includes "
276                             "additional fees."), None
277            if category in ('schoolfee_1', 'schoolfee_2'):
278                if student.current_mode == 'ug_pt':
279                    return _("Part-time students are not allowed "
280                             "to pay by instalments."), None
281                if student.entry_session < 2015:
282                    return _("You are not allowed "
283                             "to pay by instalments."), None
284            if student.state == CLEARED and category != 'schoolfee_2':
285                amount = getattr(certificate, 'school_fee_1', 0.0)
286                # Cut school fee by 50%
287                if category == 'schoolfee_1' and amount:
288                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
289            elif student.is_fresh and category == 'schoolfee_2':
290                amount = getattr(certificate, 'school_fee_1', 0.0)
291                # Cut school fee by 50%
292                if amount:
293                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
294            elif student.state == RETURNING and category != 'schoolfee_2':
295                if not student.father_name:
296                    return _("Personal data form is not properly filled."), None
297                # In case of returning school fee payment the payment session
298                # and level contain the values of the session the student
299                # has paid for.
300                p_session, p_level = self.getReturningData(student)
301                try:
302                    academic_session = grok.getSite()[
303                        'configuration'][str(p_session)]
304                except KeyError:
305                    return _(u'Session configuration object is not available.'), None
306                if student.entry_session >= 2015:
307                    amount = getattr(certificate, 'school_fee_2', 0.0)
308                    # Cut school fee by 50%
309                    if category == 'schoolfee_1' and amount:
310                        amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
311                else:
312                    amount = getattr(certificate, 'school_fee_3', 0.0)
313            elif category == 'schoolfee_2':
314                amount = getattr(certificate, 'school_fee_2', 0.0)
315                # Cut school fee by 50%
316                if amount:
317                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
318            else:
319                return _('Wrong state.'), None
320            if amount in (0.0, None):
321                return _(u'Amount could not be determined.'), None
322            # Add Student Union Fee , Student Id Card Fee and Welfare Assurance
323            if category in ('schoolfee_incl', 'schoolfee_1'):
324                amount += gateway_net_amt(academic_session.welfare_fee) + \
325                    gateway_net_amt(academic_session.union_fee)
326                if student.entry_session == 2016 \
327                    and student.entry_mode == 'ug_ft' \
328                    and student.state == CLEARED:
329                    amount += gateway_net_amt(academic_session.id_card_fee)
330            # Add non-indigenous fee and session specific penalty fees
331            if student.is_postgrad:
332                amount += academic_session.penalty_pg
333                if student.lga and not student.lga.startswith('edo'):
334                    amount += 20000.0
335            else:
336                amount += academic_session.penalty_ug
337        elif not student.is_postgrad:
338            fee_name = category + '_fee'
339            amount = getattr(academic_session, fee_name, 0.0)
340        if amount in (0.0, None):
341            return _(u'Amount could not be determined.'), None
342        # Create ticket.
343        for key in student['payments'].keys():
344            ticket = student['payments'][key]
345            if ticket.p_state == 'paid' and\
346               ticket.p_category == category and \
347               ticket.p_item == p_item and \
348               ticket.p_session == p_session:
349                  return _('This type of payment has already been made.'), None
350            # Additional condition in AAUE
351            if category in ('schoolfee', 'schoolfee_incl', 'schoolfee_1'):
352                if ticket.p_state == 'paid' and \
353                   ticket.p_category in ('schoolfee',
354                                         'schoolfee_incl',
355                                         'schoolfee_1') and \
356                   ticket.p_item == p_item and \
357                   ticket.p_session == p_session:
358                      return _(
359                          'Another school fee payment for this '
360                          'session has already been made.'), None
361
362        if self._isPaymentDisabled(p_session, category, student):
363            return _('This category of payments has been disabled.'), None
364        payment = createObject(u'waeup.StudentOnlinePayment')
365        timestamp = ("%d" % int(time()*10000))[1:]
366        payment.p_id = "p%s" % timestamp
367        payment.p_category = category
368        payment.p_item = p_item
369        payment.p_session = p_session
370        payment.p_level = p_level
371        payment.p_current = p_current
372        payment.amount_auth = amount
373        return None, payment
374
375    def _admissionText(self, student, portal_language):
376        inst_name = grok.getSite()['configuration'].name
377        entry_session = student['studycourse'].entry_session
378        entry_session = academic_sessions_vocab.getTerm(entry_session).title
379        text = trans(_(
380            'This is to inform you that you have been offered provisional'
381            ' admission into ${a} for the ${b} academic session as follows:',
382            mapping = {'a': inst_name, 'b': entry_session}),
383            portal_language)
384        return text
385
386    def maxCredits(self, studylevel):
387        """Return maximum credits.
388
389        """
390        return 48
391
392    def getBedCoordinates(self, bedticket):
393        """Return descriptive bed coordinates.
394        This method can be used to customize the `display_coordinates`
395        property method in order to  display a
396        customary description of the bed space.
397        """
398        bc = bedticket.bed_coordinates.split(',')
399        if len(bc) == 4:
400            return bc[0]
401        return bedticket.bed_coordinates
402
403    def getAccommodationDetails(self, student):
404        """Determine the accommodation data of a student.
405        """
406        d = {}
407        d['error'] = u''
408        hostels = grok.getSite()['hostels']
409        d['booking_session'] = hostels.accommodation_session
410        d['allowed_states'] = hostels.accommodation_states
411        d['startdate'] = hostels.startdate
412        d['enddate'] = hostels.enddate
413        d['expired'] = hostels.expired
414        # Determine bed type
415        bt = 'all'
416        if student.sex == 'f':
417            sex = 'female'
418        else:
419            sex = 'male'
420        special_handling = 'regular'
421        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
422        return d
423
424    def checkAccommodationRequirements(self, student, acc_details):
425        msg = super(CustomStudentsUtils, self).checkAccommodationRequirements(
426            student, acc_details)
427        if msg:
428            return msg
429        if student.current_mode not in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
430            return _('You are not eligible to book accommodation.')
431        return
432
433    # AAUE prefix
434    STUDENT_ID_PREFIX = u'E'
Note: See TracBrowser for help on using the repository browser.