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

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

Add trimmedpayments.

  • Property svn:keywords set to Id
File size: 21.7 KB
RevLine 
[7419]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##
[11773]18import grok
[8598]19from time import time
[9459]20from zope.component import createObject, getUtility
[9513]21from waeup.kofa.interfaces import (IKofaUtils,
22    CLEARED, RETURNING, PAID, REGISTERED, VALIDATED)
[13251]23from waeup.kofa.utils.helpers import to_timezone
24from waeup.kofa.students.utils import trans
[8821]25from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
[8020]26from waeup.uniben.interfaces import MessageFactory as _
[6902]27
[8821]28class CustomStudentsUtils(NigeriaStudentsUtils):
[7151]29    """A collection of customized methods.
30
31    """
32
[8270]33    def getReturningData(self, student):
34        """ This method defines what happens after school fee payment
[8319]35        of returning students depending on the student's senate verdict.
[8270]36        """
[8319]37        prev_level = student['studycourse'].current_level
38        cur_verdict = student['studycourse'].current_verdict
[15482]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',):
[8319]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
[8270]51        new_session = student['studycourse'].current_session + 1
52        return new_session, new_level
53
[13251]54
[13248]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.")
[13777]69        if not student.is_postgrad and student.current_mode != 'ug_ft':
[13446]70            return _("Only undergraduate full-time students are eligible to book accommodation.")
[13283]71        bt = acc_details.get('bt')
72        if not bt:
[13248]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.')
[13283]80        stage = bt.split('_')[2]
[13777]81        if not student.is_postgrad and stage != 'fr' and not student[
[14328]82            'studycourse'].previous_verdict in (
[15392]83                'A', 'B', 'F', 'J', 'L', 'M', 'C', 'Z'):
[13283]84            return _("Your are not eligible to book accommodation.")
[15307]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:
[13248]89            return _('You already booked a bed space in '
90                     'current accommodation session.')
91        return
[13244]92
[13295]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'
[13777]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'
[15354]129            desired_hostel = student['accommodation'].desired_hostel
130            if student.faccode in ('MED', 'DEN') and (
131                not desired_hostel or desired_hostel.startswith('clinical')):
[13777]132                special_handling = 'clinical'
133            elif student.certcode in ('BARTMAS', 'BARTTHR', 'BARTFAA',
[15957]134                                      'BAEDFAA', 'BSCEDECHED', 'BAFAA'):
[13777]135                special_handling = 'ekenwan'
[13295]136        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
137        return d
138
[9520]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
[13566]148    def _isPaymentDisabled(self, p_session, category, student):
149        academic_session = self._getSessionConfiguration(p_session)
[14688]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
[13566]172        if category == 'hostel_maintenance' and \
173            'maint_all' in academic_session.payment_disabled:
174            return True
175        return False
176
[13251]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
[12566]185
[14960]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
[15320]196    def samePaymentMade(self, student, category, p_item, p_session):
[15447]197        if category in ('bed_allocation', 'transcript'):
[15320]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
[9152]208    def setPaymentDetails(self, category, student,
[15666]209            previous_session, previous_level, combi):
[8598]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
[9152]216        if previous_session:
[9520]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
[9152]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
[9520]234        academic_session = self._getSessionConfiguration(p_session)
235        if academic_session == None:
[8598]236            return _(u'Session configuration object is not available.'), None
[8676]237        # Determine fee.
[7151]238        if category == 'transfer':
[8598]239            amount = academic_session.transfer_fee
[10469]240        elif category == 'transcript':
241            amount = academic_session.transcript_fee
[7151]242        elif category == 'gown':
[8598]243            amount = academic_session.gown_fee
[13785]244        elif category == 'jupeb':
245            amount = academic_session.jupeb_fee
[14358]246        elif category == 'clinexam':
247            amount = academic_session.clinexam_fee
[14960]248        elif category.startswith('pharmd') \
249            and student.current_mode == 'special_ft':
250            amount = 80000.0
[15108]251        #elif category == 'develop' and student.is_postgrad:
252        #    amount = academic_session.development_fee
[13251]253        elif category == 'bed_allocation':
254            p_item = self.getAccommodationDetails(student)['bt']
[15268]255            desired_hostel = student['accommodation'].desired_hostel
[15314]256            if not desired_hostel:
257                return _(u'Select your favoured hostel first.'), None
258            if desired_hostel and desired_hostel != 'no':
[15268]259                p_item = u'%s (%s)' % (p_item, desired_hostel)
[13251]260            amount = academic_session.booking_fee
[15002]261            if student.is_postgrad:
262                amount += 500
[13251]263        elif category == 'hostel_maintenance':
264            amount = 0.0
265            bedticket = student['accommodation'].get(
266                str(student.current_session), None)
[13500]267            if bedticket is not None and bedticket.bed is not None:
[13251]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:
[13508]275                return _(u'No bed allocated.'), None
[13251]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
[7151]289        elif category == 'clearance':
[9796]290            p_item = student.certcode
291            if p_item is None:
[8598]292                return _('Study course data are incomplete.'), None
[14902]293            if student.is_jupeb:
[14897]294                amount = 50000.0
295            elif student.faccode.startswith('FCETA'):
[13869]296                # ASABA and AKOKA
[14932]297                amount = 35000.0
[15399]298            elif student.faccode in ('BMS', 'MED', 'DEN'):
[15342]299            #elif p_item in ('BSCANA', 'BSCMBC', 'BMLS', 'BSCNUR', 'BSCPHS', 'BDS',
300            #    'MBBSMED', 'MBBSNDU', 'BSCPTY', 'BSCPST'):
[14897]301                amount = 80000.0
[15574]302            elif student.faccode == 'DCOEM':
303                return _('Acceptance fee payment not necessary.'), None
[11479]304            else:
[14897]305                amount = 60000.0
[7151]306        elif category == 'schoolfee':
[8598]307            try:
308                certificate = student['studycourse'].certificate
309                p_item = certificate.code
310            except (AttributeError, TypeError):
311                return _('Study course data are incomplete.'), None
[9152]312            if previous_session:
[9520]313                # Students can pay for previous sessions in all workflow states.
314                # Fresh students are excluded by the update method of the
315                # PreviousPaymentAddFormPage.
[9157]316                if previous_session == student['studycourse'].entry_session:
[9152]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)
[9006]321                else:
[9152]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)
[14904]326                        # Old returning students might get a discount.
[15094]327                        if student.entry_session < 2017 \
328                            and certificate.custom_float_1:
329                            amount -= certificate.custom_float_1
[9152]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)
[14858]336                elif student.state == PAID and student.is_postgrad:
[9513]337                    p_session += 1
[9520]338                    academic_session = self._getSessionConfiguration(p_session)
339                    if academic_session == None:
[9513]340                        return _(u'Session configuration object is not available.'), None
[9570]341
[9520]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.
[9570]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
[9513]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)
[9152]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)
[9520]360                    academic_session = self._getSessionConfiguration(p_session)
361                    if academic_session == None:
[9152]362                        return _(u'Session configuration object is not available.'), None
[9570]363
[9520]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.
[9570]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
[9152]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)
[14893]377                        # Old returning students might get a discount.
[14892]378                        if student.entry_session < 2017 \
[14893]379                            and certificate.custom_float_1:
380                            amount -= certificate.custom_float_1
[14960]381                # PHARMD school fee amount is fixed and previously paid
382                # installments in current session are deducted.
[15363]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)
[9006]389            # Give 50% school fee discount to staff members.
390            if student.is_staff:
391                amount /= 2
[8598]392        if amount in (0.0, None):
[9520]393            return _('Amount could not be determined.'), None
[8676]394        # Add session specific penalty fee.
395        if category == 'schoolfee' and student.is_postgrad:
396            amount += academic_session.penalty_pg
[15108]397            amount += academic_session.development_fee
[13757]398        elif category == 'schoolfee' and student.current_mode == ('ug_ft'):
[13756]399            amount += academic_session.penalty_ug_ft
[13757]400        elif category == 'schoolfee' and student.current_mode == ('ug_pt'):
[13756]401            amount += academic_session.penalty_ug_pt
[13998]402        elif category == 'schoolfee' and student.current_mode == ('ug_sw'):
403            amount += academic_session.penalty_sw
[14717]404        elif category == 'schoolfee' and student.current_mode in (
405            'dp_ft', 'dp_pt'):
406            amount += academic_session.penalty_dp
[9727]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
[12566]411            # will be obsolete when Uniben returns to Kofa bed allocation.
[9727]412            category = 'hostel_maintenance'
[8676]413        # Create ticket.
[15320]414        if self.samePaymentMade(student, category, p_item, p_session):
[11644]415            return _('This type of payment has already been made.'), None
[11459]416        if self._isPaymentDisabled(p_session, category, student):
[13814]417            return _('This category of payments has been disabled.'), None
[8715]418        payment = createObject(u'waeup.StudentOnlinePayment')
[8950]419        timestamp = ("%d" % int(time()*10000))[1:]
[8598]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
[9152]425        payment.p_current = p_current
[8598]426        payment.amount_auth = amount
427        return None, payment
[7621]428
[14588]429    def warnCreditsOOR(self, studylevel, course=None):
[9831]430        studycourse = studylevel.__parent__
431        certificate = getattr(studycourse,'certificate', None)
432        current_level = studycourse.current_level
433        if None in (current_level, certificate):
[14588]434            return
[9831]435        end_level = certificate.end_level
[15840]436        if studylevel.student.faccode in (
437            'MED', 'DEN', 'BMS') and studylevel.level == 200:
438            limit = 61
439        elif current_level >= end_level:
[14588]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
[9831]448
[11773]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
[14038]461    #: A tuple containing the names of registration states in which changing of
462    #: passport pictures is allowed.
463    PORTRAIT_CHANGE_STATES = ()
464
[8441]465    # Uniben prefix
[15980]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.