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

Last change on this file since 17875 was 17875, checked in by Henrik Bettermann, 6 weeks ago

Final clearance for final year students only.

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