mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 03:00:54 +00:00
[Feature] Stocktake reports (#4345)
* Add settings to control upcoming stocktake features * Adds migration for "cost range" when performing stocktake * Add cost data to PartStocktakeSerializer Implement a new custom serializer for currency data type * Refactor existing currency serializers * Update stocktake table and forms * Prevent trailing zeroes in forms * Calculate cost range when adding manual stocktake entry * Display interactive chart for part stocktake history * Ensure chart data are converted to common currency * Adds new model for building stocktake reports * Add admin integration for new model * Adds API endpoint to expose list of stocktake reports available for download - No ability to edit or delete via API * Add setting to control automated deletion of old stocktake reports * Updates for settings page - Load part stocktake report table - Refactor function to render a downloadable media file - Fix bug with forcing files to be downloaded - Split js code into separate templates - Make use of onPanelLoad functionalitty * Fix conflicting migration files * Adds API endpoint for manual generation of stocktake report * Offload task to generate new stocktake report * Adds python function to perform stocktake on a single part instance * Small bug fixes * Various tweaks - Prevent new stocktake models from triggering plugin events when created - Construct a simple csv dataset * Generate new report * Updates for report generation - Prefetch related data - Add extra columns - Keep track of stocktake instances (for saving to database later on) * Updates: - Add confirmation message - Serializer validation checks * Ensure that background worker is running before manually scheduling a new stocktake report * Add extra fields to stocktake models Also move code from part/models.py to part/tasks.py * Add 'part_count' to PartStocktakeReport table * Updates for stocktake generation - remove old performStocktake javascript code - Now handled by automated server-side calculation - Generate report for a single part * Add a new "role" for stocktake - Allows fine-grained control on viewing / creating / deleting stocktake data - More in-line with existing permission controls - Remove STOCKTAKE_OWNER setting * Add serializer field to limit stocktake report to particular locations * Use location restriction when generating a stocktake report * Add UI buttons to perform stocktake for a whole category tree * Add button to perform stocktake report for a location tree * Adds a background tasks to handle periodic generation of stocktake reports - Reports are generated at fixed intervals - Deletes old reports after certain number of days * Implement notifications for new stocktake reports - If manually requested by a user, notify that user - Cleanup notification table - Amend PartStocktakeModel for better notification rendering * Hide buttons on location and category page if stocktake is not enabled * Cleanup log messages during server start * Extend functionality of RoleRequired permission mixin - Allow 'role_required' attribute to be added to an API view - Useful when using a serializer class that does not have a model defined * Add boolean option to toggle whether a report will be generated * Update generateStocktake function * Improve location filtering - Don't limit the actual stock items - Instead, select only parts which exist within a given location tree * Update API version * String tweaks * Fix permissions for PartStocktake API * More unit testing for stocktake functionality * QoL fix * Fix for assigning inherited permissions
This commit is contained in:
@ -15,28 +15,32 @@ from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from sql_util.utils import SubqueryCount, SubquerySum
|
||||
|
||||
import common.models
|
||||
import company.models
|
||||
import InvenTree.helpers
|
||||
import InvenTree.status
|
||||
import part.filters
|
||||
import part.tasks
|
||||
import stock.models
|
||||
from common.settings import currency_code_default, currency_code_mappings
|
||||
from InvenTree.serializers import (DataFileExtractSerializer,
|
||||
DataFileUploadSerializer,
|
||||
InvenTreeAttachmentSerializer,
|
||||
InvenTreeAttachmentSerializerField,
|
||||
InvenTreeCurrencySerializer,
|
||||
InvenTreeDecimalField,
|
||||
InvenTreeImageSerializerField,
|
||||
InvenTreeModelSerializer,
|
||||
InvenTreeMoneySerializer, RemoteImageMixin,
|
||||
UserSerializer)
|
||||
from InvenTree.status_codes import BuildStatus
|
||||
from InvenTree.tasks import offload_task
|
||||
|
||||
from .models import (BomItem, BomItemSubstitute, Part, PartAttachment,
|
||||
PartCategory, PartCategoryParameterTemplate,
|
||||
PartInternalPriceBreak, PartParameter,
|
||||
PartParameterTemplate, PartPricing, PartRelated,
|
||||
PartSellPriceBreak, PartStar, PartStocktake,
|
||||
PartTestTemplate)
|
||||
PartStocktakeReport, PartTestTemplate)
|
||||
|
||||
|
||||
class CategorySerializer(InvenTreeModelSerializer):
|
||||
@ -137,16 +141,9 @@ class PartSalePriceSerializer(InvenTreeModelSerializer):
|
||||
|
||||
quantity = InvenTreeDecimalField()
|
||||
|
||||
price = InvenTreeMoneySerializer(
|
||||
allow_null=True
|
||||
)
|
||||
price = InvenTreeMoneySerializer(allow_null=True)
|
||||
|
||||
price_currency = serializers.ChoiceField(
|
||||
choices=currency_code_mappings(),
|
||||
default=currency_code_default,
|
||||
label=_('Currency'),
|
||||
help_text=_('Purchase currency of this stock item'),
|
||||
)
|
||||
price_currency = InvenTreeCurrencySerializer(help_text=_('Purchase currency of this stock item'))
|
||||
|
||||
class Meta:
|
||||
"""Metaclass defining serializer fields"""
|
||||
@ -169,12 +166,7 @@ class PartInternalPriceSerializer(InvenTreeModelSerializer):
|
||||
allow_null=True
|
||||
)
|
||||
|
||||
price_currency = serializers.ChoiceField(
|
||||
choices=currency_code_mappings(),
|
||||
default=currency_code_default,
|
||||
label=_('Currency'),
|
||||
help_text=_('Purchase currency of this stock item'),
|
||||
)
|
||||
price_currency = InvenTreeCurrencySerializer(help_text=_('Purchase currency of this stock item'))
|
||||
|
||||
class Meta:
|
||||
"""Metaclass defining serializer fields"""
|
||||
@ -720,6 +712,12 @@ class PartStocktakeSerializer(InvenTreeModelSerializer):
|
||||
|
||||
user_detail = UserSerializer(source='user', read_only=True, many=False)
|
||||
|
||||
cost_min = InvenTreeMoneySerializer(allow_null=True)
|
||||
cost_min_currency = InvenTreeCurrencySerializer()
|
||||
|
||||
cost_max = InvenTreeMoneySerializer(allow_null=True)
|
||||
cost_max_currency = InvenTreeCurrencySerializer()
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options"""
|
||||
|
||||
@ -728,7 +726,12 @@ class PartStocktakeSerializer(InvenTreeModelSerializer):
|
||||
'pk',
|
||||
'date',
|
||||
'part',
|
||||
'item_count',
|
||||
'quantity',
|
||||
'cost_min',
|
||||
'cost_min_currency',
|
||||
'cost_max',
|
||||
'cost_max_currency',
|
||||
'note',
|
||||
'user',
|
||||
'user_detail',
|
||||
@ -751,6 +754,92 @@ class PartStocktakeSerializer(InvenTreeModelSerializer):
|
||||
super().save()
|
||||
|
||||
|
||||
class PartStocktakeReportSerializer(InvenTreeModelSerializer):
|
||||
"""Serializer for stocktake report class"""
|
||||
|
||||
user_detail = UserSerializer(source='user', read_only=True, many=False)
|
||||
|
||||
report = InvenTreeAttachmentSerializerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
"""Metaclass defines serializer fields"""
|
||||
|
||||
model = PartStocktakeReport
|
||||
fields = [
|
||||
'pk',
|
||||
'date',
|
||||
'report',
|
||||
'part_count',
|
||||
'user',
|
||||
'user_detail',
|
||||
]
|
||||
|
||||
|
||||
class PartStocktakeReportGenerateSerializer(serializers.Serializer):
|
||||
"""Serializer class for manually generating a new PartStocktakeReport via the API"""
|
||||
|
||||
part = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Part.objects.all(),
|
||||
required=False, allow_null=True,
|
||||
label=_('Part'), help_text=_('Limit stocktake report to a particular part, and any variant parts')
|
||||
)
|
||||
|
||||
category = serializers.PrimaryKeyRelatedField(
|
||||
queryset=PartCategory.objects.all(),
|
||||
required=False, allow_null=True,
|
||||
label=_('Category'), help_text=_('Limit stocktake report to a particular part category, and any child categories'),
|
||||
)
|
||||
|
||||
location = serializers.PrimaryKeyRelatedField(
|
||||
queryset=stock.models.StockLocation.objects.all(),
|
||||
required=False, allow_null=True,
|
||||
label=_('Location'), help_text=_('Limit stocktake report to a particular stock location, and any child locations')
|
||||
)
|
||||
|
||||
generate_report = serializers.BooleanField(
|
||||
default=True,
|
||||
label=_('Generate Report'),
|
||||
help_text=_('Generate report file containing calculated stocktake data'),
|
||||
)
|
||||
|
||||
update_parts = serializers.BooleanField(
|
||||
default=True,
|
||||
label=_('Update Parts'),
|
||||
help_text=_('Update specified parts with calculated stocktake data')
|
||||
)
|
||||
|
||||
def validate(self, data):
|
||||
"""Custom validation for this serializer"""
|
||||
|
||||
# Stocktake functionality must be enabled
|
||||
if not common.models.InvenTreeSetting.get_setting('STOCKTAKE_ENABLE', False):
|
||||
raise serializers.ValidationError(_("Stocktake functionality is not enabled"))
|
||||
|
||||
# Check that background worker is running
|
||||
if not InvenTree.status.is_worker_running():
|
||||
raise serializers.ValidationError(_("Background worker check failed"))
|
||||
|
||||
return data
|
||||
|
||||
def save(self):
|
||||
"""Saving this serializer instance requests generation of a new stocktake report"""
|
||||
|
||||
data = self.validated_data
|
||||
user = self.context['request'].user
|
||||
|
||||
# Generate a new report
|
||||
offload_task(
|
||||
part.tasks.generate_stocktake_report,
|
||||
force_async=True,
|
||||
user=user,
|
||||
part=data.get('part', None),
|
||||
category=data.get('category', None),
|
||||
location=data.get('location', None),
|
||||
generate_report=data.get('generate_report', True),
|
||||
update_parts=data.get('update_parts', True),
|
||||
)
|
||||
|
||||
|
||||
class PartPricingSerializer(InvenTreeModelSerializer):
|
||||
"""Serializer for Part pricing information"""
|
||||
|
||||
|
Reference in New Issue
Block a user