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.

abstract_models.py 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. from django.db import models
  2. from django.contrib.auth.models import User
  3. from django.template.defaultfilters import slugify
  4. from django.utils.translation import ugettext_lazy as _
  5. class AbstractOrder(models.Model):
  6. """
  7. An order
  8. """
  9. number = models.PositiveIntegerField(_("Order number"), db_index=True)
  10. basket = models.ForeignKey('basket.Basket')
  11. # Orders can be anonymous so we don't always have a customer ID
  12. user = models.ForeignKey(User, related_name='orders', null=True, blank=True)
  13. # Billing address is not always required (eg paying by gift card)
  14. billing_address = models.ForeignKey('order.BillingAddress', null=True, blank=True)
  15. # Total price looks like it could be calculated by adding up the
  16. # prices of the associated batches, but in some circumstances extra
  17. # order-level charges are added and so we need to store it separately
  18. total_incl_tax = models.DecimalField(_("Order total (inc. tax)"), decimal_places=2, max_digits=12)
  19. total_excl_tax = models.DecimalField(_("Order total (excl. tax)"), decimal_places=2, max_digits=12)
  20. shipping_incl_tax = models.DecimalField(_("Shipping charge (inc. tax)"), decimal_places=2, max_digits=12, default=0)
  21. shipping_excl_tax = models.DecimalField(_("Shipping charge (excl. tax)"), decimal_places=2, max_digits=12, default=0)
  22. date_placed = models.DateTimeField(auto_now_add=True)
  23. def shipping_address(self):
  24. batches = self.batches.all()
  25. if len(batches) > 0:
  26. return batches[0].shipping_address
  27. return None
  28. @property
  29. def basket_total_incl_tax(self):
  30. return self.total_incl_tax - self.shipping_incl_tax
  31. @property
  32. def basket_total_excl_tax(self):
  33. return self.total_excl_tax - self.shipping_excl_tax
  34. class Meta:
  35. abstract = True
  36. def save(self, *args, **kwargs):
  37. if not self.number:
  38. self.number= self.basket.id
  39. super(AbstractOrder, self).save(*args, **kwargs)
  40. def __unicode__(self):
  41. return u"#%s (amount: %.2f)" % (self.number, self.total_incl_tax)
  42. class AbstractBatch(models.Model):
  43. """
  44. A batch of items from a single fulfillment partner
  45. This is a set of order lines which are fulfilled by a single partner
  46. """
  47. order = models.ForeignKey('order.Order', related_name="batches")
  48. partner = models.ForeignKey('stock.Partner')
  49. # Not all batches are actually shipped (such as downloads)
  50. shipping_address = models.ForeignKey('order.ShippingAddress', null=True, blank=True)
  51. shipping_method = models.CharField(_("Shipping method"), max_length=128, null=True, blank=True)
  52. def get_num_items(self):
  53. return len(self.lines.all())
  54. class Meta:
  55. abstract = True
  56. verbose_name_plural = _("Batches")
  57. def __unicode__(self):
  58. return "%s batch for order #%d" % (self.partner.name, self.order.number)
  59. class AbstractBatchLine(models.Model):
  60. """
  61. A line within a batch.
  62. Not using a line model as it's difficult to capture and payment
  63. information when it splits across a line.
  64. """
  65. order = models.ForeignKey('order.Order', related_name='lines')
  66. batch = models.ForeignKey('order.Batch', related_name='lines')
  67. product = models.ForeignKey('product.Item')
  68. quantity = models.PositiveIntegerField(default=1)
  69. # Price information (these fields are actually redundant as the information
  70. # can be calculated from the BatchLinePrice models
  71. line_price_incl_tax = models.DecimalField(decimal_places=2, max_digits=12)
  72. line_price_excl_tax = models.DecimalField(decimal_places=2, max_digits=12)
  73. # Partner information
  74. partner_reference = models.CharField(_("Partner reference"), max_length=128, blank=True, null=True,
  75. help_text=_("This is the item number that the partner uses within their system"))
  76. partner_notes = models.TextField(blank=True, null=True)
  77. @property
  78. def description(self):
  79. d = str(self.product)
  80. ops = []
  81. for attribute in self.attributes.all():
  82. ops.append("%s = '%s'" % (attribute.option.name, attribute.value))
  83. if ops:
  84. d = "%s (%s)" % (d, ", ".join(ops))
  85. return d
  86. class Meta:
  87. abstract = True
  88. verbose_name_plural = _("Batch lines")
  89. def __unicode__(self):
  90. return u"Product '%s', quantity '%s'" % (self.product, self.quantity)
  91. class AbstractBatchLinePrice(models.Model):
  92. """
  93. For tracking the prices paid for each unit within a line.
  94. This is necessary as offers can lead to units within a line
  95. having different prices. For example, one product may be sold at
  96. 50% off as it's part of an offer while the remainder are full price.
  97. """
  98. line = models.ForeignKey('order.BatchLine', related_name='prices')
  99. quantity = models.PositiveIntegerField(default=1)
  100. price_incl_tax = models.DecimalField(decimal_places=2, max_digits=12)
  101. price_excl_tax = models.DecimalField(decimal_places=2, max_digits=12)
  102. shipping_incl_tax = models.DecimalField(decimal_places=2, max_digits=12, default=0)
  103. shipping_excl_tax = models.DecimalField(decimal_places=2, max_digits=12, default=0)
  104. class Meta:
  105. abstract = True
  106. def __unicode__(self):
  107. return u"Line '%s' (quantity %d) price %s" % (self.line, self.quantity, self.price_incl_tax)
  108. class AbstractPaymentEvent(models.Model):
  109. """
  110. An event is something which happens to a line such as
  111. payment being taken for 2 items, or 1 item being dispatched.
  112. """
  113. line = models.ForeignKey('order.BatchLine', related_name='payment_events')
  114. quantity = models.PositiveIntegerField(default=1)
  115. event_type = models.ForeignKey('order.PaymentEventType')
  116. date = models.DateTimeField(auto_now_add=True)
  117. class Meta:
  118. abstract = True
  119. verbose_name_plural = _("Payment events")
  120. def __unicode__(self):
  121. return u"Order #%d, batch #%d, line %s: %d items %s" % (
  122. self.line.batch.order.number, self.line.batch.id, self.line.line_id, self.quantity, self.event_type)
  123. class AbstractPaymentEventType(models.Model):
  124. """
  125. Payment events are things like 'Paid', 'Failed', 'Refunded'
  126. """
  127. # Code is used in forms
  128. code = models.CharField(max_length=128)
  129. # Name is the friendly description of an event
  130. name = models.CharField(max_length=255)
  131. # The normal order in which these shipping events take place
  132. order = models.PositiveIntegerField(default=0)
  133. def save(self, *args, **kwargs):
  134. if not self.code:
  135. self.code = slugify(self.name)
  136. super(AbstractPaymentEventType, self).save(*args, **kwargs)
  137. class Meta:
  138. abstract = True
  139. verbose_name_plural = _("Payment event types")
  140. ordering = ('order',)
  141. def __unicode__(self):
  142. return self.name
  143. class AbstractShippingEvent(models.Model):
  144. """
  145. An event is something which happens to a line such as
  146. 1 item being dispatched.
  147. """
  148. line = models.ForeignKey('order.BatchLine', related_name='shipping_events')
  149. quantity = models.PositiveIntegerField(default=1)
  150. event_type = models.ForeignKey('order.ShippingEventType')
  151. notes = models.TextField(_("Event notes"), blank=True, null=True,
  152. help_text="This could be the dispatch reference, or a tracking number")
  153. date = models.DateTimeField(auto_now_add=True)
  154. class Meta:
  155. abstract = True
  156. verbose_name_plural = _("Shipping events")
  157. def __unicode__(self):
  158. return u"Order #%d, line %s: %d items set to '%s'" % (
  159. self.line.batch.order.number, self.line.batch.id, self.line.id, self.quantity, self.event_type)
  160. class AbstractShippingEventType(models.Model):
  161. """
  162. Shipping events are things like 'OrderPlaced', 'Acknowledged', 'Dispatched', 'Refunded'
  163. """
  164. # Code is used in forms
  165. code = models.CharField(max_length=128)
  166. # Name is the friendly description of an event
  167. name = models.CharField(max_length=255)
  168. # The normal order in which these shipping events take place
  169. order = models.PositiveIntegerField(default=0)
  170. def save(self, *args, **kwargs):
  171. if not self.code:
  172. self.code = slugify(self.name)
  173. super(AbstractShippingEventType, self).save(*args, **kwargs)
  174. class Meta:
  175. abstract = True
  176. verbose_name_plural = _("Shipping event types")
  177. ordering = ('order',)
  178. def __unicode__(self):
  179. return self.name
  180. class AbstractBatchLineAttribute(models.Model):
  181. """
  182. An attribute of a batch line.
  183. """
  184. line = models.ForeignKey('order.BatchLine', related_name='attributes')
  185. type = models.CharField(_("Type"), max_length=128)
  186. value = models.CharField(_("Value"), max_length=255)
  187. class Meta:
  188. abstract = True
  189. def __unicode__(self):
  190. return "%s = %s" % (self.type, self.value)