mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-13 10:35:40 +00:00
Merge branch 'inventree:master' into bom-internal
This commit is contained in:
@ -30,7 +30,7 @@ from mptt.models import TreeForeignKey, MPTTModel
|
||||
|
||||
from stdimage.models import StdImageField
|
||||
|
||||
from decimal import Decimal
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from datetime import datetime
|
||||
from rapidfuzz import fuzz
|
||||
import hashlib
|
||||
@ -39,7 +39,7 @@ from InvenTree import helpers
|
||||
from InvenTree import validators
|
||||
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
|
||||
from InvenTree.fields import InvenTreeURLField
|
||||
from InvenTree.helpers import decimal2string, normalize
|
||||
from InvenTree.helpers import decimal2string, normalize, decimal2money
|
||||
|
||||
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus, SalesOrderStatus
|
||||
|
||||
@ -75,6 +75,10 @@ class PartCategory(InvenTreeTree):
|
||||
|
||||
default_keywords = models.CharField(null=True, blank=True, max_length=250, verbose_name=_('Default keywords'), help_text=_('Default keywords for parts in this category'))
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
return reverse('api-part-category-list')
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('category-detail', kwargs={'pk': self.id})
|
||||
|
||||
@ -329,6 +333,11 @@ class Part(MPTTModel):
|
||||
# For legacy reasons the 'variant_of' field is used to indicate the MPTT parent
|
||||
parent_attr = 'variant_of'
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
|
||||
return reverse('api-part-list')
|
||||
|
||||
def get_context_data(self, request, **kwargs):
|
||||
"""
|
||||
Return some useful context data about this part for template rendering
|
||||
@ -683,7 +692,6 @@ class Part(MPTTModel):
|
||||
null=True, blank=True,
|
||||
limit_choices_to={
|
||||
'is_template': True,
|
||||
'active': True,
|
||||
},
|
||||
on_delete=models.SET_NULL,
|
||||
help_text=_('Is this part a variant of another part?'),
|
||||
@ -1479,16 +1487,17 @@ class Part(MPTTModel):
|
||||
|
||||
return True
|
||||
|
||||
def get_price_info(self, quantity=1, buy=True, bom=True):
|
||||
def get_price_info(self, quantity=1, buy=True, bom=True, internal=False):
|
||||
""" Return a simplified pricing string for this part
|
||||
|
||||
Args:
|
||||
quantity: Number of units to calculate price for
|
||||
buy: Include supplier pricing (default = True)
|
||||
bom: Include BOM pricing (default = True)
|
||||
internal: Include internal pricing (default = False)
|
||||
"""
|
||||
|
||||
price_range = self.get_price_range(quantity, buy, bom)
|
||||
price_range = self.get_price_range(quantity, buy, bom, internal)
|
||||
|
||||
if price_range is None:
|
||||
return None
|
||||
@ -1576,9 +1585,10 @@ class Part(MPTTModel):
|
||||
|
||||
- Supplier price (if purchased from suppliers)
|
||||
- BOM price (if built from other parts)
|
||||
- Internal price (if set for the part)
|
||||
|
||||
Returns:
|
||||
Minimum of the supplier price or BOM price. If no pricing available, returns None
|
||||
Minimum of the supplier, BOM or internal price. If no pricing available, returns None
|
||||
"""
|
||||
|
||||
# only get internal price if set and should be used
|
||||
@ -1966,6 +1976,10 @@ class PartAttachment(InvenTreeAttachment):
|
||||
Model for storing file attachments against a Part object
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
return reverse('api-part-attachment-list')
|
||||
|
||||
def getSubdir(self):
|
||||
return os.path.join("part_files", str(self.part.id))
|
||||
|
||||
@ -1977,6 +1991,10 @@ class PartSellPriceBreak(common.models.PriceBreak):
|
||||
"""
|
||||
Represents a price break for selling this part
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
return reverse('api-part-sale-price-list')
|
||||
|
||||
part = models.ForeignKey(
|
||||
Part, on_delete=models.CASCADE,
|
||||
@ -1994,6 +2012,10 @@ class PartInternalPriceBreak(common.models.PriceBreak):
|
||||
Represents a price break for internally selling this part
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
return reverse('api-part-internal-price-list')
|
||||
|
||||
part = models.ForeignKey(
|
||||
Part, on_delete=models.CASCADE,
|
||||
related_name='internalpricebreaks',
|
||||
@ -2038,6 +2060,10 @@ class PartTestTemplate(models.Model):
|
||||
run on the model (refer to the validate_unique function).
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
return reverse('api-part-test-template-list')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
self.clean()
|
||||
@ -2136,6 +2162,10 @@ class PartParameterTemplate(models.Model):
|
||||
units: The units of the Parameter [string]
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
return reverse('api-part-parameter-template-list')
|
||||
|
||||
def __str__(self):
|
||||
s = str(self.name)
|
||||
if self.units:
|
||||
@ -2173,6 +2203,10 @@ class PartParameter(models.Model):
|
||||
data: The data (value) of the Parameter [string]
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
return reverse('api-part-parameter-list')
|
||||
|
||||
def __str__(self):
|
||||
# String representation of a PartParameter (used in the admin interface)
|
||||
return "{part} : {param} = {data}{units}".format(
|
||||
@ -2264,6 +2298,10 @@ class BomItem(models.Model):
|
||||
allow_variants: Stock for part variants can be substituted for this BomItem
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
return reverse('api-bom-list')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
self.clean()
|
||||
@ -2380,6 +2418,15 @@ class BomItem(models.Model):
|
||||
- If the "sub_part" is trackable, then the "part" must be trackable too!
|
||||
"""
|
||||
|
||||
super().clean()
|
||||
|
||||
try:
|
||||
self.quantity = Decimal(self.quantity)
|
||||
except InvalidOperation:
|
||||
raise ValidationError({
|
||||
'quantity': _('Must be a valid number')
|
||||
})
|
||||
|
||||
try:
|
||||
# Check for circular BOM references
|
||||
if self.sub_part:
|
||||
@ -2412,7 +2459,7 @@ class BomItem(models.Model):
|
||||
return "{n} x {child} to make {parent}".format(
|
||||
parent=self.part.full_name,
|
||||
child=self.sub_part.full_name,
|
||||
n=helpers.decimal2string(self.quantity))
|
||||
n=decimal2string(self.quantity))
|
||||
|
||||
def available_stock(self):
|
||||
"""
|
||||
@ -2496,10 +2543,12 @@ class BomItem(models.Model):
|
||||
return required
|
||||
|
||||
@property
|
||||
def price_range(self):
|
||||
def price_range(self, internal=False):
|
||||
""" Return the price-range for this BOM item. """
|
||||
|
||||
prange = self.sub_part.get_price_range(self.quantity)
|
||||
# get internal price setting
|
||||
use_internal = common.models.InvenTreeSetting.get_setting('PART_BOM_USE_INTERNAL_PRICE', False)
|
||||
prange = self.sub_part.get_price_range(self.quantity, internal=use_internal and internal)
|
||||
|
||||
if prange is None:
|
||||
return prange
|
||||
@ -2507,11 +2556,11 @@ class BomItem(models.Model):
|
||||
pmin, pmax = prange
|
||||
|
||||
if pmin == pmax:
|
||||
return decimal2string(pmin)
|
||||
return decimal2money(pmin)
|
||||
|
||||
# Convert to better string representation
|
||||
pmin = decimal2string(pmin)
|
||||
pmax = decimal2string(pmax)
|
||||
pmin = decimal2money(pmin)
|
||||
pmax = decimal2money(pmax)
|
||||
|
||||
return "{pmin} to {pmax}".format(pmin=pmin, pmax=pmax)
|
||||
|
||||
|
Reference in New Issue
Block a user