mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Add missing args to docstrings
This commit is contained in:
parent
ff9873f92c
commit
c24882bf66
@ -162,7 +162,7 @@ def normalize(d):
|
|||||||
|
|
||||||
|
|
||||||
def increment(n):
|
def increment(n):
|
||||||
"""Attempt to increment an integer (or a string that looks like an integer!)
|
"""Attempt to increment an integer (or a string that looks like an integer).
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
|
|
||||||
@ -332,7 +332,7 @@ def GetExportFormats():
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def DownloadFile(data, filename, content_type='application/text', inline=False):
|
def DownloadFile(data, filename, content_type='application/text', inline=False) -> StreamingHttpResponse:
|
||||||
"""Create a dynamic file for the user to download.
|
"""Create a dynamic file for the user to download.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -362,7 +362,7 @@ def DownloadFile(data, filename, content_type='application/text', inline=False):
|
|||||||
|
|
||||||
|
|
||||||
def extract_serial_numbers(serials, expected_quantity, next_number: int):
|
def extract_serial_numbers(serials, expected_quantity, next_number: int):
|
||||||
"""Attempt to extract serial numbers from an input string:
|
"""Attempt to extract serial numbers from an input string.
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- Serial numbers can be either strings, or integers
|
- Serial numbers can be either strings, or integers
|
||||||
|
@ -211,7 +211,7 @@ class AjaxMixin(InvenTreeRoleMixin):
|
|||||||
ajax_form_title = ''
|
ajax_form_title = ''
|
||||||
|
|
||||||
def get_form_title(self):
|
def get_form_title(self):
|
||||||
"""Default implementation - return the ajax_form_title variable"""
|
"""Default implementation - return the ajax_form_title variable."""
|
||||||
return self.ajax_form_title
|
return self.ajax_form_title
|
||||||
|
|
||||||
def get_param(self, name, method='GET'):
|
def get_param(self, name, method='GET'):
|
||||||
@ -230,7 +230,7 @@ class AjaxMixin(InvenTreeRoleMixin):
|
|||||||
return self.request.GET.get(name, None)
|
return self.request.GET.get(name, None)
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
"""Get extra context data (default implementation is empty dict)
|
"""Get extra context data (default implementation is empty dict).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict object (empty)
|
dict object (empty)
|
||||||
@ -444,6 +444,9 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
|
|||||||
Args:
|
Args:
|
||||||
object - The current object, to be updated
|
object - The current object, to be updated
|
||||||
form - The validated form
|
form - The validated form
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
object instance for supplied form
|
||||||
"""
|
"""
|
||||||
self.object = form.save()
|
self.object = form.save()
|
||||||
|
|
||||||
|
@ -340,9 +340,12 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_overdue(self):
|
def is_overdue(self):
|
||||||
"""Returns true if this build is "overdue":
|
"""Returns true if this build is "overdue".
|
||||||
|
|
||||||
Makes use of the OVERDUE_FILTER to avoid code duplication
|
Makes use of the OVERDUE_FILTER to avoid code duplication
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: Is the build overdue
|
||||||
"""
|
"""
|
||||||
query = Build.objects.filter(pk=self.pk)
|
query = Build.objects.filter(pk=self.pk)
|
||||||
query = query.filter(Build.OVERDUE_FILTER)
|
query = query.filter(Build.OVERDUE_FILTER)
|
||||||
@ -574,9 +577,9 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
|||||||
def unallocateStock(self, bom_item=None, output=None):
|
def unallocateStock(self, bom_item=None, output=None):
|
||||||
"""Unallocate stock from this Build.
|
"""Unallocate stock from this Build.
|
||||||
|
|
||||||
Arguments:
|
Args:
|
||||||
- bom_item: Specify a particular BomItem to unallocate stock against
|
bom_item: Specify a particular BomItem to unallocate stock against
|
||||||
- output: Specify a particular StockItem (output) to unallocate stock against
|
output: Specify a particular StockItem (output) to unallocate stock against
|
||||||
"""
|
"""
|
||||||
allocations = BuildItem.objects.filter(
|
allocations = BuildItem.objects.filter(
|
||||||
build=self,
|
build=self,
|
||||||
@ -693,8 +696,9 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
|||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def delete_output(self, output):
|
def delete_output(self, output):
|
||||||
"""Remove a build output from the database:
|
"""Remove a build output from the database.
|
||||||
|
|
||||||
|
Executes:
|
||||||
- Unallocate any build items against the output
|
- Unallocate any build items against the output
|
||||||
- Delete the output StockItem
|
- Delete the output StockItem
|
||||||
"""
|
"""
|
||||||
@ -882,8 +886,8 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
|||||||
"""Get the quantity of a part required to complete the particular build output.
|
"""Get the quantity of a part required to complete the particular build output.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
part: The Part object
|
bom_item: The Part object
|
||||||
output - The particular build output (StockItem)
|
output: The particular build output (StockItem)
|
||||||
"""
|
"""
|
||||||
quantity = bom_item.quantity
|
quantity = bom_item.quantity
|
||||||
|
|
||||||
@ -895,14 +899,14 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
|||||||
return quantity
|
return quantity
|
||||||
|
|
||||||
def allocated_bom_items(self, bom_item, output=None):
|
def allocated_bom_items(self, bom_item, output=None):
|
||||||
"""Return all BuildItem objects which allocate stock of <bom_item> to <output>
|
"""Return all BuildItem objects which allocate stock of <bom_item> to <output>.
|
||||||
|
|
||||||
Note that the bom_item may allow variants, or direct substitutes,
|
Note that the bom_item may allow variants, or direct substitutes,
|
||||||
making things difficult.
|
making things difficult.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
bom_item - The BomItem object
|
bom_item: The BomItem object
|
||||||
output - Build output (StockItem).
|
output: Build output (StockItem).
|
||||||
"""
|
"""
|
||||||
allocations = BuildItem.objects.filter(
|
allocations = BuildItem.objects.filter(
|
||||||
build=self,
|
build=self,
|
||||||
@ -991,7 +995,7 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def required_parts(self):
|
def required_parts(self):
|
||||||
"""Returns a list of parts required to build this part (BOM)"""
|
"""Returns a list of parts required to build this part (BOM)."""
|
||||||
parts = []
|
parts = []
|
||||||
|
|
||||||
for item in self.bom_items:
|
for item in self.bom_items:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from ctypes import Union
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -193,12 +194,12 @@ class BuildTest(BuildTestBase):
|
|||||||
quantity=99
|
quantity=99
|
||||||
)
|
)
|
||||||
|
|
||||||
def allocate_stock(self, output, allocations):
|
def allocate_stock(self, output: Union[StockItem, None], allocations: dict[StockItem, int]) -> None:
|
||||||
"""Allocate stock to this build, against a particular output.
|
"""Allocate stock to this build, against a particular output.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
output - StockItem object (or None)
|
output (Union[StockItem, None]): StockItem object or None
|
||||||
allocations - Map of {StockItem: quantity}
|
allocations (dict[StockItem, int]): Map of `{StockItem: quantity}`
|
||||||
"""
|
"""
|
||||||
for item, quantity in allocations.items():
|
for item, quantity in allocations.items():
|
||||||
BuildItem.objects.create(
|
BuildItem.objects.create(
|
||||||
|
@ -94,8 +94,11 @@ class FileManager:
|
|||||||
"""Try to match a header (from the file) to a list of known headers.
|
"""Try to match a header (from the file) to a list of known headers.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
header - Header name to look for
|
header (Any): Header name to look for
|
||||||
threshold - Match threshold for fuzzy search
|
threshold (int, optional): Match threshold for fuzzy search. Defaults to 80.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: Matched headers
|
||||||
"""
|
"""
|
||||||
# Replace null values with empty string
|
# Replace null values with empty string
|
||||||
if header is None:
|
if header is None:
|
||||||
|
@ -477,7 +477,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def choices(self):
|
def choices(self):
|
||||||
"""Return the available choices for this setting (or None if no choices are defined)"""
|
"""Return the available choices for this setting (or None if no choices are defined)."""
|
||||||
return self.__class__.get_setting_choices(self.key, **self.get_kwargs())
|
return self.__class__.get_setting_choices(self.key, **self.get_kwargs())
|
||||||
|
|
||||||
def valid_options(self):
|
def valid_options(self):
|
||||||
@ -1519,7 +1519,7 @@ class PriceBreak(models.Model):
|
|||||||
"""Convert the unit-price at this price break to the specified currency code.
|
"""Convert the unit-price at this price break to the specified currency code.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
currency_code - The currency code to convert to (e.g "USD" or "AUD")
|
currency_code: The currency code to convert to (e.g "USD" or "AUD")
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
converted = convert_money(self.price, currency_code)
|
converted = convert_money(self.price, currency_code)
|
||||||
|
@ -231,7 +231,7 @@ class Company(models.Model):
|
|||||||
return self.purchase_orders.filter(status__in=PurchaseOrderStatus.OPEN)
|
return self.purchase_orders.filter(status__in=PurchaseOrderStatus.OPEN)
|
||||||
|
|
||||||
def pending_purchase_orders(self):
|
def pending_purchase_orders(self):
|
||||||
"""Return purchase orders which are PENDING (not yet issued)"""
|
"""Return purchase orders which are PENDING (not yet issued)."""
|
||||||
return self.purchase_orders.filter(status=PurchaseOrderStatus.PENDING)
|
return self.purchase_orders.filter(status=PurchaseOrderStatus.PENDING)
|
||||||
|
|
||||||
def closed_purchase_orders(self):
|
def closed_purchase_orders(self):
|
||||||
@ -598,12 +598,12 @@ class SupplierPart(models.Model):
|
|||||||
def unit_pricing(self):
|
def unit_pricing(self):
|
||||||
return self.get_price(1)
|
return self.get_price(1)
|
||||||
|
|
||||||
def add_price_break(self, quantity, price):
|
def add_price_break(self, quantity, price) -> None:
|
||||||
"""Create a new price break for this part.
|
"""Create a new price break for this part.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
quantity - Numerical quantity
|
quantity: Numerical quantity
|
||||||
price - Must be a Money object
|
price: Must be a Money object
|
||||||
"""
|
"""
|
||||||
# Check if a price break at that quantity already exists...
|
# Check if a price break at that quantity already exists...
|
||||||
if self.price_breaks.filter(quantity=quantity, part=self.pk).exists():
|
if self.price_breaks.filter(quantity=quantity, part=self.pk).exists():
|
||||||
|
@ -329,16 +329,24 @@ class PurchaseOrder(Order):
|
|||||||
return reverse('po-detail', kwargs={'pk': self.id})
|
return reverse('po-detail', kwargs={'pk': self.id})
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def add_line_item(self, supplier_part, quantity, group=True, reference='', purchase_price=None):
|
def add_line_item(self, supplier_part, quantity, group: bool = True, reference: str = '', purchase_price=None):
|
||||||
"""Add a new line item to this purchase order. This function will check that:
|
"""Add a new line item to this purchase order.
|
||||||
|
|
||||||
|
This function will check that:
|
||||||
* The supplier part matches the supplier specified for this purchase order
|
* The supplier part matches the supplier specified for this purchase order
|
||||||
* The quantity is greater than zero
|
* The quantity is greater than zero
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
supplier_part - The supplier_part to add
|
supplier_part: The supplier_part to add
|
||||||
quantity - The number of items to add
|
quantity : The number of items to add
|
||||||
group - If True, this new quantity will be added to an existing line item for the same supplier_part (if it exists)
|
group (bool, optional): If True, this new quantity will be added to an existing line item for the same supplier_part (if it exists). Defaults to True.
|
||||||
|
reference (str, optional): Reference to item. Defaults to ''.
|
||||||
|
purchase_price (optional): Price of item. Defaults to None.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValidationError: quantity is smaller than 0
|
||||||
|
ValidationError: quantity is not type int
|
||||||
|
ValidationError: supplier is not supplier of purchase order
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
quantity = int(quantity)
|
quantity = int(quantity)
|
||||||
@ -416,7 +424,11 @@ class PurchaseOrder(Order):
|
|||||||
return query.exists()
|
return query.exists()
|
||||||
|
|
||||||
def can_cancel(self):
|
def can_cancel(self):
|
||||||
"""A PurchaseOrder can only be cancelled under the following circumstances:"""
|
"""A PurchaseOrder can only be cancelled under the following circumstances.
|
||||||
|
|
||||||
|
- Status is PLACED
|
||||||
|
- Status is PENDING
|
||||||
|
"""
|
||||||
return self.status in [
|
return self.status in [
|
||||||
PurchaseOrderStatus.PLACED,
|
PurchaseOrderStatus.PLACED,
|
||||||
PurchaseOrderStatus.PENDING
|
PurchaseOrderStatus.PENDING
|
||||||
@ -655,7 +667,7 @@ class SalesOrder(Order):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_overdue(self):
|
def is_overdue(self):
|
||||||
"""Returns true if this SalesOrder is "overdue":
|
"""Returns true if this SalesOrder is "overdue".
|
||||||
|
|
||||||
Makes use of the OVERDUE_FILTER to avoid code duplication.
|
Makes use of the OVERDUE_FILTER to avoid code duplication.
|
||||||
"""
|
"""
|
||||||
@ -692,7 +704,7 @@ class SalesOrder(Order):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def is_completed(self):
|
def is_completed(self):
|
||||||
"""Check if this order is "shipped" (all line items delivered)"""
|
"""Check if this order is "shipped" (all line items delivered)."""
|
||||||
return self.lines.count() > 0 and all([line.is_completed() for line in self.lines.all()])
|
return self.lines.count() > 0 and all([line.is_completed() for line in self.lines.all()])
|
||||||
|
|
||||||
def can_complete(self, raise_error=False):
|
def can_complete(self, raise_error=False):
|
||||||
@ -749,8 +761,9 @@ class SalesOrder(Order):
|
|||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def cancel_order(self):
|
def cancel_order(self):
|
||||||
"""Cancel this order (only if it is "pending")
|
"""Cancel this order (only if it is "pending").
|
||||||
|
|
||||||
|
Executes:
|
||||||
- Mark the order as 'cancelled'
|
- Mark the order as 'cancelled'
|
||||||
- Delete any StockItems which have been allocated
|
- Delete any StockItems which have been allocated
|
||||||
"""
|
"""
|
||||||
@ -1110,7 +1123,7 @@ class SalesOrderLineItem(OrderLineItem):
|
|||||||
return self.allocated_quantity() > self.quantity
|
return self.allocated_quantity() > self.quantity
|
||||||
|
|
||||||
def is_completed(self):
|
def is_completed(self):
|
||||||
"""Return True if this line item is completed (has been fully shipped)"""
|
"""Return True if this line item is completed (has been fully shipped)."""
|
||||||
return self.shipped >= self.quantity
|
return self.shipped >= self.quantity
|
||||||
|
|
||||||
|
|
||||||
@ -1222,8 +1235,9 @@ class SalesOrderShipment(models.Model):
|
|||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def complete_shipment(self, user, **kwargs):
|
def complete_shipment(self, user, **kwargs):
|
||||||
"""Complete this particular shipment:
|
"""Complete this particular shipment.
|
||||||
|
|
||||||
|
Executes:
|
||||||
1. Update any stock items associated with this shipment
|
1. Update any stock items associated with this shipment
|
||||||
2. Update the "shipped" quantity of all associated line items
|
2. Update the "shipped" quantity of all associated line items
|
||||||
3. Set the "shipment_date" to now
|
3. Set the "shipment_date" to now
|
||||||
@ -1295,8 +1309,9 @@ class SalesOrderAllocation(models.Model):
|
|||||||
return reverse('api-so-allocation-list')
|
return reverse('api-so-allocation-list')
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Validate the SalesOrderAllocation object:
|
"""Validate the SalesOrderAllocation object.
|
||||||
|
|
||||||
|
Executes:
|
||||||
- Cannot allocate stock to a line item without a part reference
|
- Cannot allocate stock to a line item without a part reference
|
||||||
- The referenced part must match the part associated with the line item
|
- The referenced part must match the part associated with the line item
|
||||||
- Allocated quantity cannot exceed the quantity of the stock item
|
- Allocated quantity cannot exceed the quantity of the stock item
|
||||||
@ -1385,8 +1400,9 @@ class SalesOrderAllocation(models.Model):
|
|||||||
return self.item.purchase_order
|
return self.item.purchase_order
|
||||||
|
|
||||||
def complete_allocation(self, user):
|
def complete_allocation(self, user):
|
||||||
"""Complete this allocation (called when the parent SalesOrder is marked as "shipped"):
|
"""Complete this allocation (called when the parent SalesOrder is marked as "shipped").
|
||||||
|
|
||||||
|
Executes:
|
||||||
- Determine if the referenced StockItem needs to be "split" (if allocated quantity != stock quantity)
|
- Determine if the referenced StockItem needs to be "split" (if allocated quantity != stock quantity)
|
||||||
- Mark the StockItem as belonging to the Customer (this will remove it from stock)
|
- Mark the StockItem as belonging to the Customer (this will remove it from stock)
|
||||||
"""
|
"""
|
||||||
|
@ -11,7 +11,7 @@ from company.models import ManufacturerPart, SupplierPart
|
|||||||
from InvenTree.helpers import DownloadFile, GetExportFormats, normalize
|
from InvenTree.helpers import DownloadFile, GetExportFormats, normalize
|
||||||
|
|
||||||
from .admin import BomItemResource
|
from .admin import BomItemResource
|
||||||
from .models import BomItem
|
from .models import BomItem, Part
|
||||||
|
|
||||||
|
|
||||||
def IsValidBOMFormat(fmt):
|
def IsValidBOMFormat(fmt):
|
||||||
@ -20,7 +20,7 @@ def IsValidBOMFormat(fmt):
|
|||||||
|
|
||||||
|
|
||||||
def MakeBomTemplate(fmt):
|
def MakeBomTemplate(fmt):
|
||||||
"""Generate a Bill of Materials upload template file (for user download)"""
|
"""Generate a Bill of Materials upload template file (for user download)."""
|
||||||
fmt = fmt.strip().lower()
|
fmt = fmt.strip().lower()
|
||||||
|
|
||||||
if not IsValidBOMFormat(fmt):
|
if not IsValidBOMFormat(fmt):
|
||||||
@ -42,12 +42,21 @@ def MakeBomTemplate(fmt):
|
|||||||
return DownloadFile(data, filename)
|
return DownloadFile(data, filename)
|
||||||
|
|
||||||
|
|
||||||
def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=False, stock_data=False, supplier_data=False, manufacturer_data=False):
|
def ExportBom(part: Part, fmt='csv', cascade: bool = False, max_levels: int = None, parameter_data=False, stock_data=False, supplier_data=False, manufacturer_data=False):
|
||||||
"""Export a BOM (Bill of Materials) for a given part.
|
"""Export a BOM (Bill of Materials) for a given part.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
fmt: File format (default = 'csv')
|
part (Part): Part for which the BOM should be exported
|
||||||
cascade: If True, multi-level BOM output is supported. Otherwise, a flat top-level-only BOM is exported.
|
fmt (str, optional): file format. Defaults to 'csv'.
|
||||||
|
cascade (bool, optional): If True, multi-level BOM output is supported. Otherwise, a flat top-level-only BOM is exported.. Defaults to False.
|
||||||
|
max_levels (int, optional): Levels of items that should be included. None for np sublevels. Defaults to None.
|
||||||
|
parameter_data (bool, optional): Additonal data that should be added. Defaults to False.
|
||||||
|
stock_data (bool, optional): Additonal data that should be added. Defaults to False.
|
||||||
|
supplier_data (bool, optional): Additonal data that should be added. Defaults to False.
|
||||||
|
manufacturer_data (bool, optional): Additonal data that should be added. Defaults to False.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
StreamingHttpResponse: Response that can be passed to the endpoint
|
||||||
"""
|
"""
|
||||||
if not IsValidBOMFormat(fmt):
|
if not IsValidBOMFormat(fmt):
|
||||||
fmt = 'csv'
|
fmt = 'csv'
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
"""Part database model definitions."""
|
"""Part database model definitions."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import decimal
|
import decimal
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
@ -110,11 +112,14 @@ class PartCategory(MetadataMixin, InvenTreeTree):
|
|||||||
verbose_name = _("Part Category")
|
verbose_name = _("Part Category")
|
||||||
verbose_name_plural = _("Part Categories")
|
verbose_name_plural = _("Part Categories")
|
||||||
|
|
||||||
def get_parts(self, cascade=True):
|
def get_parts(self, cascade=True) -> set[Part]:
|
||||||
"""Return a queryset for all parts under this category.
|
"""Return a queryset for all parts under this category.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
cascade - If True, also look under subcategories (default = True)
|
cascade (bool, optional): If True, also look under subcategories. Defaults to True.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
set[Part]: All matching parts
|
||||||
"""
|
"""
|
||||||
if cascade:
|
if cascade:
|
||||||
"""Select any parts which exist in this category or any child categories."""
|
"""Select any parts which exist in this category or any child categories."""
|
||||||
@ -129,7 +134,7 @@ class PartCategory(MetadataMixin, InvenTreeTree):
|
|||||||
return self.partcount()
|
return self.partcount()
|
||||||
|
|
||||||
def partcount(self, cascade=True, active=False):
|
def partcount(self, cascade=True, active=False):
|
||||||
"""Return the total part count under this category (including children of child categories)"""
|
"""Return the total part count under this category (including children of child categories)."""
|
||||||
query = self.get_parts(cascade=cascade)
|
query = self.get_parts(cascade=cascade)
|
||||||
|
|
||||||
if active:
|
if active:
|
||||||
@ -1071,8 +1076,9 @@ class Part(MetadataMixin, MPTTModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def net_stock(self):
|
def net_stock(self):
|
||||||
"""Return the 'net' stock. It takes into account:
|
"""Return the 'net' stock.
|
||||||
|
|
||||||
|
It takes into account:
|
||||||
- Stock on hand (total_stock)
|
- Stock on hand (total_stock)
|
||||||
- Stock on order (on_order)
|
- Stock on order (on_order)
|
||||||
- Stock allocated (allocation_count)
|
- Stock allocated (allocation_count)
|
||||||
@ -1370,12 +1376,12 @@ class Part(MetadataMixin, MPTTModel):
|
|||||||
|
|
||||||
return queryset.prefetch_related('sub_part')
|
return queryset.prefetch_related('sub_part')
|
||||||
|
|
||||||
def get_installed_part_options(self, include_inherited=True, include_variants=True):
|
def get_installed_part_options(self, include_inherited: bool = True, include_variants: bool = True):
|
||||||
"""Return a set of all Parts which can be "installed" into this part, based on the BOM.
|
"""Return a set of all Parts which can be "installed" into this part, based on the BOM.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
include_inherited - If set, include BomItem entries defined for parent parts
|
include_inherited (bool): If set, include BomItem entries defined for parent parts
|
||||||
include_variants - If set, include variant parts for BomItems which allow variants
|
include_variants (bool): If set, include variant parts for BomItems which allow variants
|
||||||
"""
|
"""
|
||||||
parts = set()
|
parts = set()
|
||||||
|
|
||||||
@ -1480,7 +1486,7 @@ class Part(MetadataMixin, MPTTModel):
|
|||||||
return str(result_hash.digest())
|
return str(result_hash.digest())
|
||||||
|
|
||||||
def is_bom_valid(self):
|
def is_bom_valid(self):
|
||||||
"""Check if the BOM is 'valid' - if the calculated checksum matches the stored value"""
|
"""Check if the BOM is 'valid' - if the calculated checksum matches the stored value."""
|
||||||
return self.get_bom_hash() == self.bom_checksum or not self.has_bom
|
return self.get_bom_hash() == self.bom_checksum or not self.has_bom
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@ -1728,8 +1734,8 @@ class Part(MetadataMixin, MPTTModel):
|
|||||||
"""Create a new price break for this part.
|
"""Create a new price break for this part.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
quantity - Numerical quantity
|
quantity: Numerical quantity
|
||||||
price - Must be a Money object
|
price: Must be a Money object
|
||||||
"""
|
"""
|
||||||
# Check if a price break at that quantity already exists...
|
# Check if a price break at that quantity already exists...
|
||||||
if self.price_breaks.filter(quantity=quantity, part=self.pk).exists():
|
if self.price_breaks.filter(quantity=quantity, part=self.pk).exists():
|
||||||
@ -1774,8 +1780,8 @@ class Part(MetadataMixin, MPTTModel):
|
|||||||
"""Copy the BOM from another part.
|
"""Copy the BOM from another part.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
other - The part to copy the BOM from
|
other: The part to copy the BOM from
|
||||||
clear - Remove existing BOM items first (default=True)
|
clear (bool, optional): Remove existing BOM items first. Defaults to True.
|
||||||
"""
|
"""
|
||||||
# Ignore if the other part is actually this part?
|
# Ignore if the other part is actually this part?
|
||||||
if other == self:
|
if other == self:
|
||||||
@ -2017,10 +2023,9 @@ class Part(MetadataMixin, MPTTModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def can_convert(self):
|
def can_convert(self):
|
||||||
"""Check if this Part can be "converted" to a different variant:
|
"""Check if this Part can be "converted" to a different variant.
|
||||||
|
|
||||||
It can be converted if:
|
It can be converted if:
|
||||||
|
|
||||||
a) It has non-virtual variant parts underneath it
|
a) It has non-virtual variant parts underneath it
|
||||||
b) It has non-virtual template parts above it
|
b) It has non-virtual template parts above it
|
||||||
c) It has non-virtual sibling variants
|
c) It has non-virtual sibling variants
|
||||||
@ -2064,8 +2069,9 @@ class Part(MetadataMixin, MPTTModel):
|
|||||||
return filtered_parts
|
return filtered_parts
|
||||||
|
|
||||||
def get_related_parts(self):
|
def get_related_parts(self):
|
||||||
"""Return list of tuples for all related parts:
|
"""Return list of tuples for all related parts.
|
||||||
|
|
||||||
|
Includes:
|
||||||
- first value is PartRelated object
|
- first value is PartRelated object
|
||||||
- second value is matching Part object
|
- second value is matching Part object
|
||||||
"""
|
"""
|
||||||
@ -2408,7 +2414,7 @@ class PartCategoryParameterTemplate(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""String representation of a PartCategoryParameterTemplate (admin interface)"""
|
"""String representation of a PartCategoryParameterTemplate (admin interface)."""
|
||||||
if self.default_value:
|
if self.default_value:
|
||||||
return f'{self.category.name} | {self.parameter_template.name} | {self.default_value}'
|
return f'{self.category.name} | {self.parameter_template.name} | {self.default_value}'
|
||||||
else:
|
else:
|
||||||
@ -2489,11 +2495,12 @@ class BomItem(models.Model, DataImportMixin):
|
|||||||
return reverse('api-bom-list')
|
return reverse('api-bom-list')
|
||||||
|
|
||||||
def get_valid_parts_for_allocation(self, allow_variants=True, allow_substitutes=True):
|
def get_valid_parts_for_allocation(self, allow_variants=True, allow_substitutes=True):
|
||||||
"""Return a list of valid parts which can be allocated against this BomItem:
|
"""Return a list of valid parts which can be allocated against this BomItem.
|
||||||
|
|
||||||
- Include the referenced sub_part
|
Includes:
|
||||||
- Include any directly specvified substitute parts
|
- The referenced sub_part
|
||||||
- If allow_variants is True, allow all variants of sub_part
|
- Any directly specvified substitute parts
|
||||||
|
- If allow_variants is True, all variants of sub_part
|
||||||
"""
|
"""
|
||||||
# Set of parts we will allow
|
# Set of parts we will allow
|
||||||
parts = set()
|
parts = set()
|
||||||
@ -2591,10 +2598,9 @@ class BomItem(models.Model, DataImportMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_item_hash(self):
|
def get_item_hash(self):
|
||||||
"""Calculate the checksum hash of this BOM line item:
|
"""Calculate the checksum hash of this BOM line item.
|
||||||
|
|
||||||
The hash is calculated from the following fields:
|
The hash is calculated from the following fields:
|
||||||
|
|
||||||
- Part.full_name (if the part name changes, the BOM checksum is invalidated)
|
- Part.full_name (if the part name changes, the BOM checksum is invalidated)
|
||||||
- Quantity
|
- Quantity
|
||||||
- Reference field
|
- Reference field
|
||||||
@ -2794,8 +2800,9 @@ class BomItemSubstitute(models.Model):
|
|||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def validate_unique(self, exclude=None):
|
def validate_unique(self, exclude=None):
|
||||||
"""Ensure that this BomItemSubstitute is "unique":
|
"""Ensure that this BomItemSubstitute is "unique".
|
||||||
|
|
||||||
|
Ensure:
|
||||||
- It cannot point to the same "part" as the "sub_part" of the parent "bom_item"
|
- It cannot point to the same "part" as the "sub_part" of the parent "bom_item"
|
||||||
"""
|
"""
|
||||||
super().validate_unique(exclude=exclude)
|
super().validate_unique(exclude=exclude)
|
||||||
@ -2830,7 +2837,7 @@ class BomItemSubstitute(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class PartRelated(models.Model):
|
class PartRelated(models.Model):
|
||||||
"""Store and handle related parts (eg. mating connector, crimps, etc.)"""
|
"""Store and handle related parts (eg. mating connector, crimps, etc.)."""
|
||||||
|
|
||||||
part_1 = models.ForeignKey(Part, related_name='related_parts_1',
|
part_1 = models.ForeignKey(Part, related_name='related_parts_1',
|
||||||
verbose_name=_('Part 1'), on_delete=models.DO_NOTHING)
|
verbose_name=_('Part 1'), on_delete=models.DO_NOTHING)
|
||||||
|
@ -52,7 +52,7 @@ class BarcodeMixin:
|
|||||||
"""Initialize the BarcodePlugin instance.
|
"""Initialize the BarcodePlugin instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
barcode_data - The raw barcode data
|
barcode_data: The raw barcode data
|
||||||
"""
|
"""
|
||||||
self.data = barcode_data
|
self.data = barcode_data
|
||||||
|
|
||||||
|
@ -10,16 +10,17 @@ from plugin.registry import registry
|
|||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
def print_label(plugin_slug, label_image, label_instance=None, user=None):
|
def print_label(plugin_slug: str, label_image, label_instance=None, user=None):
|
||||||
"""Print label with the provided plugin.
|
"""Print label with the provided plugin.
|
||||||
|
|
||||||
This task is nominally handled by the background worker.
|
This task is nominally handled by the background worker.
|
||||||
|
|
||||||
If the printing fails (throws an exception) then the user is notified.
|
If the printing fails (throws an exception) then the user is notified.
|
||||||
|
|
||||||
Arguments:
|
Args:
|
||||||
plugin_slug: The unique slug (key) of the plugin
|
plugin_slug (str): The unique slug (key) of the plugin
|
||||||
label_image: A PIL.Image image object to be printed
|
label_image (_type_): A PIL.Image image object to be printed
|
||||||
|
label_instance (Union[LabelTemplate, None], optional): The template instance that should be printed. Defaults to None.
|
||||||
|
user (Union[User, None], optional): User that should be informed of errors. Defaults to None.
|
||||||
"""
|
"""
|
||||||
logger.info(f"Plugin '{plugin_slug}' is printing a label")
|
logger.info(f"Plugin '{plugin_slug}' is printing a label")
|
||||||
|
|
||||||
|
@ -46,13 +46,13 @@ class MetadataMixin(models.Model):
|
|||||||
|
|
||||||
return self.metadata.get(key, backup_value)
|
return self.metadata.get(key, backup_value)
|
||||||
|
|
||||||
def set_metadata(self, key: str, data, commit=True):
|
def set_metadata(self, key: str, data, commit: bool = True):
|
||||||
"""Save the provided metadata under the provided key.
|
"""Save the provided metadata under the provided key.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: String key for saving metadata
|
key (str): Key for saving metadata
|
||||||
data: Data object to save - must be able to be rendered as a JSON string
|
data (Any): Data object to save - must be able to be rendered as a JSON string
|
||||||
overwrite: If true, existing metadata with the provided key will be overwritten. If false, a merge will be attempted
|
commit (bool, optional): If true, existing metadata with the provided key will be overwritten. If false, a merge will be attempted. Defaults to True.
|
||||||
"""
|
"""
|
||||||
if self.metadata is None:
|
if self.metadata is None:
|
||||||
# Handle a null field value
|
# Handle a null field value
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
"""Stock database model definitions."""
|
"""Stock database model definitions."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
@ -129,8 +131,12 @@ class StockLocation(MetadataMixin, InvenTreeTree):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def barcode(self):
|
def barcode(self) -> str:
|
||||||
"""Brief payload data (e.g. for labels)"""
|
"""Get Brief payload data (e.g. for labels).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Brief pyload data
|
||||||
|
"""
|
||||||
return self.format_barcode(brief=True)
|
return self.format_barcode(brief=True)
|
||||||
|
|
||||||
def get_stock_items(self, cascade=True):
|
def get_stock_items(self, cascade=True):
|
||||||
@ -335,8 +341,9 @@ class StockItem(MetadataMixin, MPTTModel):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""Save this StockItem to the database. Performs a number of checks:
|
"""Save this StockItem to the database.
|
||||||
|
|
||||||
|
Performs a number of checks:
|
||||||
- Unique serial number requirement
|
- Unique serial number requirement
|
||||||
- Adds a transaction note when the item is first created.
|
- Adds a transaction note when the item is first created.
|
||||||
"""
|
"""
|
||||||
@ -433,10 +440,9 @@ class StockItem(MetadataMixin, MPTTModel):
|
|||||||
raise ValidationError({"serial": _("StockItem with this serial number already exists")})
|
raise ValidationError({"serial": _("StockItem with this serial number already exists")})
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Validate the StockItem object (separate to field validation)
|
"""Validate the StockItem object (separate to field validation).
|
||||||
|
|
||||||
The following validation checks are performed:
|
The following validation checks are performed:
|
||||||
|
|
||||||
- The 'part' and 'supplier_part.part' fields cannot point to the same Part object
|
- The 'part' and 'supplier_part.part' fields cannot point to the same Part object
|
||||||
- The 'part' does not belong to itself
|
- The 'part' does not belong to itself
|
||||||
- Quantity must be 1 if the StockItem has a serial number
|
- Quantity must be 1 if the StockItem has a serial number
|
||||||
@ -558,7 +564,11 @@ class StockItem(MetadataMixin, MPTTModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def barcode(self):
|
def barcode(self):
|
||||||
"""Brief payload data (e.g. for labels)"""
|
"""Get Brief payload data (e.g. for labels).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Brief pyload data
|
||||||
|
"""
|
||||||
return self.format_barcode(brief=True)
|
return self.format_barcode(brief=True)
|
||||||
|
|
||||||
uid = models.CharField(blank=True, max_length=128, help_text=("Unique identifier field"))
|
uid = models.CharField(blank=True, max_length=128, help_text=("Unique identifier field"))
|
||||||
@ -825,8 +835,9 @@ class StockItem(MetadataMixin, MPTTModel):
|
|||||||
return self.expiry_date < today
|
return self.expiry_date < today
|
||||||
|
|
||||||
def clearAllocations(self):
|
def clearAllocations(self):
|
||||||
"""Clear all order allocations for this StockItem:
|
"""Clear all order allocations for this StockItem.
|
||||||
|
|
||||||
|
Clears:
|
||||||
- SalesOrder allocations
|
- SalesOrder allocations
|
||||||
- Build allocations
|
- Build allocations
|
||||||
"""
|
"""
|
||||||
@ -966,8 +977,9 @@ class StockItem(MetadataMixin, MPTTModel):
|
|||||||
return max(self.quantity - self.allocation_count(), 0)
|
return max(self.quantity - self.allocation_count(), 0)
|
||||||
|
|
||||||
def can_delete(self):
|
def can_delete(self):
|
||||||
"""Can this stock item be deleted? It can NOT be deleted under the following circumstances:
|
"""Can this stock item be deleted?
|
||||||
|
|
||||||
|
It can NOT be deleted under the following circumstances:
|
||||||
- Has installed stock items
|
- Has installed stock items
|
||||||
- Is installed inside another StockItem
|
- Is installed inside another StockItem
|
||||||
- It has been assigned to a SalesOrder
|
- It has been assigned to a SalesOrder
|
||||||
@ -981,13 +993,16 @@ class StockItem(MetadataMixin, MPTTModel):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_installed_items(self, cascade=False):
|
def get_installed_items(self, cascade: bool = False) -> set[StockItem]:
|
||||||
"""Return all stock items which are *installed* in this one!
|
"""Return all stock items which are *installed* in this one!
|
||||||
|
|
||||||
Args:
|
|
||||||
cascade - Include items which are installed in items which are installed in items
|
|
||||||
|
|
||||||
Note: This function is recursive, and may result in a number of database hits!
|
Note: This function is recursive, and may result in a number of database hits!
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cascade (bool, optional): Include items which are installed in items which are installed in items. Defaults to False.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
set[StockItem]: Sll stock items which are installed
|
||||||
"""
|
"""
|
||||||
installed = set()
|
installed = set()
|
||||||
|
|
||||||
@ -1157,15 +1172,14 @@ class StockItem(MetadataMixin, MPTTModel):
|
|||||||
def has_tracking_info(self):
|
def has_tracking_info(self):
|
||||||
return self.tracking_info_count > 0
|
return self.tracking_info_count > 0
|
||||||
|
|
||||||
def add_tracking_entry(self, entry_type, user, deltas=None, notes='', **kwargs):
|
def add_tracking_entry(self, entry_type: int, user: User, deltas: dict = None, notes: str = '', **kwargs):
|
||||||
"""Add a history tracking entry for this StockItem.
|
"""Add a history tracking entry for this StockItem.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entry_type - Integer code describing the "type" of historical action (see StockHistoryCode)
|
entry_type (int): Code describing the "type" of historical action (see StockHistoryCode)
|
||||||
user - The user performing this action
|
user (User): The user performing this action
|
||||||
deltas - A map of the changes made to the model
|
deltas (dict, optional): A map of the changes made to the model. Defaults to None.
|
||||||
notes - User notes associated with this tracking entry
|
notes (str, optional): URL associated with this tracking entry. Defaults to ''.
|
||||||
url - Optional URL associated with this tracking entry
|
|
||||||
"""
|
"""
|
||||||
if deltas is None:
|
if deltas is None:
|
||||||
deltas = {}
|
deltas = {}
|
||||||
@ -1915,7 +1929,7 @@ class StockItemAttachment(InvenTreeAttachment):
|
|||||||
|
|
||||||
|
|
||||||
class StockItemTracking(models.Model):
|
class StockItemTracking(models.Model):
|
||||||
"""Stock tracking entry - used for tracking history of a particular StockItem
|
"""Stock tracking entry - used for tracking history of a particular StockItem.
|
||||||
|
|
||||||
Note: 2021-05-11
|
Note: 2021-05-11
|
||||||
The legacy StockTrackingItem model contained very litle information about the "history" of the item.
|
The legacy StockTrackingItem model contained very litle information about the "history" of the item.
|
||||||
|
@ -348,12 +348,12 @@ def update_group_roles(group, debug=False):
|
|||||||
permissions_to_delete = set()
|
permissions_to_delete = set()
|
||||||
|
|
||||||
def add_model(name, action, allowed):
|
def add_model(name, action, allowed):
|
||||||
"""Add a new model to the pile:
|
"""Add a new model to the pile.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name - The name of the model e.g. part_part
|
name: The name of the model e.g. part_part
|
||||||
action - The permission action e.g. view
|
action: The permission action e.g. view
|
||||||
allowed - Whether or not the action is allowed
|
allowed: Whether or not the action is allowed
|
||||||
"""
|
"""
|
||||||
if action not in ['view', 'add', 'change', 'delete']: # pragma: no cover
|
if action not in ['view', 'add', 'change', 'delete']: # pragma: no cover
|
||||||
raise ValueError("Action {a} is invalid".format(a=action))
|
raise ValueError("Action {a} is invalid".format(a=action))
|
||||||
@ -400,7 +400,7 @@ def update_group_roles(group, debug=False):
|
|||||||
"""Find the permission object in the database, from the simplified permission string.
|
"""Find the permission object in the database, from the simplified permission string.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
permission_string - a simplified permission_string e.g. 'part.view_partcategory'
|
permission_string: a simplified permission_string e.g. 'part.view_partcategory'
|
||||||
|
|
||||||
Returns the permission object in the database associated with the permission string
|
Returns the permission object in the database associated with the permission string
|
||||||
"""
|
"""
|
||||||
@ -521,10 +521,11 @@ class Owner(models.Model):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_owners_matching_user(cls, user):
|
def get_owners_matching_user(cls, user):
|
||||||
"""Return all "owner" objects matching the provided user:
|
"""Return all "owner" objects matching the provided user.
|
||||||
|
|
||||||
A) An exact match for the user
|
Includes:
|
||||||
B) Any groups that the user is a part of
|
- An exact match for the user
|
||||||
|
- Any groups that the user is a part of
|
||||||
"""
|
"""
|
||||||
user_type = ContentType.objects.get(app_label='auth', model='user')
|
user_type = ContentType.objects.get(app_label='auth', model='user')
|
||||||
group_type = ContentType.objects.get(app_label='auth', model='group')
|
group_type = ContentType.objects.get(app_label='auth', model='group')
|
||||||
|
Loading…
Reference in New Issue
Block a user