Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds sales order line item price #1545

Merged
merged 32 commits into from
May 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1614c6e
Add in sale price model
matmair May 4, 2021
294e86c
Add in sale price model
matmair May 4, 2021
a6fc5d2
Merge branch 'issue1425' of https://github.com/matmair/InvenTree into…
matmair May 4, 2021
7fa2352
sale price in ui
matmair May 4, 2021
251603b
removing temp fix for invoke error
matmair May 4, 2021
ee028ef
space cleanup
matmair May 5, 2021
dc4fb64
add in price modal in table
matmair May 5, 2021
b392586
quantity also for modal
matmair May 5, 2021
2cfb9c6
space cleanup
matmair May 5, 2021
287a05d
clearer spacing in html
matmair May 5, 2021
1a227fa
abstracting get_price
matmair May 5, 2021
1b7ade9
adding in missing parts for full saleprice
matmair May 5, 2021
030865f
sale price in pricing table
matmair May 5, 2021
efa9da2
removed unused imports
matmair May 5, 2021
c4d0865
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair May 5, 2021
66f198b
removing duplicate information in pricing table
matmair May 5, 2021
b09f49a
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair May 6, 2021
c2a5e1f
moved the special stuff into the inherited view
matmair May 6, 2021
4830ff2
new function for initials
matmair May 6, 2021
90c207b
keeping part id in inherited form
matmair May 6, 2021
660a3f9
cleaner get function
matmair May 6, 2021
792b2d1
cleanup
matmair May 6, 2021
aac05db
style fixing
matmair May 6, 2021
0537932
same spacing for tables
matmair May 6, 2021
985967f
save return of part.id
matmair May 7, 2021
09fe9cc
sales order item tracking
matmair May 7, 2021
f73863e
adding in cstm action buttons function
matmair May 7, 2021
c775c46
adding custom action button
matmair May 7, 2021
ae01503
handeling data in an inheritable way
matmair May 7, 2021
9e59d41
style improvments
matmair May 7, 2021
b6043af
auto-set price if sales-order line is added
matmair May 7, 2021
63cf75e
styling again
matmair May 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions InvenTree/InvenTree/static/css/inventree.css
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,24 @@
background: #eee;
}

/* pricing table widths */
.table-price-two tr td:first-child {
width: 40%;
}

.table-price-three tr td:first-child {
width: 40%;
}

.table-price-two tr td:last-child {
width: 60%;
}

.table-price-three tr td:last-child {
width: 30%;
}
/* !pricing table widths */

