diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index 3945b56d7c..637dbbf2cf 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -14,7 +14,7 @@ from .models import BomItem from .models import PartParameterTemplate, PartParameter from .models import PartCategoryParameterTemplate from .models import PartTestTemplate -from .models import PartSellPriceBreak +from .models import PartSellPriceBreak, PartInternalPriceBreak from stock.models import StockLocation from company.models import SupplierPart @@ -286,6 +286,14 @@ class PartSellPriceBreakAdmin(admin.ModelAdmin): list_display = ('part', 'quantity', 'price',) +class PartInternalPriceBreakAdmin(admin.ModelAdmin): + + class Meta: + model = PartInternalPriceBreak + + list_display = ('part', 'quantity', 'price',) + + admin.site.register(Part, PartAdmin) admin.site.register(PartCategory, PartCategoryAdmin) admin.site.register(PartRelated, PartRelatedAdmin) @@ -297,3 +305,4 @@ admin.site.register(PartParameter, ParameterAdmin) admin.site.register(PartCategoryParameterTemplate, PartCategoryParameterAdmin) admin.site.register(PartTestTemplate, PartTestTemplateAdmin) admin.site.register(PartSellPriceBreak, PartSellPriceBreakAdmin) +admin.site.register(PartInternalPriceBreak, PartInternalPriceBreakAdmin) diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 537b0f9e40..8883699f9a 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -25,7 +25,7 @@ from django.urls import reverse from .models import Part, PartCategory, BomItem from .models import PartParameter, PartParameterTemplate from .models import PartAttachment, PartTestTemplate -from .models import PartSellPriceBreak +from .models import PartSellPriceBreak, PartInternalPriceBreak from .models import PartCategoryParameterTemplate from common.models import InvenTreeSetting @@ -194,6 +194,23 @@ class PartSalePriceList(generics.ListCreateAPIView): ] +class PartInternalPriceList(generics.ListCreateAPIView): + """ + API endpoint for list view of PartInternalPriceBreak model + """ + + queryset = PartInternalPriceBreak.objects.all() + serializer_class = part_serializers.PartInternalPriceSerializer + + filter_backends = [ + DjangoFilterBackend + ] + + filter_fields = [ + 'part', + ] + + class PartAttachmentList(generics.ListCreateAPIView, AttachmentMixin): """ API endpoint for listing (and creating) a PartAttachment (file upload). @@ -1017,6 +1034,11 @@ part_api_urls = [ url(r'^.*$', PartSalePriceList.as_view(), name='api-part-sale-price-list'), ])), + # Base URL for part internal pricing + url(r'^internal-price/', include([ + url(r'^.*$', PartInternalPriceList.as_view(), name='api-part-internal-price-list'), + ])), + # Base URL for PartParameter API endpoints url(r'^parameter/', include([ url(r'^template/$', PartParameterTemplateList.as_view(), name='api-part-param-template-list'), diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 95de4961f9..ec799bcf8d 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -20,7 +20,7 @@ from .models import BomItem from .models import PartParameterTemplate, PartParameter from .models import PartCategoryParameterTemplate from .models import PartTestTemplate -from .models import PartSellPriceBreak +from .models import PartSellPriceBreak, PartInternalPriceBreak class PartModelChoiceField(forms.ModelChoiceField): @@ -394,3 +394,19 @@ class EditPartSalePriceBreakForm(HelperForm): 'quantity', 'price', ] + + +class EditPartInternalPriceBreakForm(HelperForm): + """ + Form for creating / editing a internal price for a part + """ + + quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity')) + + class Meta: + model = PartInternalPriceBreak + fields = [ + 'part', + 'quantity', + 'price', + ] diff --git a/InvenTree/part/migrations/0067_partinternalpricebreak.py b/InvenTree/part/migrations/0067_partinternalpricebreak.py new file mode 100644 index 0000000000..f1b16fe87c --- /dev/null +++ b/InvenTree/part/migrations/0067_partinternalpricebreak.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2 on 2021-06-05 14:13 + +import InvenTree.fields +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import djmoney.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0066_bomitem_allow_variants'), + ] + + operations = [ + migrations.CreateModel( + name='PartInternalPriceBreak', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', InvenTree.fields.RoundingDecimalField(decimal_places=5, default=1, help_text='Price break quantity', max_digits=15, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Quantity')), + ('price_currency', djmoney.models.fields.CurrencyField(choices=[('AUD', 'Australian Dollar'), ('CAD', 'Canadian Dollar'), ('EUR', 'Euro'), ('NZD', 'New Zealand Dollar'), ('GBP', 'Pound Sterling'), ('USD', 'US Dollar'), ('JPY', 'Yen')], default='USD', editable=False, max_length=3)), + ('price', djmoney.models.fields.MoneyField(decimal_places=4, default_currency='USD', help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price')), + ('part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='internalpricebreaks', to='part.part', verbose_name='Part')), + ], + options={ + 'unique_together': {('part', 'quantity')}, + }, + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 7b9038fecb..a09e6db217 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1983,6 +1983,21 @@ class PartSellPriceBreak(common.models.PriceBreak): unique_together = ('part', 'quantity') +class PartInternalPriceBreak(common.models.PriceBreak): + """ + Represents a price break for internally selling this part + """ + + part = models.ForeignKey( + Part, on_delete=models.CASCADE, + related_name='internalpricebreaks', + verbose_name=_('Part') + ) + + class Meta: + unique_together = ('part', 'quantity') + + class PartStar(models.Model): """ A PartStar object creates a relationship between a User and a Part. diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index d03a37c6dc..76275bd5e1 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -17,7 +17,8 @@ from stock.models import StockItem from .models import (BomItem, Part, PartAttachment, PartCategory, PartParameter, PartParameterTemplate, PartSellPriceBreak, - PartStar, PartTestTemplate, PartCategoryParameterTemplate) + PartStar, PartTestTemplate, PartCategoryParameterTemplate, + PartInternalPriceBreak) class CategorySerializer(InvenTreeModelSerializer): @@ -100,6 +101,25 @@ class PartSalePriceSerializer(InvenTreeModelSerializer): ] +class PartInternalPriceSerializer(InvenTreeModelSerializer): + """ + Serializer for internal prices for Part model. + """ + + quantity = serializers.FloatField() + + price = serializers.CharField() + + class Meta: + model = PartInternalPriceBreak + fields = [ + 'pk', + 'part', + 'quantity', + 'price', + ] + + class PartThumbSerializer(serializers.Serializer): """ Serializer for the 'image' field of the Part model. diff --git a/InvenTree/part/templates/part/internal_prices.html b/InvenTree/part/templates/part/internal_prices.html new file mode 100644 index 0000000000..dbf3986943 --- /dev/null +++ b/InvenTree/part/templates/part/internal_prices.html @@ -0,0 +1,108 @@ +{% extends "part/part_base.html" %} +{% load static %} +{% load i18n %} + +{% block menubar %} +{% include 'part/navbar.html' with tab='internal-prices' %} +{% endblock %} + +{% block heading %} +{% trans "Sell Price Information" %} +{% endblock %} + +{% block details %} + +