feature/#26_enhance_storage_stock_display #37
@@ -44,6 +44,7 @@ class ComponentSerializer(serializers.HyperlinkedModelSerializer):
 | 
				
			|||||||
    ro_component_type = serializers.ReadOnlyField(source='component_type.class_name')
 | 
					    ro_component_type = serializers.ReadOnlyField(source='component_type.class_name')
 | 
				
			||||||
    ro_parameters = ComponentParameterSerializer(many=True, source='componentparameter_set', read_only=True)
 | 
					    ro_parameters = ComponentParameterSerializer(many=True, source='componentparameter_set', read_only=True)
 | 
				
			||||||
    ro_distributor_numbers = ComponentDistributorNumSerializer(many=True, source='distributornum_set', read_only=True)
 | 
					    ro_distributor_numbers = ComponentDistributorNumSerializer(many=True, source='distributornum_set', read_only=True)
 | 
				
			||||||
 | 
					    ro_dynamic_description = serializers.ReadOnlyField(source='dynamic_description')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = parts_models.Component
 | 
					        model = parts_models.Component
 | 
				
			||||||
@@ -60,12 +61,14 @@ class ComponentSerializer(serializers.HyperlinkedModelSerializer):
 | 
				
			|||||||
                  'ro_image',
 | 
					                  'ro_image',
 | 
				
			||||||
                  'ro_component_type',
 | 
					                  'ro_component_type',
 | 
				
			||||||
                  'ro_parameters',
 | 
					                  'ro_parameters',
 | 
				
			||||||
                  'ro_distributor_numbers']
 | 
					                  'ro_distributor_numbers',
 | 
				
			||||||
 | 
					                  'ro_dynamic_description']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StockSerializer(serializers.HyperlinkedModelSerializer):
 | 
					class StockSerializer(serializers.HyperlinkedModelSerializer):
 | 
				
			||||||
    id = serializers.ReadOnlyField()
 | 
					    id = serializers.ReadOnlyField()
 | 
				
			||||||
    ro_package_name = serializers.ReadOnlyField(source='component.package.name')
 | 
					    ro_package_name = serializers.ReadOnlyField(source='component.package.name')
 | 
				
			||||||
    ro_component_name = serializers.ReadOnlyField(source='component.name')
 | 
					    ro_component_name = serializers.ReadOnlyField(source='component.name')
 | 
				
			||||||
 | 
					    ro_component_dynamic_description = serializers.ReadOnlyField(source='component.dynamic_description')
 | 
				
			||||||
    ro_manufacturer_name = serializers.ReadOnlyField(source='component.manufacturer.name')
 | 
					    ro_manufacturer_name = serializers.ReadOnlyField(source='component.manufacturer.name')
 | 
				
			||||||
    ro_image = serializers.ReadOnlyField(source='component.get_resolved_image')
 | 
					    ro_image = serializers.ReadOnlyField(source='component.get_resolved_image')
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
@@ -80,7 +83,7 @@ class StorageSerializer(serializers.HyperlinkedModelSerializer):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = parts_models.Storage
 | 
					        model = parts_models.Storage
 | 
				
			||||||
        fields = ['url', 'id', 'name', 'verbose_name', 'parent_storage', 'responsible', 'template', 'full_path']
 | 
					        fields = ['url', 'id', 'name', 'verbose_name', 'parent_storage', 'responsible', 'template', 'full_path', 'full_path_verbose']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StorageSerializerStocksExpanded(StorageSerializer):
 | 
					class StorageSerializerStocksExpanded(StorageSerializer):
 | 
				
			||||||
    ro_stocks = StockSerializerExpandComponent(many=True, read_only=True, source='stock_set')
 | 
					    ro_stocks = StockSerializerExpandComponent(many=True, read_only=True, source='stock_set')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,8 +43,8 @@ class GroupViewSet(viewsets.ReadOnlyModelViewSet):
 | 
				
			|||||||
