mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-17 12:35:46 +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:
@ -2839,6 +2839,7 @@ class PartStocktakeTest(InvenTreeAPITestCase):
|
||||
'category',
|
||||
'part',
|
||||
'location',
|
||||
'stock',
|
||||
]
|
||||
|
||||
def test_list_endpoint(self):
|
||||
@ -2887,8 +2888,8 @@ class PartStocktakeTest(InvenTreeAPITestCase):
|
||||
|
||||
url = reverse('api-part-stocktake-list')
|
||||
|
||||
self.assignRole('part.add')
|
||||
self.assignRole('part.view')
|
||||
self.assignRole('stocktake.add')
|
||||
self.assignRole('stocktake.view')
|
||||
|
||||
for p in Part.objects.all():
|
||||
|
||||
@ -2930,12 +2931,6 @@ class PartStocktakeTest(InvenTreeAPITestCase):
|
||||
self.assignRole('part.view')
|
||||
|
||||
# Test we can retrieve via API
|
||||
self.get(url, expected_code=403)
|
||||
|
||||
# Assign staff permission
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
|
||||
self.get(url, expected_code=200)
|
||||
|
||||
# Try to edit data
|
||||
@ -2948,7 +2943,7 @@ class PartStocktakeTest(InvenTreeAPITestCase):
|
||||
)
|
||||
|
||||
# Assign 'edit' role permission
|
||||
self.assignRole('part.change')
|
||||
self.assignRole('stocktake.change')
|
||||
|
||||
# Try again
|
||||
self.patch(
|
||||
@ -2962,6 +2957,59 @@ class PartStocktakeTest(InvenTreeAPITestCase):
|
||||
# Try to delete
|
||||
self.delete(url, expected_code=403)
|
||||
|
||||
self.assignRole('part.delete')
|
||||
self.assignRole('stocktake.delete')
|
||||
|
||||
self.delete(url, expected_code=204)
|
||||
|
||||
def test_report_list(self):
|
||||
"""Test for PartStocktakeReport list endpoint"""
|
||||
|
||||
from part.tasks import generate_stocktake_report
|
||||
|
||||
n_parts = Part.objects.count()
|
||||
|
||||
# Initially, no stocktake records are available
|
||||
self.assertEqual(PartStocktake.objects.count(), 0)
|
||||
|
||||
# Generate stocktake data for all parts (default configuration)
|
||||
generate_stocktake_report()
|
||||
|
||||
# There should now be 1 stocktake entry for each part
|
||||
self.assertEqual(PartStocktake.objects.count(), n_parts)
|
||||
|
||||
self.assignRole('stocktake.view')
|
||||
|
||||
response = self.get(reverse('api-part-stocktake-list'), expected_code=200)
|
||||
|
||||
self.assertEqual(len(response.data), n_parts)
|
||||
|
||||
# Stocktake report should be available via the API, also
|
||||
response = self.get(reverse('api-part-stocktake-report-list'), expected_code=200)
|
||||
|
||||
self.assertEqual(len(response.data), 1)
|
||||
|
||||
data = response.data[0]
|
||||
|
||||
self.assertEqual(data['part_count'], 14)
|
||||
self.assertEqual(data['user'], None)
|
||||
self.assertTrue(data['report'].endswith('.csv'))
|
||||
|
||||
def test_report_generate(self):
|
||||
"""Test API functionality for generating a new stocktake report"""
|
||||
|
||||
url = reverse('api-part-stocktake-report-generate')
|
||||
|
||||
# Permission denied, initially
|
||||
self.assignRole('stocktake.view')
|
||||
response = self.post(url, data={}, expected_code=403)
|
||||
|
||||
# Stocktake functionality disabled
|
||||
InvenTreeSetting.set_setting('STOCKTAKE_ENABLE', False, None)
|
||||
self.assignRole('stocktake.add')
|
||||
response = self.post(url, data={}, expected_code=400)
|
||||
|
||||
self.assertIn('Stocktake functionality is not enabled', str(response.data))
|
||||
|
||||
InvenTreeSetting.set_setting('STOCKTAKE_ENABLE', True, None)
|
||||
response = self.post(url, data={}, expected_code=400)
|
||||
self.assertIn('Background worker check failed', str(response.data))
|
||||
|
Reference in New Issue
Block a user