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

Last change on this file since 13474 was 13461, checked in by Henrik Bettermann, 9 years ago

Adjust to changes in base package.

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