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

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

Add conditions for restitution fee payments.

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