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