mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-20 22:06:28 +00:00
Merge branch 'master' into spaces!
This commit is contained in:
24
InvenTree/part/migrations/0065_auto_20210505_2144.py
Normal file
24
InvenTree/part/migrations/0065_auto_20210505_2144.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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):
|
||||
"""
|
||||
|
@ -91,7 +91,7 @@
|
||||
{% if part.salable and roles.sales_order.view %}
|
||||
<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'></span>
|
||||
<span class='menu-tab-icon fas fa-dollar-sign' style='width: 20px;'></span>
|
||||
{% trans "Sale Price" %}
|
||||
</a>
|
||||
</li>
|
||||
|
@ -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 %}
|
@ -2,7 +2,7 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block menubar %}}
|
||||
{% block menubar %}
|
||||
{% include 'part/navbar.html' with tab='sales-prices' %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -200,18 +200,28 @@ class I18nStaticNode(StaticNode):
|
||||
return ret
|
||||
|
||||
|
||||
@register.tag('i18n_static')
|
||||
def do_i18n_static(parser, token):
|
||||
"""
|
||||
Overrides normal static, adds language - lookup for prerenderd files #1485
|
||||
# use the dynamic url - tag if in Debugging-Mode
|
||||
if settings.DEBUG:
|
||||
|
||||
usage (like static):
|
||||
{% i18n_static path [as varname] %}
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
loc_name = settings.STATICFILES_I18_PREFIX
|
||||
@register.simple_tag()
|
||||
def i18n_static(url_name):
|
||||
""" simple tag to enable {% url %} functionality instead of {% static %} """
|
||||
return reverse(url_name)
|
||||
|
||||
# change path to called ressource
|
||||
bits[1] = f"'{loc_name}/{{lng}}.{bits[1][1:-1]}'"
|
||||
token.contents = ' '.join(bits)
|
||||
return I18nStaticNode.handle_token(parser, token)
|
||||
else:
|
||||
|
||||
@register.tag('i18n_static')
|
||||
def do_i18n_static(parser, token):
|
||||
"""
|
||||
Overrides normal static, adds language - lookup for prerenderd files #1485
|
||||
|
||||
usage (like static):
|
||||
{% i18n_static path [as varname] %}
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
loc_name = settings.STATICFILES_I18_PREFIX
|
||||
|
||||
# change path to called ressource
|
||||
bits[1] = f"'{loc_name}/{{lng}}.{bits[1][1:-1]}'"
|
||||
token.contents = ' '.join(bits)
|
||||
return I18nStaticNode.handle_token(parser, token)
|
||||
|
@ -30,7 +30,6 @@ 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'),
|
||||
|
@ -884,7 +884,7 @@ class PartImageDownloadFromURL(AjaxUpdateView):
|
||||
|
||||
# Check for valid response code
|
||||
if not response.status_code == 200:
|
||||
form.add_error('url', f"{_('Invalid response')}: {response.status_code}")
|
||||
form.add_error('url', _('Invalid response: {code}').format(code=response.status_code))
|
||||
return
|
||||
|
||||
response.raw.decode_content = True
|
||||
@ -1959,7 +1959,6 @@ class PartPricing(AjaxView):
|
||||
|
||||
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))
|
||||
|
||||
|
Reference in New Issue
Block a user