source: main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/browser.py @ 12703

Last change on this file since 12703 was 12690, checked in by uli, 10 years ago

Make something was selected before proceed.

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