2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-20 22:06:28 +00:00

Merge pull request #1545 from matmair/issue1425

adds sales order line item price
This commit is contained in:
Oliver
2021-05-07 23:54:45 +10:00
committed by GitHub
18 changed files with 427 additions and 110 deletions

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2 on 2021-05-05 21:44
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('part', '0064_auto_20210404_2016'),
]
operations = [
migrations.AddField(
model_name='part',
name='base_cost',
field=models.DecimalField(decimal_places=3, default=0, help_text='Minimum charge (e.g. stocking fee)', max_digits=10, validators=[django.core.validators.MinValueValidator(0)], verbose_name='base cost'),
),
migrations.AddField(
model_name='part',
name='multiple',
field=models.PositiveIntegerField(default=1, help_text='Sell multiple', validators=[django.core.validators.MinValueValidator(1)], verbose_name='multiple'),
),
]

View File

@ -1611,6 +1611,44 @@ class Part(MPTTModel):
max(buy_price_range[1], bom_price_range[1])
)
base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], verbose_name=_('base cost'), help_text=_('Minimum charge (e.g. stocking fee)'))
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], verbose_name=_('multiple'), help_text=_('Sell multiple'))
get_price = common.models.get_price
@property
def has_price_breaks(self):
return self.price_breaks.count() > 0
@property
def price_breaks(self):
""" Return the associated price breaks in the correct order """
return self.salepricebreaks.order_by('quantity').all()
@property
def unit_pricing(self):
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
PartSellPriceBreak.objects.create(
part=self,
quantity=quantity,
price=price
)
@transaction.atomic
def copy_bom_from(self, other, clear=True, **kwargs):
"""

View File

@ -4,24 +4,20 @@
{% block pre_form_content %}
<div class='alert alert-info alert-block'>
{% blocktrans %}Pricing information for:<br>{{part}}.{% endblocktrans %}
</div>
<h4>{% trans 'Quantity' %}</h4>
<table class='table table-striped table-condensed'>
<table class='table table-striped table-condensed table-price-two'>
<tr>
<td><b>{% trans 'Part' %}</b></td>
<td colspan='2'>{{ part }}</td>
<td>{{ part }}</td>
</tr>
<tr>
<td><b>{% trans 'Quantity' %}</b></td>
<td colspan='2'>{{ quantity }}</td>
<td>{{ quantity }}</td>
</tr>
</table>
{% if part.supplier_count > 0 %}
{% if part.supplier_count > 0 %}
<h4>{% trans 'Supplier Pricing' %}</h4>
<table class='table table-striped table-condensed'>
<table class='table table-striped table-condensed table-price-three'>
{% if min_total_buy_price %}
<tr>
<td><b>{% trans 'Unit Cost' %}</b></td>
@ -42,12 +38,12 @@
</td>
</tr>
{% endif %}
</table>
{% endif %}
</table>
{% endif %}
{% if part.bom_count > 0 %}
{% if part.bom_count > 0 %}
<h4>{% trans 'BOM Pricing' %}</h4>
<table class='table table-striped table-condensed'>
<table class='table table-striped table-condensed table-price-three'>
{% if min_total_bom_price %}
<tr>
<td><b>{% trans 'Unit Cost' %}</b></td>
@ -75,8 +71,22 @@
</td>
</tr>
{% endif %}
</table>
{% endif %}
</table>
{% endif %}
{% if total_part_price %}
<h4>{% trans 'Sale Price' %}</h4>
<table class='table table-striped table-condensed table-price-two'>
<tr>
<td><b>{% trans 'Unit Cost' %}</b></td>
<td>{% include "price.html" with price=unit_part_price %}</td>
</tr>
<tr>
<td><b>{% trans 'Total Cost' %}</b></td>
<td>{% include "price.html" with price=total_part_price %}</td>
</tr>
</table>
{% endif %}
{% if min_unit_buy_price or min_unit_bom_price %}
{% else %}
@ -84,7 +94,5 @@
{% trans 'No pricing information is available for this part.' %}
</div>
{% endif %}
<hr>
{% endblock %}

View File

@ -30,11 +30,10 @@ sale_price_break_urls = [
]
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'),
url(r'^template/(?P<pk>\d+)/delete/', views.PartParameterTemplateDelete.as_view(), name='part-param-template-edit'),
url(r'^new/', views.PartParameterCreate.as_view(), name='part-param-create'),
url(r'^(?P<pk>\d+)/edit/', views.PartParameterEdit.as_view(), name='part-param-edit'),
url(r'^(?P<pk>\d+)/delete/', views.PartParameterDelete.as_view(), name='part-param-delete'),
@ -49,10 +48,10 @@ part_detail_urls = [
url(r'^duplicate/', views.PartDuplicate.as_view(), name='part-duplicate'),
url(r'^make-variant/', views.MakePartVariant.as_view(), name='make-part-variant'),
url(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'),
url(r'^bom-upload/?', views.BomUpload.as_view(), name='upload-bom'),
url(r'^bom-duplicate/?', views.BomDuplicate.as_view(), name='duplicate-bom'),
url(r'^params/', views.PartDetail.as_view(template_name='part/params.html'), name='part-params'),
url(r'^variants/?', views.PartDetail.as_view(template_name='part/variants.html'), name='part-variants'),
url(r'^stock/?', views.PartDetail.as_view(template_name='part/stock.html'), name='part-stock'),
@ -70,7 +69,7 @@ part_detail_urls = [
url(r'^related-parts/?', views.PartDetail.as_view(template_name='part/related.html'), name='part-related'),
url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'),
url(r'^notes/?', views.PartNotes.as_view(), name='part-notes'),
url(r'^qr_code/?', views.PartQRCode.as_view(), name='part-qr'),
# Normal thumbnail with form
@ -104,7 +103,7 @@ category_urls = [
url(r'^subcategory/', views.CategoryDetail.as_view(template_name='part/subcategory.html'), name='category-subcategory'),
url(r'^parametric/', views.CategoryParametric.as_view(), name='category-parametric'),
# Anything else
url(r'^.*$', views.CategoryDetail.as_view(), name='category-detail'),
]))

View File

@ -1956,10 +1956,9 @@ class PartPricing(AjaxView):
form_class = part_forms.PartPriceForm
role_required = ['sales_order.view', 'part.view']
def get_quantity(self):
""" Return set quantity in decimal format """
return Decimal(self.request.POST.get('quantity', 1))
def get_part(self):
@ -1969,12 +1968,7 @@ class PartPricing(AjaxView):
return None
def get_pricing(self, quantity=1, currency=None):
# try:
# quantity = int(quantity)
# except ValueError:
# quantity = 1
""" returns context with pricing information """
if quantity <= 0:
quantity = 1
@ -2044,11 +2038,22 @@ class PartPricing(AjaxView):
ctx['max_total_bom_price'] = max_bom_price
ctx['max_unit_bom_price'] = max_unit_bom_price
# part pricing information
part_price = part.get_price(quantity)
if part_price is not None:
ctx['total_part_price'] = round(part_price, 3)
ctx['unit_part_price'] = round(part_price / quantity, 3)
return ctx
def get(self, request, *args, **kwargs):
def get_initials(self):
""" returns initials for form """
return {'quantity': self.get_quantity()}
return self.renderJsonResponse(request, self.form_class(), context=self.get_pricing())
def get(self, request, *args, **kwargs):
init = self.get_initials()
qty = self.get_quantity()
return self.renderJsonResponse(request, self.form_class(initial=init), context=self.get_pricing(qty))
def post(self, request, *args, **kwargs):
@ -2057,16 +2062,19 @@ class PartPricing(AjaxView):
quantity = self.get_quantity()
# Retain quantity value set by user
form = self.form_class()
form.fields['quantity'].initial = quantity
form = self.form_class(initial=self.get_initials())
# TODO - How to handle pricing in different currencies?
currency = None
# check if data is set
try:
data = self.data
except AttributeError:
data = {}
# Always mark the form as 'invalid' (the user may wish to keep getting pricing data)
data = {
'form_valid': False,
}
data['form_valid'] = False
return self.renderJsonResponse(request, form, data=data, context=self.get_pricing(quantity, currency))