Repository: django-oscar/django-oscar

Issue:
StockRecord: rename price_excl_tax
The docstring for `price_excl_tax` already explains what it truly is: the base price for tax calculations. I've had two projects where we knew the tax-inclusive price, and only calculated the price excl tax. I'd argue that a stock record is merely a record of a base price, and only the pricing policy gives it any meaning in regards to tax, so the field name should reflect that. 
I realize that the field is probably used directly more often than it should (instead of through pricing strategies), so we need a good idea about being friendly as far as backwards-compatibility is concerned.



Discussion:
Yeah I agree! :+1:  All projects i've worked on communicate the tax-inclusive price and we need to calculate the excl price. This is required for prices like 19.99 where going from excl to incl results in 20.00 which isn't what the client wants :-)

If that's the case, we should probably ship some sample strategies to show how to work with this as well. Note to self: when doing this, also look at potential off-by-one bugs when calculating line and basket totals. 

This issue actually contained two issues, renaming a field and deleting two. As I found more candidates for deletion, I moved that part of this issue into https://github.com/django-oscar/django-oscar/issues/1968

@maikhoepfel @mvantellingen how about renaming into `base_price`? What about backward compatibility?

I think you should be careful with renaming this field. Having the price excluding tax and calculate the price including tax yourselves make more sense than the other way around (Especially if you have international shops).

However, I agree that the StockRecord should supply a base price and that the pricing policy gives a meaning to it, so it should not named like this.

I would say to deprecate the `price_excl_tax` field for the next release (1.2?) and rename it to a more sane name in the release after?

@sasha0 Backwards compatibility could be tricky. Initially, I thought we could define a `price_excl_tax` property, but that would only handle a few cases. Basically any code that references `price_excl_tax` using the ORM would break.

After research and observation, I came to conclusion it does make sense to rename `price_excl_tax` into `price` but as @john-parton mentioned before, there's no chance to make this change backwards compatible which drastically reduces motivation to apply this change.

I also totally agree with @maerteijn that we'd firstly deprecate this field and rename in the next major release (2.0).
nice to hear that, I had to disable the tax calculation because of it. The disadvantages I see is to deal with multiple countries with different taxes and in b2b pricing.
I may would propose the ability to limit stockrecords to countries.

Fix patch:
diff --git a/src/oscar/apps/dashboard/catalogue/forms.py b/src/oscar/apps/dashboard/catalogue/forms.py
--- a/src/oscar/apps/dashboard/catalogue/forms.py
+++ b/src/oscar/apps/dashboard/catalogue/forms.py
@@ -80,7 +80,7 @@ def __init__(self, product_class, user, *args, **kwargs):
                 if field_name in self.fields:
                     del self.fields[field_name]
         else:
-            for field_name in ['price_excl_tax', 'num_in_stock']:
+            for field_name in ['price', 'num_in_stock']:
                 if field_name in self.fields:
                     self.fields[field_name].required = True
 
@@ -88,7 +88,7 @@ class Meta:
         model = StockRecord
         fields = [
             'partner', 'partner_sku',
-            'price_currency', 'price_excl_tax', 'price_retail', 'cost_price',
+            'price_currency', 'price',
             'num_in_stock', 'low_stock_threshold',
         ]
 
diff --git a/src/oscar/apps/order/abstract_models.py b/src/oscar/apps/order/abstract_models.py
--- a/src/oscar/apps/order/abstract_models.py
+++ b/src/oscar/apps/order/abstract_models.py
@@ -543,10 +543,6 @@ class AbstractLine(models.Model):
         _("Price before discounts (excl. tax)"),
         decimal_places=2, max_digits=12)
 
-    # Deprecated - will be removed in Oscar 2.1
-    unit_cost_price = models.DecimalField(
-        _("Unit Cost Price"), decimal_places=2, max_digits=12, blank=True,
-        null=True)
     # Normal site price for item (without discounts)
     unit_price_incl_tax = models.DecimalField(
         _("Unit Price (inc. tax)"), decimal_places=2, max_digits=12,
@@ -554,19 +550,11 @@ class AbstractLine(models.Model):
     unit_price_excl_tax = models.DecimalField(
         _("Unit Price (excl. tax)"), decimal_places=2, max_digits=12,
         blank=True, null=True)
-    # Deprecated - will be removed in Oscar 2.1
-    unit_retail_price = models.DecimalField(
-        _("Unit Retail Price"), decimal_places=2, max_digits=12,
-        blank=True, null=True)
 
     # Partners often want to assign some status to each line to help with their
     # own business processes.
     status = models.CharField(_("Status"), max_length=255, blank=True)
 
-    # Deprecated - will be removed in Oscar 2.1
-    est_dispatch_date = models.DateField(
-        _("Estimated Dispatch Date"), blank=True, null=True)
-
     #: Order status pipeline.  This should be a dict where each (key, value)
     #: corresponds to a status and the possible statuses that can follow that
     #: one.
diff --git a/src/oscar/apps/order/migrations/0010_auto_20200724_0909.py b/src/oscar/apps/order/migrations/0010_auto_20200724_0909.py
new file mode 100644
--- /dev/null
+++ b/src/oscar/apps/order/migrations/0010_auto_20200724_0909.py
@@ -0,0 +1,25 @@
+# Generated by Django 2.2.10 on 2020-07-24 08:09
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('order', '0009_surcharge'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='line',
+            name='est_dispatch_date',
+        ),
+        migrations.RemoveField(
+            model_name='line',
+            name='unit_cost_price',
+        ),
+        migrations.RemoveField(
+            model_name='line',
+            name='unit_retail_price',
+        ),
+    ]
diff --git a/src/oscar/apps/order/utils.py b/src/oscar/apps/order/utils.py
--- a/src/oscar/apps/order/utils.py
+++ b/src/oscar/apps/order/utils.py
@@ -179,13 +179,8 @@ def create_line_models(self, order, basket_line, extra_line_fields=None):
             'line_price_before_discounts_incl_tax':
             basket_line.line_price_incl_tax,
             # Reporting details
