From e7fb4cd238a9ea87a86fff568652597e4292683d Mon Sep 17 00:00:00 2001 From: helmutm Date: Sat, 3 Jan 2009 09:24:14 +0000 Subject: [PATCH] work in progress: orders and order items git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@3110 fd906abe-77d9-0310-91a1-e0d9ade77398 --- commerce/README.txt | 25 +++++++++++- commerce/common.py | 50 +++++++++++++++++++++--- commerce/customer.py | 9 ++--- commerce/interfaces.py | 88 ++++++++++++++++++++++++++++++++++++------ commerce/manager.py | 17 +++++++- commerce/order.py | 84 ++++++++++++++++++++++++++++++++++++++++ commerce/product.py | 31 +++++++++------ 7 files changed, 266 insertions(+), 38 deletions(-) create mode 100644 commerce/order.py diff --git a/commerce/README.txt b/commerce/README.txt index 4b9a8cc..9952315 100644 --- a/commerce/README.txt +++ b/commerce/README.txt @@ -4,6 +4,8 @@ Commerce: Shope, Products, Customers, Orders, ... ($Id$) + >>> from zope import component + >>> from cybertools.commerce.manager import Manager >>> manager = Manager() @@ -42,6 +44,13 @@ it belongs to. >>> sorted((s.name, s.title) for s in p003.shops) [(u'shop1', u'PC up Ltd'), (u'shop2', u'Video up Ltd')] +We can also create a manufacturer and set it for a product. + + >>> mf001 = manager.manufacturers.create(u'001', title=u'Global Electronics') + >>> p001.manufacturer = mf001 + >>> [p.title for p in mf001.products] + [u'Silent Case'] + Customers ========= @@ -66,6 +75,18 @@ Customers [(u'shop1', u'PC up Ltd'), (u'shop2', u'Video up Ltd')] -Orders -====== +Carts and Orders +================ + >>> from cybertools.commerce.order import OrderItems + >>> component.provideAdapter(OrderItems) + + >>> orderItems = manager.orderItems + + >>> orderItems.add(p001, c001, quantity=3) + + +Orders +------ + + >>> ord001 = manager.orders.create(u'001', shop=shop1, customer=c001) diff --git a/commerce/common.py b/commerce/common.py index 592e601..8d857dc 100644 --- a/commerce/common.py +++ b/commerce/common.py @@ -51,22 +51,62 @@ class ContainerAttribute(object): class RelationSet(object): - def __init__(self, parent, attributeName): + def __init__(self, parent, attributeName=None): self.parent = parent self.attributeName = attributeName self.data = {} def add(self, related): self.data[related.name] = related - relatedData = getattr(related, self.attributeName).data - relatedData[self.parent.name] = self.parent + if self.attributeName: + value = getattr(related, self.attributeName) + if isinstance(value, RelationSet): + relatedData = value.data + relatedData[self.parent.name] = self.parent + else: + setattr(related, self.attributeName, self.parent) def remove(self, related): name = related.name del self.data[name] - relatedData = getattr(related, self.attributeName).data - del relatedData[self.parent.name] + if self.attributeName: + value = getattr(related, self.attributeName) + if isinstance(value, RelationSet): + relatedData = value.data + del relatedData[self.parent.name] + else: + setattr(related, self.attributeName, None) def __iter__(self): for obj in self.data.values(): yield obj + + +class Relation(object): + + def __init__(self, name, otherName): + self.name = name + self.otherName = otherName + + def __get__(self, inst, class_=None): + if inst is None: + return self + return getattr(inst, self.name) + + def __set__(self, inst, value): + existing = getattr(inst, self.name, None) + if existing is not None: + other = getattr(existing, self.otherName).data + for k, v in other.items(): + if v != inst: + del other[k] + if value is not None: + other = getattr(value, self.otherName).data + other[inst.name] = inst + setattr(inst, self.name, value) + + +class BaseObject(object): + + collection = RelationSet + diff --git a/commerce/customer.py b/commerce/customer.py index d658f60..cd932b8 100644 --- a/commerce/customer.py +++ b/commerce/customer.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2008 Helmut Merz helmutm@cy55.de +# Copyright (c) 2009 Helmut Merz helmutm@cy55.de # # 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 @@ -24,21 +24,20 @@ $Id$ from zope.interface import implements, Interface -from cybertools.commerce.common import RelationSet +from cybertools.commerce.common import RelationSet, BaseObject from cybertools.commerce.interfaces import ICustomer, IAddress -class Customer(object): +class Customer(BaseObject): implements(ICustomer) - collection = RelationSet - def __init__(self, customerId, title=None, client=None): self.name = self.customerId = customerId self.title = title or u'unknown' self.client = client self.shops = self.collection(self, 'customers') + self.orders = self.collection(self, 'customer') class Address(object): diff --git a/commerce/interfaces.py b/commerce/interfaces.py index b083e70..fa6e2dc 100644 --- a/commerce/interfaces.py +++ b/commerce/interfaces.py @@ -1,5 +1,6 @@ +#-*- coding: UTF-8 -*- # -# Copyright (c) 2008 Helmut Merz helmutm@cy55.de +# Copyright (c) 2009 Helmut Merz helmutm@cy55.de # # 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 @@ -29,6 +30,7 @@ from zope.i18nmessageid import MessageFactory from cybertools.util.jeep import Jeep, Term from cybertools.organize.interfaces import IAddress as IBaseAddress +from cybertools.organize.interfaces import IPerson as IBasePerson from loops import util _ = MessageFactory('cybertools.commerce') @@ -41,12 +43,15 @@ class IManager(Interface): all components of a commerce site. """ - shops = Attribute('All shops in this commerce manager.') - products = Attribute('All products in this commerce manager.') - categories = Attribute('All product categories in this commerce manager.') - manufacturers = Attribute('All manufacturers in this commerce manager.') - suppliers = Attribute('All suppliers in this commerce manager.') - customers = Attribute('All customers in this commerce manager.') + shops = Attribute(u'All shops in this commerce manager.') + products = Attribute(u'All products in this commerce manager.') + categories = Attribute(u'All product categories in this commerce manager.') + manufacturers = Attribute(u'All manufacturers in this commerce manager.') + suppliers = Attribute(u'All suppliers in this commerce manager.') + customers = Attribute(u'All customers in this commerce manager.') + orders = Attribute(u'All orders in this commerce manager.') + orderItems = Attribute(u'All order items; may also be cart items without ' + u'relation to an existing order.') # shops @@ -179,6 +184,11 @@ class ICategory(Interface): """ A product category. """ + name = schema.ASCIILine( + title=_(u'Category Identifier'), + description=_(u'An internal name uniquely identifying the category.'), + default='', + required=True) title = schema.TextLine( title=_(u'Title'), description=_(u'Short title of the category.'), @@ -203,6 +213,7 @@ class ICategory(Interface): products = Attribute(u'The products belonging to this category.') subcategories = Attribute(u'The sub-categories belonging to this category.') + parentCategories = Attribute(u'The parent categories belonging to this category.') shops = Attribute(u'The shops providing this category.') accessories = Attribute(u'Accessories for this category.') @@ -210,8 +221,8 @@ class ICategory(Interface): # customers class ICustomer(Interface): - """ Typically a role of - for example - a person or institution (the ``client``) - representing a customer of one or more shops. + """ Typically a role instance of - for example - a person or institution + (the ``client``) representing a customer of one or more shops. The client may be None in which case the customer is an object on its own. @@ -229,16 +240,23 @@ class ICustomer(Interface): required=True) description = schema.Text( title=_(u'Description'), - description=_(u'A medium-length description of the category.'), + description=_(u'A medium-length description of the customer.'), default=u'', missing_value=u'', required=False) shops = Attribute(u'The shops the client object is a customer of.') + paymentTypes = Attribute(u'A collection of payment types supported.') + orders = Attribute(u'A collection of the customer\'s orders.') client = Attribute(u'An optional (real) client object of the customer role.') +class IPerson(IBasePerson): + + customer = Attribute(u'The customer the person belongs to.') + + addressTypesVoc = util.KeywordVocabulary(( ('standard', _(u'Standard Address')), ('invoice', _(u'Invoice Address')), @@ -256,9 +274,42 @@ class IAddress(IBaseAddress): default='standard', required=False) + clients = Attribute(u'A collection of objects that this address belongs to.') # orders +valueTypesVoc = util.KeywordVocabulary(( + ('product', _(u'Product Prices')), + ('shipping', _(u'Shipping Cost')), +)) + +vatRatesVoc = util.KeywordVocabulary(( + ('product', 0), + ('reduced', 7), + ('standard', 19), +)) + + +currencyVoc = util.KeywordVocabulary(( + ('EUR', u'€'), + ('USD', u'$'), +)) + + +class IValue(Interface): + """ An order or item value with additional information about the type of + the value and e.g. the VAT rate. + """ + + type = Attribute(u'The type of the value; see valueTypesVoc.') + value = Attribute(u'The value in the standard currency (EUR), ' + u'stored as a Decimal value.') + currency = Attribute(u'The currency of the value.') + currencyRate = Attribute(u'The rate for converting the value to the ' + u'standard currency, default is 1.') + vat = Attribute(u'The id of the VAT rate; see vatRatesVoc.') + + class IOrder(Interface): """ """ @@ -266,10 +317,16 @@ class IOrder(Interface): orderId = Attribute(u'Order Identifier') shop = Attribute(u'The shop this order belongs to.') customer = Attribute(u'The customer issuing this order.') + invoiceAddress = Attribute(u'The address the invoice should be sent to.') + shippingAddress = Attribute(u'The address the products should be sent to.') + paymentType = Attribute(u'The payment type to be used for the order.') + netValues = Attribute(u'A collection of net total values (IValue objects)' + u'of the order.') + total = Attribute(u'The total gross value (Decimal) of the order.') class IOrderItem(Interface): - """ + """ An individual order or cart item. """ quantity = schema.Int( @@ -278,7 +335,14 @@ class IOrderItem(Interface): default=1, required=True) - order = Attribute(u'Order this order item belongs to.') + order = Attribute(u'The order this order item belongs to.') product = Attribute(u'The product represented by this order item.') unitPrice = Attribute(u'The basic unit price for one of the product ' u'items ordered.') + fullPrice = Attribute(u'The full price for the quantity ordered.') + + +class IOrderItems(Interface): + """ A collection of order items. + """ + diff --git a/commerce/manager.py b/commerce/manager.py index c24b132..26fac27 100644 --- a/commerce/manager.py +++ b/commerce/manager.py @@ -26,9 +26,11 @@ from zope.interface import implements from cybertools.commerce.common import ContainerAttribute from cybertools.commerce.customer import Customer -from cybertools.commerce.interfaces import IManager -from cybertools.commerce.product import Product +from cybertools.commerce.interfaces import IManager, IOrderItems +from cybertools.commerce.product import Product, Category, Manufacturer, Supplier +from cybertools.commerce.order import Order, OrderItem from cybertools.commerce.shop import Shop +from cybertools.tracking.btree import TrackingStorage class Manager(object): @@ -38,4 +40,15 @@ class Manager(object): def __init__(self): self.shops = ContainerAttribute(Shop) self.products = ContainerAttribute(Product, 'productId') + self.categories = ContainerAttribute(Category, 'name') + self.manufacturers = ContainerAttribute(Manufacturer, 'name') + self.suppliers = ContainerAttribute(Supplier, 'name') self.customers = ContainerAttribute(Customer, 'customerId') + self.orders = ContainerAttribute(Order, 'orderId') + self._orderItems = TrackingStorage(trackFactory=OrderItem) + + @property + def orderItems(self): + return IOrderItems(self._orderItems) + + diff --git a/commerce/order.py b/commerce/order.py new file mode 100644 index 0000000..269ca5a --- /dev/null +++ b/commerce/order.py @@ -0,0 +1,84 @@ +# +# Copyright (c) 2009 Helmut Merz helmutm@cy55.de +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Order and order item classes. + +$Id$ +""" + +from zope.component import adapts +from zope.interface import implements, Interface + +from cybertools.commerce.common import Relation, BaseObject +from cybertools.commerce.interfaces import IOrder, IOrderItem, IOrderItems +from cybertools.tracking.btree import Track +from cybertools.tracking.interfaces import ITrackingStorage + + +class Order(object): + + implements(IOrder) + + customer = Relation('_customer', 'orders') + + def __init__(self, orderId, shop=None, customer=None): + self.name = self.orderId = orderId + self.shop = shop + self.customer = customer + + +class OrderItem(Track): + + implements(IOrderItem) + + def __getattr__(self, attr): + if attr not in IOrderItem: + raise AttributeError(attr) + return self.data.get(attr) + + +class OrderItems(object): + """ A tracking storage adapter managing order items. + """ + + implements(IOrderItems) + adapts(ITrackingStorage) + + def __init__(self, context): + self.context = context + + def __getitem__(self, key): + return self.context[key] + + def __iter__(self): + return iter(self.context.values()) + + def query(self, **criteria): + if 'product' in criteria: + criteria['taskId'] = criteria.pop('product') + if 'person' in criteria: + criteria['userName'] = criteria.pop('person') + if 'run' in criteria: + criteria['runId'] = criteria.pop('run') + return self.context.query(**criteria) + + def add(self, product, person, run=0, **kw): + trackId = self.context.saveUserTrack(product, run, person, kw) + track = self[trackId] + return track diff --git a/commerce/product.py b/commerce/product.py index 1e5fc3a..50e9ecb 100644 --- a/commerce/product.py +++ b/commerce/product.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2008 Helmut Merz helmutm@cy55.de +# Copyright (c) 2009 Helmut Merz helmutm@cy55.de # # 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 @@ -24,46 +24,53 @@ $Id$ from zope.interface import implements, Interface -from cybertools.commerce.common import RelationSet +from cybertools.commerce.common import Relation, RelationSet, BaseObject from cybertools.commerce.interfaces import IProduct, ICategory from cybertools.commerce.interfaces import IManufacturer, ISupplier -class Product(object): +class Product(BaseObject): implements(IProduct) - collection = RelationSet + manufacturer = Relation('_manufacturer', 'products') def __init__(self, productId, title=None): self.name = self.productId = productId self.title = title or u'unknown' self.shops = self.collection(self, 'products') + self.categories = self.collection(self, 'products') + self.suppliers = self.collection(self, 'products') -class Category(object): +class Category(BaseObject): implements(ICategory) - collection = RelationSet - - def __init__(self, title=None): + def __init__(self, name, title=None): + self.name = name self.title = title or u'unknown' self.shops = self.collection(self, 'categories') + self.products = self.collection(self, 'categories') + self.subcategories = self.collection(self, 'parentCategories') + self.parentCategories = self.collection(self, 'subCategories') -class Manufacturer(object): +class Manufacturer(BaseObject): implements(IManufacturer) - def __init__(self, title=None): + def __init__(self, name, title=None): + self.name = name self.title = title or u'unknown' + self.products = self.collection(self, 'manufacturer') -class Supplier(object): +class Supplier(BaseObject): implements(ISupplier) - def __init__(self, title=None): + def __init__(self, name, title=None): + self.name = name self.title = title or u'unknown'