Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

utils.py 13KB

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