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

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

Add trimmedpayments.

  • Property svn:keywords set to Id
File size: 21.7 KB
Line 
1## $Id: utils.py 15980 2020-02-04 10:14: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, 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        return False
176
177    #def _hostelApplicationPaymentMade(self, student, session):
178    #    if len(student['payments']):
179    #        for ticket in student['payments'].values():
180    #            if ticket.p_state == 'paid' and \
181    #                ticket.p_category == 'hostel_application' and \
182    #                ticket.p_session == session:
183    #                return True
184    #    return False
185
186    def _pharmdInstallments(self, student):
187        installments = 0.0
188        if len(student['payments']):
189            for ticket in student['payments'].values():
190                if ticket.p_state == 'paid' and \
191                    ticket.p_category.startswith('pharmd') and \
192                    ticket.p_session == student.current_session:
193                    installments += ticket.amount_auth
194        return installments
195
196    def samePaymentMade(self, student, category, p_item, p_session):
197        if category in ('bed_allocation', 'transcript'):
198            return False
199        for key in student['payments'].keys():
200            ticket = student['payments'][key]
201            if ticket.p_state == 'paid' and\
202               ticket.p_category == category and \
203               ticket.p_item == p_item and \
204               ticket.p_session == p_session:
205                  return True
206        return False
207
208    def setPaymentDetails(self, category, student,
209            previous_session, previous_level, combi):
210        """Create Payment object and set the payment data of a student for
211        the payment category specified.
212
213        """
214        p_item = u''
215        amount = 0.0
216        if previous_session:
217            if previous_session < student['studycourse'].entry_session:
218                return _('The previous session must not fall below '
219                         'your entry session.'), None
220            if category == 'schoolfee':
221                # School fee is always paid for the following session
222                if previous_session > student['studycourse'].current_session:
223                    return _('This is not a previous session.'), None
224            else:
225                if previous_session > student['studycourse'].current_session - 1:
226                    return _('This is not a previous session.'), None
227            p_session = previous_session
228            p_level = previous_level
229            p_current = False
230        else:
231            p_session = student['studycourse'].current_session
232            p_level = student['studycourse'].current_level
233            p_current = True
234        academic_session = self._getSessionConfiguration(p_session)
235        if academic_session == None:
236            return _(u'Session configuration object is not available.'), None
237        # Determine fee.
238        if category == 'transfer':
239            amount = academic_session.transfer_fee
240        elif category == 'transcript':
241            amount = academic_session.transcript_fee
242        elif category == 'gown':
243            amount = academic_session.gown_fee
244        elif category == 'jupeb':
245            amount = academic_session.jupeb_fee
246        elif category == 'clinexam':
247            amount = academic_session.clinexam_fee
248        elif category.startswith('pharmd') \
249            and student.current_mode == 'special_ft':
250            amount = 80000.0
251        #elif category == 'develop' and student.is_postgrad:
252        #    amount = academic_session.development_fee
253        elif category == 'bed_allocation':
254            p_item = self.getAccommodationDetails(student)['bt']
255            desired_hostel = student['accommodation'].desired_hostel
256            if not desired_hostel:
257                return _(u'Select your favoured hostel first.'), None
258            if desired_hostel and desired_hostel != 'no':
259                p_item = u'%s (%s)' % (p_item, desired_hostel)
260            amount = academic_session.booking_fee
261            if student.is_postgrad:
262                amount += 500
263        elif category == 'hostel_maintenance':
264            amount = 0.0
265            bedticket = student['accommodation'].get(
266                str(student.current_session), None)
267            if bedticket is not None and bedticket.bed is not None:
268                p_item = bedticket.bed_coordinates
269                if bedticket.bed.__parent__.maint_fee > 0:
270                    amount = bedticket.bed.__parent__.maint_fee
271                else:
272                    # fallback
273                    amount = academic_session.maint_fee
274            else:
275                return _(u'No bed allocated.'), None
276        #elif category == 'hostel_application':
277        #    amount = 1000.0
278        #elif category.startswith('tempmaint'):
279        #    if not self._hostelApplicationPaymentMade(
280        #        student, student.current_session):
281        #        return _(
282        #            'You have not yet paid the hostel application fee.'), None
283        #    if category == 'tempmaint_1':
284        #        amount = 8150.0
285        #    elif category == 'tempmaint_2':
286        #        amount = 12650.0
287        #    elif category == 'tempmaint_3':
288        #        amount = 9650.0
289        elif category == 'clearance':
290            p_item = student.certcode
291            if p_item is None:
292                return _('Study course data are incomplete.'), None
293            if student.is_jupeb:
294                amount = 50000.0
295            elif student.faccode.startswith('FCETA'):
296                # ASABA and AKOKA
297                amount = 35000.0
298            elif student.faccode in ('BMS', 'MED', 'DEN'):
299            #elif p_item in ('BSCANA', 'BSCMBC', 'BMLS', 'BSCNUR', 'BSCPHS', 'BDS',
300            #    'MBBSMED', 'MBBSNDU', 'BSCPTY', 'BSCPST'):
301                amount = 80000.0
302            elif student.faccode == 'DCOEM':
303                return _('Acceptance fee payment not necessary.'), None
304            else:
305                amount = 60000.0
306        elif category == 'schoolfee':
307            try:
308                certificate = student['studycourse'].certificate
309                p_item = certificate.code
310            except (AttributeError, TypeError):
311                return _('Study course data are incomplete.'), None
312            if previous_session:
313                # Students can pay for previous sessions in all workflow states.
314                # Fresh students are excluded by the update method of the
315                # PreviousPaymentAddFormPage.
316                if previous_session == student['studycourse'].entry_session:
317                    if student.is_foreigner:
318                        amount = getattr(certificate, 'school_fee_3', 0.0)
319                    else:
320                        amount = getattr(certificate, 'school_fee_1', 0.0)
321                else:
322                    if student.is_foreigner:
323                        amount = getattr(certificate, 'school_fee_4', 0.0)
324                    else:
325                        amount = getattr(certificate, 'school_fee_2', 0.0)
326                        # Old returning students might get a discount.
327                        if student.entry_session < 2017 \
328                            and certificate.custom_float_1:
329                            amount -= certificate.custom_float_1
330            else:
331                if student.state == CLEARED:
332                    if student.is_foreigner:
333                        amount = getattr(certificate, 'school_fee_3', 0.0)
334                    else:
335                        amount = getattr(certificate, 'school_fee_1', 0.0)
336                elif student.state == PAID and student.is_postgrad:
337                    p_session += 1
338                    academic_session = self._getSessionConfiguration(p_session)
339                    if academic_session == None:
340                        return _(u'Session configuration object is not available.'), None
341
342                    # Students are only allowed to pay for the next session
343                    # if current session payment
344                    # has really been made, i.e. payment object exists.
345                    #if not self._paymentMade(
346                    #    student, student.current_session):
347                    #    return _('You have not yet paid your current/active' +
348                    #             ' session. Please use the previous session' +
349                    #             ' payment form first.'), None
350
351                    if student.is_foreigner:
352                        amount = getattr(certificate, 'school_fee_4', 0.0)
353                    else:
354                        amount = getattr(certificate, 'school_fee_2', 0.0)
355                elif student.state == RETURNING:
356                    # In case of returning school fee payment the payment session
357                    # and level contain the values of the session the student
358                    # has paid for.
359                    p_session, p_level = self.getReturningData(student)
360                    academic_session = self._getSessionConfiguration(p_session)
361                    if academic_session == None:
362                        return _(u'Session configuration object is not available.'), None
363
364                    # Students are only allowed to pay for the next session
365                    # if current session payment has really been made,
366                    # i.e. payment object exists and is paid.
367                    #if not self._paymentMade(
368                    #    student, student.current_session):
369                    #    return _('You have not yet paid your current/active' +
370                    #             ' session. Please use the previous session' +
371                    #             ' payment form first.'), None
372
373                    if student.is_foreigner:
374                        amount = getattr(certificate, 'school_fee_4', 0.0)
375                    else:
376                        amount = getattr(certificate, 'school_fee_2', 0.0)
377                        # Old returning students might get a discount.
378                        if student.entry_session < 2017 \
379                            and certificate.custom_float_1:
380                            amount -= certificate.custom_float_1
381                # PHARMD school fee amount is fixed and previously paid
382                # installments in current session are deducted.
383                if student.current_mode == 'special_ft' \
384                    and student.state in (RETURNING, CLEARED):
385                    if student.is_foreigner:
386                        amount = 260000.0 - self._pharmdInstallments(student)
387                    else:
388                        amount = 160000.0 - self._pharmdInstallments(student)
389            # Give 50% school fee discount to staff members.
390            if student.is_staff:
391                amount /= 2
392        if amount in (0.0, None):
393            return _('Amount could not be determined.'), None
394        # Add session specific penalty fee.
395        if category == 'schoolfee' and student.is_postgrad:
396            amount += academic_session.penalty_pg
397            amount += academic_session.development_fee
398        elif category == 'schoolfee' and student.current_mode == ('ug_ft'):
399            amount += academic_session.penalty_ug_ft
400        elif category == 'schoolfee' and student.current_mode == ('ug_pt'):
401            amount += academic_session.penalty_ug_pt
402        elif category == 'schoolfee' and student.current_mode == ('ug_sw'):
403            amount += academic_session.penalty_sw
404        elif category == 'schoolfee' and student.current_mode in (
405            'dp_ft', 'dp_pt'):
406            amount += academic_session.penalty_dp
407        if category.startswith('tempmaint'):
408            p_item = getUtility(IKofaUtils).PAYMENT_CATEGORIES[category]
409            p_item = unicode(p_item)
410            # Now we change the category because tempmaint payments
411            # will be obsolete when Uniben returns to Kofa bed allocation.
412            category = 'hostel_maintenance'
413        # Create ticket.
414        if self.samePaymentMade(student, category, p_item, p_session):
415            return _('This type of payment has already been made.'), None
416        if self._isPaymentDisabled(p_session, category, student):
417            return _('This category of payments has been disabled.'), None
418        payment = createObject(u'waeup.StudentOnlinePayment')
419        timestamp = ("%d" % int(time()*10000))[1:]
420        payment.p_id = "p%s" % timestamp
421        payment.p_category = category
422        payment.p_item = p_item
423        payment.p_session = p_session
424        payment.p_level = p_level
425        payment.p_current = p_current
426        payment.amount_auth = amount
427        return None, payment
428
429    def warnCreditsOOR(self, studylevel, course=None):
430        studycourse = studylevel.__parent__
431        certificate = getattr(studycourse,'certificate', None)
432        current_level = studycourse.current_level
433        if None in (current_level, certificate):
434            return
435        end_level = certificate.end_level
436        if studylevel.student.faccode in (
437            'MED', 'DEN', 'BMS') and studylevel.level == 200:
438            limit = 61
439        elif current_level >= end_level:
440            limit = 51
441        else:
442            limit = 50
443        if course and studylevel.total_credits + course.credits > limit:
444            return _('Maximum credits exceeded.')
445        elif studylevel.total_credits > limit:
446            return _('Maximum credits exceeded.')
447        return
448
449    def clearance_disabled_message(self, student):
450        if student.is_postgrad:
451            return None
452        try:
453            session_config = grok.getSite()[
454                'configuration'][str(student.current_session)]
455        except KeyError:
456            return _('Session configuration object is not available.')
457        if not session_config.clearance_enabled:
458            return _('Clearance is disabled for this session.')
459        return None
460
461    #: A tuple containing the names of registration states in which changing of
462    #: passport pictures is allowed.
463    PORTRAIT_CHANGE_STATES = ()
464
465    # Uniben prefix
466    STUDENT_ID_PREFIX = u'B'
467
468    STUDENT_EXPORTER_NAMES = (
469            'students',
470            'studentstudycourses',
471            'studentstudylevels',
472            'coursetickets',
473            'studentpayments',
474            'bedtickets',
475            'trimmed',
476            'outstandingcourses',
477            'unpaidpayments',
478            'sfpaymentsoverview',
479            'sessionpaymentsoverview',
480            'studylevelsoverview',
481            'combocard',
482            'bursary',
483            'accommodationpayments',
484            'transcriptdata',
485            'trimmedpayments',
486            )
Note: See TracBrowser for help on using the repository browser.