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

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

Copy over valid_from and valid_to from products when creating contract objects.

Fix ContractProcessorBase?. Do really allow to import tc_dict, valid_from, title and valid_to.

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