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

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

Render all payment data (depending on interface) on PDFContractReceiptPage.

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