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
RevLine 
[7419]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##
[11773]18import grok
[17457]19import os
20import csv
[8598]21from time import time
[16108]22from reportlab.platypus import Paragraph, Table
[9459]23from zope.component import createObject, getUtility
[16106]24from reportlab.lib.styles import getSampleStyleSheet
[16108]25from waeup.kofa.browser.pdf import ENTRY1_STYLE
[16359]26from waeup.kofa.interfaces import (IKofaUtils, ADMITTED, CLEARANCE,
[16445]27    CLEARED, REQUESTED, RETURNING, PAID, REGISTERED, VALIDATED, GRADUATED)
[13251]28from waeup.kofa.utils.helpers import to_timezone
[16108]29from waeup.kofa.students.utils import (
30    trans, render_student_data, formatted_text, render_transcript_data,
31    SLIP_STYLE)
[8821]32from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
[8020]33from waeup.uniben.interfaces import MessageFactory as _
[6902]34
[17457]35SCHOOLFEES = dict()
36
37schoolfees_path = os.path.join(
[17707]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(
[17457]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
[8821]69class CustomStudentsUtils(NigeriaStudentsUtils):
[7151]70    """A collection of customized methods.
71
72    """
73
[16382]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'),
[17255]87        'form.genotype': _(u'TISHIP Registration Form Data'),
[16382]88        }
89
[8270]90    def getReturningData(self, student):
91        """ This method defines what happens after school fee payment
[8319]92        of returning students depending on the student's senate verdict.
[8270]93        """
[8319]94        prev_level = student['studycourse'].current_level
95        cur_verdict = student['studycourse'].current_verdict
[16714]96        if cur_verdict == 'N' and prev_level in (100, 200):
[15482]97            new_level = prev_level
[17536]98        elif cur_verdict in ('A','B','M','N','Z',):
[8319]99            # Successful student
100            new_level = divmod(int(prev_level),100)[0]*100 + 100
[17536]101        elif cur_verdict in ('C', 'L',):
[8319]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
[8270]108        new_session = student['studycourse'].current_session + 1
109        return new_session, new_level
110
[17743]111    ACCOMMODATION_SPAN = 5
[13251]112
[13248]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.")
[13777]127        if not student.is_postgrad and student.current_mode != 'ug_ft':
[13446]128            return _("Only undergraduate full-time students are eligible to book accommodation.")
[13283]129        bt = acc_details.get('bt')
130        if not bt:
[13248]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.")
[17255]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.')
[13283]142        stage = bt.split('_')[2]
[13777]143        if not student.is_postgrad and stage != 'fr' and not student[
[14328]144            'studycourse'].previous_verdict in (
[15392]145                'A', 'B', 'F', 'J', 'L', 'M', 'C', 'Z'):
[13283]146            return _("Your are not eligible to book accommodation.")
[15307]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:
[13248]151            return _('You already booked a bed space in '
152                     'current accommodation session.')
153        return
[13244]154
[13295]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'
[13777]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'
[15354]191            desired_hostel = student['accommodation'].desired_hostel
192            if student.faccode in ('MED', 'DEN') and (
193                not desired_hostel or desired_hostel.startswith('clinical')):
[13777]194                special_handling = 'clinical'
195            elif student.certcode in ('BARTMAS', 'BARTTHR', 'BARTFAA',
[17324]196                                      'BAEDFAA', 'BSCEDECHED', 'BAFAA',
[17603]197                                      'BARTMUS', 'BSCEDECHED', 'BEDECHEDU'):
[13777]198                special_handling = 'ekenwan'
[13295]199        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
200        return d
201
[9520]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
[13566]211    def _isPaymentDisabled(self, p_session, category, student):
212        academic_session = self._getSessionConfiguration(p_session)
[14688]213        if category == 'schoolfee':
214            if 'sf_all' in academic_session.payment_disabled:
215                return True
[16516]216            if student.state == RETURNING and \
217                'sf_return' in academic_session.payment_disabled:
218                return True
[14688]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
[13566]238        if category == 'hostel_maintenance' and \
239            'maint_all' in academic_session.payment_disabled:
240            return True
[16025]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
[16374]247            if not student.is_jupeb and \
248                'cl_allexj' in academic_session.payment_disabled:
249                return True
[13566]250        return False
251
[13251]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
[12566]260
[14960]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
[15320]271    def samePaymentMade(self, student, category, p_item, p_session):
[15447]272        if category in ('bed_allocation', 'transcript'):
[15320]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
[17606]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
[9152]390    def setPaymentDetails(self, category, student,
[15666]391            previous_session, previous_level, combi):
[8598]392        """Create Payment object and set the payment data of a student for
393        the payment category specified.
394
395        """
[17606]396        if grok.getSite().__name__ == 'uniben-cdl':
397            return self.setCDLPaymentDetails(category, student,
398                previous_session, previous_level, combi)
[8598]399        p_item = u''
400        amount = 0.0
[9152]401        if previous_session:
[9520]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
[9152]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
[9520]419        academic_session = self._getSessionConfiguration(p_session)
420        if academic_session == None:
[8598]421            return _(u'Session configuration object is not available.'), None
[8676]422        # Determine fee.
[16873]423        if category.startswith('pharmd') \
[14960]424            and student.current_mode == 'special_ft':
425            amount = 80000.0
[16780]426        elif category == 'plag_test':
427            amount = 2500.0
428            if student.is_postgrad:
429                amount = 5000.0
[15108]430        #elif category == 'develop' and student.is_postgrad:
431        #    amount = academic_session.development_fee
[13251]432        elif category == 'bed_allocation':
[17236]433            acco_details = self.getAccommodationDetails(student)
434            p_session = acco_details['booking_session']
435            p_item = acco_details['bt']
[15268]436            desired_hostel = student['accommodation'].desired_hostel
[15314]437            if not desired_hostel:
438                return _(u'Select your favoured hostel first.'), None
439            if desired_hostel and desired_hostel != 'no':
[15268]440                p_item = u'%s (%s)' % (p_item, desired_hostel)
[13251]441            amount = academic_session.booking_fee
[15002]442            if student.is_postgrad:
443                amount += 500
[13251]444        elif category == 'hostel_maintenance':
445            amount = 0.0
[17236]446            booking_session = grok.getSite()['hostels'].accommodation_session
447            bedticket = student['accommodation'].get(str(booking_session), None)
[13500]448            if bedticket is not None and bedticket.bed is not None:
[13251]449                p_item = bedticket.bed_coordinates
[17236]450                p_session = booking_session
[13251]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:
[13508]457                return _(u'No bed allocated.'), None
[13251]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
[7151]471        elif category == 'clearance':
[9796]472            p_item = student.certcode
473            if p_item is None:
[8598]474                return _('Study course data are incomplete.'), None
[17606]475            if student.is_jupeb:
[14897]476                amount = 50000.0
477            elif student.faccode.startswith('FCETA'):
[13869]478                # ASABA and AKOKA
[14932]479                amount = 35000.0
[17112]480            elif student.depcode == 'DMIC':
481                amount = 70000.0
[17361]482            elif student.faccode in ('BMS', 'MED', 'DEN') \
483                and not student.is_postgrad:
[14897]484                amount = 80000.0
[15574]485            elif student.faccode == 'DCOEM':
486                return _('Acceptance fee payment not necessary.'), None
[11479]487            else:
[14897]488                amount = 60000.0
[7151]489        elif category == 'schoolfee':
[8598]490            try:
491                certificate = student['studycourse'].certificate
492                p_item = certificate.code
493            except (AttributeError, TypeError):
494                return _('Study course data are incomplete.'), None
[17457]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]
[17707]502                elif student.entry_session < 2023:
503                    schoolfees_dict = SCHOOLFEES[22][p_item]
[17457]504                else:
[17707]505                    schoolfees_dict = SCHOOLFEES[23][p_item]
[17457]506            except KeyError:
507                return _('School fee not yet fixed: p_item = %s' % p_item), None
[9152]508            if previous_session:
[9520]509                # Students can pay for previous sessions in all workflow states.
510                # Fresh students are excluded by the update method of the
511                # PreviousPaymentAddFormPage.
[9157]512                if previous_session == student['studycourse'].entry_session:
[9152]513                    if student.is_foreigner:
[17457]514                        amount = schoolfees_dict['initial_foreigner']
[9152]515                    else:
[17457]516                        amount = schoolfees_dict['initial']
[9006]517                else:
[9152]518                    if student.is_foreigner:
[17457]519                        amount = schoolfees_dict['returning_foreigner']
[9152]520                    else:
[17457]521                        amount = schoolfees_dict['returning']
[9152]522            else:
523                if student.state == CLEARED:
524                    if student.is_foreigner:
[17457]525                        amount = schoolfees_dict['initial_foreigner']
[9152]526                    else:
[17457]527                        amount = schoolfees_dict['initial']
[14858]528                elif student.state == PAID and student.is_postgrad:
[9513]529                    p_session += 1
[9520]530                    academic_session = self._getSessionConfiguration(p_session)
531                    if academic_session == None:
[9513]532                        return _(u'Session configuration object is not available.'), None
533                    if student.is_foreigner:
[17457]534                        amount = schoolfees_dict['returning_foreigner']
[9513]535                    else:
[17457]536                        amount = schoolfees_dict['returning']
[9152]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)
[9520]542                    academic_session = self._getSessionConfiguration(p_session)
543                    if academic_session == None:
[9152]544                        return _(u'Session configuration object is not available.'), None
[9570]545
[9520]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.
[9570]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
[9152]555                    if student.is_foreigner:
[17457]556                        amount = schoolfees_dict['returning_foreigner']
[9152]557                    else:
[17457]558                        amount = schoolfees_dict['returning']
[14960]559                # PHARMD school fee amount is fixed and previously paid
560                # installments in current session are deducted.
[15363]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)
[17457]567            try:
568                amount = float(amount)
569            except ValueError:
570                return _(u'School fee not yet fixed: p_item = %s' % p_item), None
[9006]571            # Give 50% school fee discount to staff members.
572            if student.is_staff:
573                amount /= 2
[17457]574
[16873]575        else:
576            fee_name = category + '_fee'
577            amount = getattr(academic_session, fee_name, 0.0)
[8598]578        if amount in (0.0, None):
[9520]579            return _('Amount could not be determined.'), None
[8676]580        # Add session specific penalty fee.
581        if category == 'schoolfee' and student.is_postgrad:
582            amount += academic_session.penalty_pg
[15108]583            amount += academic_session.development_fee
[13757]584        elif category == 'schoolfee' and student.current_mode == ('ug_ft'):
[13756]585            amount += academic_session.penalty_ug_ft
[13757]586        elif category == 'schoolfee' and student.current_mode == ('ug_pt'):
[13756]587            amount += academic_session.penalty_ug_pt
[13998]588        elif category == 'schoolfee' and student.current_mode == ('ug_sw'):
589            amount += academic_session.penalty_sw
[14717]590        elif category == 'schoolfee' and student.current_mode in (
591            'dp_ft', 'dp_pt'):
592            amount += academic_session.penalty_dp
[9727]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
[12566]597            # will be obsolete when Uniben returns to Kofa bed allocation.
[9727]598            category = 'hostel_maintenance'
[8676]599        # Create ticket.
[15320]600        if self.samePaymentMade(student, category, p_item, p_session):
[11644]601            return _('This type of payment has already been made.'), None
[11459]602        if self._isPaymentDisabled(p_session, category, student):
[13814]603            return _('This category of payments has been disabled.'), None
[8715]604        payment = createObject(u'waeup.StudentOnlinePayment')
[8950]605        timestamp = ("%d" % int(time()*10000))[1:]
[8598]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
[9152]611        payment.p_current = p_current
[8598]612        payment.amount_auth = amount
613        return None, payment
[7621]614
[14588]615    def warnCreditsOOR(self, studylevel, course=None):
[9831]616        studycourse = studylevel.__parent__
617        certificate = getattr(studycourse,'certificate', None)
618        current_level = studycourse.current_level
619        if None in (current_level, certificate):
[14588]620            return
[16438]621        if studylevel.__parent__.previous_verdict == 'R':
622            return
[9831]623        end_level = certificate.end_level
[15840]624        if studylevel.student.faccode in (
625            'MED', 'DEN', 'BMS') and studylevel.level == 200:
626            limit = 61
627        elif current_level >= end_level:
[14588]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
[9831]636
[16406]637    def warnCourseAlreadyPassed(self, studylevel, course):
638        """Return message if course has already been passed at
639        previous levels.
640        """
[16413]641
642        # med: we may need to put this matter on hold and allow
643        # all the students to register.
644        return False
645
[16406]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
[11773]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
[16100]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)
[17498]771            if not transcript_data[1]:
772                f_text = formatted_text('No results yet!')
[16100]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
[17498]779        if transcript_data[1]:
780            transcripttables = render_transcript_data(
781                view, tableheader, levels_data, lang=portal_language)
782            data.extend(transcripttables)
[16100]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,
[16906]807                note=note, sigs_in_footer=sigs_in_footer, topMargin=topMargin,
808                view=view)
[16100]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
[14038]817    #: A tuple containing the names of registration states in which changing of
818    #: passport pictures is allowed.
[17814]819    PORTRAIT_CHANGE_STATES = (CLEARANCE, ADMITTED, REQUESTED)
[14038]820
[17807]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
[16611]830    #: A tuple containing the names of registration states in which changing of
831    #: scanned signatures is allowed.
[16620]832    SIGNATURE_CHANGE_STATES = (CLEARED, RETURNING, PAID, REGISTERED, VALIDATED, )
[16611]833
[17870]834    def final_clearance_enabled(self, student):
[17875]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
[17870]846
[8441]847    # Uniben prefix
[17145]848    @property
849    def STUDENT_ID_PREFIX(self):
850        if grok.getSite().__name__ == 'uniben-cdl':
851            return u'C'
852        return u'B'
[15980]853
854    STUDENT_EXPORTER_NAMES = (
855            'students',
856            'studentstudycourses',
[16840]857            'studentstudycourses_1',
[15980]858            'studentstudylevels',
[16840]859            #'studentstudylevels_1',
[15980]860            'coursetickets',
[16840]861            #'coursetickets_1',
[15980]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',
[16390]875            'medicalhistory',
[17396]876            'nysc',
[15980]877            )
Note: See TracBrowser for help on using the repository browser.