Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

utils.py 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. from decimal import Decimal as D
  2. from django.contrib.sites.models import Site
  3. from django.conf import settings
  4. from django.utils.translation import ugettext_lazy as _
  5. from oscar.core.loading import get_model
  6. from oscar.core.loading import get_class
  7. from . import exceptions
  8. Order = get_model('order', 'Order')
  9. Line = get_model('order', 'Line')
  10. OrderDiscount = get_model('order', 'OrderDiscount')
  11. order_placed = get_class('order.signals', 'order_placed')
  12. class OrderNumberGenerator(object):
  13. """
  14. Simple object for generating order numbers.
  15. We need this as the order number is often required for payment
  16. which takes place before the order model has been created.
  17. """
  18. def order_number(self, basket):
  19. """
  20. Return an order number for a given basket
  21. """
  22. return 100000 + basket.id
  23. class OrderCreator(object):
  24. """
  25. Places the order by writing out the various models
  26. """
  27. def place_order(self, basket, total, # noqa (too complex (12))
  28. shipping_method, shipping_charge, user=None,
  29. shipping_address=None, billing_address=None,
  30. order_number=None, status=None, **kwargs):
  31. """
  32. Placing an order involves creating all the relevant models based on the
  33. basket and session data.
  34. """
  35. if basket.is_empty:
  36. raise ValueError(_("Empty baskets cannot be submitted"))
  37. if not order_number:
  38. generator = OrderNumberGenerator()
  39. order_number = generator.order_number(basket)
  40. if not status and hasattr(settings, 'OSCAR_INITIAL_ORDER_STATUS'):
  41. status = getattr(settings, 'OSCAR_INITIAL_ORDER_STATUS')
  42. try:
  43. Order._default_manager.get(number=order_number)
  44. except Order.DoesNotExist:
  45. pass
  46. else:
  47. raise ValueError(_("There is already an order with number %s")
  48. % order_number)
  49. # Ok - everything seems to be in order, let's place the order
  50. order = self.create_order_model(
  51. user, basket, shipping_address, shipping_method, shipping_charge,
  52. billing_address, total, order_number, status, **kwargs)
  53. for line in basket.all_lines():
  54. self.create_line_models(order, line)
  55. self.update_stock_records(line)
  56. # Record any discounts associated with this order
  57. for application in basket.offer_applications:
  58. # Trigger any deferred benefits from offers and capture the
  59. # resulting message
  60. application['message'] \
  61. = application['offer'].apply_deferred_benefit(basket, order,
  62. application)
  63. # Record offer application results
  64. if application['result'].affects_shipping:
  65. # Skip zero shipping discounts
  66. shipping_discount = shipping_method.discount(basket)
  67. if shipping_discount <= D('0.00'):
  68. continue
  69. # If a shipping offer, we need to grab the actual discount off
  70. # the shipping method instance, which should be wrapped in an
  71. # OfferDiscount instance.
  72. application['discount'] = shipping_discount
  73. self.create_discount_model(order, application)
  74. self.record_discount(application)
  75. for voucher in basket.vouchers.all():
  76. self.record_voucher_usage(order, voucher, user)
  77. # Send signal for analytics to pick up
  78. order_placed.send(sender=self, order=order, user=user)
  79. return order
  80. def create_order_model(self, user, basket, shipping_address,
  81. shipping_method, shipping_charge, billing_address,
  82. total, order_number, status, **extra_order_fields):
  83. """
  84. Create an order model.
  85. """
  86. order_data = {'basket': basket,
  87. 'number': order_number,
  88. 'site': Site._default_manager.get_current(),
  89. 'currency': total.currency,
  90. 'total_incl_tax': total.incl_tax,
  91. 'total_excl_tax': total.excl_tax,
  92. 'shipping_incl_tax': shipping_charge.incl_tax,
  93. 'shipping_excl_tax': shipping_charge.excl_tax,
  94. 'shipping_method': shipping_method.name,
  95. 'shipping_code': shipping_method.code}
  96. if shipping_address:
  97. order_data['shipping_address'] = shipping_address
  98. if billing_address:
  99. order_data['billing_address'] = billing_address
  100. if user and user.is_authenticated():
  101. order_data['user_id'] = user.id
  102. if status:
  103. order_data['status'] = status
  104. if extra_order_fields:
  105. order_data.update(extra_order_fields)
  106. order = Order(**order_data)
  107. order.save()
  108. return order
  109. def create_line_models(self, order, basket_line, extra_line_fields=None):
  110. """
  111. Create the batch line model.
  112. You can set extra fields by passing a dictionary as the
  113. extra_line_fields value
  114. """
  115. product = basket_line.product
  116. stockrecord = basket_line.stockrecord
  117. if not stockrecord:
  118. raise exceptions.UnableToPlaceOrder(
  119. "Baket line #%d has no stockrecord" % basket_line.id)
  120. partner = stockrecord.partner
  121. line_data = {
  122. 'order': order,
  123. # Partner details
  124. 'partner': partner,
  125. 'partner_name': partner.name,
  126. 'partner_sku': stockrecord.partner_sku,
  127. 'stockrecord': stockrecord,
  128. # Product details
  129. 'product': product,
  130. 'title': product.get_title(),
  131. 'upc': product.upc,
  132. 'quantity': basket_line.quantity,
  133. # Price details
  134. 'line_price_excl_tax':
  135. basket_line.line_price_excl_tax_incl_discounts,
  136. 'line_price_incl_tax':
  137. basket_line.line_price_incl_tax_incl_discounts,
  138. 'line_price_before_discounts_excl_tax':
  139. basket_line.line_price_excl_tax,
  140. 'line_price_before_discounts_incl_tax':
  141. basket_line.line_price_incl_tax,
  142. # Reporting details
  143. 'unit_cost_price': stockrecord.cost_price,
  144. 'unit_price_incl_tax': basket_line.unit_price_incl_tax,
  145. 'unit_price_excl_tax': basket_line.unit_price_excl_tax,
  146. 'unit_retail_price': stockrecord.price_retail,
  147. # Shipping details
  148. 'est_dispatch_date':
  149. basket_line.purchase_info.availability.dispatch_date
  150. }
  151. extra_line_fields = extra_line_fields or {}
  152. if hasattr(settings, 'OSCAR_INITIAL_LINE_STATUS'):
  153. if not (extra_line_fields and 'status' in extra_line_fields):
  154. extra_line_fields['status'] = getattr(
  155. settings, 'OSCAR_INITIAL_LINE_STATUS')
  156. if extra_line_fields:
  157. line_data.update(extra_line_fields)
  158. order_line = Line._default_manager.create(**line_data)
  159. self.create_line_price_models(order, order_line, basket_line)
  160. self.create_line_attributes(order, order_line, basket_line)
  161. self.create_additional_line_models(order, order_line, basket_line)
  162. return order_line
  163. def update_stock_records(self, line):
  164. """
  165. Update any relevant stock records for this order line
  166. """
  167. if line.product.get_product_class().track_stock:
  168. line.stockrecord.allocate(line.quantity)
  169. def create_additional_line_models(self, order, order_line, basket_line):
  170. """
  171. Empty method designed to be overridden.
  172. Some applications require additional information about lines, this
  173. method provides a clean place to create additional models that
  174. relate to a given line.
  175. """
  176. pass
  177. def create_line_price_models(self, order, order_line, basket_line):
  178. """
  179. Creates the batch line price models
  180. """
  181. breakdown = basket_line.get_price_breakdown()
  182. for price_incl_tax, price_excl_tax, quantity in breakdown:
  183. order_line.prices.create(
  184. order=order,
  185. quantity=quantity,
  186. price_incl_tax=price_incl_tax,
  187. price_excl_tax=price_excl_tax)
  188. def create_line_attributes(self, order, order_line, basket_line):
  189. """
  190. Creates the batch line attributes.
  191. """
  192. for attr in basket_line.attributes.all():
  193. order_line.attributes.create(
  194. option=attr.option,
  195. type=attr.option.code,
  196. value=attr.value)
  197. def create_discount_model(self, order, discount):
  198. """
  199. Create an order discount model for each offer application attached to
  200. the basket.
  201. """
  202. order_discount = OrderDiscount(
  203. order=order,
  204. message=discount['message'] or '',
  205. offer_id=discount['offer'].id,
  206. frequency=discount['freq'],
  207. amount=discount['discount'])
  208. result = discount['result']
  209. if result.affects_shipping:
  210. order_discount.category = OrderDiscount.SHIPPING
  211. elif result.affects_post_order:
  212. order_discount.category = OrderDiscount.DEFERRED
  213. voucher = discount.get('voucher', None)
  214. if voucher:
  215. order_discount.voucher_id = voucher.id
  216. order_discount.voucher_code = voucher.code
  217. order_discount.save()
  218. def record_discount(self, discount):
  219. discount['offer'].record_usage(discount)
  220. if 'voucher' in discount and discount['voucher']:
  221. discount['voucher'].record_discount(discount)
  222. def record_voucher_usage(self, order, voucher, user):
  223. """
  224. Updates the models that care about this voucher.
  225. """
  226. voucher.record_usage(order, user)