mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 20:16:44 +00:00
Updated Supplier models
- Added cost calculation for supplier part - Added more validators and help text
This commit is contained in:
parent
9b0fefb0b4
commit
cd0b6a6511
30
InvenTree/part/migrations/0006_auto_20190416_2354.py
Normal file
30
InvenTree/part/migrations/0006_auto_20190416_2354.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Generated by Django 2.2 on 2019-04-16 13:54
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('part', '0005_part_consumable'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='bomitem',
|
||||||
|
name='sub_part',
|
||||||
|
field=models.ForeignKey(limit_choices_to={'consumable': True}, on_delete=django.db.models.deletion.CASCADE, related_name='used_in', to='part.Part'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='part',
|
||||||
|
name='consumable',
|
||||||
|
field=models.BooleanField(default=True, help_text='Can this part be used to build other parts?'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplierpricebreak',
|
||||||
|
name='quantity',
|
||||||
|
field=models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(2)]),
|
||||||
|
),
|
||||||
|
]
|
54
InvenTree/part/migrations/0007_auto_20190417_0007.py
Normal file
54
InvenTree/part/migrations/0007_auto_20190417_0007.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Generated by Django 2.2 on 2019-04-16 14:07
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('part', '0006_auto_20190416_2354'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='supplierpart',
|
||||||
|
name='note',
|
||||||
|
field=models.CharField(blank=True, help_text='Notes', max_length=100),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplierpart',
|
||||||
|
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)]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplierpart',
|
||||||
|
name='description',
|
||||||
|
field=models.CharField(blank=True, help_text='Supplier part description', max_length=250),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplierpart',
|
||||||
|
name='minimum',
|
||||||
|
field=models.PositiveIntegerField(default=1, help_text='Minimum order quantity (MOQ)', validators=[django.core.validators.MinValueValidator(0)]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplierpart',
|
||||||
|
name='multiple',
|
||||||
|
field=models.PositiveIntegerField(default=1, help_text='Order multiple', validators=[django.core.validators.MinValueValidator(0)]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplierpart',
|
||||||
|
name='packaging',
|
||||||
|
field=models.CharField(blank=True, help_text='Part packaging', max_length=50),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplierpart',
|
||||||
|
name='single_price',
|
||||||
|
field=models.DecimalField(decimal_places=3, default=0, help_text='Price for single quantity', max_digits=10, validators=[django.core.validators.MinValueValidator(0)]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplierpricebreak',
|
||||||
|
name='cost',
|
||||||
|
field=models.DecimalField(decimal_places=3, max_digits=10, validators=[django.core.validators.MinValueValidator(0)]),
|
||||||
|
),
|
||||||
|
]
|
@ -3,6 +3,8 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
@ -469,22 +471,25 @@ class SupplierPart(models.Model):
|
|||||||
|
|
||||||
URL = models.URLField(blank=True)
|
URL = models.URLField(blank=True)
|
||||||
|
|
||||||
description = models.CharField(max_length=250, blank=True)
|
description = models.CharField(max_length=250, blank=True, help_text='Supplier part description')
|
||||||
|
|
||||||
|
# Note attached to this BOM line item
|
||||||
|
note = models.CharField(max_length=100, blank=True, help_text='Notes')
|
||||||
|
|
||||||
# Default price for a single unit
|
# Default price for a single unit
|
||||||
single_price = models.DecimalField(max_digits=10, decimal_places=3, default=0)
|
single_price = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], help_text='Price for single quantity')
|
||||||
|
|
||||||
# Base charge added to order independent of quantity e.g. "Reeling Fee"
|
# Base charge added to order independent of quantity e.g. "Reeling Fee"
|
||||||
base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0)
|
base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], help_text='Minimum charge (e.g. stocking fee)')
|
||||||
|
|
||||||
# packaging that the part is supplied in, e.g. "Reel"
|
# packaging that the part is supplied in, e.g. "Reel"
|
||||||
packaging = models.CharField(max_length=50, blank=True)
|
packaging = models.CharField(max_length=50, blank=True, help_text='Part packaging')
|
||||||
|
|
||||||
# multiple that the part is provided in
|
# multiple that the part is provided in
|
||||||
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)])
|
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)], help_text='Order multiple')
|
||||||
|
|
||||||
# Mimumum number required to order
|
# Mimumum number required to order
|
||||||
minimum = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)])
|
minimum = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)], help_text='Minimum order quantity (MOQ)')
|
||||||
|
|
||||||
# lead time for parts that cannot be delivered immediately
|
# lead time for parts that cannot be delivered immediately
|
||||||
lead_time = models.DurationField(blank=True, null=True)
|
lead_time = models.DurationField(blank=True, null=True)
|
||||||
@ -501,6 +506,47 @@ class SupplierPart(models.Model):
|
|||||||
|
|
||||||
return ' | '.join(items)
|
return ' | '.join(items)
|
||||||
|
|
||||||
|
def get_price(self, quantity, moq=True, multiples=True):
|
||||||
|
""" Calculate the supplier price based on quantity price breaks.
|
||||||
|
- If no price breaks available, use the single_price field
|
||||||
|
- 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
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Order multiples
|
||||||
|
if multiples:
|
||||||
|
quantity = int(math.ceil(quantity / self.multipe) * self.multiple)
|
||||||
|
|
||||||
|
# Minimum ordering requirement
|
||||||
|
if moq and self.minimum > quantity:
|
||||||
|
quantity = self.minimum
|
||||||
|
|
||||||
|
pb_found = False
|
||||||
|
pb_quantity = -1
|
||||||
|
pb_cost = 0.0
|
||||||
|
|
||||||
|
for pb in self.price_breaks.all():
|
||||||
|
# Ignore this pricebreak!
|
||||||
|
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
|
||||||
|
pb_cost = pb.cost
|
||||||
|
|
||||||
|
# No appropriate price-break found - use the single cost!
|
||||||
|
if pb_found:
|
||||||
|
cost = pb_cost * quantity
|
||||||
|
else:
|
||||||
|
cost = self.single_price * quantity
|
||||||
|
|
||||||
|
return cost + self.base_cost
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{sku} - {supplier}".format(
|
return "{sku} - {supplier}".format(
|
||||||
sku=self.SKU,
|
sku=self.SKU,
|
||||||
@ -514,8 +560,11 @@ class SupplierPriceBreak(models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
part = models.ForeignKey(SupplierPart, on_delete=models.CASCADE, related_name='price_breaks')
|
part = models.ForeignKey(SupplierPart, on_delete=models.CASCADE, related_name='price_breaks')
|
||||||
quantity = models.PositiveIntegerField(validators=[MinValueValidator(0)])
|
|
||||||
cost = models.DecimalField(max_digits=10, decimal_places=3)
|
# At least 2 units are required for a 'price break' - Otherwise, just use single-price!
|
||||||
|
quantity = models.PositiveIntegerField(validators=[MinValueValidator(2)])
|
||||||
|
|
||||||
|
cost = models.DecimalField(max_digits=10, decimal_places=3, validators=[MinValueValidator(0)])
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ("part", "quantity")
|
unique_together = ("part", "quantity")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user