mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-19 05:25:42 +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:
@ -84,7 +84,7 @@ class RoleGroupAdmin(admin.ModelAdmin): # pragma: no cover
|
||||
RuleSetInline,
|
||||
]
|
||||
|
||||
list_display = ('name', 'admin', 'part_category', 'part', 'stock_location',
|
||||
list_display = ('name', 'admin', 'part_category', 'part', 'stocktake', 'stock_location',
|
||||
'stock_item', 'build', 'purchase_order', 'sales_order')
|
||||
|
||||
def get_rule_set(self, obj, rule_set_type):
|
||||
@ -137,6 +137,10 @@ class RoleGroupAdmin(admin.ModelAdmin): # pragma: no cover
|
||||
"""Return the ruleset for the Part role"""
|
||||
return self.get_rule_set(obj, 'part')
|
||||
|
||||
def stocktake(self, obj):
|
||||
"""Return the ruleset for the Stocktake role"""
|
||||
return self.get_rule_set(obj, 'stocktake')
|
||||
|
||||
def stock_location(self, obj):
|
||||
"""Return the ruleset for the StockLocation role"""
|
||||
return self.get_rule_set(obj, 'stock_location')
|
||||
|
18
InvenTree/users/migrations/0006_alter_ruleset_name.py
Normal file
18
InvenTree/users/migrations/0006_alter_ruleset_name.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.16 on 2023-02-16 22:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0005_owner_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ruleset',
|
||||
name='name',
|
||||
field=models.CharField(choices=[('admin', 'Admin'), ('part_category', 'Part Categories'), ('part', 'Parts'), ('stocktake', 'Stocktake'), ('stock_location', 'Stock Locations'), ('stock', 'Stock Items'), ('build', 'Build Orders'), ('purchase_order', 'Purchase Orders'), ('sales_order', 'Sales Orders')], help_text='Permission set', max_length=50),
|
||||
),
|
||||
]
|
@ -21,7 +21,7 @@ logger = logging.getLogger("inventree")
|
||||
|
||||
|
||||
class RuleSet(models.Model):
|
||||
"""A RuleSet is somewhat like a superset of the django permission class, in that in encapsulates a bunch of permissions.
|
||||
"""A RuleSet is somewhat like a superset of the django permission class, in that in encapsulates a bunch of permissions.
|
||||
|
||||
There are *many* apps models used within InvenTree,
|
||||
so it makes sense to group them into "roles".
|
||||
@ -36,6 +36,7 @@ class RuleSet(models.Model):
|
||||
('admin', _('Admin')),
|
||||
('part_category', _('Part Categories')),
|
||||
('part', _('Parts')),
|
||||
('stocktake', _('Stocktake')),
|
||||
('stock_location', _('Stock Locations')),
|
||||
('stock', _('Stock Items')),
|
||||
('build', _('Build Orders')),
|
||||
@ -97,13 +98,16 @@ class RuleSet(models.Model):
|
||||
'part_partrelated',
|
||||
'part_partstar',
|
||||
'part_partcategorystar',
|
||||
'part_partstocktake',
|
||||
'company_supplierpart',
|
||||
'company_manufacturerpart',
|
||||
'company_manufacturerpartparameter',
|
||||
'company_manufacturerpartattachment',
|
||||
'label_partlabel',
|
||||
],
|
||||
'stocktake': [
|
||||
'part_partstocktake',
|
||||
'part_partstocktakereport',
|
||||
],
|
||||
'stock_location': [
|
||||
'stock_stocklocation',
|
||||
'label_stocklocationlabel',
|
||||
@ -467,13 +471,13 @@ def update_group_roles(group, debug=False):
|
||||
# Enable all action permissions for certain children models
|
||||
# if parent model has 'change' permission
|
||||
for (parent, child) in RuleSet.RULESET_CHANGE_INHERIT:
|
||||
parent_change_perm = f'{parent}.change_{parent}'
|
||||
parent_child_string = f'{parent}_{child}'
|
||||
|
||||
# Check if parent change permission exists
|
||||
if parent_change_perm in group_permissions:
|
||||
# Add child model permissions
|
||||
for action in ['add', 'change', 'delete']:
|
||||
# Check each type of permission
|
||||
for action in ['view', 'change', 'add', 'delete']:
|
||||
parent_perm = f'{parent}.{action}_{parent}'
|
||||
|
||||
if parent_perm in group_permissions:
|
||||
child_perm = f'{parent}.{action}_{child}'
|
||||
|
||||
# Check if child permission not already in group
|
||||
|
@ -126,6 +126,7 @@ class RuleSetModelTest(TestCase):
|
||||
|
||||
# Add some more rules
|
||||
for rule in rulesets:
|
||||
rule.can_view = True
|
||||
rule.can_add = True
|
||||
rule.can_change = True
|
||||
|
||||
|
Reference in New Issue
Block a user