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

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

Session specific penalty fee must be added before re-categorization of carryover fees.

Fees are required and must not be set to None.

  • Property svn:keywords set to Id
File size: 12.9 KB
Line 
1## $Id: utils.py 11623 2014-05-06 17:40:21Z 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, 28500.0, 29500.0, 28500.0, 0.0, 40000.0, 29400.0, 31700.0, 29400.0, 0.0, 56600.0), # science
58            (34500.0, 37900.0, 27000.0, 28000.0, 27000.0, 0.0, 38500.0, 27900.0, 30200.0, 27900.0, 0.0, 55100.0)  # arts
59          ), # local
60          ( # 10       100      110      200      210     300    400     410      500      510     600   999
61            (49600.0, 53900.0, 37600.0, 32090.0, 37600.0, 0.0, 56400.0, 39100.0, 34900.0, 39100.0, 0.0, 83250.0), # science
62            (49600.0, 52400.0, 36100.0, 30590.0, 36100.0, 0.0, 54900.0, 37600.0, 33400.0, 37600.0, 0.0, 81750.0)  # arts
63          ), # non-local
64        ), # ft
65       
66#Repeaters fees now included for part-times students to mirror that of full-time students
67        (
68          ( # 10    100      110      200      210      300      400      410      500      510      600    999
69            (0.0, 40700.0, 28500.0, 29900.0, 28500.0, 29900.0, 41100.0, 29400.0, 31050.0, 29400.0, 31050.0, 0.0), # science
70            (0.0, 39200.0, 27000.0, 28400.0, 27000.0, 28400.0, 39600.0, 27900.0, 29550.0, 29400.0, 29550.0, 0.0)  # arts
71          ), # local
72          ( # 10    100      110      200      210      300      400      410      500      510      600    999
73            (0.0, 55400.0, 37600.0, 33850.0, 37600.0, 33850.0, 58300.0, 39100.0, 42350.0, 39100.0, 42350.0, 0.0), # science
74            (0.0, 53900.0, 36100.0, 32350.0, 36100.0, 32350.0, 56800.0, 37600.0, 40850.0, 37600.0, 40850.0, 0.0)  # arts
75          ), # non-local
76        ), # pt
77    )
78
79SCHOOL_FEES = FeeTable(FEES_PARAMS, FEES_VALUES)
80
81def local_nonlocal(student):
82    lga = getattr(student, 'lga')
83    if lga and lga.startswith('kwara'):
84        return 'local'
85    else:
86        return 'non-local'
87
88def arts_science(student):
89    if student.faccode == 'IFMS':
90        return 'arts'
91    else:
92        return 'science'
93
94def pt_ft(student):
95    if student.current_mode.endswith('pt'):
96        return 'pt'
97    else:
98        return 'ft'
99
100class CustomStudentsUtils(NigeriaStudentsUtils):
101    """A collection of customized methods.
102
103    """
104
105
106    def selectBed(self, available_beds):
107        """Randomly select a bed from a list of available beds.
108
109        """
110        return random.choice(available_beds)
111
112    def getReturningData(self, student):
113        """ This method defines what happens after school fee payment
114        of returning students depending on the student's senate verdict.
115        """
116        prev_level = student['studycourse'].current_level
117        cur_verdict = student['studycourse'].current_verdict
118        if cur_verdict in ('A','B','L','M','N','Z',):
119            # Successful student
120            new_level = divmod(int(prev_level),100)[0]*100 + 100
121        elif cur_verdict == 'C':
122            # Student on probation
123            new_level = int(prev_level) + 10
124        else:
125            # Student is somehow in an undefined state.
126            # Level has to be set manually.
127            new_level = prev_level
128        new_session = student['studycourse'].current_session + 1
129        return new_session, new_level
130
131    def _maintPaymentMade(self, student, session):
132        if len(student['payments']):
133            for ticket in student['payments'].values():
134                if ticket.p_category == 'hostel_maintenance' and \
135                    ticket.p_session == session and ticket.p_state == 'paid':
136                        return True
137        return False
138
139    def _bedAvailable(self, student):
140        acc_details  = self.getAccommodationDetails(student)
141        cat = getUtility(ICatalog, name='beds_catalog')
142        entries = cat.searchResults(
143            owner=(student.student_id,student.student_id))
144        if len(entries):
145            # Bed has already been booked.
146            return True
147        entries = cat.searchResults(
148            bed_type=(acc_details['bt'],acc_details['bt']))
149        available_beds = [
150            entry for entry in entries if entry.owner == NOT_OCCUPIED]
151        if available_beds:
152            # Bed has not yet been booked but beds are available.
153            return True
154        return False
155
156    def _isPaymentDisabled(self, p_session, category, student):
157        academic_session = self._getSessionConfiguration(p_session)
158        if category == 'schoolfee':
159            if 'sf_all' in academic_session.payment_disabled:
160                return True
161            if 'sf_non_pg' in academic_session.payment_disabled and \
162                not student.is_postgrad:
163                return True
164        return False
165
166    def setPaymentDetails(self, category, student,
167            previous_session=None, previous_level=None):
168        """Create Payment object and set the payment data of a student for
169        the payment category specified.
170
171        """
172        details = {}
173        p_item = u''
174        amount = 0.0
175        error = u''
176        if previous_session:
177            return _('Previous session payment not yet implemented.'), None
178        p_session = student['studycourse'].current_session
179        p_level = student['studycourse'].current_level
180        p_current = True
181        academic_session = self._getSessionConfiguration(p_session)
182        if academic_session == None:
183            return _(u'Session configuration object is not available.'), None
184        # Determine fee.
185        if category == 'transfer':
186            amount = academic_session.transfer_fee
187        elif category == 'gown':
188            amount = academic_session.gown_fee
189        elif category == 'bed_allocation':
190            amount = academic_session.booking_fee
191        elif category == 'hostel_maintenance':
192            amount = 0.0
193            bedticket = student['accommodation'].get(
194                str(student.current_session), None)
195            if bedticket:
196                p_item = bedticket.bed_coordinates
197                if bedticket.bed.__parent__.maint_fee > 0:
198                    amount = bedticket.bed.__parent__.maint_fee
199                else:
200                    # fallback
201                    amount = academic_session.maint_fee
202            else:
203                # Should not happen because this is already checked
204                # in the browser module, but anyway ...
205                portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
206                p_item = trans(_('no bed allocated'), portal_language)
207        elif category == 'clearance':
208            amount = academic_session.clearance_fee
209            try:
210                p_item = student['studycourse'].certificate.code
211            except (AttributeError, TypeError):
212                return _('Study course data are incomplete.'), None
213        elif category == 'schoolfee':
214            try:
215                certificate = student['studycourse'].certificate
216                p_item = certificate.code
217            except (AttributeError, TypeError):
218                return _('Study course data are incomplete.'), None
219            if student.state == RETURNING:
220                # Override p_session and p_level
221                p_session, p_level = self.getReturningData(student)
222                academic_session = self._getSessionConfiguration(p_session)
223                if academic_session == None:
224                    return _(u'Session configuration object '
225                              'is not available.'), None
226            if student.state == CLEARED and student.current_mode in (
227                                                            'hnd_ft', 'nd_ft'):
228                # Fresh students must have booked and paid for accommodation.
229                if self._bedAvailable(student):
230                    if not self._maintPaymentMade(student, p_session):
231                        return _('Book and pay for accommodation first '
232                                 'before making school fee payments.'), None
233            if student.state in (RETURNING, CLEARED):
234                if p_level in PAYMENT_LEVELS:
235                    amount = SCHOOL_FEES.get_fee(
236                        (pt_ft(student),
237                         local_nonlocal(student),
238                         arts_science(student),
239                         p_level)
240                        )
241        elif category == 'carryover1':
242            amount = 6000.0
243        elif category == 'carryover2':
244            amount = 7000.0
245        elif category == 'carryover3':
246            amount = 8000.0
247
248        else:
249            fee_name = category + '_fee'
250            amount = getattr(academic_session, fee_name, 0.0)
251        if amount in (0.0, None):
252            return _(u'Amount could not be determined.'), None
253        for key in student['payments'].keys():
254            ticket = student['payments'][key]
255            if ticket.p_state == 'paid' and\
256               ticket.p_category == category and \
257               ticket.p_item == p_item and \
258               ticket.p_session == p_session:
259                  return _('This type of payment has already been made.'), None
260        # Add session specific penalty fee.
261        if category == 'schoolfee' and student.is_postgrad:
262            amount += academic_session.penalty_pg
263        elif category == 'schoolfee':
264            amount += academic_session.penalty_ug
265        # Recategorize carryover fees.
266        if category.startswith('carryover'):
267            p_item = getUtility(IKofaUtils).PAYMENT_CATEGORIES[category]
268            p_item = unicode(p_item)
269            # Now we change the category to reduce the number of categories.
270            category = 'schoolfee'
271        if self._isPaymentDisabled(p_session, category, student):
272            return _('Payment temporarily disabled.'), None
273        payment = createObject(u'waeup.StudentOnlinePayment')
274        timestamp = ("%d" % int(time()*10000))[1:]
275        payment.p_id = "p%s" % timestamp
276        payment.p_category = category
277        payment.p_item = p_item
278        payment.p_session = p_session
279        payment.p_level = p_level
280        payment.p_current = p_current
281        payment.amount_auth = float(amount)
282        return None, payment
283
284    def getAccommodationDetails(self, student):
285        """Determine the accommodation data of a student.
286        """
287        d = {}
288        d['error'] = u''
289        hostels = grok.getSite()['hostels']
290        d['booking_session'] = hostels.accommodation_session
291        d['allowed_states'] = hostels.accommodation_states
292        d['startdate'] = hostels.startdate
293        d['enddate'] = hostels.enddate
294        d['expired'] = hostels.expired
295        # Determine bed type
296        studycourse = student['studycourse']
297        certificate = getattr(studycourse,'certificate',None)
298        current_level = studycourse.current_level
299        if None in (current_level, certificate):
300            return d
301        end_level = certificate.end_level
302        if current_level == 10:
303            bt = 'pr'
304        elif current_level in (100, 400):
305            bt = 'fr'
306        elif current_level in (300, 600):
307            bt = 'fi'
308        else:
309            bt = 're'
310        if student.sex == 'f':
311            sex = 'female'
312        else:
313            sex = 'male'
314        special_handling = 'regular'
315        if student.faccode == 'ITCH':
316            special_handling = 'itch'
317        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
318        return d
319
320    PWCHANGE_STATES = (ADMITTED, CLEARED, RETURNING)
321
322    # KwaraPoly prefix
323    STUDENT_ID_PREFIX = u'W'
Note: See TracBrowser for help on using the repository browser.