mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 03:56:43 +00:00
Add option to add a single-quantity price-break when creating a new SupplierPart object
- Add unit testing!
This commit is contained in:
parent
534f43872f
commit
47cbf3071d
@ -14,7 +14,6 @@ import django.forms
|
|||||||
import djmoney.settings
|
import djmoney.settings
|
||||||
from djmoney.forms.fields import MoneyField
|
from djmoney.forms.fields import MoneyField
|
||||||
|
|
||||||
from common.models import InvenTreeSetting
|
|
||||||
import common.settings
|
import common.settings
|
||||||
|
|
||||||
from .models import Company
|
from .models import Company
|
||||||
|
@ -380,6 +380,25 @@ class SupplierPart(models.Model):
|
|||||||
def unit_pricing(self):
|
def unit_pricing(self):
|
||||||
return self.get_price(1)
|
return self.get_price(1)
|
||||||
|
|
||||||
|
def add_price_break(self, quantity, price):
|
||||||
|
"""
|
||||||
|
Create a new price break for this part
|
||||||
|
|
||||||
|
args:
|
||||||
|
quantity - Numerical quantity
|
||||||
|
price - Must be a Money object
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check if a price break at that quantity already exists...
|
||||||
|
if self.price_breaks.filter(quantity=quantity, part=self.pk).exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
SupplierPriceBreak.objects.create(
|
||||||
|
part=self,
|
||||||
|
quantity=quantity,
|
||||||
|
price=price
|
||||||
|
)
|
||||||
|
|
||||||
def get_price(self, quantity, moq=True, multiples=True, currency=None):
|
def get_price(self, quantity, moq=True, multiples=True, currency=None):
|
||||||
""" Calculate the supplier price based on quantity price breaks.
|
""" Calculate the supplier price based on quantity price breaks.
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
@ -11,7 +13,7 @@ from django.contrib.auth.models import Group
|
|||||||
from .models import SupplierPart
|
from .models import SupplierPart
|
||||||
|
|
||||||
|
|
||||||
class CompanyViewTest(TestCase):
|
class CompanyViewTestBase(TestCase):
|
||||||
|
|
||||||
fixtures = [
|
fixtures = [
|
||||||
'category',
|
'category',
|
||||||
@ -47,14 +49,105 @@ class CompanyViewTest(TestCase):
|
|||||||
|
|
||||||
self.client.login(username='username', password='password')
|
self.client.login(username='username', password='password')
|
||||||
|
|
||||||
def test_company_index(self):
|
|
||||||
""" Test the company index """
|
|
||||||
|
|
||||||
response = self.client.get(reverse('company-index'))
|
class SupplierPartViewTests(CompanyViewTestBase):
|
||||||
|
"""
|
||||||
|
Tests for the SupplierPart views.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def post(self, data, valid=None):
|
||||||
|
"""
|
||||||
|
POST against this form and return the response (as a JSON object)
|
||||||
|
"""
|
||||||
|
url = reverse('supplier-part-create')
|
||||||
|
|
||||||
|
response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
json_data = json.loads(response.content)
|
||||||
|
|
||||||
|
# If a particular status code is required
|
||||||
|
if valid is not None:
|
||||||
|
if valid:
|
||||||
|
self.assertEqual(json_data['form_valid'], True)
|
||||||
|
else:
|
||||||
|
self.assertEqual(json_data['form_valid'], False)
|
||||||
|
|
||||||
|
form_errors = json.loads(json_data['form_errors'])
|
||||||
|
|
||||||
|
return json_data, form_errors
|
||||||
|
|
||||||
|
def test_supplier_part_create(self):
|
||||||
|
"""
|
||||||
|
Test the SupplierPartCreate view.
|
||||||
|
|
||||||
|
This view allows some additional functionality,
|
||||||
|
specifically it allows the user to create a single-quantity price break
|
||||||
|
automatically, when saving the new SupplierPart model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
url = reverse('supplier-part-create')
|
||||||
|
|
||||||
|
# First check that we can GET the form
|
||||||
|
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# How many supplier parts are already in the database?
|
||||||
|
n = SupplierPart.objects.all().count()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'part': 1,
|
||||||
|
'supplier': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
# SKU is required! (form should fail)
|
||||||
|
(response, errors) = self.post(data, valid=False)
|
||||||
|
|
||||||
|
self.assertIsNotNone(errors.get('SKU', None))
|
||||||
|
|
||||||
|
data['SKU'] = 'TEST-ME-123'
|
||||||
|
|
||||||
|
(response, errors) = self.post(data, valid=True)
|
||||||
|
|
||||||
|
# Check that the SupplierPart was created!
|
||||||
|
self.assertEqual(n + 1, SupplierPart.objects.all().count())
|
||||||
|
|
||||||
|
# Check that it was created *without* a price-break
|
||||||
|
supplier_part = SupplierPart.objects.get(pk=response['pk'])
|
||||||
|
|
||||||
|
self.assertEqual(supplier_part.price_breaks.count(), 0)
|
||||||
|
|
||||||
|
# Duplicate SKU is prohibited
|
||||||
|
(response, errors) = self.post(data, valid=False)
|
||||||
|
|
||||||
|
self.assertIsNotNone(errors.get('__all__', None))
|
||||||
|
|
||||||
|
# Add with a different SKU, *and* a single-quantity price
|
||||||
|
data['SKU'] = 'TEST-ME-1234'
|
||||||
|
data['single_pricing_0'] = '123.4'
|
||||||
|
data['single_pricing_1'] = 'CAD'
|
||||||
|
|
||||||
|
(response, errors) = self.post(data, valid=True)
|
||||||
|
|
||||||
|
pk = response.get('pk')
|
||||||
|
|
||||||
|
# Check that *another* SupplierPart was created
|
||||||
|
self.assertEqual(n + 2, SupplierPart.objects.all().count())
|
||||||
|
|
||||||
|
supplier_part = SupplierPart.objects.get(pk=pk)
|
||||||
|
|
||||||
|
# Check that a price-break has been created!
|
||||||
|
self.assertEqual(supplier_part.price_breaks.count(), 1)
|
||||||
|
|
||||||
|
price_break = supplier_part.price_breaks.first()
|
||||||
|
|
||||||
|
self.assertEqual(price_break.quantity, 1)
|
||||||
|
|
||||||
def test_supplier_part_delete(self):
|
def test_supplier_part_delete(self):
|
||||||
""" Test the SupplierPartDelete view """
|
"""
|
||||||
|
Test the SupplierPartDelete view
|
||||||
|
"""
|
||||||
|
|
||||||
url = reverse('supplier-part-delete')
|
url = reverse('supplier-part-delete')
|
||||||
|
|
||||||
@ -80,3 +173,15 @@ class CompanyViewTest(TestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
self.assertEqual(n - 2, SupplierPart.objects.count())
|
self.assertEqual(n - 2, SupplierPart.objects.count())
|
||||||
|
|
||||||
|
|
||||||
|
class CompanyViewTest(CompanyViewTestBase):
|
||||||
|
"""
|
||||||
|
Tests for various 'Company' views
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_company_index(self):
|
||||||
|
""" Test the company index """
|
||||||
|
|
||||||
|
response = self.client.get(reverse('company-index'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
@ -271,6 +271,14 @@ class SupplierPartEdit(AjaxUpdateView):
|
|||||||
ajax_form_title = _('Edit Supplier Part')
|
ajax_form_title = _('Edit Supplier Part')
|
||||||
role_required = 'purchase_order.change'
|
role_required = 'purchase_order.change'
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
form = super().get_form()
|
||||||
|
|
||||||
|
# Hide the single-pricing field (only for creating a new SupplierPart!)
|
||||||
|
form.fields['single_pricing'].widget = HiddenInput()
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartCreate(AjaxCreateView):
|
class SupplierPartCreate(AjaxCreateView):
|
||||||
""" Create view for making new SupplierPart """
|
""" Create view for making new SupplierPart """
|
||||||
@ -282,6 +290,30 @@ class SupplierPartCreate(AjaxCreateView):
|
|||||||
context_object_name = 'part'
|
context_object_name = 'part'
|
||||||
role_required = 'purchase_order.add'
|
role_required = 'purchase_order.add'
|
||||||
|
|
||||||
|
def validate(self, part, form):
|
||||||
|
|
||||||
|
single_pricing = form.cleaned_data.get('single_pricing', None)
|
||||||
|
|
||||||
|
if single_pricing:
|
||||||
|
# TODO - What validation steps can be performed on the single_pricing field?
|
||||||
|
pass
|
||||||
|
|
||||||
|
def save(self, form):
|
||||||
|
"""
|
||||||
|
If single_pricing is defined, add a price break for quantity=1
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Save the supplier part object
|
||||||
|
supplier_part = super().save(form)
|
||||||
|
|
||||||
|
single_pricing = form.cleaned_data.get('single_pricing', None)
|
||||||
|
|
||||||
|
if single_pricing:
|
||||||
|
|
||||||
|
supplier_part.add_price_break(1, single_pricing)
|
||||||
|
|
||||||
|
return supplier_part
|
||||||
|
|
||||||
def get_form(self):
|
def get_form(self):
|
||||||
""" Create Form instance to create a new SupplierPart object.
|
""" Create Form instance to create a new SupplierPart object.
|
||||||
Hide some fields if they are not appropriate in context
|
Hide some fields if they are not appropriate in context
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
category: 8
|
category: 8
|
||||||
link: www.acme.com/parts/m2x4lphs
|
link: www.acme.com/parts/m2x4lphs
|
||||||
tree_id: 0
|
tree_id: 0
|
||||||
|
purchaseable: True
|
||||||
level: 0
|
level: 0
|
||||||
lft: 0
|
lft: 0
|
||||||
rght: 0
|
rght: 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user