mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 20:16:44 +00:00
adding internal price breaks as in #1606
This commit is contained in:
parent
92f77b6bbb
commit
0d93c96f2a
@ -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)
|
||||
|
@ -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'),
|
||||
|
@ -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',
|
||||
]
|
||||
|
30
InvenTree/part/migrations/0067_partinternalpricebreak.py
Normal file
30
InvenTree/part/migrations/0067_partinternalpricebreak.py
Normal file
@ -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')},
|
||||
},
|
||||
),
|
||||
]
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
108
InvenTree/part/templates/part/internal_prices.html
Normal file
108
InvenTree/part/templates/part/internal_prices.html
Normal file
@ -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 %}
|
||||
|
||||
<div id='internal-price-break-toolbar' class='btn-group'>
|
||||
<button class='btn btn-primary' id='new-internal-price-break' type='button'>
|
||||
<span class='fas fa-plus-circle'></span> {% trans "Add Internal Price Break" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class='table table-striped table-condensed' id='internal-price-break-table' data-toolbar='#internal-price-break-toolbar'>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
function reloadPriceBreaks() {
|
||||
$("#internal-price-break-table").bootstrapTable("refresh");
|
||||
}
|
||||
|
||||
$('#new-internal-price-break').click(function() {
|
||||
launchModalForm("{% url 'internal-price-break-create' %}",
|
||||
{
|
||||
success: reloadPriceBreaks,
|
||||
data: {
|
||||
part: {{ part.id }},
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$('#internal-price-break-table').inventreeTable({
|
||||
name: 'internalprice',
|
||||
formatNoMatches: function() { return "{% trans 'No internal price break information found' %}"; },
|
||||
queryParams: {
|
||||
part: {{ part.id }},
|
||||
},
|
||||
url: "{% url 'api-part-internal-price-list' %}",
|
||||
onPostBody: function() {
|
||||
var table = $('#internal-price-break-table');
|
||||
|
||||
table.find('.button-internal-price-break-delete').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
launchModalForm(
|
||||
`/part/internal-price/${pk}/delete/`,
|
||||
{
|
||||
success: reloadPriceBreaks
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
table.find('.button-internal-price-break-edit').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
launchModalForm(
|
||||
`/part/internal-price/${pk}/edit/`,
|
||||
{
|
||||
success: reloadPriceBreaks
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
field: 'pk',
|
||||
title: 'ID',
|
||||
visible: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'quantity',
|
||||
title: '{% trans "Quantity" %}',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
field: 'price',
|
||||
title: '{% trans "Price" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index) {
|
||||
var html = value;
|
||||
|
||||
html += `<div class='btn-group float-right' role='group'>`
|
||||
|
||||
html += makeIconButton('fa-edit icon-blue', 'button-internal-price-break-edit', row.pk, '{% trans "Edit internal price break" %}');
|
||||
html += makeIconButton('fa-trash-alt icon-red', 'button-internal-price-break-delete', row.pk, '{% trans "Delete internal price break" %}');
|
||||
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
{% endblock %}
|
@ -95,6 +95,12 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if part.salable and roles.sales_order.view %}
|
||||
<li class='list-group-item {% if tab == "internal-prices" %}active{% endif %}' title='{% trans "Internal Price Information" %}'>
|
||||
<a href='{% url "part-internal-prices" part.id %}'>
|
||||
<span class='menu-tab-icon fas fa-dollar-sign' style='width: 20px;'></span>
|
||||
{% trans "Internal Price" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class='list-group-item {% if tab == "sales-prices" %}active{% endif %}' title='{% trans "Sales Price Information" %}'>
|
||||
<a href='{% url "part-sale-prices" part.id %}'>
|
||||
<span class='menu-tab-icon fas fa-dollar-sign' style='width: 20px;'></span>
|
||||
|
@ -29,6 +29,12 @@ sale_price_break_urls = [
|
||||
url(r'^(?P<pk>\d+)/delete/', views.PartSalePriceBreakDelete.as_view(), name='sale-price-break-delete'),
|
||||
]
|
||||
|
||||
internal_price_break_urls = [
|
||||
url(r'^new/', views.PartInternalPriceBreakCreate.as_view(), name='internal-price-break-create'),
|
||||
url(r'^(?P<pk>\d+)/edit/', views.PartInternalPriceBreakEdit.as_view(), name='internal-price-break-edit'),
|
||||
url(r'^(?P<pk>\d+)/delete/', views.PartInternalPriceBreakDelete.as_view(), name='internal-price-break-delete'),
|
||||
]
|
||||
|
||||
part_parameter_urls = [
|
||||
url(r'^template/new/', views.PartParameterTemplateCreate.as_view(), name='part-param-template-create'),
|
||||
url(r'^template/(?P<pk>\d+)/edit/', views.PartParameterTemplateEdit.as_view(), name='part-param-template-edit'),
|
||||
@ -65,6 +71,7 @@ part_detail_urls = [
|
||||
url(r'^orders/?', views.PartDetail.as_view(template_name='part/orders.html'), name='part-orders'),
|
||||
url(r'^sales-orders/', views.PartDetail.as_view(template_name='part/sales_orders.html'), name='part-sales-orders'),
|
||||
url(r'^sale-prices/', views.PartDetail.as_view(template_name='part/sale_prices.html'), name='part-sale-prices'),
|
||||
url(r'^internal-prices/', views.PartDetail.as_view(template_name='part/internal_prices.html'), name='part-internal-prices'),
|
||||
url(r'^tests/', views.PartDetail.as_view(template_name='part/part_tests.html'), name='part-test-templates'),
|
||||
url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
|
||||
url(r'^related-parts/?', views.PartDetail.as_view(template_name='part/related.html'), name='part-related'),
|
||||
@ -145,6 +152,9 @@ part_urls = [
|
||||
# Part price breaks
|
||||
url(r'^sale-price/', include(sale_price_break_urls)),
|
||||
|
||||
# Part internal price breaks
|
||||
url(r'^internal-price/', include(internal_price_break_urls)),
|
||||
|
||||
# Part test templates
|
||||
url(r'^test-template/', include([
|
||||
url(r'^new/', views.PartTestTemplateCreate.as_view(), name='part-test-template-create'),
|
||||
|
@ -36,7 +36,7 @@ from .models import PartCategoryParameterTemplate
|
||||
from .models import BomItem
|
||||
from .models import match_part_names
|
||||
from .models import PartTestTemplate
|
||||
from .models import PartSellPriceBreak
|
||||
from .models import PartSellPriceBreak, PartInternalPriceBreak
|
||||
|
||||
from common.models import InvenTreeSetting
|
||||
from company.models import SupplierPart
|
||||
@ -2794,3 +2794,26 @@ class PartSalePriceBreakDelete(AjaxDeleteView):
|
||||
model = PartSellPriceBreak
|
||||
ajax_form_title = _("Delete Price Break")
|
||||
ajax_template_name = "modal_delete_form.html"
|
||||
|
||||
|
||||
class PartInternalPriceBreakCreate(PartSalePriceBreakCreate):
|
||||
""" View for creating a internal price break for a part """
|
||||
|
||||
model = PartInternalPriceBreak
|
||||
form_class = part_forms.EditPartInternalPriceBreakForm
|
||||
ajax_form_title = _('Add Internal Price Break')
|
||||
|
||||
|
||||
class PartInternalPriceBreakEdit(PartSalePriceBreakEdit):
|
||||
""" View for editing a internal price break """
|
||||
|
||||
model = PartInternalPriceBreak
|
||||
form_class = part_forms.EditPartInternalPriceBreakForm
|
||||
ajax_form_title = _('Edit Internal Price Break')
|
||||
|
||||
|
||||
class PartInternalPriceBreakDelete(PartSalePriceBreakDelete):
|
||||
""" View for deleting a internal price break """
|
||||
|
||||
model = PartInternalPriceBreak
|
||||
ajax_form_title = _("Delete Internal Price Break")
|
||||
|
Loading…
x
Reference in New Issue
Block a user