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

Last change on this file since 17930 was 17930, checked in by Henrik Bettermann, 8 hours ago

2024 school fees

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