source: main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/utils.py @ 12905

Last change on this file since 12905 was 12904, checked in by Henrik Bettermann, 10 years ago

Customize matric_number construction methods.

  • Property svn:keywords set to Id
File size: 15.1 KB
Line 
1## $Id: utils.py 12904 2015-05-05 12:17: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##
18import grok
19import random
20from time import time
21from zope.component import createObject, getUtility
22from zope.catalog.interfaces import ICatalog
23from waeup.kofa.interfaces import CLEARED, RETURNING, PAID
24from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
25from waeup.kofa.accesscodes import create_accesscode
26from waeup.kofa.interfaces import CLEARED, RETURNING, ADMITTED, IKofaUtils
27from waeup.kofa.fees import FeeTable
28from waeup.kofa.hostels.hostel import NOT_OCCUPIED
29from waeup.kwarapoly.interfaces import MessageFactory as _
30
31
32# 10  = PreND (1)
33# 100 = ND1 (2)
34# 110 = ND1R (3)
35# 200 = ND2 (4)
36# 210 = ND2R (5)
37# 300 = ND3 (6)
38# 400 = HND1 (7)
39# 410 = HND1R (8)
40# 500 = HND2 (9)
41# 510 = HND2R (10)
42# 600 = HND3 (11)
43# 999 = PGD (12)
44
45PAYMENT_LEVELS = (10, 100, 110, 200, 210, 300, 400, 410, 500, 510, 600, 999)
46
47FEES_PARAMS = (
48        ('ft', 'pt'),
49        ('local', 'non-local'),
50        ('science','arts'),
51        PAYMENT_LEVELS
52    )
53
54FEES_VALUES = (
55        (
56          ( # 10       100      110      200      210     300    400     410      500      510     600   999
57            (34500.0, 39400.0, 28800.0, 30500.0, 28800.0, 0.0, 40000.0, 29900.0, 33700.0, 29900.0, 0.0, 48750.0), # science
58            (34500.0, 37900.0, 27300.0, 29000.0, 27300.0, 0.0, 38500.0, 28400.0, 32200.0, 28400.0, 0.0, 47200.0)  # arts
59          ), # local
60          ( # 10       100      110      200      210     300    400     410      500      510     600   999
61            (49600.0, 53900.0, 35900.0, 33090.0, 35900.0, 0.0, 56400.0, 38600.0, 36900.0, 38600.0, 0.0, 63180.0), # science
62            (49600.0, 52400.0, 34400.0, 31590.0, 34400.0, 0.0, 54900.0, 37100.0, 35400.0, 37100.0, 0.0, 61680.0)  # arts
63          ), # non-local
64        ), # ft
65        (
66          ( # 10    100       110    200      210      300      400        410    500     510       600     999
67            (0.0, 40700.0, 28800.0, 30900.0, 28800.0, 30900.0, 41100.0, 29900.0, 33050.0, 29900.0, 33050.0, 0.0), # science
68            (0.0, 39200.0, 27300.0, 29400.0, 27300.0, 29400.0, 39600.0, 28400.0, 31550.0, 28400.0, 31550.0, 0.0)  # arts
69          ), # local
70          ( # 10   100         110    200       210      300      400     410      500     510      600     999
71            (0.0, 55400.0, 35900.0, 34850.0, 35900.0, 34850.0, 57800.0, 38600.0, 44350.0, 38600.0, 44350.0, 0.0), # science
72            (0.0, 53900.0, 34400.0, 33350.0, 34400.0, 33350.0, 56300.0, 37100.0, 42850.0, 37100.0, 42850.0, 0.0)  # arts
73          ), # non-local
74        ), # pt
75    )
76
77SCHOOL_FEES = FeeTable(FEES_PARAMS, FEES_VALUES)
78
79def local_nonlocal(student):
80    lga = getattr(student, 'lga')
81    if lga and lga.startswith('kwara'):
82        return 'local'
83    else:
84        return 'non-local'
85
86def arts_science(student):
87    if student.faccode == 'IFMS':
88        return 'arts'
89    else:
90        return 'science'
91
92def pt_ft(student):
93    if student.current_mode.endswith('pt'):
94        return 'pt'
95    else:
96        return 'ft'
97
98class CustomStudentsUtils(NigeriaStudentsUtils):
99    """A collection of customized methods.
100
101    """
102
103    def maxCredits(self, studylevel):
104        # Students do not have any credit load limit
105        return None
106
107    def selectBed(self, available_beds):
108        """Randomly select a bed from a list of available beds.
109
110        """
111        return random.choice(available_beds)
112
113    def getReturningData(self, student):
114        """ This method defines what happens after school fee payment
115        of returning students depending on the student's senate verdict.
116        """
117        prev_level = student['studycourse'].current_level
118        cur_verdict = student['studycourse'].current_verdict
119        if cur_verdict in ('A','B','L','M','N','Z',):
120            # Successful student
121            new_level = divmod(int(prev_level),100)[0]*100 + 100
122        elif cur_verdict == 'C':
123            # Student on probation
124            new_level = int(prev_level) + 10
125        else:
126            # Student is somehow in an undefined state.
127            # Level has to be set manually.
128            new_level = prev_level
129        new_session = student['studycourse'].current_session + 1
130        return new_session, new_level
131
132    def _maintPaymentMade(self, student, session):
133
134        # Maint payment checking disabled on 5th Dec 2014
135        return True
136
137        #if len(student['payments']):
138        #    for ticket in student['payments'].values():
139        #        if ticket.p_category == 'hostel_maintenance' and \
140        #            ticket.p_session == session and ticket.p_state == 'paid':
141        #                return True
142        #return False
143
144    def _bedAvailable(self, student):
145        acc_details  = self.getAccommodationDetails(student)
146        cat = getUtility(ICatalog, name='beds_catalog')
147        entries = cat.searchResults(
148            owner=(student.student_id,student.student_id))
149        if len(entries):
150            # Bed has already been booked.
151            return True
152        entries = cat.searchResults(
153            bed_type=(acc_details['bt'],acc_details['bt']))
154        available_beds = [
155            entry for entry in entries if entry.owner == NOT_OCCUPIED]
156        if available_beds:
157            # Bed has not yet been booked but beds are available.
158            return True
159        return False
160
161    def _isPaymentDisabled(self, p_session, category, student):
162        academic_session = self._getSessionConfiguration(p_session)
163        if category == 'schoolfee':
164            if 'sf_all' in academic_session.payment_disabled:
165                return True
166            if 'sf_non_pg' in academic_session.payment_disabled and \
167                not student.is_postgrad:
168                return True
169        return False
170
171    def setPaymentDetails(self, category, student,
172            previous_session=None, previous_level=None):
173        """Create Payment object and set the payment data of a student for
174        the payment category specified.
175
176        """
177        details = {}
178        p_item = u''
179        amount = 0.0
180        error = u''
181        if previous_session:
182            return _('Previous session payment not yet implemented.'), None
183        p_session = student['studycourse'].current_session
184        p_level = student['studycourse'].current_level
185        p_current = True
186        academic_session = self._getSessionConfiguration(p_session)
187        if academic_session == None:
188            return _(u'Session configuration object is not available.'), None
189        # Determine fee.
190        if category == 'transfer':
191            amount = academic_session.transfer_fee
192        elif category == 'gown':
193            amount = academic_session.gown_fee
194        elif category == 'bed_allocation':
195            amount = academic_session.booking_fee
196        elif category == 'hostel_maintenance':
197            amount = 0.0
198            bedticket = student['accommodation'].get(
199                str(student.current_session), None)
200            if bedticket:
201                p_item = bedticket.bed_coordinates
202                if bedticket.bed.__parent__.maint_fee > 0:
203                    amount = bedticket.bed.__parent__.maint_fee
204                else:
205                    # fallback
206                    amount = academic_session.maint_fee
207            else:
208                # Should not happen because this is already checked
209                # in the browser module, but anyway ...
210                portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
211                p_item = trans(_('no bed allocated'), portal_language)
212        elif category == 'clearance':
213            amount = academic_session.clearance_fee
214            try:
215                p_item = student['studycourse'].certificate.code
216            except (AttributeError, TypeError):
217                return _('Study course data are incomplete.'), None
218        elif category == 'schoolfee':
219            try:
220                certificate = student['studycourse'].certificate
221                p_item = certificate.code
222            except (AttributeError, TypeError):
223                return _('Study course data are incomplete.'), None
224            if student.state == RETURNING:
225                # Override p_session and p_level
226                p_session, p_level = self.getReturningData(student)
227                academic_session = self._getSessionConfiguration(p_session)
228                if academic_session == None:
229                    return _(u'Session configuration object '
230                              'is not available.'), None
231            if student.state == CLEARED and student.current_mode in (
232                                                            'hnd_ft', 'nd_ft'):
233                # Fresh students must have booked and paid for accommodation.
234                if self._bedAvailable(student):
235                    if not student.faccode == 'IOT' and \
236                        not self._maintPaymentMade(student, p_session):
237                        return _('Book and pay for accommodation first '
238                                 'before making school fee payments.'), None
239            if student.state in (RETURNING, CLEARED):
240                if p_level in PAYMENT_LEVELS:
241                    amount = SCHOOL_FEES.get_fee(
242                        (pt_ft(student),
243                         local_nonlocal(student),
244                         arts_science(student),
245                         p_level)
246                        )
247        elif category == 'carryover1':
248            amount = 6000.0
249        elif category == 'carryover2':
250            amount = 7000.0
251        elif category == 'carryover3':
252            amount = 8000.0
253
254        else:
255            fee_name = category + '_fee'
256            amount = getattr(academic_session, fee_name, 0.0)
257        if amount in (0.0, None):
258            return _(u'Amount could not be determined.'), None
259        if self.samePaymentMade(student, category, p_item, p_session):
260            return _('This type of payment has already been made.'), None
261        # Add session specific penalty fee.
262        if category == 'schoolfee' and student.is_postgrad:
263            amount += academic_session.penalty_pg
264        elif category == 'schoolfee':
265            amount += academic_session.penalty_ug
266        # Recategorize carryover fees.
267        if category.startswith('carryover'):
268            p_item = getUtility(IKofaUtils).PAYMENT_CATEGORIES[category]
269            p_item = unicode(p_item)
270            # Now we change the category to reduce the number of categories.
271            category = 'schoolfee'
272        if self._isPaymentDisabled(p_session, category, student):
273            return _('Payment temporarily disabled.'), None
274        payment = createObject(u'waeup.StudentOnlinePayment')
275        timestamp = ("%d" % int(time()*10000))[1:]
276        payment.p_id = "p%s" % timestamp
277        payment.p_category = category
278        payment.p_item = p_item
279        payment.p_session = p_session
280        payment.p_level = p_level
281        payment.p_current = p_current
282        payment.amount_auth = float(amount)
283        return None, payment
284
285    def getAccommodationDetails(self, student):
286        """Determine the accommodation data of a student.
287        """
288        d = {}
289        d['error'] = u''
290        hostels = grok.getSite()['hostels']
291        d['booking_session'] = hostels.accommodation_session
292        d['allowed_states'] = hostels.accommodation_states
293        d['startdate'] = hostels.startdate
294        d['enddate'] = hostels.enddate
295        d['expired'] = hostels.expired
296        # Determine bed type
297        studycourse = student['studycourse']
298        certificate = getattr(studycourse,'certificate',None)
299        current_level = studycourse.current_level
300        if None in (current_level, certificate):
301            return d
302        end_level = certificate.end_level
303        if current_level == 10:
304            bt = 'pr'
305        elif current_level in (100, 400):
306            bt = 'fr'
307        elif current_level in (300, 600):
308            bt = 'fi'
309        else:
310            bt = 're'
311        if student.sex == 'f':
312            sex = 'female'
313        else:
314            sex = 'male'
315        special_handling = 'regular'
316        if student.faccode == 'IOT':
317            special_handling = 'iot'
318        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
319        return d
320
321    def increaseMatricInteger(self, student):
322        """Increase counter for matric numbers.
323        """
324        cert = getattr(student.get('studycourse', None), 'certificate', None)
325        entry_session = getattr(
326            student.get('studycourse', None), 'entry_session', None)
327        dep = cert.__parent__.__parent__
328        next_matric_dict = getattr(dep, 'next_matric_dict', None)
329        if not next_matric_dict:
330            # add next_matric_dict attribute
331            dep.next_matric_dict = {}
332        if not dep.next_matric_dict.get(entry_session, None):
333            # initialize counter element
334            dep.next_matric_dict[entry_session] = 2
335            return
336        # increase counter value of entry_session
337        dep.next_matric_dict[entry_session] += 1
338        return
339
340    def constructMatricNumber(self, student):
341        """Fetch the matric number counter which fits the student and
342        construct the new matric number of the student.
343
344        A typical matriculation number is like this: ND/14/STA/FT/015
345
346        ND = Study Mode Prefix
347        14 = Year of Entry
348        STA = Department Code
349        FT = Study Mode Suffix
350        015 = Serial Number (Every department starts from "001" every
351        session and the numbers build up arithmetically)
352        """
353        cert = getattr(student.get('studycourse', None), 'certificate', None)
354        entry_session = getattr(
355            student.get('studycourse', None), 'entry_session', None)
356        entry_mode = getattr(
357            student.get('studycourse', None), 'entry_mode', None)
358        if None in (cert, entry_session, entry_mode):
359            return _('Matriculation number cannot be set.'), None
360        try:
361            (prefix, suffix) = entry_mode.split('_')
362            (prefix, suffix) = (prefix.upper(), suffix.upper())
363        except ValueError:
364            return _('Matriculation number cannot be set.'), None
365        dep = cert.__parent__.__parent__
366        next_integer = getattr(
367            dep, 'next_matric_dict', {}).get(entry_session, 1)
368        entry_year = entry_session - 100*(entry_session/100)
369        matric_number = "%s/%s/%s/%s/%03d" % (
370            prefix, entry_year, student.depcode, suffix, next_integer)
371        return None, matric_number
372
373    PWCHANGE_STATES = (ADMITTED, CLEARED, RETURNING)
374
375    # KwaraPoly prefix
376    STUDENT_ID_PREFIX = u'W'
Note: See TracBrowser for help on using the repository browser.