Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

prices_and_availability.rst 11KB

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