-            'unit_cost_price': stockrecord.cost_price,
             'unit_price_incl_tax': basket_line.unit_price_incl_tax,
             'unit_price_excl_tax': basket_line.unit_price_excl_tax,
-            'unit_retail_price': stockrecord.price_retail,
-            # Shipping details
-            'est_dispatch_date':
-            basket_line.purchase_info.availability.dispatch_date
         }
         extra_line_fields = extra_line_fields or {}
         if hasattr(settings, 'OSCAR_INITIAL_LINE_STATUS'):
diff --git a/src/oscar/apps/partner/abstract_models.py b/src/oscar/apps/partner/abstract_models.py
--- a/src/oscar/apps/partner/abstract_models.py
+++ b/src/oscar/apps/partner/abstract_models.py
@@ -109,24 +109,12 @@ class AbstractStockRecord(models.Model):
     price_currency = models.CharField(
         _("Currency"), max_length=12, default=get_default_currency)
 
-    # This is the base price for calculations - tax should be applied by the
-    # appropriate method.  We don't store tax here as its calculation is highly
-    # domain-specific.  It is NULLable because some items don't have a fixed
-    # price but require a runtime calculation (possible from an external
-    # service). Current field name `price_excl_tax` is deprecated and will be
-    # renamed into `price` in Oscar 2.1.
-    price_excl_tax = models.DecimalField(
-        _("Price (excl. tax)"), decimal_places=2, max_digits=12,
-        blank=True, null=True)
-
-    # Deprecated - will be removed in Oscar 2.1
-    price_retail = models.DecimalField(
-        _("Price (retail)"), decimal_places=2, max_digits=12,
-        blank=True, null=True)
-
-    # Deprecated - will be removed in Oscar 2.1
-    cost_price = models.DecimalField(
-        _("Cost Price"), decimal_places=2, max_digits=12,
+    # This is the base price for calculations - whether this is inclusive or exclusive of
+    # tax depends on your implementation, as this is highly domain-specific.
+    # It is nullable because some items don't have a fixed
+    # price but require a runtime calculation (possibly from an external service).
+    price = models.DecimalField(
+        _("Price"), decimal_places=2, max_digits=12,
         blank=True, null=True)
 
     #: Number of items in stock
diff --git a/src/oscar/apps/partner/admin.py b/src/oscar/apps/partner/admin.py
--- a/src/oscar/apps/partner/admin.py
+++ b/src/oscar/apps/partner/admin.py
@@ -7,7 +7,7 @@
 
 
 class StockRecordAdmin(admin.ModelAdmin):
-    list_display = ('product', 'partner', 'partner_sku', 'price_excl_tax', 'num_in_stock')
+    list_display = ('product', 'partner', 'partner_sku', 'price', 'num_in_stock')
     list_filter = ('partner',)
 
 
diff --git a/src/oscar/apps/partner/importers.py b/src/oscar/apps/partner/importers.py
--- a/src/oscar/apps/partner/importers.py
+++ b/src/oscar/apps/partner/importers.py
@@ -101,8 +101,7 @@ def _create_item(self, product_class, category_str, upc, title,
 
         return item
 
-    def _create_stockrecord(self, item, partner_name, partner_sku,
-                            price_excl_tax, num_in_stock, stats):
+    def _create_stockrecord(self, item, partner_name, partner_sku, price, num_in_stock, stats):
         # Create partner and stock record
         partner, _ = Partner.objects.get_or_create(
             name=partner_name)
@@ -114,7 +113,7 @@ def _create_stockrecord(self, item, partner_name, partner_sku,
         stock.product = item
         stock.partner = partner
         stock.partner_sku = partner_sku
-        stock.price_excl_tax = D(price_excl_tax)
+        stock.price = D(price)
         stock.num_in_stock = num_in_stock
         stock.save()
 
diff --git a/src/oscar/apps/partner/migrations/0006_auto_20200724_0909.py b/src/oscar/apps/partner/migrations/0006_auto_20200724_0909.py
new file mode 100644
--- /dev/null
+++ b/src/oscar/apps/partner/migrations/0006_auto_20200724_0909.py
@@ -0,0 +1,31 @@
+# Generated by Django 2.2.10 on 2020-07-24 08:09
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('partner', '0005_auto_20181115_1953'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='stockrecord',
+            name='cost_price',
+        ),
+        migrations.RemoveField(
+            model_name='stockrecord',
+            name='price_retail',
+        ),
+        migrations.AlterField(
+            model_name='stockrecord',
+            name='price_excl_tax',
+            field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, verbose_name='Price'),
+        ),
+        migrations.RenameField(
+            model_name='stockrecord',
+            old_name='price_excl_tax',
+            new_name='price',
+        ),
+    ]
diff --git a/src/oscar/apps/partner/strategy.py b/src/oscar/apps/partner/strategy.py
--- a/src/oscar/apps/partner/strategy.py
+++ b/src/oscar/apps/partner/strategy.py
@@ -229,17 +229,17 @@ def parent_availability_policy(self, product, children_stock):
 class NoTax(object):
     """
     Pricing policy mixin for use with the ``Structured`` base strategy.
-    This mixin specifies zero tax and uses the ``price_excl_tax`` from the
+    This mixin specifies zero tax and uses the ``price`` from the
     stockrecord.
     """
 
     def pricing_policy(self, product, stockrecord):
         # Check stockrecord has the appropriate data
-        if not stockrecord or stockrecord.price_excl_tax is None:
+        if not stockrecord or stockrecord.price is None:
             return UnavailablePrice()
         return FixedPrice(
             currency=stockrecord.price_currency,
-            excl_tax=stockrecord.price_excl_tax,
+            excl_tax=stockrecord.price,
             tax=D('0.00'))
 
     def parent_pricing_policy(self, product, children_stock):
@@ -250,7 +250,7 @@ def parent_pricing_policy(self, product, children_stock):
         stockrecord = stockrecords[0]
         return FixedPrice(
             currency=stockrecord.price_currency,
-            excl_tax=stockrecord.price_excl_tax,
+            excl_tax=stockrecord.price,
             tax=D('0.00'))
 
 
@@ -265,14 +265,14 @@ class FixedRateTax(object):
     exponent = D('0.01')  # Default to two decimal places
 
     def pricing_policy(self, product, stockrecord):
-        if not stockrecord or stockrecord.price_excl_tax is None:
+        if not stockrecord or stockrecord.price is None:
             return UnavailablePrice()
         rate = self.get_rate(product, stockrecord)
         exponent = self.get_exponent(stockrecord)
-        tax = (stockrecord.price_excl_tax * rate).quantize(exponent)
+        tax = (stockrecord.price * rate).quantize(exponent)
         return TaxInclusiveFixedPrice(
             currency=stockrecord.price_currency,
-            excl_tax=stockrecord.price_excl_tax,
+            excl_tax=stockrecord.price,
             tax=tax)
 
     def parent_pricing_policy(self, product, children_stock):
@@ -284,11 +284,11 @@ def parent_pricing_policy(self, product, children_stock):
         stockrecord = stockrecords[0]
         rate = self.get_rate(product, stockrecord)
         exponent = self.get_exponent(stockrecord)
-        tax = (stockrecord.price_excl_tax * rate).quantize(exponent)
+        tax = (stockrecord.price * rate).quantize(exponent)
 
         return FixedPrice(
             currency=stockrecord.price_currency,
-            excl_tax=stockrecord.price_excl_tax,
+            excl_tax=stockrecord.price,
             tax=tax)
 
     def get_rate(self, product, stockrecord):
@@ -318,11 +318,11 @@ class DeferredTax(object):
     """
 
     def pricing_policy(self, product, stockrecord):
-        if not stockrecord or stockrecord.price_excl_tax is None:
+        if not stockrecord or stockrecord.price is None:
             return UnavailablePrice()
         return FixedPrice(
             currency=stockrecord.price_currency,
-            excl_tax=stockrecord.price_excl_tax)
+            excl_tax=stockrecord.price)
 
     def parent_pricing_policy(self, product, children_stock):
         stockrecords = [x[1] for x in children_stock if x[1] is not None]
@@ -334,7 +334,7 @@ def parent_pricing_policy(self, product, children_stock):
 
         return FixedPrice(
             currency=stockrecord.price_currency,
-            excl_tax=stockrecord.price_excl_tax)
+            excl_tax=stockrecord.price)
 
 
 # Example strategy composed of above mixins.  For real projects, it's likely


Test patch:
diff --git a/src/oscar/test/basket.py b/src/oscar/test/basket.py
--- a/src/oscar/test/basket.py
+++ b/src/oscar/test/basket.py
@@ -23,7 +23,7 @@ def add_product(basket, price=None, quantity=1, product=None):
         record = product.stockrecords.all()[0]
     else:
         record = factories.create_stockrecord(
-            product=product, price_excl_tax=price,
+            product=product, price=price,
             num_in_stock=quantity + 1)
     basket.add_product(record.product, quantity)
 
diff --git a/src/oscar/test/factories/__init__.py b/src/oscar/test/factories/__init__.py
--- a/src/oscar/test/factories/__init__.py
+++ b/src/oscar/test/factories/__init__.py
@@ -49,7 +49,7 @@
 ConditionalOffer = get_model('offer', 'ConditionalOffer')
 
 
-def create_stockrecord(product=None, price_excl_tax=None, partner_sku=None,
+def create_stockrecord(product=None, price=None, partner_sku=None,
                        num_in_stock=None, partner_name=None,
                        currency=settings.OSCAR_DEFAULT_CURRENCY,
                        partner_users=None):
@@ -59,21 +59,21 @@ def create_stockrecord(product=None, price_excl_tax=None, partner_sku=None,
     if partner_users:
         for user in partner_users:
             partner.users.add(user)
-    if price_excl_tax is None:
-        price_excl_tax = D('9.99')
+    if price is None:
+        price = D('9.99')
     if partner_sku is None:
         partner_sku = 'sku_%d_%d' % (product.id, random.randint(0, 10000))
     return product.stockrecords.create(
         partner=partner, partner_sku=partner_sku,
         price_currency=currency,
-        price_excl_tax=price_excl_tax, num_in_stock=num_in_stock)
+        price=price, num_in_stock=num_in_stock)
 
 
 def create_purchase_info(record):
     return PurchaseInfo(
         price=FixedPrice(
             record.price_currency,
-            record.price_excl_tax,
+            record.price,
             D('0.00')  # Default to no tax
         ),
         availability=StockRequired(
@@ -109,7 +109,7 @@ def create_product(upc=None, title="Dùｍϻϒ title",
         price, partner_sku, partner_name, num_in_stock, partner_users]
     if any([field is not None for field in stockrecord_fields]):
         create_stockrecord(
-            product, price_excl_tax=price, num_in_stock=num_in_stock,
+            product, price=price, num_in_stock=num_in_stock,
             partner_users=partner_users, partner_sku=partner_sku,
             partner_name=partner_name)
     return product
@@ -157,8 +157,7 @@ def create_order(number=None, basket=None, user=None, shipping_address=None,
         basket = Basket.objects.create()
         basket.strategy = Default()
         product = create_product()
-        create_stockrecord(
-            product, num_in_stock=10, price_excl_tax=D('10.00'))
+        create_stockrecord(product, num_in_stock=10, price=D('10.00'))
         basket.add_product(product)
     if not basket.id:
         basket.save()
diff --git a/src/oscar/test/factories/order.py b/src/oscar/test/factories/order.py
--- a/src/oscar/test/factories/order.py
+++ b/src/oscar/test/factories/order.py
@@ -100,24 +100,16 @@ class OrderLineFactory(factory.DjangoModelFactory):
         lambda l: l.product.stockrecords.first())
     quantity = 1
 
-    line_price_incl_tax = factory.LazyAttribute(
-        lambda obj: tax_add(obj.stockrecord.price_excl_tax) * obj.quantity)
-    line_price_excl_tax = factory.LazyAttribute(
-        lambda obj: obj.stockrecord.price_excl_tax * obj.quantity)
+    line_price_incl_tax = factory.LazyAttribute(lambda obj: tax_add(obj.stockrecord.price) * obj.quantity)
+    line_price_excl_tax = factory.LazyAttribute(lambda obj: obj.stockrecord.price * obj.quantity)
 
     line_price_before_discounts_incl_tax = (
         factory.SelfAttribute('.line_price_incl_tax'))
     line_price_before_discounts_excl_tax = (
         factory.SelfAttribute('.line_price_excl_tax'))
 
-    unit_price_incl_tax = factory.LazyAttribute(
-        lambda obj: tax_add(obj.stockrecord.price_excl_tax))
-    unit_cost_price = factory.LazyAttribute(
-        lambda obj: obj.stockrecord.cost_price)
-    unit_price_excl_tax = factory.LazyAttribute(
-        lambda obj: obj.stockrecord.price_excl_tax)
-    unit_retail_price = factory.LazyAttribute(
-        lambda obj: obj.stockrecord.price_retail)
+    unit_price_incl_tax = factory.LazyAttribute(lambda obj: tax_add(obj.stockrecord.price))
+    unit_price_excl_tax = factory.LazyAttribute(lambda obj: obj.stockrecord.price)
 
     class Meta:
         model = get_model('order', 'Line')
diff --git a/src/oscar/test/factories/partner.py b/src/oscar/test/factories/partner.py
--- a/src/oscar/test/factories/partner.py
+++ b/src/oscar/test/factories/partner.py
@@ -29,7 +29,7 @@ class StockRecordFactory(factory.DjangoModelFactory):
     partner = factory.SubFactory(PartnerFactory)
     partner_sku = factory.Sequence(lambda n: 'unit%d' % n)
     price_currency = "GBP"
-    price_excl_tax = D('9.99')
+    price = D('9.99')
     num_in_stock = 100
 
     class Meta:
diff --git a/tests/_site/apps/partner/migrations/0007_auto_20200724_0909.py b/tests/_site/apps/partner/migrations/0007_auto_20200724_0909.py
new file mode 100644
--- /dev/null
+++ b/tests/_site/apps/partner/migrations/0007_auto_20200724_0909.py
@@ -0,0 +1,31 @@
+# Generated by Django 2.2.10 on 2020-07-24 08:09
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('partner', '0006_auto_20190816_0910'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='stockrecord',
+            name='cost_price',
+        ),
+        migrations.RemoveField(
+            model_name='stockrecord',
+            name='price_retail',
+        ),
+        migrations.AlterField(
+            model_name='stockrecord',
+            name='price_excl_tax',
+            field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, verbose_name='Price'),
+        ),
+        migrations.RenameField(
+            model_name='stockrecord',
+            old_name='price_excl_tax',
+            new_name='price',
+        ),
+    ]
diff --git a/tests/functional/checkout/__init__.py b/tests/functional/checkout/__init__.py
--- a/tests/functional/checkout/__init__.py
+++ b/tests/functional/checkout/__init__.py
@@ -17,14 +17,14 @@ def create_digital_product(self):
             requires_shipping=False, track_stock=False)
         product = factories.ProductFactory(product_class=product_class)
         factories.StockRecordFactory(
-            num_in_stock=None, price_excl_tax=D('12.00'), product=product)
+            num_in_stock=None, price=D('12.00'), product=product)
         return product
 
     def add_product_to_basket(self, product=None):
         if product is None:
             product = factories.ProductFactory()
             factories.StockRecordFactory(
-                num_in_stock=10, price_excl_tax=D('12.00'), product=product)
+                num_in_stock=10, price=D('12.00'), product=product)
         detail_page = self.get(product.get_absolute_url())
         form = detail_page.forms['add_to_basket_form']
         form.submit()
diff --git a/tests/functional/dashboard/test_catalogue.py b/tests/functional/dashboard/test_catalogue.py
--- a/tests/functional/dashboard/test_catalogue.py
+++ b/tests/functional/dashboard/test_catalogue.py
@@ -127,7 +127,7 @@ def test_can_create_and_continue_editing_a_product(self):
         form['stockrecords-0-partner'] = self.partner.id
         form['stockrecords-0-partner_sku'] = '14'
         form['stockrecords-0-num_in_stock'] = '555'
-        form['stockrecords-0-price_excl_tax'] = '13.99'
+        form['stockrecords-0-price'] = '13.99'
         page = form.submit(name='action', value='continue')
 
         self.assertEqual(Product.objects.count(), 1)
diff --git a/tests/functional/test_basket.py b/tests/functional/test_basket.py
--- a/tests/functional/test_basket.py
+++ b/tests/functional/test_basket.py
@@ -64,7 +64,7 @@ def test_price_is_recorded(self):
         basket = Basket.objects.get(id=basket_id)
         line = basket.lines.get(product=self.product)
         stockrecord = self.product.stockrecords.all()[0]
-        self.assertEqual(stockrecord.price_excl_tax, line.price_excl_tax)
+        self.assertEqual(stockrecord.price, line.price_excl_tax)
 
 
 class BasketSummaryViewTests(WebTestCase):
diff --git a/tests/integration/basket/test_models.py b/tests/integration/basket/test_models.py
--- a/tests/integration/basket/test_models.py
+++ b/tests/integration/basket/test_models.py
@@ -84,7 +84,7 @@ def test_line_tax_for_zero_tax_strategies(self):
         product = factories.create_product()
         # Tax for the default strategy will be 0
         factories.create_stockrecord(
-            product, price_excl_tax=D('75.00'), num_in_stock=10)
+            product, price=D('75.00'), num_in_stock=10)
         basket.add(product, 1)
 
         self.assertEqual(basket.lines.first().line_tax, D('0'))
@@ -95,7 +95,7 @@ class UnknownTaxStrategy(strategy.Default):
             """ A test strategy where the tax is not known """
 
             def pricing_policy(self, product, stockrecord):
-                return prices.FixedPrice('GBP', stockrecord.price_excl_tax, tax=None)
+                return prices.FixedPrice('GBP', stockrecord.price, tax=None)
 
         basket = Basket()
         basket.strategy = UnknownTaxStrategy()
@@ -114,7 +114,7 @@ def setUp(self):
         self.product = factories.create_product()
         self.record = factories.create_stockrecord(
             currency='GBP',
-            product=self.product, price_excl_tax=D('10.00'))
+            product=self.product, price=D('10.00'))
         self.purchase_info = factories.create_purchase_info(self.record)
         self.basket.add(self.product)
 
@@ -136,7 +136,7 @@ def test_adding_negative_quantity(self):
     def test_means_another_currency_product_cannot_be_added(self):
         product = factories.create_product()
         factories.create_stockrecord(
-            currency='USD', product=product, price_excl_tax=D('20.00'))
+            currency='USD', product=product, price=D('20.00'))
         with self.assertRaises(ValueError):
             self.basket.add(product)
 
@@ -153,7 +153,7 @@ def setUp(self):
         self.basket.strategy = strategy.Default()
         self.product = factories.create_product()
         self.record = factories.create_stockrecord(
-            self.product, price_excl_tax=D('10.00'))
+            self.product, price=D('10.00'))
         self.purchase_info = factories.create_purchase_info(self.record)
         self.basket.add(self.product, 10)
 
@@ -171,21 +171,21 @@ def test_returns_correct_line_quantity_for_existing_product_and_stockrecord(self
 
     def test_returns_zero_line_quantity_for_alternative_stockrecord(self):
         record = factories.create_stockrecord(
-            self.product, price_excl_tax=D('5.00'))
+            self.product, price=D('5.00'))
         self.assertEqual(0, self.basket.line_quantity(
             self.product, record))
 
     def test_returns_zero_line_quantity_for_missing_product_and_stockrecord(self):
         product = factories.create_product()
         record = factories.create_stockrecord(
-            product, price_excl_tax=D('5.00'))
+            product, price=D('5.00'))
         self.assertEqual(0, self.basket.line_quantity(
             product, record))
 
     def test_returns_correct_quantity_for_existing_product_and_stockrecord_and_options(self):
         product = factories.create_product()
         record = factories.create_stockrecord(
-            product, price_excl_tax=D('5.00'))
+            product, price=D('5.00'))
         option = Option.objects.create(name="Message")
         options = [{"option": option, "value": "2"}]
 
@@ -198,7 +198,7 @@ def test_returns_correct_quantity_for_existing_product_and_stockrecord_and_optio
     def test_total_sums_product_totals(self):
         product = factories.create_product()
         factories.create_stockrecord(
-            product, price_excl_tax=D('5.00'))
+            product, price=D('5.00'))
         self.basket.add(product, 1)
         self.assertEqual(self.basket.total_excl_tax, 105)
 
@@ -208,7 +208,7 @@ def test_totals_for_free_products(self):
         # Add a zero-priced product to the basket
         product = factories.create_product()
         factories.create_stockrecord(
-            product, price_excl_tax=D('0.00'), num_in_stock=10)
+            product, price=D('0.00'), num_in_stock=10)
         basket.add(product, 1)
 
         self.assertEqual(basket.lines.count(), 1)
@@ -218,7 +218,7 @@ def test_totals_for_free_products(self):
     def test_basket_prices_calculation_for_unavailable_pricing(self):
         new_product = factories.create_product()
         factories.create_stockrecord(
-            new_product, price_excl_tax=D('5.00'))
+            new_product, price=D('5.00'))
         self.basket.add(new_product, 1)
 
         class UnavailableProductStrategy(strategy.Default):
@@ -288,7 +288,7 @@ class TestMergingTwoBaskets(TestCase):
     def setUp(self):
         self.product = factories.create_product()
         self.record = factories.create_stockrecord(
-            self.product, price_excl_tax=D('10.00'))
+            self.product, price=D('10.00'))
         self.purchase_info = factories.create_purchase_info(self.record)
 
         self.main_basket = Basket()
diff --git a/tests/integration/partner/test_availability_mixin.py b/tests/integration/partner/test_availability_mixin.py
--- a/tests/integration/partner/test_availability_mixin.py
+++ b/tests/integration/partner/test_availability_mixin.py
@@ -12,7 +12,7 @@ def setUp(self):
         self.mixin = strategy.StockRequired()
         self.product = mock.Mock()
         self.stockrecord = mock.Mock()
-        self.stockrecord.price_excl_tax = D('12.00')
+        self.stockrecord.price = D('12.00')
 
     def test_returns_unavailable_without_stockrecord(self):
         policy = self.mixin.availability_policy(
diff --git a/tests/integration/partner/test_import.py b/tests/integration/partner/test_import.py
--- a/tests/integration/partner/test_import.py
+++ b/tests/integration/partner/test_import.py
@@ -96,7 +96,7 @@ def test_null_fields_are_skipped(self):
 
     def test_price_is_imported(self):
         stockrecord = self.product.stockrecords.all()[0]
-        self.assertEqual(D('10.32'), stockrecord.price_excl_tax)
+        self.assertEqual(D('10.32'), stockrecord.price)
 
     def test_num_in_stock_is_imported(self):
         stockrecord = self.product.stockrecords.all()[0]
diff --git a/tests/integration/partner/test_models.py b/tests/integration/partner/test_models.py
--- a/tests/integration/partner/test_models.py
+++ b/tests/integration/partner/test_models.py
@@ -15,10 +15,10 @@ class TestStockRecord(TestCase):
     def setUp(self):
         self.product = factories.create_product()
         self.stockrecord = factories.create_stockrecord(
-            self.product, price_excl_tax=D('10.00'), num_in_stock=10)
+            self.product, price=D('10.00'), num_in_stock=10)
 
     def test_get_price_excl_tax_returns_correct_value(self):
-        self.assertEqual(D('10.00'), self.stockrecord.price_excl_tax)
+        self.assertEqual(D('10.00'), self.stockrecord.price)
 
     def test_net_stock_level_with_no_allocation(self):
         self.assertEqual(10, self.stockrecord.net_stock_level)
@@ -64,7 +64,7 @@ def setUp(self):
     def test_allocate_does_nothing(self):
         product = factories.ProductFactory(product_class=self.product_class)
         stockrecord = factories.create_stockrecord(
-            product, price_excl_tax=D('10.00'), num_in_stock=10)
+            product, price=D('10.00'), num_in_stock=10)
 
         self.assertFalse(stockrecord.can_track_allocations)
         stockrecord.allocate(5)
@@ -76,7 +76,7 @@ def test_allocate_does_nothing_for_child_product(self):
         child_product = factories.ProductFactory(
             parent=parent_product, product_class=None, structure='child')
         stockrecord = factories.create_stockrecord(
-            child_product, price_excl_tax=D('10.00'), num_in_stock=10)
+            child_product, price=D('10.00'), num_in_stock=10)
 
         self.assertFalse(stockrecord.can_track_allocations)
         stockrecord.allocate(5)
diff --git a/tests/integration/partner/test_strategy.py b/tests/integration/partner/test_strategy.py
--- a/tests/integration/partner/test_strategy.py
+++ b/tests/integration/partner/test_strategy.py
@@ -53,7 +53,7 @@ def test_availability_does_not_require_price(self):
         # The availability policy should be independent of price.
         product_class = factories.ProductClassFactory(track_stock=False)
         product = factories.ProductFactory(product_class=product_class, stockrecords=[])
-        factories.StockRecordFactory(price_excl_tax=None, product=product)
+        factories.StockRecordFactory(price=None, product=product)
         info = self.strategy.fetch_for_product(product)
         self.assertTrue(info.availability.is_available_to_buy)
 
@@ -123,7 +123,7 @@ class TestFixedRateTax(TestCase):
 
     def test_pricing_policy_unavailable_if_no_price_excl_tax(self):
         product = factories.ProductFactory(stockrecords=[])
-        factories.StockRecordFactory(price_excl_tax=None, product=product)
+        factories.StockRecordFactory(price=None, product=product)
         info = strategy.UK().fetch_for_product(product)
         self.assertFalse(info.price.exists)
 
@@ -132,6 +132,6 @@ class TestDeferredTax(TestCase):
 
     def test_pricing_policy_unavailable_if_no_price_excl_tax(self):
         product = factories.ProductFactory(stockrecords=[])
-        factories.StockRecordFactory(price_excl_tax=None, product=product)
+        factories.StockRecordFactory(price=None, product=product)
         info = strategy.US().fetch_for_product(product)
         self.assertFalse(info.price.exists)
diff --git a/tests/integration/partner/test_tax_mixin.py b/tests/integration/partner/test_tax_mixin.py
--- a/tests/integration/partner/test_tax_mixin.py
+++ b/tests/integration/partner/test_tax_mixin.py
@@ -12,7 +12,7 @@ def setUp(self):
         self.mixin = strategy.NoTax()
         self.product = mock.Mock()
         self.stockrecord = mock.Mock()
-        self.stockrecord.price_excl_tax = D('12.00')
+        self.stockrecord.price = D('12.00')
 
     def test_returns_no_prices_without_stockrecord(self):
         policy = self.mixin.pricing_policy(
@@ -37,7 +37,7 @@ def setUp(self):
         self.mixin.rate = D('0.10')
         self.product = mock.Mock()
         self.stockrecord = mock.Mock()
-        self.stockrecord.price_excl_tax = D('12.00')
+        self.stockrecord.price = D('12.00')
 
     def test_returns_no_prices_without_stockrecord(self):
         policy = self.mixin.pricing_policy(
@@ -47,7 +47,7 @@ def test_returns_no_prices_without_stockrecord(self):
     def test_returns_correct_tax(self):
         policy = self.mixin.pricing_policy(
             self.product, self.stockrecord)
-        expected_tax = self.stockrecord.price_excl_tax * self.mixin.get_rate(
+        expected_tax = self.stockrecord.price * self.mixin.get_rate(
             self.product, self.stockrecord)
         self.assertEqual(expected_tax, policy.tax)
 
diff --git a/tests/integration/shipping/test_model_method.py b/tests/integration/shipping/test_model_method.py
--- a/tests/integration/shipping/test_model_method.py
+++ b/tests/integration/shipping/test_model_method.py
@@ -69,7 +69,7 @@ def test_free_shipping_with_empty_basket(self):
         self.assertEqual(D('0.00'), charge.incl_tax)
 
     def test_free_shipping_with_nonempty_basket(self):
-        record = factories.create_stockrecord(price_excl_tax=D('5.00'))
+        record = factories.create_stockrecord(price=D('5.00'))
         self.basket.add_product(record.product)
         charge = self.method.calculate(self.basket)
         self.assertEqual(D('0.00'), charge.incl_tax)
@@ -83,7 +83,7 @@ def setUp(self):
         self.basket = factories.create_basket(empty=True)
 
     def test_basket_below_threshold(self):
-        record = factories.create_stockrecord(price_excl_tax=D('5.00'))
+        record = factories.create_stockrecord(price=D('5.00'))
         self.basket.add_product(record.product)
 
         charge = self.method.calculate(self.basket)
@@ -91,7 +91,7 @@ def test_basket_below_threshold(self):
         self.assertEqual(D('10.00'), charge.incl_tax)
 
     def test_basket_on_threshold(self):
-        record = factories.create_stockrecord(price_excl_tax=D('5.00'))
+        record = factories.create_stockrecord(price=D('5.00'))
         self.basket.add_product(record.product, quantity=4)
 
         charge = self.method.calculate(self.basket)
@@ -99,7 +99,7 @@ def test_basket_on_threshold(self):
         self.assertEqual(D('0.00'), charge.incl_tax)
 
     def test_basket_above_threshold(self):
-        record = factories.create_stockrecord(price_excl_tax=D('5.00'))
+        record = factories.create_stockrecord(price=D('5.00'))
         self.basket.add_product(record.product, quantity=8)
 
         charge = self.method.calculate(self.basket)
diff --git a/tests/unit/fixtures/catalogue.json b/tests/unit/fixtures/catalogue.json
--- a/tests/unit/fixtures/catalogue.json
+++ b/tests/unit/fixtures/catalogue.json
@@ -159,9 +159,7 @@
         "partner": 1,
         "partner_sku": "henk",
         "price_currency": "EUR",
-        "price_excl_tax": "400.00",
-        "price_retail": "500.00",
-        "cost_price": "300.00",
+        "price": "400.00",
         "num_in_stock": 23,
         "num_allocated": null,
         "low_stock_threshold": null,
@@ -177,9 +175,7 @@
         "partner": 1,
         "partner_sku": "asdasd",
         "price_currency": "EUR",
-        "price_excl_tax": "23.00",
-        "price_retail": "0.05",
-        "cost_price": null,
+        "price": "23.00",
         "num_in_stock": null,
         "num_allocated": null,
         "low_stock_threshold": null,
diff --git a/tests/unit/fixtures/productattributes.json b/tests/unit/fixtures/productattributes.json
--- a/tests/unit/fixtures/productattributes.json
+++ b/tests/unit/fixtures/productattributes.json
@@ -803,9 +803,7 @@
         "partner": 1,
         "partner_sku": "henk",
         "price_currency": "EUR",
-        "price_excl_tax": "400.00",
-        "price_retail": "500.00",
-        "cost_price": "300.00",
+        "price": "400.00",
         "num_in_stock": 23,
         "num_allocated": null,
         "low_stock_threshold": null,
@@ -821,9 +819,7 @@
         "partner": 1,
         "partner_sku": "asdasd",
         "price_currency": "EUR",
-        "price_excl_tax": "23.00",
-        "price_retail": "0.05",
-        "cost_price": null,
+        "price": "23.00",
         "num_in_stock": null,
         "num_allocated": null,
         "low_stock_threshold": null,
@@ -839,9 +835,7 @@
         "partner": 1,
         "partner_sku": "err56",
         "price_currency": "EUR",
-        "price_excl_tax": "234.00",
-        "price_retail": "123.00",
-        "cost_price": null,
+        "price": "234.00",
         "num_in_stock": 345,
         "num_allocated": null,
         "low_stock_threshold": 12,
@@ -857,9 +851,7 @@
         "partner": 1,
         "partner_sku": "23478",
         "price_currency": "EUR",
-        "price_excl_tax": "12.00",
-        "price_retail": "11.00",
-        "cost_price": null,
+        "price": "12.00",
         "num_in_stock": 34,
         "num_allocated": null,
         "low_stock_threshold": 8,