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

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

Add test to ensure that credit load limit deactivation works.

  • Property svn:keywords set to Id
File size: 12.8 KB
Line 
1## $Id: utils.py 12049 2014-11-24 08:40:04Z 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        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                        (pt_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 = 7000.0
247        elif category == 'carryover3':
248            amount = 8000.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    PWCHANGE_STATES = (ADMITTED, CLEARED, RETURNING)
318
319    # KwaraPoly prefix
320    STUDENT_ID_PREFIX = u'W'
Note: See TracBrowser for help on using the repository browser.