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

Last change on this file since 16961 was 16583, checked in by Henrik Bettermann, 3 years ago

Customize samePaymentMade.

  • Property svn:keywords set to Id
File size: 13.5 KB
Line 
1## $Id: utils.py 16583 2021-08-26 21:23:34Z 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 samePaymentMade(self, student, category, p_item, p_session):
110        if category.startswith('resit'):
111            return False
112        for key in student['payments'].keys():
113            ticket = student['payments'][key]
114            if ticket.p_state == 'paid' and\
115               ticket.p_category == category and \
116               ticket.p_item != 'Balance' and \
117               ticket.p_item == p_item and \
118               ticket.p_session == p_session:
119                  return True
120        return False
121
122    def setPaymentDetails(self, category, student,
123            previous_session=None, previous_level=None, combi=[]):
124        """Create a payment ticket and set the payment data of a
125        student for the payment category specified.
126        """
127        p_item = u''
128        amount = 0.0
129        if previous_session:
130            if previous_session < student['studycourse'].entry_session:
131                return _('The previous session must not fall below '
132                         'your entry session.'), None
133            if category == 'schoolfee':
134                # School fee is always paid for the following session
135                if previous_session > student['studycourse'].current_session:
136                    return _('This is not a previous session.'), None
137            else:
138                if previous_session > student['studycourse'].current_session - 1:
139                    return _('This is not a previous session.'), None
140            p_session = previous_session
141            p_level = previous_level
142            p_current = False
143        else:
144            p_session = student['studycourse'].current_session
145            p_level = student['studycourse'].current_level
146            p_current = True
147            if category in self.REQUIRED_PAYMENTS_FRESH.keys() \
148                + self.REQUIRED_PAYMENTS_RETURNING.keys() \
149                + ['schoolfee','schoolfee40','secondinstal'] \
150                and student.state == RETURNING:
151                # In case of school fee or required sundry fee payments the
152                # payment session is always next session if students are in
153                # state returning.
154                p_session, p_level = self.getReturningData(student)
155        academic_session = self._getSessionConfiguration(p_session)
156        if academic_session == None:
157            return _(u'Session configuration object is not available.'), None
158        # Determine fee.
159        if category in ('schoolfee', 'schoolfee40', 'secondinstal'):
160            rpm = self._requiredPaymentsMissing(student, p_session)
161            if rpm:
162                return rpm, None
163            try:
164                certificate = student['studycourse'].certificate
165                p_item = certificate.code
166            except (AttributeError, TypeError):
167                return _('Study course data are incomplete.'), None
168            if previous_session:
169                # Students can pay for previous sessions in all
170                # workflow states.  Fresh students are excluded by the
171                # update method of the PreviousPaymentAddFormPage.
172                if previous_level == 100:
173                    amount = getattr(certificate, 'school_fee_1', 0.0)
174                else:
175                    amount = getattr(certificate, 'school_fee_2', 0.0)
176                if category == 'schoolfee40':
177                    amount = 0.4*amount
178                elif category == 'secondinstal':
179                    amount = 0.6*amount
180            else:
181                if category == 'secondinstal':
182                    if student.is_fresh:
183                        amount = 0.6 * getattr(certificate, 'school_fee_1', 0.0)
184                    else:
185                        amount = 0.6 * getattr(certificate, 'school_fee_2', 0.0)
186                else:
187                    if student.state in (CLEARANCE, REQUESTED, CLEARED):
188                        amount = getattr(certificate, 'school_fee_1', 0.0)
189                    elif student.state == RETURNING:
190                        amount = getattr(certificate, 'school_fee_2', 0.0)
191                    elif student.is_postgrad and student.state == PAID:
192                        # Returning postgraduate students also pay for the
193                        # next session but their level always remains the
194                        # same.
195                        p_session += 1
196                        academic_session = self._getSessionConfiguration(p_session)
197                        if academic_session == None:
198                            return _(
199                                u'Session configuration object is not available.'
200                                ), None
201                        amount = getattr(certificate, 'school_fee_2', 0.0)
202                    if amount and category == 'schoolfee40':
203                        amount = 0.4*amount
204        elif category == 'clearance':
205            try:
206                p_item = student['studycourse'].certificate.code
207            except (AttributeError, TypeError):
208                return _('Study course data are incomplete.'), None
209            amount = academic_session.clearance_fee
210            if student.is_postgrad:
211                amount *= 0.5
212        elif category.startswith('resit'):
213            amount = academic_session.resit_fee
214            number = int(category.strip('resit'))
215            amount *= number
216        #elif category == 'bed_allocation':
217        #    p_item = self.getAccommodationDetails(student)['bt']
218        #    amount = academic_session.booking_fee
219        #elif category == 'hostel_maintenance':
220        #    amount = 0.0
221        #    bedticket = student['accommodation'].get(
222        #        str(student.current_session), None)
223        #    if bedticket is not None and bedticket.bed is not None:
224        #        p_item = bedticket.bed_coordinates
225        #        if bedticket.bed.__parent__.maint_fee > 0:
226        #            amount = bedticket.bed.__parent__.maint_fee
227        #        else:
228        #            # fallback
229        #            amount = academic_session.maint_fee
230        #    else:
231        #        return _(u'No bed allocated.'), None
232        elif category == 'combi' and combi:
233            categories = getUtility(IKofaUtils).COMBI_PAYMENT_CATEGORIES
234            for cat in combi:
235                fee_name = cat + '_fee'
236                cat_amount = getattr(academic_session, fee_name, 0.0)
237                if not cat_amount:
238                    return _('%s undefined.' % categories[cat]), None
239                amount += cat_amount
240                p_item += u'%s + ' % categories[cat]
241            p_item = p_item.strip(' + ')
242        elif category == 'required_combi':
243            if student.is_postgrad:
244                rp = self.REQUIRED_PAYMENTS_PG
245            elif student.is_fresh:
246                rp = self.REQUIRED_PAYMENTS_FRESH
247            else:
248                rp = self.REQUIRED_PAYMENTS_RETURNING
249            for cat in rp:
250                fee_name = cat + '_fee'
251                cat_amount = getattr(academic_session, fee_name, 0.0)
252                if not cat_amount:
253                    return _('%s undefined.' % rp[cat]), None
254                amount += cat_amount
255                p_item += u'%s + ' % rp[cat]
256            p_item = p_item.strip(' + ')
257        else:
258            fee_name = category + '_fee'
259            amount = getattr(academic_session, fee_name, 0.0)
260        if amount in (0.0, None):
261            return _('Amount could not be determined.'), None
262        if self.samePaymentMade(student, category, p_item, p_session):
263            return _('This type of payment has already been made.'), None
264        if self._isPaymentDisabled(p_session, category, student):
265            return _('This category of payments has been disabled.'), None
266        payment = createObject(u'waeup.StudentOnlinePayment')
267        timestamp = ("%d" % int(time()*10000))[1:]
268        if category in (
269            'registration', 'required_combi', 'pg_other', 'jupeb_reg'):
270            payment.provider_amt = 5000.0
271        if category in (
272            'schoolfee', 'schoolfee40') and student.is_jupeb:
273            payment.provider_amt = 5000.0
274        payment.p_id = "p%s" % timestamp
275        payment.p_category = category
276        payment.p_item = p_item
277        payment.p_session = p_session
278        payment.p_level = p_level
279        payment.p_current = p_current
280        payment.amount_auth = amount
281        payment.p_combi = combi
282        return None, payment
283
284    def setBalanceDetails(self, category, student,
285            balance_session, balance_level, balance_amount):
286        """Create a balance payment ticket and set the payment data
287        as selected by the student.
288        """
289        if category in ('schoolfee', 'schoolfee40', 'secondinstal') \
290            and balance_session > 2019:
291            rpm = self._requiredPaymentsMissing(student, balance_session)
292            if rpm:
293                return rpm, None
294        return super(
295            CustomStudentsUtils, self).setBalanceDetails(category, student,
296            balance_session, balance_level, balance_amount)
297
298    def constructMatricNumber(self, student):
299        """Fetch the matric number counter which fits the student and
300        construct the new matric number of the student.
301        """
302        next_integer = grok.getSite()['configuration'].next_matric_integer
303        if next_integer == 0:
304            return _('Matriculation number cannot be set.'), None
305        if not student.state in (
306            RETURNING, CLEARED, PAID, REGISTERED, VALIDATED):
307            return _('Matriculation number cannot be set.'), None
308        year = unicode(student.entry_session)[2:]
309        return None, "%s/%06d/%s" % (year, next_integer, student.faccode)
Note: See TracBrowser for help on using the repository browser.