Index: main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py	(revision 12620)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py	(revision 12623)
@@ -38,5 +38,5 @@
     IBatchProcessor, FatalCSVError, IObjectConverter, IUserAccount,
     IObjectHistory, VALIDATED, REGISTERED, IGNORE_MARKER)
-from waeup.kofa.interfaces import IKofaUtils
+from waeup.kofa.interfaces import IKofaUtils, DuplicationError
 from waeup.kofa.interfaces import MessageFactory as _
 from waeup.kofa.students.interfaces import (
@@ -743,7 +743,21 @@
         return
 
+    def samePaymentMade(self, student, category, p_session):
+        for key in student['payments'].keys():
+            ticket = student['payments'][key]
+            if ticket.p_state == 'paid' and\
+               ticket.p_category == category and \
+               ticket.p_session == p_session:
+                  return True
+        return False
+
     def addEntry(self, obj, row, site):
         parent = self.getParent(row, site)
+        student = parent.student
         p_id = row['p_id'].strip('#')
+        # Requirement added on 19/02/2015: same payment must not exist.
+        #if not None in (obj.p_category, obj.p_session):
+        if self.samePaymentMade(student, obj.p_category, obj.p_session):
+            raise DuplicationError('Same payment has already been made.')
         if len(p_id.split('-')) != 3 and not p_id.startswith('p'):
             # For data migration from old SRP
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_create_payment_data.csv
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_create_payment_data.csv	(revision 12620)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_create_payment_data.csv	(revision 12623)
@@ -1,51 +1,15 @@
-p_state,p_category,reg_number,session_id,r_amount_approved,p_item,amount_auth,r_card_num,r_code,creation_date,type
+p_state,p_category,reg_number,p_session,r_amount_approved,p_item,amount_auth,r_card_num,r_code,creation_date,type
+paid,schoolfee,1,2003,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
+paid,schoolfee,1,2004,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
+paid,schoolfee,1,2005,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
+paid,schoolfee,1,2006,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
+paid,schoolfee,1,2007,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
+paid,schoolfee,1,2008,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
+paid,schoolfee,1,2009,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
 paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
-paid,schoolfee,1,2010,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
+paid,schoolfee,1,2011,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
+paid,schoolfee,1,2012,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
+paid,schoolfee,1,2013,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
+paid,schoolfee,1,2014,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
+paid,schoolfee,1,2015,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
+paid,schoolfee,1,2015,19500,BTECHBDT,19500,942,0,2010/11/26 19:59:33.744 GMT+1,online
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_payment_data.csv
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_payment_data.csv	(revision 12620)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_payment_data.csv	(revision 12623)
@@ -1,3 +1,3 @@
-matric_number,p_state,p_category,p_id,reg_number,session_id,r_amount_approved,p_item,amount_auth,r_card_num,r_code,creation_date,type,p_current
+matric_number,p_state,p_category,p_id,reg_number,p_session,r_amount_approved,p_item,amount_auth,r_card_num,r_code,creation_date,type,p_current
 ,paid,schoolfee,3816951290797973744#,1,2010,19500,BTECHBDT,19500,0942,00,2010/11/26 19:59:33.744 GMT+1,online,1
 100001,unpaid,schoolfee,3816951290712593757,,2010,0,BTECHBDT,19500.1,0942,Z0,2010-11-25 20:16:33.757 WAT,online,0
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py	(revision 12620)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py	(revision 12623)
@@ -34,5 +34,6 @@
 
 from waeup.kofa.app import University
-from waeup.kofa.interfaces import IBatchProcessor, FatalCSVError, IUserAccount
+from waeup.kofa.interfaces import (
+    IBatchProcessor, FatalCSVError, IUserAccount, DuplicationError)
 from waeup.kofa.students.batching import (
     StudentProcessor, StudentStudyCourseProcessor,
@@ -982,4 +983,7 @@
         payment = createObject(u'waeup.StudentOnlinePayment')
         payment.p_id = 'p120'
+        payment.p_session = 2012
+        payment.p_category = 'schoolfee'
+        payment.p_state = 'paid'
         self.student['payments'][payment.p_id] = payment
 
@@ -1046,4 +1050,12 @@
         self.assertEqual(len(self.student['payments'].keys()),3)
         self.assertEqual(self.student['payments']['p560'].p_id, 'p560')
+        # Requirement added on 19/02/2015: same payment must not exist.
+        payment3 = createObject(u'waeup.StudentOnlinePayment')
+        payment3.p_id = 'p456'
+        payment3.p_session = 2012
+        payment3.p_category = 'schoolfee'
+        self.assertRaises(
+            DuplicationError, self.processor.addEntry, payment3,
+            dict(student_id=self.student.student_id, p_id='p456'), self.app)
 
     def test_checkConversion(self):
@@ -1110,4 +1122,5 @@
             'p_item=BTECHBDT, creation_date=2010-02-15 13:19:01+00:00, '
             'p_category=schoolfee, amount_auth=19500.0, p_current=True, '
+            'p_session=2009, '
             'p_id=p1266236341955, r_code=00, r_amount_approved=19500.0, '
             'p_state=paid'
@@ -1118,4 +1131,5 @@
             'p_item=BTECHBDT, creation_date=2010-02-15 13:19:01+00:00, '
             'p_category=schoolfee, amount_auth=19500.6, p_current=True, '
+            'p_session=2011, '
             'p_id=ABC-11-1234567890, r_code=SC, r_amount_approved=19500.0, '
             'p_state=paid'
@@ -1153,7 +1167,14 @@
         num, num_warns, fin_file, fail_file = self.processor.doImport(
             self.csv_file2, PAYMENT_CREATE_HEADER_FIELDS,'create')
-        self.assertEqual(num_warns,0)
-        shutil.rmtree(os.path.dirname(fin_file))
-        self.assertEqual(len(self.app['students']['X666666']['payments']), 50)
+        # One payment with same session and category exists
+        self.assertEqual(num_warns,1)
+        content = open(fail_file).read()
+        self.assertTrue(
+            '1,942,online,BTECHBDT,2010/11/26 19:59:33.744 GMT+1,0,'
+            '19500,schoolfee,19500,2015,paid,'
+            'Same payment has already been made. Skipping.'
+            in content)
+        shutil.rmtree(os.path.dirname(fin_file))
+        self.assertEqual(len(self.app['students']['X666666']['payments']), 13)
 
 class StudentVerdictProcessorTest(StudentImportExportSetup):
