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

Last change on this file since 17437 was 17431, checked in by Henrik Bettermann, 19 months ago

Rewrite payment configuration.

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