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

Last change on this file since 17718 was 17707, checked in by Henrik Bettermann, 9 months ago

New school fees in 2023.

  • Property svn:keywords set to Id
File size: 37.2 KB
Line 
1## $Id: utils.py 17707 2024-02-28 16:03:03Z 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
19import os
20import csv
21from time import time
22from reportlab.platypus import Paragraph, Table
23from zope.component import createObject, getUtility
24from reportlab.lib.styles import getSampleStyleSheet
25from waeup.kofa.browser.pdf import ENTRY1_STYLE
26from waeup.kofa.interfaces import (IKofaUtils, ADMITTED, CLEARANCE,
27    CLEARED, REQUESTED, RETURNING, PAID, REGISTERED, VALIDATED, GRADUATED)
28from waeup.kofa.utils.helpers import to_timezone
29from waeup.kofa.students.utils import (
30    trans, render_student_data, formatted_text, render_transcript_data,
31    SLIP_STYLE)
32from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
33from waeup.uniben.interfaces import MessageFactory as _
34
35SCHOOLFEES = dict()
36
37schoolfees_path = os.path.join(
38    os.path.dirname(__file__), 'schoolfees_23.csv')
39reader = csv.DictReader(open(schoolfees_path, 'rb'))
40SCHOOLFEES[23] = {line['code']: {item[0]:item[1] for item in line.items()}
41    for line in reader}
42
43schoolfees_path = os.path.join(
44    os.path.dirname(__file__), 'schoolfees_22.csv')
45reader = csv.DictReader(open(schoolfees_path, 'rb'))
46SCHOOLFEES[22] = {line['code']: {item[0]:item[1] for item in line.items()}
47    for line in reader}
48
49
50schoolfees_path = os.path.join(
51    os.path.dirname(__file__), 'schoolfees_20.csv')
52reader = csv.DictReader(open(schoolfees_path, 'rb'))
53SCHOOLFEES[20] = {line['code']: {item[0]:item[1] for item in line.items()}
54    for line in reader}
55
56
57schoolfees_path = os.path.join(
58    os.path.dirname(__file__), 'schoolfees_17.csv')
59reader = csv.DictReader(open(schoolfees_path, 'rb'))
60SCHOOLFEES[17] = {line['code']: {item[0]:item[1] for item in line.items()}
61    for line in reader}
62
63schoolfees_path = os.path.join(
64    os.path.dirname(__file__), 'schoolfees_12.csv')
65reader = csv.DictReader(open(schoolfees_path, 'rb'))
66SCHOOLFEES[12] = {line['code']: {item[0]:item[1] for item in line.items()}
67    for line in reader}
68
69class CustomStudentsUtils(NigeriaStudentsUtils):
70    """A collection of customized methods.
71
72    """
73
74    SEPARATORS_DICT = {
75        'form.fst_sit_fname': _(u'First Sitting Record'),
76        'form.scd_sit_fname': _(u'Second Sitting Record'),
77        'form.alr_fname': _(u'Advanced Level Record'),
78        'form.hq_type': _(u'Higher Education Record'),
79        'form.hq2_type': _(u'Second Higher Education Record'),
80        'form.nysc_year': _(u'NYSC Information'),
81        'form.employer': _(u'Employment History'),
82        'form.former_matric': _(u'Former Student'),
83        'form.fever': _(u'History of Symptoms'),
84        'form.asthma': _(u'Medical History'),
85        'form.lagos_abuja': _(u'Travel History'),
86        'form.company_suspected': _(u'History of Contact/Infection'),
87        'form.genotype': _(u'TISHIP Registration Form Data'),
88        }
89
90    def getReturningData(self, student):
91        """ This method defines what happens after school fee payment
92        of returning students depending on the student's senate verdict.
93        """
94        prev_level = student['studycourse'].current_level
95        cur_verdict = student['studycourse'].current_verdict
96        if cur_verdict == 'N' and prev_level in (100, 200):
97            new_level = prev_level
98        elif cur_verdict in ('A','B','M','N','Z',):
99            # Successful student
100            new_level = divmod(int(prev_level),100)[0]*100 + 100
101        elif cur_verdict in ('C', 'L',):
102            # Student on probation
103            new_level = int(prev_level) + 10
104        else:
105            # Student is somehow in an undefined state.
106            # Level has to be set manually.
107            new_level = prev_level
108        new_session = student['studycourse'].current_session + 1
109        return new_session, new_level
110
111
112    def checkAccommodationRequirements(self, student, acc_details):
113        if acc_details.get('expired', False):
114            startdate = acc_details.get('startdate')
115            enddate = acc_details.get('enddate')
116            if startdate and enddate:
117                tz = getUtility(IKofaUtils).tzinfo
118                startdate = to_timezone(
119                    startdate, tz).strftime("%d/%m/%Y %H:%M:%S")
120                enddate = to_timezone(
121                    enddate, tz).strftime("%d/%m/%Y %H:%M:%S")
122                return _("Outside booking period: ${a} - ${b}",
123                         mapping = {'a': startdate, 'b': enddate})
124            else:
125                return _("Outside booking period.")
126        if not student.is_postgrad and student.current_mode != 'ug_ft':
127            return _("Only undergraduate full-time students are eligible to book accommodation.")
128        bt = acc_details.get('bt')
129        if not bt:
130            return _("Your data are incomplete.")
131        if not student.state in acc_details['allowed_states']:
132            return _("You are in the wrong registration state.")
133        #if student['studycourse'].current_session != acc_details[
134        #    'booking_session']:
135        #    return _('Your current session does not '
136        #             'match accommodation session.')
137        if  acc_details['booking_session'] - student[
138            'studycourse'].current_session > self.ACCOMMODATION_SPAN:
139            return _('Your current session does not allow ' + \
140                    'to book accommodation.')
141        stage = bt.split('_')[2]
142        if not student.is_postgrad and stage != 'fr' and not student[
143            'studycourse'].previous_verdict in (
144                'A', 'B', 'F', 'J', 'L', 'M', 'C', 'Z'):
145            return _("Your are not eligible to book accommodation.")
146        bsession = str(acc_details['booking_session'])
147        if bsession in student['accommodation'].keys() \
148            and not 'booking expired' in \
149            student['accommodation'][bsession].bed_coordinates:
150            return _('You already booked a bed space in '
151                     'current accommodation session.')
152        return
153
154    def getAccommodationDetails(self, student):
155        """Determine the accommodation data of a student.
156        """
157        d = {}
158        d['error'] = u''
159        hostels = grok.getSite()['hostels']
160        d['booking_session'] = hostels.accommodation_session
161        d['allowed_states'] = hostels.accommodation_states
162        d['startdate'] = hostels.startdate
163        d['enddate'] = hostels.enddate
164        d['expired'] = hostels.expired
165        # Determine bed type
166        studycourse = student['studycourse']
167        certificate = getattr(studycourse,'certificate',None)
168        entry_session = studycourse.entry_session
169        current_level = studycourse.current_level
170        if None in (entry_session, current_level, certificate):
171            return d
172        if student.sex == 'f':
173            sex = 'female'
174        else:
175            sex = 'male'
176        if student.is_postgrad:
177            bt = 'all'
178            special_handling = 'pg'
179        else:
180            end_level = certificate.end_level
181            if current_level == 10:
182                bt = 'pr'
183            elif entry_session == grok.getSite()['hostels'].accommodation_session:
184                bt = 'fr'
185            elif current_level >= end_level:
186                bt = 'fi'
187            else:
188                bt = 're'
189            special_handling = 'regular'
190            desired_hostel = student['accommodation'].desired_hostel
191            if student.faccode in ('MED', 'DEN') and (
192                not desired_hostel or desired_hostel.startswith('clinical')):
193                special_handling = 'clinical'
194            elif student.certcode in ('BARTMAS', 'BARTTHR', 'BARTFAA',
195                                      'BAEDFAA', 'BSCEDECHED', 'BAFAA',
196                                      'BARTMUS', 'BSCEDECHED', 'BEDECHEDU'):
197                special_handling = 'ekenwan'
198        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
199        return d
200
201    def _paymentMade(self, student, session):
202        if len(student['payments']):
203            for ticket in student['payments'].values():
204                if ticket.p_state == 'paid' and \
205                    ticket.p_category == 'schoolfee' and \
206                    ticket.p_session == session:
207                    return True
208        return False
209
210    def _isPaymentDisabled(self, p_session, category, student):
211        academic_session = self._getSessionConfiguration(p_session)
212        if category == 'schoolfee':
213            if 'sf_all' in academic_session.payment_disabled:
214                return True
215            if student.state == RETURNING and \
216                'sf_return' in academic_session.payment_disabled:
217                return True
218            if student.current_mode == 'found' and \
219                'sf_found' in academic_session.payment_disabled:
220                return True
221            if student.is_postgrad:
222                if 'sf_pg' in academic_session.payment_disabled:
223                    return True
224                return False
225            if student.current_mode.endswith('ft') and \
226                'sf_ft' in academic_session.payment_disabled:
227                return True
228            if student.current_mode.endswith('pt') and \
229                'sf_pt' in academic_session.payment_disabled:
230                return True
231            if student.current_mode.startswith('dp') and \
232                'sf_dp' in academic_session.payment_disabled:
233                return True
234            if student.current_mode.endswith('sw') and \
235                'sf_sw' in academic_session.payment_disabled:
236                return True
237        if category == 'hostel_maintenance' and \
238            'maint_all' in academic_session.payment_disabled:
239            return True
240        if category == 'clearance':
241            if 'cl_all' in academic_session.payment_disabled:
242                return True
243            if student.is_jupeb and \
244                'cl_jupeb' in academic_session.payment_disabled:
245                return True
246            if not student.is_jupeb and \
247                'cl_allexj' in academic_session.payment_disabled:
248                return True
249        return False
250
251    #def _hostelApplicationPaymentMade(self, student, session):
252    #    if len(student['payments']):
253    #        for ticket in student['payments'].values():
254    #            if ticket.p_state == 'paid' and \
255    #                ticket.p_category == 'hostel_application' and \
256    #                ticket.p_session == session:
257    #                return True
258    #    return False
259
260    def _pharmdInstallments(self, student):
261        installments = 0.0
262        if len(student['payments']):
263            for ticket in student['payments'].values():
264                if ticket.p_state == 'paid' and \
265                    ticket.p_category.startswith('pharmd') and \
266                    ticket.p_session == student.current_session:
267                    installments += ticket.amount_auth
268        return installments
269
270    def samePaymentMade(self, student, category, p_item, p_session):
271        if category in ('bed_allocation', 'transcript'):
272            return False
273        for key in student['payments'].keys():
274            ticket = student['payments'][key]
275            if ticket.p_state == 'paid' and\
276               ticket.p_category == category and \
277               ticket.p_item == p_item and \
278               ticket.p_session == p_session:
279                  return True
280        return False
281
282    def setCDLPaymentDetails(self, category, student,
283            previous_session, previous_level, combi):
284        """Create Payment object and set the payment data of a CDL student for
285        the payment category specified.
286        """
287        p_item = u''
288        amount = 0.0
289        if previous_session:
290            if previous_session < student['studycourse'].entry_session:
291                return _('The previous session must not fall below '
292                         'your entry session.'), None
293            if category == 'schoolfee':
294                # School fee is always paid for the following session
295                if previous_session > student['studycourse'].current_session:
296                    return _('This is not a previous session.'), None
297            else:
298                if previous_session > student['studycourse'].current_session - 1:
299                    return _('This is not a previous session.'), None
300            p_session = previous_session
301            p_level = previous_level
302            p_current = False
303        else:
304            p_session = student['studycourse'].current_session
305            p_level = student['studycourse'].current_level
306            p_current = True
307        academic_session = self._getSessionConfiguration(p_session)
308        if academic_session == None:
309            return _(u'Session configuration object is not available.'), None
310        # Determine fee.
311        if category == 'schoolfee':
312            try:
313                certificate = student['studycourse'].certificate
314                p_item = certificate.code
315            except (AttributeError, TypeError):
316                return _('Study course data are incomplete.'), None
317            if previous_session:
318                # Students can pay for previous sessions in all
319                # workflow states.  Fresh students are excluded by the
320                # update method of the PreviousPaymentAddFormPage.
321                if previous_level == 100:
322                    amount = getattr(certificate, 'school_fee_1', 0.0)
323                else:
324                    amount = getattr(certificate, 'school_fee_2', 0.0)
325            else:
326                if student.state == CLEARED:
327                    amount = getattr(certificate, 'school_fee_1', 0.0)
328                elif student.state == RETURNING:
329                    # In case of returning school fee payment the
330                    # payment session and level contain the values of
331                    # the session the student has paid for. Payment
332                    # session is always next session.
333                    p_session, p_level = self.getReturningData(student)
334                    academic_session = self._getSessionConfiguration(p_session)
335                    if academic_session == None:
336                        return _(
337                            u'Session configuration object is not available.'
338                            ), None
339                    amount = getattr(certificate, 'school_fee_2', 0.0)
340                elif student.is_postgrad and student.state == PAID:
341                    # Returning postgraduate students also pay for the
342                    # next session but their level always remains the
343                    # same.
344                    p_session += 1
345                    academic_session = self._getSessionConfiguration(p_session)
346                    if academic_session == None:
347                        return _(
348                            u'Session configuration object is not available.'
349                            ), None
350                    amount = getattr(certificate, 'school_fee_2', 0.0)
351        elif category == 'clearance':
352            try:
353                p_item = student['studycourse'].certificate.code
354            except (AttributeError, TypeError):
355                return _('Study course data are incomplete.'), None
356            amount = academic_session.clearance_fee
357        elif category == 'combi' and combi:
358            categories = getUtility(IKofaUtils).COMBI_PAYMENT_CATEGORIES
359            for cat in combi:
360                fee_name = cat + '_fee'
361                cat_amount = getattr(academic_session, fee_name, 0.0)
362                if not cat_amount:
363                    return _('%s undefined.' % categories[cat]), None
364                amount += cat_amount
365                p_item += u'%s + ' % categories[cat]
366            p_item = p_item.strip(' + ')
367        else:
368            fee_name = category + '_fee'
369            amount = getattr(academic_session, fee_name, 0.0)
370        if amount in (0.0, None):
371            return _('Amount could not be determined.'), None
372        if self.samePaymentMade(student, category, p_item, p_session):
373            return _('This type of payment has already been made.'), None
374        if self._isPaymentDisabled(p_session, category, student):
375            return _('This category of payments has been disabled.'), None
376        payment = createObject(u'waeup.StudentOnlinePayment')
377        timestamp = ("%d" % int(time()*10000))[1:]
378        payment.p_id = "p%s" % timestamp
379        payment.p_category = category
380        payment.p_item = p_item
381        payment.p_session = p_session
382        payment.p_level = p_level
383        payment.p_current = p_current
384        payment.amount_auth = amount
385        payment.p_combi = combi
386        return None, payment
387
388
389    def setPaymentDetails(self, category, student,
390            previous_session, previous_level, combi):
391        """Create Payment object and set the payment data of a student for
392        the payment category specified.
393
394        """
395        if grok.getSite().__name__ == 'uniben-cdl':
396            return self.setCDLPaymentDetails(category, student,
397                previous_session, previous_level, combi)
398        p_item = u''
399        amount = 0.0
400        if previous_session:
401            if previous_session < student['studycourse'].entry_session:
402                return _('The previous session must not fall below '
403                         'your entry session.'), None
404            if category == 'schoolfee':
405                # School fee is always paid for the following session
406                if previous_session > student['studycourse'].current_session:
407                    return _('This is not a previous session.'), None
408            else:
409                if previous_session > student['studycourse'].current_session - 1:
410                    return _('This is not a previous session.'), None
411            p_session = previous_session
412            p_level = previous_level
413            p_current = False
414        else:
415            p_session = student['studycourse'].current_session
416            p_level = student['studycourse'].current_level
417            p_current = True
418        academic_session = self._getSessionConfiguration(p_session)
419        if academic_session == None:
420            return _(u'Session configuration object is not available.'), None
421        # Determine fee.
422        if category.startswith('pharmd') \
423            and student.current_mode == 'special_ft':
424            amount = 80000.0
425        elif category == 'plag_test':
426            amount = 2500.0
427            if student.is_postgrad:
428                amount = 5000.0
429        #elif category == 'develop' and student.is_postgrad:
430        #    amount = academic_session.development_fee
431        elif category == 'bed_allocation':
432            acco_details = self.getAccommodationDetails(student)
433            p_session = acco_details['booking_session']
434            p_item = acco_details['bt']
435            desired_hostel = student['accommodation'].desired_hostel
436            if not desired_hostel:
437                return _(u'Select your favoured hostel first.'), None
438            if desired_hostel and desired_hostel != 'no':
439                p_item = u'%s (%s)' % (p_item, desired_hostel)
440            amount = academic_session.booking_fee
441            if student.is_postgrad:
442                amount += 500
443        elif category == 'hostel_maintenance':
444            amount = 0.0
445            booking_session = grok.getSite()['hostels'].accommodation_session
446            bedticket = student['accommodation'].get(str(booking_session), None)
447            if bedticket is not None and bedticket.bed is not None:
448                p_item = bedticket.bed_coordinates
449                p_session = booking_session
450                if bedticket.bed.__parent__.maint_fee > 0:
451                    amount = bedticket.bed.__parent__.maint_fee
452                else:
453                    # fallback
454                    amount = academic_session.maint_fee
455            else:
456                return _(u'No bed allocated.'), None
457        #elif category == 'hostel_application':
458        #    amount = 1000.0
459        #elif category.startswith('tempmaint'):
460        #    if not self._hostelApplicationPaymentMade(
461        #        student, student.current_session):
462        #        return _(
463        #            'You have not yet paid the hostel application fee.'), None
464        #    if category == 'tempmaint_1':
465        #        amount = 8150.0
466        #    elif category == 'tempmaint_2':
467        #        amount = 12650.0
468        #    elif category == 'tempmaint_3':
469        #        amount = 9650.0
470        elif category == 'clearance':
471            p_item = student.certcode
472            if p_item is None:
473                return _('Study course data are incomplete.'), None
474            if student.is_jupeb:
475                amount = 50000.0
476            elif student.faccode.startswith('FCETA'):
477                # ASABA and AKOKA
478                amount = 35000.0
479            elif student.depcode == 'DMIC':
480                amount = 70000.0
481            elif student.faccode in ('BMS', 'MED', 'DEN') \
482                and not student.is_postgrad:
483                amount = 80000.0
484            elif student.faccode == 'DCOEM':
485                return _('Acceptance fee payment not necessary.'), None
486            else:
487                amount = 60000.0
488        elif category == 'schoolfee':
489            try:
490                certificate = student['studycourse'].certificate
491                p_item = certificate.code
492            except (AttributeError, TypeError):
493                return _('Study course data are incomplete.'), None
494            try:
495                if student.entry_session < 2017:
496                    schoolfees_dict = SCHOOLFEES[12][p_item]
497                elif student.entry_session < 2020:
498                    schoolfees_dict = SCHOOLFEES[17][p_item]
499                elif student.entry_session < 2022:
500                    schoolfees_dict = SCHOOLFEES[20][p_item]
501                elif student.entry_session < 2023:
502                    schoolfees_dict = SCHOOLFEES[22][p_item]
503                else:
504                    schoolfees_dict = SCHOOLFEES[23][p_item]
505            except KeyError:
506                return _('School fee not yet fixed: p_item = %s' % p_item), None
507            if previous_session:
508                # Students can pay for previous sessions in all workflow states.
509                # Fresh students are excluded by the update method of the
510                # PreviousPaymentAddFormPage.
511                if previous_session == student['studycourse'].entry_session:
512                    if student.is_foreigner:
513                        amount = schoolfees_dict['initial_foreigner']
514                    else:
515                        amount = schoolfees_dict['initial']
516                else:
517                    if student.is_foreigner:
518                        amount = schoolfees_dict['returning_foreigner']
519                    else:
520                        amount = schoolfees_dict['returning']
521            else:
522                if student.state == CLEARED:
523                    if student.is_foreigner:
524                        amount = schoolfees_dict['initial_foreigner']
525                    else:
526                        amount = schoolfees_dict['initial']
527                elif student.state == PAID and student.is_postgrad:
528                    p_session += 1
529                    academic_session = self._getSessionConfiguration(p_session)
530                    if academic_session == None:
531                        return _(u'Session configuration object is not available.'), None
532                    if student.is_foreigner:
533                        amount = schoolfees_dict['returning_foreigner']
534                    else:
535                        amount = schoolfees_dict['returning']
536                elif student.state == RETURNING:
537                    # In case of returning school fee payment the payment session
538                    # and level contain the values of the session the student
539                    # has paid for.
540                    p_session, p_level = self.getReturningData(student)
541                    academic_session = self._getSessionConfiguration(p_session)
542                    if academic_session == None:
543                        return _(u'Session configuration object is not available.'), None
544
545                    # Students are only allowed to pay for the next session
546                    # if current session payment has really been made,
547                    # i.e. payment object exists and is paid.
548                    #if not self._paymentMade(
549                    #    student, student.current_session):
550                    #    return _('You have not yet paid your current/active' +
551                    #             ' session. Please use the previous session' +
552                    #             ' payment form first.'), None
553
554                    if student.is_foreigner:
555                        amount = schoolfees_dict['returning_foreigner']
556                    else:
557                        amount = schoolfees_dict['returning']
558                # PHARMD school fee amount is fixed and previously paid
559                # installments in current session are deducted.
560                if student.current_mode == 'special_ft' \
561                    and student.state in (RETURNING, CLEARED):
562                    if student.is_foreigner:
563                        amount = 260000.0 - self._pharmdInstallments(student)
564                    else:
565                        amount = 160000.0 - self._pharmdInstallments(student)
566            try:
567                amount = float(amount)
568            except ValueError:
569                return _(u'School fee not yet fixed: p_item = %s' % p_item), None
570            # Give 50% school fee discount to staff members.
571            if student.is_staff:
572                amount /= 2
573
574        else:
575            fee_name = category + '_fee'
576            amount = getattr(academic_session, fee_name, 0.0)
577        if amount in (0.0, None):
578            return _('Amount could not be determined.'), None
579        # Add session specific penalty fee.
580        if category == 'schoolfee' and student.is_postgrad:
581            amount += academic_session.penalty_pg
582            amount += academic_session.development_fee
583        elif category == 'schoolfee' and student.current_mode == ('ug_ft'):
584            amount += academic_session.penalty_ug_ft
585        elif category == 'schoolfee' and student.current_mode == ('ug_pt'):
586            amount += academic_session.penalty_ug_pt
587        elif category == 'schoolfee' and student.current_mode == ('ug_sw'):
588            amount += academic_session.penalty_sw
589        elif category == 'schoolfee' and student.current_mode in (
590            'dp_ft', 'dp_pt'):
591            amount += academic_session.penalty_dp
592        if category.startswith('tempmaint'):
593            p_item = getUtility(IKofaUtils).PAYMENT_CATEGORIES[category]
594            p_item = unicode(p_item)
595            # Now we change the category because tempmaint payments
596            # will be obsolete when Uniben returns to Kofa bed allocation.
597            category = 'hostel_maintenance'
598        # Create ticket.
599        if self.samePaymentMade(student, category, p_item, p_session):
600            return _('This type of payment has already been made.'), None
601        if self._isPaymentDisabled(p_session, category, student):
602            return _('This category of payments has been disabled.'), None
603        payment = createObject(u'waeup.StudentOnlinePayment')
604        timestamp = ("%d" % int(time()*10000))[1:]
605        payment.p_id = "p%s" % timestamp
606        payment.p_category = category
607        payment.p_item = p_item
608        payment.p_session = p_session
609        payment.p_level = p_level
610        payment.p_current = p_current
611        payment.amount_auth = amount
612        return None, payment
613
614    def warnCreditsOOR(self, studylevel, course=None):
615        studycourse = studylevel.__parent__
616        certificate = getattr(studycourse,'certificate', None)
617        current_level = studycourse.current_level
618        if None in (current_level, certificate):
619            return
620        if studylevel.__parent__.previous_verdict == 'R':
621            return
622        end_level = certificate.end_level
623        if studylevel.student.faccode in (
624            'MED', 'DEN', 'BMS') and studylevel.level == 200:
625            limit = 61
626        elif current_level >= end_level:
627            limit = 51
628        else:
629            limit = 50
630        if course and studylevel.total_credits + course.credits > limit:
631            return _('Maximum credits exceeded.')
632        elif studylevel.total_credits > limit:
633            return _('Maximum credits exceeded.')
634        return
635
636    def warnCourseAlreadyPassed(self, studylevel, course):
637        """Return message if course has already been passed at
638        previous levels.
639        """
640
641        # med: we may need to put this matter on hold and allow
642        # all the students to register.
643        return False
644
645        previous_verdict = studylevel.__parent__.previous_verdict
646        if previous_verdict in ('C', 'M'):
647            return False
648        for slevel in studylevel.__parent__.values():
649            for cticket in slevel.values():
650                if cticket.code == course.code \
651                    and cticket.total_score >= cticket.passmark:
652                    return _('Course has already been passed at previous level.')
653        return False
654
655    def clearance_disabled_message(self, student):
656        if student.is_postgrad:
657            return None
658        try:
659            session_config = grok.getSite()[
660                'configuration'][str(student.current_session)]
661        except KeyError:
662            return _('Session configuration object is not available.')
663        if not session_config.clearance_enabled:
664            return _('Clearance is disabled for this session.')
665        return None
666
667    def renderPDFTranscript(self, view, filename='transcript.pdf',
668                  student=None,
669                  studentview=None,
670                  note=None,
671                  signatures=(),
672                  sigs_in_footer=(),
673                  digital_sigs=(),
674                  show_scans=True, topMargin=1.5,
675                  omit_fields=(),
676                  tableheader=None,
677                  no_passport=False,
678                  save_file=False):
679        """Render pdf slip of a transcripts.
680        """
681        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
682        # XXX: tell what the different parameters mean
683        style = getSampleStyleSheet()
684        creator = self.getPDFCreator(student)
685        data = []
686        doc_title = view.label
687        author = '%s (%s)' % (view.request.principal.title,
688                              view.request.principal.id)
689        footer_text = view.label.split('\n')
690        if len(footer_text) > 2:
691            # We can add a department in first line
692            footer_text = footer_text[1]
693        else:
694            # Only the first line is used for the footer
695            footer_text = footer_text[0]
696        if getattr(student, 'student_id', None) is not None:
697            footer_text = "%s - %s - " % (student.student_id, footer_text)
698
699        # Insert student data table
700        if student is not None:
701            #bd_translation = trans(_('Base Data'), portal_language)
702            #data.append(Paragraph(bd_translation, HEADING_STYLE))
703            data.append(render_student_data(
704                studentview, view.context,
705                omit_fields, lang=portal_language,
706                slipname=filename,
707                no_passport=no_passport))
708
709        transcript_data = view.context.getTranscriptData()
710        levels_data = transcript_data[0]
711
712        contextdata = []
713        f_label = trans(_('Course of Study:'), portal_language)
714        f_label = Paragraph(f_label, ENTRY1_STYLE)
715        f_text = formatted_text(view.context.certificate.longtitle)
716        f_text = Paragraph(f_text, ENTRY1_STYLE)
717        contextdata.append([f_label,f_text])
718
719        f_label = trans(_('Faculty:'), portal_language)
720        f_label = Paragraph(f_label, ENTRY1_STYLE)
721        f_text = formatted_text(
722            view.context.certificate.__parent__.__parent__.__parent__.longtitle)
723        f_text = Paragraph(f_text, ENTRY1_STYLE)
724        contextdata.append([f_label,f_text])
725
726        f_label = trans(_('Department:'), portal_language)
727        f_label = Paragraph(f_label, ENTRY1_STYLE)
728        f_text = formatted_text(
729            view.context.certificate.__parent__.__parent__.longtitle)
730        f_text = Paragraph(f_text, ENTRY1_STYLE)
731        contextdata.append([f_label,f_text])
732
733        f_label = trans(_('Entry Session:'), portal_language)
734        f_label = Paragraph(f_label, ENTRY1_STYLE)
735        f_text = formatted_text(
736            view.session_dict.get(view.context.entry_session))
737        f_text = Paragraph(f_text, ENTRY1_STYLE)
738        contextdata.append([f_label,f_text])
739
740        f_label = trans(_('Final Session:'), portal_language)
741        f_label = Paragraph(f_label, ENTRY1_STYLE)
742        f_text = formatted_text(
743            view.session_dict.get(view.context.current_session))
744        f_text = Paragraph(f_text, ENTRY1_STYLE)
745        contextdata.append([f_label,f_text])
746
747        f_label = trans(_('Entry Mode:'), portal_language)
748        f_label = Paragraph(f_label, ENTRY1_STYLE)
749        f_text = formatted_text(view.studymode_dict.get(
750            view.context.entry_mode))
751        f_text = Paragraph(f_text, ENTRY1_STYLE)
752        contextdata.append([f_label,f_text])
753
754        f_label = trans(_('Final Verdict:'), portal_language)
755        f_label = Paragraph(f_label, ENTRY1_STYLE)
756        f_text = formatted_text(view.studymode_dict.get(
757            view.context.current_verdict))
758        f_text = Paragraph(f_text, ENTRY1_STYLE)
759        contextdata.append([f_label,f_text])
760
761        f_label = trans(_('Cumulative GPA:'), portal_language)
762        f_label = Paragraph(f_label, ENTRY1_STYLE)
763        format_float = getUtility(IKofaUtils).format_float
764        cgpa = format_float(transcript_data[1], 3)
765        if student.state == GRADUATED:
766            f_text = formatted_text('%s (%s)' % (
767                cgpa, self.getClassFromCGPA(transcript_data[1], student)[1]))
768        else:
769            f_text = formatted_text('%s' % cgpa)
770            if not transcript_data[1]:
771                f_text = formatted_text('No results yet!')
772        f_text = Paragraph(f_text, ENTRY1_STYLE)
773        contextdata.append([f_label,f_text])
774
775        contexttable = Table(contextdata,style=SLIP_STYLE)
776        data.append(contexttable)
777
778        if transcript_data[1]:
779            transcripttables = render_transcript_data(
780                view, tableheader, levels_data, lang=portal_language)
781            data.extend(transcripttables)
782
783        # Insert signatures
784        # XXX: We are using only sigs_in_footer in waeup.kofa, so we
785        # do not have a test for the following lines.
786        if signatures and not sigs_in_footer:
787            data.append(Spacer(1, 20))
788            # Render one signature table per signature to
789            # get date and signature in line.
790            for signature in signatures:
791                signaturetables = get_signature_tables(signature)
792                data.append(signaturetables[0])
793
794        # Insert digital signatures
795        if digital_sigs:
796            data.append(Spacer(1, 20))
797            sigs = digital_sigs.split('\n')
798            for sig in sigs:
799                data.append(Paragraph(sig, NOTE_STYLE))
800
801        view.response.setHeader(
802            'Content-Type', 'application/pdf')
803        try:
804            pdf_stream = creator.create_pdf(
805                data, None, doc_title, author=author, footer=footer_text,
806                note=note, sigs_in_footer=sigs_in_footer, topMargin=topMargin,
807                view=view)
808        except IOError:
809            view.flash(_('Error in image file.'))
810            return view.redirect(view.url(view.context))
811        if save_file:
812            self._saveTranscriptPDF(student, pdf_stream)
813            return
814        return pdf_stream
815
816    #: A tuple containing the names of registration states in which changing of
817    #: passport pictures is allowed.
818    PORTRAIT_CHANGE_STATES = (CLEARANCE, REQUESTED)
819
820    #: A tuple containing the names of registration states in which changing of
821    #: scanned signatures is allowed.
822    SIGNATURE_CHANGE_STATES = (CLEARED, RETURNING, PAID, REGISTERED, VALIDATED, )
823
824    # Uniben prefix
825    @property
826    def STUDENT_ID_PREFIX(self):
827        if grok.getSite().__name__ == 'uniben-cdl':
828            return u'C'
829        return u'B'
830
831    STUDENT_EXPORTER_NAMES = (
832            'students',
833            'studentstudycourses',
834            'studentstudycourses_1',
835            'studentstudylevels',
836            #'studentstudylevels_1',
837            'coursetickets',
838            #'coursetickets_1',
839            'studentpayments',
840            'bedtickets',
841            'trimmed',
842            'outstandingcourses',
843            'unpaidpayments',
844            'sfpaymentsoverview',
845            'sessionpaymentsoverview',
846            'studylevelsoverview',
847            'combocard',
848            'bursary',
849            'accommodationpayments',
850            'transcriptdata',
851            'trimmedpayments',
852            'medicalhistory',
853            'nysc',
854            )
Note: See TracBrowser for help on using the repository browser.