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

Last change on this file since 18128 was 18128, checked in by Henrik Bettermann, 22 hours ago

PHD students pay more.

  • Property svn:keywords set to Id
File size: 39.7 KB
Line 
1## $Id: utils.py 18128 2025-07-17 13:53: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 = 6000.0
434            if student.is_postgrad:
435                amount = 10000.0
436            if student.current_mode == 'pgphd':
437                amount = 20000.0
438        #elif category == 'develop' and student.is_postgrad:
439        #    amount = academic_session.development_fee
440        elif category == 'bed_allocation':
441            acco_details = self.getAccommodationDetails(student)
442            p_session = acco_details['booking_session']
443            p_item = acco_details['bt']
444            desired_hostel = student['accommodation'].desired_hostel
445            if not desired_hostel:
446                return _(u'Select your favoured hostel first.'), None
447            if desired_hostel and desired_hostel != 'no':
448                p_item = u'%s (%s)' % (p_item, desired_hostel)
449            amount = academic_session.booking_fee
450            if student.is_postgrad:
451                amount += 1000
452        elif category == 'hostel_maintenance':
453            amount = 0.0
454            booking_session = grok.getSite()['hostels'].accommodation_session
455            bedticket = student['accommodation'].get(str(booking_session), None)
456            if bedticket is not None and bedticket.bed is not None:
457                p_item = bedticket.bed_coordinates
458                p_session = booking_session
459                if bedticket.bed.__parent__.maint_fee > 0:
460                    amount = bedticket.bed.__parent__.maint_fee
461                else:
462                    # fallback
463                    amount = academic_session.maint_fee
464            else:
465                return _(u'No bed allocated.'), None
466        #elif category == 'hostel_application':
467        #    amount = 1000.0
468        #elif category.startswith('tempmaint'):
469        #    if not self._hostelApplicationPaymentMade(
470        #        student, student.current_session):
471        #        return _(
472        #            'You have not yet paid the hostel application fee.'), None
473        #    if category == 'tempmaint_1':
474        #        amount = 8150.0
475        #    elif category == 'tempmaint_2':
476        #        amount = 12650.0
477        #    elif category == 'tempmaint_3':
478        #        amount = 9650.0
479        elif category == 'clearance':
480            p_item = student.certcode
481            if p_item is None:
482                return _('Study course data are incomplete.'), None
483            if student.is_jupeb:
484                amount = 50000.0
485            elif student.faccode.startswith('FCETA'):
486                # ASABA and AKOKA
487                amount = 50000.0
488            elif student.depcode == 'DMIC':
489                amount = 70000.0
490            elif student.faccode in ('BMS', 'MED', 'DEN') \
491                and not student.is_postgrad:
492                amount = 80000.0
493            elif student.faccode == 'DCOEM':
494                return _('Acceptance fee payment not necessary.'), None
495            else:
496                amount = 60000.0
497        elif student.is_jupeb:
498            try:
499                certificate = student['studycourse'].certificate
500                p_item = certificate.code
501            except (AttributeError, TypeError):
502                return _('Study course data are incomplete.'), None
503            if student.state == CLEARED and category == 'schoolfee':
504                amount = 220000.0
505            if student.state == CLEARED and category == 'schoolfee_1':
506                amount = 110000.0
507            elif category == 'secondinstall':
508                amount = 110000.0
509        elif category == 'schoolfee':
510            try:
511                certificate = student['studycourse'].certificate
512                p_item = certificate.code
513            except (AttributeError, TypeError):
514                return _('Study course data are incomplete.'), None
515            try:
516                if student.entry_session < 2017:
517                    schoolfees_dict = SCHOOLFEES[12][p_item]
518                elif student.entry_session < 2020:
519                    schoolfees_dict = SCHOOLFEES[17][p_item]
520                elif student.entry_session < 2022:
521                    schoolfees_dict = SCHOOLFEES[20][p_item]
522                elif student.entry_session < 2023:
523                    schoolfees_dict = SCHOOLFEES[22][p_item]
524                elif student.entry_session < 2024:
525                    schoolfees_dict = SCHOOLFEES[23][p_item]
526                else:
527                    schoolfees_dict = SCHOOLFEES[24][p_item]
528            except KeyError:
529                return _('School fee not yet fixed: p_item = %s' % p_item), None
530            if previous_session:
531                # Students can pay for previous sessions in all workflow states.
532                # Fresh students are excluded by the update method of the
533                # PreviousPaymentAddFormPage.
534                if previous_session == student['studycourse'].entry_session:
535                    if student.is_foreigner:
536                        amount = schoolfees_dict['initial_foreigner']
537                    else:
538                        amount = schoolfees_dict['initial']
539                else:
540                    if student.is_foreigner:
541                        amount = schoolfees_dict['returning_foreigner']
542                    else:
543                        amount = schoolfees_dict['returning']
544            else:
545                if student.state == CLEARED:
546                    if student.is_foreigner:
547                        amount = schoolfees_dict['initial_foreigner']
548                    else:
549                        amount = schoolfees_dict['initial']
550                elif student.state == PAID and student.is_postgrad:
551                    p_session += 1
552                    academic_session = self._getSessionConfiguration(p_session)
553                    if academic_session == None:
554                        return _(u'Session configuration object is not available.'), None
555                    if student.is_foreigner:
556                        amount = schoolfees_dict['returning_foreigner']
557                    else:
558                        amount = schoolfees_dict['returning']
559                elif student.state == RETURNING:
560                    # In case of returning school fee payment the payment session
561                    # and level contain the values of the session the student
562                    # has paid for.
563                    p_session, p_level = self.getReturningData(student)
564                    academic_session = self._getSessionConfiguration(p_session)
565                    if academic_session == None:
566                        return _(u'Session configuration object is not available.'), None
567
568                    # Students are only allowed to pay for the next session
569                    # if current session payment has really been made,
570                    # i.e. payment object exists and is paid.
571                    #if not self._paymentMade(
572                    #    student, student.current_session):
573                    #    return _('You have not yet paid your current/active' +
574                    #             ' session. Please use the previous session' +
575                    #             ' payment form first.'), None
576
577                    if student.is_foreigner:
578                        amount = schoolfees_dict['returning_foreigner']
579                    else:
580                        amount = schoolfees_dict['returning']
581                # PHARMD school fee amount is fixed and previously paid
582                # installments in current session are deducted.
583                if student.current_mode == 'special_ft' \
584                    and student.state in (RETURNING, CLEARED):
585                    if student.is_foreigner:
586                        amount = 260000.0 - self._pharmdInstallments(student)
587                    else:
588                        amount = 160000.0 - self._pharmdInstallments(student)
589            try:
590                amount = float(amount)
591            except ValueError:
592                return _(u'School fee not yet fixed: p_item = %s' % p_item), None
593            # Give 50% school fee discount to staff members.
594            if student.is_staff:
595                amount /= 2
596            # Add additional school fees
597            if student.state == CLEARED and getattr(certificate, 'school_fee_3'):
598                amount += getattr(certificate, 'school_fee_3')
599            elif student.state == PAID and student.is_postgrad and getattr(
600                                                    certificate, 'school_fee_4'):
601                amount += getattr(certificate, 'school_fee_4')
602            elif student.state == RETURNING and getattr(
603                                                    certificate, 'school_fee_4'):
604                amount += getattr(certificate, 'school_fee_4')
605        else:
606            fee_name = category + '_fee'
607            amount = getattr(academic_session, fee_name, 0.0)
608        if amount in (0.0, None):
609            return _('Amount could not be determined.'), None
610        # Add session specific penalty fee.
611        if category == 'schoolfee' and student.is_postgrad:
612            amount += academic_session.penalty_pg
613            amount += academic_session.development_fee
614        elif category == 'schoolfee' and student.current_mode == ('ug_ft'):
615            amount += academic_session.penalty_ug_ft
616        elif category == 'schoolfee' and student.current_mode == ('ug_pt'):
617            amount += academic_session.penalty_ug_pt
618        elif category == 'schoolfee' and student.current_mode == ('ug_sw'):
619            amount += academic_session.penalty_sw
620        elif category == 'schoolfee' and student.current_mode in (
621            'dp_ft', 'dp_pt'):
622            amount += academic_session.penalty_dp
623        if category.startswith('tempmaint'):
624            p_item = getUtility(IKofaUtils).PAYMENT_CATEGORIES[category]
625            p_item = unicode(p_item)
626            # Now we change the category because tempmaint payments
627            # will be obsolete when Uniben returns to Kofa bed allocation.
628            category = 'hostel_maintenance'
629        # Create ticket.
630        if self.samePaymentMade(student, category, p_item, p_session):
631            return _('This type of payment has already been made.'), None
632        if self._isPaymentDisabled(p_session, category, student):
633            return _('This category of payments has been disabled.'), None
634        payment = createObject(u'waeup.StudentOnlinePayment')
635        timestamp = ("%d" % int(time()*10000))[1:]
636        payment.p_id = "p%s" % timestamp
637        payment.p_category = category
638        payment.p_item = p_item
639        payment.p_session = p_session
640        payment.p_level = p_level
641        payment.p_current = p_current
642        payment.amount_auth = amount
643        return None, payment
644
645    def warnCreditsOOR(self, studylevel, course=None):
646        studycourse = studylevel.__parent__
647        certificate = getattr(studycourse,'certificate', None)
648        current_level = studycourse.current_level
649        if None in (current_level, certificate):
650            return
651        if studylevel.__parent__.previous_verdict == 'R':
652            return
653        end_level = certificate.end_level
654        if studylevel.student.faccode in (
655            'MED', 'DEN', 'VNM') and studylevel.level >= 200:
656            limit = 76
657        elif current_level >= end_level:
658            if studycourse.current_verdict == 'R':
659                limit = 999
660            else:
661                limit = 53
662        else:
663            limit = 51
664        if course and studylevel.total_credits + course.credits > limit:
665            return _('Maximum credits exceeded.')
666        elif studylevel.total_credits > limit:
667            return _('Maximum credits exceeded.')
668        return
669
670    def warnCourseAlreadyPassed(self, studylevel, course):
671        """Return message if course has already been passed at
672        previous levels.
673        """
674
675        # med: we may need to put this matter on hold and allow
676        # all the students to register.
677        return False
678
679        previous_verdict = studylevel.__parent__.previous_verdict
680        if previous_verdict in ('C', 'M'):
681            return False
682        for slevel in studylevel.__parent__.values():
683            for cticket in slevel.values():
684                if cticket.code == course.code \
685                    and cticket.total_score >= cticket.passmark:
686                    return _('Course has already been passed at previous level.')
687        return False
688
689    def clearance_disabled_message(self, student):
690        if student.is_postgrad:
691            return None
692        try:
693            session_config = grok.getSite()[
694                'configuration'][str(student.current_session)]
695        except KeyError:
696            return _('Session configuration object is not available.')
697        if not session_config.clearance_enabled:
698            return _('Clearance is disabled for this session.')
699        return None
700
701    def renderPDFTranscript(self, view, filename='transcript.pdf',
702                  student=None,
703                  studentview=None,
704                  note=None,
705                  signatures=(),
706                  sigs_in_footer=(),
707                  digital_sigs=(),
708                  show_scans=True, topMargin=1.5,
709                  omit_fields=(),
710                  tableheader=None,
711                  no_passport=False,
712                  save_file=False):
713        """Render pdf slip of a transcripts.
714        """
715        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
716        # XXX: tell what the different parameters mean
717        style = getSampleStyleSheet()
718        creator = self.getPDFCreator(student)
719        data = []
720        doc_title = view.label
721        author = '%s (%s)' % (view.request.principal.title,
722                              view.request.principal.id)
723        footer_text = view.label.split('\n')
724        if len(footer_text) > 2:
725            # We can add a department in first line
726            footer_text = footer_text[1]
727        else:
728            # Only the first line is used for the footer
729            footer_text = footer_text[0]
730        if getattr(student, 'student_id', None) is not None:
731            footer_text = "%s - %s - " % (student.student_id, footer_text)
732
733        # Insert student data table
734        if student is not None:
735            #bd_translation = trans(_('Base Data'), portal_language)
736            #data.append(Paragraph(bd_translation, HEADING_STYLE))
737            data.append(render_student_data(
738                studentview, view.context,
739                omit_fields, lang=portal_language,
740                slipname=filename,
741                no_passport=no_passport))
742
743        transcript_data = view.context.getTranscriptData()
744        levels_data = transcript_data[0]
745
746        contextdata = []
747        f_label = trans(_('Course of Study:'), portal_language)
748        f_label = Paragraph(f_label, ENTRY1_STYLE)
749        f_text = formatted_text(view.context.certificate.longtitle)
750        f_text = Paragraph(f_text, ENTRY1_STYLE)
751        contextdata.append([f_label,f_text])
752
753        f_label = trans(_('Faculty:'), portal_language)
754        f_label = Paragraph(f_label, ENTRY1_STYLE)
755        f_text = formatted_text(
756            view.context.certificate.__parent__.__parent__.__parent__.longtitle)
757        f_text = Paragraph(f_text, ENTRY1_STYLE)
758        contextdata.append([f_label,f_text])
759
760        f_label = trans(_('Department:'), portal_language)
761        f_label = Paragraph(f_label, ENTRY1_STYLE)
762        f_text = formatted_text(
763            view.context.certificate.__parent__.__parent__.longtitle)
764        f_text = Paragraph(f_text, ENTRY1_STYLE)
765        contextdata.append([f_label,f_text])
766
767        f_label = trans(_('Entry Session:'), portal_language)
768        f_label = Paragraph(f_label, ENTRY1_STYLE)
769        f_text = formatted_text(
770            view.session_dict.get(view.context.entry_session))
771        f_text = Paragraph(f_text, ENTRY1_STYLE)
772        contextdata.append([f_label,f_text])
773
774        f_label = trans(_('Final Session:'), portal_language)
775        f_label = Paragraph(f_label, ENTRY1_STYLE)
776        f_text = formatted_text(
777            view.session_dict.get(view.context.current_session))
778        f_text = Paragraph(f_text, ENTRY1_STYLE)
779        contextdata.append([f_label,f_text])
780
781        f_label = trans(_('Entry Mode:'), portal_language)
782        f_label = Paragraph(f_label, ENTRY1_STYLE)
783        f_text = formatted_text(view.studymode_dict.get(
784            view.context.entry_mode))
785        f_text = Paragraph(f_text, ENTRY1_STYLE)
786        contextdata.append([f_label,f_text])
787
788        f_label = trans(_('Class of Degree:'), portal_language)
789        f_label = Paragraph(f_label, ENTRY1_STYLE)
790        f_text = formatted_text(view.verdict_dict.get(
791            view.context.current_verdict))
792        f_text = Paragraph(f_text, ENTRY1_STYLE)
793        contextdata.append([f_label,f_text])
794
795        f_label = trans(_('Cumulative GPA:'), portal_language)
796        f_label = Paragraph(f_label, ENTRY1_STYLE)
797        format_float = getUtility(IKofaUtils).format_float
798        cgpa = format_float(transcript_data[1], 3)
799        if student.state == GRADUATED:
800            f_text = formatted_text('%s (%s)' % (
801                cgpa, self.getClassFromCGPA(transcript_data[1], student)[1]))
802        else:
803            f_text = formatted_text('%s' % cgpa)
804            if not transcript_data[1]:
805                f_text = formatted_text('No results yet!')
806        f_text = Paragraph(f_text, ENTRY1_STYLE)
807        contextdata.append([f_label,f_text])
808
809        contexttable = Table(contextdata,style=SLIP_STYLE)
810        data.append(contexttable)
811
812        if transcript_data[1]:
813            transcripttables = render_transcript_data(
814                view, tableheader, levels_data, lang=portal_language)
815            data.extend(transcripttables)
816
817        # Insert signatures
818        # XXX: We are using only sigs_in_footer in waeup.kofa, so we
819        # do not have a test for the following lines.
820        if signatures and not sigs_in_footer:
821            data.append(Spacer(1, 20))
822            # Render one signature table per signature to
823            # get date and signature in line.
824            for signature in signatures:
825                signaturetables = get_signature_tables(signature)
826                data.append(signaturetables[0])
827
828        # Insert digital signatures
829        if digital_sigs:
830            data.append(Spacer(1, 20))
831            sigs = digital_sigs.split('\n')
832            for sig in sigs:
833                data.append(Paragraph(sig, NOTE_STYLE))
834
835        view.response.setHeader(
836            'Content-Type', 'application/pdf')
837        view.response.setHeader(
838            'Content-Disposition:', 'attachment; filename="%s' % filename)
839       
840        try:
841            pdf_stream = creator.create_pdf(
842                data, None, doc_title, author=author, footer=footer_text,
843                note=note, sigs_in_footer=sigs_in_footer, topMargin=topMargin,
844                view=view)
845        except IOError:
846            view.flash(_('Error in image file.'))
847            return view.redirect(view.url(view.context))
848        if save_file:
849            self._saveTranscriptPDF(student, pdf_stream)
850            return
851        return pdf_stream
852
853    #: A tuple containing the names of registration states in which changing of
854    #: passport pictures is allowed.
855    PORTRAIT_CHANGE_STATES = (CLEARANCE, ADMITTED, REQUESTED)
856
857    def allowPortraitChange(self, student):
858        """Check if student is allowed to change the portrait file.
859        """
860        if student.is_jupeb:
861            return False
862        if student.state not in self.PORTRAIT_CHANGE_STATES:
863            return False
864        return True
865
866    #: A tuple containing the names of registration states in which changing of
867    #: scanned signatures is allowed.
868    SIGNATURE_CHANGE_STATES = (CLEARED, RETURNING, PAID, REGISTERED, VALIDATED, )
869
870    def final_clearance_enabled(self, student):
871        studycourse = student['studycourse']
872        certificate = getattr(studycourse,'certificate',None)
873        current_level = studycourse.current_level
874        if None in (current_level, certificate):
875            return False
876        if student.is_postgrad:
877            return True
878        end_level = certificate.end_level
879        if current_level >= end_level:
880            return True
881        return False
882
883    # Uniben prefix
884    @property
885    def STUDENT_ID_PREFIX(self):
886        if grok.getSite().__name__ == 'uniben-cdl':
887            return u'C'
888        return u'B'
889
890    STUDENT_EXPORTER_NAMES = (
891            'students',
892            'studentstudycourses',
893            'studentstudycourses_1',
894            'studentstudylevels',
895            #'studentstudylevels_1',
896            'coursetickets',
897            #'coursetickets_1',
898            'studentpayments',
899            'bedtickets',
900            'trimmed',
901            'outstandingcourses',
902            'unpaidpayments',
903            'sfpaymentsoverview',
904            'sessionpaymentsoverview',
905            'studylevelsoverview',
906            'combocard',
907            'bursary',
908            'accommodationpayments',
909            'transcriptdata',
910            'trimmedpayments',
911            'medicalhistory',
912            'routingslip',
913            'nysc',
914            )
Note: See TracBrowser for help on using the repository browser.