You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

prices_and_availability.rst 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. =======================
  2. Prices and availability
  3. =======================
  4. This page explains how prices and availability are determined in Oscar. In
  5. short, it seems quite complicated at first as there are several parts to it, but what
  6. this buys is flexibility: buckets of it.
  7. Overview
  8. --------
  9. Simpler e-commerce frameworks often tie prices to the product model directly:
  10. .. code-block:: python
  11. >>> product = Product.objects.get(id=1)
  12. >>> product.price
  13. Decimal('17.99')
  14. Oscar, on the other hand, distinguishes products from stockrecords and provides
  15. a swappable 'strategy' component for selecting the appropriate stockrecord,
  16. calculating prices and availability information.
  17. .. code-block:: python
  18. >>> product = Product.objects.get(id=1)
  19. >>> info = strategy.fetch_for_product(product)
  20. # Availability information
  21. >>> info.availability.is_available_to_buy
  22. True
  23. >>> msg = info.availability.message
  24. >>> unicode(msg)
  25. u"In stock (58 available)"
  26. >>> info.availability.is_purchase_permitted(59)
  27. (False, u"A maximum of 58 can be bought")
  28. # Price information
  29. >>> info.price.excl_tax
  30. Decimal('17.99')
  31. >>> info.price.is_tax_known
  32. True
  33. >>> info.price.incl_tax
  34. Decimal('21.59')
  35. >>> info.price.tax
  36. Decimal('3.60')
  37. >>> info.price.currency
  38. 'GBP'
  39. The product model captures the core data about the product (title, description,
  40. images) while a stockrecord represents fulfillment information for one
  41. particular partner (number in stock, base price). A product can have multiple
  42. stockrecords although only one is selected by the strategy to determine pricing and
  43. availability.
  44. By using your own custom strategy class, a wide range of pricing, tax and
  45. availability problems can be easily solved.
  46. .. note::
  47. Oscar's handling of prices and availability was reworked for v0.6.
  48. The old APIs are still available but considered deprecated and
  49. will be removed in Oscar 0.7.
  50. .. _strategy_class:
  51. The strategy class
  52. ------------------
  53. Oscar uses a 'strategy' object to determine product availability and pricing. A
  54. new strategy instance is assigned to the request by the basket middleware. A
  55. :class:`~oscar.apps.partner.strategy.Selector`
  56. class determines the appropriate strategy for the
  57. request. By modifying the
  58. :class:`~oscar.apps.partner.strategy.Selector`
  59. class, it's possible to return
  60. different strategies for different customers.
  61. Given a product, the strategy class is responsible for:
  62. - Selecting a "pricing policy", an object detailing the prices of the product and whether tax is known.
  63. - Selecting an "availability policy", an object responsible for
  64. availability logic (ie is the product available to buy) and customer
  65. messaging.
  66. - Selecting the appropriate stockrecord to use for fulfillment. If a product
  67. can be fulfilled by several fulfilment partners, then each will have their
  68. own stockrecord.
  69. These three entities are wrapped up in a ``PurchaseInfo`` object, which is a
  70. simple named tuple. The strategy class provides ``fetch_for_product`` and
  71. ``fetch_for_group`` methods which takes a product and returns a ``PurchaseInfo``
  72. instance:
  73. The strategy class is accessed in several places in Oscar's codebase. In templates, a
  74. ``purchase_info_for_product`` template tag is used to load the price and availability
  75. information into the template context:
  76. .. code-block:: html+django
  77. {% load purchase_info_tags %}
  78. {% load currency_filters %}
  79. {% purchase_info_for_product request product as session %}
  80. <p>
  81. {% if session.price.is_tax_known %}
  82. Price is {{ session.price.incl_tax|currency:session.price.currency }}
  83. {% else %}
  84. Price is {{ session.price.excl_tax|currency:session.price.currency }} +
  85. tax
  86. {% endif %}
  87. </p>
  88. Note that the ``currency`` template tag accepts a currency parameter from the
  89. pricing policy.
  90. Also, basket instances have a strategy instance assigned so they can calculate
  91. prices including taxes. This is done automatically in the basket middleware.
  92. This seems quite complicated...
  93. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  94. While this probably seems like quite an involved way of looking up a product's
  95. price, it gives the developer an immense amount of flexibility. Here's a few
  96. examples of things you can do with a strategy class:
  97. - Transact in multiple currencies. The strategy
  98. class can use the customer's location to select a stockrecord from a local
  99. distribution partner which will be in the local currency of the customer.
  100. - Elegantly handle different tax models. A strategy can return prices including
  101. tax for a UK or European visitor, but without tax for US
  102. visitors where tax is only determined once shipping details are confirmed.
  103. - Charge different prices to different customers. A strategy can return a
  104. different pricing policy depending on the user/session.
  105. - Use a chain of preferred partners for fulfillment. A site could have many
  106. stockrecords for the same product, each from a different fulfillment partner.
  107. The strategy class could select the partner with the best margin and stock
  108. available. When stock runs out with that partner, the strategy could
  109. seamlessly switch to the next best partner.
  110. These are the kinds of problems that other e-commerce frameworks would struggle
  111. with.
  112. API
  113. ~~~
  114. All strategies subclass a common ``Base`` class:
  115. .. autoclass:: oscar.apps.partner.strategy.Base
  116. :members: fetch_for_product, fetch_for_group, fetch_for_line
  117. :noindex:
  118. Oscar also provides a "structured" strategy class which provides overridable
  119. methods for selecting the stockrecord, and determining pricing and availability
  120. policies:
  121. .. autoclass:: oscar.apps.partner.strategy.Structured
  122. :members:
  123. :noindex:
  124. For most projects, subclassing and overriding the ``Structured`` base class
  125. should be sufficient. However, Oscar also provides mixins to easily compose the
  126. appropriate strategy class for your domain.
  127. Loading a strategy
  128. ------------------
  129. Strategy instances are determined by the ``Selector`` class:
  130. .. autoclass:: oscar.apps.partner.strategy.Selector
  131. :members:
  132. :noindex:
  133. It's common to override this class so a custom strategy class can be returned.
  134. .. _pricing_policies:
  135. Pricing policies
  136. ----------------
  137. A pricing policy is a simple class with several properties Its job is to
  138. contain all price and tax information about a product.
  139. There is a base class that defines the interface a pricing policy should have:
  140. .. autoclass:: oscar.apps.partner.prices.Base
  141. :members:
  142. :noindex:
  143. There are also several policies that accommodate common scenarios:
  144. .. automodule:: oscar.apps.partner.prices
  145. :members: Unavailable, FixedPrice, DelegateToStockRecord
  146. :noindex:
  147. .. _availability_policies:
  148. Availability policies
  149. ---------------------
  150. Like pricing policies, availability policies are simple classes with several
  151. properties and methods. The job of an availability policy is to provide
  152. availability messaging to show to the customer as well as methods to determine
  153. if the product is available to buy.
  154. The base class defines the interface:
  155. .. autoclass:: oscar.apps.partner.availability.Base
  156. :members:
  157. :noindex:
  158. There are also several pre-defined availability policies:
  159. .. automodule:: oscar.apps.partner.availability
  160. :members: Unavailable, Available, StockRequired, DelegateToStockRecord
  161. :noindex:
  162. Strategy mixins
  163. ---------------
  164. Oscar also ships with several mixins which implement one method of the
  165. ``Structured`` strategy. These allow strategies to be easily
  166. composed from re-usable parts:
  167. .. automodule:: oscar.apps.partner.strategy
  168. :members: UseFirstStockRecord, StockRequired, NoTax, FixedRateTax,
  169. DeferredTax
  170. :noindex:
  171. Default strategy
  172. ----------------
  173. Oscar's default ``Selector`` class returns a ``Default`` strategy built from
  174. the strategy mixins:
  175. .. code-block:: python
  176. class Default(UseFirstStockRecord, StockRequired, NoTax, Structured):
  177. pass
  178. The behaviour of this strategy is:
  179. - Always picks the first stockrecord (this is backwards compatible with
  180. Oscar<0.6 where a product could only have one stockrecord).
  181. - Charge no tax.
  182. - Only allow purchases where there is appropriate stock (eg no back-orders).
  183. How to use
  184. ----------
  185. There's lots of ways to use strategies, pricing and availability policies to
  186. handle your domain's requirements.
  187. The normal first step is provide your own ``Selector`` class which returns a custom
  188. strategy class. Your custom strategy class can be composed of the above mixins
  189. or your own custom logic.
  190. Example 1: UK VAT
  191. ~~~~~~~~~~~~~~~~~
  192. Here's an example ``strategy.py`` module which is used to charge VAT on prices.
  193. .. code-block:: python
  194. # myproject/partner/strategy.py
  195. from oscar.apps.partner import strategy, prices
  196. class Selector(object):
  197. """
  198. Custom selector to return a UK-specific strategy that charges VAT
  199. """
  200. def strategy(self, request=None, user=None, **kwargs):
  201. return UKStrategy(territory)
  202. class IncludingVAT(strategy.FixedRateTax):
  203. """
  204. Price policy to charge VAT on the base price
  205. """
  206. # We can simply override the tax rate on the core FixedRateTax. Note
  207. # this is a simplification: in reality, you might want to store tax
  208. # rates and the date ranges they apply in a database table. Your
  209. # pricing policy could simply look up the appropriate rate.
  210. rate = D('0.20')
  211. class UKStrategy(strategy.UseFirstStockRecord, IncludingVAT,
  212. strategy.StockRequired, strategy.Structured):
  213. """
  214. Typical UK strategy for physical goods.
  215. - There's only one warehouse/partner so we use the first and only stockrecord
  216. - Enforce stock level. Don't allow purchases when we don't have stock.
  217. - Charge UK VAT on prices. Assume everything is standard-rated.
  218. """
  219. Example 2: US sales tax
  220. ~~~~~~~~~~~~~~~~~~~~~~~
  221. Here's an example ``strategy.py`` module which is suitable for use in the US
  222. where taxes can't be calculated until the shipping address is known. You
  223. normally need to use a 3rd party service to determine taxes - details omitted
  224. here.
  225. .. code-block:: python
  226. from oscar.apps.partner import strategy, prices
  227. class Selector(object):
  228. """
  229. Custom selector class to returns a US strategy
  230. """
  231. def strategy(self, request=None, user=None, **kwargs):
  232. return USStrategy()
  233. class USStrategy(strategy.UseFirstStockRecord, strategy.DeferredTax,
  234. strategy.StockRequired, strategy.Structured):
  235. """
  236. Typical US strategy for physical goods. Note we use the ``DeferredTax``
  237. mixin to ensure prices are returned without tax.
  238. - Use first stockrecord
  239. - Enforce stock level
  240. - Taxes aren't known for prices at this stage
  241. """