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

Last change on this file since 17237 was 17078, checked in by Henrik Bettermann, 2 years ago

School fees changed in 2021 and not in 2020.

  • Property svn:keywords set to Id
File size: 27.5 KB
Line 
1## $Id: utils.py 17078 2022-08-26 21:39:43Z 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, queryUtility
21from zope.catalog.interfaces import ICatalog
22from waeup.kofa.interfaces import (
23    ADMITTED, CLEARANCE, REQUESTED, CLEARED, RETURNING, PAID,
24    REGISTERED, VALIDATED, academic_sessions_vocab)
25from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
26from waeup.kofa.accesscodes import create_accesscode
27from waeup.kofa.students.utils import trans
28from waeup.aaue.interswitch.browser import gateway_net_amt, GATEWAY_AMT
29from waeup.aaue.interfaces import MessageFactory as _
30
31MINIMUM_UNITS_THRESHOLD = 15
32
33class CustomStudentsUtils(NigeriaStudentsUtils):
34    """A collection of customized methods.
35
36    """
37
38    # PORTRAIT_CHANGE_STATES = (ADMITTED, CLEARANCE, REQUESTED, CLEARED)
39
40    def allowPortraitChange(self, student):
41        if student.is_fresh:
42            return True
43        return False
44
45    def GPABoundaries(self, faccode=None, depcode=None,
46                            certcode=None, student=None):
47        if student and student.current_mode.startswith('dp'):
48            return ((1.5, 'IRNS / NER / NYV'),
49                    (2.4, 'Pass'),
50                    (3.5, 'Merit'),
51                    (4.5, 'Credit'),
52                    (5, 'Distinction'))
53        elif student:
54            return ((1, 'FRNS / NER / NYV'),
55                    (1.5, 'Pass'),
56                    (2.4, '3rd Class Honours'),
57                    (3.5, '2nd Class Honours Lower Division'),
58                    (4.5, '2nd Class Honours Upper Division'),
59                    (5, '1st Class Honours'))
60        # Session Results Presentations depend on certificate
61        results = None
62        if certcode:
63            cat = queryUtility(ICatalog, name='certificates_catalog')
64            results = list(
65                cat.searchResults(code=(certcode, certcode)))
66        if results and results[0].study_mode.startswith('dp'):
67            return ((1.5, 'IRNS / NER / NYV'),
68                    (2.4, 'Pass'),
69                    (3.5, 'Merit'),
70                    (4.5, 'Credit'),
71                    (5, 'Distinction'))
72        else:
73            return ((1, 'FRNS / NER / NYV'),
74                    (1.5, 'Pass'),
75                    (2.4, '3rd Class Honours'),
76                    (3.5, '2nd Class Honours Lower Division'),
77                    (4.5, '2nd Class Honours Upper Division'),
78                    (5, '1st Class Honours'))
79
80    def getClassFromCGPA(self, gpa, student):
81        gpa_boundaries = self.GPABoundaries(student=student)
82
83        try:
84            certificate = getattr(student['studycourse'],'certificate',None)
85            end_level = getattr(certificate, 'end_level', None)
86            final_level = max([studylevel.level for studylevel in student['studycourse'].values()])
87            if end_level and final_level >= end_level:
88                dummy, repeat = divmod(final_level, 100)
89                if gpa <= 5.1 and repeat == 20:
90                    # Irrespective of the CGPA of a student, if the He/She has
91                    # 3rd Extension, such student will be graduated with a "Pass".
92                    return 1, gpa_boundaries[1][1]
93        except ValueError:
94            pass
95
96        if gpa < gpa_boundaries[0][0]:
97            # FRNS / Fail
98            return 0, gpa_boundaries[0][1]
99        if student.entry_session < 2013 or \
100            student.current_mode.startswith('dp'):
101            if gpa < gpa_boundaries[1][0]:
102                # Pass
103                return 1, gpa_boundaries[1][1]
104        else:
105            if gpa < gpa_boundaries[1][0]:
106                # FRNS
107                # Pass degree has been phased out in 2013 for non-diploma
108                # students
109                return 0, gpa_boundaries[0][1]
110        if gpa < gpa_boundaries[2][0]:
111            # 3rd / Merit
112            return 2, gpa_boundaries[2][1]
113        if gpa < gpa_boundaries[3][0]:
114            # 2nd L / Credit
115            return 3, gpa_boundaries[3][1]
116        if gpa < gpa_boundaries[4][0]:
117            # 2nd U / Distinction
118            return 4, gpa_boundaries[4][1]
119        if gpa <= gpa_boundaries[5][0]:
120            # 1st
121            return 5, gpa_boundaries[5][1]
122        return
123
124    def getDegreeClassNumber(self, level_obj):
125        """Get degree class number (used for SessionResultsPresentation
126        reports).
127        """
128        certificate = getattr(level_obj.__parent__,'certificate', None)
129        end_level = getattr(certificate, 'end_level', None)
130        if level_obj.level_verdict in ('FRNS', 'NER', 'NYV'):
131            return 0
132        if end_level and level_obj.level >= end_level:
133            if level_obj.level > end_level:
134                # spill-over level
135                if level_obj.gpa_params[1] == 0:
136                    # no credits taken
137                    return 0
138            elif level_obj.gpa_params[1] < MINIMUM_UNITS_THRESHOLD:
139                # credits taken below limit
140                return 0
141            failed_courses = level_obj.passed_params[4]
142            not_taken_courses = level_obj.passed_params[5]
143            if '_m' in failed_courses:
144                return 0
145            if len(not_taken_courses) \
146                and not not_taken_courses == 'Nil':
147                return 0
148        elif level_obj.gpa_params[1] < MINIMUM_UNITS_THRESHOLD:
149            # credits taken below limit
150            return 0
151        # use gpa_boundaries above
152        return self.getClassFromCGPA(
153            level_obj.cumulative_params[0], level_obj.student)[0]
154
155    def increaseMatricInteger(self, student):
156        """Increase counter for matric numbers.
157        This counter can be a centrally stored attribute or an attribute of
158        faculties, departments or certificates. In the base package the counter
159        is as an attribute of the site configuration container.
160        """
161        if student.current_mode in ('ug_pt', 'de_pt', 'ug_dsh', 'de_dsh'):
162            grok.getSite()['configuration'].next_matric_integer += 1
163            return
164        elif student.is_postgrad:
165            grok.getSite()['configuration'].next_matric_integer_3 += 1
166            return
167        elif student.current_mode in ('dp_ft',):
168            grok.getSite()['configuration'].next_matric_integer_4 += 1
169            return
170        grok.getSite()['configuration'].next_matric_integer_2 += 1
171        return
172
173    def _concessionalPaymentMade(self, student):
174        if len(student['payments']):
175            for ticket in student['payments'].values():
176                if ticket.p_state == 'paid' and \
177                    ticket.p_category == 'concessional':
178                    return True
179        return False
180
181    def constructMatricNumber(self, student):
182        faccode = student.faccode
183        depcode = student.depcode
184        certcode = student.certcode
185        degree = getattr(
186            getattr(student.get('studycourse', None), 'certificate', None),
187                'degree', None)
188        year = unicode(student.entry_session)[2:]
189        if not student.state in (PAID, ) or not student.is_fresh or \
190            student.current_mode in ('found', 'ijmbe'):
191            return _('Matriculation number cannot be set.'), None
192        #if student.current_mode not in ('mug_ft', 'mde_ft') and \
193        #    not self._concessionalPaymentMade(student):
194        #    return _('Matriculation number cannot be set.'), None
195        if student.is_postgrad:
196            next_integer = grok.getSite()['configuration'].next_matric_integer_3
197            if not degree or next_integer == 0:
198                return _('Matriculation number cannot be set.'), None
199            if student.faccode in ('IOE'):
200                return None, "AAU/SPS/%s/%s/%s/%05d" % (
201                    faccode, year, degree, next_integer)
202            return None, "AAU/SPS/%s/%s/%s/%s/%05d" % (
203                faccode, depcode, year, degree, next_integer)
204        if student.current_mode in ('ug_dsh', 'de_dsh'):
205            next_integer = grok.getSite()['configuration'].next_matric_integer
206            if next_integer == 0:
207                return _('Matriculation number cannot be set.'), None
208            return None, "DSH/%s/%s/%s/%05d" % (
209                faccode, depcode, year, next_integer)
210        if student.current_mode in ('ug_pt', 'de_pt'):
211            next_integer = grok.getSite()['configuration'].next_matric_integer
212            if next_integer == 0:
213                return _('Matriculation number cannot be set.'), None
214            return None, "PTP/%s/%s/%s/%05d" % (
215                faccode, depcode, year, next_integer)
216        if student.current_mode in ('dp_ft',):
217            next_integer = grok.getSite()['configuration'].next_matric_integer_4
218            if next_integer == 0:
219                return _('Matriculation number cannot be set.'), None
220            return None, "IOE/DIP/%s/%05d" % (year, next_integer)
221        next_integer = grok.getSite()['configuration'].next_matric_integer_2
222        if next_integer == 0:
223            return _('Matriculation number cannot be set.'), None
224        if student.faccode in ('FBM', 'FCS', 'FMLS'):
225            return None, "CMS/%s/%s/%s/%05d" % (
226                faccode, depcode, year, next_integer)
227        return None, "%s/%s/%s/%05d" % (faccode, depcode, year, next_integer)
228
229    def getReturningData(self, student):
230        """ This method defines what happens after school fee payment
231        of returning students depending on the student's senate verdict.
232        """
233        prev_level = student['studycourse'].current_level
234        cur_verdict = student['studycourse'].current_verdict
235        if cur_verdict in ('A','B','L','M','N','Z',):
236            # Successful student
237            new_level = divmod(int(prev_level),100)[0]*100 + 100
238        elif cur_verdict == 'C':
239            # Student on probation
240            new_level = int(prev_level) + 10
241        else:
242            # Student is somehow in an undefined state.
243            # Level has to be set manually.
244            new_level = prev_level
245        new_session = student['studycourse'].current_session + 1
246        return new_session, new_level
247
248    def _isPaymentDisabled(self, p_session, category, student):
249        academic_session = self._getSessionConfiguration(p_session)
250        if category.startswith('schoolfee'):
251            if 'sf_all' in academic_session.payment_disabled:
252                return True
253            if 'sf_pg' in academic_session.payment_disabled and \
254                student.is_postgrad:
255                return True
256            if 'sf_ug_pt' in academic_session.payment_disabled and \
257                student.current_mode in ('ug_pt', 'de_pt'):
258                return True
259            if 'sf_found' in academic_session.payment_disabled and \
260                student.current_mode == 'found':
261                return True
262        if category.startswith('clearance') and \
263            'cl_regular' in academic_session.payment_disabled and \
264            student.current_mode in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
265            return True
266        if category == 'hostel_maintenance' and \
267            'maint_all' in academic_session.payment_disabled:
268            return True
269        return False
270
271    def setPaymentDetails(self, category, student,
272            previous_session=None, previous_level=None, combi=[]):
273        """Create Payment object and set the payment data of a student for
274        the payment category specified.
275
276        """
277        details = {}
278        p_item = u''
279        amount = 0.0
280        error = u''
281        if previous_session:
282            if previous_session < student['studycourse'].entry_session:
283                return _('The previous session must not fall below '
284                         'your entry session.'), None
285            if category == 'schoolfee':
286                # School fee is always paid for the following session
287                if previous_session > student['studycourse'].current_session:
288                    return _('This is not a previous session.'), None
289            else:
290                if previous_session > student['studycourse'].current_session - 1:
291                    return _('This is not a previous session.'), None
292            p_session = previous_session
293            p_level = previous_level
294            p_current = False
295        else:
296            p_session = student['studycourse'].current_session
297            p_level = student['studycourse'].current_level
298            p_current = True
299        academic_session = self._getSessionConfiguration(p_session)
300        if academic_session == None:
301            return _(u'Session configuration object is not available.'), None
302        # Determine fee.
303        if category == 'transfer':
304            amount = academic_session.transfer_fee
305        elif category == 'transcript_local':
306            amount = academic_session.transcript_fee_local
307        elif category == 'transcript_inter':
308            amount = academic_session.transcript_fee_inter
309        elif category == 'bed_allocation':
310            amount = academic_session.booking_fee
311        elif category == 'restitution':
312            if student.current_session != 2016 \
313                or student.current_mode not in ('ug_ft', 'dp_ft') \
314                or student.is_fresh:
315                return _(u'Restitution fee payment not required.'), None
316            amount = academic_session.restitution_fee
317        elif category == 'hostel_maintenance':
318            amount = 0.0
319            bedticket = student['accommodation'].get(
320                str(student.current_session), None)
321            if bedticket is not None and bedticket.bed is not None:
322                p_item = bedticket.display_coordinates
323                if bedticket.bed.__parent__.maint_fee > 0:
324                    amount = bedticket.bed.__parent__.maint_fee
325                else:
326                    # fallback
327                    amount = academic_session.maint_fee
328            else:
329                return _(u'No bed allocated.'), None
330        elif student.current_mode == 'found' and category not in (
331            'schoolfee', 'clearance', 'late_registration'):
332            return _('Not allowed.'), None
333        elif category.startswith('clearance'):
334            if student.state not in (ADMITTED, CLEARANCE, REQUESTED, CLEARED):
335                return _(u'Acceptance Fee payments not allowed.'), None
336            if student.current_mode in (
337                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
338                'transfer', 'mug_ft', 'mde_ft') \
339                and category != 'clearance_incl':
340                    return _("Additional fees must be included."), None
341            if student.current_mode == 'ijmbe':
342                amount = academic_session.clearance_fee_ijmbe
343            elif student.current_mode == 'bridge':
344                amount = academic_session.clearance_fee_bridge
345            elif student.current_mode == 'dp_ft':
346                amount = academic_session.clearance_fee_dp
347            elif student.faccode == 'FP':
348                amount = academic_session.clearance_fee_fp
349            elif student.current_mode in ('ug_pt', 'de_pt', 'ug_dsh', 'de_dsh'):
350                amount = academic_session.clearance_fee_ug_pt
351            elif student.current_mode == 'special_pg_pt':
352                amount = academic_session.clearance_fee_pg_pt
353            elif student.faccode == 'FCS':
354                # Students in clinical medical sciences pay the medical
355                # acceptance fee
356                amount = academic_session.clearance_fee_med
357            elif student.current_mode == 'special_pg_ft':
358                if category != 'clearance':
359                    return _("No additional fees required."), None
360                amount = academic_session.clearance_fee_pg
361            else:
362                amount = academic_session.clearance_fee
363            p_item = student['studycourse'].certificate.code
364            if amount in (0.0, None):
365                return _(u'Amount could not be determined.'), None
366            # Add Matric Gown Fee and Lapel Fee
367            if category == 'clearance_incl':
368                amount += gateway_net_amt(academic_session.matric_gown_fee) + \
369                    gateway_net_amt(academic_session.lapel_fee)
370        elif category == 'late_registration':
371            if student.is_postgrad:
372                amount = academic_session.late_pg_registration_fee
373            else:
374                amount = academic_session.late_registration_fee
375        elif category == 'ict':
376            if student.is_fresh:
377                amount = 2200
378            else:
379                amount = 1200
380        elif category.startswith('schoolfee'):
381            try:
382                certificate = student['studycourse'].certificate
383                p_item = certificate.code
384            except (AttributeError, TypeError):
385                return _('Study course data are incomplete.'), None
386            if student.is_postgrad and category != 'schoolfee':
387                return _("No additional fees required."), None
388            if not previous_session and student.current_mode in (
389                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
390                'transfer', 'mug_ft', 'mde_ft') \
391                and not category in (
392                'schoolfee_incl', 'schoolfee_1', 'schoolfee_2'):
393                    return _("You must choose a payment which includes "
394                             "additional fees."), None
395            if category in ('schoolfee_1', 'schoolfee_2'):
396                if student.current_mode == 'ug_pt':
397                    return _("Part-time students are not allowed "
398                             "to pay by instalments."), None
399                if student.entry_session < 2015:
400                    return _("You are not allowed "
401                             "to pay by instalments."), None
402            # We determine the base amount first
403            if student.entry_session < 2015:
404                amount = getattr(certificate, 'school_fee_3', 0.0)
405            elif student.entry_session < 2021:
406                amount = getattr(certificate, 'school_fee_2', 0.0)
407            else:
408                amount = getattr(certificate, 'school_fee_1', 0.0)
409            if previous_session:
410                # Students can pay for previous sessions in all
411                # workflow states.  Fresh students are excluded by the
412                # update method of the PreviousPaymentAddFormPage.
413                pass
414            elif student.state == CLEARED:
415                # Cut school fee by 50%
416                if category in ('schoolfee_1', 'schoolfee_2') and amount:
417                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
418            elif student.state == RETURNING and category != 'schoolfee_2':
419                if not student.father_name:
420                    return _("Personal data form is not properly filled."), None
421                # In case of returning school fee payment the payment session
422                # and level contain the values of the session the student
423                # has paid for.
424                p_session, p_level = self.getReturningData(student)
425                try:
426                    academic_session = grok.getSite()[
427                        'configuration'][str(p_session)]
428                except KeyError:
429                    return _(u'Session configuration object is not available.'), None
430                # Cut school fee by 50%
431                if category == 'schoolfee_1' and amount:
432                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
433            elif category == 'schoolfee_2' and amount:
434                amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
435            else:
436                return _('Wrong state.'), None
437            if amount in (0.0, None):
438                return _(u'Amount could not be determined.'), None
439            # Add Student Union Fee, Student Id Card Fee, Sports Dev. Fee,
440            # Library Dev. Fee and Welfare Assurance
441            if category in ('schoolfee_incl', 'schoolfee_1') \
442                and student.current_mode != 'ijmbe':
443                amount += gateway_net_amt(academic_session.welfare_fee) + \
444                    gateway_net_amt(academic_session.union_fee)
445                if student.entry_session >= 2018 and student.is_fresh:
446                    amount += gateway_net_amt(academic_session.sports_fee)
447                    if student.is_postgrad:
448                        amount += gateway_net_amt(academic_session.library_pg_fee)
449                    else:
450                        amount += gateway_net_amt(academic_session.library_fee)
451                if student.entry_session >= 2021 \
452                    and student.current_mode == 'ug_ft':
453                    amount += gateway_net_amt(academic_session.lms_sund_fee)
454            # Add non-indigenous fee and session specific penalty fees
455            if student.is_postgrad:
456                amount += academic_session.penalty_pg
457                if student.lga and not student.lga.startswith('edo'):
458                    amount += 20000.0
459            else:
460                amount += academic_session.penalty_ug
461        #elif category == ('fac_dep'):
462        #    if student.faccode in ('FAT', 'FED', 'FLW', 'FMS', 'FSS'):
463        #        amount = 4200.0
464        #    elif student.faccode in ('FAG', 'FBM', 'FCS',
465        #                             'FES', 'FET', 'FLS', 'FPS'):
466        #        amount = 5200.0
467        #    elif student.faccode == 'FAG' and student.current_level == 400:
468        #        amount = 10200.0
469        #    elif student.depcode == 'VTE':
470        #        amount = 5200.0
471        #    if student.entry_session >= 2019:
472        #        amount += gateway_net_amt(academic_session.ict_fee)
473        elif category == 'sports_library': # temporarily in 2020
474            if student.is_postgrad:
475                amount = academic_session.sports_fee + gateway_net_amt(
476                    academic_session.library_pg_fee)
477            else:
478                amount = academic_session.sports_fee + gateway_net_amt(
479                    academic_session.library_fee)
480        elif not student.is_postgrad:
481            fee_name = category + '_fee'
482            amount = getattr(academic_session, fee_name, 0.0)
483        if amount in (0.0, None):
484            return _(u'Amount could not be determined.'), None
485        # Create ticket.
486        for key in student['payments'].keys():
487            ticket = student['payments'][key]
488            if ticket.p_state == 'paid' and\
489               ticket.p_category == category and \
490               not ticket.p_category.startswith('transcript') and \
491               ticket.p_item == p_item and \
492               ticket.p_session == p_session:
493                  return _('This type of payment has already been made.'), None
494            # Additional condition in AAUE
495            if category in ('schoolfee', 'schoolfee_incl', 'schoolfee_1'):
496                if ticket.p_state == 'paid' and \
497                   ticket.p_category in ('schoolfee',
498                                         'schoolfee_incl',
499                                         'schoolfee_1') and \
500                   ticket.p_item == p_item and \
501                   ticket.p_session == p_session:
502                      return _(
503                          'Another school fee payment for this '
504                          'session has already been made.'), None
505
506        if self._isPaymentDisabled(p_session, category, student):
507            return _('This category of payments has been disabled.'), None
508        payment = createObject(u'waeup.StudentOnlinePayment')
509        timestamp = ("%d" % int(time()*10000))[1:]
510        payment.p_id = "p%s" % timestamp
511        payment.p_category = category
512        payment.p_item = p_item
513        payment.p_session = p_session
514        payment.p_level = p_level
515        payment.p_current = p_current
516        payment.amount_auth = amount
517        return None, payment
518
519    def _admissionText(self, student, portal_language):
520        inst_name = grok.getSite()['configuration'].name
521        entry_session = student['studycourse'].entry_session
522        entry_session = academic_sessions_vocab.getTerm(entry_session).title
523        text = trans(_(
524            'This is to inform you that you have been offered provisional'
525            ' admission into ${a} for the ${b} academic session as follows:',
526            mapping = {'a': inst_name, 'b': entry_session}),
527            portal_language)
528        return text
529
530    def warnCreditsOOR(self, studylevel, course=None):
531        studycourse = studylevel.__parent__
532        certificate = getattr(studycourse,'certificate', None)
533        current_level = studycourse.current_level
534        if None in (current_level, certificate):
535            return
536        end_level = certificate.end_level
537        if current_level >= end_level:
538            limit = 52
539        else:
540            limit = 48
541        if course and studylevel.total_credits + course.credits > limit:
542            return  _('Maximum credits exceeded.')
543        elif studylevel.total_credits > limit:
544            return _('Maximum credits exceeded.')
545        return
546
547    def getBedCoordinates(self, bedticket):
548        """Return descriptive bed coordinates.
549        This method can be used to customize the `display_coordinates`
550        property method in order to  display a
551        customary description of the bed space.
552        """
553        bc = bedticket.bed_coordinates.split(',')
554        if len(bc) == 4:
555            return bc[0]
556        return bedticket.bed_coordinates
557
558    def getAccommodationDetails(self, student):
559        """Determine the accommodation data of a student.
560        """
561        d = {}
562        d['error'] = u''
563        hostels = grok.getSite()['hostels']
564        d['booking_session'] = hostels.accommodation_session
565        d['allowed_states'] = hostels.accommodation_states
566        d['startdate'] = hostels.startdate
567        d['enddate'] = hostels.enddate
568        d['expired'] = hostels.expired
569        # Determine bed type
570        bt = 'all'
571        if student.sex == 'f':
572            sex = 'female'
573        else:
574            sex = 'male'
575        special_handling = 'regular'
576        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
577        return d
578
579    def checkAccommodationRequirements(self, student, acc_details):
580        msg = super(CustomStudentsUtils, self).checkAccommodationRequirements(
581            student, acc_details)
582        if msg:
583            return msg
584        if student.current_mode not in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
585            return _('You are not eligible to book accommodation.')
586        return
587
588    # AAUE prefix
589    STUDENT_ID_PREFIX = u'E'
590
591    STUDENT_EXPORTER_NAMES = (
592            'students',
593            'studentstudycourses',
594            'studentstudycourses_1',
595            'studentstudylevels',
596            #'studentstudylevels_1',
597            'coursetickets',
598            #'coursetickets_1',
599            'studentpayments',
600            'bedtickets',
601            'unpaidpayments',
602            'sfpaymentsoverview',
603            'studylevelsoverview',
604            'combocard',
605            'bursary',
606            'levelreportdata',
607            'outstandingcourses',
608            'sessionpaymentsoverview',
609            'accommodationpayments',
610            'transcriptdata',
611            'trimmedpayments',
612            'trimmed',
613            'outstandingcourses_2'
614            )
615
616    # Maximum size of upload files in kB
617    MAX_KB = 500
Note: See TracBrowser for help on using the repository browser.