source: main/waeup.ikoba/trunk/src/waeup/ikoba/customers/contracts.py @ 17929

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

Remove unnecessary test.

  • Property svn:keywords set to Id
File size: 9.9 KB
RevLine 
[12089]1## $Id: contracts.py 12769 2015-03-15 13:14:08Z 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"""
[12097]19Customer contract components.
[12089]20"""
21import grok
[12741]22from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
23from zope.catalog.interfaces import ICatalog
24from zope.component import getUtility, queryUtility
[12089]25from zope.component.interfaces import IFactory
26from zope.interface import implementedBy
[12144]27from zope.schema import getFields
[12089]28from waeup.ikoba.interfaces import MessageFactory as _
[12258]29from waeup.ikoba.interfaces import (
[12741]30    IObjectHistory, VERIFIED, APPROVED, PROVISIONALLY, IIDSource)
[12089]31from waeup.ikoba.customers.interfaces import (
[12741]32    IContractsContainer, ICustomerNavigation, IContract,
33    IContractSelectProduct, ICustomersUtils, ISampleContract,
34    ISampleContractProcess, ISampleContractEdit, ISampleContractOfficialUse)
35from waeup.ikoba.payments.interfaces import (
36    IPayer, IPayableFinder, IPayable, IPaymentWaitingForGatewayEvent,
37    STATE_PAID, STATE_FAILED, IPaymentFinishedEvent
38    )
39from waeup.ikoba.payments.payment import (
40    PaymentItem, find_payable_from_payable_id,
41    )
[12089]42from waeup.ikoba.utils.helpers import attrs_to_fields
43
[12741]44
[12097]45class ContractsContainer(grok.Container):
46    """This is a container for customer contracts.
[12089]47    """
[12097]48    grok.implements(IContractsContainer, ICustomerNavigation)
49    grok.provides(IContractsContainer)
[12089]50
[12097]51    def addContract(self, contract):
52        if not IContract.providedBy(contract):
[12089]53            raise TypeError(
[12097]54                'ContractsContainers contain only IContract instances')
55        self[contract.contract_id] = contract
[12089]56        return
57
58    @property
59    def customer(self):
60        return self.__parent__
61
62    def writeLogMessage(self, view, message):
63        return self.__parent__.writeLogMessage(view, message)
64
[12097]65ContractsContainer = attrs_to_fields(ContractsContainer)
[12089]66
[12741]67
68class ContractPayer(grok.Adapter):
69    """Adapter to turn contracts into IPayers.
70    """
71    grok.implements(IPayer)
72    grok.context(IContract)
73
74    def __init__(self, context):
75        self.context = context
76
77    @property
78    def _customer(self):
79        return self.context.customer
80
81    @property
82    def first_name(self):
83        return getattr(self._customer, 'firstname', None)
84
85    @property
86    def last_name(self):
87        return getattr(self._customer, 'lastname', None)
88
89    @property
90    def payer_id(self):
91        return getattr(self._customer, 'customer_id', None)
92
93
[12097]94class ContractBase(grok.Container):
95    """This is a customer contract baseclass.
[12089]96    """
[12741]97    grok.implements(IContractSelectProduct)  # Neccessary for the
98                                             # selectproduct page (why?)
[12089]99    grok.baseclass()
100
101    def __init__(self):
[12097]102        super(ContractBase, self).__init__()
[12089]103        # The site doesn't exist in unit tests
[12258]104        source = getUtility(IIDSource)
105        self.contract_id = unicode(source.get_hex_uuid())
[12094]106        self.last_product_id = None
[12363]107        self.tc_dict = {}
[12580]108        self.title = None
[12633]109        self.valid_to = None
110        self.valid_from = None
[12089]111        return
112
113    @property
114    def history(self):
115        history = IObjectHistory(self)
116        return history
117
118    @property
119    def state(self):
120        return IWorkflowState(self).getState()
121
122    @property
123    def translated_state(self):
124        try:
125            TRANSLATED_STATES = getUtility(
[12097]126                ICustomersUtils).TRANSLATED_CONTRACT_STATES
[12089]127            return TRANSLATED_STATES[self.state]
128        except KeyError:
129            return
130
131    @property
132    def class_name(self):
133        return self.__class__.__name__
134
135    @property
136    def formatted_transition_date(self):
137        try:
[12210]138            return self.history.messages[-1].split(' - ')[0]
139        except IndexError:
[12089]140            return
141
142    @property
143    def customer(self):
144        try:
145            return self.__parent__.__parent__
146        except AttributeError:
147            return None
148
[12289]149    @property
150    def user_id(self):
151        if self.customer is not None:
152            return self.customer.customer_id
153        return
154
[12089]155    def writeLogMessage(self, view, message):
156        return self.__parent__.__parent__.writeLogMessage(view, message)
157
158    @property
[12337]159    def is_editable(self):
[12089]160        try:
[12573]161            # Customer must have requested
[12089]162            cond1 = self.customer.state in getUtility(
[12099]163                ICustomersUtils).CONMANAGE_CUSTOMER_STATES
[12097]164            # Contract must be in state created
[12089]165            cond2 = self.state in getUtility(
[12099]166                ICustomersUtils).CONMANAGE_CONTRACT_STATES
[12089]167            if not (cond1 and cond2):
168                return False
169        except AttributeError:
170            pass
171        return True
172
173    @property
[12144]174    def is_approvable(self):
[12573]175        if self.customer and not self.customer.state in (
176            APPROVED, PROVISIONALLY):
[12352]177            return False, _("Customer has not yet been approved.")
[12144]178        for key, field in getFields(self.check_docs_interface).items():
179            if key.endswith('_object'):
180                obj = getattr(self, key, None)
181                state = getattr(obj, 'state', None)
182                if state and state != VERIFIED:
[12741]183                    return False, _(
184                        "Attached documents must be verified first.")
[12168]185        return True, None
[12144]186
187    @property
[12089]188    def translated_class_name(self):
189        try:
[12099]190            CONTYPES_DICT = getUtility(ICustomersUtils).CONTYPES_DICT
191            return CONTYPES_DICT[self.class_name]
[12089]192        except KeyError:
193            return
194
[12663]195    @property
196    def fee_based(self):
197        if self.product_options:
198            amount = 0
199            for option in self.product_options:
200                amount += option.fee
201            if amount:
202                return True
203        return False
[12089]204
[12663]205
[12097]206class SampleContract(ContractBase):
207    """This is a sample contract.
[12089]208    """
209
[12333]210    grok.implements(
[12741]211        ISampleContractProcess,  # must come before ISampleContract
[12333]212        ISampleContract,
213        ISampleContractEdit,
214        ICustomerNavigation)
[12103]215
[12097]216    contract_category = 'sample'
[12092]217
[12103]218    form_fields_interface = ISampleContract
219
220    edit_form_fields_interface = ISampleContractEdit
221
[12500]222    ou_form_fields_interface = ISampleContractOfficialUse
223
[12144]224    check_docs_interface = ISampleContract
225
[12097]226SampleContract = attrs_to_fields(SampleContract)
[12089]227
228
[12097]229# Contracts must be importable. So we might need a factory.
230class SampleContractFactory(grok.GlobalUtility):
231    """A factory for contracts.
[12089]232    """
233    grok.implements(IFactory)
[12097]234    grok.name(u'waeup.SampleContract')
235    title = u"Create a new contract.",
236    description = u"This factory instantiates new sample contract instances."
[12089]237
238    def __call__(self, *args, **kw):
[12097]239        return SampleContract(*args, **kw)
[12089]240
241    def getInterfaces(self):
[12097]242        return implementedBy(SampleContract)
[12089]243
[12741]244
[12097]245@grok.subscribe(IContract, grok.IObjectAddedEvent)
246def handle_contract_added(contract, event):
247    """If an contract is added the transition create is fired.
[12090]248    The latter produces a logging message.
249    """
[12097]250    if IWorkflowState(contract).getState() is None:
251        IWorkflowInfo(contract).fireTransition('create')
[12090]252    return
[12741]253
254
255@grok.subscribe(IPaymentWaitingForGatewayEvent)
256def handle_payment_waiting_for_gw(event):
257    maybe_contract = find_payable_from_payable_id(
258        event.object.payable_id)
259    if IContract.providedBy(maybe_contract):
260        IWorkflowInfo(maybe_contract).fireTransition('await')
261
262
263@grok.subscribe(IPaymentFinishedEvent)
264def handle_payment_finished(event):
265    payment = event.object
266    maybe_contract = find_payable_from_payable_id(payment.payable_id)
267    if not IContract.providedBy(maybe_contract):
268        return
269    if payment.state == STATE_PAID:
270        IWorkflowInfo(maybe_contract).fireTransition('confirm')
271    else:
[12747]272        IWorkflowInfo(maybe_contract).fireTransition('abort')
[12741]273
274
275class ContractFinder(grok.GlobalUtility):
276    grok.name('contracts_finder')
277    grok.implements(IPayableFinder)
278
279    def get_payable_by_id(self, contract_id):
280        catalog = queryUtility(ICatalog, 'contracts_catalog')
281        if catalog is None:
282            return None
283        result = catalog.searchResults(
284            contract_id=(contract_id, contract_id))
285        result = [x for x in result]
286        if not result:
287            return None
288        # there should not be more than one result really.
289        return result[0]
290
291
292class PayableContract(grok.Adapter):
293    """Adapter to adapt IContracts to IPayable.
294    """
295
296    grok.context(IContract)
297    grok.implements(IPayable)
298
299    def __init__(self, context):
300        self.context = context
301        currencies = set([x.currency for x in context.product_options])
[12769]302        # Cannot happen, but anyway ...
[12741]303        if len(currencies) > 1:
304            raise ValueError(
305                "Only contracts with same currency for all options allowed.")
306        return
307
308    @property
309    def payable_id(self):
310        return self.context.contract_id
311
312    @property
313    def title(self):
314        return self.context.title or u''
315
316    @property
317    def currency(self):
318        if not len(self.context.product_options):
319            return None
320        return self.context.product_options[0].currency
321
322    @property
323    def payment_items(self):
324        result = []
325        for num, option in enumerate(self.context.product_options):
326            item = PaymentItem()
327            item.item_id = u'%s' % num
328            item.title = option.title
329            item.amount = option.fee
330            result.append(item)
331        return tuple(result)
Note: See TracBrowser for help on using the repository browser.