eggification
[photoprint.git] / Products / photoprint / tool.py
diff --git a/Products/photoprint/tool.py b/Products/photoprint/tool.py
new file mode 100755 (executable)
index 0000000..ce244b5
--- /dev/null
@@ -0,0 +1,294 @@
+# -*- coding: utf-8 -*-
+#######################################################################################
+# Copyright © 2009 Benoît Pin <pin@cri.ensmp.fr>                                      #
+# Plinn - http://plinn.org                                                            #
+#                                                                                     #
+#                                                                                     #
+#   This program is free software; you can redistribute it and/or                     #
+#   modify it under the terms of the GNU General Public License                       #
+#   as published by the Free Software Foundation; either version 2                    #
+#   of the License, or (at your option) any later version.                            #
+#                                                                                     #
+#   This program is distributed in the hope that it will be useful,                   #
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of                    #
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                     #
+#   GNU General Public License for more details.                                      #
+#                                                                                     #
+#   You should have received a copy of the GNU General Public License                 #
+#   along with this program; if not, write to the Free Software                       #
+#   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.   #
+#######################################################################################
+"""
+Photo print tool. Used to link photo to print orders.
+
+
+
+"""
+
+from AccessControl import ClassSecurityInfo
+from AccessControl.requestmethod import postonly
+from Acquisition import aq_base, aq_inner
+from Globals import InitializeClass
+from OFS.OrderedFolder import OrderedFolder
+from Products.CMFCore.utils import UniqueObject, getToolByName
+from permissions import ManagePrintOrderTemplate
+from price import Price
+from utils import Message as _
+from Products.Plinn.utils import makeValidId
+from zope.component import getUtility
+from zope.component.interfaces import IFactory
+from DateTime import DateTime
+from Products.Plinn.utils import _sudo
+
+
+PRINTING_OPTIONS_ID = 'printingOptions'
+COPIES_COUNTERS = '_copies_counters'
+SOLD_OUT = 'SOLD_OUT'
+
+
+class PhotoPrintTool(UniqueObject, OrderedFolder) :
+       """
+       Provide utilities to configure possible printing works
+       over photo of the portal.
+       """
+       
+       id = 'portal_photo_print'
+       meta_type = 'Photo print tool'
+       
+       security = ClassSecurityInfo()
+       
+       incomingOrderPath = 'commandes'
+       no_shipping_threshold = 150
+       shipping = 6.0
+       shipping_vat = 0.196
+       store_name = ''
+       _order_counter = 0
+       _transaction_id_counter = 0
+       
+       _properties = OrderedFolder._properties + (
+               {'id' : 'incomingOrderPath',            'type' : 'string',      'mode' : 'w'},
+               {'id' : 'no_shipping_threshold',        'type' : 'int',         'mode' : 'w'},
+               {'id' : 'shipping',                                     'type' : 'float',       'mode' : 'w'},
+               {'id' : 'shipping_vat',                         'type' : 'float',       'mode' : 'w'},
+               {'id' : 'store_name',                           'type' : 'string',      'mode' : 'w'}
+               )
+       
+       
+       security.declarePublic('getPrintingOptionsFor')
+       def getPrintingOptionsFor(self, ob) :
+               "returns printing options for the given ob."
+               optionsContainer = getattr(aq_inner(ob), PRINTING_OPTIONS_ID, None)
+               if optionsContainer is None :
+                       return None
+               
+               counters = self.getCountersFor(ob)
+               if counters.get(SOLD_OUT) :
+                       return None
+               
+               options = []
+               for o in optionsContainer.objectValues() :
+                       if o.maxCopies == 0 or \
+                               counters.get(o.productReference, 0) < o.maxCopies :
+                               options.append(o)
+               
+               return options
+       
+       security.declarePublic('getPrintingOptionsContainerFor')
+       def getPrintingOptionsContainerFor(self, ob):
+               """getPrintingOptionsContainerFor
+               """
+               return getattr(ob, PRINTING_OPTIONS_ID, None)
+       
+       security.declarePrivate('getCountersFor')
+       def getCountersFor(self, ob):
+               if hasattr(ob.aq_self, COPIES_COUNTERS) :
+                       return getattr(ob, COPIES_COUNTERS)
+               else :
+                       return {}
+       
+       
+       security.declareProtected(ManagePrintOrderTemplate, 'createPrintingOptionsContainer')
+       def createPrintingOptionsContainer(self, ob):
+               container = PrintingOptionsContainer()
+               setattr(ob, PRINTING_OPTIONS_ID, container)
+               return getattr(ob, PRINTING_OPTIONS_ID)
+       
+       security.declareProtected(ManagePrintOrderTemplate, 'deletePrintingOptionsContainer')
+       def deletePrintingOptionsContainer(self, ob):
+               if not self.hasPrintingOptions(ob) :
+                       raise ValueError( _('No printing options found at %r') % ob.absolute_url() )
+               else :
+                       delattr(ob, PRINTING_OPTIONS_ID)
+       
+       security.declareProtected(ManagePrintOrderTemplate, 'hasPrintingOptions')
+       def hasPrintingOptions(self, ob):
+               """ return boolean that instruct if there's printing
+                       options especially defined on ob 
+               """
+               return hasattr(aq_base(ob), PRINTING_OPTIONS_ID)
+       
+       
+       security.declareProtected(ManagePrintOrderTemplate, 'getPrintingOptionsSrc')
+       def getPrintingOptionsSrc(self, ob) :
+               optionsContainer = getattr(ob, PRINTING_OPTIONS_ID, None)
+               if optionsContainer is None :
+                       return None
+               src = optionsContainer.aq_inner.aq_parent
+               return src
+       
+       security.declareProtected(ManagePrintOrderTemplate, 'getPrintOrderOptionsContainerFor')
+       def getPrintOrderOptionsContainerFor(self, ob) :
+               """
+               returns the printing options container or None.
+               """
+               if hasattr(aq_base(ob), PRINTING_OPTIONS_ID) :
+                       return getattr(ob, PRINTING_OPTIONS_ID)
+       
+       security.declareProtected(ManagePrintOrderTemplate, 'addPrintOrderTemplate')
+       @postonly
+       def addPrintOrderTemplate(self
+                                                       , ob
+                                                       , title=''
+                                                       , description=''
+                                                       , productReference=''
+                                                       , maxCopies=0
+                                                       , price=0
+                                                       , VATRate=0
+                                                       , REQUEST=None):
+               
+               title, maxCopies, price, VATRate = PhotoPrintTool._ckeckTemplateParams(title, maxCopies, price,  VATRate)
+               
+               container = getattr(ob, PRINTING_OPTIONS_ID)
+               
+               id = makeValidId(container, title)
+               
+               factory = getUtility(IFactory, 'photoprint.order_template')
+               orderTemplate = factory( id
+                                                          , title=title
+                                                          , description=description
+                                                          , productReference=productReference
+                                                          , maxCopies=maxCopies
+                                                          , price=price
+                                                          , VATRate=VATRate
+                                                          )
+               container._setObject(id, orderTemplate)
+               return orderTemplate.__of__(container)
+               
+       
+       security.declareProtected(ManagePrintOrderTemplate, 'editPrintOrderTemplate')
+       @postonly
+       def editPrintOrderTemplate(self, ob, id, REQUEST=None, **kw):
+               container = self.getPrintingOptionsContainerFor(ob)
+               orderTemplate = getattr(container, id)
+
+               g = kw.get
+               title, description, productReference, maxCopies, price, VATRate = \
+                       g('title', ''), g('description', ''), g('productReference'), g('maxCopies',0), g('price',0), g('VATRate', 0)
+               title, maxCopies, price, VATRate = PhotoPrintTool._ckeckTemplateParams(title, maxCopies, price, VATRate)
+               
+               orderTemplate.edit( title=title
+                                                 , description=description
+                                                 , productReference=productReference
+                                                 , maxCopies = maxCopies
+                                                 , price=price
+                                                 , VATRate=VATRate)
+               
+               return orderTemplate
+       
+       @staticmethod
+       def _ckeckTemplateParams(title, maxCopies, price, VATRate) :
+               title = title.strip()
+               
+               if not title :
+                       raise ValueError(_(u'You must enter a title.'))
+               try :
+                       maxCopies = int(maxCopies)
+               except ValueError :
+                       raise ValueError(_(u'You must enter an integer number\nfor the maximum number of copies.'))
+               if maxCopies < 0 :
+                       raise ValueError(_(u'You must enter a positive value\nfor the maximum number of copies.'))
+               try :
+                       price = float(price.replace(',', '.'))
+               except ValueError :
+                       raise ValueError(_(u'You must enter a numeric value for the price.'))
+       
+               try :
+                       VATRate = float(VATRate.replace(',', '.')) / 100
+               except ValueError :
+                       raise ValueError(_(u'You must enter a numeric value for the VAT rate.'))
+               
+               return title, maxCopies, price, VATRate
+       
+       security.declarePublic('addPrintOrder')
+       def addPrintOrder(self, cart):
+               utool = getToolByName(self, 'portal_url')
+               portal = utool.getPortalObject()
+               ttool = getToolByName(portal, 'portal_types')
+
+               baseContainer = portal.unrestrictedTraverse(self.getProperty('incomingOrderPath'), None)
+               if baseContainer is None:
+                       parts = self.getProperty('incomingOrderPath').split('/')
+                       baseContainer = portal
+                       for id in parts :
+                               if not hasattr(baseContainer.aq_base, id) :
+                                       id = _sudo(lambda:ttool.constructContent('Order Folder', baseContainer, id))
+                               baseContainer = getattr(baseContainer, id)
+
+               now = DateTime()
+               monthId = now.strftime('%Y-%m')
+               if not hasattr(baseContainer.aq_base, monthId) :
+                       monthId = _sudo(lambda:ttool.constructContent('Order Folder', baseContainer, monthId))
+               
+               container = getattr(baseContainer, monthId)
+
+               self._order_counter += 1
+               id = '%s-%d' % (monthId, self._order_counter)
+               id = container.invokeFactory('Order', id)
+               ob = getattr(container,id)
+               ob.loadCart(cart)
+               return ob
+       
+       security.declarePublic('getShippingFeesFor')
+       def getShippingFeesFor(self, shippable=None, price=None):
+               # returns Fees
+               # TODO: use adapters
+               # for the moment, shippable objet must provide a 'price' attribute
+               
+               if shippable and price :
+                       raise AttributeError("'shippable' and 'price' are mutually exclusive.")
+               
+               if shippable :
+                       amount = shippable.price.getValues()['taxed']
+               else :
+                       amount = price.getValues()['taxed']
+               
+               threshold = self.getProperty('no_shipping_threshold')
+
+               if amount < threshold :
+                       fees = Price(self.getProperty('shipping')
+                                               , self.getProperty('shipping_vat'))
+               else :
+                       fees = Price(0,0)
+               return fees
+       
+       security.declarePrivate('getNextTransactionId')
+       def getNextTransactionId(self):
+               trid = self._transaction_id_counter
+               trid = (trid + 1) % 1000000
+               self._transaction_id_counter = trid
+               trid = str(trid).zfill(6)
+               return trid
+
+
+InitializeClass(PhotoPrintTool)
+
+
+class PrintingOptionsContainer(OrderedFolder) :
+       meta_type = 'Printing options container'
+       security = ClassSecurityInfo()
+       
+       def __init__(self) :
+               self.id = PRINTING_OPTIONS_ID
+       
+       def __getitem__(self, k) :
+               sd = context.session_data_manager.getSessionData(create = 1)