luxia--
[photoprint.git] / tool.py
1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Copyright © 2009 Benoît Pin <pin@cri.ensmp.fr> #
4 # Plinn - http://plinn.org #
5 # #
6 # #
7 # This program is free software; you can redistribute it and/or #
8 # modify it under the terms of the GNU General Public License #
9 # as published by the Free Software Foundation; either version 2 #
10 # of the License, or (at your option) any later version. #
11 # #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
16 # #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program; if not, write to the Free Software #
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #
20 #######################################################################################
21 """
22 Photo print tool. Used to link photo to print orders.
23
24
25
26 """
27
28 from AccessControl import ClassSecurityInfo
29 from AccessControl.requestmethod import postonly
30 from Acquisition import aq_base, aq_inner
31 from Globals import InitializeClass
32 from OFS.OrderedFolder import OrderedFolder
33 from Products.CMFCore.utils import UniqueObject, getToolByName
34 from permissions import ManagePrintOrderTemplate
35 from price import Price
36 from utils import Message as _
37 from Products.Plinn.utils import makeValidId
38 from zope.component import getUtility
39 from zope.component.interfaces import IFactory
40 from DateTime import DateTime
41 from Products.Plinn.utils import _sudo
42
43
44 PRINTING_OPTIONS_ID = 'printingOptions'
45 COPIES_COUNTERS = '_copies_counters'
46 SOLD_OUT = 'SOLD_OUT'
47
48
49 class PhotoPrintTool(UniqueObject, OrderedFolder) :
50 """
51 Provide utilities to configure possible printing works
52 over photo of the portal.
53 """
54
55 id = 'portal_photo_print'
56 meta_type = 'Photo print tool'
57
58 security = ClassSecurityInfo()
59
60 incomingOrderPath = 'commandes'
61 no_shipping_threshold = 150
62 shipping = 6.0
63 shipping_vat = 0.196
64 store_name = ''
65 _order_counter = 0
66 _transaction_id_counter = 0
67
68 _properties = OrderedFolder._properties + (
69 {'id' : 'incomingOrderPath', 'type' : 'string', 'mode' : 'w'},
70 {'id' : 'no_shipping_threshold', 'type' : 'int', 'mode' : 'w'},
71 {'id' : 'shipping', 'type' : 'float', 'mode' : 'w'},
72 {'id' : 'shipping_vat', 'type' : 'float', 'mode' : 'w'},
73 {'id' : 'store_name', 'type' : 'string', 'mode' : 'w'}
74 )
75
76
77 security.declarePublic('getPrintingOptionsFor')
78 def getPrintingOptionsFor(self, ob) :
79 "returns printing options for the given ob."
80 optionsContainer = getattr(aq_inner(ob), PRINTING_OPTIONS_ID, None)
81 if optionsContainer is None :
82 return None
83
84 counters = self.getCountersFor(ob)
85 if counters.get(SOLD_OUT) :
86 return None
87
88 options = []
89 for o in optionsContainer.objectValues() :
90 if o.maxCopies == 0 or \
91 counters.get(o.productReference, 0) < o.maxCopies :
92 options.append(o)
93
94 return options
95
96 security.declarePublic('getPrintingOptionsContainerFor')
97 def getPrintingOptionsContainerFor(self, ob):
98 """getPrintingOptionsContainerFor
99 """
100 return getattr(ob, PRINTING_OPTIONS_ID, None)
101
102 security.declarePrivate('getCountersFor')
103 def getCountersFor(self, ob):
104 if hasattr(ob.aq_self, COPIES_COUNTERS) :
105 return getattr(ob, COPIES_COUNTERS)
106 else :
107 return {}
108
109
110 security.declareProtected(ManagePrintOrderTemplate, 'createPrintingOptionsContainer')
111 def createPrintingOptionsContainer(self, ob):
112 container = PrintingOptionsContainer()
113 setattr(ob, PRINTING_OPTIONS_ID, container)
114 return getattr(ob, PRINTING_OPTIONS_ID)
115
116 security.declareProtected(ManagePrintOrderTemplate, 'deletePrintingOptionsContainer')
117 def deletePrintingOptionsContainer(self, ob):
118 if not self.hasPrintingOptions(ob) :
119 raise ValueError( _('No printing options found at %r') % ob.absolute_url() )
120 else :
121 delattr(ob, PRINTING_OPTIONS_ID)
122
123 security.declareProtected(ManagePrintOrderTemplate, 'hasPrintingOptions')
124 def hasPrintingOptions(self, ob):
125 """ return boolean that instruct if there's printing
126 options especially defined on ob
127 """
128 return hasattr(aq_base(ob), PRINTING_OPTIONS_ID)
129
130
131 security.declareProtected(ManagePrintOrderTemplate, 'getPrintingOptionsSrc')
132 def getPrintingOptionsSrc(self, ob) :
133 optionsContainer = getattr(ob, PRINTING_OPTIONS_ID, None)
134 if optionsContainer is None :
135 return None
136 src = optionsContainer.aq_inner.aq_parent
137 return src
138
139 security.declareProtected(ManagePrintOrderTemplate, 'getPrintOrderOptionsContainerFor')
140 def getPrintOrderOptionsContainerFor(self, ob) :
141 """
142 returns the printing options container or None.
143 """
144 if hasattr(aq_base(ob), PRINTING_OPTIONS_ID) :
145 return getattr(ob, PRINTING_OPTIONS_ID)
146
147 security.declareProtected(ManagePrintOrderTemplate, 'addPrintOrderTemplate')
148 @postonly
149 def addPrintOrderTemplate(self
150 , ob
151 , title=''
152 , description=''
153 , productReference=''
154 , maxCopies=0
155 , price=0
156 , VATRate=0
157 , REQUEST=None):
158
159 title, maxCopies, price, VATRate = PhotoPrintTool._ckeckTemplateParams(title, maxCopies, price, VATRate)
160
161 container = getattr(ob, PRINTING_OPTIONS_ID)
162
163 id = makeValidId(container, title)
164
165 factory = getUtility(IFactory, 'photoprint.order_template')
166 orderTemplate = factory( id
167 , title=title
168 , description=description
169 , productReference=productReference
170 , maxCopies=maxCopies
171 , price=price
172 , VATRate=VATRate
173 )
174 container._setObject(id, orderTemplate)
175 return orderTemplate.__of__(container)
176
177
178 security.declareProtected(ManagePrintOrderTemplate, 'editPrintOrderTemplate')
179 @postonly
180 def editPrintOrderTemplate(self, ob, id, REQUEST=None, **kw):
181 container = self.getPrintingOptionsContainerFor(ob)
182 orderTemplate = getattr(container, id)
183
184 g = kw.get
185 title, description, productReference, maxCopies, price, VATRate = \
186 g('title', ''), g('description', ''), g('productReference'), g('maxCopies',0), g('price',0), g('VATRate', 0)
187 title, maxCopies, price, VATRate = PhotoPrintTool._ckeckTemplateParams(title, maxCopies, price, VATRate)
188
189 orderTemplate.edit( title=title
190 , description=description
191 , productReference=productReference
192 , maxCopies = maxCopies
193 , price=price
194 , VATRate=VATRate)
195
196 return orderTemplate
197
198 @staticmethod
199 def _ckeckTemplateParams(title, maxCopies, price, VATRate) :
200 title = title.strip()
201
202 if not title :
203 raise ValueError(_(u'You must enter a title.'))
204 try :
205 maxCopies = int(maxCopies)
206 except ValueError :
207 raise ValueError(_(u'You must enter an integer number\nfor the maximum number of copies.'))
208 if maxCopies < 0 :
209 raise ValueError(_(u'You must enter a positive value\nfor the maximum number of copies.'))
210 try :
211 price = float(price.replace(',', '.'))
212 except ValueError :
213 raise ValueError(_(u'You must enter a numeric value for the price.'))
214
215 try :
216 VATRate = float(VATRate.replace(',', '.')) / 100
217 except ValueError :
218 raise ValueError(_(u'You must enter a numeric value for the VAT rate.'))
219
220 return title, maxCopies, price, VATRate
221
222 security.declarePublic('addPrintOrder')
223 def addPrintOrder(self, cart):
224 utool = getToolByName(self, 'portal_url')
225 portal = utool.getPortalObject()
226 ttool = getToolByName(portal, 'portal_types')
227
228 baseContainer = portal.unrestrictedTraverse(self.getProperty('incomingOrderPath'), None)
229 if baseContainer is None:
230 parts = self.getProperty('incomingOrderPath').split('/')
231 baseContainer = portal
232 for id in parts :
233 if not hasattr(baseContainer.aq_base, id) :
234 id = _sudo(lambda:ttool.constructContent('Order Folder', baseContainer, id))
235 baseContainer = getattr(baseContainer, id)
236
237 now = DateTime()
238 monthId = now.strftime('%Y-%m')
239 if not hasattr(baseContainer.aq_base, monthId) :
240 monthId = _sudo(lambda:ttool.constructContent('Order Folder', baseContainer, monthId))
241
242 container = getattr(baseContainer, monthId)
243
244 self._order_counter += 1
245 id = '%s-%d' % (monthId, self._order_counter)
246 id = container.invokeFactory('Order', id)
247 ob = getattr(container,id)
248 ob.loadCart(cart)
249 return ob
250
251 security.declarePublic('getShippingFeesFor')
252 def getShippingFeesFor(self, shippable=None, price=None):
253 # returns Fees
254 # TODO: use adapters
255 # for the moment, shippable objet must provide a 'price' attribute
256
257 if shippable and price :
258 raise AttributeError("'shippable' and 'price' are mutually exclusive.")
259
260 if shippable :
261 amount = shippable.price.getValues()['taxed']
262 else :
263 amount = price.getValues()['taxed']
264
265 threshold = self.getProperty('no_shipping_threshold')
266
267 if amount < threshold :
268 fees = Price(self.getProperty('shipping')
269 , self.getProperty('shipping_vat'))
270 else :
271 fees = Price(0,0)
272 return fees
273
274 security.declarePrivate('getNextTransactionId')
275 def getNextTransactionId(self):
276 trid = self._transaction_id_counter
277 trid = (trid + 1) % 1000000
278 self._transaction_id_counter = trid
279 trid = str(trid).zfill(6)
280 return trid
281
282
283 InitializeClass(PhotoPrintTool)
284
285
286 class PrintingOptionsContainer(OrderedFolder) :
287 meta_type = 'Printing options container'
288 security = ClassSecurityInfo()
289
290 def __init__(self) :
291 self.id = PRINTING_OPTIONS_ID
292
293 def __getitem__(self, k) :
294 sd = context.session_data_manager.getSessionData(create = 1)