.btn-glyph {
padding-left: 6px;
padding-right: 6px;
Expand Down
2 changes: 1 addition & 1 deletion InvenTree/InvenTree/static/script/inventree/inventree.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function makeIconButton(icon, cls, pk, title, options={}) {
if (options.disabled) {
extraProps += "disabled='true' ";
}

html += `<button pk='${pk}' id='${id}' class='${classes}' title='${title}' ${extraProps}>`;
html += `<span class='fas ${icon}'></span>`;
html += `</button>`;
Expand Down
68 changes: 68 additions & 0 deletions InvenTree/common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from __future__ import unicode_literals

import os
import decimal
import math

from django.db import models, transaction
from django.db.utils import IntegrityError, OperationalError
Expand Down Expand Up @@ -730,6 +732,72 @@ def convert_to(self, currency_code):
return converted.amount


def get_price(instance, quantity, moq=True, multiples=True, currency=None):
""" Calculate the price based on quantity price breaks.

- Don't forget to add in flat-fee cost (base_cost field)
- If MOQ (minimum order quantity) is required, bump quantity
- If order multiples are to be observed, then we need to calculate based on that, too
"""

price_breaks = instance.price_breaks.all()

# No price break information available?
if len(price_breaks) == 0:
return None

# Check if quantity is fraction and disable multiples
multiples = (quantity % 1 == 0)

# Order multiples
if multiples:
quantity = int(math.ceil(quantity / instance.multiple) * instance.multiple)

pb_found = False
pb_quantity = -1
pb_cost = 0.0

if currency is None:
# Default currency selection
currency = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')

pb_min = None
for pb in instance.price_breaks.all():
# Store smallest price break
if not pb_min:
pb_min = pb

# Ignore this pricebreak (quantity is too high)
if pb.quantity > quantity:
continue

pb_found = True

# If this price-break quantity is the largest so far, use it!
if pb.quantity > pb_quantity:
pb_quantity = pb.quantity

# Convert everything to the selected currency
pb_cost = pb.convert_to(currency)

# Use smallest price break
if not pb_found and pb_min:
# Update price break information
pb_quantity = pb_min.quantity
pb_cost = pb_min.convert_to(currency)
# Trigger cost calculation using smallest price break
pb_found = True

# Convert quantity to decimal.Decimal format
quantity = decimal.Decimal(f'{quantity}')

if pb_found:
cost = pb_cost * quantity
return InvenTree.helpers.normalize(cost + instance.base_cost)
else:
return None


class ColorTheme(models.Model):
""" Color Theme Setting """

Expand Down
68 changes: 1 addition & 67 deletions InvenTree/company/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
from __future__ import unicode_literals

import os
import decimal
import math

from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator
Expand All @@ -26,7 +24,6 @@
from stdimage.models import StdImageField

from InvenTree.helpers import getMediaUrl, getBlankImage, getBlankThumbnail
from InvenTree.helpers import normalize
from InvenTree.fields import InvenTreeURLField
from InvenTree.status_codes import PurchaseOrderStatus

Expand Down Expand Up @@ -558,70 +555,7 @@ def add_price_break(self, quantity, price):
price=price
)

def get_price(self, quantity, moq=True, multiples=True, currency=None):
""" Calculate the supplier price based on quantity price breaks.

- Don't forget to add in flat-fee cost (base_cost field)
- If MOQ (minimum order quantity) is required, bump quantity
- If order multiples are to be observed, then we need to calculate based on that, too
"""

price_breaks = self.price_breaks.all()

# No price break information available?
if len(price_breaks) == 0:
return None

# Check if quantity is fraction and disable multiples
multiples = (quantity % 1 == 0)

# Order multiples
if multiples:
quantity = int(math.ceil(quantity / self.multiple) * self.multiple)

pb_found = False
pb_quantity = -1
pb_cost = 0.0

if currency is None:
# Default currency selection
currency = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')

pb_min = None
for pb in self.price_breaks.all():
# Store smallest price break
if not pb_min:
pb_min = pb

# Ignore this pricebreak (quantity is too high)
if pb.quantity > quantity:
continue

pb_found = True

# If this price-break quantity is the largest so far, use it!
if pb.quantity > pb_quantity:
pb_quantity = pb.quantity

# Convert everything to the selected currency
pb_cost = pb.convert_to(currency)

# Use smallest price break
if not pb_found and pb_min:
# Update price break information
pb_quantity = pb_min.quantity
pb_cost = pb_min.convert_to(currency)
# Trigger cost calculation using smallest price break
pb_found = True

# Convert quantity to decimal.Decimal format
quantity = decimal.Decimal(f'{quantity}')

if pb_found:
cost = pb_cost * quantity
return normalize(cost + self.base_cost)
else:
return None
get_price = common.models.get_price

def open_orders(self):
""" Return a database query for PO line items for this SupplierPart,
Expand Down
1 change: 1 addition & 0 deletions InvenTree/order/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ class Meta:
'part',
'quantity',
'reference',
'sale_price',
'notes'
]

Expand Down
24 changes: 24 additions & 0 deletions InvenTree/order/migrations/0045_auto_20210504_1946.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 3.2 on 2021-05-04 19:46

from django.db import migrations
import djmoney.models.fields


class Migration(migrations.Migration):

dependencies = [
('order', '0044_auto_20210404_2016'),
]

operations = [
migrations.AddField(
model_name='salesorderlineitem',
name='sale_price',
field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency='USD', help_text='Unit sale price', max_digits=19, null=True, verbose_name='Sale Price'),
),
migrations.AddField(
model_name='salesorderlineitem',
name='sale_price_currency',
field=djmoney.models.fields.CurrencyField(choices=[('AUD', 'Australian Dollar'), ('GBP', 'British Pound'), ('CAD', 'Canadian Dollar'), ('EUR', 'Euro'), ('JPY', 'Japanese Yen'), ('NZD', 'New Zealand Dollar'), ('USD', 'US Dollar')], default='USD', editable=False, max_length=3),
),
]
10 changes: 10 additions & 0 deletions InvenTree/order/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -672,12 +672,22 @@ class SalesOrderLineItem(OrderLineItem):
Attributes:
order: Link to the SalesOrder that this line item belongs to
part: Link to a Part object (may be null)
sale_price: The unit sale price for this OrderLineItem
"""

order = models.ForeignKey(SalesOrder, on_delete=models.CASCADE, related_name='lines', verbose_name=_('Order'), help_text=_('Sales Order'))

part = models.ForeignKey('part.Part', on_delete=models.SET_NULL, related_name='sales_order_line_items', null=True, verbose_name=_('Part'), help_text=_('Part'), limit_choices_to={'salable': True})

sale_price = MoneyField(
max_digits=19,
decimal_places=4,
default_currency='USD',
null=True, blank=True,
verbose_name=_('Sale Price'),
help_text=_('Unit sale price'),
)

class Meta:
unique_together = [
]
Expand Down
4 changes: 4 additions & 0 deletions InvenTree/order/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ def __init__(self, *args, **kwargs):
quantity = serializers.FloatField()
allocated = serializers.FloatField(source='allocated_quantity', read_only=True)
fulfilled = serializers.FloatField(source='fulfilled_quantity', read_only=True)
sale_price_string = serializers.CharField(source='sale_price', read_only=True)

class Meta:
model = SalesOrderLineItem
Expand All @@ -294,6 +295,9 @@ class Meta:
'order_detail',
'part',
'part_detail',
'sale_price',
'sale_price_currency',
'sale_price_string',
]


