Ignore:
Timestamp:
4 Jun 2016, 04:08:45 (9 years ago)
Author:
Henrik Bettermann
Message:

Add ApplicantOnlinePaymentProcessor?.

Location:
main/waeup.kofa/trunk/src/waeup/kofa/applicants
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • main/waeup.kofa/trunk/src/waeup/kofa/applicants/batching.py

    r12872 r13872  
    2222from zope.schema import getFields
    2323from zope.interface import Interface
    24 from zope.component import queryUtility
     24from zope.component import queryUtility, getUtility
    2525from hurry.workflow.interfaces import IWorkflowState
    2626from zope.catalog.interfaces import ICatalog
    2727from waeup.kofa.interfaces import (
    2828    IBatchProcessor, IObjectConverter, FatalCSVError, IGNORE_MARKER,
    29     IObjectHistory, IUserAccount)
     29    IObjectHistory, IUserAccount, DuplicationError)
    3030from waeup.kofa.interfaces import MessageFactory as _
     31from waeup.kofa.payments.interfaces import IPayer
    3132from waeup.kofa.utils.batching import BatchProcessor
    3233from waeup.kofa.applicants.interfaces import (
    33     IApplicantsContainer, IApplicant, IApplicantUpdateByRegNo)
     34    IApplicantsContainer, IApplicant, IApplicantUpdateByRegNo,
     35    IApplicantOnlinePayment)
    3436from waeup.kofa.applicants.workflow import  IMPORTABLE_STATES, CREATED
    3537
     
    335337            return 'Applicant is blocked.'
    336338        return None
     339
     340class ApplicantOnlinePaymentProcessor(BatchProcessor):
     341    """The Applicant Online Payment Processor imports applicant payment tickets.
     342    The tickets are located in the applicant container.
     343    The only additional locator is `p_id`, the object id.
     344
     345    The `checkConversion` method checks the format of the payment identifier.
     346    In create mode it does also ensures that same p_id does not exist
     347    elsewhere. It must be portal-wide unique.
     348
     349    When adding a payment ticket, the `addEntry` method checks if a
     350    payment has already been made. If so, a `DuplicationError` is raised.
     351    """
     352    grok.implements(IBatchProcessor)
     353    grok.provides(IBatchProcessor)
     354    grok.context(Interface)
     355    util_name = 'applicantpaymentprocessor'
     356    grok.name(util_name)
     357
     358    name = _('ApplicantOnlinePayment Processor')
     359    iface = IApplicantOnlinePayment
     360    factory_name = 'waeup.ApplicantOnlinePayment'
     361
     362    location_fields = ['applicant_id', 'p_id']
     363
     364    @property
     365    def available_fields(self):
     366        af = sorted(list(set(
     367            self.location_fields + getFields(self.iface).keys())) + ['p_id',])
     368        af.remove('display_item')
     369        return af
     370
     371    def parentsExist(self, row, site):
     372        return self.getParent(row, site) is not None
     373
     374    def getParent(self, row, site):
     375        applicant_id = row['applicant_id']
     376        cat = queryUtility(ICatalog, name='applicants_catalog')
     377        results = list(
     378            cat.searchResults(applicant_id=(applicant_id, applicant_id)))
     379        if results:
     380            return results[0]
     381        return None
     382
     383    def getEntry(self, row, site):
     384        applicant = self.getParent(row, site)
     385        if applicant is None:
     386            return None
     387        p_id = row.get('p_id', None)
     388        if p_id in (None, IGNORE_MARKER):
     389            return None
     390        # We can use the hash symbol at the end of p_id in import files
     391        # to avoid annoying automatic number transformation
     392        # by Excel or Calc
     393        p_id = p_id.strip('#')
     394        entry = applicant.get(p_id)
     395        return entry
     396
     397    def entryExists(self, row, site):
     398        return self.getEntry(row, site) is not None
     399
     400    def updateEntry(self, obj, row, site, filename):
     401        """Update obj to the values given in row.
     402        """
     403        items_changed = super(ApplicantOnlinePaymentProcessor, self).updateEntry(
     404            obj, row, site, filename)
     405        applicant = self.getParent(row, site)
     406        applicant.__parent__.__parent__.logger.info(
     407            '%s - %s - %s - updated: %s'
     408            % (self.name, filename, applicant.applicant_id, items_changed))
     409        return
     410
     411    def samePaymentMade(self, applicant, category):
     412        for key in applicant.keys():
     413            ticket = applicant[key]
     414            if ticket.p_state == 'paid' and\
     415                ticket.p_category == category:
     416                  return True
     417        return False
     418
     419    def addEntry(self, obj, row, site):
     420        applicant = self.getParent(row, site)
     421        p_id = row['p_id'].strip('#')
     422        if self.samePaymentMade(applicant, obj.p_category):
     423            applicant.__parent__.__parent__.logger.info(
     424                '%s - %s - previous update cancelled'
     425                % (self.name, applicant.applicant_id))
     426            raise DuplicationError('Payment has already been made.')
     427        applicant[p_id] = obj
     428        return
     429
     430    def delEntry(self, row, site):
     431        payment = self.getEntry(row, site)
     432        applicant = self.getParent(row, site)
     433        if payment is not None:
     434            applicant.__parent__.__parent__.logger.info('%s - Payment ticket removed: %s'
     435                % (applicant.applicant_id, payment.p_id))
     436            del applicant[payment.p_id]
     437        return
     438
     439    def checkConversion(self, row, mode='ignore'):
     440        """Validates all values in row.
     441        """
     442        errs, inv_errs, conv_dict = super(
     443            ApplicantOnlinePaymentProcessor, self).checkConversion(row, mode=mode)
     444        # We have to check p_id.
     445        p_id = row.get('p_id', None)
     446        if mode == 'create' and p_id in (None, IGNORE_MARKER):
     447            timestamp = ("%d" % int(time()*10000))[1:]
     448            p_id = "p%s" % timestamp
     449            conv_dict['p_id'] = p_id
     450            return errs, inv_errs, conv_dict
     451        elif p_id in (None, IGNORE_MARKER):
     452            errs.append(('p_id','missing'))
     453            return errs, inv_errs, conv_dict
     454        else:
     455            p_id = p_id.strip('#')
     456            if not len(p_id) == 14:
     457                errs.append(('p_id','invalid length'))
     458                return errs, inv_errs, conv_dict
     459        if mode == 'create':
     460            cat = getUtility(ICatalog, name='payments_catalog')
     461            results = list(cat.searchResults(p_id=(p_id, p_id)))
     462            if len(results) > 0:
     463                sids = [IPayer(payment).id for payment in results]
     464                sids_string = ''
     465                for id in sids:
     466                    sids_string += '%s ' % id
     467                errs.append(('p_id','p_id exists in %s' % sids_string))
     468                return errs, inv_errs, conv_dict
     469        return errs, inv_errs, conv_dict
  • main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_batching.py

    r13073 r13872  
    3333from waeup.kofa.app import University
    3434from waeup.kofa.applicants.batching import (
    35     ApplicantsContainerProcessor, ApplicantProcessor)
     35    ApplicantsContainerProcessor, ApplicantProcessor,
     36    ApplicantOnlinePaymentProcessor)
    3637from waeup.kofa.applicants.container import ApplicantsContainer
    3738from waeup.kofa.applicants.applicant import Applicant
     
    3940from waeup.kofa.university.department import Department
    4041from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
    41 from waeup.kofa.interfaces import IBatchProcessor, IUserAccount
     42from waeup.kofa.interfaces import (
     43    IBatchProcessor, IUserAccount, DuplicationError)
    4244from waeup.kofa.applicants.workflow import CREATED
    4345
     
    7577
    7678APPLICANT_HEADER_FIELDS_UPDATE2 = APPLICANT_SAMPLE_DATA_UPDATE2.split(
     79    '\n')[0].split(',')
     80
     81PAYMENT_SAMPLE_DATA = open(
     82    os.path.join(os.path.dirname(__file__), 'sample_payment_data.csv'),
     83    'rb').read()
     84
     85PAYMENT_HEADER_FIELDS = PAYMENT_SAMPLE_DATA.split(
    7786    '\n')[0].split(',')
    7887
     
    225234            self.application_number]
    226235        self.workdir = tempfile.mkdtemp()
     236
     237        self.logfile = os.path.join(
     238            self.app['datacenter'].storage, 'logs', 'applicants.log')
    227239        return
    228240
     
    252264        open(self.csv_file_update, 'wb').write(APPLICANT_SAMPLE_DATA_UPDATE)
    253265        open(self.csv_file_update2, 'wb').write(APPLICANT_SAMPLE_DATA_UPDATE2)
    254 
    255         self.logfile = os.path.join(
    256             self.app['datacenter'].storage, 'logs', 'applicants.log')
    257266
    258267    def test_interface(self):
     
    415424            logcontent)
    416425        shutil.rmtree(os.path.dirname(fin_file))
     426
     427class PaymentProcessorTest(ApplicantImportExportSetup):
     428
     429    def setUp(self):
     430        super(PaymentProcessorTest, self).setUp()
     431
     432        applicant = Applicant()
     433        applicant.firstname = u'Anna2'
     434        applicant.lastname = u'Tester'
     435        applicant.applicant_id = u'dp2011_1234'
     436        self.app['applicants']['dp2011'].addApplicant(applicant)
     437        payment = createObject(u'waeup.ApplicantOnlinePayment')
     438        payment.p_id = 'p120'
     439        payment.p_session = 2012
     440        payment.p_category = 'application'
     441        payment.p_state = 'paid'
     442        applicant['p120'] = payment
     443        self.applicant2 = applicant
     444        self.processor = ApplicantOnlinePaymentProcessor()
     445        self.csv_file = os.path.join(
     446            self.workdir, 'sample_payment_data.csv')
     447        open(self.csv_file, 'wb').write(PAYMENT_SAMPLE_DATA)
     448
     449    def test_interface(self):
     450        # Make sure we fulfill the interface contracts.
     451        assert verifyObject(IBatchProcessor, self.processor) is True
     452        assert verifyClass(
     453            IBatchProcessor, ApplicantOnlinePaymentProcessor) is True
     454
     455    def test_getEntry(self):
     456        assert self.processor.getEntry(
     457            dict(applicant_id='ID_NONE', p_id='nonsense'), self.app) is None
     458        assert self.processor.getEntry(
     459            dict(applicant_id=self.applicant2.applicant_id, p_id='p120'),
     460            self.app) is self.applicant2['p120']
     461        assert self.processor.getEntry(
     462            dict(applicant_id=self.applicant2.applicant_id, p_id='p120#'),
     463            self.app) is self.applicant2['p120']
     464
     465    def test_delEntry(self):
     466        assert self.processor.getEntry(
     467            dict(applicant_id=self.applicant2.applicant_id, p_id='p120'),
     468            self.app) is self.applicant2['p120']
     469        self.assertEqual(len(self.applicant2.keys()),1)
     470        self.processor.delEntry(
     471            dict(applicant_id=self.applicant2.applicant_id, p_id='p120'),
     472            self.app)
     473        assert self.processor.getEntry(
     474            dict(applicant_id=self.applicant2.applicant_id, p_id='p120'),
     475            self.app) is None
     476        self.assertEqual(len(self.applicant.keys()),0)
     477
     478    def test_addEntry(self):
     479        self.assertEqual(len(self.applicant2.keys()),1)
     480        self.processor.delEntry(
     481            dict(applicant_id=self.applicant2.applicant_id, p_id='p120'),
     482            self.app)
     483        payment1 = createObject(u'waeup.ApplicantOnlinePayment')
     484        payment1.p_category = 'application'
     485        payment1.p_id = 'p234'
     486        self.processor.addEntry(
     487            payment1, dict(applicant_id=self.applicant2.applicant_id, p_id='p234'),
     488            self.app)
     489        self.assertEqual(len(self.applicant2.keys()),1)
     490        self.assertEqual(self.applicant2['p234'].p_id, 'p234')
     491        # Same payment must not exist.
     492        payment1.p_state = 'paid'
     493        payment2 = createObject(u'waeup.ApplicantOnlinePayment')
     494        payment2.p_id = 'p456'
     495        payment2.p_category = 'application'
     496        self.assertRaises(
     497            DuplicationError, self.processor.addEntry, payment2,
     498            dict(applicant_id=self.applicant2.applicant_id, p_id='p456'), self.app)
     499
     500    def test_import(self):
     501        num, num_warns, fin_file, fail_file = self.processor.doImport(
     502            self.csv_file, PAYMENT_HEADER_FIELDS,'create')
     503        self.assertEqual(num_warns,1)
     504        fail_file = open(fail_file).read()
     505        self.assertTrue('Payment has already been made' in fail_file)
     506        self.processor.delEntry(
     507            dict(applicant_id=self.applicant2.applicant_id, p_id='p120'),
     508            self.app)
     509        num, num_warns, fin_file, fail_file = self.processor.doImport(
     510            self.csv_file, PAYMENT_HEADER_FIELDS,'create')
     511        self.assertEqual(num_warns,0)
     512        payment = self.processor.getEntry(dict(applicant_id='dp2011_1234',
     513            p_id='p1266236341955'), self.app)
     514        self.assertEqual(payment.p_id, 'p1266236341955')
     515        cdate = payment.creation_date.strftime("%Y-%m-%d %H:%M:%S")
     516        # Ooooh, still the old problem, see
     517        # http://mail.dzug.org/mailman/archives/zope/2006-August/001153.html.
     518        # WAT is interpreted as GMT-1 and not GMT+1
     519        self.assertEqual(cdate, '2010-11-25 21:16:33')
     520        self.assertEqual(str(payment.creation_date.tzinfo),'UTC')
     521        shutil.rmtree(os.path.dirname(fin_file))
     522        logcontent = open(self.logfile).read()
     523        # Logging message from updateEntry
     524        self.assertTrue(
     525            'INFO - system - ApplicantOnlinePayment Processor - dp2011_1234 - '
     526            'previous update cancelled'
     527            in logcontent)
     528        self.assertTrue(
     529            'INFO - system - ApplicantOnlinePayment Processor - '
     530            'sample_payment_data - dp2011_1234 - updated: p_id=p1266236341955, '
     531            'creation_date=2010-11-25 21:16:33.757000+00:00, '
     532            'r_amount_approved=19500.1, p_category=application, '
     533            'amount_auth=19500.1, p_session=2015, p_state=paid'
     534            in logcontent)
Note: See TracChangeset for help on using the changeset viewer.