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

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

provider_amt has already to be stored when creating the payment ticket. Otherwise students will go to the bank and pay without this fee.

  • Property svn:keywords set to Id
File size: 12.7 KB
Line 
1## $Id: utils.py 16270 2020-10-05 13:19:29Z 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        if course and studylevel.total_credits + course.credits > 60:
72            return _('Maximum credits exceeded.')
73        elif studylevel.total_credits > 60:
74            return _('Maximum credits exceeded.')
75        return
76
77    def _requiredPaymentsMissing(self, student, session):
78        # Deactivated on 29/09/20 (don't know why)
79        return
80
81        if student.is_postgrad:
82            rp = self.REQUIRED_PAYMENTS_PG
83        elif student.is_fresh:
84            rp = self.REQUIRED_PAYMENTS_FRESH
85        else:
86            rp = self.REQUIRED_PAYMENTS_RETURNING
87        for ticket in student['payments'].values():
88            if ticket.p_category == 'required_combi'and \
89                ticket.p_session == session and \
90                ticket.p_state == 'paid':
91                return
92        cats_missing = deepcopy(rp)
93        if len(student['payments']):
94            for category in rp.keys():
95                for ticket in student['payments'].values():
96                    if ticket.p_state == 'paid' and \
97                        ticket.p_category == category and \
98                        ticket.p_session == session:
99                        del cats_missing[category]
100                if not cats_missing:
101                    return
102        return "%s must be paid before Tution Fee. Make either single payments or make a 'Required Combi Payment'." % ', '.join(
103            cats_missing.values())
104
105    def setPaymentDetails(self, category, student,
106            previous_session=None, previous_level=None, combi=[]):
107        """Create a payment ticket and set the payment data of a
108        student for the payment category specified.
109        """
110        p_item = u''
111        amount = 0.0
112        if previous_session:
113            if previous_session < student['studycourse'].entry_session:
114                return _('The previous session must not fall below '
115                         'your entry session.'), None
116            if category == 'schoolfee':
117                # School fee is always paid for the following session
118                if previous_session > student['studycourse'].current_session:
119                    return _('This is not a previous session.'), None
120            else:
121                if previous_session > student['studycourse'].current_session - 1:
122                    return _('This is not a previous session.'), None
123            p_session = previous_session
124            p_level = previous_level
125            p_current = False
126        else:
127            p_session = student['studycourse'].current_session
128            p_level = student['studycourse'].current_level
129            p_current = True
130            if category in self.REQUIRED_PAYMENTS_FRESH.keys() \
131                + self.REQUIRED_PAYMENTS_RETURNING.keys() \
132                + ['schoolfee','schoolfee40','secondinstal'] \
133                and student.state == RETURNING:
134                # In case of school fee or required sundry fee payments the
135                # payment session is always next session if students are in
136                # state returning.
137                p_session, p_level = self.getReturningData(student)
138        academic_session = self._getSessionConfiguration(p_session)
139        if academic_session == None:
140            return _(u'Session configuration object is not available.'), None
141        # Determine fee.
142        if category in ('schoolfee', 'schoolfee40', 'secondinstal'):
143            rpm = self._requiredPaymentsMissing(student, p_session)
144            if rpm:
145                return rpm, None
146            try:
147                certificate = student['studycourse'].certificate
148                p_item = certificate.code
149            except (AttributeError, TypeError):
150                return _('Study course data are incomplete.'), None
151            if previous_session:
152                # Students can pay for previous sessions in all
153                # workflow states.  Fresh students are excluded by the
154                # update method of the PreviousPaymentAddFormPage.
155                if previous_level == 100:
156                    amount = getattr(certificate, 'school_fee_1', 0.0)
157                else:
158                    amount = getattr(certificate, 'school_fee_2', 0.0)
159                if category == 'schoolfee40':
160                    amount = 0.4*amount
161                elif category == 'secondinstal':
162                    amount = 0.6*amount
163            else:
164                if category == 'secondinstal':
165                    if student.is_fresh:
166                        amount = 0.6 * getattr(certificate, 'school_fee_1', 0.0)
167                    else:
168                        amount = 0.6 * getattr(certificate, 'school_fee_2', 0.0)
169                else:
170                    if student.state in (CLEARANCE, REQUESTED, CLEARED):
171                        amount = getattr(certificate, 'school_fee_1', 0.0)
172                    elif student.state == RETURNING:
173                        amount = getattr(certificate, 'school_fee_2', 0.0)
174                    elif student.is_postgrad and student.state == PAID:
175                        # Returning postgraduate students also pay for the
176                        # next session but their level always remains the
177                        # same.
178                        p_session += 1
179                        academic_session = self._getSessionConfiguration(p_session)
180                        if academic_session == None:
181                            return _(
182                                u'Session configuration object is not available.'
183                                ), None
184                        amount = getattr(certificate, 'school_fee_2', 0.0)
185                    if amount and category == 'schoolfee40':
186                        amount = 0.4*amount
187        elif category == 'clearance':
188            try:
189                p_item = student['studycourse'].certificate.code
190            except (AttributeError, TypeError):
191                return _('Study course data are incomplete.'), None
192            amount = academic_session.clearance_fee
193        elif category.startswith('resit'):
194            amount = academic_session.resit_fee
195            number = int(category.strip('resit'))
196            amount *= number
197        #elif category == 'bed_allocation':
198        #    p_item = self.getAccommodationDetails(student)['bt']
199        #    amount = academic_session.booking_fee
200        #elif category == 'hostel_maintenance':
201        #    amount = 0.0
202        #    bedticket = student['accommodation'].get(
203        #        str(student.current_session), None)
204        #    if bedticket is not None and bedticket.bed is not None:
205        #        p_item = bedticket.bed_coordinates
206        #        if bedticket.bed.__parent__.maint_fee > 0:
207        #            amount = bedticket.bed.__parent__.maint_fee
208        #        else:
209        #            # fallback
210        #            amount = academic_session.maint_fee
211        #    else:
212        #        return _(u'No bed allocated.'), None
213        elif category == 'combi' and combi:
214            categories = getUtility(IKofaUtils).COMBI_PAYMENT_CATEGORIES
215            for cat in combi:
216                fee_name = cat + '_fee'
217                cat_amount = getattr(academic_session, fee_name, 0.0)
218                if not cat_amount:
219                    return _('%s undefined.' % categories[cat]), None
220                amount += cat_amount
221                p_item += u'%s + ' % categories[cat]
222            p_item = p_item.strip(' + ')
223        elif category == 'required_combi':
224            if student.is_postgrad:
225                rp = self.REQUIRED_PAYMENTS_PG
226            elif student.is_fresh:
227                rp = self.REQUIRED_PAYMENTS_FRESH
228            else:
229                rp = self.REQUIRED_PAYMENTS_RETURNING
230            for cat in rp:
231                fee_name = cat + '_fee'
232                cat_amount = getattr(academic_session, fee_name, 0.0)
233                if not cat_amount:
234                    return _('%s undefined.' % rp[cat]), None
235                amount += cat_amount
236                p_item += u'%s + ' % rp[cat]
237            p_item = p_item.strip(' + ')
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 _('Amount could not be determined.'), None
243        if self.samePaymentMade(student, category, p_item, p_session):
244            return _('This type of payment has already been made.'), None
245        if self._isPaymentDisabled(p_session, category, student):
246            return _('This category of payments has been disabled.'), None
247        payment = createObject(u'waeup.StudentOnlinePayment')
248        timestamp = ("%d" % int(time()*10000))[1:]
249        if category in (
250            'registration', 'required_combi', 'pg_other'):
251            payment.provider_amt = 5000.0
252        if category in (
253            'schoolfee', 'schoolfee40') and student.is_jupeb:
254            payment.provider_amt = 5000.0
255        payment.p_id = "p%s" % timestamp
256        payment.p_category = category
257        payment.p_item = p_item
258        payment.p_session = p_session
259        payment.p_level = p_level
260        payment.p_current = p_current
261        payment.amount_auth = amount
262        payment.p_combi = combi
263        return None, payment
264
265    def setBalanceDetails(self, category, student,
266            balance_session, balance_level, balance_amount):
267        """Create a balance payment ticket and set the payment data
268        as selected by the student.
269        """
270        if category in ('schoolfee', 'schoolfee40', 'secondinstal') \
271            and balance_session > 2019:
272            rpm = self._requiredPaymentsMissing(student, balance_session)
273            if rpm:
274                return rpm, None
275        return super(
276            CustomStudentsUtils, self).setBalanceDetails(category, student,
277            balance_session, balance_level, balance_amount)
278
279    def constructMatricNumber(self, student):
280        """Fetch the matric number counter which fits the student and
281        construct the new matric number of the student.
282        """
283        next_integer = grok.getSite()['configuration'].next_matric_integer
284        if next_integer == 0:
285            return _('Matriculation number cannot be set.'), None
286        if not student.state in (
287            RETURNING, CLEARED, PAID, REGISTERED, VALIDATED):
288            return _('Matriculation number cannot be set.'), None
289        year = unicode(student.entry_session)[2:]
290        return None, "%s/%06d" % (year, next_integer)
Note: See TracBrowser for help on using the repository browser.