class PartsStorageViewSet(viewsets.ModelViewSet):
 | 
					class PartsStorageViewSet(viewsets.ModelViewSet):
 | 
				
			||||||
    queryset = parts_models.Storage.objects.all()
 | 
					    queryset = parts_models.Storage.objects.all()
 | 
				
			||||||
    permission_classes = [permissions.DjangoModelPermissions]
 | 
					    permission_classes = [permissions.DjangoModelPermissions]
 | 
				
			||||||
    filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
 | 
					    filter_backends = [django_filters.rest_framework.DjangoFilterBackend, filters.SearchFilter]
 | 
				
			||||||
    search_fields = ['id', 'name', 'parent_storage']
 | 
					    search_fields = ['id', 'name', 'verbose_name']
 | 
				
			||||||
    filterset_fields = ['id', 'name', 'parent_storage']
 | 
					    filterset_fields = ['id', 'name', 'parent_storage']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_serializer_class(self):
 | 
					    def get_serializer_class(self):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -101,7 +101,8 @@ class ChangeStorageForm(forms.Form):
 | 
				
			|||||||
                                              foreign_model=get_user_model(),
 | 
					                                              foreign_model=get_user_model(),
 | 
				
			||||||
                                              prepend='@')
 | 
					                                              prepend='@')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    is_template = forms.BooleanField(label='is_template', required=False)
 | 
					    expand_sub_storage_stocks = forms.BooleanField(label='Expand sub storage Stocks', required=False)
 | 
				
			||||||
 | 
					    is_template = forms.BooleanField(label='Is template', required=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AddSubStorageForm(ChangeStorageForm):
 | 
					class AddSubStorageForm(ChangeStorageForm):
 | 
				
			||||||
    template = AutocompleteForeingKeyField(api_search_url='storage-template-list',
 | 
					    template = AutocompleteForeingKeyField(api_search_url='storage-template-list',
 | 
				
			||||||
@@ -112,10 +113,8 @@ class AddSubStorageForm(ChangeStorageForm):
 | 
				
			|||||||
class DeleteStockForm(forms.Form):
 | 
					class DeleteStockForm(forms.Form):
 | 
				
			||||||
    stock_uuid = forms.UUIDField()
 | 
					    stock_uuid = forms.UUIDField()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EditWatermarkForm(forms.Form):
 | 
					class EditStockBaseForm(forms.Form):
 | 
				
			||||||
    stock_uuid = forms.UUIDField()
 | 
					    stock_uuid = forms.UUIDField()
 | 
				
			||||||
    watermark_active = forms.BooleanField(required=False) #If it is false, the webbrowser won't send it at all. Therefore we have to set it to required=False
 | 
					 | 
				
			||||||
    watermark = forms.IntegerField(min_value=0)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def clean(self):
 | 
					    def clean(self):
 | 
				
			||||||
        cleaned_data = super().clean()
 | 
					        cleaned_data = super().clean()
 | 
				
			||||||
@@ -133,6 +132,10 @@ class EditWatermarkForm(forms.Form):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return cleaned_data
 | 
					        return cleaned_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EditWatermarkForm(EditStockBaseForm):
 | 
				
			||||||
 | 
					    watermark_active = forms.BooleanField(required=False) #If it is false, the webbrowser won't send it at all. Therefore we have to set it to required=False
 | 
				
			||||||
 | 
					    watermark = forms.IntegerField(min_value=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def save(self):
 | 
					    def save(self):
 | 
				
			||||||
        stock = self.cleaned_data['stock']
 | 
					        stock = self.cleaned_data['stock']
 | 
				
			||||||
        active = self.cleaned_data['watermark_active']
 | 
					        active = self.cleaned_data['watermark_active']
 | 
				
			||||||
@@ -144,26 +147,30 @@ class EditWatermarkForm(forms.Form):
 | 
				
			|||||||
        stock.watermark = watermark
 | 
					        stock.watermark = watermark
 | 
				
			||||||
        stock.save()
 | 
					        stock.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EditStockAmountForm(forms.Form):
 | 
					class EditLotForm(EditStockBaseForm):
 | 
				
			||||||
    stock_uuid = forms.UUIDField()
 | 
					    lot = forms.IntegerField(min_value=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def save(self):
 | 
				
			||||||
 | 
					        stock = self.cleaned_data['stock']
 | 
				
			||||||
 | 
					        lot = self.cleaned_data['lot']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stock.lot = lot
 | 
				
			||||||
 | 
					        stock.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RelocateStockForm(forms.ModelForm):
 | 
				
			||||||
 | 
					    storage = AutocompleteForeingKeyField(api_search_url='storage-list',
 | 
				
			||||||
 | 
					                                          foreign_model=parts_models.Storage,
 | 
				
			||||||
 | 
					                                          name_field_name='full_path_verbose',
 | 
				
			||||||
 | 
					                                          image_field_name=None,
 | 
				
			||||||
 | 
					                                          required=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = parts_models.Stock
 | 
				
			||||||
 | 
					        fields = ['storage']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EditStockAmountForm(EditStockBaseForm):
 | 
				
			||||||
    amount = forms.IntegerField(min_value=0)
 | 
					    amount = forms.IntegerField(min_value=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def clean(self):
 | 
					 | 
				
			||||||
        cleaned_data = super().clean()
 | 
					 | 
				
			||||||
        id = cleaned_data.get("stock_uuid")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not id:
 | 
					 | 
				
			||||||
            raise ValidationError("No stock UUID given")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        stock = None
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            stock = parts_models.Stock.objects.get(id=id)
 | 
					 | 
				
			||||||
        except:
 | 
					 | 
				
			||||||
            raise ValidationError("Stock with uuid %s does not exist" % (id))
 | 
					 | 
				
			||||||
        cleaned_data['stock'] = stock
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return cleaned_data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def save(self, increase: bool):
 | 
					    def save(self, increase: bool):
 | 
				
			||||||
        stock = self.cleaned_data['stock']
 | 
					        stock = self.cleaned_data['stock']
 | 
				
			||||||
        amount = self.cleaned_data['amount']
 | 
					        amount = self.cleaned_data['amount']
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					# Generated by Django 5.1.3 on 2025-01-25 14:01
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('parts', '0013_packageparameter'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='storage',
 | 
				
			||||||
 | 
					            name='expand_sub_storage_stocks',
 | 
				
			||||||
 | 
					            field=models.BooleanField(default=False),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					# Generated by Django 5.1.3 on 2025-01-31 21:14
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('parts', '0014_storage_expand_sub_storage_stocks'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='componenttype',
 | 
				
			||||||
 | 
					            name='description_template',
 | 
				
			||||||
 | 
					            field=models.TextField(blank=True),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					# Generated by Django 5.1.3 on 2025-01-31 21:44
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('parts', '0015_componenttype_description_template'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='componentparametertype',
 | 
				
			||||||
 | 
					            name='interfix',
 | 
				
			||||||
 | 
					            field=models.CharField(blank=True, max_length=10),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					# Generated by Django 5.1.3 on 2025-02-02 10:52
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('parts', '0016_componentparametertype_interfix'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='componentparametertype',
 | 
				
			||||||
 | 
					            name='interfix',
 | 
				
			||||||
 | 
					            field=models.CharField(blank=True, help_text='char to be used as decimal point in dynamic description eg. 2R2', max_length=10),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='componenttype',
 | 
				
			||||||
 | 
					            name='description_template',
 | 
				
			||||||
 | 
					            field=models.TextField(blank=True, help_text="Template to assemble the dynamic description. Use template syntax, access the component with 'object', parameters with 'param_*'."),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -6,6 +6,7 @@ from django.core.exceptions import ValidationError
 | 
				
			|||||||
from django.core.validators import RegexValidator
 | 
					from django.core.validators import RegexValidator
 | 
				
			||||||
from django.dispatch import receiver
 | 
					from django.dispatch import receiver
 | 
				
			||||||
from django.core.validators import MinValueValidator, MaxValueValidator
 | 
					from django.core.validators import MinValueValidator, MaxValueValidator
 | 
				
			||||||
 | 
					from django.template import engines
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import uuid
 | 
					import uuid
 | 
				
			||||||
from shimatta_modules.EngineeringNumberConverter import EngineeringNumberConverter as NumConv
 | 
					from shimatta_modules.EngineeringNumberConverter import EngineeringNumberConverter as NumConv
 | 
				
			||||||
@@ -29,6 +30,7 @@ class ComponentParameterType(models.Model):
 | 
				
			|||||||
	parameter_name = models.CharField(max_length=50, unique=True)
 | 
						parameter_name = models.CharField(max_length=50, unique=True)
 | 
				
			||||||
	parameter_description = models.TextField(null=False, blank=True)
 | 
						parameter_description = models.TextField(null=False, blank=True)
 | 
				
			||||||
	unit = models.CharField(max_length=10, null=False, blank=True)
 | 
						unit = models.CharField(max_length=10, null=False, blank=True)
 | 
				
			||||||
 | 
						interfix = models.CharField(max_length=10, null=False, blank=True, help_text="char to be used as decimal point in dynamic description eg. 2R2")
 | 
				
			||||||
	parameter_type = models.CharField(max_length=1, choices=TYPE_CHOICES, default='N')
 | 
						parameter_type = models.CharField(max_length=1, choices=TYPE_CHOICES, default='N')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def __str__(self):
 | 
						def __str__(self):
 | 
				
			||||||
@@ -49,6 +51,9 @@ class ComponentType(models.Model):
 | 
				
			|||||||
	class_name = models.CharField(max_length=50, unique=True)
 | 
						class_name = models.CharField(max_length=50, unique=True)
 | 
				
			||||||
	passive = models.BooleanField()
 | 
						passive = models.BooleanField()
 | 
				
			||||||
	possible_parameter = models.ManyToManyField(ComponentParameterType, blank=True)
 | 
						possible_parameter = models.ManyToManyField(ComponentParameterType, blank=True)
 | 
				
			||||||
 | 
						description_template = models.TextField(blank=True,
 | 
				
			||||||
 | 
															 help_text="Template to assemble the dynamic description. "
 | 
				
			||||||
 | 
															 "Use template syntax, access the component with 'object', parameters with 'param_*'.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def __str__(self):
 | 
						def __str__(self):
 | 
				
			||||||
		return '[' + self.class_name + ']'
 | 
							return '[' + self.class_name + ']'
 | 
				
			||||||
@@ -65,6 +70,7 @@ class Storage(models.Model):
 | 
				
			|||||||
	verbose_name = models.CharField(max_length=100, null=True, blank=True)
 | 
						verbose_name = models.CharField(max_length=100, null=True, blank=True)
 | 
				
			||||||
	parent_storage = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True)
 | 
						parent_storage = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True)
 | 
				
			||||||
	responsible = models.ForeignKey(get_user_model(), on_delete=models.SET_NULL, blank=True, null=True)
 | 
						responsible = models.ForeignKey(get_user_model(), on_delete=models.SET_NULL, blank=True, null=True)
 | 
				
			||||||
 | 
						expand_sub_storage_stocks = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# allow storages to be templates which can be selected when adding new storages
 | 
						# allow storages to be templates which can be selected when adding new storages
 | 
				
			||||||
	is_template = models.BooleanField(default=False)
 | 
						is_template = models.BooleanField(default=False)
 | 
				
			||||||
@@ -74,25 +80,40 @@ class Storage(models.Model):
 | 
				
			|||||||
								null=True,
 | 
													null=True,
 | 
				
			||||||
								related_name='template_of')
 | 
													related_name='template_of')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def get_path_components(self):
 | 
						# caching variable for subtrees
 | 
				
			||||||
 | 
						storage_list = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def get_path_components(self, top_level=None):
 | 
				
			||||||
		chain = []
 | 
							chain = []
 | 
				
			||||||
		iterator = self
 | 
							iterator = self
 | 
				
			||||||
		chain.append(self)
 | 
							chain.append(self)
 | 
				
			||||||
		while iterator.parent_storage is not None:
 | 
							while iterator.parent_storage is not None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if top_level and iterator.parent_storage == top_level:
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			chain.append(iterator.parent_storage)
 | 
								chain.append(iterator.parent_storage)
 | 
				
			||||||
			iterator = iterator.parent_storage
 | 
								iterator = iterator.parent_storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return chain
 | 
							return chain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def get_full_path(self):
 | 
						def get_full_path(self, top_level=None):
 | 
				
			||||||
		output = ''
 | 
							output = ''
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		chain = self.get_path_components()
 | 
							chain = self.get_path_components(top_level)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for i in range(len(chain) - 1, -1, -1):
 | 
							for i in range(len(chain) - 1, -1, -1):
 | 
				
			||||||
			output = output + '/' + chain[i].name
 | 
								output = output + '/' + chain[i].name
 | 
				
			||||||
		return output
 | 
							return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@property
 | 
				
			||||||
 | 
						def full_path_verbose(self):
 | 
				
			||||||
 | 
							full_path = f'{self.get_full_path()} ({self.id})'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if self.verbose_name:
 | 
				
			||||||
 | 
								full_path += f' ({self.verbose_name})'
 | 
				
			||||||
 | 
							return full_path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def get_qr_code(self):
 | 
						def get_qr_code(self):
 | 
				
			||||||
		qrdata = '[stor_uuid]' + str(self.id)
 | 
							qrdata = '[stor_uuid]' + str(self.id)
 | 
				
			||||||
		return qrdata
 | 
							return qrdata
 | 
				
			||||||
@@ -106,6 +127,19 @@ class Storage(models.Model):
 | 
				
			|||||||
		else:
 | 
							else:
 | 
				
			||||||
			return self.storage_set.all()
 | 
								return self.storage_set.all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@classmethod
 | 
				
			||||||
 | 
						def get_substorage_list(cls, sub_storages):
 | 
				
			||||||
 | 
							sub_sub_storages = cls.objects.filter(parent_storage__in=sub_storages)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							final_sub_storages = sub_storages | sub_sub_storages
 | 
				
			||||||
 | 
							if sub_sub_storages:
 | 
				
			||||||
 | 
								final_sub_storages |= cls.get_substorage_list(sub_sub_storages)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return final_sub_storages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def get_storage_list(self):
 | 
				
			||||||
 | 
							return Storage.objects.filter(id=self.id) | self.get_substorage_list(self.storage_set.all())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def validate_unique(self, exclude=None):
 | 
						def validate_unique(self, exclude=None):
 | 
				
			||||||
		if Storage.objects.exclude(id=self.id).filter(name=self.name, parent_storage__isnull=True).exists():
 | 
							if Storage.objects.exclude(id=self.id).filter(name=self.name, parent_storage__isnull=True).exists():
 | 
				
			||||||
			if self.parent_storage is None:
 | 
								if self.parent_storage is None:
 | 
				
			||||||
@@ -116,12 +150,18 @@ class Storage(models.Model):
 | 
				
			|||||||
			raise
 | 
								raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def get_total_stock_amount(self):
 | 
						def get_total_stock_amount(self):
 | 
				
			||||||
		stocks = Stock.objects.filter(storage=self)
 | 
							stocks = Stock.objects.filter(storage__in=self.storage_list or self.get_storage_list())
 | 
				
			||||||
		sum = stocks.aggregate(Sum('amount'))['amount__sum']
 | 
							sum = stocks.aggregate(Sum('amount'))['amount__sum']
 | 
				
			||||||
		if sum is None:
 | 
							if sum is None:
 | 
				
			||||||
			sum = 0
 | 
								sum = 0
 | 
				
			||||||
		return sum
 | 
							return sum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def get_total_stock_count(self):
 | 
				
			||||||
 | 
							return Stock.objects.filter(storage__in=self.storage_list or self.get_storage_list()).count()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def get_total_substorage_amount(self):
 | 
				
			||||||
 | 
							return len(self.storage_list or self.get_storage_list()) - 1 # -1 as thhe storage list counts the parent storage as well
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@classmethod
 | 
						@classmethod
 | 
				
			||||||
	def from_path(cls, path, root_storage=None):
 | 
						def from_path(cls, path, root_storage=None):
 | 
				
			||||||
		'''
 | 
							'''
 | 
				
			||||||
@@ -238,6 +278,22 @@ class Component(models.Model):
 | 
				
			|||||||
			sum = 0
 | 
								sum = 0
 | 
				
			||||||
		return sum
 | 
							return sum
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						@property
 | 
				
			||||||
 | 
						def dynamic_description(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if not self.component_type or not self.component_type.description_template:
 | 
				
			||||||
 | 
								return ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							django_engine = engines["django"]
 | 
				
			||||||
 | 
							template = django_engine.from_string(self.component_type.description_template)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							parameters = list(ComponentParameter.objects.filter(component=self))
 | 
				
			||||||
 | 
							parameters += list(PackageParameter.objects.filter(package=self.package))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							context = {f'param_{param.parameter_type.parameter_name}': param for param in parameters}
 | 
				
			||||||
 | 
							context.update({'object': self})
 | 
				
			||||||
 | 
							return template.render(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AbstractParameter(models.Model):
 | 
					class AbstractParameter(models.Model):
 | 
				
			||||||
	class Meta:
 | 
						class Meta:
 | 
				
			||||||
		abstract = True
 | 
							abstract = True
 | 
				
			||||||
@@ -271,6 +327,25 @@ class AbstractParameter(models.Model):
 | 
				
			|||||||
		elif my_type == 'F':
 | 
							elif my_type == 'F':
 | 
				
			||||||
			return self.text_value
 | 
								return self.text_value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def resolved_value_as_short_string(self):
 | 
				
			||||||
 | 
							my_type = self.parameter_type.parameter_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if my_type == 'E' or my_type == 'I':
 | 
				
			||||||
 | 
								# Engineering float number
 | 
				
			||||||
 | 
								(num, prefix) = NumConv.number_to_engineering(self.value, it_unit=(True if my_type=='I' else False))
 | 
				
			||||||
 | 
								result = f'{round(num, 3):g}'
 | 
				
			||||||
 | 
								interpostfix = (prefix if prefix else self.parameter_type.interfix or '.')
 | 
				
			||||||
 | 
								if '.' in result:
 | 
				
			||||||
 | 
									result = result.replace('.', interpostfix)
 | 
				
			||||||
 | 
								else:
 | 
				
			||||||
 | 
									result = result + interpostfix
 | 
				
			||||||
 | 
								return result
 | 
				
			||||||
 | 
							elif my_type == 'N':
 | 
				
			||||||
 | 
								# Standard float number
 | 
				
			||||||
 | 
								return  f'{round(self.value, 3):g}{self.parameter_type.unit}'
 | 
				
			||||||
 | 
							else:
 | 
				
			||||||
 | 
								return self.resolved_value_as_string()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ComponentParameter(AbstractParameter):
 | 
					class ComponentParameter(AbstractParameter):
 | 
				
			||||||
	class Meta:
 | 
						class Meta:
 | 
				
			||||||
		unique_together = ('component', 'parameter_type')
 | 
							unique_together = ('component', 'parameter_type')
 | 
				
			||||||
@@ -308,7 +383,7 @@ class Stock(models.Model):
 | 
				
			|||||||
		return True
 | 
							return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def get_qr_code(self):
 | 
						def get_qr_code(self):
 | 
				
			||||||
		qr_data = '[stock]'+str(self.id)
 | 
							qr_data = '[stck_uuid]'+str(self.id)
 | 
				
			||||||
		return qr_data
 | 
							return qr_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def __str__(self):
 | 
						def __str__(self):
 | 
				
			||||||
@@ -407,5 +482,6 @@ def auto_apply_template_structure(sender, instance, created, **kwargs):
 | 
				
			|||||||
                Storage.objects.create(name=sub_storage.name,
 | 
					                Storage.objects.create(name=sub_storage.name,
 | 
				
			||||||
                                       parent_storage=instance,
 | 
					                                       parent_storage=instance,
 | 
				
			||||||
                                       responsible=instance.responsible,
 | 
					                                       responsible=instance.responsible,
 | 
				
			||||||
 | 
					                                       expand_sub_storage_stocks=instance.expand_sub_storage_stocks,
 | 
				
			||||||
                                       is_template=False,
 | 
					                                       is_template=False,
 | 
				
			||||||
                                       template=sub_storage)
 | 
					                                       template=sub_storage)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ from django.core.exceptions import ValidationError, ObjectDoesNotExist
 | 
				
			|||||||
from django.urls import reverse as url_reverse
 | 
					from django.urls import reverse as url_reverse
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import Storage, Component
 | 
					from .models import Storage, Component, Stock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class QrCode:
 | 
					class QrCode:
 | 
				
			||||||
    prefix = ''
 | 
					    prefix = ''
 | 
				
			||||||
@@ -19,6 +19,7 @@ class QrCodeValidator:
 | 
				
			|||||||
    qr_patterns = {
 | 
					    qr_patterns = {
 | 
				
			||||||
        'stor_uuid': QrCode('stor_uuid', 'parts-stocks-detail', Storage),
 | 
					        'stor_uuid': QrCode('stor_uuid', 'parts-stocks-detail', Storage),
 | 
				
			||||||
        'comp_uuid': QrCode('comp_uuid', 'parts-components-detail', Component),
 | 
					        'comp_uuid': QrCode('comp_uuid', 'parts-components-detail', Component),
 | 
				
			||||||
 | 
					        'stck_uuid': QrCode('stck_uuid', 'parts-stock-detail', Stock),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
@@ -32,16 +33,13 @@ class QrCodeValidator:
 | 
				
			|||||||
        
 | 
					        
 | 
				
			||||||
        qr_type = matches.group('prefix')
 | 
					        qr_type = matches.group('prefix')
 | 
				
			||||||
        qr_uuid = matches.group('uuid')
 | 
					        qr_uuid = matches.group('uuid')
 | 
				
			||||||
        url_name = self.qr_patterns[qr_type].detail_view
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        model = None
 | 
					 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
 | 
					            url_name = self.qr_patterns[qr_type].detail_view
 | 
				
			||||||
            model = self.qr_patterns[qr_type].model
 | 
					            model = self.qr_patterns[qr_type].model
 | 
				
			||||||
        except:
 | 
					        except KeyError as ex:
 | 
				
			||||||
            model = None
 | 
					            raise ValidationError('QR Pattern not registered') from ex
 | 
				
			||||||
        if model is None:
 | 
					        return (model, qr_uuid, url_name)
 | 
				
			||||||
            raise ValidationError('QR Pattern not registered')
 | 
					 | 
				
			||||||
        return (model,qr_uuid, url_name)
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    def get_redirect_url(self, data):
 | 
					    def get_redirect_url(self, data):
 | 
				
			||||||
        model, uuid, url_name = self._get_model_from_qr(data)
 | 
					        model, uuid, url_name = self._get_model_from_qr(data)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										0
									
								
								shimatta_kenkyusho/parts/templatetags/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								shimatta_kenkyusho/parts/templatetags/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										9
									
								
								shimatta_kenkyusho/parts/templatetags/storage_tags.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								shimatta_kenkyusho/parts/templatetags/storage_tags.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					from django import template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					register = template.Library()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@register.filter(name="get_relative_storage_path")
 | 
				
			||||||
 | 
					def get_relative_storage_path(storage, top_level):
 | 
				
			||||||
 | 
					    return f'.{storage.get_full_path(top_level)}'
 | 
				
			||||||
@@ -74,7 +74,13 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
 | 
				
			|||||||
        return crumbs
 | 
					        return crumbs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def search_stock_queryset(self, search):
 | 
					    def search_stock_queryset(self, search):
 | 
				
			||||||
        stocks_in_storage = Stock.objects.filter(storage=self.object).order_by(Lower('component__name'))
 | 
					
 | 
				
			||||||
 | 
					        if self.object.expand_sub_storage_stocks:
 | 
				
			||||||
 | 
					            stocks_in_storage = Stock.objects.filter(storage__in=self.object.get_storage_list())
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            stocks_in_storage = Stock.objects.filter(storage=self.object)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stocks_in_storage = stocks_in_storage.order_by(Lower('component__name'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if search is None or search == '':
 | 
					        if search is None or search == '':
 | 
				
			||||||
            return stocks_in_storage
 | 
					            return stocks_in_storage
 | 
				
			||||||
@@ -115,14 +121,16 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
 | 
				
			|||||||
        context['storages'] = storage_paginator.get_page(storage_page)
 | 
					        context['storages'] = storage_paginator.get_page(storage_page)
 | 
				
			||||||
        stocks = stock_paginator.get_page(componente_stock_page)
 | 
					        stocks = stock_paginator.get_page(componente_stock_page)
 | 
				
			||||||
        context['stocks'] = stocks
 | 
					        context['stocks'] = stocks
 | 
				
			||||||
 | 
					        context['stocks_with_forms'] = [{'object': s, 'relocate_form': RelocateStockForm(instance=s, prefix=str(s.id))} for s in stocks]
 | 
				
			||||||
        context['stock_search'] = stock_search_input
 | 
					        context['stock_search'] = stock_search_input
 | 
				
			||||||
        add_storage_form = AddSubStorageForm()
 | 
					        add_storage_form = AddSubStorageForm()
 | 
				
			||||||
        add_storage_form.fields['responsible'].initial = self.request.user.id
 | 
					        add_storage_form.fields['responsible'].initial = self.request.user.id
 | 
				
			||||||
        context['add_storage_form'] = add_storage_form
 | 
					        context['add_storage_form'] = add_storage_form
 | 
				
			||||||
        change_storage_form = ChangeStorageForm()
 | 
					        change_storage_form = ChangeStorageForm(prefix='change_storage')
 | 
				
			||||||
        change_storage_form.fields['storage_name'].initial = self.object.name
 | 
					        change_storage_form.fields['storage_name'].initial = self.object.name
 | 
				
			||||||
        change_storage_form.fields['verbose_name'].initial = self.object.verbose_name
 | 
					        change_storage_form.fields['verbose_name'].initial = self.object.verbose_name
 | 
				
			||||||
        change_storage_form.fields['responsible'].initial = self.object.responsible.id
 | 
					        change_storage_form.fields['responsible'].initial = self.object.responsible.id
 | 
				
			||||||
 | 
					        change_storage_form.fields['expand_sub_storage_stocks'].initial = self.object.expand_sub_storage_stocks
 | 
				
			||||||
        change_storage_form.fields['is_template'].initial = self.object.is_template
 | 
					        change_storage_form.fields['is_template'].initial = self.object.is_template
 | 
				
			||||||
        context['change_storage_form'] = change_storage_form
 | 
					        context['change_storage_form'] = change_storage_form
 | 
				
			||||||
        context['delete_storage_error'] = None
 | 
					        context['delete_storage_error'] = None
 | 
				
			||||||
@@ -139,6 +147,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
 | 
				
			|||||||
                                        verbose_name=f.cleaned_data.get('verbose_name'),
 | 
					                                        verbose_name=f.cleaned_data.get('verbose_name'),
 | 
				
			||||||
                                        parent_storage=self.object,
 | 
					                                        parent_storage=self.object,
 | 
				
			||||||
                                        responsible=f.cleaned_data['responsible'],
 | 
					                                        responsible=f.cleaned_data['responsible'],
 | 
				
			||||||
 | 
					                                        expand_sub_storage_stocks=f.cleaned_data['expand_sub_storage_stocks'],
 | 
				
			||||||
                                        is_template=f.cleaned_data['is_template'],
 | 
					                                        is_template=f.cleaned_data['is_template'],
 | 
				
			||||||
                                        template=f.cleaned_data.get('template'))
 | 
					                                        template=f.cleaned_data.get('template'))
 | 
				
			||||||
            except ValidationError as v_err:
 | 
					            except ValidationError as v_err:
 | 
				
			||||||
@@ -148,12 +157,13 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
 | 
				
			|||||||
        return self.render_to_response(context)
 | 
					        return self.render_to_response(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_change_storage_post(self, request, **kwargs):
 | 
					    def handle_change_storage_post(self, request, **kwargs):
 | 
				
			||||||
        f = ChangeStorageForm(data=request.POST)
 | 
					        f = ChangeStorageForm(data=request.POST, prefix='change_storage')
 | 
				
			||||||
        if f.is_valid():
 | 
					        if f.is_valid():
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                self.object.name = f.cleaned_data['storage_name']
 | 
					                self.object.name = f.cleaned_data['storage_name']
 | 
				
			||||||
                self.object.verbose_name = f.cleaned_data.get('verbose_name')
 | 
					                self.object.verbose_name = f.cleaned_data.get('verbose_name')
 | 
				
			||||||
                self.object.responsible = f.cleaned_data['responsible']
 | 
					                self.object.responsible = f.cleaned_data['responsible']
 | 
				
			||||||
 | 
					                self.object.expand_sub_storage_stocks = f.cleaned_data['expand_sub_storage_stocks']
 | 
				
			||||||
                self.object.is_template = f.cleaned_data['is_template']
 | 
					                self.object.is_template = f.cleaned_data['is_template']
 | 
				
			||||||
                self.object.save()
 | 
					                self.object.save()
 | 
				
			||||||
            except ValidationError as v_err:
 | 
					            except ValidationError as v_err:
 | 
				
			||||||
@@ -205,6 +215,27 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
 | 
				
			|||||||
        context = self.get_context_data(**kwargs)
 | 
					        context = self.get_context_data(**kwargs)
 | 
				
			||||||
        return self.render_to_response(context)
 | 
					        return self.render_to_response(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_update_lot(self, request, **kwargs):
 | 
				
			||||||
 | 
					        edit_form = EditLotForm(data=request.POST)
 | 
				
			||||||
 | 
					        if edit_form.is_valid():
 | 
				
			||||||
 | 
					            edit_form.save()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            pass # Todo: Handle error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        context = self.get_context_data(**kwargs)
 | 
				
			||||||
 | 
					        return self.render_to_response(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_relocate_stock(self, request, **kwargs):
 | 
				
			||||||
 | 
					        instance = Stock.objects.get(id=request.POST['prefix'])
 | 
				
			||||||
 | 
					        edit_form = RelocateStockForm(instance=instance, data=request.POST, prefix=request.POST['prefix'])
 | 
				
			||||||
 | 
					        if edit_form.is_valid():
 | 
				
			||||||
 | 
					            edit_form.save()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            pass # Todo: Handle error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        context = self.get_context_data(**kwargs)
 | 
				
			||||||
 | 
					        return self.render_to_response(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_amount_change_post(self, request, increase, **kwargs):
 | 
					    def handle_amount_change_post(self, request, increase, **kwargs):
 | 
				
			||||||
        edit_form = EditStockAmountForm(data=request.POST)
 | 
					        edit_form = EditStockAmountForm(data=request.POST)
 | 
				
			||||||
        if edit_form.is_valid():
 | 
					        if edit_form.is_valid():
 | 
				
			||||||
@@ -244,6 +275,10 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
 | 
				
			|||||||
            return self.handle_del_stock_post(request, **kwargs)
 | 
					            return self.handle_del_stock_post(request, **kwargs)
 | 
				
			||||||
        elif 'submit-edit-watermark' in request.POST:
 | 
					        elif 'submit-edit-watermark' in request.POST:
 | 
				
			||||||
            return self.handle_update_watermark(request, **kwargs)
 | 
					            return self.handle_update_watermark(request, **kwargs)
 | 
				
			||||||
 | 
					        elif 'submit-edit-lot' in request.POST:
 | 
				
			||||||
 | 
					            return self.handle_update_lot(request, **kwargs)
 | 
				
			||||||
 | 
					        elif 'submit-relocate-stock' in request.POST:
 | 
				
			||||||
 | 
					            return self.handle_relocate_stock(request, **kwargs)
 | 
				
			||||||
        elif 'submit-amount-reduce' in request.POST:
 | 
					        elif 'submit-amount-reduce' in request.POST:
 | 
				
			||||||
            return self.handle_amount_change_post(request, False, **kwargs)
 | 
					            return self.handle_amount_change_post(request, False, **kwargs)
 | 
				
			||||||
        elif 'submit-amount-increase' in request.POST:
 | 
					        elif 'submit-amount-increase' in request.POST:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,6 +83,13 @@
 | 
				
			|||||||
            const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
 | 
					            const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
 | 
				
			||||||
            const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
 | 
					            const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
 | 
				
			||||||
        </script>
 | 
					        </script>
 | 
				
			||||||
 | 
					        <!-- Initialize bootstrap tooltips -->
 | 
				
			||||||
 | 
					        <script type="text/javascript">
 | 
				
			||||||
 | 
					            const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
 | 
				
			||||||
 | 
					            const tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
 | 
				
			||||||
 | 
					                return new bootstrap.Tooltip(tooltipTriggerEl)
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        </script>
 | 
				
			||||||
        <!-- Select search field on start of QR scan if no input is currently selevted([) -->
 | 
					        <!-- Select search field on start of QR scan if no input is currently selevted([) -->
 | 
				
			||||||
        <script type="text/javascript">
 | 
					        <script type="text/javascript">
 | 
				
			||||||
            window.addEventListener("keydown", (event)=>{
 | 
					            window.addEventListener("keydown", (event)=>{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -76,6 +76,11 @@
 | 
				
			|||||||
                            No description available
 | 
					                            No description available
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    {% endif %}
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    {% if component.dynamic_description %}
 | 
				
			||||||
 | 
					                    <h2>Dynamic Description</h2>
 | 
				
			||||||
 | 
					                    <pre>{{ component.dynamic_description }}</pre>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="col-4">
 | 
					                <div class="col-4">
 | 
				
			||||||
                    {% if component.pref_distri %}
 | 
					                    {% if component.pref_distri %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,7 +54,12 @@
 | 
				
			|||||||
                                Manufacturer: {{comp.manufacturer}}
 | 
					                                Manufacturer: {{comp.manufacturer}}
 | 
				
			||||||
                            {% endif %}
 | 
					                            {% endif %}
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                        <span class="badge bg-primary rounded-pill">{{comp.get_total_amount}}</span>
 | 
					                        <div class="flex-grow-1 d-block ms-3" style="text-align: right;">
 | 
				
			||||||
 | 
					                            <pre>{{ comp.dynamic_description }}</pre>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div style="width: 10%; text-align: right;">
 | 
				
			||||||
 | 
					                            <span class="badge bg-primary rounded-pill me-2">{{comp.get_total_amount}}</span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
                    </li>
 | 
					                    </li>
 | 
				
			||||||
                    </a>
 | 
					                    </a>
 | 
				
			||||||
                {% endfor %}
 | 
					                {% endfor %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,6 +47,21 @@ needs following context:
 | 
				
			|||||||
                            <input type="submit" class="btn btn-primary" name="submit-edit-watermark" value="Update Watermark">
 | 
					                            <input type="submit" class="btn btn-primary" name="submit-edit-watermark" value="Update Watermark">
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </form>
 | 
					                    </form>
 | 
				
			||||||
 | 
					                    <form method="post">
 | 
				
			||||||
 | 
					                        {% csrf_token %}
 | 
				
			||||||
 | 
					                        <input type="hidden" name="stock_uuid" value="{{stock.id}}">
 | 
				
			||||||
 | 
					                        <div class="input-group mb-3">
 | 
				
			||||||
 | 
					                            <input type="text" name="lot" id="ch-stk-lot-{{stock.id}}" class="form-control" value="{{stock.lot}}" required>
 | 
				
			||||||
 | 
					                            <input type="submit" class="btn btn-primary" name="submit-edit-lot" value="Update Lot">
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </form>
 | 
				
			||||||
 | 
					                    <form method="post">
 | 
				
			||||||
 | 
					                        {% csrf_token %}
 | 
				
			||||||
 | 
					                        <input type="hidden" name="prefix" value="{{relocate_form.prefix}}">
 | 
				
			||||||
 | 
					                        {{ relocate_form|crispy }}
 | 
				
			||||||
 | 
					                        <input type="submit" class="btn btn-warning" name="submit-relocate-stock" value="Relocate Stock">
 | 
				
			||||||
 | 
					                    </form>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
{% load qr_code %}
 | 
					{% load qr_code %}
 | 
				
			||||||
{% load static %}
 | 
					{% load static %}
 | 
				
			||||||
{% load crispy_forms_tags %}
 | 
					{% load crispy_forms_tags %}
 | 
				
			||||||
 | 
					{% load storage_tags %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
<div class="container">
 | 
					<div class="container">
 | 
				
			||||||
@@ -21,13 +22,28 @@
 | 
				
			|||||||
                {% qr_from_text object.get_qr_code size="m" image_format="svg" %}
 | 
					                {% qr_from_text object.get_qr_code size="m" image_format="svg" %}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="row">
 | 
					            <div class="row">
 | 
				
			||||||
                <h4>{{storage.name}}{% if storage.verbose_name %}<small> ({{storage.verbose_name}})</small>{% endif %}</h4>
 | 
					                <h4>{{storage.name}}
 | 
				
			||||||
 | 
					                    {% if storage.verbose_name %}
 | 
				
			||||||
 | 
					                    <small>
 | 
				
			||||||
 | 
					                        ({{storage.verbose_name}})
 | 
				
			||||||
 | 
					                    </small>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                    {% if storage.is_template %}
 | 
				
			||||||
 | 
					                    <small>
 | 
				
			||||||
 | 
					                        (template)
 | 
				
			||||||
 | 
					                    </small>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                </h4>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="row">
 | 
					            <div class="row">
 | 
				
			||||||
                {% if object.parent_storage %}
 | 
					                {% if object.parent_storage %}
 | 
				
			||||||
                <h1>Sub-Storages <a class="btn btn-secondary" href="{% url 'parts-stocks-detail' uuid=object.parent_storage.id %}">Parent Storage</a>            {% else %}
 | 
					                <h1>Sub-Storages <a class="btn btn-secondary" href="{% url 'parts-stocks-detail' uuid=object.parent_storage.id %}">Parent Storage</a>
 | 
				
			||||||
 | 
					                {% else %}
 | 
				
			||||||
                <h1>Sub-Storages <a class="btn btn-secondary" href="{% url 'parts-stocks'%}">Stock Overview</a>
 | 
					                <h1>Sub-Storages <a class="btn btn-secondary" href="{% url 'parts-stocks'%}">Stock Overview</a>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                {% if storage.template %}
 | 
				
			||||||
 | 
					                <a class="btn btn-secondary" href="{% url 'parts-stocks-detail' uuid=storage.template.id %}">Template</a>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
                <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#delete-storage-modal">Delete</button>
 | 
					                <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#delete-storage-modal">Delete</button>
 | 
				
			||||||
                <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-sub-modal"><i class="bi bi-plus-circle"></i></button>
 | 
					                <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-sub-modal"><i class="bi bi-plus-circle"></i></button>
 | 
				
			||||||
                <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#change-modal"><i class="bi bi-pencil-square"></i></button>
 | 
					                <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#change-modal"><i class="bi bi-pencil-square"></i></button>
 | 
				
			||||||
@@ -40,7 +56,9 @@
 | 
				
			|||||||
                            <h5>{{storage.name}}{% if storage.verbose_name %}<small> ({{storage.verbose_name}})</small>{% endif %}</h5>
 | 
					                            <h5>{{storage.name}}{% if storage.verbose_name %}<small> ({{storage.verbose_name}})</small>{% endif %}</h5>
 | 
				
			||||||
                            Responsible: {{ storage.responsible }}
 | 
					                            Responsible: {{ storage.responsible }}
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                            <span class="badge bg-primary rounded-pill">{{storage.get_total_stock_amount}}</span>
 | 
					                            <span class="badge ms-1 bg-primary rounded-pill" data-bs-toggle="tooltip" data-bs-placement="top" title="Total number of stored parts">{{storage.get_total_stock_amount}}</span>
 | 
				
			||||||
 | 
					                            <span class="badge ms-1 bg-secondary rounded-pill d-none d-lg-block" data-bs-toggle="tooltip" data-bs-placement="top" title="Number of stored lots">{{storage.get_total_stock_count}}</span>
 | 
				
			||||||
 | 
					                            <span class="badge ms-1 bg-info rounded-pill d-none d-lg-block" data-bs-toggle="tooltip" data-bs-placement="top" title="Number of substorages">{{storage.get_total_substorage_amount}}</span>
 | 
				
			||||||
                        </li>
 | 
					                        </li>
 | 
				
			||||||
                    </a>
 | 
					                    </a>
 | 
				
			||||||
                {% endfor %}
 | 
					                {% endfor %}
 | 
				
			||||||
@@ -80,11 +98,20 @@
 | 
				
			|||||||
                            {% if stock.component.manufacturer %}
 | 
					                            {% if stock.component.manufacturer %}
 | 
				
			||||||
                                Manufacturer: {{stock.component.manufacturer}}
 | 
					                                Manufacturer: {{stock.component.manufacturer}}
 | 
				
			||||||
                            {% endif %}
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                            {% if stock.storage != storage %}
 | 
				
			||||||
 | 
					                            <span class="text-secondary"><br>{{ stock.storage|get_relative_storage_path:storage }}</span>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
                            {% if stock.lot %}
 | 
					                            {% if stock.lot %}
 | 
				
			||||||
                                <span class="text-secondary"><br>Lot: {{stock.lot}}</span>
 | 
					                                <span class="text-secondary"><br>Lot: {{stock.lot}}</span>
 | 
				
			||||||
                            {% endif %}
 | 
					                            {% endif %}
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                        <div class="ms-3">
 | 
					                        <div class="flex-grow-2 ms-3 d-none d-lg-block" style="text-align: right;">
 | 
				
			||||||
 | 
					                            <pre>{{ stock.component.dynamic_description }}</pre>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="flex-grow-2 ms-5 d-none d-lg-block">
 | 
				
			||||||
 | 
					                            {% qr_from_text stock.get_qr_code size="6" image_format="svg" %}
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="ms-3" style="width: 20%;">
 | 
				
			||||||
                            Amount: {{stock.amount}}
 | 
					                            Amount: {{stock.amount}}
 | 
				
			||||||
                            {% if stock.watermark >= 0 %}
 | 
					                            {% if stock.watermark >= 0 %}
 | 
				
			||||||
                            <br>Watermark: {{stock.watermark}}
 | 
					                            <br>Watermark: {{stock.watermark}}
 | 
				
			||||||
@@ -108,14 +135,14 @@
 | 
				
			|||||||
            {% include 'paginator.html' with paginator=stocks get_param='stock_page' aria_label='Stock Page Navigation' %}
 | 
					            {% include 'paginator.html' with paginator=stocks get_param='stock_page' aria_label='Stock Page Navigation' %}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    {% for stock in stocks  %}
 | 
					    {% for stock in stocks_with_forms  %}
 | 
				
			||||||
    {% include 'parts/modals/update-stock-modal.html' with stock=stock form=change_stock_form %}
 | 
					    {% include 'parts/modals/update-stock-modal.html' with stock=stock.object form=change_stock_form relocate_form=stock.relocate_form %}
 | 
				
			||||||
    {% endfor %}
 | 
					    {% endfor %}
 | 
				
			||||||
    <!-- Modal for adding a substorage-->
 | 
					    <!-- Modal for adding a substorage-->
 | 
				
			||||||
    {% with add_storage_form as form %}
 | 
					    {% with add_storage_form as form %}
 | 
				
			||||||
    {% include 'parts/modals/add-substorage-modal.html' %}
 | 
					    {% include 'parts/modals/add-substorage-modal.html' %}
 | 
				
			||||||
    {% endwith %}
 | 
					    {% endwith %}
 | 
				
			||||||
    <!-- Modal to change current storag-->
 | 
					    <!-- Modal to change current storage-->
 | 
				
			||||||
    {% with change_storage_form as form %}
 | 
					    {% with change_storage_form as form %}
 | 
				
			||||||
    {% include 'parts/modals/change-storage-modal.html' %}
 | 
					    {% include 'parts/modals/change-storage-modal.html' %}
 | 
				
			||||||
    {% endwith %}
 | 
					    {% endwith %}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user