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

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

PG students pay 50% acceptance fee.

  • Property svn:keywords set to Id
File size: 13.0 KB
Line 
1## $Id: utils.py 16464 2021-04-16 14:54:47Z 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            if student.is_postgrad:
198                amount *= 0.5
199        elif category.startswith('resit'):
200            amount = academic_session.resit_fee
201            number = int(category.strip('resit'))
202            amount *= number
203        #elif category == 'bed_allocation':
204        #    p_item = self.getAccommodationDetails(student)['bt']
205        #    amount = academic_session.booking_fee
206        #elif category == 'hostel_maintenance':
207        #    amount = 0.0
208        #    bedticket = student['accommodation'].get(
209        #        str(student.current_session), None)
210        #    if bedticket is not None and bedticket.bed is not None:
211        #        p_item = bedticket.bed_coordinates
212        #        if bedticket.bed.__parent__.maint_fee > 0:
213        #            amount = bedticket.bed.__parent__.maint_fee
214        #        else:
215        #            # fallback
216        #            amount = academic_session.maint_fee
217        #    else:
218        #        return _(u'No bed allocated.'), None
219        elif category == 'combi' and combi:
220            categories = getUtility(IKofaUtils).COMBI_PAYMENT_CATEGORIES
221            for cat in combi:
222                fee_name = cat + '_fee'
223                cat_amount = getattr(academic_session, fee_name, 0.0)
224                if not cat_amount:
225                    return _('%s undefined.' % categories[cat]), None
226                amount += cat_amount
227                p_item += u'%s + ' % categories[cat]
228            p_item = p_item.strip(' + ')
229        elif category == 'required_combi':
230            if student.is_postgrad:
231                rp = self.REQUIRED_PAYMENTS_PG
232            elif student.is_fresh:
233                rp = self.REQUIRED_PAYMENTS_FRESH
234            else:
235                rp = self.REQUIRED_PAYMENTS_RETURNING
236            for cat in rp:
237                fee_name = cat + '_fee'
238                cat_amount = getattr(academic_session, fee_name, 0.0)
239                if not cat_amount:
240                    return _('%s undefined.' % rp[cat]), None
241                amount += cat_amount
242                p_item += u'%s + ' % rp[cat]
243            p_item = p_item.strip(' + ')
244        else:
245            fee_name = category + '_fee'
246            amount = getattr(academic_session, fee_name, 0.0)
247        if amount in (0.0, None):
248            return _('Amount could not be determined.'), None
249        if self.samePaymentMade(student, category, p_item, p_session):
250            return _('This type of payment has already been made.'), None
251        if self._isPaymentDisabled(p_session, category, student):
252            return _('This category of payments has been disabled.'), None
253        payment = createObject(u'waeup.StudentOnlinePayment')
254        timestamp = ("%d" % int(time()*10000))[1:]
255        if category in (
256            'registration', 'required_combi', 'pg_other', 'jupeb_reg'):
257            payment.provider_amt = 5000.0
258        if category in (
259            'schoolfee', 'schoolfee40') and student.is_jupeb:
260            payment.provider_amt = 5000.0
261        payment.p_id = "p%s" % timestamp
262        payment.p_category = category
263        payment.p_item = p_item
264        payment.p_session = p_session
265        payment.p_level = p_level
266        payment.p_current = p_current
267        payment.amount_auth = amount
268        payment.p_combi = combi
269        return None, payment
270
271    def setBalanceDetails(self, category, student,
272            balance_session, balance_level, balance_amount):
273        """Create a balance payment ticket and set the payment data
274        as selected by the student.
275        """
276        if category in ('schoolfee', 'schoolfee40', 'secondinstal') \
277            and balance_session > 2019:
278            rpm = self._requiredPaymentsMissing(student, balance_session)
279            if rpm:
280                return rpm, None
281        return super(
282            CustomStudentsUtils, self).setBalanceDetails(category, student,
283            balance_session, balance_level, balance_amount)
284
285    def constructMatricNumber(self, student):
286        """Fetch the matric number counter which fits the student and
287        construct the new matric number of the student.
288        """
289        next_integer = grok.getSite()['configuration'].next_matric_integer
290        if next_integer == 0:
291            return _('Matriculation number cannot be set.'), None
292        if not student.state in (
293            RETURNING, CLEARED, PAID, REGISTERED, VALIDATED):
294            return _('Matriculation number cannot be set.'), None
295        year = unicode(student.entry_session)[2:]
296        return None, "%s/%06d/%s" % (year, next_integer, student.faccode)
Note: See TracBrowser for help on using the repository browser.