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

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

Add PaymentsPage? (work in progress).

  • Property svn:keywords set to Id
File size: 59.6 KB
Line 
1## $Id: browser.py 12757 2015-03-12 22:28:55Z henrik $
2##
3## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""UI components for customers and related components.
19"""
20import grok
21import os
22from urllib import urlencode
23from zope.event import notify
24from zope.i18n import translate
25from zope.catalog.interfaces import ICatalog
26from zope.component import queryUtility, getUtility, createObject
27from zope.security import checkPermission
28from hurry.workflow.interfaces import (
29    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
30from waeup.ikoba.interfaces import MessageFactory as _
31from waeup.ikoba.interfaces import (
32    IContactForm, IObjectHistory, IIkobaObject, IIkobaUtils, IExtFileStore,
33    IPasswordValidator, IUserAccount,
34    STARTED, VERIFIED, REJECTED, EXPIRED, CREATED, REQUESTED,
35    APPROVED, PROVISIONALLY)
36from waeup.ikoba.browser.layout import (
37    IkobaPage, IkobaEditFormPage, IkobaAddFormPage, IkobaDisplayFormPage,
38    NullValidator, jsaction, action, UtilityView)
39from waeup.ikoba.browser.pages import ContactAdminForm
40from waeup.ikoba.browser.breadcrumbs import Breadcrumb
41from waeup.ikoba.browser.interfaces import ICaptchaManager
42from waeup.ikoba.mandates.mandate import PasswordMandate
43from waeup.ikoba.payments.payment import (
44    get_payments_from_payer_id,
45    find_payable_from_payable_id,
46    get_payment_providers
47    )
48from waeup.ikoba.payments.interfaces import (
49    IPaymentGatewayServicesLister, IPaymentGatewayService, IPayer, IPayable,
50    payment_states
51    )
52from waeup.ikoba.widgets.hrefwidget import HREFDisplayWidget
53from waeup.ikoba.utils.helpers import (
54    get_current_principal, format_date)
55from waeup.ikoba.customers.interfaces import (
56    ICustomer, ICustomersContainer, ICustomerRequestPW, ICustomersUtils,
57    ICustomerDocument, ICustomerDocumentsContainer, ICustomerCreate,
58    ICustomerPDFDocument, IContractsContainer, IContract,
59    IContractSelectProduct,
60    )
61from waeup.ikoba.customers.catalog import search
62from waeup.ikoba.customers.workflow import PAYMENT_TRANSITIONS
63
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        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        for i in range(1,3):
1101            tabledata.append(sorted(
1102                [value for value in self.context.values()]))
1103            tableheader.append([(Id, 'document_id', 2),
1104                             (Title, 'title', 6),
1105                             (Type, 'translated_class_name', 6),
1106                             (State, 'translated_state', 2),
1107                             (LT, 'formatted_transition_date', 3),
1108                             ])
1109        customerview = CustomerBasePDFFormPage(self.context.customer,
1110            self.request, self.omit_fields)
1111        customers_utils = getUtility(ICustomersUtils)
1112        return customers_utils.renderPDF(
1113            self, 'overview_slip.pdf',
1114            self.context.customer, customerview,
1115            tableheader=tableheader,
1116            tabledata=tabledata,
1117            omit_fields=self.omit_fields)
1118
1119
1120class PDFDocumentSlipPage(UtilityView, grok.View):
1121    """Deliver pdf file including metadata.
1122    """
1123    grok.context(ICustomerDocument)
1124    grok.name('document_slip.pdf')
1125    grok.require('waeup.viewCustomer')
1126    prefix = 'form'
1127
1128    omit_fields = ('suspended', 'sex',
1129                   'suspended_comment',)
1130
1131    form_fields =()
1132
1133    @property
1134    def label(self):
1135        return '%s of %s\nTitle: %s' % (
1136            self.context.translated_class_name,
1137            self.context.customer.display_fullname,
1138            self.context.title)
1139
1140    def render(self):
1141        customerview = CustomerBasePDFFormPage(self.context.customer,
1142            self.request, self.omit_fields)
1143        customers_utils = getUtility(ICustomersUtils)
1144        return customers_utils.renderPDF(
1145            self, 'document_slip.pdf',
1146            self.context.customer, customerview,
1147            omit_fields=self.omit_fields)
1148
1149
1150class PDFMergeDocumentSlipPage(PDFDocumentSlipPage):
1151    """Deliver pdf file including metadata.
1152    """
1153    grok.context(ICustomerPDFDocument)
1154
1155    def render(self):
1156        customerview = CustomerBasePDFFormPage(self.context.customer,
1157            self.request, self.omit_fields)
1158        customers_utils = getUtility(ICustomersUtils)
1159        if self.context.state == VERIFIED:
1160            watermark_path = os.path.join(
1161                os.path.dirname(__file__), 'static', 'verified.pdf')
1162        else:
1163            watermark_path = os.path.join(
1164                os.path.dirname(__file__), 'static', 'unverified.pdf')
1165        watermark = open(watermark_path, 'rb')
1166        return customers_utils.renderPDF(
1167            self, 'pdfdocument_slip.pdf',
1168            self.context.customer, customerview,
1169            omit_fields=self.omit_fields,
1170            mergefiles=self.context.connected_files,
1171            watermark=watermark)
1172
1173# Pages for customer contracts
1174
1175class ContractsBreadcrumb(Breadcrumb):
1176    """A breadcrumb for the contracts container.
1177    """
1178    grok.context(IContractsContainer)
1179    title = _('Contracts')
1180
1181
1182class ContractBreadcrumb(Breadcrumb):
1183    """A breadcrumb for the customer container.
1184    """
1185    grok.context(IContract)
1186
1187    @property
1188    def title(self):
1189        return "%s..." % self.context.contract_id[:9]
1190
1191
1192class ContractsFormPage(IkobaEditFormPage):
1193    """ Page to display, edit or manage customer contracts
1194
1195    This form page is for both customers and officers.
1196    """
1197    grok.context(IContractsContainer)
1198    grok.name('index')
1199    grok.require('waeup.viewCustomer')
1200    form_fields = grok.AutoFields(IContractsContainer)
1201    grok.template('contractsmanagepage')
1202    pnav = 4
1203
1204    @property
1205    def edit_contracts_allowed(self):
1206        right_customer_state = self.context.customer.state in getUtility(
1207            ICustomersUtils).CONMANAGE_CUSTOMER_STATES
1208        if isCustomer(self.request.principal) and not right_customer_state:
1209            return False
1210        return checkPermission('waeup.editContracts', self.context)
1211
1212    def remove_contract_allowed(self, contract):
1213        if isCustomer(self.request.principal):
1214            right_customer_state = self.context.customer.state in getUtility(
1215                ICustomersUtils).CONMANAGE_CUSTOMER_STATES
1216            if not right_customer_state:
1217                return False
1218            if contract.state in (APPROVED, REJECTED, EXPIRED):
1219                return False
1220        return checkPermission('waeup.editContracts', self.context)
1221
1222    @property
1223    def label(self):
1224        return _('${a}: Contracts',
1225            mapping = {'a':self.context.__parent__.display_fullname})
1226
1227    @action(_('Add contract'), validator=NullValidator, style='primary')
1228    def addContract(self, **data):
1229        self.redirect(self.url(self.context, 'addcontract'))
1230        return
1231
1232    @jsaction(_('Remove selected contracts'))
1233    def delContract(self, **data):
1234        form = self.request.form
1235        if 'val_id' in form:
1236            child_id = form['val_id']
1237        else:
1238            self.flash(_('No contract selected.'), type="warning")
1239            self.redirect(self.url(self.context))
1240            return
1241        if not isinstance(child_id, list):
1242            child_id = [child_id]
1243        deleted = []
1244        for id in child_id:
1245            # Customers are not allowed to remove used contracts
1246            contract = self.context.get(id, None)
1247            if contract is not None and self.remove_contract_allowed(contract):
1248                del self.context[id]
1249                deleted.append(id)
1250        if len(deleted):
1251            self.flash(_('Successfully removed: ${a}',
1252                mapping = {'a': ', '.join(deleted)}))
1253            self.context.writeLogMessage(
1254                self,'removed: %s' % ', '.join(deleted))
1255        self.redirect(self.url(self.context))
1256        return
1257
1258
1259class ContractAddFormPage(IkobaAddFormPage):
1260    """ Page to add an contract
1261
1262    This page is for both customers and officers.
1263    """
1264    grok.context(IContractsContainer)
1265    grok.name('addcontract')
1266    grok.template('contractaddpage')
1267    grok.require('waeup.editContracts')
1268    label = _('Add contract')
1269    pnav = 4
1270
1271    @property
1272    def edit_contracts_allowed(self):
1273        right_customer_state = self.context.customer.state in getUtility(
1274            ICustomersUtils).CONMANAGE_CUSTOMER_STATES
1275        if right_customer_state:
1276            return True
1277        return False
1278
1279    def update(self):
1280        if not self.edit_contracts_allowed:
1281            emit_lock_message(self)
1282            return
1283        return super(ContractAddFormPage, self).update()
1284
1285    @property
1286    def selectable_contypes(self):
1287        contypes = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT
1288        return sorted(contypes.items())
1289
1290    @action(_('Add contract'), style='primary')
1291    def createContract(self, **data):
1292        form = self.request.form
1293        contype = form.get('contype', None)
1294        # Here we can create various instances of Contract derived
1295        # classes depending on the contype parameter given in form.
1296        contract = createObject('waeup.%s' % contype)
1297        self.context.addContract(contract)
1298        contype = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT[contype]
1299        self.flash(_('${a} added.', mapping = {'a': contype}))
1300        self.context.writeLogMessage(
1301            self,'added: %s %s' % (contype, contract.contract_id))
1302        self.redirect(self.url(contract, 'selectproduct'))
1303        return
1304
1305    @action(_('Cancel'), validator=NullValidator)
1306    def cancel(self, **data):
1307        self.redirect(self.url(self.context))
1308
1309
1310class ContractSelectProductPage(IkobaAddFormPage):
1311    """ Page to select a contract product
1312
1313    This page is for both customers and officers.
1314    """
1315    grok.context(IContract)
1316    grok.name('selectproduct')
1317    grok.require('waeup.editContracts')
1318    label = _('Select product')
1319    pnav = 4
1320
1321    form_fields = grok.AutoFields(IContractSelectProduct)
1322
1323    def update(self):
1324        if self.context.product_object is not None:
1325            emit_lock_message(self)
1326            return
1327        return super(ContractSelectProductPage, self).update()
1328
1329    @action(_('Save and proceed'), style='primary')
1330    def save(self, **data):
1331        msave(self, **data)
1332        self.context.title = self.context.product_object.contract_autotitle
1333        self.context.tc_dict = self.context.product_object.tc_dict
1334        self.context.valid_from = self.context.product_object.valid_from
1335        self.context.valid_to = self.context.product_object.valid_to
1336        isCustomer = getattr(
1337            self.request.principal, 'user_type', None) == 'customer'
1338        if isCustomer:
1339            self.redirect(self.url(self.context, 'edit'))
1340        else:
1341            self.redirect(self.url(self.context, 'manage'))
1342        return
1343
1344
1345class ContractDisplayFormPage(IkobaDisplayFormPage):
1346    """ Page to view a contract
1347    """
1348    grok.context(IContract)
1349    grok.name('index')
1350    grok.require('waeup.viewCustomer')
1351    grok.template('contractpage')
1352    pnav = 4
1353    label = None  # We render the context title in the contractpage template
1354
1355    @property
1356    def form_fields(self):
1357        form_fields = grok.AutoFields(self.context.form_fields_interface)
1358        for field in form_fields:
1359            if field.__name__.endswith('_object'):
1360                form_fields[field.__name__].custom_widget = HREFDisplayWidget
1361        return form_fields
1362
1363    @property
1364    def terms_and_conditions(self):
1365        lang = self.request.cookies.get('ikoba.language')
1366        html = self.context.tc_dict.get(lang,'')
1367        if html =='':
1368            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1369            html = self.context.tc_dict.get(portal_language,'')
1370        return html
1371
1372
1373class ContractManageFormPage(IkobaEditFormPage):
1374    """ Page to manage a contract
1375    """
1376    grok.context(IContract)
1377    grok.name('manage')
1378    grok.require('waeup.manageCustomer')
1379    grok.template('contracteditpage')
1380    pnav = 4
1381    deletion_warning = _('Are you sure?')
1382
1383    def update(self):
1384        if not self.context.is_editable:
1385            emit_lock_message(self)
1386            return
1387        return super(ContractManageFormPage, self).update()
1388
1389    @property
1390    def terms_and_conditions(self):
1391        lang = self.request.cookies.get('ikoba.language')
1392        html = self.context.tc_dict.get(lang,'')
1393        if html =='':
1394            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1395            html = self.context.tc_dict.get(portal_language,'')
1396        return html
1397
1398    @property
1399    def form_fields(self):
1400        return grok.AutoFields(self.context.form_fields_interface).omit(
1401            'contract_id', 'product_object')
1402
1403    @property
1404    def label(self):
1405        return self.context.title
1406
1407    @action(_('Save'), style='primary')
1408    def save(self, **data):
1409        msave(self, **data)
1410        return
1411
1412
1413class ContractOfficialUsePage(IkobaEditFormPage):
1414    """ Page to manage a official use data of contract
1415    """
1416    grok.context(IContract)
1417    grok.name('official_use')
1418    grok.require('waeup.manageCustomer')
1419    grok.template('contracteditpage')
1420    pnav = 4
1421    deletion_warning = _('Are you sure?')
1422    terms_and_conditions = None
1423
1424    @property
1425    def form_fields(self):
1426        return grok.AutoFields(self.context.ou_form_fields_interface)
1427
1428    @property
1429    def label(self):
1430        return self.context.title
1431
1432    @action(_('Save'), style='primary')
1433    def save(self, **data):
1434        msave(self, **data)
1435        return
1436
1437
1438class ContractEditFormPage(ContractManageFormPage):
1439    """ Page to edit a contract by customer only
1440    """
1441    grok.name('edit')
1442    grok.require('waeup.handleCustomer')
1443
1444    def submission_allowed(self, action=None):
1445        if self.context.state == CREATED and not self.context.fee_based:
1446            return True
1447        return False
1448
1449    def payment_expected(self, action=None):
1450        if self.context.state == CREATED and self.context.fee_based:
1451            return True
1452        return False
1453
1454    @property
1455    def form_fields(self):
1456        return grok.AutoFields(self.context.edit_form_fields_interface).omit(
1457            'contract_id', 'product_object')
1458
1459    @action(_('Save'), style='primary')
1460    def save(self, **data):
1461        msave(self, **data)
1462        return
1463
1464    @action(_('Apply now (final submission)'), warning=WARNING_CON,
1465            condition=submission_allowed, style='primary')
1466    def submit(self, **data):
1467        if self.terms_and_conditions and not self.request.form.get(
1468            'confirm_tc', False):
1469            self.flash(_('Please read the terms and conditions and '
1470                     'confirm your acceptance of these by ticking '
1471                     'the confirmation box.'), type="danger")
1472            return
1473        msave(self, **data)
1474        IWorkflowInfo(self.context).fireTransition('submit')
1475        self.flash(_('Application form has been submitted.'))
1476        self.redirect(self.url(self.context))
1477        return
1478
1479    @action(_('Proceed to checkout'),
1480            condition=payment_expected, style='primary')
1481    def proceed_checkout(self, **data):
1482        if self.terms_and_conditions and not self.request.form.get(
1483            'confirm_tc', False):
1484            self.flash(_('Please read the terms and conditions and '
1485                     'confirm your acceptance of these by ticking '
1486                     'the confirmation box.'), type="danger")
1487            return
1488        msave(self, **data)
1489        self.redirect(self.url(self.context, 'select_payment_method'))
1490        return
1491
1492
1493class ContractSelectPaymentMethodPage(IkobaEditFormPage):
1494    """ Page to to select payment method
1495    """
1496    grok.context(IContract)
1497    grok.name('select_payment_method')
1498    grok.require('waeup.handleCustomer')
1499    grok.template('selectpaymentmethodpage')
1500    pnav = 4
1501    label = _('Select payment method')
1502
1503    @property
1504    def payment_gateways(self):
1505        """Get an iter over registered and enabled gateway service providers.
1506
1507        We provide tuples ``(value, description)`` for each supported
1508        payment gateway.
1509        """
1510        lister = getUtility(IPaymentGatewayServicesLister)
1511        for name, service in lister().items():
1512            yield {'name': name, 'title': service.title}
1513
1514    def update(self, CANCEL=None, gw=None):
1515        if self.context.state != CREATED or not self.context.fee_based:
1516            emit_lock_message(self)
1517            return
1518        self.gw = gw
1519        super(ContractSelectPaymentMethodPage, self).update()
1520        return
1521
1522    @action(_('Select payment method (final submission)'),
1523            style='primary')
1524    def confirm(self, **data):
1525        if self.gw is None:
1526            self.flash(_('Please pick a payment method.'),
1527                       type='warning')
1528        else:
1529            service = queryUtility(IPaymentGatewayService, name=self.gw)
1530            if service is None:
1531                self.flash(_('Invalid payment gateway.'), type='danger')
1532                return
1533            payer = IPayer(self.context)
1534            payable = IPayable(self.context)
1535            payment = service.create_payment(payer, payable)
1536            service.store(payment)
1537            payment, view_name = service.next_step(payment.payment_id)
1538            url = self.url(payment, view_name)
1539            self.redirect(url)
1540            return
1541        return
1542
1543    @action(_('Cancel'))
1544    def cancel(self, **data):
1545        self.redirect(self.url(self.context, 'edit'))
1546        return
1547
1548
1549class ContractTriggerTransitionFormPage(IkobaEditFormPage):
1550    """ View to trigger customer contract transitions
1551    """
1552    grok.context(IContract)
1553    grok.name('trigtrans')
1554    grok.require('waeup.triggerTransition')
1555    grok.template('trigtrans')
1556    label = _('Trigger contract transition')
1557    pnav = 4
1558
1559    def update(self):
1560        return super(IkobaEditFormPage, self).update()
1561
1562    def getTransitions(self):
1563        """Return a list of dicts of allowed transition ids and titles.
1564
1565        Each list entry provides keys ``name`` and ``title`` for
1566        internal name and (human readable) title of a single
1567        transition.
1568        """
1569        wf_info = IWorkflowInfo(self.context)
1570        allowed_transitions = [t for t in wf_info.getManualTransitions()]
1571        # Skip payment transitions if total amount is zero
1572        if not self.context.fee_based:
1573            allowed_transitions = [t for t in allowed_transitions
1574                                   if not t[0] in PAYMENT_TRANSITIONS]
1575        return [dict(name='', title=_('No transition'))] +[
1576            dict(name=x, title=y) for x, y in allowed_transitions]
1577
1578    @action(_('Apply'), style='primary')
1579    def apply(self, **data):
1580        form = self.request.form
1581        if 'transition' in form and form['transition']:
1582            transition_id = form['transition']
1583            wf_info = IWorkflowInfo(self.context)
1584            try:
1585                wf_info.fireTransition(transition_id)
1586                self.flash(_("Transition '%s' executed." % transition_id))
1587            except InvalidTransitionError, error:
1588                self.flash(error, type="warning")
1589            self.redirect(self.url(self.context))
1590        return
1591
1592class PDFContractsOverviewPage(UtilityView, grok.View):
1593    """Deliver an overview slip.
1594    """
1595    grok.context(IContractsContainer)
1596    grok.name('contracts_overview_slip.pdf')
1597    grok.require('waeup.viewCustomer')
1598    prefix = 'form'
1599
1600    omit_fields = ('suspended', 'sex',
1601                   'suspended_comment',)
1602
1603    form_fields = None
1604
1605    @property
1606    def label(self):
1607        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1608        return translate(_('Contracts of'),
1609            'waeup.ikoba', target_language=portal_language) \
1610            + ' %s' % self.context.customer.display_fullname
1611
1612    @property
1613    def tabletitle(self):
1614        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1615        tabletitle = []
1616        tabletitle.append(translate(_('Customer Contracts'), 'waeup.ikoba',
1617            target_language=portal_language))
1618        return tabletitle
1619
1620    def render(self):
1621        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1622        #Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
1623        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
1624        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
1625        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
1626        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
1627        tableheader = []
1628        tabledata = []
1629        for i in range(1,3):
1630            tabledata.append(sorted(
1631                [value for value in self.context.values()]))
1632            tableheader.append([
1633                             #(Id, 'contract_id', 2),
1634                             (Title, 'title', 6),
1635                             (Type, 'translated_class_name', 6),
1636                             (State, 'translated_state', 2),
1637                             (LT, 'formatted_transition_date', 3),
1638                             ])
1639        customerview = CustomerBasePDFFormPage(self.context.customer,
1640            self.request, self.omit_fields)
1641        customers_utils = getUtility(ICustomersUtils)
1642        return customers_utils.renderPDF(
1643            self, 'overview_slip.pdf',
1644            self.context.customer, customerview,
1645            tableheader=tableheader,
1646            tabledata=tabledata,
1647            omit_fields=self.omit_fields)
1648
1649
1650class PDFContractSlipPage(UtilityView, grok.View):
1651    """Deliver pdf file including metadata.
1652    """
1653    grok.context(IContract)
1654    grok.name('contract_slip.pdf')
1655    grok.require('waeup.viewCustomer')
1656    prefix = 'form'
1657
1658    omit_fields = ('suspended', 'sex',
1659                   'suspended_comment',)
1660
1661    @property
1662    def form_fields(self):
1663        return grok.AutoFields(self.context.form_fields_interface)
1664
1665    @property
1666    def terms_and_conditions(self):
1667        lang = self.request.cookies.get('ikoba.language')
1668        html = self.context.tc_dict.get(lang,'')
1669        if html =='':
1670            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1671            html = self.context.tc_dict.get(portal_language,'')
1672        return html
1673
1674    @property
1675    def validity_period(self):
1676        if self.context.valid_from or self.context.valid_to:
1677            return "%s - %s" % (format_date(self.context.valid_from, self.request),
1678                                format_date(self.context.valid_to, self.request))
1679        return
1680
1681    @property
1682    def label(self):
1683        return self.context.title
1684
1685    # XXX: not used in waeup.ikoba and thus not tested
1686    def _signatures(self):
1687        return ([_('Customer Signature')],
1688                [_('Company Officer Signature')]
1689                )
1690
1691    def _sigsInFooter(self):
1692        return (_('Date, Customer Signature'),
1693                _('Date, Company Officer Signature'),
1694                )
1695
1696    def render(self):
1697        customerview = CustomerBasePDFFormPage(self.context.customer,
1698            self.request, self.omit_fields)
1699        customers_utils = getUtility(ICustomersUtils)
1700        return customers_utils.renderPDF(
1701            self, 'contract_slip.pdf',
1702            self.context.customer, customerview,
1703            signatures=self._signatures(),
1704            sigs_in_footer=self._sigsInFooter(),
1705            omit_fields=self.omit_fields)
1706
1707
1708class PaymentsPage(IkobaPage):
1709    """ Page to display all payments made by customer.
1710
1711    """
1712    grok.context(ICustomer)
1713    grok.name('payments')
1714    grok.require('waeup.manageCustomer')
1715    grok.template('paymentspage')
1716    pnav = 4
1717
1718    @property
1719    def label(self):
1720        return _('${a}: Payments', mapping={'a':self.context.display_fullname})
1721
1722    @property
1723    def payment_states(self):
1724        return payment_states
1725
1726    @property
1727    def gateway_services(self):
1728        return get_payment_providers()
1729
1730    def payments(self):
1731        values = []
1732        payable = None
1733        for payment in get_payments_from_payer_id(
1734            self.context.customer.customer_id):
1735            payable_object = find_payable_from_payable_id(payment.payable_id)
1736            if payable_object is not None:
1737                payable = IPayable(payable_object)
1738            values.append((payment, payable, payable_object))
1739        return values
Note: See TracBrowser for help on using the repository browser.