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

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

Use init_update and samePaymentMade methods.

  • Property svn:keywords set to Id
File size: 12.7 KB
Line 
1## $Id: utils.py 11646 2014-05-14 05:17:40Z 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        if self.samePaymentMade(student, category, p_item, p_session):
254            return _('This type of payment has already been made.'), None
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        # Recategorize carryover fees.
261        if category.startswith('carryover'):
262            p_item = getUtility(IKofaUtils).PAYMENT_CATEGORIES[category]
263            p_item = unicode(p_item)
264            # Now we change the category to reduce the number of categories.
265            category = 'schoolfee'
266        if self._isPaymentDisabled(p_session, category, student):
267            return _('Payment temporarily disabled.'), None
268        payment = createObject(u'waeup.StudentOnlinePayment')
269        timestamp = ("%d" % int(time()*10000))[1:]
270        payment.p_id = "p%s" % timestamp
271        payment.p_category = category
272        payment.p_item = p_item
273        payment.p_session = p_session
274        payment.p_level = p_level
275        payment.p_current = p_current
276        payment.amount_auth = float(amount)
277        return None, payment
278
279    def getAccommodationDetails(self, student):
280        """Determine the accommodation data of a student.
281        """
282        d = {}
283        d['error'] = u''
284        hostels = grok.getSite()['hostels']
285        d['booking_session'] = hostels.accommodation_session
286        d['allowed_states'] = hostels.accommodation_states
287        d['startdate'] = hostels.startdate
288        d['enddate'] = hostels.enddate
289        d['expired'] = hostels.expired
290        # Determine bed type
291        studycourse = student['studycourse']
292        certificate = getattr(studycourse,'certificate',None)
293        current_level = studycourse.current_level
294        if None in (current_level, certificate):
295            return d
296        end_level = certificate.end_level
297        if current_level == 10:
298            bt = 'pr'
299        elif current_level in (100, 400):
300            bt = 'fr'
301        elif current_level in (300, 600):
302            bt = 'fi'
303        else:
304            bt = 're'
305        if student.sex == 'f':
306            sex = 'female'
307        else:
308            sex = 'male'
309        special_handling = 'regular'
310        if student.faccode == 'ITCH':
311            special_handling = 'itch'
312        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
313        return d
314
315    PWCHANGE_STATES = (ADMITTED, CLEARED, RETURNING)
316
317    # KwaraPoly prefix
318    STUDENT_ID_PREFIX = u'W'
Note: See TracBrowser for help on using the repository browser.