## $Id: utils.py 17889 2024-08-15 09:38:17Z henrik $ ## ## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## import grok from time import time from copy import deepcopy from zope.component import createObject, getUtility from waeup.kofa.interfaces import (IKofaUtils, ADMITTED, CLEARANCE, REQUESTED, CLEARED, RETURNING, PAID, REGISTERED, VALIDATED) from kofacustom.nigeria.students.utils import NigeriaStudentsUtils from kofacustom.iuokada.interfaces import MessageFactory as _ class CustomStudentsUtils(NigeriaStudentsUtils): """A collection of customized methods. """ @property def STUDENT_ID_PREFIX(self): if grok.getSite().__name__ == 'iuokada-cdl': return u'F' return u'I' SKIP_UPLOAD_VIEWLETS = ( 'acceptanceletterupload', 'certificateupload' ) # Maximum size of upload files in kB MAX_KB = 500 #: A tuple containing the names of registration states in which changing of #: passport pictures is allowed. PORTRAIT_CHANGE_STATES = (ADMITTED, CLEARANCE,) REQUIRED_PAYMENTS_FRESH = { 'registration': 'Registration Fee', 'book': 'Book Deposit', 'develop': 'Development Fee', 'parentsconsult': 'Parents Consultative Forum (PCF) Fee', 'municipal_fresh': 'Fresh Students Municipal Fee', } REQUIRED_PAYMENTS_RETURNING = { 'registration': 'Registration Fee', 'book': 'Book Deposit', 'develop': 'Development Fee', 'parentsconsult': 'Parents Consultative Forum (PCF) Fee', 'municipal_returning': 'Returning Students Municipal Fee', } REQUIRED_PAYMENTS_PG = { 'pg_other': 'PG Other Charges', } def warnCreditsOOR(self, studylevel, course=None): """Return message if credits are out of range. In the base package only maximum credits is set. """ max_credits = 60 end_level = getattr(studylevel.__parent__.certificate, 'end_level', None) if end_level and studylevel.level >= end_level: max_credits = 80 if course and studylevel.total_credits + course.credits > max_credits: return _('Maximum credits exceeded.') elif studylevel.total_credits > max_credits: return _('Maximum credits exceeded.') return def _requiredPaymentsMissing(self, student, session): # Deactivated on 29/09/20 (don't know why) return if student.is_postgrad: rp = self.REQUIRED_PAYMENTS_PG elif student.is_fresh: rp = self.REQUIRED_PAYMENTS_FRESH else: rp = self.REQUIRED_PAYMENTS_RETURNING for ticket in student['payments'].values(): if ticket.p_category == 'required_combi'and \ ticket.p_session == session and \ ticket.p_state == 'paid': return cats_missing = deepcopy(rp) if len(student['payments']): for category in rp.keys(): for ticket in student['payments'].values(): if ticket.p_state == 'paid' and \ ticket.p_category == category and \ ticket.p_session == session: del cats_missing[category] if not cats_missing: return return "%s must be paid before Tution Fee. Make either single payments or make a 'Required Combi Payment'." % ', '.join( cats_missing.values()) def samePaymentMade(self, student, category, p_item, p_session): if category.startswith('resit'): return False if category == 'combi': return False for key in student['payments'].keys(): ticket = student['payments'][key] if ticket.p_state == 'paid' and\ ticket.p_category == category and \ ticket.p_item != 'Balance' and \ ticket.p_item == p_item and \ ticket.p_session == p_session: return True return False def setPaymentDetails(self, category, student, previous_session=None, previous_level=None, combi=[]): """Create a payment ticket and set the payment data of a student for the payment category specified. """ if grok.getSite().__name__ == 'iuokada-cdl': return self.setCDLPaymentDetails(category, student, previous_session, previous_level, combi) p_item = u'' amount = 0.0 if previous_session: if previous_session < student['studycourse'].entry_session: return _('The previous session must not fall below ' 'your entry session.'), None if category == 'schoolfee': # School fee is always paid for the following session if previous_session > student['studycourse'].current_session: return _('This is not a previous session.'), None else: if previous_session > student['studycourse'].current_session - 1: return _('This is not a previous session.'), None p_session = previous_session p_level = previous_level p_current = False else: p_session = student['studycourse'].current_session p_level = student['studycourse'].current_level p_current = True if category in self.REQUIRED_PAYMENTS_FRESH.keys() \ + self.REQUIRED_PAYMENTS_RETURNING.keys() \ + ['schoolfee','schoolfee40','secondinstal'] \ and student.state == RETURNING: # In case of school fee or required sundry fee payments the # payment session is always next session if students are in # state returning. p_session, p_level = self.getReturningData(student) academic_session = self._getSessionConfiguration(p_session) if academic_session == None: return _(u'Session configuration object is not available.'), None # Determine fee. if category in ('schoolfee', 'schoolfee40', 'secondinstal'): rpm = self._requiredPaymentsMissing(student, p_session) if rpm: return rpm, None try: certificate = student['studycourse'].certificate p_item = certificate.code except (AttributeError, TypeError): return _('Study course data are incomplete.'), None if previous_session: # Students can pay for previous sessions in all # workflow states. Fresh students are excluded by the # update method of the PreviousPaymentAddFormPage. if previous_level == 100: amount = getattr(certificate, 'school_fee_1', 0.0) else: amount = getattr(certificate, 'school_fee_2', 0.0) if category == 'schoolfee40': amount = 0.4*amount elif category == 'secondinstal': amount = 0.6*amount else: if category == 'secondinstal': if student.is_fresh: amount = 0.6 * getattr(certificate, 'school_fee_1', 0.0) else: amount = 0.6 * getattr(certificate, 'school_fee_2', 0.0) else: if student.state in (CLEARANCE, REQUESTED, CLEARED): amount = getattr(certificate, 'school_fee_1', 0.0) elif student.state == RETURNING: amount = getattr(certificate, 'school_fee_2', 0.0) elif student.is_postgrad and student.state == PAID: # Returning postgraduate students also pay for the # next session but their level always remains the # same. p_session += 1 academic_session = self._getSessionConfiguration(p_session) if academic_session == None: return _( u'Session configuration object is not available.' ), None amount = getattr(certificate, 'school_fee_2', 0.0) if amount and category == 'schoolfee40': amount = 0.4*amount elif category == 'clearance': try: p_item = student['studycourse'].certificate.code except (AttributeError, TypeError): return _('Study course data are incomplete.'), None amount = academic_session.clearance_fee if student.is_postgrad: amount *= 0.5 elif category.startswith('resit'): amount = academic_session.resit_fee number = int(category.strip('resit')) amount *= number #elif category == 'bed_allocation': # p_item = self.getAccommodationDetails(student)['bt'] # amount = academic_session.booking_fee #elif category == 'hostel_maintenance': # amount = 0.0 # bedticket = student['accommodation'].get( # str(student.current_session), None) # if bedticket is not None and bedticket.bed is not None: # p_item = bedticket.bed_coordinates # if bedticket.bed.__parent__.maint_fee > 0: # amount = bedticket.bed.__parent__.maint_fee # else: # # fallback # amount = academic_session.maint_fee # else: # return _(u'No bed allocated.'), None elif category == 'combi' and combi: categories = getUtility(IKofaUtils).COMBI_PAYMENT_CATEGORIES for cat in combi: fee_name = cat + '_fee' cat_amount = getattr(academic_session, fee_name, 0.0) if not cat_amount: return _('%s undefined.' % categories[cat]), None amount += cat_amount p_item += u'%s + ' % categories[cat] p_item = p_item.strip(' + ') elif category == 'required_combi': if student.is_postgrad: rp = self.REQUIRED_PAYMENTS_PG elif student.is_fresh: rp = self.REQUIRED_PAYMENTS_FRESH else: rp = self.REQUIRED_PAYMENTS_RETURNING for cat in rp: fee_name = cat + '_fee' cat_amount = getattr(academic_session, fee_name, 0.0) if not cat_amount: return _('%s undefined.' % rp[cat]), None amount += cat_amount p_item += u'%s + ' % rp[cat] p_item = p_item.strip(' + ') else: fee_name = category + '_fee' amount = getattr(academic_session, fee_name, 0.0) if amount in (0.0, None): return _('Amount could not be determined.'), None if self.samePaymentMade(student, category, p_item, p_session): return _('This type of payment has already been made.'), None if self._isPaymentDisabled(p_session, category, student): return _('This category of payments has been disabled.'), None payment = createObject(u'waeup.StudentOnlinePayment') timestamp = ("%d" % int(time()*10000))[1:] if category in ( 'registration', 'required_combi', 'pg_other', 'jupeb_reg'): payment.provider_amt = 5000.0 if category in ( 'schoolfee', 'schoolfee40') and student.is_jupeb: payment.provider_amt = 5000.0 payment.p_id = "p%s" % timestamp payment.p_category = category payment.p_item = p_item payment.p_session = p_session payment.p_level = p_level payment.p_current = p_current payment.amount_auth = amount payment.p_combi = combi return None, payment def setCDLPaymentDetails(self, category, student, previous_session=None, previous_level=None, combi=[]): """Create a payment ticket and set the payment data of a student for the payment category specified. """ p_item = u'' amount = 0.0 if previous_session: if previous_session < student['studycourse'].entry_session: return _('The previous session must not fall below ' 'your entry session.'), None if category == 'schoolfee': # School fee is always paid for the following session if previous_session > student['studycourse'].current_session: return _('This is not a previous session.'), None else: if previous_session > student['studycourse'].current_session - 1: return _('This is not a previous session.'), None p_session = previous_session p_level = previous_level p_current = False else: p_session = student['studycourse'].current_session p_level = student['studycourse'].current_level p_current = True if category in self.REQUIRED_PAYMENTS_FRESH.keys() \ + self.REQUIRED_PAYMENTS_RETURNING.keys() \ + ['schoolfee','schoolfee40','secondinstal'] \ and student.state == RETURNING: # In case of school fee or required sundry fee payments the # payment session is always next session if students are in # state returning. p_session, p_level = self.getReturningData(student) academic_session = self._getSessionConfiguration(p_session) if academic_session == None: return _(u'Session configuration object is not available.'), None # Determine fee. if category in ('schoolfee', 'schoolfee40', 'secondinstal'): rpm = self._requiredPaymentsMissing(student, p_session) if rpm: return rpm, None try: certificate = student['studycourse'].certificate p_item = certificate.code except (AttributeError, TypeError): return _('Study course data are incomplete.'), None if previous_session: # Students can pay for previous sessions in all # workflow states. Fresh students are excluded by the # update method of the PreviousPaymentAddFormPage. if previous_level == 100: amount = getattr(certificate, 'school_fee_1', 0.0) else: amount = getattr(certificate, 'school_fee_2', 0.0) if category == 'schoolfee40': amount = 0.4*amount elif category == 'secondinstal': amount = 0.6*amount else: if category == 'secondinstal': if student.is_fresh: amount = 0.6 * getattr(certificate, 'school_fee_1', 0.0) else: amount = 0.6 * getattr(certificate, 'school_fee_2', 0.0) else: if student.state in (CLEARANCE, REQUESTED, CLEARED): amount = getattr(certificate, 'school_fee_1', 0.0) elif student.state == RETURNING: amount = getattr(certificate, 'school_fee_2', 0.0) elif student.is_postgrad and student.state == PAID: # Returning postgraduate students also pay for the # next session but their level always remains the # same. p_session += 1 academic_session = self._getSessionConfiguration(p_session) if academic_session == None: return _( u'Session configuration object is not available.' ), None amount = getattr(certificate, 'school_fee_2', 0.0) if amount and category == 'schoolfee40': amount = 0.4*amount elif category == 'clearance': try: p_item = student['studycourse'].certificate.code except (AttributeError, TypeError): return _('Study course data are incomplete.'), None amount = academic_session.clearance_fee elif category.startswith('cdlcourse'): amount = academic_session.course_fee number = int(category.strip('cdlcourse')) amount *= number elif category == 'combi' and combi: categories = getUtility(IKofaUtils).COMBI_PAYMENT_CATEGORIES for cat in combi: fee_name = cat + '_fee' cat_amount = getattr(academic_session, fee_name, 0.0) if not cat_amount: return _('%s undefined.' % categories[cat]), None amount += cat_amount p_item += u'%s + ' % categories[cat] p_item = p_item.strip(' + ') else: fee_name = category + '_fee' amount = getattr(academic_session, fee_name, 0.0) if amount in (0.0, None): return _('Amount could not be determined.'), None if self.samePaymentMade(student, category, p_item, p_session): return _('This type of payment has already been made.'), None if self._isPaymentDisabled(p_session, category, student): return _('This category of payments has been disabled.'), None payment = createObject(u'waeup.StudentOnlinePayment') timestamp = ("%d" % int(time()*10000))[1:] if category in ( 'registration', 'required_combi', 'pg_other', 'jupeb_reg'): payment.provider_amt = 5000.0 if category in ( 'schoolfee', 'schoolfee40') and student.is_jupeb: payment.provider_amt = 5000.0 payment.p_id = "p%s" % timestamp payment.p_category = category payment.p_item = p_item payment.p_session = p_session payment.p_level = p_level payment.p_current = p_current payment.amount_auth = amount payment.p_combi = combi return None, payment def setBalanceDetails(self, category, student, balance_session, balance_level, balance_amount): """Create a balance payment ticket and set the payment data as selected by the student. """ if category in ('schoolfee', 'schoolfee40', 'secondinstal') \ and balance_session > 2019: rpm = self._requiredPaymentsMissing(student, balance_session) if rpm: return rpm, None return super( CustomStudentsUtils, self).setBalanceDetails(category, student, balance_session, balance_level, balance_amount) def constructMatricNumber(self, student): """Fetch the matric number counter which fits the student and construct the new matric number of the student. """ next_integer = grok.getSite()['configuration'].next_matric_integer if next_integer == 0: return _('Matriculation number cannot be set.'), None if not student.state in ( RETURNING, CLEARED, PAID, REGISTERED, VALIDATED): return _('Matriculation number cannot be set.'), None year = unicode(student.entry_session)[2:] if grok.getSite().__name__ == 'iuokada-cdl': return None, "%s/%04d/%s/CDL" % (year, next_integer, student.faccode) return None, "%s/%06d/%s" % (year, next_integer, student.faccode)