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

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

Extend contract workflow to integrate payment.

Prepare (empty) page to select payment method and finally create a payment object.

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