source: main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/contracts.py @ 12717

Last change on this file since 12717 was 12717, checked in by uli, 10 years ago

Avoid paymentitem mismatch.

  • Property svn:keywords set to Id
File size: 8.3 KB
Line 
1## $Id: contracts.py 12717 2015-03-10 15:16:01Z uli $
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 zope.catalog.interfaces import ICatalog
23from zope.component import getUtility
24from zope.component.interfaces import IFactory
25from zope.interface import implementedBy
26from zope.schema import getFields
27from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
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 IPayer, IPayableFinder
36from waeup.ikoba.payments.payment import PaymentItem
37from waeup.ikoba.utils.helpers import attrs_to_fields
38
39
40class ContractsContainer(grok.Container):
41    """This is a container for customer contracts.
42    """
43    grok.implements(IContractsContainer, ICustomerNavigation)
44    grok.provides(IContractsContainer)
45
46    def addContract(self, contract):
47        if not IContract.providedBy(contract):
48            raise TypeError(
49                'ContractsContainers contain only IContract instances')
50        self[contract.contract_id] = contract
51        return
52
53    @property
54    def customer(self):
55        return self.__parent__
56
57    def writeLogMessage(self, view, message):
58        return self.__parent__.writeLogMessage(view, message)
59
60ContractsContainer = attrs_to_fields(ContractsContainer)
61
62
63def payment_items_from_contract(contract):
64    """Turn contract product options into payment items.
65    """
66    result = []
67    for num, option in enumerate(contract.product_options):
68        item = PaymentItem()
69        item.item_id = u'%s' % num
70        item.title = option.title
71        item.amount = option.fee
72        result.append(item)
73    return result
74
75
76class ContractPayer(grok.Adapter):
77    """Adapter to turn contracts into IPayers.
78    """
79    grok.implements(IPayer)
80    grok.context(IContract)
81
82    def __init__(self, context):
83        self.context = context
84
85    @property
86    def _customer(self):
87        return self.context.customer
88
89    @property
90    def first_name(self):
91        return getattr(self._customer, 'firstname', None)
92
93    @property
94    def last_name(self):
95        return getattr(self._customer, 'lastname', None)
96
97    @property
98    def payer_id(self):
99        return getattr(self._customer, 'customer_id', None)
100
101
102class ContractBase(grok.Container):
103    """This is a customer contract baseclass.
104    """
105    grok.implements(IContractSelectProduct)  # Neccessary for the
106                                             # selectproduct page (why?)
107    grok.baseclass()
108
109    def __init__(self):
110        super(ContractBase, self).__init__()
111        # The site doesn't exist in unit tests
112        source = getUtility(IIDSource)
113        self.contract_id = unicode(source.get_hex_uuid())
114        self.last_product_id = None
115        self.tc_dict = {}
116        self.title = None
117        self.valid_to = None
118        self.valid_from = None
119        return
120
121    @property
122    def history(self):
123        history = IObjectHistory(self)
124        return history
125
126    @property
127    def state(self):
128        return IWorkflowState(self).getState()
129
130    @property
131    def translated_state(self):
132        try:
133            TRANSLATED_STATES = getUtility(
134                ICustomersUtils).TRANSLATED_CONTRACT_STATES
135            return TRANSLATED_STATES[self.state]
136        except KeyError:
137            return
138
139    @property
140    def class_name(self):
141        return self.__class__.__name__
142
143    @property
144    def formatted_transition_date(self):
145        try:
146            return self.history.messages[-1].split(' - ')[0]
147        except IndexError:
148            return
149
150    @property
151    def customer(self):
152        try:
153            return self.__parent__.__parent__
154        except AttributeError:
155            return None
156
157    @property
158    def user_id(self):
159        if self.customer is not None:
160            return self.customer.customer_id
161        return
162
163    def writeLogMessage(self, view, message):
164        return self.__parent__.__parent__.writeLogMessage(view, message)
165
166    @property
167    def is_editable(self):
168        try:
169            # Customer must have requested
170            cond1 = self.customer.state in getUtility(
171                ICustomersUtils).CONMANAGE_CUSTOMER_STATES
172            # Contract must be in state created
173            cond2 = self.state in getUtility(
174                ICustomersUtils).CONMANAGE_CONTRACT_STATES
175            if not (cond1 and cond2):
176                return False
177        except AttributeError:
178            pass
179        return True
180
181    @property
182    def is_approvable(self):
183        if self.customer and not self.customer.state in (
184            APPROVED, PROVISIONALLY):
185            return False, _("Customer has not yet been approved.")
186        for key, field in getFields(self.check_docs_interface).items():
187            if key.endswith('_object'):
188                obj = getattr(self, key, None)
189                state = getattr(obj, 'state', None)
190                if state and state != VERIFIED:
191                    return False, _(
192                        "Attached documents must be verified first.")
193        return True, None
194
195    @property
196    def translated_class_name(self):
197        try:
198            CONTYPES_DICT = getUtility(ICustomersUtils).CONTYPES_DICT
199            return CONTYPES_DICT[self.class_name]
200        except KeyError:
201            return
202
203    @property
204    def fee_based(self):
205        if self.product_options:
206            amount = 0
207            for option in self.product_options:
208                amount += option.fee
209            if amount:
210                return True
211        return False
212
213
214class SampleContract(ContractBase):
215    """This is a sample contract.
216    """
217
218    grok.implements(
219        ISampleContractProcess,  # must come before ISampleContract
220        ISampleContract,
221        ISampleContractEdit,
222        ICustomerNavigation)
223
224    contract_category = 'sample'
225
226    form_fields_interface = ISampleContract
227
228    edit_form_fields_interface = ISampleContractEdit
229
230    ou_form_fields_interface = ISampleContractOfficialUse
231
232    check_docs_interface = ISampleContract
233
234SampleContract = attrs_to_fields(SampleContract)
235
236
237# Contracts must be importable. So we might need a factory.
238class SampleContractFactory(grok.GlobalUtility):
239    """A factory for contracts.
240    """
241    grok.implements(IFactory)
242    grok.name(u'waeup.SampleContract')
243    title = u"Create a new contract.",
244    description = u"This factory instantiates new sample contract instances."
245
246    def __call__(self, *args, **kw):
247        return SampleContract(*args, **kw)
248
249    def getInterfaces(self):
250        return implementedBy(SampleContract)
251
252
253@grok.subscribe(IContract, grok.IObjectAddedEvent)
254def handle_contract_added(contract, event):
255    """If an contract is added the transition create is fired.
256    The latter produces a logging message.
257    """
258    if IWorkflowState(contract).getState() is None:
259        IWorkflowInfo(contract).fireTransition('create')
260    return
261
262
263class ContractFinder(grok.GlobalUtility):
264    grok.name('contracts_finder')
265    grok.implements(IPayableFinder)
266
267    def get_payable_by_id(self, contract_id):
268        catalog = getUtility(ICatalog, 'contracts_catalog')
269        result = catalog.searchResults(
270            contract_id=(contract_id, contract_id))
271        result = [x for x in result]
272        if not result:
273            return None
274        # there should not be more than one result really.
275        return result[0]
Note: See TracBrowser for help on using the repository browser.