source: main/kofacustom.iuokada/trunk/src/kofacustom/iuokada/students/utils.py @ 16377

Last change on this file since 16377 was 16377, checked in by Henrik Bettermann, 4 years ago

Maximum credit load for final year is 80.

  • Property svn:keywords set to Id
File size: 12.9 KB
Line 
1## $Id: utils.py 16377 2021-01-20 08:54:13Z 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 copy import deepcopy
21from zope.component import createObject, getUtility
22from waeup.kofa.interfaces import (IKofaUtils,
23    ADMITTED, CLEARANCE, REQUESTED, CLEARED, RETURNING, PAID,
24    REGISTERED, VALIDATED)
25from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
26from kofacustom.iuokada.interfaces import MessageFactory as _
27
28class CustomStudentsUtils(NigeriaStudentsUtils):
29    """A collection of customized methods.
30
31    """
32
33    # prefix
34    STUDENT_ID_PREFIX = u'I'
35
36    SKIP_UPLOAD_VIEWLETS = (
37        'acceptanceletterupload', 'certificateupload'
38        )
39    # Maximum size of upload files in kB
40    MAX_KB = 500
41
42    #: A tuple containing the names of registration states in which changing of
43    #: passport pictures is allowed.
44
45    PORTRAIT_CHANGE_STATES = (ADMITTED, CLEARANCE,)
46
47    REQUIRED_PAYMENTS_FRESH = {
48        'registration': 'Registration Fee',
49        'book': 'Book Deposit',
50        'develop': 'Development Fee',
51        'parentsconsult': 'Parents Consultative Forum (PCF) Fee',
52        'municipal_fresh': 'Fresh Students Municipal Fee',
53        }
54
55    REQUIRED_PAYMENTS_RETURNING = {
56        'registration': 'Registration Fee',
57        'book': 'Book Deposit',
58        'develop': 'Development Fee',
59        'parentsconsult': 'Parents Consultative Forum (PCF) Fee',
60        'municipal_returning': 'Returning Students Municipal Fee',
61        }
62
63    REQUIRED_PAYMENTS_PG = {
64        'pg_other': 'PG Other Charges',
65        }
66
67    def warnCreditsOOR(self, studylevel, course=None):
68        """Return message if credits are out of range. In the base
69        package only maximum credits is set.
70        """
71        max_credits = 60
72        end_level = getattr(studylevel.__parent__.certificate, 'end_level', None)
73        if end_level and studylevel.level >= end_level:
74            max_credits = 80
75        if course and studylevel.total_credits + course.credits > max_credits:
76            return _('Maximum credits exceeded.')
77        elif studylevel.total_credits > max_credits:
78            return _('Maximum credits exceeded.')
79        return
80
81    def _requiredPaymentsMissing(self, student, session):
82        # Deactivated on 29/09/20 (don't know why)
83        return
84
85        if student.is_postgrad:
86            rp = self.REQUIRED_PAYMENTS_PG
87        elif student.is_fresh:
88            rp = self.REQUIRED_PAYMENTS_FRESH
89        else:
90            rp = self.REQUIRED_PAYMENTS_RETURNING
91        for ticket in student['payments'].values():
92            if ticket.p_category == 'required_combi'and \
93                ticket.p_session == session and \
94                ticket.p_state == 'paid':
95                return
96        cats_missing = deepcopy(rp)
97        if len(student['payments']):
98            for category in rp.keys():
99                for ticket in student['payments'].values():
100                    if ticket.p_state == 'paid' and \
101                        ticket.p_category == category and \
102                        ticket.p_session == session:
103                        del cats_missing[category]
104                if not cats_missing:
105                    return
106        return "%s must be paid before Tution Fee. Make either single payments or make a 'Required Combi Payment'." % ', '.join(
107            cats_missing.values())
108
109    def setPaymentDetails(self, category, student,
110            previous_session=None, previous_level=None, combi=[]):
111        """Create a payment ticket and set the payment data of a
112        student for the payment category specified.
113        """
114        p_item = u''
115        amount = 0.0
116        if previous_session:
117            if previous_session < student['studycourse'].entry_session:
118                return _('The previous session must not fall below '
119                         'your entry session.'), None
120            if category == 'schoolfee':
121                # School fee is always paid for the following session
122                if previous_session > student['studycourse'].current_session:
123                    return _('This is not a previous session.'), None
124            else:
125                if previous_session > student['studycourse'].current_session - 1:
126                    return _('This is not a previous session.'), None
127            p_session = previous_session
128            p_level = previous_level
129            p_current = False
130        else:
131            p_session = student['studycourse'].current_session
132            p_level = student['studycourse'].current_level
133            p_current = True
134            if category in self.REQUIRED_PAYMENTS_FRESH.keys() \
135                + self.REQUIRED_PAYMENTS_RETURNING.keys() \
136                + ['schoolfee','schoolfee40','secondinstal'] \
137                and student.state == RETURNING:
138                # In case of school fee or required sundry fee payments the
139                # payment session is always next session if students are in
140                # state returning.
141                p_session, p_level = self.getReturningData(student)
142        academic_session = self._getSessionConfiguration(p_session)
143        if academic_session == None:
144            return _(u'Session configuration object is not available.'), None
145        # Determine fee.
146        if category in ('schoolfee', 'schoolfee40', 'secondinstal'):
147            rpm = self._requiredPaymentsMissing(student, p_session)
148            if rpm:
149                return rpm, None
150            try:
151                certificate = student['studycourse'].certificate
152                p_item = certificate.code
153            except (AttributeError, TypeError):
154                return _('Study course data are incomplete.'), None
155            if previous_session:
156                # Students can pay for previous sessions in all
157                # workflow states.  Fresh students are excluded by the
158                # update method of the PreviousPaymentAddFormPage.
159                if previous_level == 100:
160                    amount = getattr(certificate, 'school_fee_1', 0.0)
161                else:
162                    amount = getattr(certificate, 'school_fee_2', 0.0)
163                if category == 'schoolfee40':
164                    amount = 0.4*amount
165                elif category == 'secondinstal':
166                    amount = 0.6*amount
167            else:
168                if category == 'secondinstal':
169                    if student.is_fresh:
170                        amount = 0.6 * getattr(certificate, 'school_fee_1', 0.0)
171                    else:
172                        amount = 0.6 * getattr(certificate, 'school_fee_2', 0.0)
173                else:
174                    if student.state in (CLEARANCE, REQUESTED, CLEARED):
175                        amount = getattr(certificate, 'school_fee_1', 0.0)
176                    elif student.state == RETURNING:
177                        amount = getattr(certificate, 'school_fee_2', 0.0)
178                    elif student.is_postgrad and student.state == PAID:
179                        # Returning postgraduate students also pay for the
180                        # next session but their level always remains the
181                        # same.
182                        p_session += 1
183                        academic_session = self._getSessionConfiguration(p_session)
184                        if academic_session == None:
185                            return _(
186                                u'Session configuration object is not available.'
187                                ), None
188                        amount = getattr(certificate, 'school_fee_2', 0.0)
189                    if amount and category == 'schoolfee40':
190                        amount = 0.4*amount
191        elif category == 'clearance':
192            try:
193                p_item = student['studycourse'].certificate.code
194            except (AttributeError, TypeError):
195                return _('Study course data are incomplete.'), None
196            amount = academic_session.clearance_fee
197        elif category.startswith('resit'):
198            amount = academic_session.resit_fee
199            number = int(category.strip('resit'))
200            amount *= number
201        #elif category == 'bed_allocation':
202        #    p_item = self.getAccommodationDetails(student)['bt']
203        #    amount = academic_session.booking_fee
204        #elif category == 'hostel_maintenance':
205        #    amount = 0.0
206        #    bedticket = student['accommodation'].get(
207        #        str(student.current_session), None)
208        #    if bedticket is not None and bedticket.bed is not None:
209        #        p_item = bedticket.bed_coordinates
210        #        if bedticket.bed.__parent__.maint_fee > 0:
211        #            amount = bedticket.bed.__parent__.maint_fee
212        #        else:
213        #            # fallback
214        #            amount = academic_session.maint_fee
215        #    else:
216        #        return _(u'No bed allocated.'), None
217        elif category == 'combi' and combi:
218            categories = getUtility(IKofaUtils).COMBI_PAYMENT_CATEGORIES
219            for cat in combi:
220                fee_name = cat + '_fee'
221                cat_amount = getattr(academic_session, fee_name, 0.0)
222                if not cat_amount:
223                    return _('%s undefined.' % categories[cat]), None
224                amount += cat_amount
225                p_item += u'%s + ' % categories[cat]
226            p_item = p_item.strip(' + ')
227        elif category == 'required_combi':
228            if student.is_postgrad:
229                rp = self.REQUIRED_PAYMENTS_PG
230            elif student.is_fresh:
231                rp = self.REQUIRED_PAYMENTS_FRESH
232            else:
233                rp = self.REQUIRED_PAYMENTS_RETURNING
234            for cat in rp:
235                fee_name = cat + '_fee'
236                cat_amount = getattr(academic_session, fee_name, 0.0)
237                if not cat_amount:
238                    return _('%s undefined.' % rp[cat]), None
239                amount += cat_amount
240                p_item += u'%s + ' % rp[cat]
241            p_item = p_item.strip(' + ')
242        else:
243            fee_name = category + '_fee'
244            amount = getattr(academic_session, fee_name, 0.0)
245        if amount in (0.0, None):
246            return _('Amount could not be determined.'), None
247        if self.samePaymentMade(student, category, p_item, p_session):
248            return _('This type of payment has already been made.'), None
249        if self._isPaymentDisabled(p_session, category, student):
250            return _('This category of payments has been disabled.'), None
251        payment = createObject(u'waeup.StudentOnlinePayment')
252        timestamp = ("%d" % int(time()*10000))[1:]
253        if category in (
254            'registration', 'required_combi', 'pg_other'):
255            payment.provider_amt = 5000.0
256        if category in (
257            'schoolfee', 'schoolfee40') and student.is_jupeb:
258            payment.provider_amt = 5000.0
259        payment.p_id = "p%s" % timestamp
260        payment.p_category = category
261        payment.p_item = p_item
262        payment.p_session = p_session
263        payment.p_level = p_level
264        payment.p_current = p_current
265        payment.amount_auth = amount
266        payment.p_combi = combi
267        return None, payment
268
269    def setBalanceDetails(self, category, student,
270            balance_session, balance_level, balance_amount):
271        """Create a balance payment ticket and set the payment data
272        as selected by the student.
273        """
274        if category in ('schoolfee', 'schoolfee40', 'secondinstal') \
275            and balance_session > 2019:
276            rpm = self._requiredPaymentsMissing(student, balance_session)
277            if rpm:
278                return rpm, None
279        return super(
280            CustomStudentsUtils, self).setBalanceDetails(category, student,
281            balance_session, balance_level, balance_amount)
282
283    def constructMatricNumber(self, student):
284        """Fetch the matric number counter which fits the student and
285        construct the new matric number of the student.
286        """
287        next_integer = grok.getSite()['configuration'].next_matric_integer
288        if next_integer == 0:
289            return _('Matriculation number cannot be set.'), None
290        if not student.state in (
291            RETURNING, CLEARED, PAID, REGISTERED, VALIDATED):
292            return _('Matriculation number cannot be set.'), None
293        year = unicode(student.entry_session)[2:]
294        return None, "%s/%06d/%s" % (year, next_integer, student.faccode)
Note: See TracBrowser for help on using the repository browser.