source: main/waeup.aaue/trunk/src/waeup/aaue/students/utils.py @ 13788

Last change on this file since 13788 was 13786, checked in by Henrik Bettermann, 9 years ago

School fee payments must not be paid twice.

  • Property svn:keywords set to Id
File size: 15.8 KB
Line 
1## $Id: utils.py 13786 2016-03-22 06:20: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
19from time import time
20from zope.component import createObject
21from waeup.kofa.interfaces import (
22    ADMITTED, CLEARANCE, REQUESTED, CLEARED, RETURNING, PAID,
23    academic_sessions_vocab)
24from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
25from waeup.kofa.accesscodes import create_accesscode
26from waeup.kofa.students.utils import trans
27from waeup.aaue.interswitch.browser import gateway_net_amt, GATEWAY_AMT
28from waeup.aaue.interfaces import MessageFactory as _
29
30class CustomStudentsUtils(NigeriaStudentsUtils):
31    """A collection of customized methods.
32
33    """
34
35    PORTRAIT_CHANGE_STATES = (ADMITTED, RETURNING)
36
37    gpa_boundaries = ((1, 'FRNS'),
38                      (1.5, 'Pass'),
39                      (2.4, '3rd Class Honours'),
40                      (3.5, '2nd Class Honours Lower Division'),
41                      (4.5, '2nd Class Honours Upper Division'),
42                      (5, '1st Class Honours'))
43
44    def increaseMatricInteger(self, student):
45        """Increase counter for matric numbers.
46        This counter can be a centrally stored attribute or an attribute of
47        faculties, departments or certificates. In the base package the counter
48        is as an attribute of the site configuration container.
49        """
50        if student.current_mode in ('ug_pt', 'de_pt'):
51            grok.getSite()['configuration'].next_matric_integer += 1
52            return
53        elif student.is_postgrad:
54            grok.getSite()['configuration'].next_matric_integer_3 += 1
55            return
56        grok.getSite()['configuration'].next_matric_integer_2 += 1
57        return
58
59    def _concessionalPaymentMade(self, student):
60        if len(student['payments']):
61            for ticket in student['payments'].values():
62                if ticket.p_state == 'paid' and \
63                    ticket.p_category == 'concessional':
64                    return True
65        return False
66
67    def constructMatricNumber(self, student):
68        faccode = student.faccode
69        depcode = student.depcode
70        certcode = student.certcode
71        degree = getattr(
72            getattr(student.get('studycourse', None), 'certificate', None),
73                'degree', None)
74        year = unicode(student.entry_session)[2:]
75        if not student.state in (PAID, ) or not student.is_fresh or \
76            student.current_mode == 'found':
77            return _('Matriculation number cannot be set.'), None
78        #if student.current_mode not in ('mug_ft', 'mde_ft') and \
79        #    not self._concessionalPaymentMade(student):
80        #    return _('Matriculation number cannot be set.'), None
81        if student.is_postgrad:
82            next_integer = grok.getSite()['configuration'].next_matric_integer_3
83            if not degree or next_integer == 0:
84                return _('Matriculation number cannot be set.'), None
85            return None, "AAU/SPS/%s/%s/%s/%s/%05d" % (
86                faccode, depcode, year, degree, next_integer)
87        if student.current_mode in ('ug_pt', 'de_pt'):
88            next_integer = grok.getSite()['configuration'].next_matric_integer
89            if next_integer == 0:
90                return _('Matriculation number cannot be set.'), None
91            return None, "PTP/%s/%s/%s/%05d" % (
92                faccode, depcode, year, next_integer)
93        next_integer = grok.getSite()['configuration'].next_matric_integer_2
94        if next_integer == 0:
95            return _('Matriculation number cannot be set.'), None
96        if student.faccode in ('FBM', 'FCS'):
97            return None, "CMS/%s/%s/%s/%05d" % (
98                faccode, depcode, year, next_integer)
99        return None, "%s/%s/%s/%05d" % (faccode, depcode, year, next_integer)
100
101    def getReturningData(self, student):
102        """ This method defines what happens after school fee payment
103        of returning students depending on the student's senate verdict.
104        """
105        prev_level = student['studycourse'].current_level
106        cur_verdict = student['studycourse'].current_verdict
107        if cur_verdict in ('A','B','L','M','N','Z',):
108            # Successful student
109            new_level = divmod(int(prev_level),100)[0]*100 + 100
110        elif cur_verdict == 'C':
111            # Student on probation
112            new_level = int(prev_level) + 10
113        else:
114            # Student is somehow in an undefined state.
115            # Level has to be set manually.
116            new_level = prev_level
117        new_session = student['studycourse'].current_session + 1
118        return new_session, new_level
119
120    def _isPaymentDisabled(self, p_session, category, student):
121        academic_session = self._getSessionConfiguration(p_session)
122        if category == 'schoolfee' and \
123            'sf_all' in academic_session.payment_disabled:
124            return True
125        if category == 'hostel_maintenance' and \
126            'maint_all' in academic_session.payment_disabled:
127            return True
128        return False
129
130    def setPaymentDetails(self, category, student,
131            previous_session=None, previous_level=None):
132        """Create Payment object and set the payment data of a student for
133        the payment category specified.
134
135        """
136        details = {}
137        p_item = u''
138        amount = 0.0
139        error = u''
140        if previous_session:
141            return _('Previous session payment not yet implemented.'), None
142        p_session = student['studycourse'].current_session
143        p_level = student['studycourse'].current_level
144        p_current = True
145        academic_session = self._getSessionConfiguration(p_session)
146        if academic_session == None:
147            return _(u'Session configuration object is not available.'), None
148        # Determine fee.
149        if category == 'transfer':
150            amount = academic_session.transfer_fee
151        elif category == 'transcript':
152            amount = academic_session.transcript_fee
153        elif category == 'bed_allocation':
154            amount = academic_session.booking_fee
155        elif category == 'hostel_maintenance':
156            amount = 0.0
157            bedticket = student['accommodation'].get(
158                str(student.current_session), None)
159            if bedticket is not None and bedticket.bed is not None:
160                p_item = bedticket.display_coordinates
161                if bedticket.bed.__parent__.maint_fee > 0:
162                    amount = bedticket.bed.__parent__.maint_fee
163                else:
164                    # fallback
165                    amount = academic_session.maint_fee
166            else:
167                return _(u'No bed allocated.'), None
168        elif category == 'welfare':
169            amount = academic_session.welfare_fee
170        elif category == 'union':
171            amount = academic_session.union_fee
172        elif category == 'lapel':
173            amount = academic_session.lapel_fee
174        elif category == 'matric_gown':
175            amount = academic_session.matric_gown_fee
176        elif category == 'concessional':
177            amount = academic_session.concessional_fee
178        elif student.current_mode == 'found' and category not in (
179            'schoolfee', 'clearance', 'late_registration'):
180            return _('Not allowed.'), None
181        elif category.startswith('clearance'):
182            if student.state not in (ADMITTED, CLEARANCE, REQUESTED, CLEARED):
183                return _(u'Acceptance Fee payments not allowed.'), None
184            if student.faccode == 'FP':
185                amount = academic_session.clearance_fee_fp
186            elif student.current_mode.endswith('_pt'):
187                if student.is_postgrad:
188                    amount = academic_session.clearance_fee_pg_pt
189                else:
190                    amount = academic_session.clearance_fee_ug_pt
191            elif student.faccode == 'FCS':
192                # Students in clinical medical sciences pay the medical
193                # acceptance fee
194                amount = academic_session.clearance_fee_med
195            elif student.is_postgrad:  # and not part-time
196                amount = academic_session.clearance_fee_pg
197            else:
198                amount = academic_session.clearance_fee
199            p_item = student['studycourse'].certificate.code
200            if amount in (0.0, None):
201                return _(u'Amount could not be determined.'), None
202            # Add Matric Gown Fee and Lapel Fee
203            if category == 'clearance_incl':
204                amount += gateway_net_amt(academic_session.matric_gown_fee) + \
205                    gateway_net_amt(academic_session.lapel_fee)
206        elif category == 'late_registration':
207            amount = academic_session.late_registration_fee
208        elif category.startswith('schoolfee'):
209            try:
210                certificate = student['studycourse'].certificate
211                p_item = certificate.code
212            except (AttributeError, TypeError):
213                return _('Study course data are incomplete.'), None
214            if category in ('schoolfee_1', 'schoolfee_2'):
215                if student.current_mode == 'ug_pt':
216                    return _("Part-time students are not allowed "
217                             "to pay by instalments."), None
218                if category == 'schoolfee_1' and student.state != CLEARED:
219                    return _("Wrong state. Only students in state 'cleared' "
220                             "are allowed to pay by instalments."), None
221            if student.state == CLEARED or category == 'schoolfee_2':
222                if student.is_foreigner:
223                    amount = getattr(certificate, 'school_fee_3', 0.0)
224                else:
225                    amount = getattr(certificate, 'school_fee_1', 0.0)
226                # Cut school fee by 50%
227                if category in ('schoolfee_1', 'schoolfee_2'):
228                    if amount:
229                        amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
230            elif student.state == RETURNING:
231                if student.is_postgrad and category == 'schoolfee_incl':
232                    return _("No additional fees required."), None
233                if not student.father_name:
234                    return _("Personal data form is not properly filled."), None
235                # In case of returning school fee payment the payment session
236                # and level contain the values of the session the student
237                # has paid for.
238                p_session, p_level = self.getReturningData(student)
239                try:
240                    academic_session = grok.getSite()[
241                        'configuration'][str(p_session)]
242                except KeyError:
243                    return _(u'Session configuration object is not available.'), None
244                if student.is_foreigner:
245                    amount = getattr(certificate, 'school_fee_4', 0.0)
246                else:
247                    amount = getattr(certificate, 'school_fee_2', 0.0)
248            else:
249                return _('Wrong state.'), None
250            if amount in (0.0, None):
251                return _(u'Amount could not be determined.'), None
252            # Add Student Union Fee and Welfare Assurance
253            if category in ('schoolfee_incl', 'schoolfee_1'):
254                amount += gateway_net_amt(academic_session.welfare_fee) + \
255                    gateway_net_amt(academic_session.union_fee)
256            # Add non-indigenous fee and session specific penalty fees
257            if student.is_postgrad:
258                amount += academic_session.penalty_pg
259                if not student.lga.startswith('edo'):
260                    amount += 20000.0
261            else:
262                amount += academic_session.penalty_ug
263        if amount in (0.0, None):
264            return _(u'Amount could not be determined.'), None
265        # Create ticket.
266        for key in student['payments'].keys():
267            ticket = student['payments'][key]
268            if ticket.p_state == 'paid' and\
269               ticket.p_category == category and \
270               ticket.p_item == p_item and \
271               ticket.p_session == p_session:
272                  return _('This type of payment has already been made.'), None
273            # Additional condition in AAUE
274            if category in ('schoolfee', 'schoolfee_incl', 'schoolfee_1'):
275                if ticket.p_state == 'paid' and \
276                   ticket.p_category in ('schoolfee',
277                                         'schoolfee_incl',
278                                         'schoolfee_1') and \
279                   ticket.p_item == p_item and \
280                   ticket.p_session == p_session:
281                      return _(
282                          'Another school fee payment for this '
283                          'session has already been made.'), None
284
285        if self._isPaymentDisabled(p_session, category, student):
286            return _('Payment temporarily disabled.'), None
287        payment = createObject(u'waeup.StudentOnlinePayment')
288        timestamp = ("%d" % int(time()*10000))[1:]
289        payment.p_id = "p%s" % timestamp
290        payment.p_category = category
291        payment.p_item = p_item
292        payment.p_session = p_session
293        payment.p_level = p_level
294        payment.p_current = p_current
295        payment.amount_auth = amount
296        return None, payment
297
298    def _admissionText(self, student, portal_language):
299        inst_name = grok.getSite()['configuration'].name
300        entry_session = student['studycourse'].entry_session
301        entry_session = academic_sessions_vocab.getTerm(entry_session).title
302        text = trans(_(
303            'This is to inform you that you have been offered provisional'
304            ' admission into ${a} for the ${b} academic session as follows:',
305            mapping = {'a': inst_name, 'b': entry_session}),
306            portal_language)
307        return text
308
309    def maxCredits(self, studylevel):
310        """Return maximum credits.
311
312        """
313        return 48
314
315    def getBedCoordinates(self, bedticket):
316        """Return descriptive bed coordinates.
317        This method can be used to customize the `display_coordinates`
318        property method in order to  display a
319        customary description of the bed space.
320        """
321        bc = bedticket.bed_coordinates.split(',')
322        if len(bc) == 4:
323            return bc[0]
324        return bedticket.bed_coordinates
325
326    def getAccommodationDetails(self, student):
327        """Determine the accommodation data of a student.
328        """
329        d = {}
330        d['error'] = u''
331        hostels = grok.getSite()['hostels']
332        d['booking_session'] = hostels.accommodation_session
333        d['allowed_states'] = hostels.accommodation_states
334        d['startdate'] = hostels.startdate
335        d['enddate'] = hostels.enddate
336        d['expired'] = hostels.expired
337        # Determine bed type
338        bt = 'all'
339        if student.sex == 'f':
340            sex = 'female'
341        else:
342            sex = 'male'
343        special_handling = 'regular'
344        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
345        return d
346
347    def checkAccommodationRequirements(self, student, acc_details):
348        super(CustomStudentsUtils, self).checkAccommodationRequirements(
349            student, acc_details)
350        if student.current_mode not in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
351            return _('You are not eligible to book accommodation.')
352        return
353
354    # AAUE prefix
355    STUDENT_ID_PREFIX = u'E'
Note: See TracBrowser for help on using the repository browser.