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

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

Customize samePaymentMade.

  • Property svn:keywords set to Id
File size: 13.5 KB
RevLine 
[10765]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##
[15802]18import grok
[10765]19from time import time
[16136]20from copy import deepcopy
[10765]21from zope.component import createObject, getUtility
22from waeup.kofa.interfaces import (IKofaUtils,
[16091]23    ADMITTED, CLEARANCE, REQUESTED, CLEARED, RETURNING, PAID,
24    REGISTERED, VALIDATED)
[10765]25from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
[15563]26from kofacustom.iuokada.interfaces import MessageFactory as _
[10765]27
28class CustomStudentsUtils(NigeriaStudentsUtils):
29    """A collection of customized methods.
30
31    """
32
[16136]33    # prefix
[15645]34    STUDENT_ID_PREFIX = u'I'
35
[15649]36    SKIP_UPLOAD_VIEWLETS = (
[15654]37        'acceptanceletterupload', 'certificateupload'
[15649]38        )
[15653]39    # Maximum size of upload files in kB
40    MAX_KB = 500
[15649]41
[15656]42    #: A tuple containing the names of registration states in which changing of
43    #: passport pictures is allowed.
[16136]44
[15657]45    PORTRAIT_CHANGE_STATES = (ADMITTED, CLEARANCE,)
[15656]46
[16223]47    REQUIRED_PAYMENTS_FRESH = {
[16136]48        'registration': 'Registration Fee',
49        'book': 'Book Deposit',
50        'develop': 'Development Fee',
51        'parentsconsult': 'Parents Consultative Forum (PCF) Fee',
[16223]52        'municipal_fresh': 'Fresh Students Municipal Fee',
[16136]53        }
54
[16223]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
[16233]63    REQUIRED_PAYMENTS_PG = {
64        'pg_other': 'PG Other Charges',
65        }
66
[15660]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        """
[16377]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:
[15660]76            return _('Maximum credits exceeded.')
[16377]77        elif studylevel.total_credits > max_credits:
[15660]78            return _('Maximum credits exceeded.')
79        return
80
[16136]81    def _requiredPaymentsMissing(self, student, session):
[16257]82        # Deactivated on 29/09/20 (don't know why)
83        return
84
[16233]85        if student.is_postgrad:
86            rp = self.REQUIRED_PAYMENTS_PG
87        elif student.is_fresh:
[16223]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)
[16136]97        if len(student['payments']):
[16223]98            for category in rp.keys():
[16136]99                for ticket in student['payments'].values():
100                    if ticket.p_state == 'paid' and \
[16223]101                        ticket.p_category == category and \
[16136]102                        ticket.p_session == session:
103                        del cats_missing[category]
[16222]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())
[16136]108
[16583]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
[15645]122    def setPaymentDetails(self, category, student,
[16136]123            previous_session=None, previous_level=None, combi=[]):
[15645]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
[16223]147            if category in self.REQUIRED_PAYMENTS_FRESH.keys() \
148                + self.REQUIRED_PAYMENTS_RETURNING.keys() \
149                + ['schoolfee','schoolfee40','secondinstal'] \
[16136]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)
[15645]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.
[16138]159        if category in ('schoolfee', 'schoolfee40', 'secondinstal'):
[16136]160            rpm = self._requiredPaymentsMissing(student, p_session)
161            if rpm:
162                return rpm, None
[15645]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)
[15780]176                if category == 'schoolfee40':
[15773]177                    amount = 0.4*amount
[15780]178                elif category == 'secondinstal':
[15773]179                    amount = 0.6*amount
[15780]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:
[16091]187                    if student.state in (CLEARANCE, REQUESTED, CLEARED):
[15780]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
[15645]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
[16464]210            if student.is_postgrad:
211                amount *= 0.5
[15937]212        elif category.startswith('resit'):
213            amount = academic_session.resit_fee
214            number = int(category.strip('resit'))
[16571]215            amount *= number
[15661]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
[15676]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(' + ')
[16222]242        elif category == 'required_combi':
[16233]243            if student.is_postgrad:
244                rp = self.REQUIRED_PAYMENTS_PG
245            elif student.is_fresh:
246                rp = self.REQUIRED_PAYMENTS_FRESH
[16223]247            else:
[16233]248                rp = self.REQUIRED_PAYMENTS_RETURNING
[16222]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(' + ')
[15645]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:]
[16270]268        if category in (
[16396]269            'registration', 'required_combi', 'pg_other', 'jupeb_reg'):
[16270]270            payment.provider_amt = 5000.0
271        if category in (
272            'schoolfee', 'schoolfee40') and student.is_jupeb:
273            payment.provider_amt = 5000.0
[15645]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
[15686]281        payment.p_combi = combi
[15802]282        return None, payment
283
[16138]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        """
[16139]289        if category in ('schoolfee', 'schoolfee40', 'secondinstal') \
290            and balance_session > 2019:
[16138]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
[15802]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
[15810]305        if not student.state in (
306            RETURNING, CLEARED, PAID, REGISTERED, VALIDATED):
[15805]307            return _('Matriculation number cannot be set.'), None
[15802]308        year = unicode(student.entry_session)[2:]
[16294]309        return None, "%s/%06d/%s" % (year, next_integer, student.faccode)
Note: See TracBrowser for help on using the repository browser.