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

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

Extend contract workflow to integrate payment.

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

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