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

Last change on this file since 16137 was 16136, checked in by Henrik Bettermann, 5 years ago

Compulsory payments before tution fee can be made.

  • Property svn:keywords set to Id
File size: 10.2 KB
Line 
1## $Id: utils.py 16136 2020-06-28 20:42:53Z 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 = {
48        'registration': 'Registration Fee',
49        'book': 'Book Deposit',
50        'develop': 'Development Fee',
51        'parentsconsult': 'Parents Consultative Forum (PCF) Fee',
52        'municipal': 'Municipal Fee',
53        }
54
55    def warnCreditsOOR(self, studylevel, course=None):
56        """Return message if credits are out of range. In the base
57        package only maximum credits is set.
58        """
59        if course and studylevel.total_credits + course.credits > 60:
60            return _('Maximum credits exceeded.')
61        elif studylevel.total_credits > 60:
62            return _('Maximum credits exceeded.')
63        return
64
65    def _requiredPaymentsMissing(self, student, session):
66        cats_missing = deepcopy(self.REQUIRED_PAYMENTS)
67        if len(student['payments']):
68            for category in self.REQUIRED_PAYMENTS.keys():
69                for ticket in student['payments'].values():
70                    if ticket.p_state == 'paid' and \
71                        ticket.p_category.startswith(category) and \
72                        ticket.p_session == session:
73                        del cats_missing[category]
74        if cats_missing:
75            return "%s must be paid before Tution Fee." % ', '.join(
76                cats_missing.values())
77        return
78
79    def setPaymentDetails(self, category, student,
80            previous_session=None, previous_level=None, combi=[]):
81        """Create a payment ticket and set the payment data of a
82        student for the payment category specified.
83        """
84        p_item = u''
85        amount = 0.0
86        if previous_session:
87            if previous_session < student['studycourse'].entry_session:
88                return _('The previous session must not fall below '
89                         'your entry session.'), None
90            if category == 'schoolfee':
91                # School fee is always paid for the following session
92                if previous_session > student['studycourse'].current_session:
93                    return _('This is not a previous session.'), None
94            else:
95                if previous_session > student['studycourse'].current_session - 1:
96                    return _('This is not a previous session.'), None
97            p_session = previous_session
98            p_level = previous_level
99            p_current = False
100        else:
101            p_session = student['studycourse'].current_session
102            p_level = student['studycourse'].current_level
103            p_current = True
104            if category in self.REQUIRED_PAYMENTS.keys() + [
105                'schoolfee','schoolfee40','secondinstal'] \
106                and student.state == RETURNING:
107                # In case of school fee or required sundry fee payments the
108                # payment session is always next session if students are in
109                # state returning.
110                p_session, p_level = self.getReturningData(student)
111        academic_session = self._getSessionConfiguration(p_session)
112        if academic_session == None:
113            return _(u'Session configuration object is not available.'), None
114        # Determine fee.
115        if category in ('schoolfee', 'schoolfee40',
116                        'secondinstal'):
117            rpm = self._requiredPaymentsMissing(student, p_session)
118            if rpm:
119                return rpm, None
120            try:
121                certificate = student['studycourse'].certificate
122                p_item = certificate.code
123            except (AttributeError, TypeError):
124                return _('Study course data are incomplete.'), None
125            if previous_session:
126                # Students can pay for previous sessions in all
127                # workflow states.  Fresh students are excluded by the
128                # update method of the PreviousPaymentAddFormPage.
129                if previous_level == 100:
130                    amount = getattr(certificate, 'school_fee_1', 0.0)
131                else:
132                    amount = getattr(certificate, 'school_fee_2', 0.0)
133                if category == 'schoolfee40':
134                    amount = 0.4*amount
135                elif category == 'secondinstal':
136                    amount = 0.6*amount
137            else:
138                if category == 'secondinstal':
139                    if student.is_fresh:
140                        amount = 0.6 * getattr(certificate, 'school_fee_1', 0.0)
141                    else:
142                        amount = 0.6 * getattr(certificate, 'school_fee_2', 0.0)
143                else:
144                    if student.state in (CLEARANCE, REQUESTED, CLEARED):
145                        amount = getattr(certificate, 'school_fee_1', 0.0)
146                    elif student.state == RETURNING:
147                        amount = getattr(certificate, 'school_fee_2', 0.0)
148                    elif student.is_postgrad and student.state == PAID:
149                        # Returning postgraduate students also pay for the
150                        # next session but their level always remains the
151                        # same.
152                        p_session += 1
153                        academic_session = self._getSessionConfiguration(p_session)
154                        if academic_session == None:
155                            return _(
156                                u'Session configuration object is not available.'
157                                ), None
158                        amount = getattr(certificate, 'school_fee_2', 0.0)
159                    if amount and category == 'schoolfee40':
160                        amount = 0.4*amount
161        elif category == 'clearance':
162            try:
163                p_item = student['studycourse'].certificate.code
164            except (AttributeError, TypeError):
165                return _('Study course data are incomplete.'), None
166            amount = academic_session.clearance_fee
167        elif category.startswith('resit'):
168            amount = academic_session.resit_fee
169            number = int(category.strip('resit'))
170            amount *= number
171        #elif category == 'bed_allocation':
172        #    p_item = self.getAccommodationDetails(student)['bt']
173        #    amount = academic_session.booking_fee
174        #elif category == 'hostel_maintenance':
175        #    amount = 0.0
176        #    bedticket = student['accommodation'].get(
177        #        str(student.current_session), None)
178        #    if bedticket is not None and bedticket.bed is not None:
179        #        p_item = bedticket.bed_coordinates
180        #        if bedticket.bed.__parent__.maint_fee > 0:
181        #            amount = bedticket.bed.__parent__.maint_fee
182        #        else:
183        #            # fallback
184        #            amount = academic_session.maint_fee
185        #    else:
186        #        return _(u'No bed allocated.'), None
187        elif category == 'combi' and combi:
188            categories = getUtility(IKofaUtils).COMBI_PAYMENT_CATEGORIES
189            for cat in combi:
190                fee_name = cat + '_fee'
191                cat_amount = getattr(academic_session, fee_name, 0.0)
192                if not cat_amount:
193                    return _('%s undefined.' % categories[cat]), None
194                amount += cat_amount
195                p_item += u'%s + ' % categories[cat]
196            p_item = p_item.strip(' + ')
197        else:
198            fee_name = category + '_fee'
199            amount = getattr(academic_session, fee_name, 0.0)
200        if amount in (0.0, None):
201            return _('Amount could not be determined.'), None
202        if self.samePaymentMade(student, category, p_item, p_session):
203            return _('This type of payment has already been made.'), None
204        if self._isPaymentDisabled(p_session, category, student):
205            return _('This category of payments has been disabled.'), None
206        payment = createObject(u'waeup.StudentOnlinePayment')
207        timestamp = ("%d" % int(time()*10000))[1:]
208        payment.p_id = "p%s" % timestamp
209        payment.p_category = category
210        payment.p_item = p_item
211        payment.p_session = p_session
212        payment.p_level = p_level
213        payment.p_current = p_current
214        payment.amount_auth = amount
215        payment.p_combi = combi
216        return None, payment
217
218    def constructMatricNumber(self, student):
219        """Fetch the matric number counter which fits the student and
220        construct the new matric number of the student.
221        """
222        next_integer = grok.getSite()['configuration'].next_matric_integer
223        if next_integer == 0:
224            return _('Matriculation number cannot be set.'), None
225        if not student.state in (
226            RETURNING, CLEARED, PAID, REGISTERED, VALIDATED):
227            return _('Matriculation number cannot be set.'), None
228        year = unicode(student.entry_session)[2:]
229        return None, "%s/%06d" % (year, next_integer)
Note: See TracBrowser for help on using the repository browser.