Expand Down
33 changes: 31 additions & 2 deletions InvenTree/order/templates/order/sales_order_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@
field: 'quantity',
title: '{% trans "Quantity" %}',
},
{
sortable: true,
field: 'sale_price',
title: '{% trans "Unit Price" %}',
formatter: function(value, row) {
return row.sale_price_string || row.sale_price;
}
},
{
field: 'allocated',
{% if order.status == SalesOrderStatus.PENDING %}
Expand Down Expand Up @@ -279,7 +287,7 @@
html += makeIconButton('fa-hashtag icon-green', 'button-add-by-sn', pk, '{% trans "Allocate serial numbers" %}');
}

html += makeIconButton('fa-sign-in-alt icon-green', 'button-add', pk, '{% trans "Allocate stock" %}');
html += makeIconButton('fa-sign-in-alt icon-green', 'button-add', pk, '{% trans "Allocate stock" %}');

if (part.purchaseable) {
html += makeIconButton('fa-shopping-cart', 'button-buy', row.part, '{% trans "Purchase stock" %}');
Expand All @@ -288,7 +296,8 @@
if (part.assembly) {
html += makeIconButton('fa-tools', 'button-build', row.part, '{% trans "Build stock" %}');
}


html += makeIconButton('fa-dollar-sign icon-green', 'button-price', pk, '{% trans "Calculate price" %}');
}

html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line item" %}');
Expand Down Expand Up @@ -388,6 +397,26 @@
},
});
});

$(".button-price").click(function() {
var pk = $(this).attr('pk');
var idx = $(this).closest('tr').attr('data-index');
var row = table.bootstrapTable('getData')[idx];

launchModalForm(
"{% url 'line-pricing' %}",
{
submit_text: '{% trans "Calculate price" %}',
data: {
line_item: pk,
quantity: row.quantity,
},
buttons: [{name: 'update_price',
title: '{% trans "Update Unit Price" %}'},],
success: reloadTable,
}
);
});
}

{% endblock %}
1 change: 1 addition & 0 deletions InvenTree/order/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
url(r'^new/', views.PurchaseOrderCreate.as_view(), name='po-create'),

url(r'^order-parts/', views.OrderParts.as_view(), name='order-parts'),
url(r'^pricing/', views.LineItemPricing.as_view(), name='line-pricing'),

# Display detail view for a single purchase order
url(r'^(?P<pk>\d+)/', include(purchase_order_detail_urls)),
Expand Down
Loading