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

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

Include fees for part-times repeaters.

  • Property svn:keywords set to Id
File size: 12.1 KB
Line 
1## $Id: utils.py 10942 2014-01-18 06:29:14Z 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        payment = createObject(u'waeup.StudentOnlinePayment')
256        timestamp = ("%d" % int(time()*10000))[1:]
257        payment.p_id = "p%s" % timestamp
258        payment.p_category = category
259        payment.p_item = p_item
260        payment.p_session = p_session
261        payment.p_level = p_level
262        payment.p_current = p_current
263        payment.amount_auth = float(amount)
264        return None, payment
265
266    def getAccommodationDetails(self, student):
267        """Determine the accommodation data of a student.
268        """
269        d = {}
270        d['error'] = u''
271        hostels = grok.getSite()['hostels']
272        d['booking_session'] = hostels.accommodation_session
273        d['allowed_states'] = hostels.accommodation_states
274        d['startdate'] = hostels.startdate
275        d['enddate'] = hostels.enddate
276        d['expired'] = hostels.expired
277        # Determine bed type
278        studycourse = student['studycourse']
279        certificate = getattr(studycourse,'certificate',None)
280        current_level = studycourse.current_level
281        if None in (current_level, certificate):
282            return d
283        end_level = certificate.end_level
284        if current_level == 10:
285            bt = 'pr'
286        elif current_level in (100, 400):
287            bt = 'fr'
288        elif current_level in (300, 600):
289            bt = 'fi'
290        else:
291            bt = 're'
292        if student.sex == 'f':
293            sex = 'female'
294        else:
295            sex = 'male'
296        special_handling = 'regular'
297        if student.faccode == 'ITCH':
298            special_handling = 'itch'
299        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
300        return d
301
302    PWCHANGE_STATES = (ADMITTED, CLEARED, RETURNING)
303
304    # KwaraPoly prefix
305    STUDENT_ID_PREFIX = u'W'
Note: See TracBrowser for help on using the repository browser.