Ignore:
Timestamp:
2 Dec 2011, 12:46:36 (13 years ago)
Author:
Henrik Bettermann
Message:

First part of acceptance fee payment integration (under construction).

Location:
main/waeup.sirp/trunk/src/waeup/sirp
Files:
2 added
10 edited

Legend:

Unmodified
Added
Removed
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/applicant.py

    r7244 r7250  
    4545        return u"xxx_1234"
    4646
    47 class Applicant(grok.Model):
     47class Applicant(grok.Container):
    4848    grok.implements(IApplicant,IApplicantEdit)
    4949    grok.provides(IApplicant)
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py

    r7249 r7250  
    2121import sys
    2222import grok
    23 
     23from time import time
    2424from datetime import datetime, date
    2525from zope.authentication.interfaces import ILogout, IAuthentication
     
    5858from waeup.sirp.utils.helpers import string_from_bytes, file_size
    5959from waeup.sirp.widgets.datewidget import (
    60     FriendlyDateWidget, FriendlyDateDisplayWidget)
     60    FriendlyDateWidget, FriendlyDateDisplayWidget,
     61    FriendlyDatetimeDisplayWidget)
    6162from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
    6263from waeup.sirp.widgets.objectwidget import (
     
    6667    IApplicant, IApplicantEdit, IApplicantsRoot,
    6768    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab,
    68     MAX_UPLOAD_SIZE,
     69    MAX_UPLOAD_SIZE, IApplicantOnlinePayment,
    6970    )
    70 from waeup.sirp.applicants.workflow import INITIALIZED, STARTED
     71from waeup.sirp.applicants.workflow import INITIALIZED, STARTED, PAID
    7172from waeup.sirp.students.viewlets import PrimaryStudentNavTab
     73from waeup.sirp.students.interfaces import IStudentsUtils
    7274
    7375grok.context(IWAeUPObject) # Make IWAeUPObject the default context
     
    228230        return self.context.application_number
    229231
     232class OnlinePaymentBreadcrumb(Breadcrumb):
     233    """A breadcrumb for payments.
     234    """
     235    grok.context(IApplicantOnlinePayment)
     236
     237    @property
     238    def title(self):
     239        return self.context.p_id
     240
    230241class ApplicantsAuthTab(PrimaryNavTab):
    231242    """Applicants tab in primary navigation.
     
    233244    grok.context(IWAeUPObject)
    234245    grok.order(3)
    235     grok.require('waeup.viewApplicationsTab')
     246    grok.require('waeup.viewApplicantsTab')
    236247    pnav = 3
    237248    tab_title = u'Applicants'
     
    478489            return '<a href="%s">%s - %s</a>' %(url,code,title)
    479490        return ''
     491
     492    def formatDatetime(self,datetimeobj):
     493        if isinstance(datetimeobj, datetime):
     494            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
     495        else:
     496            return None
     497
     498class AcceptanceFeePaymentAddPage(grok.View):
     499    """ Page to add an online payment ticket
     500    """
     501    grok.context(IApplicant)
     502    grok.name('addafp')
     503    grok.require('waeup.payApplicant')
     504
     505    def update(self):
     506        p_category = 'acceptance'
     507        d = {}
     508        session = str(self.context.__parent__.year)
     509        try:
     510            academic_session = grok.getSite()['configuration'][session]
     511        except KeyError:
     512            self.flash('Session configuration object is not available.')
     513            return
     514        timestamp = "%d" % int(time()*1000)
     515        #order_id = "%s%s" % (student_id[1:],timestamp)
     516        for key in self.context.keys():
     517            ticket = self.context[key]
     518            if ticket.p_state == 'paid':
     519                  self.flash(
     520                      'This type of payment has already been made.')
     521                  self.redirect(self.url(self.context))
     522                  return
     523        payment = createObject(u'waeup.ApplicantOnlinePayment')
     524        payment.p_id = "p%s" % timestamp
     525        payment.p_item = self.context.__parent__.title
     526        payment.p_year = self.context.__parent__.year
     527        payment.p_category = p_category
     528        payment.amount_auth = academic_session.acceptance_fee
     529        payment.surcharge_1 = academic_session.surcharge_1
     530        payment.surcharge_2 = academic_session.surcharge_2
     531        payment.surcharge_3 = academic_session.surcharge_3
     532        self.context[payment.p_id] = payment
     533        #import pdb; pdb.set_trace()
     534        self.flash('Payment ticket created.')
     535        return
     536
     537    def render(self):
     538        usertype = getattr(self.request.principal, 'user_type', None)
     539        if usertype == 'applicant':
     540            self.redirect(self.url(self.context, '@@edit'))
     541            return
     542        self.redirect(self.url(self.context, '@@manage'))
     543        return
     544
     545
     546class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
     547    """ Page to view an online payment ticket
     548    """
     549    grok.context(IApplicantOnlinePayment)
     550    grok.name('index')
     551    grok.require('waeup.viewApplication')
     552    form_fields = grok.AutoFields(IApplicantOnlinePayment)
     553    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
     554    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
     555    pnav = 3
     556
     557    @property
     558    def title(self):
     559        return 'Online Payment Ticket %s' % self.context.p_id
     560
     561    @property
     562    def label(self):
     563        return '%s: Online Payment Ticket %s' % (
     564            self.context.__parent__.fullname,self.context.p_id)
     565
     566class PaymentReceiptActionButton(ManageActionButton):
     567    grok.order(1)
     568    grok.context(IApplicantOnlinePayment)
     569    grok.view(OnlinePaymentDisplayFormPage)
     570    grok.require('waeup.viewApplication')
     571    icon = 'actionicon_pdf.png'
     572    text = 'Download payment receipt'
     573    target = 'payment_receipt.pdf'
     574
     575    @property
     576    def target_url(self):
     577        if self.context.p_state != 'paid':
     578            return ''
     579        return self.view.url(self.view.context, self.target)
     580
     581class RequestCallbackActionButton(ManageActionButton):
     582    grok.order(2)
     583    grok.context(IApplicantOnlinePayment)
     584    grok.view(OnlinePaymentDisplayFormPage)
     585    grok.require('waeup.payApplicant')
     586    icon = 'actionicon_call.png'
     587    text = 'Request callback'
     588    target = 'callback'
     589
     590    @property
     591    def target_url(self):
     592        if self.context.p_state != 'unpaid':
     593            return ''
     594        return self.view.url(self.view.context, self.target)
     595
     596class OnlinePaymentCallbackPage(grok.View):
     597    """ Callback view
     598    """
     599    grok.context(IApplicantOnlinePayment)
     600    grok.name('callback')
     601    grok.require('waeup.payApplicant')
     602
     603    # This update method simulates a valid callback und must be
     604    # specified in the customization package. The parameters must be taken
     605    # from the incoming request.
     606    def update(self):
     607        if self.context.p_state == 'paid':
     608            self.flash('This ticket has already been paid.')
     609            return
     610        self.context.r_amount_approved = self.context.amount_auth
     611        self.context.r_card_num = u'0000'
     612        self.context.r_code = u'00'
     613        self.context.p_state = 'paid'
     614        self.context.payment_date = datetime.now()
     615        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
     616        self.context.__parent__.loggerInfo(
     617            ob_class, 'valid callback: %s' % self.context.p_id)
     618        self.wf_info = IWorkflowInfo(self.context.__parent__)
     619        self.wf_info.fireTransition('pay')
     620        self.flash('Valid callback received.')
     621        return
     622
     623    def render(self):
     624        self.redirect(self.url(self.context, '@@index'))
     625        return
     626
     627class ExportPDFPaymentSlipPage(grok.View):
     628    """Deliver a PDF slip of the context.
     629    """
     630    grok.context(IApplicantOnlinePayment)
     631    grok.name('payment_receipt.pdf')
     632    grok.require('waeup.viewApplication')
     633    form_fields = grok.AutoFields(IApplicantOnlinePayment)
     634    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
     635    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
     636    prefix = 'form'
     637
     638    @property
     639    def label(self):
     640        return 'Online Payment Receipt %s' % self.context.p_id
     641
     642    def render(self):
     643        if self.context.p_state != 'paid':
     644            self.flash('Ticket not yet paid.')
     645            self.redirect(self.url(self.context))
     646            return
     647        applicantview = ApplicantDisplayFormPage(self.context.__parent__,
     648            self.request)
     649        students_utils = getUtility(IStudentsUtils)
     650        return students_utils.renderPDF(self,'Payment', 'payment_receipt.pdf',
     651            self.context.__parent__, applicantview)
    480652
    481653class PDFActionButton(ManageActionButton):
     
    646818    manage_applications = True
    647819    pnav = 3
     820    display_actions = [['Save', 'Final Submit'],
     821                       ['Add online payment ticket','Remove selected tickets']]
    648822
    649823    def update(self):
     
    716890        return
    717891
     892    def formatDatetime(self,datetimeobj):
     893        if isinstance(datetimeobj, datetime):
     894            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
     895        else:
     896            return None
     897
     898    def unremovable(self, ticket):
     899        usertype = getattr(self.request.principal, 'user_type', None)
     900        if not usertype:
     901            return False
     902        return self.request.principal.user_type == 'applicant' and ticket.r_code
     903
     904    # This method is also used by the ApplicantEditFormPage
     905    def delPaymentTickets(self, **data):
     906        form = self.request.form
     907        if form.has_key('val_id'):
     908            child_id = form['val_id']
     909        else:
     910            self.flash('No payment selected.')
     911            self.redirect(self.url(self.context))
     912            return
     913        if not isinstance(child_id, list):
     914            child_id = [child_id]
     915        deleted = []
     916        for id in child_id:
     917            # Applicants are not allowed to remove used payment tickets
     918            if not self.unremovable(self.context[id]):
     919                try:
     920                    del self.context[id]
     921                    deleted.append(id)
     922                except:
     923                    self.flash('Could not delete %s: %s: %s' % (
     924                            id, sys.exc_info()[0], sys.exc_info()[1]))
     925        if len(deleted):
     926            self.flash('Successfully removed: %s' % ', '.join(deleted))
     927            ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
     928            self.context.loggerInfo(ob_class, 'removed: % s' % ', '.join(deleted))
     929        return
     930
     931    @grok.action('Add online payment ticket')
     932    def addPaymentTicket(self, **data):
     933        self.redirect(self.url(self.context, '@@addafp'))
     934
     935    @grok.action('Remove selected tickets')
     936    def removePaymentTickets(self, **data):
     937        self.delPaymentTickets(**data)
     938        self.redirect(self.url(self.context) + '/@@manage')
     939        return
     940
    718941class ApplicantEditFormPage(ApplicantManageFormPage):
    719942    """An applicant-centered edit view for applicant data.
     
    731954    title = u'Your Application Form'
    732955
     956    @property
     957    def display_actions(self):
     958        state = IWorkflowState(self.context).getState()
     959        if state == INITIALIZED:
     960            actions = [[],[]]
     961        elif state == STARTED:
     962            actions = [['Save'],
     963                       ['Add online payment ticket','Remove selected tickets']]
     964        elif state == PAID:
     965            actions = [['Save', 'Final Submit'],
     966                       ['Remove selected tickets']]
     967        elif state == SUBMITTED:
     968            actions = [[],[]]
     969        return actions
     970
    733971    def emit_lock_message(self):
    734972        self.flash('The requested form is locked (read-only).')
     
    748986            return 'Passport confirmation box not ticked.'
    749987        return False
     988
     989    @grok.action('Add online payment ticket')
     990    def addPaymentTicket(self, **data):
     991        self.redirect(self.url(self.context, '@@addafp'))
     992
     993    @grok.action('Remove selected tickets')
     994    def removePaymentTickets(self, **data):
     995        self.delPaymentTickets(**data)
     996        self.redirect(self.url(self.context) + '/@@edit')
     997        return
    750998
    751999    @grok.action('Save')
     
    7541002            return # error during image upload. Ignore other values
    7551003        self.applyData(self.context, **data)
    756         #self.context._p_changed = True
    7571004        self.flash('Form has been saved.')
    7581005        return
     
    7701017        # This shouldn't happen, but the application officer
    7711018        # might have forgotten to lock the form after changing the state
    772         if state != STARTED:
     1019        if state != PAID:
    7731020            self.flash('This form cannot be submitted. Wrong state!')
    7741021            return
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicantdisplaypage.pt

    r7240 r7250  
    4343  </tbody>
    4444</table>
     45<h3 i18n:translate="">Acceptance Fee Payment Tickets</h3>
     46<table class="zebra">
     47  <thead>
     48    <tr>
     49      <th>Payment Id</th>
     50      <th>Creation Date</th>
     51      <th>Payment Date</th>
     52      <th>Category</th>
     53      <th>Item</th>
     54      <th>State</th>
     55    </tr>
     56  </thead>
     57  <tbody>
     58    <tr tal:repeat="value context/values">
     59      <td> <a tal:attributes="href value/__name__">
     60      <span tal:content="value/p_id">PID</span></a></td>
     61      <td tal:content="python: view.formatDatetime(value.creation_date)">CREATION DATE</td>
     62      <td tal:content="python: view.formatDatetime(value.payment_date)">PAYMENT DATE</td>
     63      <td tal:content ="value/category">CATEGORY</td>
     64      <td tal:content ="value/p_item">ITEM</td>
     65      <td tal:content ="value/state">STATE</td>
     66    </tr>
     67  </tbody>
     68</table>
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicanteditpage.pt

    r7240 r7250  
    9595
    9696  <div class="actionButtons" tal:condition="view/availableActions">
    97     <input tal:repeat="action view/actions"
    98            tal:replace="structure action/render"
    99            />
     97    <span tal:repeat="action view/actions"
     98          tal:omit-tag="">
     99      <input tal:condition="python:action.label in view.display_actions[0]"
     100             tal:replace="structure action/render"/>
     101    </span>
     102  </div>
     103
     104  <br /><br />
     105  <h3 i18n:translate="">Acceptance Fee Payment Tickets</h3>
     106
     107  <table class="display dataTableManage">
     108    <thead>
     109    <tr>
     110      <th>&nbsp;</th>
     111      <th>Payment Id</th>
     112      <th>Creation Date</th>
     113      <th>Payment Date</th>
     114      <th>Category</th>
     115      <th>Item</th>
     116      <th>State</th>
     117    </tr>
     118    </thead>
     119    <tbody>
     120      <tr tal:repeat="cl context/values">
     121         <td>
     122          <input type="checkbox"
     123                 name="val_id"
     124                 tal:attributes="value cl/__name__"
     125         tal:condition="python: not view.unremovable(cl)" />
     126        </td>
     127        <td> <a tal:attributes="href cl/__name__">
     128        <span tal:content="cl/p_id">PID</span></a></td>
     129        <td tal:content="python: view.formatDatetime(cl.creation_date)">CREATION DATE</td>
     130        <td tal:content="python: view.formatDatetime(cl.payment_date)">PAYMENT DATE</td>
     131      <td tal:content ="cl/category">CATEGORY</td>
     132      <td tal:content ="cl/p_item">ITEM</td>
     133      <td tal:content ="cl/state">STATE</td>
     134      </tr>
     135    </tbody>
     136  </table>
     137
     138  <div class="actionButtons" tal:condition="view/availableActions">
     139    <span tal:repeat="action view/actions"
     140          tal:omit-tag="">
     141      <input tal:condition="python:action.label in view.display_actions[1]"
     142             tal:replace="structure action/render"/>
     143    </span>
    100144  </div>
    101145</form>
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/interfaces.py

    r7240 r7250  
    2929from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
    3030from zc.sourcefactory.basic import BasicSourceFactory
    31 from waeup.sirp.interfaces import IWAeUPObject, year_range, validate_email
     31from waeup.sirp.interfaces import (
     32    IWAeUPObject, year_range, validate_email, academic_sessions_vocab)
    3233from waeup.sirp.university.vocabularies import application_categories
    3334from waeup.sirp.students.vocabularies import (
     
    3839  AppCatCertificateSource,
    3940  )
     41from waeup.sirp.payments.interfaces import IOnlinePayment
    4042
    4143#: Maximum upload size for applicant passport photographs (in bytes)
     
    351353        )
    352354    student_id = schema.TextLine(
    353         title = u'Student ID',
     355        title = u'Student Id',
    354356        required = False,
    355357        readonly = True,
     
    398400        readonly = True,
    399401        )
     402
     403class IApplicantOnlinePayment(IOnlinePayment):
     404    """An applicant payment via payment gateways.
     405
     406    """
     407    p_year = schema.Choice(
     408        title = u'Payment Session',
     409        source = academic_sessions_vocab,
     410        required = False,
     411        )
     412
     413IApplicantOnlinePayment['p_year'].order = IApplicantOnlinePayment[
     414    'p_year'].order
    400415
    401416class IApplicantsContainerProvider(Interface):
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/permissions.py

    r7240 r7250  
    3030
    3131class ViewApplicationsTab(grok.Permission):
    32     grok.name('waeup.viewApplicationsTab')
     32    grok.name('waeup.viewApplicantsTab')
    3333
    3434class ViewMyApplicationDataTab(grok.Permission):
     
    3838    grok.name('waeup.manageApplication')
    3939
     40class PayApplicant(grok.Permission):
     41    grok.name('waeup.payApplicant')
     42
    4043# Local role
    4144class ApplicationOwner(grok.Role):
     
    4346    grok.title(u'Application Owner')
    4447    grok.permissions('waeup.handleApplication', 'waeup.viewApplication',
    45                      'waeup.Authenticated')
     48                     'waeup.Authenticated', 'waeup.payApplicant')
    4649
    4750# Site role
     
    5558    grok.title(u'Applications Officer')
    5659    grok.permissions('waeup.manageApplication', 'waeup.viewApplication'
    57                      'waeup.viewApplicationsTab')
     60                     'waeup.viewApplicantsTab', 'waeup.payApplicant')
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/workflow.py

    r7192 r7250  
    2828INITIALIZED = 'initialized'
    2929STARTED = 'started'
     30PAID = 'paid'
    3031SUBMITTED = 'submitted'
    3132ADMITTED = 'admitted'
     
    5051
    5152    Transition(
     53        transition_id = 'pay',
     54        title = 'Pay acceptance fee',
     55        msg = 'Fee paid',
     56        source = STARTED,
     57        destination = PAID),
     58
     59    Transition(
    5260        transition_id = 'submit',
    5361        title = 'Submit application',
    5462        msg = 'Application submitted',
    55         source = STARTED,
     63        source = PAID,
    5664        destination = SUBMITTED),
    5765
     
    8694    Transition(
    8795        transition_id = 'reset1',
    88         title = 'Reset application',
     96        title = 'Reset application to started',
    8997        msg = 'Application reset',
    9098        source = SUBMITTED,
     
    93101    Transition(
    94102        transition_id = 'reset2',
    95         title = 'Reset application',
     103        title = 'Reset application to started',
    96104        msg = 'Application reset',
    97105        source = ADMITTED,
     
    100108    Transition(
    101109        transition_id = 'reset3',
    102         title = 'Reset application',
     110        title = 'Reset application to started',
    103111        msg = 'Application reset',
    104112        source = NOT_ADMITTED,
     
    107115    Transition(
    108116        transition_id = 'reset4',
    109         title = 'Reset application',
     117        title = 'Reset application to started',
    110118        msg = 'Application reset',
    111119        source = CREATED,
    112120        destination = STARTED),
     121    Transition(
     122        transition_id = 'reset5',
     123        title = 'Reset application to paid',
     124        msg = 'Application reset',
     125        source = SUBMITTED,
     126        destination = PAID),
    113127    )
    114128
  • main/waeup.sirp/trunk/src/waeup/sirp/interfaces.py

    r7233 r7250  
    458458        )
    459459
     460    acceptance_fee = schema.Int(
     461        title = u'Acceptance Fee',
     462        default = 0,
     463        )
     464
    460465    def getSessionString():
    461466        """Returns the session string from the vocabulary.
  • main/waeup.sirp/trunk/src/waeup/sirp/payments/vocabularies.py

    r7195 r7250  
    3232    ('Transfer','transfer'),
    3333    ('Gown','gown'),
     34    ('Acceptance Fee', 'acceptance'),
    3435    )
  • main/waeup.sirp/trunk/src/waeup/sirp/permissions.py

    r7242 r7250  
    114114                     'waeup.managePortalConfiguration', 'waeup.viewApplication',
    115115                     'waeup.manageApplication', 'waeup.handleApplication',
    116                      'waeup.viewApplicationsTab',
    117                      'waeup.viewStudent', 'waeup.manageStudent', 'clearStudent',
    118                      'waeup.uploadStudentFile',
     116                     'waeup.viewApplicantsTab', 'waeup.payApplicant',
     117                     'waeup.viewStudent', 'waeup.manageStudent',
     118                     'waeup.clearStudent', 'waeup.payStudent',
     119                     'waeup.uploadStudentFile', 'waeup.showStudents',
     120                     'waeup.viewStudentsContainer','waeup.viewStudentsTab',
    119121                     'waeup.viewHostels', 'waeup.manageHostels',
    120                      'waeup.showStudents',
    121                      'waeup.viewStudentsContainer','waeup.viewStudentsTab',
    122122                     )
    123123
Note: See TracChangeset for help on using the changeset viewer.