Build Order Updates (#4855)

* Add new BuildLine model

- Represents an instance of a BOM item against a BuildOrder

* Create BuildLine instances automatically

When a new Build is created, automatically generate new BuildLine items

* Improve logic for handling exchange rate backends

* logic fixes

* Adds API endpoints

Add list and detail API endpoints for new BuildLine model

* update users/models.py

- Add new model to roles definition

* bulk-create on auto_allocate

Save database hits by performing a bulk-create

* Add skeleton data migration

* Create BuildLines for existing orders

* Working on building out BuildLine table

* Adds link for "BuildLine" to "BuildItem"

- A "BuildItem" will now be tracked against a BuildLine
- Not tracked directly against a build
- Not tracked directly against a BomItem
- Add schema migration
- Add data migration to update links

* Adjust migration 0045

- bom_item and build fields are about to be removed
- Set them to "nullable" so the data doesn't get removed

* Remove old fields from BuildItem model

- build fk
- bom_item fk
- A lot of other required changes too

* Update BuildLine.bom_item field

- Delete the BuildLine if the BomItem is removed
- This is closer to current behaviour

* Cleanup for Build model

- tracked_bom_items -> tracked_line_items
- untracked_bom_items -> tracked_bom_items
- remove build.can_complete
- move bom_item specific methods to the BuildLine model
- Cleanup / consolidation

* front-end work

- Update javascript
- Cleanup HTML templates

* Add serializer annotation and filtering

- Annotate 'allocated' quantity
- Filter by allocated / trackable / optional / consumable

* Make table sortable

* Add buttons

* Add callback for building new stock

* Fix Part annotation

* Adds callback to order parts

* Allocation works again

* template cleanup

* Fix allocate / unallocate actions

- Also turns out "unallocate" is not a word..

* auto-allocate works again

* Fix call to build.is_over_allocated

* Refactoring updates

* Bump API version

* Cleaner implementation of allocation sub-table

* Fix rendering in build output table

* Improvements to StockItem list API

- Refactor very old code
- Add option to include test results to queryset

* Add TODO for later me

* Fix for serializers.py

* Working on cleaner implementation of build output table

* Add function to determine if a single output is fully allocated

* Updates to build.js

- Button callbacks
- Table rendering

* Revert previous changes to build.serializers.py

* Fix for forms.js

* Rearrange code in build.js

* Rebuild "allocated lines" for output table

* Fix allocation calculation

* Show or hide column for tracked parts

* Improve debug messages

* Refactor "loadBuildLineTable"

- Allow it to also be used as output sub-table

* Refactor "completed tests" column

* Remove old javascript

- Cleans up a *lot* of crusty old code

* Annotate the available stock quantity to BuildLine serializer

- Similar pattern to BomItem serializer
- Needs refactoring in the future

* Update available column

* Fix build allocation table

- Bug fix
- Make pretty

* linting fixes

* Allow sorting by available stock

* Tweak for "required tests" column

* Bug fix for completing a build output

* Fix for consumable stock

* Fix for trim_allocated_stock

* Fix for creating new build

* Migration fix

- Ensure initial django_q migrations are applied
- Why on earth is this failing now?

* Catch exception

* Update for exception handling

* Update migrations

- Ensure inventreesetting is added

* Catch all exceptions when getting default currency code

* Bug fix for currency exchange rates update

* Working on unit tests

* Unit test fixes

* More work on unit tests

* Use bulk_create in unit test

* Update required quantity when a BuildOrder is saved

* Tweak overage display in BOM table

* Fix icon in BOM table

* Fix spelling error

* More unit test fixes

* Build reports

- Add line_items
- Update docs
- Cleanup

* Reimplement is_partially_allocated method

* Update docs about overage

* Unit testing for data migration

* Add "required_for_build_orders" annotation

- Makes API query *much* faster now
- remove old "required_parts_to_complete_build" method
- Cleanup part API filter code

* Adjust order of fixture loading

* Fix unit test

* Prevent "schedule_pricing_update" in unit tests

- Should cut down on DB hits significantly

* Unit test updates

* Improvements for unit test

- Don't hard-code pk values
- postgresql no likey

* Better unit test
This commit is contained in:
Oliver
2023-06-13 20:18:32 +10:00
committed by GitHub
parent 98bddd32d0
commit 6ba777d363
54 changed files with 2193 additions and 1903 deletions

View File

@ -582,6 +582,9 @@ class BuildAllocationTest(BuildAPITest):
self.build = Build.objects.get(pk=1)
# Regenerate BuildLine objects
self.build.create_build_line_items()
# Record number of build items which exist at the start of each test
self.n = BuildItem.objects.count()
@ -593,7 +596,7 @@ class BuildAllocationTest(BuildAPITest):
self.assertEqual(self.build.part.bom_items.count(), 4)
# No items yet allocated to this build
self.assertEqual(self.build.allocated_stock.count(), 0)
self.assertEqual(BuildItem.objects.filter(build_line__build=self.build).count(), 0)
def test_get(self):
"""A GET request to the endpoint should return an error."""
@ -634,7 +637,7 @@ class BuildAllocationTest(BuildAPITest):
{
"items": [
{
"bom_item": 1, # M2x4 LPHS
"build_line": 1, # M2x4 LPHS
"stock_item": 2, # 5,000 screws available
}
]
@ -658,7 +661,7 @@ class BuildAllocationTest(BuildAPITest):
expected_code=400
).data
self.assertIn("This field is required", str(data["items"][0]["bom_item"]))
self.assertIn("This field is required", str(data["items"][0]["build_line"]))
# Missing stock_item
data = self.post(
@ -666,7 +669,7 @@ class BuildAllocationTest(BuildAPITest):
{
"items": [
{
"bom_item": 1,
"build_line": 1,
"quantity": 5000,
}
]
@ -681,12 +684,25 @@ class BuildAllocationTest(BuildAPITest):
def test_invalid_bom_item(self):
"""Test by passing an invalid BOM item."""
# Find the right (in this case, wrong) BuildLine instance
si = StockItem.objects.get(pk=11)
lines = self.build.build_lines.all()
wrong_line = None
for line in lines:
if line.bom_item.sub_part.pk != si.pk:
wrong_line = line
break
data = self.post(
self.url,
{
"items": [
{
"bom_item": 5,
"build_line": wrong_line.pk,
"stock_item": 11,
"quantity": 500,
}
@ -695,19 +711,31 @@ class BuildAllocationTest(BuildAPITest):
expected_code=400
).data
self.assertIn('must point to the same part', str(data))
self.assertIn('Selected stock item does not match BOM line', str(data))
def test_valid_data(self):
"""Test with valid data.
This should result in creation of a new BuildItem object
"""
# Find the correct BuildLine
si = StockItem.objects.get(pk=2)
right_line = None
for line in self.build.build_lines.all():
if line.bom_item.sub_part.pk == si.part.pk:
right_line = line
break
self.post(
self.url,
{
"items": [
{
"bom_item": 1,
"build_line": right_line.pk,
"stock_item": 2,
"quantity": 5000,
}
@ -749,16 +777,22 @@ class BuildOverallocationTest(BuildAPITest):
cls.state = {}
cls.allocation = {}
for i, bi in enumerate(cls.build.part.bom_items.all()):
rq = cls.build.required_quantity(bi, None) + i + 1
si = StockItem.objects.filter(part=bi.sub_part, quantity__gte=rq).first()
items_to_create = []
cls.state[bi.sub_part] = (si, si.quantity, rq)
BuildItem.objects.create(
build=cls.build,
for idx, build_line in enumerate(cls.build.build_lines.all()):
required = build_line.quantity + idx + 1
sub_part = build_line.bom_item.sub_part
si = StockItem.objects.filter(part=sub_part, quantity__gte=required).first()
cls.state[sub_part] = (si, si.quantity, required)
items_to_create.append(BuildItem(
build_line=build_line,
stock_item=si,
quantity=rq,
)
quantity=required,
))
BuildItem.objects.bulk_create(items_to_create)
# create and complete outputs
cls.build.create_build_output(cls.build.quantity)
@ -822,9 +856,10 @@ class BuildOverallocationTest(BuildAPITest):
self.assertTrue(self.build.is_complete)
# Check stock items have reduced only by bom requirement (overallocation trimmed)
for bi in self.build.part.bom_items.all():
si, oq, _ = self.state[bi.sub_part]
rq = self.build.required_quantity(bi, None)
for line in self.build.build_lines.all():
si, oq, _ = self.state[line.bom_item.sub_part]
rq = line.quantity
si.refresh_from_db()
self.assertEqual(si.quantity, oq - rq)