Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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