mirror of
https://github.com/inventree/InvenTree.git
synced 2026-06-15 04:50:49 +00:00
Merge branch 'master' into generic-parameters
This commit is contained in:
@@ -7,6 +7,7 @@ import os
|
||||
import random
|
||||
import shutil
|
||||
import string
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
@@ -195,7 +196,13 @@ def load_config_data(set_cache: bool = False) -> map | None:
|
||||
cfg_file = get_config_file()
|
||||
|
||||
with open(cfg_file, encoding='utf-8') as cfg:
|
||||
data = yaml.safe_load(cfg)
|
||||
try:
|
||||
data = yaml.safe_load(cfg)
|
||||
except yaml.parser.ParserError as error:
|
||||
logger.error(
|
||||
"Error reading InvenTree configuration file '%s': %s", cfg_file, error
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Set the cache if requested
|
||||
if set_cache:
|
||||
@@ -223,6 +230,11 @@ def do_typecast(value, type, var_name=None):
|
||||
elif type is dict:
|
||||
value = to_dict(value)
|
||||
|
||||
# Special handling for boolean typecasting
|
||||
elif type is bool:
|
||||
val = is_true(value)
|
||||
return val
|
||||
|
||||
elif type is not None:
|
||||
# Try to typecast the value
|
||||
try:
|
||||
|
||||
@@ -2,30 +2,76 @@
|
||||
|
||||
import logging
|
||||
import re
|
||||
from hashlib import md5
|
||||
from typing import Optional
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import pint
|
||||
|
||||
_unit_registry = None
|
||||
|
||||
import structlog
|
||||
|
||||
from common.settings import get_global_setting, set_global_setting
|
||||
from InvenTree.cache import get_session_cache, set_session_cache
|
||||
|
||||
_UNIT_REG_CACHE_KEY = 'unit_registry_hash'
|
||||
_unit_registry = None
|
||||
_unit_registry_hash: str = ''
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
|
||||
# Disable log output for Pint library
|
||||
logging.getLogger('pint').setLevel(logging.ERROR)
|
||||
|
||||
|
||||
def get_unit_registry_hash():
|
||||
"""Return a hash representing the current state of the unit registry.
|
||||
|
||||
We use this to determine if we need to reload the unit registry,
|
||||
due to changes in the database.
|
||||
"""
|
||||
# Look in the session cache first (faster, and potentially newer)
|
||||
registry_hash = get_session_cache(_UNIT_REG_CACHE_KEY)
|
||||
|
||||
if registry_hash is None:
|
||||
registry_hash = get_global_setting(
|
||||
'_UNIT_REGISTRY_HASH', create=False, backup_value=''
|
||||
)
|
||||
|
||||
if registry_hash:
|
||||
set_session_cache(_UNIT_REG_CACHE_KEY, registry_hash)
|
||||
|
||||
return registry_hash
|
||||
|
||||
|
||||
def set_unit_registry_hash(registry_hash: str):
|
||||
"""Save the hash representing the current state of the unit registry.
|
||||
|
||||
Because most of the registry is static, we only need to consider the
|
||||
CustomUnit entries in the database.
|
||||
"""
|
||||
global _unit_registry_hash
|
||||
_unit_registry_hash = registry_hash
|
||||
|
||||
# Save to both the global settings and the session cache
|
||||
set_global_setting('_UNIT_REGISTRY_HASH', registry_hash)
|
||||
set_session_cache(_UNIT_REG_CACHE_KEY, registry_hash)
|
||||
|
||||
|
||||
def get_unit_registry():
|
||||
"""Return a custom instance of the Pint UnitRegistry."""
|
||||
global _unit_registry
|
||||
global _unit_registry_hash
|
||||
|
||||
# Cache the unit registry for speedier access
|
||||
if _unit_registry is None:
|
||||
return reload_unit_registry()
|
||||
|
||||
# Check if the unit registry has changed
|
||||
if _unit_registry_hash != get_unit_registry_hash():
|
||||
logger.info('Unit registry hash has changed, reloading unit registry')
|
||||
return reload_unit_registry()
|
||||
|
||||
return _unit_registry
|
||||
|
||||
|
||||
@@ -60,9 +106,16 @@ def reload_unit_registry():
|
||||
try:
|
||||
from common.models import CustomUnit
|
||||
|
||||
# Calculate a hash of all custom units
|
||||
hash_md5 = md5()
|
||||
|
||||
for cu in CustomUnit.objects.all():
|
||||
try:
|
||||
reg.define(cu.fmt_string())
|
||||
fmt = cu.fmt_string()
|
||||
reg.define(fmt)
|
||||
|
||||
hash_md5.update(fmt.encode('utf-8'))
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
'Failed to load custom unit: %s - %s', cu.fmt_string(), e
|
||||
@@ -71,6 +124,9 @@ def reload_unit_registry():
|
||||
# Once custom units are loaded, save registry
|
||||
_unit_registry = reg
|
||||
|
||||
# Update the unit registry hash
|
||||
set_unit_registry_hash(hash_md5.hexdigest())
|
||||
|
||||
except Exception:
|
||||
# Database is not ready, or CustomUnit model is not available
|
||||
pass
|
||||
|
||||
@@ -16,7 +16,7 @@ from django.conf import settings
|
||||
from django.contrib.staticfiles.storage import StaticFilesStorage
|
||||
from django.core.exceptions import FieldError, ValidationError
|
||||
from django.core.files.storage import default_storage
|
||||
from django.db.models.fields.files import ImageFieldFile
|
||||
from django.db.models.fields.files import FieldFile, ImageFieldFile
|
||||
from django.http import StreamingHttpResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -176,10 +176,14 @@ def constructPathString(path: list[str], max_chars: int = 250) -> str:
|
||||
return pathstring
|
||||
|
||||
|
||||
def getMediaUrl(file: StdImageFieldFile | ImageFieldFile, name: str | None = None):
|
||||
def getMediaUrl(
|
||||
file: FieldFile | ImageFieldFile | StdImageFieldFile, name: str | None = None
|
||||
):
|
||||
"""Return the qualified access path for the given file, under the media directory."""
|
||||
if not isinstance(file, StdImageFieldFile):
|
||||
raise ValueError('file_obj must be an instance of StdImageFieldFile')
|
||||
if not isinstance(file, (FieldFile, ImageFieldFile, StdImageFieldFile)):
|
||||
raise TypeError(
|
||||
'file must be one of FileField, ImageFileField, StdImageFieldFile'
|
||||
)
|
||||
if name is not None:
|
||||
file = regenerate_imagefile(file, name)
|
||||
if settings.STORAGE_TARGET == StorageBackends.S3:
|
||||
|
||||
@@ -702,7 +702,7 @@ class TestHelpers(TestCase):
|
||||
def testMediaUrl(self):
|
||||
"""Test getMediaUrl."""
|
||||
# Str should not work
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(TypeError):
|
||||
helpers.getMediaUrl('xx/yy.png') # type: ignore
|
||||
|
||||
# Correct usage
|
||||
|
||||
@@ -39,6 +39,7 @@ from InvenTree.serializers import (
|
||||
NotesFieldMixin,
|
||||
enable_filter,
|
||||
)
|
||||
from InvenTree.tasks import offload_task
|
||||
from stock.generators import generate_batch_code
|
||||
from stock.models import StockItem, StockLocation
|
||||
from stock.serializers import (
|
||||
@@ -51,6 +52,7 @@ from users.serializers import OwnerSerializer, UserSerializer
|
||||
|
||||
from .models import Build, BuildItem, BuildLine
|
||||
from .status_codes import BuildStatus
|
||||
from .tasks import consume_build_item, consume_build_line
|
||||
|
||||
|
||||
class BuildSerializer(
|
||||
@@ -1845,12 +1847,14 @@ class BuildConsumeSerializer(serializers.Serializer):
|
||||
|
||||
return data
|
||||
|
||||
@transaction.atomic
|
||||
def save(self):
|
||||
"""Perform the stock consumption step."""
|
||||
data = self.validated_data
|
||||
request = self.context.get('request')
|
||||
notes = data.get('notes', '')
|
||||
|
||||
# We may be passed either a list of BuildItem or BuildLine instances
|
||||
items = data.get('items', [])
|
||||
lines = data.get('lines', [])
|
||||
|
||||
@@ -1865,25 +1869,23 @@ class BuildConsumeSerializer(serializers.Serializer):
|
||||
# Instead, it gets consumed when the output is completed
|
||||
continue
|
||||
|
||||
build_item.complete_allocation(
|
||||
quantity=quantity,
|
||||
# Offload a background task to consume this BuildItem
|
||||
offload_task(
|
||||
consume_build_item,
|
||||
build_item.pk,
|
||||
quantity,
|
||||
notes=notes,
|
||||
user=request.user if request else None,
|
||||
user_id=request.user.pk if request else None,
|
||||
)
|
||||
|
||||
# Process the provided BuildLine objects
|
||||
for line in lines:
|
||||
build_line = line['build_line']
|
||||
|
||||
# In this case, perform full consumption of all allocated stock
|
||||
for item in build_line.allocations.all():
|
||||
# If the build item is tracked into an output, we do not consume now
|
||||
# Instead, it gets consumed when the output is completed
|
||||
if item.install_into:
|
||||
continue
|
||||
|
||||
item.complete_allocation(
|
||||
quantity=item.quantity,
|
||||
notes=notes,
|
||||
user=request.user if request else None,
|
||||
)
|
||||
# Offload a background task to consume this BuildLine
|
||||
offload_task(
|
||||
consume_build_line,
|
||||
build_line.pk,
|
||||
notes=notes,
|
||||
user_id=request.user.pk if request else None,
|
||||
)
|
||||
|
||||
@@ -39,6 +39,51 @@ def auto_allocate_build(build_id: int, **kwargs):
|
||||
build_order.auto_allocate_stock(**kwargs)
|
||||
|
||||
|
||||
@tracer.start_as_current_span('consume_build_item')
|
||||
def consume_build_item(
|
||||
item_id: str, quantity, notes: str = '', user_id: int | None = None
|
||||
):
|
||||
"""Consume stock against a particular BuildOrderLineItem allocation."""
|
||||
from build.models import BuildItem
|
||||
|
||||
item = BuildItem.objects.filter(pk=item_id).first()
|
||||
|
||||
if not item:
|
||||
logger.warning(
|
||||
'Could not consume stock for BuildItem <%s> - BuildItem does not exist',
|
||||
item_id,
|
||||
)
|
||||
return
|
||||
|
||||
item.complete_allocation(
|
||||
quantity=quantity,
|
||||
notes=notes,
|
||||
user=User.objects.filter(pk=user_id).first() if user_id else None,
|
||||
)
|
||||
|
||||
|
||||
@tracer.start_as_current_span('consume_build_line')
|
||||
def consume_build_line(line_id: int, notes: str = '', user_id: int | None = None):
|
||||
"""Consume stock against a particular BuildOrderLineItem."""
|
||||
from build.models import BuildLine
|
||||
|
||||
line_item = BuildLine.objects.filter(pk=line_id).first()
|
||||
|
||||
if not line_item:
|
||||
logger.warning(
|
||||
'Could not consume stock for LineItem <%s> - LineItem does not exist',
|
||||
line_id,
|
||||
)
|
||||
return
|
||||
|
||||
for item in line_item.allocations.all():
|
||||
item.complete_allocation(
|
||||
quantity=item.quantity,
|
||||
notes=notes,
|
||||
user=User.objects.filter(pk=user_id).first() if user_id else None,
|
||||
)
|
||||
|
||||
|
||||
@tracer.start_as_current_span('complete_build_allocations')
|
||||
def complete_build_allocations(build_id: int, user_id: int):
|
||||
"""Complete build allocations for a specified BuildOrder."""
|
||||
|
||||
@@ -2039,7 +2039,7 @@ class Attachment(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel
|
||||
if self.attachment:
|
||||
import InvenTree.helpers_model
|
||||
|
||||
media_url = InvenTree.helpers.getMediaUrl(self.attachment.url)
|
||||
media_url = InvenTree.helpers.getMediaUrl(self.attachment)
|
||||
return InvenTree.helpers_model.construct_absolute_url(media_url)
|
||||
|
||||
return ''
|
||||
|
||||
@@ -202,6 +202,21 @@ class AttachmentTest(InvenTreeAPITestCase):
|
||||
self.assignRole('part.delete')
|
||||
self.delete(url, expected_code=204)
|
||||
|
||||
def test_fully_qualified_url(self):
|
||||
"""Test that the fully qualified URL is returned correctly."""
|
||||
part = Part.objects.first()
|
||||
|
||||
attachment = Attachment.objects.create(
|
||||
attachment=self.generate_file('test.txt'),
|
||||
comment='Testing filename: test.txt',
|
||||
model_type='part',
|
||||
model_id=part.pk,
|
||||
)
|
||||
|
||||
url = attachment.fully_qualified_url()
|
||||
self.assertIs(type(url), str)
|
||||
self.assertIn(f'/media/attachments/part/{part.pk}/test', url)
|
||||
|
||||
|
||||
class SettingsTest(InvenTreeTestCase):
|
||||
"""Tests for the 'settings' model."""
|
||||
|
||||
@@ -309,13 +309,23 @@ class StatusCodeMixin:
|
||||
|
||||
return status is not None and status == self.get_custom_status()
|
||||
|
||||
def set_status(self, status: int) -> bool:
|
||||
"""Set the status code for this object."""
|
||||
def set_status(self, status: int, custom_values=None) -> bool:
|
||||
"""Set the status code for this object.
|
||||
|
||||
Arguments:
|
||||
status: The status code to set
|
||||
custom_values: Optional list of custom values to consider (can be used to avoid DB queries)
|
||||
"""
|
||||
if not self.status_class:
|
||||
raise NotImplementedError('Status class not defined')
|
||||
|
||||
base_values = self.status_class.values()
|
||||
custom_value_set = self.status_class.custom_values()
|
||||
|
||||
custom_value_set = (
|
||||
self.status_class.custom_values()
|
||||
if custom_values is None
|
||||
else custom_values
|
||||
)
|
||||
|
||||
custom_field = f'{self.STATUS_FIELD}_custom_key'
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -976,6 +976,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
# Prefetch line item objects for DB efficiency
|
||||
line_items_ids = [item['line_item'].pk for item in items]
|
||||
|
||||
# Cache the custom status options for the StockItem model
|
||||
custom_stock_status_values = stock.models.StockItem.STATUS_CLASS.custom_values()
|
||||
|
||||
line_items = PurchaseOrderLineItem.objects.filter(
|
||||
pk__in=line_items_ids
|
||||
).prefetch_related('part', 'part__part', 'order')
|
||||
@@ -1050,7 +1053,6 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
'supplier_part': supplier_part,
|
||||
'purchase_order': self,
|
||||
'purchase_price': purchase_price,
|
||||
'status': item.get('status', StockStatus.OK.value),
|
||||
'location': stock_location,
|
||||
'quantity': 1 if serialize else stock_quantity,
|
||||
'batch': item.get('batch_code', ''),
|
||||
@@ -1059,6 +1061,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
'packaging': item.get('packaging') or supplier_part.packaging,
|
||||
}
|
||||
|
||||
# Extract the "status" field
|
||||
status = item.get('status', StockStatus.OK.value)
|
||||
|
||||
# Check linked build order
|
||||
# This is for receiving against an *external* build order
|
||||
if build_order := line.build_order:
|
||||
@@ -1099,11 +1104,14 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
|
||||
# Now, create the new stock items
|
||||
if serialize:
|
||||
stock_items.extend(
|
||||
stock.models.StockItem._create_serial_numbers(
|
||||
serials=serials, **stock_data
|
||||
)
|
||||
new_items = stock.models.StockItem._create_serial_numbers(
|
||||
serials=serials, **stock_data
|
||||
)
|
||||
|
||||
for item in new_items:
|
||||
item.set_status(status, custom_values=custom_stock_status_values)
|
||||
stock_items.append(item)
|
||||
|
||||
else:
|
||||
new_item = stock.models.StockItem(
|
||||
**stock_data,
|
||||
@@ -1115,10 +1123,11 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
rght=2,
|
||||
)
|
||||
|
||||
new_item.set_status(status, custom_values=custom_stock_status_values)
|
||||
|
||||
if barcode:
|
||||
new_item.assign_barcode(barcode_data=barcode, save=False)
|
||||
|
||||
# new_item.save()
|
||||
bulk_create_items.append(new_item)
|
||||
|
||||
# Update the line item quantity
|
||||
@@ -1379,8 +1388,13 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
return any(line.is_overallocated() for line in self.lines.all())
|
||||
|
||||
def is_completed(self) -> bool:
|
||||
"""Check if this order is "shipped" (all line items delivered)."""
|
||||
return all(line.is_completed() for line in self.lines.all())
|
||||
"""Check if this order is "shipped" (all line items delivered).
|
||||
|
||||
Note: Any "virtual" parts are ignored in this calculation.
|
||||
"""
|
||||
lines = self.lines.all().filter(part__virtual=False)
|
||||
|
||||
return all(line.is_completed() for line in lines)
|
||||
|
||||
def can_complete(
|
||||
self, raise_error: bool = False, allow_incomplete_lines: bool = False
|
||||
@@ -1415,10 +1429,15 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
_('Order cannot be completed as there are incomplete allocations')
|
||||
)
|
||||
|
||||
if not allow_incomplete_lines and self.pending_line_count > 0:
|
||||
raise ValidationError(
|
||||
_('Order cannot be completed as there are incomplete line items')
|
||||
)
|
||||
if not allow_incomplete_lines:
|
||||
pending_lines = self.pending_line_items().exclude(part__virtual=True)
|
||||
|
||||
if pending_lines.count() > 0:
|
||||
raise ValidationError(
|
||||
_(
|
||||
'Order cannot be completed as there are incomplete line items'
|
||||
)
|
||||
)
|
||||
|
||||
except ValidationError as e:
|
||||
if raise_error:
|
||||
@@ -1475,6 +1494,7 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
|
||||
trigger_event(SalesOrderEvents.HOLD, id=self.pk)
|
||||
|
||||
@transaction.atomic
|
||||
def _action_complete(self, *args, **kwargs):
|
||||
"""Mark this order as "complete."""
|
||||
user = kwargs.pop('user', None)
|
||||
@@ -1486,6 +1506,16 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
get_global_setting('SALESORDER_SHIP_COMPLETE')
|
||||
)
|
||||
|
||||
# Update line items
|
||||
for line in self.lines.all():
|
||||
# Mark any "virtual" parts as shipped at this point
|
||||
if line.part and line.part.virtual and line.shipped != line.quantity:
|
||||
line.shipped = line.quantity
|
||||
line.save()
|
||||
|
||||
if line.part:
|
||||
line.part.schedule_pricing_update(create=True)
|
||||
|
||||
if bypass_shipped or self.status == SalesOrderStatus.SHIPPED:
|
||||
self.status = SalesOrderStatus.COMPLETE.value
|
||||
else:
|
||||
@@ -1497,11 +1527,6 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
|
||||
self.save()
|
||||
|
||||
# Schedule pricing update for any referenced parts
|
||||
for line in self.lines.all():
|
||||
if line.part:
|
||||
line.part.schedule_pricing_update(create=True)
|
||||
|
||||
trigger_event(SalesOrderEvents.COMPLETED, id=self.pk)
|
||||
|
||||
return True
|
||||
@@ -1565,7 +1590,7 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
"""Attempt to transition to COMPLETED status."""
|
||||
return self.handle_transition(
|
||||
self.status,
|
||||
SalesOrderStatus.COMPLETED.value,
|
||||
SalesOrderStatus.COMPLETE.value,
|
||||
self,
|
||||
self._action_complete,
|
||||
user=user,
|
||||
|
||||
@@ -1226,6 +1226,8 @@ class PurchaseOrderReceiveTest(OrderTest):
|
||||
|
||||
def test_receive_large_quantity(self):
|
||||
"""Test receipt of a large number of items."""
|
||||
from stock.status_codes import StockStatus
|
||||
|
||||
sp = SupplierPart.objects.first()
|
||||
|
||||
# Create a new order
|
||||
@@ -1256,7 +1258,12 @@ class PurchaseOrderReceiveTest(OrderTest):
|
||||
url,
|
||||
{
|
||||
'items': [
|
||||
{'line_item': line.pk, 'quantity': line.quantity} for line in lines
|
||||
{
|
||||
'line_item': line.pk,
|
||||
'quantity': line.quantity,
|
||||
'status': StockStatus.QUARANTINED.value,
|
||||
}
|
||||
for line in lines
|
||||
],
|
||||
'location': location.pk,
|
||||
},
|
||||
@@ -1269,6 +1276,7 @@ class PurchaseOrderReceiveTest(OrderTest):
|
||||
|
||||
for item in response:
|
||||
self.assertEqual(item['purchase_order'], po.pk)
|
||||
self.assertEqual(item['status'], StockStatus.QUARANTINED)
|
||||
|
||||
# Check that the order has been completed
|
||||
po.refresh_from_db()
|
||||
|
||||
@@ -488,3 +488,45 @@ class SalesOrderTest(InvenTreeTestCase):
|
||||
p.set_metadata(k, k)
|
||||
|
||||
self.assertEqual(len(p.metadata.keys()), 4)
|
||||
|
||||
def test_virtual_parts(self):
|
||||
"""Test shipment of virtual parts against an order."""
|
||||
vp = Part.objects.create(
|
||||
name='Virtual Part',
|
||||
salable=True,
|
||||
virtual=True,
|
||||
description='A virtual part that I sell',
|
||||
)
|
||||
|
||||
so = SalesOrder.objects.create(
|
||||
customer=self.customer,
|
||||
reference='SO-VIRTUAL-1',
|
||||
customer_reference='VIRT-001',
|
||||
)
|
||||
|
||||
for qty in [5, 10, 15]:
|
||||
SalesOrderLineItem.objects.create(order=so, part=vp, quantity=qty)
|
||||
|
||||
# Delete pending shipments (if any)
|
||||
so.shipments.all().delete()
|
||||
|
||||
for line in so.lines.all():
|
||||
self.assertEqual(line.part.virtual, True)
|
||||
self.assertEqual(line.shipped, 0)
|
||||
self.assertGreater(line.quantity, 0)
|
||||
self.assertTrue(line.is_fully_allocated())
|
||||
self.assertTrue(line.is_completed())
|
||||
|
||||
# Complete the order
|
||||
so.ship_order(None)
|
||||
|
||||
so.refresh_from_db()
|
||||
self.assertEqual(so.status, status.SalesOrderStatus.SHIPPED)
|
||||
|
||||
so.complete_order(None)
|
||||
so.refresh_from_db()
|
||||
self.assertEqual(so.status, status.SalesOrderStatus.COMPLETE)
|
||||
|
||||
# Ensure that virtual line item quantity values have been updated
|
||||
for line in so.lines.all():
|
||||
self.assertEqual(line.shipped, line.quantity)
|
||||
|
||||
@@ -827,7 +827,7 @@ class StockItem(
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# If user information is provided, and no existing note exists, create one!
|
||||
if user and add_note and self.tracking_info.count() == 0:
|
||||
if add_note and self.tracking_info.count() == 0:
|
||||
tracking_info = {'status': self.status}
|
||||
|
||||
self.add_tracking_entry(
|
||||
|
||||
@@ -1060,12 +1060,19 @@ class StockChangeStatusSerializer(serializers.Serializer):
|
||||
# Instead of performing database updates for each item,
|
||||
# perform bulk database updates (much more efficient)
|
||||
|
||||
# Pre-cache the custom status values (to reduce DB hits)
|
||||
custom_status_codes = StockItem.STATUS_CLASS.custom_values()
|
||||
|
||||
for item in items:
|
||||
# Ignore items which are already in the desired status
|
||||
if item.compare_status(status):
|
||||
continue
|
||||
|
||||
item.set_status(status)
|
||||
# Careful check for custom status codes also
|
||||
if item.compare_status(status):
|
||||
custom_status = item.get_custom_status()
|
||||
if status == custom_status or custom_status is None:
|
||||
continue
|
||||
|
||||
item.set_status(status, custom_values=custom_status_codes)
|
||||
item.save(add_note=False)
|
||||
|
||||
# Create a new transaction note for each item
|
||||
|
||||
@@ -1701,12 +1701,18 @@ class StockItemTest(StockAPITestCase):
|
||||
|
||||
prt = Part.objects.first()
|
||||
|
||||
# Number of items to create
|
||||
N_ITEMS = 10
|
||||
|
||||
# Create a bunch of items
|
||||
items = [StockItem.objects.create(part=prt, quantity=10) for _ in range(10)]
|
||||
items = [
|
||||
StockItem.objects.create(part=prt, quantity=10) for _ in range(N_ITEMS)
|
||||
]
|
||||
|
||||
for item in items:
|
||||
item.refresh_from_db()
|
||||
self.assertEqual(item.status, StockStatus.OK.value)
|
||||
self.assertEqual(item.tracking_info.count(), 1)
|
||||
|
||||
data = {
|
||||
'items': [item.pk for item in items],
|
||||
@@ -1719,10 +1725,10 @@ class StockItemTest(StockAPITestCase):
|
||||
for item in items:
|
||||
item.refresh_from_db()
|
||||
self.assertEqual(item.status, StockStatus.DAMAGED.value)
|
||||
self.assertEqual(item.tracking_info.count(), 1)
|
||||
self.assertEqual(item.tracking_info.count(), 2)
|
||||
|
||||
# Same test, but with one item unchanged
|
||||
items[0].status = StockStatus.ATTENTION.value
|
||||
items[0].set_status(StockStatus.ATTENTION.value)
|
||||
items[0].save()
|
||||
|
||||
data['status'] = StockStatus.ATTENTION.value
|
||||
@@ -1732,7 +1738,7 @@ class StockItemTest(StockAPITestCase):
|
||||
for item in items:
|
||||
item.refresh_from_db()
|
||||
self.assertEqual(item.status, StockStatus.ATTENTION.value)
|
||||
self.assertEqual(item.tracking_info.count(), 2)
|
||||
self.assertEqual(item.tracking_info.count(), 3)
|
||||
|
||||
tracking = item.tracking_info.last()
|
||||
self.assertEqual(tracking.tracking_type, StockHistoryCode.EDITED.value)
|
||||
|
||||
@@ -230,99 +230,99 @@ click==8.3.0 \
|
||||
--hash=sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc \
|
||||
--hash=sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4
|
||||
# via pip-tools
|
||||
coverage[toml]==7.11.2 \
|
||||
--hash=sha256:004bdc5985b86f565772af627925e368256ee2172623db10a0d78a3b53f20ef1 \
|
||||
--hash=sha256:03e7e7dc31a7deaebf121c3c3bd3c6442b7fbf50aca72aae2a1d08aa30ca2a20 \
|
||||
--hash=sha256:07e14a4050525fd98bf3d793f229eb8b3ae81678f4031e38e6a18a068bd59fd4 \
|
||||
--hash=sha256:0f4a958ff286038ac870f836351e9fb8912f1614d1cdbda200fc899235f7dc9b \
|
||||
--hash=sha256:10f10c9acf584ef82bfaaa7296163bd11c7487237f1670e81fc2fa7e972be67b \
|
||||
--hash=sha256:17047fb65fcd1ce8a2f97dd2247c2b59cb4bc8848b3911db02dcb05856f91b71 \
|
||||
--hash=sha256:1ac3f647ecf25d883051ef42d38d823016e715b9f289f8c1768be5117075d1bd \
|
||||
--hash=sha256:230317450af65a37c1fdbdd3546f7277e0c1c1b65e0d57409248e5dd0fa13493 \
|
||||
--hash=sha256:2442afabe9e83b881be083238bb7cf5afd4a10e47f29b6094470338d2336b33c \
|
||||
--hash=sha256:2970c03fefee2a5f1aebc91201a0706a7d0061cc71ab452bb5c5345b7174a349 \
|
||||
--hash=sha256:2ca963994d28e44285dc104cf94b25d8a7fd0c6f87cf944f46a23f473910703f \
|
||||
--hash=sha256:30c437e8b51ce081fe3903c9e368e85c9a803b093fd062c49215f3bf4fd1df37 \
|
||||
--hash=sha256:36c41bf2ee6f6062de8177e249fee17cd5c9662cd373f7a41e6468a34c5b9c0f \
|
||||
--hash=sha256:38a5509fe7fabb6fb3161059b947641753b6529150ef483fc01c4516a546f2ad \
|
||||
--hash=sha256:397778cf6d50df59c890bd3ac10acb5bf413388ff6a013305134f1403d5db648 \
|
||||
--hash=sha256:3aa8c62460499e10ceac5ea61cc09c4f7ddcd8a68c6313cf08785ad353dfd311 \
|
||||
--hash=sha256:410cafc1aba1f7eb8c09823d5da381be30a2c9b3595758a4c176fcfc04732731 \
|
||||
--hash=sha256:43ecf9dca4fcb3baf8a886019dd5ce663c95a5e1c5172719c414f0ebd9eeb785 \
|
||||
--hash=sha256:44b6e04bb94e59927a2807cd4de86386ce34248eaea95d9f1049a72f81828c38 \
|
||||
--hash=sha256:461577af3f8ad4da244a55af66c0731b68540ce571dbdc02598b5ec9e7a09e73 \
|
||||
--hash=sha256:4648c90cf741fb61e142826db1557a44079de0ca868c5c5a363c53d852897e84 \
|
||||
--hash=sha256:4aa799c61869318d2b86c0d3c413d6805546aec42069f009cbb27df2eefb2790 \
|
||||
--hash=sha256:4aaf2212302b6f748dde596424b0f08bc3e1285192104e2480f43d56b6824f35 \
|
||||
--hash=sha256:4c4423ea9c28749080b41e18ec74d658e6c9f148a6b47e719f3d7f56197f8227 \
|
||||
--hash=sha256:4d1ff4b87ad438148976f2215141a490ae000e878536370d53f8da8c59a175a6 \
|
||||
--hash=sha256:4f8f6bcaa7fe162460abb38f7a5dbfd7f47cfc51e2a0bf0d3ef9e51427298391 \
|
||||
--hash=sha256:55ae008253df6000bc885a780c1b0e939bd8c932f41e16df1cfe19a00428a98a \
|
||||
--hash=sha256:595c6bb2b565cc2d930ee634cae47fa959dfd24cc0e8ae4cf2b6e7e131e0d1f7 \
|
||||
--hash=sha256:5a02818ec44803e325d66bd022828212df934739b894d1699c9a05b9105d30f2 \
|
||||
--hash=sha256:5b284931d57389ec97a63fb1edf91c68ec369cee44bc40b37b5c3985ba0a2914 \
|
||||
--hash=sha256:5c31cdbb95ab0f4a60224a04efc43cfb406ce904f0b60fb6b2a72f37718ea5cb \
|
||||
--hash=sha256:5db683000ff6217273071c752bd6a1d341b6dc5d6aaa56678c53577a4e70e78a \
|
||||
--hash=sha256:5f72a49504e1f35443b157d97997c9259a017384373eab52fd09b8ade2ae4674 \
|
||||
--hash=sha256:61d6a7cc1e7a7a761ac59dcc88cee54219fd4231face52bd1257cfd3df29ae9f \
|
||||
--hash=sha256:632904d126ca97e5d4ecf7e51ae8b20f086b6f002c6075adcfd4ff3a28574527 \
|
||||
--hash=sha256:6681164bc697b93676945c8c814b76ac72204c395e11b71ba796a93b33331c24 \
|
||||
--hash=sha256:689d3b4dd0d4c912ed8bfd7a1b5ff2c5ecb1fa16571840573174704ff5437862 \
|
||||
--hash=sha256:6f70fa1ef17cba5dada94e144ea1b6e117d4f174666842d1da3aaf765d6eb477 \
|
||||
--hash=sha256:72a3d109ac233666064d60b29ae5801dd28bc51d1990e69f183a2b91b92d4baf \
|
||||
--hash=sha256:75ef769be19d69ea71b0417d7fbf090032c444792579cdf9b166346a340987d5 \
|
||||
--hash=sha256:7e01ab8d69b6cffa2463e78a4d760a6b69dfebe5bf21837eabcc273655c7e7b3 \
|
||||
--hash=sha256:7ea36e981a8a591acdaa920704f8dc798f9fff356c97dbd5d5702046ae967ce1 \
|
||||
--hash=sha256:7f1aa017b47e1879d7bac50161b00d2b886f2ff3882fa09427119e1b3572ede1 \
|
||||
--hash=sha256:7f933bc1fead57373922e383d803e1dd5ec7b5a786c220161152ebee1aa3f006 \
|
||||
--hash=sha256:8014a28a37ffabf7da7107f4f154d68c6b89672f27fef835a0574591c5cd140b \
|
||||
--hash=sha256:805efa416085999da918f15f81b26636d8e79863e1fbac1495664686d1e6a6e9 \
|
||||
--hash=sha256:811bff1f93566a8556a9aeb078bd82573e37f4d802a185fba4cbe75468615050 \
|
||||
--hash=sha256:84e8e0f5ab5134a2d32d4ebadc18b433dbbeddd0b73481f816333b1edd3ff1c8 \
|
||||
--hash=sha256:87d7c7b0b2279e174f36d276e2afb7bf16c9ea04e824d4fa277eea1854f4cfd4 \
|
||||
--hash=sha256:89565d7c9340858424a5ca3223bfefe449aeb116942cdc98cd76c07ca50e9db8 \
|
||||
--hash=sha256:940d195f4c8ba3ec6e7c302c9f546cdbe63e57289ed535452bc52089b1634f1c \
|
||||
--hash=sha256:94ced4a29a6987af99faaa49a513bf8d0458e8af004c54174e05dd7a8a31c7d9 \
|
||||
--hash=sha256:9a6468e1a3a40d3d1f9120a9ff221d3eacef4540a6f819fff58868fe0bd44fa9 \
|
||||
--hash=sha256:9a95b7a6043b221ec1a0d4d5481e424272b37028353265fbe5fcd3768d652eb7 \
|
||||
--hash=sha256:9f5f6ee021b3b25e748a9a053f3a8dd61a62b6689efd6425cb47e27360994903 \
|
||||
--hash=sha256:a35701fe0b5ee9d4b67d31aa76555237af32a36b0cf8dd33f8a74470cf7cd2f5 \
|
||||
--hash=sha256:a913b21f716aa05b149a8656e9e234d9da04bc1f9842136ad25a53172fecc20e \
|
||||
--hash=sha256:ae43149b7732df15c3ca9879b310c48b71d08cd8a7ba77fda7f9108f78499e93 \
|
||||
--hash=sha256:b4776c6555a9f378f37fa06408f2e1cc1d06e4c4e06adb3d157a4926b549efbe \
|
||||
--hash=sha256:b7658f3d4f728092368c091c18efcfb679be9b612c93bfdf345f33635a325188 \
|
||||
--hash=sha256:b7fc943097fa48de00d14d2a2f3bcebfede024e031d7cd96063fe135f8cbe96e \
|
||||
--hash=sha256:b9f28b900d96d83e2ae855b68d5cf5a704fa0b5e618999133fd2fb3bbe35ecb1 \
|
||||
--hash=sha256:bc65e32fe5bb942f0f5247e1500e355cbbdf326181198f5e27e3bb3ddb81e203 \
|
||||
--hash=sha256:bee1911c44c52cad6b51d436aa8c6ff5ca5d414fa089c7444592df9e7b890be9 \
|
||||
--hash=sha256:c4b1bea4c707f4c09f682fe0e646a114dfd068f627880d4a208850d01f8164ad \
|
||||
--hash=sha256:c5769159986eb174f0f66d049a52da03f2d976ac1355679371f1269e83528599 \
|
||||
--hash=sha256:c65f4291aec39692a3bfbe1d92ae5bea58c16b5553fdf021de61c655d987233f \
|
||||
--hash=sha256:c7ea5dec77d79dabb7b5fc712c59361aac52e459cd22028480625c3c743323d0 \
|
||||
--hash=sha256:c85f44ed4260221e46a4e9e8e8df4b359ab6c0a742c79e85d649779bcf77b534 \
|
||||
--hash=sha256:c8b9a7ebc6a29202fb095877fd8362aab09882894d1c950060c76d61fb116114 \
|
||||
--hash=sha256:cbffd1d5c5bf4c576ca247bf77646cdad4dced82928337eeb0b85e2b3be4d64b \
|
||||
--hash=sha256:d0e80c9946da61cc0bf55dfd90d65707acc1aa5bdcb551d4285ea8906255bb33 \
|
||||
--hash=sha256:d30a717493583c2a83c99f195e934c073be7f4291b32b7352c246d52e43f6893 \
|
||||
--hash=sha256:d423991415f73a70c0a5f3e0a226cf4ab374dd0da7409978069b844df3d31582 \
|
||||
--hash=sha256:d73da4893125e0671f762e408dea9957b2bda0036c9589c2fd258a6b870acbdb \
|
||||
--hash=sha256:d752a8e398a19e2fb24781e4c73089bfeb417b6ac55f96c2c42cfe5bdb21cc18 \
|
||||
--hash=sha256:e3b92e10ca996b5421232dd6629b9933f97eb57ce374bca800ab56681fbeda2b \
|
||||
--hash=sha256:e448ceee2fb880427eafc9a3f8e6162b2ac7cc3e9b30b85d6511f25cc8a11820 \
|
||||
--hash=sha256:e48b95abe2983be98cdf52900e07127eb7fe7067c87a700851f4f1f53d2b00e6 \
|
||||
--hash=sha256:e52a028a56889d3ad036c0420e866e4a69417d3203e2fc5f03dcb8841274b64c \
|
||||
--hash=sha256:e7d3fccd5781c5d29ca0bd1ea272630f05cd40a71d419e7e6105c0991400eb14 \
|
||||
--hash=sha256:e8eb6cbd7d3b238335b5da0f3ce281102435afb503be4d7bdd69eea3c700a952 \
|
||||
--hash=sha256:ea10a57568af7cf082a7a4d98a699f993652c2ffbdd5a6c9d63c9ca10b693b4d \
|
||||
--hash=sha256:ea910cc737ee8553c81ad5c104bc5b135106ebb36f88be506c3493e001b4c733 \
|
||||
--hash=sha256:eaa2a5eeb82fa7a6a9cd65c4f968ee2a53839d451b4e88e060c67d87a0a40732 \
|
||||
--hash=sha256:ed6ba9f1777fdd1c8e5650c7d123211fa484a187c61af4d82948dc5ee3c0afcc \
|
||||
--hash=sha256:ef2d3081562cd83f97984a96e02e7a294efa28f58d5e7f4e28920f59fd752b41 \
|
||||
--hash=sha256:f633da28958f57b846e955d28661b2b323d8ae84668756e1eea64045414dbe34 \
|
||||
--hash=sha256:f6b2498f86f2554ed6cb8df64201ee95b8c70fb77064a8b2ae8a7185e7a4a5f0 \
|
||||
--hash=sha256:f6f985e175dfa1fb8c0a01f47186720ae25d5e20c181cc5f3b9eba95589b8148 \
|
||||
--hash=sha256:f80cb5b328e870bf3df0568b41643a85ee4b8ccd219a096812389e39aa310ea4 \
|
||||
--hash=sha256:fd3f7cc6cb999e3eff91a2998a70c662b0fcd3c123d875766147c530ca0d3248
|
||||
coverage[toml]==7.11.3 \
|
||||
--hash=sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63 \
|
||||
--hash=sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa \
|
||||
--hash=sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd \
|
||||
--hash=sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060 \
|
||||
--hash=sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5 \
|
||||
--hash=sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704 \
|
||||
--hash=sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507 \
|
||||
--hash=sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5 \
|
||||
--hash=sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5 \
|
||||
--hash=sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5 \
|
||||
--hash=sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b \
|
||||
--hash=sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c \
|
||||
--hash=sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0 \
|
||||
--hash=sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2 \
|
||||
--hash=sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f \
|
||||
--hash=sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75 \
|
||||
--hash=sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc \
|
||||
--hash=sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1 \
|
||||
--hash=sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64 \
|
||||
--hash=sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7 \
|
||||
--hash=sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001 \
|
||||
--hash=sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237 \
|
||||
--hash=sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31 \
|
||||
--hash=sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131 \
|
||||
--hash=sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe \
|
||||
--hash=sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80 \
|
||||
--hash=sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b \
|
||||
--hash=sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e \
|
||||
--hash=sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f \
|
||||
--hash=sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c \
|
||||
--hash=sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d \
|
||||
--hash=sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e \
|
||||
--hash=sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7 \
|
||||
--hash=sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240 \
|
||||
--hash=sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1 \
|
||||
--hash=sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f \
|
||||
--hash=sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb \
|
||||
--hash=sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc \
|
||||
--hash=sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f \
|
||||
--hash=sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e \
|
||||
--hash=sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926 \
|
||||
--hash=sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428 \
|
||||
--hash=sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2 \
|
||||
--hash=sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832 \
|
||||
--hash=sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de \
|
||||
--hash=sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a \
|
||||
--hash=sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36 \
|
||||
--hash=sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7 \
|
||||
--hash=sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87 \
|
||||
--hash=sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5 \
|
||||
--hash=sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a \
|
||||
--hash=sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06 \
|
||||
--hash=sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3 \
|
||||
--hash=sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c \
|
||||
--hash=sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb \
|
||||
--hash=sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83 \
|
||||
--hash=sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac \
|
||||
--hash=sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9 \
|
||||
--hash=sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2 \
|
||||
--hash=sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd \
|
||||
--hash=sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76 \
|
||||
--hash=sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200 \
|
||||
--hash=sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655 \
|
||||
--hash=sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71 \
|
||||
--hash=sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055 \
|
||||
--hash=sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902 \
|
||||
--hash=sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac \
|
||||
--hash=sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044 \
|
||||
--hash=sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094 \
|
||||
--hash=sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4 \
|
||||
--hash=sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944 \
|
||||
--hash=sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362 \
|
||||
--hash=sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86 \
|
||||
--hash=sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a \
|
||||
--hash=sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8 \
|
||||
--hash=sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7 \
|
||||
--hash=sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55 \
|
||||
--hash=sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297 \
|
||||
--hash=sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739 \
|
||||
--hash=sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e \
|
||||
--hash=sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46 \
|
||||
--hash=sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405 \
|
||||
--hash=sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1 \
|
||||
--hash=sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e \
|
||||
--hash=sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd \
|
||||
--hash=sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820 \
|
||||
--hash=sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e \
|
||||
--hash=sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df \
|
||||
--hash=sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428 \
|
||||
--hash=sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7 \
|
||||
--hash=sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c \
|
||||
--hash=sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203
|
||||
# via -r src/backend/requirements-dev.in
|
||||
cryptography==46.0.3 \
|
||||
--hash=sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217 \
|
||||
@@ -452,9 +452,9 @@ pip==25.3 \
|
||||
--hash=sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343 \
|
||||
--hash=sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd
|
||||
# via pip-tools
|
||||
pip-tools==7.5.1 \
|
||||
--hash=sha256:a051a94794ba52df9acad2d7c9b0b09ae001617db458a543f8287fea7b89c2cf \
|
||||
--hash=sha256:f5ff803823529edc0e6e40c86b1aa7da7266fb1078093c8beea4e5b77877036a
|
||||
pip-tools==7.5.2 \
|
||||
--hash=sha256:2d64d72da6a044da1110257d333960563d7a4743637e8617dd2610ae7b82d60f \
|
||||
--hash=sha256:2fe16db727bbe5bf28765aeb581e792e61be51fc275545ef6725374ad720a1ce
|
||||
# via -r src/backend/requirements-dev.in
|
||||
platformdirs==4.5.0 \
|
||||
--hash=sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312 \
|
||||
|
||||
@@ -71,4 +71,4 @@ opentelemetry-instrumentation-psycopg
|
||||
opentelemetry-instrumentation-pymysql
|
||||
|
||||
# Pins
|
||||
xmlsec==1.3.14 # 2025-06-02 pinned to avoid issues with builds - see https://github.com/inventree/InvenTree/pull/9713
|
||||
xmlsec==1.3.17
|
||||
|
||||
+146
-94
@@ -661,38 +661,38 @@ drf-spectacular==0.29.0 \
|
||||
--hash=sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc \
|
||||
--hash=sha256:d1ee7c9535d89848affb4427347f7c4a22c5d22530b8842ef133d7b72e19b41a
|
||||
# via -r src/backend/requirements.in
|
||||
dulwich==0.24.8 \
|
||||
--hash=sha256:07aa6e7d41358fcba2a8ac53731e1b8ab201cac7a192ec678ef0da34c7643cf1 \
|
||||
--hash=sha256:0e9aacbbb0b0cf4b3fecac2c29ddd4d4e36f03ced30851889c193986e8bb327e \
|
||||
--hash=sha256:108c74b329599931bfe66c4a34fb9312cd8136053cbfc04e7007e7c34081c6b7 \
|
||||
--hash=sha256:138fd2480c1db43f372d52e4e6ed050c15f92ffbeeab9a0877dceb9801c65d2a \
|
||||
--hash=sha256:16e335bce0d5192d476db0ca81de1f90fb56863ad7d0b985b0333a8194c73c64 \
|
||||
--hash=sha256:17d8223cc69cf79ddd7a2f0893e223708f7efc2ea372f919ddcc0d852b3f5d06 \
|
||||
--hash=sha256:19855e8a0ce299cdcdafdc8bc4f6653bea9e02124a5022e13cda8103fb36912d \
|
||||
--hash=sha256:19be46710a9d810a66d4a925754abf3818ba22d61134e7d7e1d7b1585c9445b6 \
|
||||
--hash=sha256:1bac020051cf228b33c787294e17ac80a284e028c3749437ee72577ee04e1cd9 \
|
||||
--hash=sha256:267e79367cd6f438091248c1e826c6cf7abd84d5b641b9fe46fbc4d9119e11ed \
|
||||
--hash=sha256:2e3a9a713fda94f3216da4743db3cc8d670330f44c4d98580ac4600242dba2c4 \
|
||||
--hash=sha256:5593a7216b27412333b99b2e1851bcc2485485d5c4694430aa86d34a36f08d63 \
|
||||
--hash=sha256:6016e3f7a0f1dd5e19df14b772cb8f42bfde0cd55c504642c05e1e8328de21e3 \
|
||||
--hash=sha256:661af1fa3852d970fef68b0ab431f0bd488c3f94306e89244c173c4e6abb978e \
|
||||
--hash=sha256:6a51a41e858e0427b14bb19df7ac1207275dd6b5cc1976f54710bf15cb4c5614 \
|
||||
--hash=sha256:6ffdd616135bcb31eb2edcccf82d4408720f1db69f596f687ffa2d26c2f5e6f4 \
|
||||
--hash=sha256:8ac85e3ea42878fa91b6a9282652327df54f5abea4aaf674036e1000608a15f0 \
|
||||
--hash=sha256:8eca5242f8aed324394c95ecd13a0a66d2a0c31c2556f0a52e7bb8dd67edef20 \
|
||||
--hash=sha256:97f7c64b02fbd366c36557aa2e8642fc686aaec7d6569bc1460d0b38a63169f9 \
|
||||
--hash=sha256:a0a780b512d4144336eac2f1f6b982eb78c313442e88ba0db853a224e2b918ef \
|
||||
--hash=sha256:a6055b12cf2b90a0b4c21d38594f2681667d26bb0c135dfc36c2ea6de7458a37 \
|
||||
--hash=sha256:a60f8a5d718c7cc1f60bb931cc915311fd5198d85d68dde0ef3c569311c14a70 \
|
||||
--hash=sha256:b03474c16bcfa3524241b4ae89007931d79ac37c0b19e059133a6e63b7039845 \
|
||||
--hash=sha256:b31e08bcd0a4eb29915987fa5273a827cccca7ee83eb27ef17bd5899f5668055 \
|
||||
--hash=sha256:c9f4748bbcca56fb57458c71c0d30e2351ac15e0583d428c739c09228be68f05 \
|
||||
--hash=sha256:da03c7a6629b7ed37e7139739a175f2c9678080a45444418c54ab28d2ec6524b \
|
||||
--hash=sha256:ea6c63d3e40fc321ec7c5673b92be036b57aba7802c7e94a18f61451af382de0 \
|
||||
--hash=sha256:ec0f62538b6fb26cdd1b2fb70788ccfdb17df26a4ba1ca70e623e196c4004f5c \
|
||||
--hash=sha256:efbf0f29d8d3d56a098e2b4a9260bdfa5f313142180a882c7b28e648d9b5ca9e \
|
||||
--hash=sha256:f7519f3b8c66ba2e4ea66f47c2156a66cefedce2a121ac3227e030abe95698f3 \
|
||||
--hash=sha256:f7a0d2cef91cf92a44071daa92639f648ab756d3db63a99e37d3a08ebacf69f3
|
||||
dulwich==0.24.10 \
|
||||
--hash=sha256:019af16c850ae85254289f9633a29dea02f45351c4182ea20b0c1394c074a13b \
|
||||
--hash=sha256:0dfae8c59b97964a907fdf4c5809154a18fd8c55f2eb6d8fd1607464165a9aa2 \
|
||||
--hash=sha256:0e1601789554e3d15b294356c78a5403521c27d5460e64dbbc44ffd5b10af4c3 \
|
||||
--hash=sha256:15b32f8c3116a1c0a042dde8da96f65a607e263e860ee42b3d4a98ce2c2f4a06 \
|
||||
--hash=sha256:1601bfea3906b52c924fae5b6ba32a0b087fb8fae927607e6b5381e6f7559611 \
|
||||
--hash=sha256:1b19af8a3ab051003ba05f15fc5c0d6f0d427e795639490790f34ec0558e99e3 \
|
||||
--hash=sha256:1f511f7afe1f36e37193214e4e069685d7d0378e756cc96a2fcb138bdf9fefca \
|
||||
--hash=sha256:2a56f9838e5d2414a2b57bab370b73b9803fefd98836ef841f0fd489b5cc1349 \
|
||||
--hash=sha256:30e028979b6fa7220c913da9c786026611c10746c06496149742602b36a11f6b \
|
||||
--hash=sha256:3581ae0af33f28e6c0834d2f41ca67ca81cd92a589e6a5f985e6c64373232958 \
|
||||
--hash=sha256:393e9c3cdd382cff20b5beb66989376d6da69e3b0dfec046a884707ab5d27ac9 \
|
||||
--hash=sha256:44f62e0244531a8c43ca7771e201ec9e7f6a2fb27f8c3c623939bc03c1f50423 \
|
||||
--hash=sha256:470d6cd8207e1a5ff1fb34c4c6fac2ec9a96d618f7062e5fb96c5260927bb9a7 \
|
||||
--hash=sha256:4914abb6408a719b7a1f7d9a182d1efd92c326e178b440faf582df50f9f032db \
|
||||
--hash=sha256:4b5c225477a529e1d4a2b5e51272a418177e34803938391ce41b7573b2e5b0d0 \
|
||||
--hash=sha256:5c724e5fc67c45f3c813f2630795ac388e3e6310534212f799a7a6bf230648c8 \
|
||||
--hash=sha256:6a25ca1605a94090514af408f9df64427281aefbb726f542e97d86d3a7c8ec18 \
|
||||
--hash=sha256:752c32d517dc608dbb8414061eaaec8ac8a05591b29531f81a83336b018b26c6 \
|
||||
--hash=sha256:843de5f678436a27b33aea0f2b87fd0453afdd0135f885a3ca44bc3147846dd2 \
|
||||
--hash=sha256:858fae0c7121715282a993abb1919385a28e1a9c4f136f568748d283c2ba874f \
|
||||
--hash=sha256:8df79c8080471f363e4dfcfc4e0d2e61e6da73af1fd7d31cb6ae0d34af45a6b4 \
|
||||
--hash=sha256:90028182b9a47ea4efed51c81298f3a98e279d7bf5c1f91c47101927a309ee45 \
|
||||
--hash=sha256:90b24c0827299cfb53c4f4d4fedc811be5c4b10c11172ff6e5a5c52277fe0b3a \
|
||||
--hash=sha256:b715a9f85ed71bef8027275c1bded064e4925071ae8c8a8d9a20c67b31faf3cd \
|
||||
--hash=sha256:c262ffc94338999e7808b434dccafaccd572d03b42d4ef140059d4b7cad765a3 \
|
||||
--hash=sha256:ce6e05ec50f258ccd14d83114eb32cc5bb241ae4a8c7199d014fd7568de285b1 \
|
||||
--hash=sha256:d9793fc1e42149a650a017dc8ce38485368a41729b9937e1dfcfedd0591ebe9d \
|
||||
--hash=sha256:e2eda4a634d6f1ac4c0d4786f8772495c8840dfc2b3e595507376bf5e5b0f9c5 \
|
||||
--hash=sha256:f102c38207540fa485e85e0b763ce3725a2d49d846dbf316ed271e27fd85ff21 \
|
||||
--hash=sha256:f7bfa9f0bfae57685754b163eef6641609047460939d28052e3beeb63efa6795 \
|
||||
--hash=sha256:fbf94fa73211d2f029751a72e1ca3a2fd35c6f5d9bb434acdf10a4a79ca322dd
|
||||
# via -r src/backend/requirements.in
|
||||
et-xmlfile==2.0.0 \
|
||||
--hash=sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa \
|
||||
@@ -1832,9 +1832,9 @@ s3transfer==0.14.0 \
|
||||
--hash=sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456 \
|
||||
--hash=sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125
|
||||
# via boto3
|
||||
sentry-sdk==2.43.0 \
|
||||
--hash=sha256:4aacafcf1756ef066d359ae35030881917160ba7f6fc3ae11e0e58b09edc2d5d \
|
||||
--hash=sha256:52ed6e251c5d2c084224d73efee56b007ef5c2d408a4a071270e82131d336e20
|
||||
sentry-sdk==2.44.0 \
|
||||
--hash=sha256:5b1fe54dfafa332e900b07dd8f4dfe35753b64e78e7d9b1655a28fd3065e2493 \
|
||||
--hash=sha256:9e36a0372b881e8f92fdbff4564764ce6cec4b7f25424d0a3a8d609c9e4651a7
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# django-q-sentry
|
||||
@@ -2026,65 +2026,117 @@ xlwt==1.3.0 \
|
||||
--hash=sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e \
|
||||
--hash=sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88
|
||||
# via tablib
|
||||
xmlsec==1.3.14 \
|
||||
--hash=sha256:004e8a82e26728bf8a60f8ece1ef3ffafdac30ef538139dfe28870e8503ca64a \
|
||||
--hash=sha256:03ccba7dacf197850de954666af0221c740a5de631a80136362a1559223fab75 \
|
||||
--hash=sha256:0bae37b2920115cf00759ee9fb7841cbdebcef3a8a92734ab93ae8fa41ac581d \
|
||||
--hash=sha256:0be3b7a28e54a03b87faf07fb3c6dc3e50a2c79b686718c3ad08300b8bf6bb67 \
|
||||
--hash=sha256:1072878301cb9243a54679e0520e6a5be2266c07a28b0ecef9e029d05a90ffcd \
|
||||
--hash=sha256:12d90059308bb0c1b94bde065784e6852999d08b91bcb2048c17e62b954acb07 \
|
||||
--hash=sha256:147934bd39dfd840663fb6b920ea9201455fa886427975713f1b42d9f20b9b29 \
|
||||
--hash=sha256:19c86bab1498e4c2e56d8e2c878f461ccb6e56b67fd7522b0c8fda46d8910781 \
|
||||
--hash=sha256:1b9b5de6bc69fdec23147e5f712cb05dc86df105462f254f140d743cc680cc7b \
|
||||
--hash=sha256:1eb3dcf244a52f796377112d8f238dbb522eb87facffb498425dc8582a84a6bf \
|
||||
--hash=sha256:1fa1311f7489d050dde9028f5a2b5849c2927bb09c9a93491cb2f28fdc563912 \
|
||||
--hash=sha256:1fe23c2dd5f5dbcb24f40e2c1061e2672a32aabee7cf8ac5337036a485607d72 \
|
||||
--hash=sha256:204d3c586b8bd6f02a5d4c59850a8157205569d40c32567f49576fa5795d897d \
|
||||
--hash=sha256:2401e162aaab7d9416c3405bac7a270e5f370988a0f1f46f0f29b735edba87e1 \
|
||||
--hash=sha256:28cd9f513cf01dc0c5b9d9f0728714ecde2e7f46b3b6f63de91f4ae32f3008b3 \
|
||||
--hash=sha256:2f84a1c509c52773365645a87949081ee9ea9c535cd452048cc8ca4ad3b45666 \
|
||||
--hash=sha256:330147ce59fbe56a9be5b2085d739c55a569f112576b3f1b33681f87416eaf33 \
|
||||
--hash=sha256:34c61ec0c0e70fda710290ae74b9efe1928d9242ed82c4eecf97aa696cff68e6 \
|
||||
--hash=sha256:38e035bf48300b7dbde2dd01d3b8569f8584fc9c73809be13886e6b6c77b74fb \
|
||||
--hash=sha256:48e894ad3e7de373f56efc09d6a56f7eae73a8dd4cec8943313134849e9c6607 \
|
||||
--hash=sha256:4922afa9234d1c5763950b26c328a5320019e55eb6000272a79dfe54fee8e704 \
|
||||
--hash=sha256:4af81ce8044862ec865782efd353d22abdcd95b92364eef3c934de57ae6d5852 \
|
||||
--hash=sha256:4dea6df3ffcb65d0b215678c3a0fe7bbc66785d6eae81291296e372498bad43a \
|
||||
--hash=sha256:4edd8db4df04bbac9c4a5ab4af855b74fe2bf2c248d07cac2e6d92a485f1a685 \
|
||||
--hash=sha256:4fac2a787ae3b9fb761f9aec6b9f10f2d1c1b87abb574ebd8ff68435bdc97e3d \
|
||||
--hash=sha256:57fed3bc7943681c9ed4d2221600ab440f060d8d1a8f92f346f2b41effe175b8 \
|
||||
--hash=sha256:6566434e2e5c58e472362a6187f208601f1627a148683a6f92bd16479f1d9e20 \
|
||||
--hash=sha256:6679cec780386d848e7351d4b0de92c4483289ea4f0a2187e216159f939a4c6b \
|
||||
--hash=sha256:73eabf5ef58189d81655058cf328c1dfa9893d89f1bff5fc941481f08533f338 \
|
||||
--hash=sha256:774d5d1e45f07f953c1cc14fd055c1063f0725f7248b6b0e681f59fd8638934d \
|
||||
--hash=sha256:77749b338503fb6e151052c664064b34264f4168e2cb0cca1de78b7e5312a783 \
|
||||
--hash=sha256:7799a9ff3593f9dd43464e18b1a621640bffc40456c47c23383727f937dca7fc \
|
||||
--hash=sha256:7882963e9cb9c0bd0e8c2715a29159a366417ff4a30d8baf42b05bc5cf249446 \
|
||||
--hash=sha256:7e8e0171916026cbe8e2022c959558d02086655fd3c3466f2bc0451b09cf9ee8 \
|
||||
--hash=sha256:82ac81deb7d7bf5cc8a748148948e5df5386597ff43fb92ec651cc5c7addb0e7 \
|
||||
--hash=sha256:86ff7b2711557c1087b72b0a1a88d82eafbf2a6d38b97309a6f7101d4a7041c3 \
|
||||
--hash=sha256:934f804f2f895bcdb86f1eaee236b661013560ee69ec108d29cdd6e5f292a2d9 \
|
||||
--hash=sha256:995e87acecc263a2f6f2aa3cc204268f651cac8f4d7a2047f11b2cd49979cc38 \
|
||||
--hash=sha256:a487c3d144f791c32f5e560aa27a705fba23171728b8a8511f36de053ff6bc93 \
|
||||
--hash=sha256:a98eadfcb0c3b23ccceb7a2f245811f8d784bd287640dcfe696a26b9db1e2fc0 \
|
||||
--hash=sha256:ad1634cabe0915fe2a12e142db0ed2daf5be80cbe3891a2cecbba0750195cc6b \
|
||||
--hash=sha256:b109cdf717257fd4daa77c1d3ec8a3fb2a81318a6d06a36c55a8a53ae381ae5e \
|
||||
--hash=sha256:b6dd86f440fec9242515c64f0be93fec8b4289287db1f6de2651eee9995aaecb \
|
||||
--hash=sha256:b7ba2ea38e3d9efa520b14f3c0b7d99a7c055244ae5ba8bc9f4ca73b18f3a215 \
|
||||
--hash=sha256:ba3b39c493e3b04354615068a3218f30897fcc2f42c6d8986d0c1d63aca87782 \
|
||||
--hash=sha256:bd10ca3201f164482775a7ce61bf7ee9aade2e7d032046044dd0f6f52c91d79d \
|
||||
--hash=sha256:bddd2a2328b4e08c8a112e06cf2cd2b4d281f4ad94df15b4cef18f06cdc49d78 \
|
||||
--hash=sha256:c12900e1903e289deb84eb893dca88591d6884d3e3cda4fb711b8812118416e8 \
|
||||
--hash=sha256:c42735cc68fdb4c6065cf0a0701dfff3a12a1734c63a36376349af9a5481f27b \
|
||||
--hash=sha256:c4d41c83c8a2b8d8030204391ebeb6174fbdb044f0331653c4b5a4ce4150bcc0 \
|
||||
--hash=sha256:ce4e165a1436697e5e39587c4fba24db4545a5c9801e0d749f1afd09ad3ab901 \
|
||||
--hash=sha256:cf35a25be3eb6263b2e0544ba26294651113fab79064f994d347a2ca5973e8e2 \
|
||||
--hash=sha256:d0762f4232bce2c7f6c0af329db8b821b4460bbe123a2528fb5677d03db7a4b5 \
|
||||
--hash=sha256:dba457ff87c39cbae3c5020475a728d24bbd9d00376df9af9724cd3bb59ff07a \
|
||||
--hash=sha256:df4aa0782a53032fd35e18dcd6d328d6126324bfcfdef0cb5c2856f25b4b6f94 \
|
||||
--hash=sha256:e6cbc914d77678db0c8bc39e723d994174633d18f9d6be4665ec29cce978a96d \
|
||||
--hash=sha256:e732a75fcb6b84872b168f972fbbf3749baf76308635f14015d1d35ed0c5719c \
|
||||
--hash=sha256:ed4034939d8566ccdcd3b4e4f23c63fd807fb8763ae5668d59a19e11640a8242
|
||||
xmlsec==1.3.17 \
|
||||
--hash=sha256:00d43d4f68ac6b11f6e1e69bcb389495f54da77bf1168b4de08f4a7785e47bbb \
|
||||
--hash=sha256:00d43d4f68ac6b11f6e1e69bcb389495f54da77bf1168b4de08f4a7785e47bbb \
|
||||
--hash=sha256:040f28a7aacfdb467df46d423e4af05569e9376bc8c7f6416b0761e16a0e3d0b \
|
||||
--hash=sha256:040f28a7aacfdb467df46d423e4af05569e9376bc8c7f6416b0761e16a0e3d0b \
|
||||
--hash=sha256:1a0b9a1dcda547e0340eefa6f4a04b87dbd9e40cd514487f347934f94fd559ab \
|
||||
--hash=sha256:1a0b9a1dcda547e0340eefa6f4a04b87dbd9e40cd514487f347934f94fd559ab \
|
||||
--hash=sha256:26cc3d81437b51839946d2e93d09371dfd73ed2831dc7e37eff0fb52fc33747c \
|
||||
--hash=sha256:26cc3d81437b51839946d2e93d09371dfd73ed2831dc7e37eff0fb52fc33747c \
|
||||
--hash=sha256:2aa5081e1e05dcb6029660ddad795c7daebb3c5771001f60850ab24a16a9cf5e \
|
||||
--hash=sha256:2aa5081e1e05dcb6029660ddad795c7daebb3c5771001f60850ab24a16a9cf5e \
|
||||
--hash=sha256:320bf7162e2c442638233da9826af1476049999da1b474b5fe07c60952610131 \
|
||||
--hash=sha256:320bf7162e2c442638233da9826af1476049999da1b474b5fe07c60952610131 \
|
||||
--hash=sha256:393eb5cc6b8d6e67c04fd64979ec30d766b3d226550b208803cd410d4b416d91 \
|
||||
--hash=sha256:393eb5cc6b8d6e67c04fd64979ec30d766b3d226550b208803cd410d4b416d91 \
|
||||
--hash=sha256:3a53c14d4bc40b0f0fcc6d7908b88f3cbbcf36e25c392f796d88aee7dee5beea \
|
||||
--hash=sha256:3a53c14d4bc40b0f0fcc6d7908b88f3cbbcf36e25c392f796d88aee7dee5beea \
|
||||
--hash=sha256:3a6ced8c7744e896cb5a9fd0156d204df3143a62bae11be91cab8e9743d40eec \
|
||||
--hash=sha256:3a6ced8c7744e896cb5a9fd0156d204df3143a62bae11be91cab8e9743d40eec \
|
||||
--hash=sha256:3d1fc1fbe2e8585a3f468cf4154d0ec36cd95a15e68429ad8cc8ccd7c04e84ae \
|
||||
--hash=sha256:3d1fc1fbe2e8585a3f468cf4154d0ec36cd95a15e68429ad8cc8ccd7c04e84ae \
|
||||
--hash=sha256:40fcb197bdb76301c82b10096c67cc44a6e887317b5b914dbbd166339d36fce4 \
|
||||
--hash=sha256:40fcb197bdb76301c82b10096c67cc44a6e887317b5b914dbbd166339d36fce4 \
|
||||
--hash=sha256:46fc9c80e8898ad212b6ad389469bb82d1ab89b90555db42e3e73235b143739d \
|
||||
--hash=sha256:46fc9c80e8898ad212b6ad389469bb82d1ab89b90555db42e3e73235b143739d \
|
||||
--hash=sha256:4f2bf6bbf04f8a912483d268b4c2727d400d1806d054624da13bee4b9f6fa28a \
|
||||
--hash=sha256:4f2bf6bbf04f8a912483d268b4c2727d400d1806d054624da13bee4b9f6fa28a \
|
||||
--hash=sha256:5319d0bdaf9e597a0ba8dfb3840c4ae57e51f462e7620953f32b07df6267f2ba \
|
||||
--hash=sha256:5319d0bdaf9e597a0ba8dfb3840c4ae57e51f462e7620953f32b07df6267f2ba \
|
||||
--hash=sha256:5346616e1fe1015f7800698c15225c7902f45db199e217af2039a21989aff7e9 \
|
||||
--hash=sha256:5346616e1fe1015f7800698c15225c7902f45db199e217af2039a21989aff7e9 \
|
||||
--hash=sha256:5616ad5016794b0dd41d03eef5b721e31bb306353226b25fc88fedb7d4f7c37e \
|
||||
--hash=sha256:5616ad5016794b0dd41d03eef5b721e31bb306353226b25fc88fedb7d4f7c37e \
|
||||
--hash=sha256:593264c192d1836162d75478c8b1cb5874f3b69dcc5bdfac642a0933abefa93a \
|
||||
--hash=sha256:593264c192d1836162d75478c8b1cb5874f3b69dcc5bdfac642a0933abefa93a \
|
||||
--hash=sha256:5c3008b32a15d24b6c9da39bf6ede8dc3122570a640a73795d763aea55a2193e \
|
||||
--hash=sha256:5c3008b32a15d24b6c9da39bf6ede8dc3122570a640a73795d763aea55a2193e \
|
||||
--hash=sha256:5c6d4b2ece9d109591d08128a1656b458e24d9eba6c02c32e93573e14eee2447 \
|
||||
--hash=sha256:5c6d4b2ece9d109591d08128a1656b458e24d9eba6c02c32e93573e14eee2447 \
|
||||
--hash=sha256:5d0e69291f90b28e9442d8e0e69d3e06cede8a3c44e856413fd284de81ce2888 \
|
||||
--hash=sha256:5d0e69291f90b28e9442d8e0e69d3e06cede8a3c44e856413fd284de81ce2888 \
|
||||
--hash=sha256:64c1184d51c8a67e3d1eb3ac477e307a07e2b40fd03cd0c8084b147ea0f342db \
|
||||
--hash=sha256:64c1184d51c8a67e3d1eb3ac477e307a07e2b40fd03cd0c8084b147ea0f342db \
|
||||
--hash=sha256:66fe5aaccf68fb85fe0b64277e3f594d6b01ddefb98ef1ceb0a666652d6ec580 \
|
||||
--hash=sha256:66fe5aaccf68fb85fe0b64277e3f594d6b01ddefb98ef1ceb0a666652d6ec580 \
|
||||
--hash=sha256:672e41dc7962da4ce84b67aa1c3a008338e3b88332f5484b9911b91cee0997ed \
|
||||
--hash=sha256:672e41dc7962da4ce84b67aa1c3a008338e3b88332f5484b9911b91cee0997ed \
|
||||
--hash=sha256:67717fe5151df68987a1387cba11ba28ce19b3bb9a2d10d650277cd910e510e7 \
|
||||
--hash=sha256:67717fe5151df68987a1387cba11ba28ce19b3bb9a2d10d650277cd910e510e7 \
|
||||
--hash=sha256:6cbf8a556c74451a7455d506ce3edc81c7cd0f60895ef7792c77dc97434a8e82 \
|
||||
--hash=sha256:6cbf8a556c74451a7455d506ce3edc81c7cd0f60895ef7792c77dc97434a8e82 \
|
||||
--hash=sha256:6d1e9ff3398e87e3abb2ab8f1576c0761e5c5c18b086b41f2b39a3eb6b2f7e11 \
|
||||
--hash=sha256:6d1e9ff3398e87e3abb2ab8f1576c0761e5c5c18b086b41f2b39a3eb6b2f7e11 \
|
||||
--hash=sha256:728058a1623a620811a3cdf2dd4894b5d9413ede20c8ddddf98fdea5eafe9529 \
|
||||
--hash=sha256:728058a1623a620811a3cdf2dd4894b5d9413ede20c8ddddf98fdea5eafe9529 \
|
||||
--hash=sha256:72fc6d336dd68d62822c6536ff4b2453fda94ea652eddb4a958ac97b16ac7001 \
|
||||
--hash=sha256:72fc6d336dd68d62822c6536ff4b2453fda94ea652eddb4a958ac97b16ac7001 \
|
||||
--hash=sha256:79b471fdd1d3a92b80907828eaa809f6e34023583488b1b8dc3f951529e7a2f8 \
|
||||
--hash=sha256:79b471fdd1d3a92b80907828eaa809f6e34023583488b1b8dc3f951529e7a2f8 \
|
||||
--hash=sha256:7a9f7f7c9ded817f8476b5d6762a831034a8c54783f6175a928f61d57bec78cb \
|
||||
--hash=sha256:7a9f7f7c9ded817f8476b5d6762a831034a8c54783f6175a928f61d57bec78cb \
|
||||
--hash=sha256:80fff2251d0e73714435b5860ce200990dffe85466dd91d08d75c4d64ee9967d \
|
||||
--hash=sha256:80fff2251d0e73714435b5860ce200990dffe85466dd91d08d75c4d64ee9967d \
|
||||
--hash=sha256:9877303e8c72d7aa2467d1af12e56d67b8fb50d324eda5848e0ec5ee2176aac5 \
|
||||
--hash=sha256:9877303e8c72d7aa2467d1af12e56d67b8fb50d324eda5848e0ec5ee2176aac5 \
|
||||
--hash=sha256:99b5b4b6fe232f2234bcec2bdd533b7ab7030b3ce6cfb8bd7153bf02441c8520 \
|
||||
--hash=sha256:99b5b4b6fe232f2234bcec2bdd533b7ab7030b3ce6cfb8bd7153bf02441c8520 \
|
||||
--hash=sha256:9bb6faa4ae0268204cfa6b0c0de1c9121eb606eea8c66c7d7ce62e89a17f9efa \
|
||||
--hash=sha256:9bb6faa4ae0268204cfa6b0c0de1c9121eb606eea8c66c7d7ce62e89a17f9efa \
|
||||
--hash=sha256:a1ef656421d01851618d0fe5518e57469159c14a48e05125f7bd3225631952f9 \
|
||||
--hash=sha256:a1ef656421d01851618d0fe5518e57469159c14a48e05125f7bd3225631952f9 \
|
||||
--hash=sha256:a3961102a6ba8250670814bd1086139fb918e03bf146ef85dc8b6084a9b027d1 \
|
||||
--hash=sha256:a3961102a6ba8250670814bd1086139fb918e03bf146ef85dc8b6084a9b027d1 \
|
||||
--hash=sha256:a603584ceee175036e1bccdbe65d551c0fff67343fd506bfa6cec52bc64d9a75 \
|
||||
--hash=sha256:a603584ceee175036e1bccdbe65d551c0fff67343fd506bfa6cec52bc64d9a75 \
|
||||
--hash=sha256:abd585642d1fb169dd014e1037dcebe21d12a06e71b6d8f0e37650a81d57d1f1 \
|
||||
--hash=sha256:abd585642d1fb169dd014e1037dcebe21d12a06e71b6d8f0e37650a81d57d1f1 \
|
||||
--hash=sha256:ac68a6ac9c0e74f6f3e476f5ac2271b1df7b349a4021b6b2cdfe32cc8fa4beec \
|
||||
--hash=sha256:ac68a6ac9c0e74f6f3e476f5ac2271b1df7b349a4021b6b2cdfe32cc8fa4beec \
|
||||
--hash=sha256:ac92a5d0104640e0ff6fd9ef82441005e08fef460fccd790240d3e39abd69ad3 \
|
||||
--hash=sha256:ac92a5d0104640e0ff6fd9ef82441005e08fef460fccd790240d3e39abd69ad3 \
|
||||
--hash=sha256:ae88c3aaab5704adfdbce913b3a18db1eb96c49c970657cc01c0d1c420ffdec3 \
|
||||
--hash=sha256:ae88c3aaab5704adfdbce913b3a18db1eb96c49c970657cc01c0d1c420ffdec3 \
|
||||
--hash=sha256:b3f306f5aef47336b8299d8dbee31fa0b2eba4579f9f41396070f7a97d0dcd49 \
|
||||
--hash=sha256:b3f306f5aef47336b8299d8dbee31fa0b2eba4579f9f41396070f7a97d0dcd49 \
|
||||
--hash=sha256:b4be73fbde421d6188300e02ad92d2d5435c708a35ede8124ebdf6b00330d7cb \
|
||||
--hash=sha256:b4be73fbde421d6188300e02ad92d2d5435c708a35ede8124ebdf6b00330d7cb \
|
||||
--hash=sha256:bc8d0a75a43a45349b37186ecce5ae028325ede47cbc217802ff3ef4db3f3cb9 \
|
||||
--hash=sha256:bc8d0a75a43a45349b37186ecce5ae028325ede47cbc217802ff3ef4db3f3cb9 \
|
||||
--hash=sha256:c4533780d91f547b841f2522a419da9f2cee2f906dbd4aa58083bc944526a45c \
|
||||
--hash=sha256:c4533780d91f547b841f2522a419da9f2cee2f906dbd4aa58083bc944526a45c \
|
||||
--hash=sha256:d360d4adfb53d3adeca398c225cb7e2a73a2246414455937082a1fa19bd8572b \
|
||||
--hash=sha256:d360d4adfb53d3adeca398c225cb7e2a73a2246414455937082a1fa19bd8572b \
|
||||
--hash=sha256:d4a7ee007c6b55f7621330aee8330ef2dafa4225fce554064571ca826beafe7e \
|
||||
--hash=sha256:d4a7ee007c6b55f7621330aee8330ef2dafa4225fce554064571ca826beafe7e \
|
||||
--hash=sha256:d586bb09f146235f82de624ff05fbca76f8aadc627eb9a072df1899317a1a9eb \
|
||||
--hash=sha256:d586bb09f146235f82de624ff05fbca76f8aadc627eb9a072df1899317a1a9eb \
|
||||
--hash=sha256:d862f023f56a49c06576be41dfaf213c9ac77e7a344e7f204278c365bb36d00e \
|
||||
--hash=sha256:d862f023f56a49c06576be41dfaf213c9ac77e7a344e7f204278c365bb36d00e \
|
||||
--hash=sha256:df4a8d7fef3ffe90e572400d47392ea480120e339c292f802830ed09d449e622 \
|
||||
--hash=sha256:df4a8d7fef3ffe90e572400d47392ea480120e339c292f802830ed09d449e622 \
|
||||
--hash=sha256:e2bf1d07c4f97afeb957f626b8c3ebb8cef300efa0cb95599e936c69a66a1b17 \
|
||||
--hash=sha256:e2bf1d07c4f97afeb957f626b8c3ebb8cef300efa0cb95599e936c69a66a1b17 \
|
||||
--hash=sha256:ea2d65749c4c6a35a3ba138debda2f910713a9f4f06b4647510c184c284d7c62 \
|
||||
--hash=sha256:ea2d65749c4c6a35a3ba138debda2f910713a9f4f06b4647510c184c284d7c62 \
|
||||
--hash=sha256:ed63cbd87dd69ebcf3a9f82d87b67818c9a7d656325dd4fb34d6c4dfbaa84017 \
|
||||
--hash=sha256:ed63cbd87dd69ebcf3a9f82d87b67818c9a7d656325dd4fb34d6c4dfbaa84017 \
|
||||
--hash=sha256:eee89c268a35f8a08a8e9abef6f466b97577e94f5cac8bf32c25e97cd5020097 \
|
||||
--hash=sha256:eee89c268a35f8a08a8e9abef6f466b97577e94f5cac8bf32c25e97cd5020097 \
|
||||
--hash=sha256:f3fac9ae679f66585925cc00c5f6839ae36c1d03157619571dee18acc05b9c01 \
|
||||
--hash=sha256:f3fac9ae679f66585925cc00c5f6839ae36c1d03157619571dee18acc05b9c01
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# python3-saml
|
||||
|
||||
@@ -7,6 +7,28 @@ import { cancelEvent } from './Events';
|
||||
export const getBaseUrl = (): string =>
|
||||
(window as any).INVENTREE_SETTINGS?.base_url || 'web';
|
||||
|
||||
/**
|
||||
* Returns the overview URL for a given model type.
|
||||
* This is the UI URL, not the API URL.
|
||||
*/
|
||||
export function getOverviewUrl(model: ModelType, absolute?: boolean): string {
|
||||
const modelInfo = ModelInformationDict[model];
|
||||
|
||||
if (modelInfo?.url_overview) {
|
||||
const url = modelInfo.url_overview;
|
||||
const base = getBaseUrl();
|
||||
|
||||
if (absolute && base) {
|
||||
return `/${base}${url}`;
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
console.error(`No overview URL found for model ${model}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the detail view URL for a given model type.
|
||||
* This is the UI URL, not the API URL.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
[build.environment]
|
||||
VITE_DEMO = "true"
|
||||
PYTHON_VERSION = "3.11"
|
||||
|
||||
# Send requests to subpath
|
||||
|
||||
|
||||
@@ -24,11 +24,7 @@ import { type NavigateFunction, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { isTrue } from '@lib/functions/Conversion';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import type {
|
||||
ApiFormFieldSet,
|
||||
ApiFormFieldType,
|
||||
ApiFormProps
|
||||
} from '@lib/types/Forms';
|
||||
import type { ApiFormFieldSet, ApiFormProps } from '@lib/types/Forms';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import {
|
||||
type NestedDict,
|
||||
@@ -46,9 +42,11 @@ import { ApiFormField } from './fields/ApiFormField';
|
||||
|
||||
export function OptionsApiForm({
|
||||
props: _props,
|
||||
opened,
|
||||
id: pId
|
||||
}: Readonly<{
|
||||
props: ApiFormProps;
|
||||
opened?: boolean;
|
||||
id?: string;
|
||||
}>) {
|
||||
const api = useApi();
|
||||
@@ -75,24 +73,26 @@ export function OptionsApiForm({
|
||||
);
|
||||
|
||||
const optionsQuery = useQuery({
|
||||
enabled: true,
|
||||
enabled: opened !== false && props.ignorePermissionCheck !== true,
|
||||
refetchOnMount: false,
|
||||
queryKey: [
|
||||
'form-options-data',
|
||||
id,
|
||||
opened,
|
||||
props.ignorePermissionCheck,
|
||||
props.method,
|
||||
props.url,
|
||||
props.pk,
|
||||
props.pathParams
|
||||
],
|
||||
queryFn: async () => {
|
||||
const response = await api.options(url);
|
||||
let fields: Record<string, ApiFormFieldType> | null = {};
|
||||
if (!props.ignorePermissionCheck) {
|
||||
fields = extractAvailableFields(response, props.method);
|
||||
if (props.ignorePermissionCheck === true || opened === false) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return fields;
|
||||
return api.options(url).then((response: any) => {
|
||||
return extractAvailableFields(response, props.method);
|
||||
});
|
||||
},
|
||||
throwOnError: (error: any) => {
|
||||
if (error.response) {
|
||||
@@ -110,6 +110,13 @@ export function OptionsApiForm({
|
||||
}
|
||||
});
|
||||
|
||||
// Refetch form options whenever the modal is opened
|
||||
useEffect(() => {
|
||||
if (opened !== false) {
|
||||
optionsQuery.refetch();
|
||||
}
|
||||
}, [opened]);
|
||||
|
||||
const formProps: ApiFormProps = useMemo(() => {
|
||||
const _props = { ...props };
|
||||
|
||||
|
||||
@@ -62,6 +62,13 @@ export function RelatedModelField({
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
const [autoFilled, setAutoFilled] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Reset auto-fill status when the form is reconstructed
|
||||
setAutoFilled(false);
|
||||
}, []);
|
||||
|
||||
// Auto-fill the field with data from the API
|
||||
useEffect(() => {
|
||||
// If there is *no value defined*, and autoFill is enabled, then fetch data from the API
|
||||
@@ -69,10 +76,17 @@ export function RelatedModelField({
|
||||
return;
|
||||
}
|
||||
|
||||
// Return if the autofill has already been performed
|
||||
if (autoFilled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.value != undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
setAutoFilled(true);
|
||||
|
||||
// Construct parameters for auto-filling the field
|
||||
const params = {
|
||||
...(definition?.filters ?? {}),
|
||||
@@ -114,6 +128,7 @@ export function RelatedModelField({
|
||||
}
|
||||
});
|
||||
}, [
|
||||
autoFilled,
|
||||
definition.autoFill,
|
||||
definition.api_url,
|
||||
definition.filters,
|
||||
@@ -395,7 +410,14 @@ export function RelatedModelField({
|
||||
menuPortalTarget={document.body}
|
||||
noOptionsMessage={() => t`No results found`}
|
||||
menuPosition='fixed'
|
||||
styles={{ menuPortal: (base: any) => ({ ...base, zIndex: 9999 }) }}
|
||||
styles={{
|
||||
menuPortal: (base: any) => ({ ...base, zIndex: 9999 }),
|
||||
clearIndicator: (base: any) => ({
|
||||
...base,
|
||||
color: 'red',
|
||||
':hover': { color: 'red' }
|
||||
})
|
||||
}}
|
||||
formatOptionLabel={(option: any) => formatOption(option)}
|
||||
theme={(theme) => {
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { TextInput, Tooltip } from '@mantine/core';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { IconCopyCheck, IconX } from '@tabler/icons-react';
|
||||
import {
|
||||
type ReactNode,
|
||||
@@ -40,30 +39,26 @@ export default function TextField({
|
||||
|
||||
const { value } = useMemo(() => field, [field]);
|
||||
|
||||
const [rawText, setRawText] = useState<string>(value || '');
|
||||
const [textValue, setTextValue] = useState<string>(value || '');
|
||||
|
||||
const [debouncedText] = useDebouncedValue(rawText, 100);
|
||||
const onTextChange = useCallback(
|
||||
(value: any) => {
|
||||
setTextValue(value);
|
||||
onChange(value);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setRawText(value || '');
|
||||
setTextValue(value || '');
|
||||
}, [value]);
|
||||
|
||||
const onTextChange = useCallback((value: any) => {
|
||||
setRawText(value);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedText !== value) {
|
||||
onChange(debouncedText);
|
||||
}
|
||||
}, [debouncedText]);
|
||||
|
||||
// Construct a "right section" for the text field
|
||||
const textFieldRightSection: ReactNode = useMemo(() => {
|
||||
if (definition.rightSection) {
|
||||
// Use the specified override value
|
||||
return definition.rightSection;
|
||||
} else if (value) {
|
||||
} else if (textValue) {
|
||||
if (!definition.required && !definition.disabled) {
|
||||
// Render a button to clear the text field
|
||||
return (
|
||||
@@ -78,7 +73,7 @@ export default function TextField({
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
!value &&
|
||||
!textValue &&
|
||||
definition.placeholder &&
|
||||
placeholderAutofill &&
|
||||
!definition.disabled
|
||||
@@ -94,7 +89,7 @@ export default function TextField({
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}, [placeholderAutofill, definition, value]);
|
||||
}, [placeholderAutofill, definition, textValue]);
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
@@ -103,19 +98,19 @@ export default function TextField({
|
||||
id={fieldId}
|
||||
aria-label={`text-field-${field.name}`}
|
||||
type={definition.field_type}
|
||||
value={rawText || ''}
|
||||
value={textValue || ''}
|
||||
error={definition.error ?? error?.message}
|
||||
radius='sm'
|
||||
onChange={(event) => onTextChange(event.currentTarget.value)}
|
||||
onBlur={(event) => {
|
||||
if (event.currentTarget.value != value) {
|
||||
onChange(event.currentTarget.value);
|
||||
if (event.currentTarget.value != textValue) {
|
||||
onTextChange(event.currentTarget.value);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.code === 'Enter') {
|
||||
// Bypass debounce on enter key
|
||||
onChange(event.currentTarget.value);
|
||||
onTextChange(event.currentTarget.value);
|
||||
}
|
||||
onKeyDown(event.code);
|
||||
}}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import type { ImportSessionState } from '../../hooks/UseImportSession';
|
||||
import { StandaloneField } from '../forms/StandaloneField';
|
||||
@@ -83,6 +84,14 @@ function ImporterDefaultField({
|
||||
}) {
|
||||
const api = useApi();
|
||||
|
||||
const [rawValue, setRawValue] = useState<any>('');
|
||||
|
||||
const fieldType: string = useMemo(() => {
|
||||
return session.availableFields[fieldName]?.type;
|
||||
}, [fieldName, session.availableFields]);
|
||||
|
||||
const [value] = useDebouncedValue(rawValue, fieldType == 'string' ? 500 : 10);
|
||||
|
||||
const onChange = useCallback(
|
||||
(value: any) => {
|
||||
// Update the default value for the field
|
||||
@@ -105,6 +114,11 @@ function ImporterDefaultField({
|
||||
[fieldName, session, session.fieldDefaults]
|
||||
);
|
||||
|
||||
// Update the default value after the debounced value changes
|
||||
useEffect(() => {
|
||||
onChange(value);
|
||||
}, [value]);
|
||||
|
||||
const fieldDef: ApiFormFieldType = useMemo(() => {
|
||||
let def: any = session.availableFields[fieldName];
|
||||
|
||||
@@ -114,7 +128,10 @@ function ImporterDefaultField({
|
||||
value: session.fieldDefaults[fieldName],
|
||||
field_type: def.type,
|
||||
description: def.help_text,
|
||||
onValueChange: onChange
|
||||
required: false,
|
||||
onValueChange: (value: string) => {
|
||||
setRawValue(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ export function RenderInlineModel({
|
||||
|
||||
return (
|
||||
<Group gap='xs' justify='space-between' title={tooltip}>
|
||||
<Group gap='xs' justify='left' wrap='nowrap'>
|
||||
<Group gap='xs' justify='left'>
|
||||
{prefix}
|
||||
{image && <Thumbnail src={image} size={18} />}
|
||||
{url ? (
|
||||
|
||||
@@ -517,7 +517,7 @@ function BuildAllocateLineRow({
|
||||
field_type: 'related field',
|
||||
api_url: apiUrl(ApiEndpoints.stock_item_list),
|
||||
model: ModelType.stockitem,
|
||||
autoFill: !!output?.serial,
|
||||
autoFill: !output || !!output?.serial,
|
||||
autoFillFilters: {
|
||||
serial: output?.serial
|
||||
},
|
||||
@@ -814,7 +814,7 @@ export function useConsumeBuildItemsForm({
|
||||
url: ApiEndpoints.build_order_consume,
|
||||
pk: buildId,
|
||||
title: t`Consume Stock`,
|
||||
successMessage: t`Stock items consumed`,
|
||||
successMessage: t`Stock items scheduled to be consumed`,
|
||||
onFormSuccess: onFormSuccess,
|
||||
size: '80%',
|
||||
fields: consumeFields,
|
||||
@@ -915,7 +915,7 @@ export function useConsumeBuildLinesForm({
|
||||
url: ApiEndpoints.build_order_consume,
|
||||
pk: buildId,
|
||||
title: t`Consume Stock`,
|
||||
successMessage: t`Stock items consumed`,
|
||||
successMessage: t`Stock items scheduled to be consumed`,
|
||||
onFormSuccess: onFormSuccess,
|
||||
fields: consumeFields,
|
||||
initialData: {
|
||||
|
||||
@@ -20,6 +20,9 @@ export function usePartFields({
|
||||
}): ApiFormFieldSet {
|
||||
const settings = useGlobalSettingsState();
|
||||
|
||||
const [virtual, setVirtual] = useState<boolean>(false);
|
||||
const [purchaseable, setPurchaseable] = useState<boolean>(false);
|
||||
|
||||
return useMemo(() => {
|
||||
const fields: ApiFormFieldSet = {
|
||||
category: {
|
||||
@@ -62,9 +65,19 @@ export function usePartFields({
|
||||
is_template: {},
|
||||
testable: {},
|
||||
trackable: {},
|
||||
purchaseable: {},
|
||||
purchaseable: {
|
||||
value: purchaseable,
|
||||
onValueChange: (value: boolean) => {
|
||||
setPurchaseable(value);
|
||||
}
|
||||
},
|
||||
salable: {},
|
||||
virtual: {},
|
||||
virtual: {
|
||||
value: virtual,
|
||||
onValueChange: (value: boolean) => {
|
||||
setVirtual(value);
|
||||
}
|
||||
},
|
||||
locked: {},
|
||||
active: {},
|
||||
starred: {
|
||||
@@ -80,33 +93,37 @@ export function usePartFields({
|
||||
if (create) {
|
||||
fields.copy_category_parameters = {};
|
||||
|
||||
fields.initial_stock = {
|
||||
icon: <IconPackages />,
|
||||
children: {
|
||||
quantity: {
|
||||
value: 0
|
||||
},
|
||||
location: {}
|
||||
}
|
||||
};
|
||||
if (!virtual) {
|
||||
fields.initial_stock = {
|
||||
icon: <IconPackages />,
|
||||
children: {
|
||||
quantity: {
|
||||
value: 0
|
||||
},
|
||||
location: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fields.initial_supplier = {
|
||||
icon: <IconBuildingStore />,
|
||||
children: {
|
||||
supplier: {
|
||||
filters: {
|
||||
is_supplier: true
|
||||
}
|
||||
},
|
||||
sku: {},
|
||||
manufacturer: {
|
||||
filters: {
|
||||
is_manufacturer: true
|
||||
}
|
||||
},
|
||||
mpn: {}
|
||||
}
|
||||
};
|
||||
if (purchaseable) {
|
||||
fields.initial_supplier = {
|
||||
icon: <IconBuildingStore />,
|
||||
children: {
|
||||
supplier: {
|
||||
filters: {
|
||||
is_supplier: true
|
||||
}
|
||||
},
|
||||
sku: {},
|
||||
manufacturer: {
|
||||
filters: {
|
||||
is_manufacturer: true
|
||||
}
|
||||
},
|
||||
mpn: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Additional fields for part duplication
|
||||
@@ -159,7 +176,7 @@ export function usePartFields({
|
||||
}
|
||||
|
||||
return fields;
|
||||
}, [create, duplicatePartInstance, settings]);
|
||||
}, [virtual, purchaseable, create, duplicatePartInstance, settings]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -125,8 +125,11 @@ export function useSalesOrderLineItemFields({
|
||||
)
|
||||
.sort((a: any, b: any) => a.quantity - b.quantity);
|
||||
|
||||
if (applicablePriceBreaks.length)
|
||||
if (applicablePriceBreaks.length) {
|
||||
setSalePrice(applicablePriceBreaks[0].price);
|
||||
} else {
|
||||
setSalePrice('');
|
||||
}
|
||||
}, [part, quantity, partCurrency, create]);
|
||||
|
||||
return useMemo(() => {
|
||||
@@ -184,6 +187,7 @@ function SalesOrderAllocateLineRow({
|
||||
field_type: 'related field',
|
||||
api_url: apiUrl(ApiEndpoints.stock_item_list),
|
||||
model: ModelType.stockitem,
|
||||
autoFill: true,
|
||||
filters: {
|
||||
available: true,
|
||||
part_detail: true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Alert, Divider, Stack } from '@mantine/core';
|
||||
import { useId } from '@mantine/hooks';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import type {
|
||||
ApiFormModalProps,
|
||||
@@ -50,14 +50,18 @@ export function useApiFormModal(props: ApiFormModalProps) {
|
||||
[props]
|
||||
);
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
const modal = useModal({
|
||||
id: modalId,
|
||||
title: formProps.title,
|
||||
onOpen: () => {
|
||||
setIsOpen(true);
|
||||
modalState.setModalOpen(modalId, true);
|
||||
formProps.onOpen?.();
|
||||
},
|
||||
onClose: () => {
|
||||
setIsOpen(false);
|
||||
modalState.setModalOpen(modalId, false);
|
||||
formProps.onClose?.();
|
||||
},
|
||||
@@ -66,7 +70,7 @@ export function useApiFormModal(props: ApiFormModalProps) {
|
||||
children: (
|
||||
<Stack gap={'xs'}>
|
||||
<Divider />
|
||||
<OptionsApiForm props={formProps} id={modalId} />
|
||||
<OptionsApiForm props={formProps} id={modalId} opened={isOpen} />
|
||||
</Stack>
|
||||
)
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user