source: main/waeup.kwarapoly/branches/0.1/src/waeup/kwarapoly/students/utils.py @ 11476

Last change on this file since 11476 was 11065, checked in by Henrik Bettermann, 11 years ago

Implement penalty fee attributes and add penalty fee to school fee.

  • Property svn:keywords set to Id
File size: 12.4 KB
Line 
1## $Id: utils.py 11065 2014-02-07 07:39:49Z 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 setPaymentDetails(self, category, student,
157            previous_session=None, previous_level=None):
158        """Create Payment object and set the payment data of a student for
159        the payment category specified.
160
161        """
162        details = {}
163        p_item = u''
164        amount = 0.0
165        error = u''
166        if previous_session:
167            return _('Previous session payment not yet implemented.'), None
168        p_session = student['studycourse'].current_session
169        p_level = student['studycourse'].current_level
170        p_current = True
171        academic_session = self._getSessionConfiguration(p_session)
172        if academic_session == None:
173            return _(u'Session configuration object is not available.'), None
174        # Determine fee.
175        if category == 'transfer':
176            amount = academic_session.transfer_fee
177        elif category == 'gown':
178            amount = academic_session.gown_fee
179        elif category == 'bed_allocation':
180            amount = academic_session.booking_fee
181        elif category == 'hostel_maintenance':
182            amount = 0.0
183            bedticket = student['accommodation'].get(
184                str(student.current_session), None)
185            if bedticket:
186                p_item = bedticket.bed_coordinates
187                if bedticket.bed.__parent__.maint_fee > 0:
188                    amount = bedticket.bed.__parent__.maint_fee
189                else:
190                    # fallback
191                    amount = academic_session.maint_fee
192            else:
193                # Should not happen because this is already checked
194                # in the browser module, but anyway ...
195                portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
196                p_item = trans(_('no bed allocated'), portal_language)
197        elif category == 'clearance':
198            amount = academic_session.clearance_fee
199            try:
200                p_item = student['studycourse'].certificate.code
201            except (AttributeError, TypeError):
202                return _('Study course data are incomplete.'), None
203        elif category == 'schoolfee':
204            try:
205                certificate = student['studycourse'].certificate
206                p_item = certificate.code
207            except (AttributeError, TypeError):
208                return _('Study course data are incomplete.'), None
209            if student.state == RETURNING:
210                # Override p_session and p_level
211                p_session, p_level = self.getReturningData(student)
212                academic_session = self._getSessionConfiguration(p_session)
213                if academic_session == None:
214                    return _(u'Session configuration object '
215                              'is not available.'), None
216            if student.state == CLEARED and student.current_mode in (
217                                                            'hnd_ft', 'nd_ft'):
218                # Fresh students must have booked and paid for accommodation.
219                if self._bedAvailable(student):
220                    if not self._maintPaymentMade(student, p_session):
221                        return _('Book and pay for accommodation first '
222                                 'before making school fee payments.'), None
223            if student.state in (RETURNING, CLEARED):
224                if p_level in PAYMENT_LEVELS:
225                    amount = SCHOOL_FEES.get_fee(
226                        (pt_ft(student),
227                         local_nonlocal(student),
228                         arts_science(student),
229                         p_level)
230                        )
231        elif category == 'carryover1':
232            amount = 6000.0
233        elif category == 'carryover2':
234            amount = 7000.0
235        elif category == 'carryover3':
236            amount = 8000.0
237
238        else:
239            fee_name = category + '_fee'
240            amount = getattr(academic_session, fee_name, 0.0)
241        if amount in (0.0, None):
242            return _(u'Amount could not be determined.'), None
243        for key in student['payments'].keys():
244            ticket = student['payments'][key]
245            if ticket.p_state == 'paid' and\
246               ticket.p_category == category and \
247               ticket.p_item == p_item and \
248               ticket.p_session == p_session:
249                  return _('This type of payment has already been made.'), None
250        if category.startswith('carryover'):
251            p_item = getUtility(IKofaUtils).PAYMENT_CATEGORIES[category]
252            p_item = unicode(p_item)
253            # Now we change the category to reduce the number of categories.
254            category = 'schoolfee'
255        # Add session specific penalty fee.
256        if category == 'schoolfee' and student.is_postgrad:
257            amount += academic_session.penalty_pg
258        elif category == 'schoolfee':
259            amount += academic_session.penalty_ug
260        payment = createObject(u'waeup.StudentOnlinePayment')
261        timestamp = ("%d" % int(time()*10000))[1:]
262        payment.p_id = "p%s" % timestamp
263        payment.p_category = category
264        payment.p_item = p_item
265        payment.p_session = p_session
266        payment.p_level = p_level
267        payment.p_current = p_current
268        payment.amount_auth = float(amount)
269        return None, payment
270
271    def getAccommodationDetails(self, student):
272        """Determine the accommodation data of a student.
273        """
274        d = {}
275        d['error'] = u''
276        hostels = grok.getSite()['hostels']
277        d['booking_session'] = hostels.accommodation_session
278        d['allowed_states'] = hostels.accommodation_states
279        d['startdate'] = hostels.startdate
280        d['enddate'] = hostels.enddate
281        d['expired'] = hostels.expired
282        # Determine bed type
283        studycourse = student['studycourse']
284        certificate = getattr(studycourse,'certificate',None)
285        current_level = studycourse.current_level
286        if None in (current_level, certificate):
287            return d
288        end_level = certificate.end_level
289        if current_level == 10:
290            bt = 'pr'
291        elif current_level in (100, 400):
292            bt = 'fr'
293        elif current_level in (300, 600):
294            bt = 'fi'
295        else:
296            bt = 're'
297        if student.sex == 'f':
298            sex = 'female'
299        else:
300            sex = 'male'
301        special_handling = 'regular'
302        if student.faccode == 'ITCH':
303            special_handling = 'itch'
304        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
305        return d
306
307    PWCHANGE_STATES = (ADMITTED, CLEARED, RETURNING)
308
309    # KwaraPoly prefix
310    STUDENT_ID_PREFIX = u'W'
Note: See TracBrowser for help on using the repository browser.