## $Id: utils.py 18036 2025-03-11 19:29:27Z 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_SCIENCE = { 'registration_fresh': 'Registration Fee (Fresh)', 'book': 'Book Deposit', 'develop': 'Development Fee', 'parentsconsult': 'Parents Consultative Forum (PCF) Fee', 'municipal_fresh': 'Fresh Students Municipal Fee', 'matric': 'Matriculation Fee', 'waecneco': 'WAEC/NECO Verification', 'jambver': 'JAMB Verification Fee', 'health_insurance': 'Student Health Insurance', 'id_card': 'I.D. Card', 'medical_screening': 'Medical Screening Fees', 'science': 'Science Bench Fee', } REQUIRED_PAYMENTS_FRESH_NON_SCIENCE = { 'registration_fresh': 'Registration Fee (Fresh)', 'book': 'Book Deposit', 'develop': 'Development Fee', 'parentsconsult': 'Parents Consultative Forum (PCF) Fee', 'municipal_fresh': 'Fresh Students Municipal Fee', 'matric': 'Matriculation Fee', 'waecneco': 'WAEC/NECO Verification', 'jambver': 'JAMB Verification Fee', 'health_insurance': 'Student Health Insurance', 'id_card': 'I.D. Card', 'medical_screening': 'Medical Screening Fees', } # all students (except PHM) returning REQUIRED_PAYMENTS_RETURNING = { 'registration_return': 'Registration Fee (Returning)', 'book': 'Book Deposit', 'develop': 'Development Fee', 'parentsconsult': 'Parents Consultative Forum (PCF) Fee', 'municipal_returning': 'Returning Students Municipal Fee', 'health_insurance': 'Student Health Insurance', } # all stdents (except PHM) final year REQUIRED_PAYMENTS_FINAL = { 'registration_return': 'Registration Fee (Returning)', 'book': 'Book Deposit', 'develop': 'Development Fee', 'parentsconsult': 'Parents Consultative Forum (PCF) Fee', 'municipal_returning': 'Returning Students Municipal Fee', 'health_insurance': 'Student Health Insurance', 'alumni': 'Alumni Fees', 'conv': 'Convocation Fees', 'clearance': 'Clearance Fees', } # PHM returning students REQUIRED_PAYMENTS_RETURNING_PHARMACY = { 'registration_return': 'Registration Fee (Returning)', 'book': 'Book Deposit', 'develop': 'Development Fee', 'parentsconsult': 'Parents Consultative Forum (PCF) Fee', 'municipal_returning': 'Returning Students Municipal Fee', 'health_insurance': 'Student Health Insurance', 'lab_support': 'Lab Support', } # PHM students final year REQUIRED_PAYMENTS_FINAL_PHARMACY = { 'registration_return': 'Registration Fee (Returning)', 'book': 'Book Deposit', 'develop': 'Development Fee', 'parentsconsult': 'Parents Consultative Forum (PCF) Fee', 'municipal_returning': 'Returning Students Municipal Fee', 'health_insurance': 'Student Health Insurance', 'alumni': 'Alumni Fees', 'conv': 'Convocation Fees', 'clearance': 'Clearance Fees', 'lab_support': 'Lab Support', } 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 studylevel.certcode == 'LLB': # max_credits = 50 if studylevel.certcode == 'MBBSMED' and studylevel.level == 200: max_credits = 100 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 _is_payment_for_final(self, student): studycourse = student['studycourse'] certificate = getattr(studycourse,'certificate',None) current_level = studycourse.current_level if None in (current_level, certificate): return False end_level = certificate.end_level if current_level >= end_level-100: return True return False def _collect_required_payment_items(self, student): if student.is_postgrad: rp = self.REQUIRED_PAYMENTS_PG elif student.is_fresh and student.faccode in ('ENG', 'HSC', 'NAS', 'PHM'): rp = self.REQUIRED_PAYMENTS_FRESH_SCIENCE elif student.is_fresh: rp = self.REQUIRED_PAYMENTS_FRESH_NON_SCIENCE elif student.faccode == 'PHM' and self._is_payment_for_final(student): rp = self.REQUIRED_PAYMENTS_FINAL_PHARMACY elif student.faccode == 'PHM': rp = self.REQUIRED_PAYMENTS_RETURNING_PHARMACY elif self._is_payment_for_final(student): rp = self.REQUIRED_PAYMENTS_FINAL else: rp = self.REQUIRED_PAYMENTS_RETURNING return rp def _requiredPaymentsMissing(self, student, session): # Has the required combi payment been made? for ticket in student['payments'].values(): if ticket.p_category == 'required_combi'and \ ticket.p_session == session and \ ticket.p_state == 'paid': return # If not, check single payments rp = self._collect_required_payment_items(student) 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()) @property def _all_required_payments(self): return set( self.REQUIRED_PAYMENTS_PG.keys() + self.REQUIRED_PAYMENTS_FRESH_SCIENCE.keys() + self.REQUIRED_PAYMENTS_FRESH_NON_SCIENCE.keys() + self.REQUIRED_PAYMENTS_FINAL_PHARMACY.keys() + self.REQUIRED_PAYMENTS_RETURNING_PHARMACY.keys() + self.REQUIRED_PAYMENTS_FINAL.keys() + self.REQUIRED_PAYMENTS_RETURNING.keys() ) 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 list(self._all_required_payments) + ['required_combi',] \ 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': rp = self._collect_required_payment_items(student) for cat in rp.keys(): 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)