source: main/kofacustom.iuokada/trunk/src/kofacustom/iuokada/students/utils.py @ 17947

Last change on this file since 17947 was 17947, checked in by Henrik Bettermann, 2 weeks ago

Change max_credits.

  • Property svn:keywords set to Id
File size: 20.5 KB
RevLine 
[10765]1## $Id: utils.py 17947 2024-10-21 12:50:58Z 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##
[15802]18import grok
[10765]19from time import time
[16136]20from copy import deepcopy
[10765]21from zope.component import createObject, getUtility
22from waeup.kofa.interfaces import (IKofaUtils,
[16091]23    ADMITTED, CLEARANCE, REQUESTED, CLEARED, RETURNING, PAID,
24    REGISTERED, VALIDATED)
[10765]25from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
[15563]26from kofacustom.iuokada.interfaces import MessageFactory as _
[10765]27
28class CustomStudentsUtils(NigeriaStudentsUtils):
29    """A collection of customized methods.
30
31    """
32
[17661]33    @property
34    def STUDENT_ID_PREFIX(self):
35        if grok.getSite().__name__ == 'iuokada-cdl':
36            return u'F'
37        return u'I'
[15645]38
[17661]39
[15649]40    SKIP_UPLOAD_VIEWLETS = (
[15654]41        'acceptanceletterupload', 'certificateupload'
[15649]42        )
[15653]43    # Maximum size of upload files in kB
44    MAX_KB = 500
[15649]45
[15656]46    #: A tuple containing the names of registration states in which changing of
47    #: passport pictures is allowed.
[16136]48
[15657]49    PORTRAIT_CHANGE_STATES = (ADMITTED, CLEARANCE,)
[15656]50
[16223]51    REQUIRED_PAYMENTS_FRESH = {
[17900]52        'registration_fresh': 'Registration Fee (Fresh)',
[16136]53        'book': 'Book Deposit',
54        'develop': 'Development Fee',
55        'parentsconsult': 'Parents Consultative Forum (PCF) Fee',
[16223]56        'municipal_fresh': 'Fresh Students Municipal Fee',
[16136]57        }
58
[16223]59    REQUIRED_PAYMENTS_RETURNING = {
[17900]60        'registration_return': 'Registration Fee (Returning)',
[16223]61        'book': 'Book Deposit',
62        'develop': 'Development Fee',
63        'parentsconsult': 'Parents Consultative Forum (PCF) Fee',
64        'municipal_returning': 'Returning Students Municipal Fee',
65        }
66
[16233]67    REQUIRED_PAYMENTS_PG = {
68        'pg_other': 'PG Other Charges',
69        }
70
[15660]71    def warnCreditsOOR(self, studylevel, course=None):
72        """Return message if credits are out of range. In the base
73        package only maximum credits is set.
74        """
[16377]75        max_credits = 60
76        end_level = getattr(studylevel.__parent__.certificate, 'end_level', None)
77        if end_level and studylevel.level >= end_level:
78            max_credits = 80
[17947]79        #if studylevel.certcode == 'LLB':
80        #    max_credits = 50
81        if studylevel.certcode == 'MBBSMED' and studylevel.level == 200:
82            max_credits = 100
[16377]83        if course and studylevel.total_credits + course.credits > max_credits:
[15660]84            return _('Maximum credits exceeded.')
[16377]85        elif studylevel.total_credits > max_credits:
[15660]86            return _('Maximum credits exceeded.')
87        return
88
[16136]89    def _requiredPaymentsMissing(self, student, session):
[16257]90        # Deactivated on 29/09/20 (don't know why)
91        return
92
[16233]93        if student.is_postgrad:
94            rp = self.REQUIRED_PAYMENTS_PG
95        elif student.is_fresh:
[16223]96            rp = self.REQUIRED_PAYMENTS_FRESH
97        else:
98            rp = self.REQUIRED_PAYMENTS_RETURNING
99        for ticket in student['payments'].values():
100            if ticket.p_category == 'required_combi'and \
101                ticket.p_session == session and \
102                ticket.p_state == 'paid':
103                return
104        cats_missing = deepcopy(rp)
[16136]105        if len(student['payments']):
[16223]106            for category in rp.keys():
[16136]107                for ticket in student['payments'].values():
108                    if ticket.p_state == 'paid' and \
[16223]109                        ticket.p_category == category and \
[16136]110                        ticket.p_session == session:
111                        del cats_missing[category]
[16222]112                if not cats_missing:
113                    return
114        return "%s must be paid before Tution Fee. Make either single payments or make a 'Required Combi Payment'." % ', '.join(
115            cats_missing.values())
[16136]116
[16583]117    def samePaymentMade(self, student, category, p_item, p_session):
118        if category.startswith('resit'):
119            return False
[17889]120        if category == 'combi':
121            return False
[16583]122        for key in student['payments'].keys():
123            ticket = student['payments'][key]
124            if ticket.p_state == 'paid' and\
125               ticket.p_category == category and \
126               ticket.p_item != 'Balance' and \
127               ticket.p_item == p_item and \
128               ticket.p_session == p_session:
129                  return True
130        return False
131
[15645]132    def setPaymentDetails(self, category, student,
[16136]133            previous_session=None, previous_level=None, combi=[]):
[15645]134        """Create a payment ticket and set the payment data of a
135        student for the payment category specified.
136        """
[17661]137        if grok.getSite().__name__ == 'iuokada-cdl':
138            return self.setCDLPaymentDetails(category, student,
139                previous_session, previous_level, combi)
[15645]140        p_item = u''
141        amount = 0.0
142        if previous_session:
143            if previous_session < student['studycourse'].entry_session:
144                return _('The previous session must not fall below '
145                         'your entry session.'), None
146            if category == 'schoolfee':
147                # School fee is always paid for the following session
148                if previous_session > student['studycourse'].current_session:
149                    return _('This is not a previous session.'), None
150            else:
151                if previous_session > student['studycourse'].current_session - 1:
152                    return _('This is not a previous session.'), None
153            p_session = previous_session
154            p_level = previous_level
155            p_current = False
156        else:
157            p_session = student['studycourse'].current_session
158            p_level = student['studycourse'].current_level
159            p_current = True
[16223]160            if category in self.REQUIRED_PAYMENTS_FRESH.keys() \
161                + self.REQUIRED_PAYMENTS_RETURNING.keys() \
162                + ['schoolfee','schoolfee40','secondinstal'] \
[16136]163                and student.state == RETURNING:
164                # In case of school fee or required sundry fee payments the
165                # payment session is always next session if students are in
166                # state returning.
167                p_session, p_level = self.getReturningData(student)
[15645]168        academic_session = self._getSessionConfiguration(p_session)
169        if academic_session == None:
170            return _(u'Session configuration object is not available.'), None
171        # Determine fee.
[16138]172        if category in ('schoolfee', 'schoolfee40', 'secondinstal'):
[16136]173            rpm = self._requiredPaymentsMissing(student, p_session)
174            if rpm:
175                return rpm, None
[15645]176            try:
177                certificate = student['studycourse'].certificate
178                p_item = certificate.code
179            except (AttributeError, TypeError):
180                return _('Study course data are incomplete.'), None
181            if previous_session:
182                # Students can pay for previous sessions in all
183                # workflow states.  Fresh students are excluded by the
184                # update method of the PreviousPaymentAddFormPage.
185                if previous_level == 100:
186                    amount = getattr(certificate, 'school_fee_1', 0.0)
187                else:
188                    amount = getattr(certificate, 'school_fee_2', 0.0)
[15780]189                if category == 'schoolfee40':
[15773]190                    amount = 0.4*amount
[15780]191                elif category == 'secondinstal':
[15773]192                    amount = 0.6*amount
[15780]193            else:
194                if category == 'secondinstal':
195                    if student.is_fresh:
196                        amount = 0.6 * getattr(certificate, 'school_fee_1', 0.0)
197                    else:
198                        amount = 0.6 * getattr(certificate, 'school_fee_2', 0.0)
199                else:
[16091]200                    if student.state in (CLEARANCE, REQUESTED, CLEARED):
[15780]201                        amount = getattr(certificate, 'school_fee_1', 0.0)
202                    elif student.state == RETURNING:
203                        amount = getattr(certificate, 'school_fee_2', 0.0)
204                    elif student.is_postgrad and student.state == PAID:
205                        # Returning postgraduate students also pay for the
206                        # next session but their level always remains the
207                        # same.
208                        p_session += 1
209                        academic_session = self._getSessionConfiguration(p_session)
210                        if academic_session == None:
211                            return _(
212                                u'Session configuration object is not available.'
213                                ), None
214                        amount = getattr(certificate, 'school_fee_2', 0.0)
215                    if amount and category == 'schoolfee40':
216                        amount = 0.4*amount
[15645]217        elif category == 'clearance':
218            try:
219                p_item = student['studycourse'].certificate.code
220            except (AttributeError, TypeError):
221                return _('Study course data are incomplete.'), None
222            amount = academic_session.clearance_fee
[16464]223            if student.is_postgrad:
224                amount *= 0.5
[15937]225        elif category.startswith('resit'):
226            amount = academic_session.resit_fee
227            number = int(category.strip('resit'))
[16571]228            amount *= number
[15661]229        #elif category == 'bed_allocation':
230        #    p_item = self.getAccommodationDetails(student)['bt']
231        #    amount = academic_session.booking_fee
232        #elif category == 'hostel_maintenance':
233        #    amount = 0.0
234        #    bedticket = student['accommodation'].get(
235        #        str(student.current_session), None)
236        #    if bedticket is not None and bedticket.bed is not None:
237        #        p_item = bedticket.bed_coordinates
238        #        if bedticket.bed.__parent__.maint_fee > 0:
239        #            amount = bedticket.bed.__parent__.maint_fee
240        #        else:
241        #            # fallback
242        #            amount = academic_session.maint_fee
243        #    else:
244        #        return _(u'No bed allocated.'), None
[15676]245        elif category == 'combi' and combi:
246            categories = getUtility(IKofaUtils).COMBI_PAYMENT_CATEGORIES
247            for cat in combi:
248                fee_name = cat + '_fee'
249                cat_amount = getattr(academic_session, fee_name, 0.0)
250                if not cat_amount:
251                    return _('%s undefined.' % categories[cat]), None
252                amount += cat_amount
253                p_item += u'%s + ' % categories[cat]
254            p_item = p_item.strip(' + ')
[16222]255        elif category == 'required_combi':
[16233]256            if student.is_postgrad:
257                rp = self.REQUIRED_PAYMENTS_PG
258            elif student.is_fresh:
259                rp = self.REQUIRED_PAYMENTS_FRESH
[16223]260            else:
[16233]261                rp = self.REQUIRED_PAYMENTS_RETURNING
[16222]262            for cat in rp:
263                fee_name = cat + '_fee'
264                cat_amount = getattr(academic_session, fee_name, 0.0)
265                if not cat_amount:
266                    return _('%s undefined.' % rp[cat]), None
267                amount += cat_amount
268                p_item += u'%s + ' % rp[cat]
269            p_item = p_item.strip(' + ')
[15645]270        else:
271            fee_name = category + '_fee'
272            amount = getattr(academic_session, fee_name, 0.0)
273        if amount in (0.0, None):
274            return _('Amount could not be determined.'), None
275        if self.samePaymentMade(student, category, p_item, p_session):
276            return _('This type of payment has already been made.'), None
277        if self._isPaymentDisabled(p_session, category, student):
278            return _('This category of payments has been disabled.'), None
279        payment = createObject(u'waeup.StudentOnlinePayment')
280        timestamp = ("%d" % int(time()*10000))[1:]
[16270]281        if category in (
[16396]282            'registration', 'required_combi', 'pg_other', 'jupeb_reg'):
[16270]283            payment.provider_amt = 5000.0
284        if category in (
285            'schoolfee', 'schoolfee40') and student.is_jupeb:
286            payment.provider_amt = 5000.0
[15645]287        payment.p_id = "p%s" % timestamp
288        payment.p_category = category
289        payment.p_item = p_item
290        payment.p_session = p_session
291        payment.p_level = p_level
292        payment.p_current = p_current
293        payment.amount_auth = amount
[15686]294        payment.p_combi = combi
[15802]295        return None, payment
296
[17661]297    def setCDLPaymentDetails(self, category, student,
298            previous_session=None, previous_level=None, combi=[]):
299        """Create a payment ticket and set the payment data of a
300        student for the payment category specified.
301        """
302        p_item = u''
303        amount = 0.0
304        if previous_session:
305            if previous_session < student['studycourse'].entry_session:
306                return _('The previous session must not fall below '
307                         'your entry session.'), None
308            if category == 'schoolfee':
309                # School fee is always paid for the following session
310                if previous_session > student['studycourse'].current_session:
311                    return _('This is not a previous session.'), None
312            else:
313                if previous_session > student['studycourse'].current_session - 1:
314                    return _('This is not a previous session.'), None
315            p_session = previous_session
316            p_level = previous_level
317            p_current = False
318        else:
319            p_session = student['studycourse'].current_session
320            p_level = student['studycourse'].current_level
321            p_current = True
322            if category in self.REQUIRED_PAYMENTS_FRESH.keys() \
323                + self.REQUIRED_PAYMENTS_RETURNING.keys() \
324                + ['schoolfee','schoolfee40','secondinstal'] \
325                and student.state == RETURNING:
326                # In case of school fee or required sundry fee payments the
327                # payment session is always next session if students are in
328                # state returning.
329                p_session, p_level = self.getReturningData(student)
330        academic_session = self._getSessionConfiguration(p_session)
331        if academic_session == None:
332            return _(u'Session configuration object is not available.'), None
333        # Determine fee.
334        if category in ('schoolfee', 'schoolfee40', 'secondinstal'):
335            rpm = self._requiredPaymentsMissing(student, p_session)
336            if rpm:
337                return rpm, None
338            try:
339                certificate = student['studycourse'].certificate
340                p_item = certificate.code
341            except (AttributeError, TypeError):
342                return _('Study course data are incomplete.'), None
343            if previous_session:
344                # Students can pay for previous sessions in all
345                # workflow states.  Fresh students are excluded by the
346                # update method of the PreviousPaymentAddFormPage.
347                if previous_level == 100:
348                    amount = getattr(certificate, 'school_fee_1', 0.0)
349                else:
350                    amount = getattr(certificate, 'school_fee_2', 0.0)
351                if category == 'schoolfee40':
352                    amount = 0.4*amount
353                elif category == 'secondinstal':
354                    amount = 0.6*amount
355            else:
356                if category == 'secondinstal':
357                    if student.is_fresh:
358                        amount = 0.6 * getattr(certificate, 'school_fee_1', 0.0)
359                    else:
360                        amount = 0.6 * getattr(certificate, 'school_fee_2', 0.0)
361                else:
362                    if student.state in (CLEARANCE, REQUESTED, CLEARED):
363                        amount = getattr(certificate, 'school_fee_1', 0.0)
364                    elif student.state == RETURNING:
365                        amount = getattr(certificate, 'school_fee_2', 0.0)
366                    elif student.is_postgrad and student.state == PAID:
367                        # Returning postgraduate students also pay for the
368                        # next session but their level always remains the
369                        # same.
370                        p_session += 1
371                        academic_session = self._getSessionConfiguration(p_session)
372                        if academic_session == None:
373                            return _(
374                                u'Session configuration object is not available.'
375                                ), None
376                        amount = getattr(certificate, 'school_fee_2', 0.0)
377                    if amount and category == 'schoolfee40':
378                        amount = 0.4*amount
379        elif category == 'clearance':
380            try:
381                p_item = student['studycourse'].certificate.code
382            except (AttributeError, TypeError):
383                return _('Study course data are incomplete.'), None
384            amount = academic_session.clearance_fee
385        elif category.startswith('cdlcourse'):
386            amount = academic_session.course_fee
387            number = int(category.strip('cdlcourse'))
388            amount *= number
389        elif category == 'combi' and combi:
[17664]390            categories = getUtility(IKofaUtils).COMBI_PAYMENT_CATEGORIES
[17661]391            for cat in combi:
392                fee_name = cat + '_fee'
393                cat_amount = getattr(academic_session, fee_name, 0.0)
394                if not cat_amount:
395                    return _('%s undefined.' % categories[cat]), None
396                amount += cat_amount
397                p_item += u'%s + ' % categories[cat]
398            p_item = p_item.strip(' + ')
399        else:
400            fee_name = category + '_fee'
401            amount = getattr(academic_session, fee_name, 0.0)
402        if amount in (0.0, None):
403            return _('Amount could not be determined.'), None
404        if self.samePaymentMade(student, category, p_item, p_session):
405            return _('This type of payment has already been made.'), None
406        if self._isPaymentDisabled(p_session, category, student):
407            return _('This category of payments has been disabled.'), None
408        payment = createObject(u'waeup.StudentOnlinePayment')
409        timestamp = ("%d" % int(time()*10000))[1:]
410        if category in (
411            'registration', 'required_combi', 'pg_other', 'jupeb_reg'):
412            payment.provider_amt = 5000.0
413        if category in (
414            'schoolfee', 'schoolfee40') and student.is_jupeb:
415            payment.provider_amt = 5000.0
416        payment.p_id = "p%s" % timestamp
417        payment.p_category = category
418        payment.p_item = p_item
419        payment.p_session = p_session
420        payment.p_level = p_level
421        payment.p_current = p_current
422        payment.amount_auth = amount
423        payment.p_combi = combi
424        return None, payment
425
[16138]426    def setBalanceDetails(self, category, student,
427            balance_session, balance_level, balance_amount):
428        """Create a balance payment ticket and set the payment data
429        as selected by the student.
430        """
[16139]431        if category in ('schoolfee', 'schoolfee40', 'secondinstal') \
432            and balance_session > 2019:
[16138]433            rpm = self._requiredPaymentsMissing(student, balance_session)
434            if rpm:
435                return rpm, None
436        return super(
437            CustomStudentsUtils, self).setBalanceDetails(category, student,
438            balance_session, balance_level, balance_amount)
439
[15802]440    def constructMatricNumber(self, student):
441        """Fetch the matric number counter which fits the student and
442        construct the new matric number of the student.
443        """
444        next_integer = grok.getSite()['configuration'].next_matric_integer
445        if next_integer == 0:
446            return _('Matriculation number cannot be set.'), None
[15810]447        if not student.state in (
448            RETURNING, CLEARED, PAID, REGISTERED, VALIDATED):
[15805]449            return _('Matriculation number cannot be set.'), None
[15802]450        year = unicode(student.entry_session)[2:]
[17818]451        if grok.getSite().__name__ == 'iuokada-cdl':
452            return None, "%s/%04d/%s/CDL" % (year, next_integer, student.faccode)
[16294]453        return None, "%s/%06d/%s" % (year, next_integer, student.faccode)
Note: See TracBrowser for help on using the repository browser.