source: main/waeup.ikoba/trunk/src/waeup/ikoba/customers/browser.py @ 12772

Last change on this file since 12772 was 12772, checked in by Henrik Bettermann, 10 years ago

Prepare contract payment receipt.

  • Property svn:keywords set to Id
File size: 60.5 KB
Line 
1## $Id: browser.py 12772 2015-03-16 10:02:14Z henrik $
2##
3## Copyright (C) 2014 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##
18"""UI components for customers and related components.
19"""
20import grok
21import os
22from urllib import urlencode
23from zope.event import notify
24from zope.i18n import translate
25from zope.catalog.interfaces import ICatalog
26from zope.component import queryUtility, getUtility, createObject
27from zope.security import checkPermission
28from hurry.workflow.interfaces import (
29    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
30from waeup.ikoba.interfaces import MessageFactory as _
31from waeup.ikoba.interfaces import (
32    IContactForm, IObjectHistory, IIkobaObject, IIkobaUtils, IExtFileStore,
33    IPasswordValidator, IUserAccount,
34    STARTED, VERIFIED, REJECTED, EXPIRED, CREATED, REQUESTED,
35    APPROVED, PROVISIONALLY, AWAITING)
36from waeup.ikoba.browser.layout import (
37    IkobaPage, IkobaEditFormPage, IkobaAddFormPage, IkobaDisplayFormPage,
38    NullValidator, jsaction, action, UtilityView)
39from waeup.ikoba.browser.pages import ContactAdminForm
40from waeup.ikoba.browser.breadcrumbs import Breadcrumb
41from waeup.ikoba.browser.interfaces import ICaptchaManager
42from waeup.ikoba.mandates.mandate import PasswordMandate
43from waeup.ikoba.payments.payment import (
44    get_payments_from_payer_id,
45    get_payments_from_payable_id,
46    find_payable_from_payable_id,
47    get_payment_providers
48    )
49from waeup.ikoba.payments.interfaces import (
50    IPaymentGatewayServicesLister, IPaymentGatewayService, IPayer, IPayable,
51    payment_states
52    )
53from waeup.ikoba.payments.catalog import search as search_payments
54from waeup.ikoba.widgets.hrefwidget import HREFDisplayWidget
55from waeup.ikoba.utils.helpers import (
56    get_current_principal, format_date)
57from waeup.ikoba.customers.interfaces import (
58    ICustomer, ICustomersContainer, ICustomerRequestPW, ICustomersUtils,
59    ICustomerDocument, ICustomerDocumentsContainer, ICustomerCreate,
60    ICustomerPDFDocument, IContractsContainer, IContract,
61    IContractSelectProduct,
62    )
63from waeup.ikoba.customers.catalog import search as search_customers
64from waeup.ikoba.customers.workflow import PAYMENT_TRANSITIONS
65
66
67grok.context(IIkobaObject)
68
69WARNING_CUST = _('You can not edit some data after final submission.'
70            ' You really want to submit?')
71
72WARNING_DOC = _('You can not edit your document after final submission.'
73            ' You really want to submit?')
74
75WARNING_CON = _('You can not edit your contract after final submission.'
76            ' You really want to submit?')
77
78
79# Save function used for save methods in pages
80def msave(view, **data):
81    changed_fields = view.applyData(view.context, **data)
82    # Turn list of lists into single list
83    if changed_fields:
84        changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
85    if 'product_object' in changed_fields and data['product_object'] is not None:
86        view.context.last_product_id = data['product_object'].product_id
87        notify(grok.ObjectModifiedEvent(view.context))
88    fields_string = ' + '.join(changed_fields)
89    view.flash(_('Form has been saved.'))
90    if fields_string:
91        view.context.writeLogMessage(
92            view, '%s - saved: %s' % (view.context.__name__, fields_string))
93    return
94
95
96def emit_lock_message(view):
97    """Flash a lock message.
98    """
99    view.flash(_('The requested form is locked (read-only).'), type="warning")
100    view.redirect(view.url(view.context))
101    return
102
103def isCustomer(principal):
104    return getattr(principal, 'user_type', None) == 'customer'
105
106
107class CustomersBreadcrumb(Breadcrumb):
108    """A breadcrumb for the customers container.
109    """
110    grok.context(ICustomersContainer)
111    title = _('Customers')
112
113    @property
114    def target(self):
115        user = get_current_principal()
116        if getattr(user, 'user_type', None) == 'customer':
117            return None
118        return self.viewname
119
120
121class CustomerBreadcrumb(Breadcrumb):
122    """A breadcrumb for the customer container.
123    """
124    grok.context(ICustomer)
125
126    def title(self):
127        return self.context.display_fullname
128
129
130class CustomersContainerPage(IkobaPage):
131    """The standard view for customer containers.
132    """
133    grok.context(ICustomersContainer)
134    grok.name('index')
135    grok.require('waeup.viewCustomersContainer')
136    grok.template('containerpage')
137    label = _('Find customers')
138    search_button = _('Find customer(s)')
139    pnav = 4
140
141    def update(self, *args, **kw):
142        form = self.request.form
143        self.hitlist = []
144        if form.get('searchtype', None) == 'suspended':
145            self.searchtype = form['searchtype']
146            self.searchterm = None
147        elif 'searchterm' in form and form['searchterm']:
148            self.searchterm = form['searchterm']
149            self.searchtype = form['searchtype']
150        elif 'old_searchterm' in form:
151            self.searchterm = form['old_searchterm']
152            self.searchtype = form['old_searchtype']
153        else:
154            if 'search' in form:
155                self.flash(_('Empty search string'), type="warning")
156            return
157        if self.searchtype == 'current_session':
158            try:
159                self.searchterm = int(self.searchterm)
160            except ValueError:
161                self.flash(_('Only year dates allowed (e.g. 2011).'),
162                           type="danger")
163                return
164        self.hitlist = search_customers(query=self.searchterm,
165            searchtype=self.searchtype, view=self)
166        if not self.hitlist:
167            self.flash(_('No customer found.'), type="warning")
168        return
169
170
171class CustomersContainerManagePage(IkobaPage):
172    """The manage page for customer containers.
173    """
174    grok.context(ICustomersContainer)
175    grok.name('manage')
176    grok.require('waeup.manageCustomer')
177    grok.template('containermanagepage')
178    pnav = 4
179    label = _('Manage customer section')
180    search_button = _('Find customer(s)')
181    remove_button = _('Remove selected')
182
183    def update(self, *args, **kw):
184        form = self.request.form
185        self.hitlist = []
186        if form.get('searchtype', None) == 'suspended':
187            self.searchtype = form['searchtype']
188            self.searchterm = None
189        elif 'searchterm' in form and form['searchterm']:
190            self.searchterm = form['searchterm']
191            self.searchtype = form['searchtype']
192        elif 'old_searchterm' in form:
193            self.searchterm = form['old_searchterm']
194            self.searchtype = form['old_searchtype']
195        else:
196            if 'search' in form:
197                self.flash(_('Empty search string'), type="warning")
198            return
199        if self.searchtype == 'current_session':
200            try:
201                self.searchterm = int(self.searchterm)
202            except ValueError:
203                self.flash(_('Only year dates allowed (e.g. 2011).'),
204                           type="danger")
205                return
206        if not 'entries' in form:
207            self.hitlist = search_customers(query=self.searchterm,
208                searchtype=self.searchtype, view=self)
209            if not self.hitlist:
210                self.flash(_('No customer found.'), type="warning")
211            if 'remove' in form:
212                self.flash(_('No item selected.'), type="warning")
213            return
214        entries = form['entries']
215        if isinstance(entries, basestring):
216            entries = [entries]
217        deleted = []
218        for entry in entries:
219            if 'remove' in form:
220                del self.context[entry]
221                deleted.append(entry)
222        self.hitlist = search_customers(query=self.searchterm,
223            searchtype=self.searchtype, view=self)
224        if len(deleted):
225            self.flash(_('Successfully removed: ${a}',
226                mapping={'a': ','.join(deleted)}))
227        return
228
229
230class CustomerAddFormPage(IkobaAddFormPage):
231    """Add-form to add a customer.
232    """
233    grok.context(ICustomersContainer)
234    grok.require('waeup.manageCustomer')
235    grok.name('addcustomer')
236    form_fields = grok.AutoFields(ICustomer).select(
237        'firstname', 'middlename', 'lastname', 'reg_number')
238    label = _('Add customer')
239    pnav = 4
240
241    @action(_('Create customer record'), style='primary')
242    def addCustomer(self, **data):
243        customer = createObject(u'waeup.Customer')
244        self.applyData(customer, **data)
245        self.context.addCustomer(customer)
246        self.flash(_('Customer created.'))
247        self.redirect(self.url(self.context[customer.customer_id], 'index'))
248        return
249
250
251class LoginAsCustomerStep1(IkobaEditFormPage):
252    """ View to temporarily set a customer password.
253    """
254    grok.context(ICustomer)
255    grok.name('loginasstep1')
256    grok.require('waeup.loginAsCustomer')
257    grok.template('loginasstep1')
258    pnav = 4
259
260    def label(self):
261        return _(u'Set temporary password for ${a}',
262            mapping={'a': self.context.display_fullname})
263
264    @action('Set password now', style='primary')
265    def setPassword(self, *args, **data):
266        ikoba_utils = getUtility(IIkobaUtils)
267        password = ikoba_utils.genPassword()
268        self.context.setTempPassword(self.request.principal.id, password)
269        self.context.writeLogMessage(
270            self, 'temp_password generated: %s' % password)
271        args = {'password': password}
272        self.redirect(self.url(self.context) +
273            '/loginasstep2?%s' % urlencode(args))
274        return
275
276
277class LoginAsCustomerStep2(IkobaPage):
278    """ View to temporarily login as customer with a temporary password.
279    """
280    grok.context(ICustomer)
281    grok.name('loginasstep2')
282    grok.require('waeup.Public')
283    grok.template('loginasstep2')
284    login_button = _('Login now')
285    pnav = 4
286
287    def label(self):
288        return _(u'Login as ${a}',
289            mapping={'a': self.context.customer_id})
290
291    def update(self, SUBMIT=None, password=None):
292        self.password = password
293        if SUBMIT is not None:
294            self.flash(_('You successfully logged in as customer.'))
295            self.redirect(self.url(self.context))
296        return
297
298
299class CustomerBaseDisplayFormPage(IkobaDisplayFormPage):
300    """ Page to display customer base data
301    """
302    grok.context(ICustomer)
303    grok.name('index')
304    grok.require('waeup.viewCustomer')
305    grok.template('basepage')
306    pnav = 4
307
308    @property
309    def form_fields(self):
310        return grok.AutoFields(
311            self.context.form_fields_interface).omit(
312            'password', 'suspended', 'suspended_comment')
313
314    @property
315    def label(self):
316        if self.context.suspended:
317            return _('${a}: Base Data (account deactivated)',
318                mapping={'a': self.context.display_fullname})
319        return  _('${a}: Base Data',
320            mapping={'a': self.context.display_fullname})
321
322    @property
323    def hasPassword(self):
324        if self.context.password:
325            return _('set')
326        return _('unset')
327
328    @property
329    def is_requestable(self):
330        if self.context.state in (REQUESTED, PROVISIONALLY, APPROVED):
331            return False
332        return True
333
334    def update(self):
335        # Fire transition if customer logs in for the first time
336        usertype = getattr(self.request.principal, 'user_type', None)
337        if usertype == 'customer' and \
338            IWorkflowState(self.context).getState() == CREATED:
339            IWorkflowInfo(self.context).fireTransition('start')
340        return
341
342
343class ContactCustomerForm(ContactAdminForm):
344    grok.context(ICustomer)
345    grok.name('contactcustomer')
346    grok.require('waeup.viewCustomer')
347    pnav = 4
348    form_fields = grok.AutoFields(IContactForm).select('subject', 'body')
349
350
351    def update(self, subject=u'', body=u''):
352        super(ContactCustomerForm, self).update()
353        self.form_fields.get('subject').field.default = subject
354        self.form_fields.get('body').field.default = body
355        return
356
357    def label(self):
358        return _(u'Send message to ${a}',
359            mapping={'a': self.context.display_fullname})
360
361    @action('Send message now', style='primary')
362    def send(self, *args, **data):
363        try:
364            email = self.request.principal.email
365        except AttributeError:
366            email = self.config.email_admin
367        usertype = getattr(self.request.principal,
368                           'user_type', 'system').title()
369        ikoba_utils = getUtility(IIkobaUtils)
370        success = ikoba_utils.sendContactForm(
371                self.request.principal.title, email,
372                self.context.display_fullname, self.context.email,
373                self.request.principal.id,usertype,
374                self.config.name,
375                data['body'], data['subject'])
376        if success:
377            self.flash(_('Your message has been sent.'))
378        else:
379            self.flash(_('An smtp server error occurred.'), type="danger")
380        return
381
382
383class CustomerBaseManageFormPage(IkobaEditFormPage):
384    """ View to manage customer base data
385    """
386    grok.context(ICustomer)
387    grok.name('manage_base')
388    grok.require('waeup.manageCustomer')
389    grok.template('basemanagepage')
390    label = _('Manage base data')
391    pnav = 4
392    deletion_warning = _('Are you sure?')
393
394    @property
395    def form_fields(self):
396        return grok.AutoFields(
397            self.context.form_fields_interface).omit(
398            'customer_id', 'suspended')
399
400    def update(self):
401        super(CustomerBaseManageFormPage, self).update()
402        self.wf_info = IWorkflowInfo(self.context)
403        return
404
405    @action(_('Save'), style='primary')
406    def save(self, **data):
407        form = self.request.form
408        password = form.get('password', None)
409        password_ctl = form.get('control_password', None)
410        if password:
411            validator = getUtility(IPasswordValidator)
412            errors = validator.validate_password(password, password_ctl)
413            if errors:
414                self.flash(' '.join(errors), type="danger")
415                return
416        changed_fields = self.applyData(self.context, **data)
417        # Turn list of lists into single list
418        if changed_fields:
419            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
420        else:
421            changed_fields = []
422        if password:
423            # Now we know that the form has no errors and can set password
424            IUserAccount(self.context).setPassword(password)
425            changed_fields.append('password')
426        fields_string = ' + '.join(changed_fields)
427        self.flash(_('Form has been saved.'))
428        if fields_string:
429            self.context.writeLogMessage(self, 'saved: % s' % fields_string)
430        return
431
432
433class CustomerTriggerTransitionFormPage(IkobaEditFormPage):
434    """ View to trigger customer workflow transitions
435    """
436    grok.context(ICustomer)
437    grok.name('trigtrans')
438    grok.require('waeup.triggerTransition')
439    grok.template('trigtrans')
440    label = _('Trigger registration transition')
441    pnav = 4
442
443    def getTransitions(self):
444        """Return a list of dicts of allowed transition ids and titles.
445
446        Each list entry provides keys ``name`` and ``title`` for
447        internal name and (human readable) title of a single
448        transition.
449        """
450        wf_info = IWorkflowInfo(self.context)
451        allowed_transitions = [t for t in wf_info.getManualTransitions()]
452        return [dict(name='', title=_('No transition'))] +[
453            dict(name=x, title=y) for x, y in allowed_transitions]
454
455    @action(_('Apply'), style='primary')
456    def apply(self, **data):
457        form = self.request.form
458        if 'transition' in form and form['transition']:
459            transition_id = form['transition']
460            wf_info = IWorkflowInfo(self.context)
461            wf_info.fireTransition(transition_id)
462            self.flash(_("Transition '%s' executed." % transition_id))
463            self.redirect(self.url(self.context))
464        return
465
466
467class CustomerActivatePage(UtilityView, grok.View):
468    """ Activate customer account
469    """
470    grok.context(ICustomer)
471    grok.name('activate')
472    grok.require('waeup.manageCustomer')
473
474    def update(self):
475        self.context.suspended = False
476        self.context.writeLogMessage(self, 'account activated')
477        history = IObjectHistory(self.context)
478        history.addMessage('Customer account activated')
479        self.flash(_('Customer account has been activated.'))
480        self.redirect(self.url(self.context))
481        return
482
483    def render(self):
484        return
485
486
487class CustomerDeactivatePage(UtilityView, grok.View):
488    """ Deactivate customer account
489    """
490    grok.context(ICustomer)
491    grok.name('deactivate')
492    grok.require('waeup.manageCustomer')
493
494    def update(self):
495        self.context.suspended = True
496        self.context.writeLogMessage(self, 'account deactivated')
497        history = IObjectHistory(self.context)
498        history.addMessage('Customer account deactivated')
499        self.flash(_('Customer account has been deactivated.'))
500        self.redirect(self.url(self.context))
501        return
502
503    def render(self):
504        return
505
506
507class CustomerHistoryPage(IkobaPage):
508    """ Page to display customer history
509    """
510    grok.context(ICustomer)
511    grok.name('history')
512    grok.require('waeup.viewCustomer')
513    grok.template('customerhistory')
514    pnav = 4
515
516    @property
517    def label(self):
518        return _('${a}: History', mapping={'a':self.context.display_fullname})
519
520
521class CustomerRequestPasswordPage(IkobaAddFormPage):
522    """Captcha'd password request page for customers.
523    """
524    grok.name('requestpw')
525    grok.require('waeup.Anonymous')
526    grok.template('requestpw')
527    form_fields = grok.AutoFields(ICustomerRequestPW)
528    label = _('Request password for first-time login')
529
530    def update(self):
531        # Handle captcha
532        self.captcha = getUtility(ICaptchaManager).getCaptcha()
533        self.captcha_result = self.captcha.verify(self.request)
534        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
535        return
536
537    def _redirect(self, email, password, customer_id):
538        # Forward only email address to landing page in base package.
539        self.redirect(self.url(self.context, 'requestpw_complete',
540            data=dict(email=email)))
541        return
542
543    def _pw_used(self):
544        # XXX: False if password has not been used. We need an extra
545        #      attribute which remembers if customer logged in.
546        return True
547
548    @action(_('Send login credentials to email address'), style='primary')
549    def get_credentials(self, **data):
550        if not self.captcha_result.is_valid:
551            # Captcha will display error messages automatically.
552            # No need to flash something.
553            return
554        number = data.get('number','')
555        firstname = data.get('firstname','')
556        cat = getUtility(ICatalog, name='customers_catalog')
557        results = list(
558            cat.searchResults(reg_number=(number, number)))
559        if results:
560            customer = results[0]
561            if getattr(customer,'firstname',None) is None:
562                self.flash(_('An error occurred.'), type="danger")
563                return
564            elif customer.firstname.lower() != firstname.lower():
565                # Don't tell the truth here. Anonymous must not
566                # know that a record was found and only the firstname
567                # verification failed.
568                self.flash(_('No customer found.'), type="warning")
569                return
570            elif customer.password is not None and self._pw_used:
571                self.flash(_('Your password has already been set and used. '
572                             'Please proceed to the login page.'),
573                           type="warning")
574                return
575            # Store email address but nothing else.
576            customer.email = data['email']
577            notify(grok.ObjectModifiedEvent(customer))
578        else:
579            # No record found, this is the truth.
580            self.flash(_('No customer found.'), type="warning")
581            return
582
583        ikoba_utils = getUtility(IIkobaUtils)
584        password = ikoba_utils.genPassword()
585        mandate = PasswordMandate()
586        mandate.params['password'] = password
587        mandate.params['user'] = customer
588        site = grok.getSite()
589        site['mandates'].addMandate(mandate)
590        # Send email with credentials
591        args = {'mandate_id':mandate.mandate_id}
592        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
593        url_info = u'Confirmation link: %s' % mandate_url
594        msg = _('You have successfully requested a password for the')
595        if ikoba_utils.sendCredentials(IUserAccount(customer),
596            password, url_info, msg):
597            email_sent = customer.email
598        else:
599            email_sent = None
600        self._redirect(email=email_sent, password=password,
601            customer_id=customer.customer_id)
602        ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
603        self.context.logger.info(
604            '%s - %s (%s) - %s' % (ob_class, number, customer.customer_id, email_sent))
605        return
606
607
608class CustomerCreateAccountPage(IkobaAddFormPage):
609    """Captcha'd account creation page for customers.
610    """
611    grok.name('createaccount')
612    grok.require('waeup.Anonymous')
613    grok.template('createaccount')
614    form_fields = grok.AutoFields(ICustomerCreate)
615    label = _('Create customer account')
616
617    def update(self):
618        # Handle captcha
619        self.captcha = getUtility(ICaptchaManager).getCaptcha()
620        self.captcha_result = self.captcha.verify(self.request)
621        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
622        return
623
624    def _redirect(self, email, password, customer_id):
625        # Forward only email address to landing page in base package.
626        self.redirect(self.url(self.context, 'requestpw_complete',
627            data=dict(email=email)))
628        return
629
630    @action(_('Send login credentials to email address'), style='primary')
631    def create_account(self, **data):
632        if not self.captcha_result.is_valid:
633            # Captcha will display error messages automatically.
634            # No need to flash something.
635            return
636        customer = createObject(u'waeup.Customer')
637        customer.firstname = data.get('firstname','')
638        customer.middlename = data.get('middlename','')
639        customer.lastname = data.get('lastname','')
640        customer.email = data.get('email','')
641        self.context['customers'].addCustomer(customer)
642        ikoba_utils = getUtility(IIkobaUtils)
643        password = ikoba_utils.genPassword()
644        mandate = PasswordMandate()
645        mandate.params['password'] = password
646        mandate.params['user'] = customer
647        site = grok.getSite()
648        site['mandates'].addMandate(mandate)
649        # Send email with credentials
650        args = {'mandate_id':mandate.mandate_id}
651        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
652        url_info = u'Confirmation link: %s' % mandate_url
653        msg = _('You have successfully created a customer account for the')
654        if ikoba_utils.sendCredentials(IUserAccount(customer),
655            password, url_info, msg):
656            email_sent = customer.email
657        else:
658            email_sent = None
659        self._redirect(email=email_sent, password=password,
660            customer_id=customer.customer_id)
661        ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
662        self.context.logger.info(
663            '%s - %s - %s' % (ob_class, customer.customer_id, email_sent))
664        return
665
666
667class CustomerRequestPasswordEmailSent(IkobaPage):
668    """Landing page after successful password request.
669
670    """
671    grok.name('requestpw_complete')
672    grok.require('waeup.Public')
673    grok.template('requestpwmailsent')
674    label = _('Your request was successful.')
675
676    def update(self, email=None, customer_id=None, password=None):
677        self.email = email
678        self.password = password
679        self.customer_id = customer_id
680        return
681
682# Pages for customers
683
684class CustomerFilesUploadPage(IkobaPage):
685    """ View to upload files by customer
686
687    We use this page only to upload a passport picture (portrait).
688    """
689    grok.context(ICustomer)
690    grok.name('upload_files')
691    grok.require('waeup.handleCustomer')
692    grok.template('filesuploadpage')
693    label = _('Upload files')
694    pnav = 4
695    deletion_warning = _('Are you sure?')
696
697    def update(self, CANCEL=None):
698        CUSTMANAGE_STATES = getUtility(
699            ICustomersUtils).CUSTMANAGE_CUSTOMER_STATES
700        if self.context.state not in CUSTMANAGE_STATES:
701            emit_lock_message(self)
702            return
703        if CANCEL is not None:
704            self.redirect(self.url(self.context))
705            return
706        super(CustomerFilesUploadPage, self).update()
707        return
708
709
710class CustomerBaseEditFormPage(IkobaEditFormPage):
711    """ View to edit customer base data
712    """
713    grok.context(ICustomer)
714    grok.name('edit_base')
715    grok.require('waeup.handleCustomer')
716    pnav = 4
717
718    def is_requestable(self, action=None):
719        if self.context.state == STARTED:
720            return True
721        return False
722
723    @property
724    def label(self):
725        if self.is_requestable():
726            return _('Edit base data and request registration')
727        return _('Edit base data')
728
729    @property
730    def form_fields(self):
731        if not self.is_requestable():
732            return grok.AutoFields(
733                self.context.form_fields_interface).select('email', 'phone')
734        return grok.AutoFields(self.context.form_fields_interface).omit(
735            'suspended', 'suspended_comment', 'reg_number', 'customer_id')
736
737    @action(_('Save'), style='primary')
738    def save(self, **data):
739        msave(self, **data)
740        return
741
742    def dataNotComplete(self):
743        store = getUtility(IExtFileStore)
744        error = ''
745        if not store.getFileByContext(self.context, attr=u'passport.jpg'):
746            error += _('Passport picture is missing.')
747        if error:
748            return error
749        return
750
751    @action(_('Save and request registration now'),
752            warning=WARNING_CUST, condition=is_requestable)
753    def finalsubmit(self, **data):
754        msave(self, **data)
755        if self.dataNotComplete():
756            self.flash(self.dataNotComplete(), type="warning")
757            self.redirect(self.url(self.context, 'upload_files'))
758            return
759        IWorkflowInfo(self.context).fireTransition('request')
760        self.flash(_('Registration form has been submitted.'))
761        self.redirect(self.url(self.context))
762        return
763
764    @action(_('Cancel'), validator=NullValidator)
765    def cancel(self, **data):
766        self.redirect(self.url(self.context))
767
768
769class CustomerChangePasswordPage(IkobaEditFormPage):
770    """ View to edit customer passords
771    """
772    grok.context(ICustomer)
773    grok.name('changepassword')
774    grok.require('waeup.handleCustomer')
775    grok.template('changepassword')
776    label = _('Change password')
777    pnav = 4
778
779    @action(_('Save'), style='primary')
780    def save(self, **data):
781        form = self.request.form
782        password = form.get('change_password', None)
783        password_ctl = form.get('change_password_repeat', None)
784        if password:
785            validator = getUtility(IPasswordValidator)
786            errors = validator.validate_password(password, password_ctl)
787            if not errors:
788                IUserAccount(self.context).setPassword(password)
789                self.context.writeLogMessage(self, 'saved: password')
790                self.flash(_('Password changed.'))
791            else:
792                self.flash(' '.join(errors), type="warning")
793        return
794
795    @action(_('Cancel'), validator=NullValidator)
796    def cancel(self, **data):
797        self.redirect(self.url(self.context))
798
799
800class CustomerBasePDFFormPage(IkobaDisplayFormPage):
801    """ Page to display customer base data in pdf files.
802    """
803
804    def __init__(self, context, request, omit_fields=()):
805        self.omit_fields = omit_fields
806        super(CustomerBasePDFFormPage, self).__init__(context, request)
807
808    @property
809    def form_fields(self):
810        form_fields = grok.AutoFields(self.context.form_fields_interface)
811        for field in self.omit_fields:
812            form_fields = form_fields.omit(field)
813        return form_fields
814
815# Pages for customer documents
816
817class DocumentsBreadcrumb(Breadcrumb):
818    """A breadcrumb for the documents container.
819    """
820    grok.context(ICustomerDocumentsContainer)
821    title = _('Documents')
822
823
824class DocumentBreadcrumb(Breadcrumb):
825    """A breadcrumb for the customer container.
826    """
827    grok.context(ICustomerDocument)
828
829    @property
830    def title(self):
831        return "%s..." % self.context.document_id[:9]
832
833
834class DocumentsManageFormPage(IkobaEditFormPage):
835    """ Page to manage the customer documents
836
837    This manage form page is for both customers and customers officers.
838    """
839    grok.context(ICustomerDocumentsContainer)
840    grok.name('index')
841    grok.require('waeup.viewCustomer')
842    form_fields = grok.AutoFields(ICustomerDocumentsContainer)
843    grok.template('documentsmanagepage')
844    pnav = 4
845
846    @property
847    def manage_documents_allowed(self):
848        return checkPermission('waeup.editCustomerDocuments', self.context)
849
850    def unremovable(self, document):
851        usertype = getattr(self.request.principal, 'user_type', None)
852        if not usertype:
853            return False
854        if not self.manage_documents_allowed:
855            return True
856        return (self.request.principal.user_type == 'customer' and \
857            document.state in (VERIFIED, REJECTED, EXPIRED))
858
859    @property
860    def label(self):
861        return _('${a}: Documents',
862            mapping = {'a':self.context.__parent__.display_fullname})
863
864    @action(_('Add document'), validator=NullValidator, style='primary')
865    def addDocument(self, **data):
866        self.redirect(self.url(self.context, 'adddoc'))
867        return
868
869    @jsaction(_('Remove selected documents'))
870    def delDocument(self, **data):
871        form = self.request.form
872        if 'val_id' in form:
873            child_id = form['val_id']
874        else:
875            self.flash(_('No document selected.'), type="warning")
876            self.redirect(self.url(self.context))
877            return
878        if not isinstance(child_id, list):
879            child_id = [child_id]
880        deleted = []
881        for id in child_id:
882            # Customers are not allowed to remove used documents
883            document = self.context.get(id, None)
884            if document is not None and not self.unremovable(document):
885                del self.context[id]
886                deleted.append(id)
887        if len(deleted):
888            self.flash(_('Successfully removed: ${a}',
889                mapping = {'a': ', '.join(deleted)}))
890            self.context.writeLogMessage(
891                self,'removed: %s' % ', '.join(deleted))
892        self.redirect(self.url(self.context))
893        return
894
895
896class DocumentAddFormPage(IkobaAddFormPage):
897    """ Page to add a document
898
899    This add form page is for both customers and customers officers.
900    """
901    grok.context(ICustomerDocumentsContainer)
902    grok.name('adddoc')
903    grok.template('documentaddpage')
904    grok.require('waeup.editCustomerDocuments')
905    label = _('Add document')
906    pnav = 4
907
908    form_fields = grok.AutoFields(ICustomerDocument).omit('document_id')
909
910    @property
911    def selectable_doctypes(self):
912        doctypes = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT
913        return sorted(doctypes.items())
914
915    @property
916    def edit_documents_allowed(self):
917        right_customer_state = self.context.customer.state in getUtility(
918            ICustomersUtils).DOCMANAGE_CUSTOMER_STATES
919        if isCustomer(self.request.principal) and not right_customer_state:
920            return False
921        return True
922
923    def update(self):
924        if not self.edit_documents_allowed:
925            emit_lock_message(self)
926            return
927        return super(DocumentAddFormPage, self).update()
928
929    @action(_('Add document'), style='primary')
930    def createDocument(self, **data):
931        form = self.request.form
932        doctype = form.get('doctype', None)
933        # Here we can create various instances of CustomerDocument derived
934        # classes depending on the doctype parameter given in form.
935        document = createObject('waeup.%s' % doctype)
936        self.applyData(document, **data)
937        self.context.addDocument(document)
938        doctype = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT[doctype]
939        self.flash(_('${a} added.', mapping = {'a': doctype}))
940        self.context.writeLogMessage(
941            self,'added: %s %s' % (doctype, document.document_id))
942        isCustomer = getattr(
943            self.request.principal, 'user_type', None) == 'customer'
944        if isCustomer:
945            self.redirect(self.url(document, 'edit') + '#tab2')
946        else:
947            self.redirect(self.url(document, 'manage') + '#tab2')
948        return
949
950    @action(_('Cancel'), validator=NullValidator)
951    def cancel(self, **data):
952        self.redirect(self.url(self.context))
953
954
955class DocumentDisplayFormPage(IkobaDisplayFormPage):
956    """ Page to view a document
957    """
958    grok.context(ICustomerDocument)
959    grok.name('index')
960    grok.require('waeup.viewCustomer')
961    grok.template('documentpage')
962    pnav = 4
963    label = None  # We render the context title in the documentpage template
964
965    @property
966    def form_fields(self):
967        return grok.AutoFields(self.context.form_fields_interface)
968
969class DocumentManageFormPage(IkobaEditFormPage):
970    """ Page to edit a document
971    """
972    grok.context(ICustomerDocument)
973    grok.name('manage')
974    grok.require('waeup.manageCustomer')
975    grok.template('documenteditpage')
976    pnav = 4
977    deletion_warning = _('Are you sure?')
978
979    @property
980    def form_fields(self):
981        return grok.AutoFields(
982            self.context.form_fields_interface).omit('document_id')
983
984    def update(self):
985        if not self.context.is_editable_by_manager:
986            emit_lock_message(self)
987            return
988        return super(DocumentManageFormPage, self).update()
989
990    @property
991    def label(self):
992        return self.context.title
993
994    @action(_('Save'), style='primary')
995    def save(self, **data):
996        msave(self, **data)
997        return
998
999
1000class DocumentEditFormPage(DocumentManageFormPage):
1001    """ Page to edit a document
1002    """
1003    grok.name('edit')
1004    grok.require('waeup.handleCustomer')
1005
1006    def update(self):
1007        if not self.context.is_editable_by_customer:
1008            emit_lock_message(self)
1009            return
1010        return super(DocumentEditFormPage, self).update()
1011
1012    @action(_('Save'), style='primary')
1013    def save(self, **data):
1014        msave(self, **data)
1015        return
1016
1017    @action(_('Final Submit'), warning=WARNING_DOC)
1018    def finalsubmit(self, **data):
1019        msave(self, **data)
1020        IWorkflowInfo(self.context).fireTransition('submit')
1021        self.flash(_('Form has been submitted.'))
1022        self.redirect(self.url(self.context))
1023        return
1024
1025
1026class DocumentTriggerTransitionFormPage(IkobaEditFormPage):
1027    """ View to trigger customer document transitions
1028    """
1029    grok.context(ICustomerDocument)
1030    grok.name('trigtrans')
1031    grok.require('waeup.triggerTransition')
1032    grok.template('trigtrans')
1033    label = _('Trigger document transition')
1034    pnav = 4
1035
1036    def update(self):
1037        return super(IkobaEditFormPage, self).update()
1038
1039    def getTransitions(self):
1040        """Return a list of dicts of allowed transition ids and titles.
1041
1042        Each list entry provides keys ``name`` and ``title`` for
1043        internal name and (human readable) title of a single
1044        transition.
1045        """
1046        wf_info = IWorkflowInfo(self.context)
1047        allowed_transitions = [t for t in wf_info.getManualTransitions()]
1048        return [dict(name='', title=_('No transition'))] +[
1049            dict(name=x, title=y) for x, y in allowed_transitions]
1050
1051    @action(_('Apply'), style='primary')
1052    def apply(self, **data):
1053        form = self.request.form
1054        if 'transition' in form and form['transition']:
1055            transition_id = form['transition']
1056            wf_info = IWorkflowInfo(self.context)
1057            try:
1058                wf_info.fireTransition(transition_id)
1059                self.flash(_("Transition '%s' executed." % transition_id))
1060            except InvalidTransitionError, error:
1061                self.flash(error, type="warning")
1062            self.redirect(self.url(self.context))
1063        return
1064
1065class PDFDocumentsOverviewPage(UtilityView, grok.View):
1066    """Deliver an overview slip.
1067    """
1068    grok.context(ICustomerDocumentsContainer)
1069    grok.name('documents_overview_slip.pdf')
1070    grok.require('waeup.viewCustomer')
1071    prefix = 'form'
1072
1073    omit_fields = ('suspended', 'sex',
1074                   'suspended_comment',)
1075
1076    form_fields = None
1077
1078    @property
1079    def label(self):
1080        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1081        return translate(_('Documents of'),
1082            'waeup.ikoba', target_language=portal_language) \
1083            + ' %s' % self.context.customer.display_fullname
1084
1085    @property
1086    def tabletitle(self):
1087        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1088        tabletitle = []
1089        tabletitle.append(translate(_('Customer Documents'), 'waeup.ikoba',
1090            target_language=portal_language))
1091        return tabletitle
1092
1093    def render(self):
1094        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1095        Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
1096        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
1097        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
1098        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
1099        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
1100        tableheader = []
1101        tabledata = []
1102        for i in range(1,3):
1103            tabledata.append(sorted(
1104                [value for value in self.context.values()]))
1105            tableheader.append([(Id, 'document_id', 2),
1106                             (Title, 'title', 6),
1107                             (Type, 'translated_class_name', 6),
1108                             (State, 'translated_state', 2),
1109                             (LT, 'formatted_transition_date', 3),
1110                             ])
1111        customerview = CustomerBasePDFFormPage(self.context.customer,
1112            self.request, self.omit_fields)
1113        customers_utils = getUtility(ICustomersUtils)
1114        return customers_utils.renderPDF(
1115            self, 'overview_slip.pdf',
1116            self.context.customer, customerview,
1117            tableheader=tableheader,
1118            tabledata=tabledata,
1119            omit_fields=self.omit_fields)
1120
1121
1122class PDFDocumentSlipPage(UtilityView, grok.View):
1123    """Deliver pdf file including metadata.
1124    """
1125    grok.context(ICustomerDocument)
1126    grok.name('document_slip.pdf')
1127    grok.require('waeup.viewCustomer')
1128    prefix = 'form'
1129
1130    omit_fields = ('suspended', 'sex',
1131                   'suspended_comment',)
1132
1133    form_fields =()
1134
1135    @property
1136    def label(self):
1137        return '%s of %s\nTitle: %s' % (
1138            self.context.translated_class_name,
1139            self.context.customer.display_fullname,
1140            self.context.title)
1141
1142    def render(self):
1143        customerview = CustomerBasePDFFormPage(self.context.customer,
1144            self.request, self.omit_fields)
1145        customers_utils = getUtility(ICustomersUtils)
1146        return customers_utils.renderPDF(
1147            self, 'document_slip.pdf',
1148            self.context.customer, customerview,
1149            omit_fields=self.omit_fields)
1150
1151
1152class PDFMergeDocumentSlipPage(PDFDocumentSlipPage):
1153    """Deliver pdf file including metadata.
1154    """
1155    grok.context(ICustomerPDFDocument)
1156
1157    def render(self):
1158        customerview = CustomerBasePDFFormPage(self.context.customer,
1159            self.request, self.omit_fields)
1160        customers_utils = getUtility(ICustomersUtils)
1161        if self.context.state == VERIFIED:
1162            watermark_path = os.path.join(
1163                os.path.dirname(__file__), 'static', 'verified.pdf')
1164        else:
1165            watermark_path = os.path.join(
1166                os.path.dirname(__file__), 'static', 'unverified.pdf')
1167        watermark = open(watermark_path, 'rb')
1168        return customers_utils.renderPDF(
1169            self, 'pdfdocument_slip.pdf',
1170            self.context.customer, customerview,
1171            omit_fields=self.omit_fields,
1172            mergefiles=self.context.connected_files,
1173            watermark=watermark)
1174
1175# Pages for customer contracts
1176
1177class ContractsBreadcrumb(Breadcrumb):
1178    """A breadcrumb for the contracts container.
1179    """
1180    grok.context(IContractsContainer)
1181    title = _('Contracts')
1182
1183
1184class ContractBreadcrumb(Breadcrumb):
1185    """A breadcrumb for the customer container.
1186    """
1187    grok.context(IContract)
1188
1189    @property
1190    def title(self):
1191        return "%s..." % self.context.contract_id[:9]
1192
1193
1194class ContractsFormPage(IkobaEditFormPage):
1195    """ Page to display, edit or manage customer contracts
1196
1197    This form page is for both customers and officers.
1198    """
1199    grok.context(IContractsContainer)
1200    grok.name('index')
1201    grok.require('waeup.viewCustomer')
1202    form_fields = grok.AutoFields(IContractsContainer)
1203    grok.template('contractsmanagepage')
1204    pnav = 4
1205
1206    @property
1207    def edit_contracts_allowed(self):
1208        right_customer_state = self.context.customer.state in getUtility(
1209            ICustomersUtils).CONMANAGE_CUSTOMER_STATES
1210        if isCustomer(self.request.principal) and not right_customer_state:
1211            return False
1212        return checkPermission('waeup.editContracts', self.context)
1213
1214    def remove_contract_allowed(self, contract):
1215        if isCustomer(self.request.principal):
1216            right_customer_state = self.context.customer.state in getUtility(
1217                ICustomersUtils).CONMANAGE_CUSTOMER_STATES
1218            if not right_customer_state:
1219                return False
1220            if contract.state in (APPROVED, AWAITING, REJECTED, EXPIRED):
1221                return False
1222        return checkPermission('waeup.editContracts', self.context)
1223
1224    @property
1225    def label(self):
1226        return _('${a}: Contracts',
1227            mapping = {'a':self.context.__parent__.display_fullname})
1228
1229    @action(_('Add contract'), validator=NullValidator, style='primary')
1230    def addContract(self, **data):
1231        self.redirect(self.url(self.context, 'addcontract'))
1232        return
1233
1234    @jsaction(_('Remove selected contracts'))
1235    def delContract(self, **data):
1236        form = self.request.form
1237        if 'val_id' in form:
1238            child_id = form['val_id']
1239        else:
1240            self.flash(_('No contract selected.'), type="warning")
1241            self.redirect(self.url(self.context))
1242            return
1243        if not isinstance(child_id, list):
1244            child_id = [child_id]
1245        deleted = []
1246        for id in child_id:
1247            # Customers are not allowed to remove used contracts
1248            contract = self.context.get(id, None)
1249            if contract is not None and self.remove_contract_allowed(contract):
1250                del self.context[id]
1251                deleted.append(id)
1252        if len(deleted):
1253            self.flash(_('Successfully removed: ${a}',
1254                mapping = {'a': ', '.join(deleted)}))
1255            self.context.writeLogMessage(
1256                self,'removed: %s' % ', '.join(deleted))
1257        self.redirect(self.url(self.context))
1258        return
1259
1260
1261class ContractAddFormPage(IkobaAddFormPage):
1262    """ Page to add an contract
1263
1264    This page is for both customers and officers.
1265    """
1266    grok.context(IContractsContainer)
1267    grok.name('addcontract')
1268    grok.template('contractaddpage')
1269    grok.require('waeup.editContracts')
1270    label = _('Add contract')
1271    pnav = 4
1272
1273    @property
1274    def edit_contracts_allowed(self):
1275        right_customer_state = self.context.customer.state in getUtility(
1276            ICustomersUtils).CONMANAGE_CUSTOMER_STATES
1277        if right_customer_state:
1278            return True
1279        return False
1280
1281    def update(self):
1282        if not self.edit_contracts_allowed:
1283            emit_lock_message(self)
1284            return
1285        return super(ContractAddFormPage, self).update()
1286
1287    @property
1288    def selectable_contypes(self):
1289        contypes = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT
1290        return sorted(contypes.items())
1291
1292    @action(_('Add contract'), style='primary')
1293    def createContract(self, **data):
1294        form = self.request.form
1295        contype = form.get('contype', None)
1296        # Here we can create various instances of Contract derived
1297        # classes depending on the contype parameter given in form.
1298        contract = createObject('waeup.%s' % contype)
1299        self.context.addContract(contract)
1300        contype = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT[contype]
1301        self.flash(_('${a} added.', mapping = {'a': contype}))
1302        self.context.writeLogMessage(
1303            self,'added: %s %s' % (contype, contract.contract_id))
1304        self.redirect(self.url(contract, 'selectproduct'))
1305        return
1306
1307    @action(_('Cancel'), validator=NullValidator)
1308    def cancel(self, **data):
1309        self.redirect(self.url(self.context))
1310
1311
1312class ContractSelectProductPage(IkobaAddFormPage):
1313    """ Page to select a contract product
1314
1315    This page is for both customers and officers.
1316    """
1317    grok.context(IContract)
1318    grok.name('selectproduct')
1319    grok.require('waeup.editContracts')
1320    label = _('Select product')
1321    pnav = 4
1322
1323    form_fields = grok.AutoFields(IContractSelectProduct)
1324
1325    def update(self):
1326        if self.context.product_object is not None:
1327            emit_lock_message(self)
1328            return
1329        return super(ContractSelectProductPage, self).update()
1330
1331    @action(_('Save and proceed'), style='primary')
1332    def save(self, **data):
1333        msave(self, **data)
1334        self.context.title = self.context.product_object.contract_autotitle
1335        self.context.tc_dict = self.context.product_object.tc_dict
1336        self.context.valid_from = self.context.product_object.valid_from
1337        self.context.valid_to = self.context.product_object.valid_to
1338        isCustomer = getattr(
1339            self.request.principal, 'user_type', None) == 'customer'
1340        if isCustomer:
1341            self.redirect(self.url(self.context, 'edit'))
1342        else:
1343            self.redirect(self.url(self.context, 'manage'))
1344        return
1345
1346
1347class ContractDisplayFormPage(IkobaDisplayFormPage):
1348    """ Page to view a contract
1349    """
1350    grok.context(IContract)
1351    grok.name('index')
1352    grok.require('waeup.viewCustomer')
1353    grok.template('contractpage')
1354    pnav = 4
1355    label = None  # We render the context title in the contractpage template
1356
1357    @property
1358    def form_fields(self):
1359        form_fields = grok.AutoFields(self.context.form_fields_interface)
1360        for field in form_fields:
1361            if field.__name__.endswith('_object'):
1362                form_fields[field.__name__].custom_widget = HREFDisplayWidget
1363        return form_fields
1364
1365    @property
1366    def terms_and_conditions(self):
1367        lang = self.request.cookies.get('ikoba.language')
1368        html = self.context.tc_dict.get(lang,'')
1369        if html =='':
1370            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1371            html = self.context.tc_dict.get(portal_language,'')
1372        return html
1373
1374
1375class ContractManageFormPage(IkobaEditFormPage):
1376    """ Page to manage a contract
1377    """
1378    grok.context(IContract)
1379    grok.name('manage')
1380    grok.require('waeup.manageCustomer')
1381    grok.template('contracteditpage')
1382    pnav = 4
1383    deletion_warning = _('Are you sure?')
1384
1385    def update(self):
1386        if not self.context.is_editable:
1387            emit_lock_message(self)
1388            return
1389        return super(ContractManageFormPage, self).update()
1390
1391    @property
1392    def terms_and_conditions(self):
1393        lang = self.request.cookies.get('ikoba.language')
1394        html = self.context.tc_dict.get(lang,'')
1395        if html =='':
1396            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1397            html = self.context.tc_dict.get(portal_language,'')
1398        return html
1399
1400    @property
1401    def form_fields(self):
1402        return grok.AutoFields(self.context.form_fields_interface).omit(
1403            'contract_id', 'product_object')
1404
1405    @property
1406    def label(self):
1407        return self.context.title
1408
1409    @action(_('Save'), style='primary')
1410    def save(self, **data):
1411        msave(self, **data)
1412        return
1413
1414
1415class ContractOfficialUsePage(IkobaEditFormPage):
1416    """ Page to manage a official use data of contract
1417    """
1418    grok.context(IContract)
1419    grok.name('official_use')
1420    grok.require('waeup.manageCustomer')
1421    grok.template('contracteditpage')
1422    pnav = 4
1423    deletion_warning = _('Are you sure?')
1424    terms_and_conditions = None
1425
1426    @property
1427    def form_fields(self):
1428        return grok.AutoFields(self.context.ou_form_fields_interface)
1429
1430    @property
1431    def label(self):
1432        return self.context.title
1433
1434    @action(_('Save'), style='primary')
1435    def save(self, **data):
1436        msave(self, **data)
1437        return
1438
1439
1440class ContractEditFormPage(ContractManageFormPage):
1441    """ Page to edit a contract by customer only
1442    """
1443    grok.name('edit')
1444    grok.require('waeup.handleCustomer')
1445
1446    def submission_allowed(self, action=None):
1447        if self.context.state == CREATED and not self.context.fee_based:
1448            return True
1449        return False
1450
1451    def payment_expected(self, action=None):
1452        if self.context.state == CREATED and self.context.fee_based:
1453            return True
1454        return False
1455
1456    @property
1457    def form_fields(self):
1458        return grok.AutoFields(self.context.edit_form_fields_interface).omit(
1459            'contract_id', 'product_object')
1460
1461    @action(_('Save'), style='primary')
1462    def save(self, **data):
1463        msave(self, **data)
1464        return
1465
1466    @action(_('Apply now (final submission)'), warning=WARNING_CON,
1467            condition=submission_allowed, style='primary')
1468    def submit(self, **data):
1469        if self.terms_and_conditions and not self.request.form.get(
1470            'confirm_tc', False):
1471            self.flash(_('Please read the terms and conditions and '
1472                     'confirm your acceptance of these by ticking '
1473                     'the confirmation box.'), type="danger")
1474            return
1475        msave(self, **data)
1476        IWorkflowInfo(self.context).fireTransition('submit')
1477        self.flash(_('Application form has been submitted.'))
1478        self.redirect(self.url(self.context))
1479        return
1480
1481    @action(_('Proceed to checkout'),
1482            condition=payment_expected, style='primary')
1483    def proceed_checkout(self, **data):
1484        if self.terms_and_conditions and not self.request.form.get(
1485            'confirm_tc', False):
1486            self.flash(_('Please read the terms and conditions and '
1487                     'confirm your acceptance of these by ticking '
1488                     'the confirmation box.'), type="danger")
1489            return
1490        msave(self, **data)
1491        self.redirect(self.url(self.context, 'select_payment_method'))
1492        return
1493
1494
1495class ContractSelectPaymentMethodPage(IkobaEditFormPage):
1496    """ Page to to select payment method
1497    """
1498    grok.context(IContract)
1499    grok.name('select_payment_method')
1500    grok.require('waeup.handleCustomer')
1501    grok.template('selectpaymentmethodpage')
1502    pnav = 4
1503    label = _('Select payment method')
1504
1505    @property
1506    def payment_gateways(self):
1507        """Get an iter over registered and enabled gateway service providers.
1508
1509        We provide tuples ``(value, description)`` for each supported
1510        payment gateway.
1511        """
1512        lister = getUtility(IPaymentGatewayServicesLister)
1513        for name, service in lister().items():
1514            yield {'name': name, 'title': service.title}
1515
1516    def update(self, CANCEL=None, gw=None):
1517        if self.context.state != CREATED or not self.context.fee_based:
1518            emit_lock_message(self)
1519            return
1520        self.gw = gw
1521        super(ContractSelectPaymentMethodPage, self).update()
1522        return
1523
1524    @action(_('Select payment method (final submission)'),
1525            style='primary')
1526    def confirm(self, **data):
1527        if self.gw is None:
1528            self.flash(_('Please pick a payment method.'),
1529                       type='warning')
1530        else:
1531            service = queryUtility(IPaymentGatewayService, name=self.gw)
1532            if service is None:
1533                self.flash(_('Invalid payment gateway.'), type='danger')
1534                return
1535            payer = IPayer(self.context)
1536            payable = IPayable(self.context)
1537            payment = service.create_payment(payer, payable)
1538            service.store(payment)
1539            payment, view_name = service.next_step(payment.payment_id)
1540            url = self.url(payment, view_name)
1541            self.redirect(url)
1542            return
1543        return
1544
1545    @action(_('Cancel'))
1546    def cancel(self, **data):
1547        self.redirect(self.url(self.context, 'edit'))
1548        return
1549
1550
1551class ContractTriggerTransitionFormPage(IkobaEditFormPage):
1552    """ View to trigger customer contract transitions
1553    """
1554    grok.context(IContract)
1555    grok.name('trigtrans')
1556    grok.require('waeup.triggerTransition')
1557    grok.template('trigtrans')
1558    label = _('Trigger contract transition')
1559    pnav = 4
1560
1561    def update(self):
1562        return super(IkobaEditFormPage, self).update()
1563
1564    def getTransitions(self):
1565        """Return a list of dicts of allowed transition ids and titles.
1566
1567        Each list entry provides keys ``name`` and ``title`` for
1568        internal name and (human readable) title of a single
1569        transition.
1570        """
1571        wf_info = IWorkflowInfo(self.context)
1572        allowed_transitions = [t for t in wf_info.getManualTransitions()]
1573        # Skip payment transitions if total amount is zero
1574        if not self.context.fee_based:
1575            allowed_transitions = [t for t in allowed_transitions
1576                                   if not t[0] in PAYMENT_TRANSITIONS]
1577        return [dict(name='', title=_('No transition'))] +[
1578            dict(name=x, title=y) for x, y in allowed_transitions]
1579
1580    @action(_('Apply'), style='primary')
1581    def apply(self, **data):
1582        form = self.request.form
1583        if 'transition' in form and form['transition']:
1584            transition_id = form['transition']
1585            wf_info = IWorkflowInfo(self.context)
1586            try:
1587                wf_info.fireTransition(transition_id)
1588                self.flash(_("Transition '%s' executed." % transition_id))
1589            except InvalidTransitionError, error:
1590                self.flash(error, type="warning")
1591            self.redirect(self.url(self.context))
1592        return
1593
1594class PDFContractsOverviewPage(UtilityView, grok.View):
1595    """Deliver an overview slip.
1596    """
1597    grok.context(IContractsContainer)
1598    grok.name('contracts_overview_slip.pdf')
1599    grok.require('waeup.viewCustomer')
1600    prefix = 'form'
1601
1602    omit_fields = ('suspended', 'sex',
1603                   'suspended_comment',)
1604
1605    form_fields = None
1606
1607    @property
1608    def label(self):
1609        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1610        return translate(_('Contracts of'),
1611            'waeup.ikoba', target_language=portal_language) \
1612            + ' %s' % self.context.customer.display_fullname
1613
1614    @property
1615    def tabletitle(self):
1616        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1617        tabletitle = []
1618        tabletitle.append(translate(_('Customer Contracts'), 'waeup.ikoba',
1619            target_language=portal_language))
1620        return tabletitle
1621
1622    def render(self):
1623        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1624        #Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
1625        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
1626        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
1627        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
1628        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
1629        tableheader = []
1630        tabledata = []
1631        for i in range(1,3):
1632            tabledata.append(sorted(
1633                [value for value in self.context.values()]))
1634            tableheader.append([
1635                             #(Id, 'contract_id', 2),
1636                             (Title, 'title', 6),
1637                             (Type, 'translated_class_name', 6),
1638                             (State, 'translated_state', 2),
1639                             (LT, 'formatted_transition_date', 3),
1640                             ])
1641        customerview = CustomerBasePDFFormPage(self.context.customer,
1642            self.request, self.omit_fields)
1643        customers_utils = getUtility(ICustomersUtils)
1644        return customers_utils.renderPDF(
1645            self, 'overview_slip.pdf',
1646            self.context.customer, customerview,
1647            tableheader=tableheader,
1648            tabledata=tabledata,
1649            omit_fields=self.omit_fields)
1650
1651
1652class PDFContractSlipPage(UtilityView, grok.View):
1653    """Deliver pdf file including metadata.
1654    """
1655    grok.context(IContract)
1656    grok.name('contract_slip.pdf')
1657    grok.require('waeup.viewCustomer')
1658    prefix = 'form'
1659
1660    omit_fields = ('suspended', 'sex',
1661                   'suspended_comment',)
1662
1663    @property
1664    def form_fields(self):
1665        return grok.AutoFields(self.context.form_fields_interface)
1666
1667    @property
1668    def terms_and_conditions(self):
1669        lang = self.request.cookies.get('ikoba.language')
1670        html = self.context.tc_dict.get(lang,'')
1671        if html =='':
1672            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1673            html = self.context.tc_dict.get(portal_language,'')
1674        return html
1675
1676    @property
1677    def validity_period(self):
1678        if self.context.valid_from or self.context.valid_to:
1679            return "%s - %s" % (format_date(self.context.valid_from, self.request),
1680                                format_date(self.context.valid_to, self.request))
1681        return
1682
1683    @property
1684    def label(self):
1685        return self.context.title
1686
1687    # XXX: not used in waeup.ikoba and thus not tested
1688    def _signatures(self):
1689        return ([_('Customer Signature')],
1690                [_('Company Officer Signature')]
1691                )
1692
1693    def _sigsInFooter(self):
1694        return (_('Date, Customer Signature'),
1695                _('Date, Company Officer Signature'),
1696                )
1697
1698    def render(self):
1699        customerview = CustomerBasePDFFormPage(self.context.customer,
1700            self.request, self.omit_fields)
1701        customers_utils = getUtility(ICustomersUtils)
1702        return customers_utils.renderPDF(
1703            self, 'contract_slip.pdf',
1704            self.context.customer, customerview,
1705            signatures=self._signatures(),
1706            sigs_in_footer=self._sigsInFooter(),
1707            omit_fields=self.omit_fields)
1708
1709
1710class PaymentsPage(IkobaPage):
1711    """ Page to display all payments made by customer.
1712
1713    """
1714    grok.context(ICustomer)
1715    grok.name('payments')
1716    grok.require('waeup.manageCustomer')
1717    grok.template('paymentspage')
1718    pnav = 4
1719
1720    @property
1721    def label(self):
1722        return _('${a}: Payments', mapping={'a':self.context.display_fullname})
1723
1724    @property
1725    def payment_states(self):
1726        return payment_states
1727
1728    @property
1729    def gateway_services(self):
1730        return get_payment_providers()
1731
1732    def payments(self):
1733        return search_payments(
1734            query=self.context.customer_id, searchtype='payer_id')
1735
1736
1737class PDFContractReceiptPage(UtilityView, grok.View):
1738    """Deliver pdf file including metadata.
1739    """
1740    grok.context(IContract)
1741    grok.name('contract_payment_receipt.pdf')
1742    grok.require('waeup.viewCustomer')
1743    prefix = 'form'
1744
1745    omit_fields = ('suspended', 'sex',
1746                   'suspended_comment',)
1747
1748    @property
1749    def form_fields(self):
1750        return grok.AutoFields(self.context.form_fields_interface)
1751
1752    @property
1753    def label(self):
1754        return 'Payment Receipt: %s' % (
1755            self.context.title)
1756
1757    @property
1758    def payment(self):
1759        return get_payments_from_payable_id(self.context.contract_id)
1760
1761    def render(self):
1762        customerview = CustomerBasePDFFormPage(self.context.customer,
1763            self.request, self.omit_fields)
1764        customers_utils = getUtility(ICustomersUtils)
1765        return customers_utils.renderPDF(
1766            self, 'contract_payment_receipt.pdf',
1767            self.context.customer, customerview,
1768            omit_fields=self.omit_fields,
1769            show_history=False)
Note: See TracBrowser for help on using the repository browser.