source: main/waeup.uniben/trunk/src/waeup/uniben/students/utils.py @ 16078

Last change on this file since 16078 was 16025, checked in by Henrik Bettermann, 5 years ago

Allow disabling clearance fee payments for JUPEB students.

  • Property svn:keywords set to Id
File size: 22.0 KB
Line 
1## $Id: utils.py 16025 2020-03-05 15:22:30Z 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, getUtility
21from waeup.kofa.interfaces import (IKofaUtils,
22    CLEARED, RETURNING, PAID, REGISTERED, VALIDATED)
23from waeup.kofa.utils.helpers import to_timezone
24from waeup.kofa.students.utils import trans
25from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
26from waeup.uniben.interfaces import MessageFactory as _
27
28class CustomStudentsUtils(NigeriaStudentsUtils):
29    """A collection of customized methods.
30
31    """
32
33    def getReturningData(self, student):
34        """ This method defines what happens after school fee payment
35        of returning students depending on the student's senate verdict.
36        """
37        prev_level = student['studycourse'].current_level
38        cur_verdict = student['studycourse'].current_verdict
39        if cur_verdict == 'N' and prev_level == 100:
40            new_level = prev_level
41        elif cur_verdict in ('A','B','L','M','N','Z',):
42            # Successful student
43            new_level = divmod(int(prev_level),100)[0]*100 + 100
44        elif cur_verdict == 'C':
45            # Student on probation
46            new_level = int(prev_level) + 10
47        else:
48            # Student is somehow in an undefined state.
49            # Level has to be set manually.
50            new_level = prev_level
51        new_session = student['studycourse'].current_session + 1
52        return new_session, new_level
53
54
55    def checkAccommodationRequirements(self, student, acc_details):
56        if acc_details.get('expired', False):
57            startdate = acc_details.get('startdate')
58            enddate = acc_details.get('enddate')
59            if startdate and enddate:
60                tz = getUtility(IKofaUtils).tzinfo
61                startdate = to_timezone(
62                    startdate, tz).strftime("%d/%m/%Y %H:%M:%S")
63                enddate = to_timezone(
64                    enddate, tz).strftime("%d/%m/%Y %H:%M:%S")
65                return _("Outside booking period: ${a} - ${b}",
66                         mapping = {'a': startdate, 'b': enddate})
67            else:
68                return _("Outside booking period.")
69        if not student.is_postgrad and student.current_mode != 'ug_ft':
70            return _("Only undergraduate full-time students are eligible to book accommodation.")
71        bt = acc_details.get('bt')
72        if not bt:
73            return _("Your data are incomplete.")
74        if not student.state in acc_details['allowed_states']:
75            return _("You are in the wrong registration state.")
76        if student['studycourse'].current_session != acc_details[
77            'booking_session']:
78            return _('Your current session does not '
79                     'match accommodation session.')
80        stage = bt.split('_')[2]
81        if not student.is_postgrad and stage != 'fr' and not student[
82            'studycourse'].previous_verdict in (
83                'A', 'B', 'F', 'J', 'L', 'M', 'C', 'Z'):
84            return _("Your are not eligible to book accommodation.")
85        bsession = str(acc_details['booking_session'])
86        if bsession in student['accommodation'].keys() \
87            and not 'booking expired' in \
88            student['accommodation'][bsession].bed_coordinates:
89            return _('You already booked a bed space in '
90                     'current accommodation session.')
91        return
92
93    def getAccommodationDetails(self, student):
94        """Determine the accommodation data of a student.
95        """
96        d = {}
97        d['error'] = u''
98        hostels = grok.getSite()['hostels']
99        d['booking_session'] = hostels.accommodation_session
100        d['allowed_states'] = hostels.accommodation_states
101        d['startdate'] = hostels.startdate
102        d['enddate'] = hostels.enddate
103        d['expired'] = hostels.expired
104        # Determine bed type
105        studycourse = student['studycourse']
106        certificate = getattr(studycourse,'certificate',None)
107        entry_session = studycourse.entry_session
108        current_level = studycourse.current_level
109        if None in (entry_session, current_level, certificate):
110            return d
111        if student.sex == 'f':
112            sex = 'female'
113        else:
114            sex = 'male'
115        if student.is_postgrad:
116            bt = 'all'
117            special_handling = 'pg'
118        else:
119            end_level = certificate.end_level
120            if current_level == 10:
121                bt = 'pr'
122            elif entry_session == grok.getSite()['hostels'].accommodation_session:
123                bt = 'fr'
124            elif current_level >= end_level:
125                bt = 'fi'
126            else:
127                bt = 're'
128            special_handling = 'regular'
129            desired_hostel = student['accommodation'].desired_hostel
130            if student.faccode in ('MED', 'DEN') and (
131                not desired_hostel or desired_hostel.startswith('clinical')):
132                special_handling = 'clinical'
133            elif student.certcode in ('BARTMAS', 'BARTTHR', 'BARTFAA',
134                                      'BAEDFAA', 'BSCEDECHED', 'BAFAA'):
135                special_handling = 'ekenwan'
136        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
137        return d
138
139    def _paymentMade(self, student, session):
140        if len(student['payments']):
141            for ticket in student['payments'].values():
142                if ticket.p_state == 'paid' and \
143                    ticket.p_category == 'schoolfee' and \
144                    ticket.p_session == session:
145                    return True
146        return False
147
148    def _isPaymentDisabled(self, p_session, category, student):
149        academic_session = self._getSessionConfiguration(p_session)
150        if category == 'schoolfee':
151            if 'sf_all' in academic_session.payment_disabled:
152                return True
153            if student.current_mode == 'found' and \
154                'sf_found' in academic_session.payment_disabled:
155                return True
156            if student.is_postgrad:
157                if 'sf_pg' in academic_session.payment_disabled:
158                    return True
159                return False
160            if student.current_mode.endswith('ft') and \
161                'sf_ft' in academic_session.payment_disabled:
162                return True
163            if student.current_mode.endswith('pt') and \
164                'sf_pt' in academic_session.payment_disabled:
165                return True
166            if student.current_mode.startswith('dp') and \
167                'sf_dp' in academic_session.payment_disabled:
168                return True
169            if student.current_mode.endswith('sw') and \
170                'sf_sw' in academic_session.payment_disabled:
171                return True
172        if category == 'hostel_maintenance' and \
173            'maint_all' in academic_session.payment_disabled:
174            return True
175        if category == 'clearance':
176            if 'cl_all' in academic_session.payment_disabled:
177                return True
178            if student.is_jupeb and \
179                'cl_jupeb' in academic_session.payment_disabled:
180                return True
181        return False
182
183    #def _hostelApplicationPaymentMade(self, student, session):
184    #    if len(student['payments']):
185    #        for ticket in student['payments'].values():
186    #            if ticket.p_state == 'paid' and \
187    #                ticket.p_category == 'hostel_application' and \
188    #                ticket.p_session == session:
189    #                return True
190    #    return False
191
192    def _pharmdInstallments(self, student):
193        installments = 0.0
194        if len(student['payments']):
195            for ticket in student['payments'].values():
196                if ticket.p_state == 'paid' and \
197                    ticket.p_category.startswith('pharmd') and \
198                    ticket.p_session == student.current_session:
199                    installments += ticket.amount_auth
200        return installments
201
202    def samePaymentMade(self, student, category, p_item, p_session):
203        if category in ('bed_allocation', 'transcript'):
204            return False
205        for key in student['payments'].keys():
206            ticket = student['payments'][key]
207            if ticket.p_state == 'paid' and\
208               ticket.p_category == category and \
209               ticket.p_item == p_item and \
210               ticket.p_session == p_session:
211                  return True
212        return False
213
214    def setPaymentDetails(self, category, student,
215            previous_session, previous_level, combi):
216        """Create Payment object and set the payment data of a student for
217        the payment category specified.
218
219        """
220        p_item = u''
221        amount = 0.0
222        if previous_session:
223            if previous_session < student['studycourse'].entry_session:
224                return _('The previous session must not fall below '
225                         'your entry session.'), None
226            if category == 'schoolfee':
227                # School fee is always paid for the following session
228                if previous_session > student['studycourse'].current_session:
229                    return _('This is not a previous session.'), None
230            else:
231                if previous_session > student['studycourse'].current_session - 1:
232                    return _('This is not a previous session.'), None
233            p_session = previous_session
234            p_level = previous_level
235            p_current = False
236        else:
237            p_session = student['studycourse'].current_session
238            p_level = student['studycourse'].current_level
239            p_current = True
240        academic_session = self._getSessionConfiguration(p_session)
241        if academic_session == None:
242            return _(u'Session configuration object is not available.'), None
243        # Determine fee.
244        if category == 'transfer':
245            amount = academic_session.transfer_fee
246        elif category == 'transcript':
247            amount = academic_session.transcript_fee
248        elif category == 'gown':
249            amount = academic_session.gown_fee
250        elif category == 'jupeb':
251            amount = academic_session.jupeb_fee
252        elif category == 'clinexam':
253            amount = academic_session.clinexam_fee
254        elif category.startswith('pharmd') \
255            and student.current_mode == 'special_ft':
256            amount = 80000.0
257        #elif category == 'develop' and student.is_postgrad:
258        #    amount = academic_session.development_fee
259        elif category == 'bed_allocation':
260            p_item = self.getAccommodationDetails(student)['bt']
261            desired_hostel = student['accommodation'].desired_hostel
262            if not desired_hostel:
263                return _(u'Select your favoured hostel first.'), None
264            if desired_hostel and desired_hostel != 'no':
265                p_item = u'%s (%s)' % (p_item, desired_hostel)
266            amount = academic_session.booking_fee
267            if student.is_postgrad:
268                amount += 500
269        elif category == 'hostel_maintenance':
270            amount = 0.0
271            bedticket = student['accommodation'].get(
272                str(student.current_session), None)
273            if bedticket is not None and bedticket.bed is not None:
274                p_item = bedticket.bed_coordinates
275                if bedticket.bed.__parent__.maint_fee > 0:
276                    amount = bedticket.bed.__parent__.maint_fee
277                else:
278                    # fallback
279                    amount = academic_session.maint_fee
280            else:
281                return _(u'No bed allocated.'), None
282        #elif category == 'hostel_application':
283        #    amount = 1000.0
284        #elif category.startswith('tempmaint'):
285        #    if not self._hostelApplicationPaymentMade(
286        #        student, student.current_session):
287        #        return _(
288        #            'You have not yet paid the hostel application fee.'), None
289        #    if category == 'tempmaint_1':
290        #        amount = 8150.0
291        #    elif category == 'tempmaint_2':
292        #        amount = 12650.0
293        #    elif category == 'tempmaint_3':
294        #        amount = 9650.0
295        elif category == 'clearance':
296            p_item = student.certcode
297            if p_item is None:
298                return _('Study course data are incomplete.'), None
299            if student.is_jupeb:
300                amount = 50000.0
301            elif student.faccode.startswith('FCETA'):
302                # ASABA and AKOKA
303                amount = 35000.0
304            elif student.faccode in ('BMS', 'MED', 'DEN'):
305            #elif p_item in ('BSCANA', 'BSCMBC', 'BMLS', 'BSCNUR', 'BSCPHS', 'BDS',
306            #    'MBBSMED', 'MBBSNDU', 'BSCPTY', 'BSCPST'):
307                amount = 80000.0
308            elif student.faccode == 'DCOEM':
309                return _('Acceptance fee payment not necessary.'), None
310            else:
311                amount = 60000.0
312        elif category == '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 previous_session:
319                # Students can pay for previous sessions in all workflow states.
320                # Fresh students are excluded by the update method of the
321                # PreviousPaymentAddFormPage.
322                if previous_session == student['studycourse'].entry_session:
323                    if student.is_foreigner:
324                        amount = getattr(certificate, 'school_fee_3', 0.0)
325                    else:
326                        amount = getattr(certificate, 'school_fee_1', 0.0)
327                else:
328                    if student.is_foreigner:
329                        amount = getattr(certificate, 'school_fee_4', 0.0)
330                    else:
331                        amount = getattr(certificate, 'school_fee_2', 0.0)
332                        # Old returning students might get a discount.
333                        if student.entry_session < 2017 \
334                            and certificate.custom_float_1:
335                            amount -= certificate.custom_float_1
336            else:
337                if student.state == CLEARED:
338                    if student.is_foreigner:
339                        amount = getattr(certificate, 'school_fee_3', 0.0)
340                    else:
341                        amount = getattr(certificate, 'school_fee_1', 0.0)
342                elif student.state == PAID and student.is_postgrad:
343                    p_session += 1
344                    academic_session = self._getSessionConfiguration(p_session)
345                    if academic_session == None:
346                        return _(u'Session configuration object is not available.'), None
347
348                    # Students are only allowed to pay for the next session
349                    # if current session payment
350                    # has really been made, i.e. payment object exists.
351                    #if not self._paymentMade(
352                    #    student, student.current_session):
353                    #    return _('You have not yet paid your current/active' +
354                    #             ' session. Please use the previous session' +
355                    #             ' payment form first.'), None
356
357                    if student.is_foreigner:
358                        amount = getattr(certificate, 'school_fee_4', 0.0)
359                    else:
360                        amount = getattr(certificate, 'school_fee_2', 0.0)
361                elif student.state == RETURNING:
362                    # In case of returning school fee payment the payment session
363                    # and level contain the values of the session the student
364                    # has paid for.
365                    p_session, p_level = self.getReturningData(student)
366                    academic_session = self._getSessionConfiguration(p_session)
367                    if academic_session == None:
368                        return _(u'Session configuration object is not available.'), None
369
370                    # Students are only allowed to pay for the next session
371                    # if current session payment has really been made,
372                    # i.e. payment object exists and is paid.
373                    #if not self._paymentMade(
374                    #    student, student.current_session):
375                    #    return _('You have not yet paid your current/active' +
376                    #             ' session. Please use the previous session' +
377                    #             ' payment form first.'), None
378
379                    if student.is_foreigner:
380                        amount = getattr(certificate, 'school_fee_4', 0.0)
381                    else:
382                        amount = getattr(certificate, 'school_fee_2', 0.0)
383                        # Old returning students might get a discount.
384                        if student.entry_session < 2017 \
385                            and certificate.custom_float_1:
386                            amount -= certificate.custom_float_1
387                # PHARMD school fee amount is fixed and previously paid
388                # installments in current session are deducted.
389                if student.current_mode == 'special_ft' \
390                    and student.state in (RETURNING, CLEARED):
391                    if student.is_foreigner:
392                        amount = 260000.0 - self._pharmdInstallments(student)
393                    else:
394                        amount = 160000.0 - self._pharmdInstallments(student)
395            # Give 50% school fee discount to staff members.
396            if student.is_staff:
397                amount /= 2
398        if amount in (0.0, None):
399            return _('Amount could not be determined.'), None
400        # Add session specific penalty fee.
401        if category == 'schoolfee' and student.is_postgrad:
402            amount += academic_session.penalty_pg
403            amount += academic_session.development_fee
404        elif category == 'schoolfee' and student.current_mode == ('ug_ft'):
405            amount += academic_session.penalty_ug_ft
406        elif category == 'schoolfee' and student.current_mode == ('ug_pt'):
407            amount += academic_session.penalty_ug_pt
408        elif category == 'schoolfee' and student.current_mode == ('ug_sw'):
409            amount += academic_session.penalty_sw
410        elif category == 'schoolfee' and student.current_mode in (
411            'dp_ft', 'dp_pt'):
412            amount += academic_session.penalty_dp
413        if category.startswith('tempmaint'):
414            p_item = getUtility(IKofaUtils).PAYMENT_CATEGORIES[category]
415            p_item = unicode(p_item)
416            # Now we change the category because tempmaint payments
417            # will be obsolete when Uniben returns to Kofa bed allocation.
418            category = 'hostel_maintenance'
419        # Create ticket.
420        if self.samePaymentMade(student, category, p_item, p_session):
421            return _('This type of payment has already been made.'), None
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 warnCreditsOOR(self, studylevel, course=None):
436        studycourse = studylevel.__parent__
437        certificate = getattr(studycourse,'certificate', None)
438        current_level = studycourse.current_level
439        if None in (current_level, certificate):
440            return
441        end_level = certificate.end_level
442        if studylevel.student.faccode in (
443            'MED', 'DEN', 'BMS') and studylevel.level == 200:
444            limit = 61
445        elif current_level >= end_level:
446            limit = 51
447        else:
448            limit = 50
449        if course and studylevel.total_credits + course.credits > limit:
450            return _('Maximum credits exceeded.')
451        elif studylevel.total_credits > limit:
452            return _('Maximum credits exceeded.')
453        return
454
455    def clearance_disabled_message(self, student):
456        if student.is_postgrad:
457            return None
458        try:
459            session_config = grok.getSite()[
460                'configuration'][str(student.current_session)]
461        except KeyError:
462            return _('Session configuration object is not available.')
463        if not session_config.clearance_enabled:
464            return _('Clearance is disabled for this session.')
465        return None
466
467    #: A tuple containing the names of registration states in which changing of
468    #: passport pictures is allowed.
469    PORTRAIT_CHANGE_STATES = ()
470
471    # Uniben prefix
472    STUDENT_ID_PREFIX = u'B'
473
474    STUDENT_EXPORTER_NAMES = (
475            'students',
476            'studentstudycourses',
477            'studentstudylevels',
478            'coursetickets',
479            'studentpayments',
480            'bedtickets',
481            'trimmed',
482            'outstandingcourses',
483            'unpaidpayments',
484            'sfpaymentsoverview',
485            'sessionpaymentsoverview',
486            'studylevelsoverview',
487            'combocard',
488            'bursary',
489            'accommodationpayments',
490            'transcriptdata',
491            'trimmedpayments',
492            )
Note: See TracBrowser for help on using the repository browser.