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

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

Replace 'apptype' by 'contype'.

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