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

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

Remove unnecessary test.

  • Property svn:keywords set to Id
File size: 9.9 KB
Line 
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"""
19Customer contract components.
20"""
21import grok
22from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
23from zope.catalog.interfaces import ICatalog
24from zope.component import getUtility, queryUtility
25from zope.component.interfaces import IFactory
26from zope.interface import implementedBy
27from zope.schema import getFields
28from waeup.ikoba.interfaces import MessageFactory as _
29from waeup.ikoba.interfaces import (
30    IObjectHistory, VERIFIED, APPROVED, PROVISIONALLY, IIDSource)
31from waeup.ikoba.customers.interfaces import (
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    )
42from waeup.ikoba.utils.helpers import attrs_to_fields
43
44
45class ContractsContainer(grok.Container):
46    """This is a container for customer contracts.
47    """
48    grok.implements(IContractsContainer, ICustomerNavigation)
49    grok.provides(IContractsContainer)
50
51    def addContract(self, contract):
52        if not IContract.providedBy(contract):
53            raise TypeError(
54                'ContractsContainers contain only IContract instances')
55        self[contract.contract_id] = contract
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
65ContractsContainer = attrs_to_fields(ContractsContainer)
66
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
94class ContractBase(grok.Container):
95    """This is a customer contract baseclass.
96    """
97    grok.implements(IContractSelectProduct)  # Neccessary for the
98                                             # selectproduct page (why?)
99    grok.baseclass()
100
101    def __init__(self):
102        super(ContractBase, self).__init__()
103        # The site doesn't exist in unit tests
104        source = getUtility(IIDSource)
105        self.contract_id = unicode(source.get_hex_uuid())
106        self.last_product_id = None
107        self.tc_dict = {}
108        self.title = None
109        self.valid_to = None
110        self.valid_from = None
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(
126                ICustomersUtils).TRANSLATED_CONTRACT_STATES
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:
138            return self.history.messages[-1].split(' - ')[0]
139        except IndexError:
140            return
141
142    @property
143    def customer(self):
144        try:
145            return self.__parent__.__parent__
146        except AttributeError:
147            return None
148
149    @property
150    def user_id(self):
151        if self.customer is not None:
152            return self.customer.customer_id
153        return
154
155    def writeLogMessage(self, view, message):
156        return self.__parent__.__parent__.writeLogMessage(view, message)
157
158    @property
159    def is_editable(self):
160        try:
161            # Customer must have requested
162            cond1 = self.customer.state in getUtility(
163                ICustomersUtils).CONMANAGE_CUSTOMER_STATES
164            # Contract must be in state created
165            cond2 = self.state in getUtility(
166                ICustomersUtils).CONMANAGE_CONTRACT_STATES
167            if not (cond1 and cond2):
168                return False
169        except AttributeError:
170            pass
171        return True
172
173    @property
174    def is_approvable(self):
175        if self.customer and not self.customer.state in (
176            APPROVED, PROVISIONALLY):
177            return False, _("Customer has not yet been approved.")
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:
183                    return False, _(
184                        "Attached documents must be verified first.")
185        return True, None
186
187    @property
188    def translated_class_name(self):
189        try:
190            CONTYPES_DICT = getUtility(ICustomersUtils).CONTYPES_DICT
191            return CONTYPES_DICT[self.class_name]
192        except KeyError:
193            return
194
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
204
205
206class SampleContract(ContractBase):
207    """This is a sample contract.
208    """
209
210    grok.implements(
211        ISampleContractProcess,  # must come before ISampleContract
212        ISampleContract,
213        ISampleContractEdit,
214        ICustomerNavigation)
215
216    contract_category = 'sample'
217
218    form_fields_interface = ISampleContract
219
220    edit_form_fields_interface = ISampleContractEdit
221
222    ou_form_fields_interface = ISampleContractOfficialUse
223
224    check_docs_interface = ISampleContract
225
226SampleContract = attrs_to_fields(SampleContract)
227
228
229# Contracts must be importable. So we might need a factory.
230class SampleContractFactory(grok.GlobalUtility):
231    """A factory for contracts.
232    """
233    grok.implements(IFactory)
234    grok.name(u'waeup.SampleContract')
235    title = u"Create a new contract.",
236    description = u"This factory instantiates new sample contract instances."
237
238    def __call__(self, *args, **kw):
239        return SampleContract(*args, **kw)
240
241    def getInterfaces(self):
242        return implementedBy(SampleContract)
243
244
245@grok.subscribe(IContract, grok.IObjectAddedEvent)
246def handle_contract_added(contract, event):
247    """If an contract is added the transition create is fired.
248    The latter produces a logging message.
249    """
250    if IWorkflowState(contract).getState() is None:
251        IWorkflowInfo(contract).fireTransition('create')
252    return
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:
272        IWorkflowInfo(maybe_contract).fireTransition('abort')
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])
302        # Cannot happen, but anyway ...
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.