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

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

Implement Required Combi Payment.

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