## $Id: utils.py 13536 2015-12-07 14:38:00Z 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 import random from time import time from zope.component import createObject, getUtility, queryUtility from zope.catalog.interfaces import ICatalog from waeup.kofa.interfaces import CLEARED, RETURNING, PAID from kofacustom.nigeria.students.utils import NigeriaStudentsUtils from waeup.kofa.accesscodes import create_accesscode from waeup.kofa.interfaces import ( CLEARED, RETURNING, ADMITTED, PAID, IKofaUtils) from waeup.kofa.fees import FeeTable from waeup.kofa.hostels.hostel import NOT_OCCUPIED from waeup.kwarapoly.interfaces import MessageFactory as _ # 10 = PreND (1) # 100 = ND1 (2) # 110 = ND1R (3) # 200 = ND2 (4) # 210 = ND2R (5) # 300 = ND3 (6) # 400 = HND1 (7) # 410 = HND1R (8) # 500 = HND2 (9) # 510 = HND2R (10) # 600 = HND3 (11) # 999 = PGD (12) PAYMENT_LEVELS = (10, 100, 110, 200, 210, 300, 400, 410, 500, 510, 600, 999) FEES_PARAMS = ( ('ft', 'we'), ('local', 'non-local'), ('science','arts'), PAYMENT_LEVELS ) FEES_VALUES = ( ( ( # 10 100 110 200 210 300 400 410 500 510 600 999 (34500.0, 40700.0, 33600.0, 31500.0, 33600.0, 0.0, 46200.0, 35600.0, 34700.0, 35600.0, 0.0, 48750.0), # science (34500.0, 38700.0, 31600.0, 29500.0, 31600.0, 0.0, 44200.0, 33600.0, 32700.0, 33600.0, 0.0, 47200.0) # arts ), # local ( # 10 100 110 200 210 300 400 410 500 510 600 999 (49600.0, 55200.0, 41100.0, 34090.0, 41100.0, 0.0, 60700.0, 45600.0, 37900.0, 45600.0, 0.0, 63180.0), # science (49600.0, 53200.0, 39100.0, 32090.0, 39100.0, 0.0, 58700.0, 43600.0, 35900.0, 43600.0, 0.0, 61680.0) # arts ), # non-local ), # ft ( ( # 10 100 110 200 210 300 400 410 500 510 600 999 (0.0, 42000.0, 33600.0, 33400.0, 33600.0, 33400.0, 42400.0, 35600.0, 35500.0, 35600.0, 35500.0, 0.0), # science (0.0, 40000.0, 31600.0, 31400.0, 31600.0, 31400.0, 40400.0, 33600.0, 33500.0, 33600.0, 33500.0, 0.0) # arts ), # local ( # 10 100 110 200 210 300 400 410 500 510 600 999 (0.0, 56700.0, 41100.0, 36350.0, 41100.0, 36350.0, 57600.0, 45600.0, 45850.0, 45600.0, 45850.0, 0.0), # science (0.0, 54700.0, 39100.0, 34350.0, 39100.0, 34350.0, 55600.0, 43600.0, 43850.0, 43600.0, 43850.0, 0.0) # arts ), # non-local ), # we ) SCHOOL_FEES = FeeTable(FEES_PARAMS, FEES_VALUES) def local_nonlocal(student): lga = getattr(student, 'lga') if lga and lga.startswith('kwara'): return 'local' else: return 'non-local' def arts_science(student): if student.faccode == 'IFMS': return 'arts' else: return 'science' def we_ft(student): if student.current_mode.endswith('we'): return 'we' else: return 'ft' class CustomStudentsUtils(NigeriaStudentsUtils): """A collection of customized methods. """ def maxCredits(self, studylevel): # Students do not have any credit load limit return None def selectBed(self, available_beds, desired_hostel): """Randomly select a bed from a list of available beds. """ return random.choice(available_beds) def getReturningData(self, student): """ This method defines what happens after school fee payment of returning students depending on the student's senate verdict. """ prev_level = student['studycourse'].current_level cur_verdict = student['studycourse'].current_verdict if cur_verdict in ('A','B','L','M','N','Z',): # Successful student new_level = divmod(int(prev_level),100)[0]*100 + 100 elif cur_verdict == 'C': # Student on probation new_level = int(prev_level) + 10 else: # Student is somehow in an undefined state. # Level has to be set manually. new_level = prev_level new_session = student['studycourse'].current_session + 1 return new_session, new_level def _maintPaymentMade(self, student, session): if len(student['payments']): for ticket in student['payments'].values(): if ticket.p_category == 'hostel_maintenance' and \ ticket.p_session == session and ticket.p_state == 'paid': return True return False def _bedAvailable(self, student): acc_details = self.getAccommodationDetails(student) cat = getUtility(ICatalog, name='beds_catalog') entries = cat.searchResults( owner=(student.student_id,student.student_id)) if len(entries): # Bed has already been booked. return True entries = cat.searchResults( bed_type=(acc_details['bt'],acc_details['bt'])) available_beds = [ entry for entry in entries if entry.owner == NOT_OCCUPIED] if available_beds: # Bed has not yet been booked but beds are available. return True return False def _isPaymentDisabled(self, p_session, category, student): academic_session = self._getSessionConfiguration(p_session) if category == 'schoolfee': if 'sf_all' in academic_session.payment_disabled: return True if 'sf_non_pg' in academic_session.payment_disabled and \ not student.is_postgrad: return True return False def setPaymentDetails(self, category, student, previous_session=None, previous_level=None): """Create Payment object and set the payment data of a student for the payment category specified. """ details = {} p_item = u'' amount = 0.0 error = u'' if previous_session: return _('Previous session payment not yet implemented.'), None p_session = student['studycourse'].current_session p_level = student['studycourse'].current_level p_current = True academic_session = self._getSessionConfiguration(p_session) if academic_session == None: return _(u'Session configuration object is not available.'), None # Determine fee. if category == 'transfer': amount = academic_session.transfer_fee elif category == 'gown': amount = academic_session.gown_fee elif category == 'bed_allocation': 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 == 'clearance': amount = academic_session.clearance_fee try: p_item = student['studycourse'].certificate.code except (AttributeError, TypeError): return _('Study course data are incomplete.'), None elif category == 'schoolfee': try: certificate = student['studycourse'].certificate p_item = certificate.code except (AttributeError, TypeError): return _('Study course data are incomplete.'), None if student.state == RETURNING: # Override p_session and p_level 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 if student.state == CLEARED and student.current_mode in ( 'hnd_ft', 'nd_ft'): # Fresh students must have booked and paid for accommodation. if self._bedAvailable(student): if not student.faccode == 'IOT' and \ not self._maintPaymentMade(student, p_session): return _('Book and pay for accommodation first ' 'before making school fee payments.'), None if student.state in (RETURNING, CLEARED): if p_level in PAYMENT_LEVELS: amount = SCHOOL_FEES.get_fee( (we_ft(student), local_nonlocal(student), arts_science(student), p_level) ) elif category == 'carryover1': amount = 6000.0 elif category == 'carryover2': amount = 10000.0 elif category == 'carryover3': amount = 15000.0 else: fee_name = category + '_fee' amount = getattr(academic_session, fee_name, 0.0) if amount in (0.0, None): return _(u'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 # Add session specific penalty fee. if category == 'schoolfee' and student.is_postgrad: amount += academic_session.penalty_pg elif category == 'schoolfee': amount += academic_session.penalty_ug # Recategorize carryover fees. if category.startswith('carryover'): p_item = getUtility(IKofaUtils).PAYMENT_CATEGORIES[category] p_item = unicode(p_item) # Now we change the category to reduce the number of categories. category = 'schoolfee' if self._isPaymentDisabled(p_session, category, student): return _('Payment temporarily disabled.'), None payment = createObject(u'waeup.StudentOnlinePayment') timestamp = ("%d" % int(time()*10000))[1:] 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 = float(amount) return None, payment def getAccommodationDetails(self, student): """Determine the accommodation data of a student. """ d = {} d['error'] = u'' hostels = grok.getSite()['hostels'] d['booking_session'] = hostels.accommodation_session d['allowed_states'] = hostels.accommodation_states d['startdate'] = hostels.startdate d['enddate'] = hostels.enddate d['expired'] = hostels.expired # Determine bed type studycourse = student['studycourse'] certificate = getattr(studycourse,'certificate',None) current_level = studycourse.current_level if None in (current_level, certificate): return d end_level = certificate.end_level if current_level == 10: bt = 'pr' elif current_level in (100, 400): bt = 'fr' elif current_level in (300, 600): bt = 'fi' else: bt = 're' if student.sex == 'f': sex = 'female' else: sex = 'male' special_handling = 'regular' if student.faccode == 'IOT': special_handling = 'iot' d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt) return d def increaseMatricInteger(self, student): """We don't use a persistent counter in Kwarapoly. """ return def constructMatricNumber(self, student): """Fetch the matric number counter which fits the student and construct the new matric number of the student. A typical matriculation number is like this: ND/14/STA/FT/015 ND = Study Mode Prefix 14 = Year of Entry STA = Department Code FT = Study Mode Suffix 015 = Serial Number (Every programme starts from "001" every session and the numbers build up arithmetically) """ cert = getattr(student.get('studycourse', None), 'certificate', None) entry_session = getattr( student.get('studycourse', None), 'entry_session', None) entry_mode = getattr( student.get('studycourse', None), 'entry_mode', None) if entry_session < 2015: return _('Available from session 2015/2016'), None if student.state not in (PAID, ): return _('Wrong state.'), None if None in (cert, entry_session, entry_mode): return _('Matriculation number cannot be set.'), None try: (prefix, suffix) = entry_mode.split('_') (prefix, suffix) = (prefix.upper(), suffix.upper()) except ValueError: return _('Matriculation number cannot be set.'), None entry_year = entry_session - 100*(entry_session/100) part1 = "%s/%s/%s/%s/" % ( prefix, entry_year, student.depcode, suffix) cat = queryUtility(ICatalog, name='students_catalog') results = cat.searchResults(matric_number=(part1+'0000', part1+'9999')) if not len(results): return None, part1 + '001' try: numbers = [int(i.matric_number.replace(part1, '')) for i in results if i.matric_number] except ValueError: return _('Matriculation number cannot be determined.'), None counter = max(numbers) + 1 matric_number = "%s%03d" % (part1, counter) return None, matric_number PORTRAIT_CHANGE_STATES = (ADMITTED, CLEARED, RETURNING) # KwaraPoly prefix STUDENT_ID_PREFIX = u'W'