Skip to content

Commit

Permalink
Add option ot mark the build item as complete if all tests passed
Browse files Browse the repository at this point in the history
  • Loading branch information
martonmiklos committed Sep 3, 2024
1 parent f144158 commit c2d9a1b
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 8 deletions.
5 changes: 5 additions & 0 deletions src/backend/InvenTree/part/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ class Meta:
column_name=_('Trackable'),
widget=widgets.BooleanWidget(),
)
complate_build_after_all_required_tests_passed = Field(
attribute='complate_build_after_all_required_tests_passed',
column_name=_('Complete build outputs when all tests passed'),
widget=widgets.BooleanWidget(),
)
virtual = Field(
attribute='virtual', column_name=_('Virtual'), widget=widgets.BooleanWidget()
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.12 on 2024-08-27 20:15

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('part', '0129_auto_20240815_0214'),
]

operations = [
migrations.AddField(
model_name='part',
name='complete_build_after_all_required_tests_passed',
field=models.BooleanField(default=False, help_text='Automatically set the build outputs completed if all required tests are passed', verbose_name='Complete build output if all tests passed'),
),
]
8 changes: 8 additions & 0 deletions src/backend/InvenTree/part/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,14 @@ def get_default_supplier(self):
help_text=_('Can this part have test results recorded against it?'),
)

complete_build_after_all_required_tests_passed = models.BooleanField(
default=False,
verbose_name=_('Complete build output if all tests passed'),
help_text=_(
'Automatically set the build outputs completed if all required tests are passed'
),
)

purchaseable = models.BooleanField(
default=part_settings.part_purchaseable_default,
verbose_name=_('Purchaseable'),
Expand Down
1 change: 1 addition & 0 deletions src/backend/InvenTree/part/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ class Meta:
'component',
'creation_date',
'creation_user',
'complete_build_after_all_required_tests_passed',
'default_expiry',
'default_location',
'default_location_detail',
Expand Down
17 changes: 17 additions & 0 deletions src/backend/InvenTree/stock/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2423,6 +2423,23 @@ def save(self, *args, **kwargs):
super().clean()
super().validate_unique()
super().save(*args, **kwargs)
self.handle_build_completion(**kwargs)

def handle_build_completion(self, **kwargs):
"""Handle the auto complete build item if all tests passed feature."""
if (
not self.result
or not self.stock_item.part.complete_build_after_all_required_tests_passed
or not self.stock_item.build
):
return

if self.stock_item.passedAllRequiredTests():
user = kwargs.pop('user', None)

if user is None:
user = getattr(self, '_user', None)
self.stock_item.build.complete_build_output(self.stock_item, user)

def clean(self):
"""Make sure all values - including for templates - are provided."""
Expand Down
81 changes: 81 additions & 0 deletions src/backend/InvenTree/stock/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,87 @@ def test_installed_tests(self):
self.assertEqual(len(tests), 3)
self.assertNotIn('somenewtest', tests)

def test_auto_complete_build_on_all_passed(self):
"""Test auto completion of the build.
Test the autocompletion of the build item in the case if all required tests
are passed and this feature is enabled for a part.
"""
part = Part.objects.create(
name='Part which autocomplete build outputs when all tests passed',
testable=True,
trackable=True,
complete_build_after_all_required_tests_passed=True,
)

build = Build.objects.create(part=part)
build.create_build_output(1)
build_item = build.get_build_outputs()[0]

# Create 3 tests: 2 required, 1 non-required
test_template_reqd1 = PartTestTemplate.objects.create(
test_name='FVT1', required=True, part=part
)

test_template_reqd2 = PartTestTemplate.objects.create(
test_name='FVT2', required=True, part=part
)

test_template_non_reqd = PartTestTemplate.objects.create(
test_name='Polishing', required=False, part=part
)

# Make two failed tests on the required tests
StockItemTestResult.objects.create(
stock_item=build_item, result=False, template=test_template_reqd1
)

StockItemTestResult.objects.create(
stock_item=build_item, result=False, template=test_template_reqd2
)

# Make a pass on the non required test
StockItemTestResult.objects.create(
stock_item=build_item, result=True, template=test_template_non_reqd
)

build_item = build.get_build_outputs()[0]
# At this point it shall not be marked as completed
self.assertTrue(build_item.is_building)

# Make two passed tests on the two required tests
StockItemTestResult.objects.create(
stock_item=build_item, result=True, template=test_template_reqd1
)

StockItemTestResult.objects.create(
stock_item=build_item, result=True, template=test_template_reqd2
)

# The build output shall be complete now
self.assertFalse(build_item.is_building)

# Disable the auto complete feature, create a new build output
part.complete_build_after_all_required_tests_passed = False
part.save()

build.create_build_output(1)
build_item = build.get_build_outputs()[1]

# Make two passed tests on the required tests
StockItemTestResult.objects.create(
stock_item=build_item, result=True, template=test_template_reqd1
)

StockItemTestResult.objects.create(
stock_item=build_item, result=True, template=test_template_reqd2
)

# At this point it shall not be marked as completed
# as the autocomplete feature is disabled
self.assertTrue(build_item.is_building)


class StockLocationTest(InvenTreeTestCase):
"""Tests for the StockLocation model."""
Expand Down
11 changes: 11 additions & 0 deletions src/backend/InvenTree/templates/js/translated/part.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,22 @@ function partFields(options={}) {
},
testable: {
group: 'attributes',
onEdit: function(value, name, field, options) {
if (value) {
showFormInput('complete_build_after_all_required_tests_passed', options);
} else {
hideFormInput('complete_build_after_all_required_tests_passed', options);
}
}
},
trackable: {
default: global_settings.PART_TRACKABLE,
group: 'attributes',
},
complete_build_after_all_required_tests_passed: {
default: false,
group: 'attributes',
},
purchaseable: {
default: global_settings.PART_PURCHASEABLE,
group: 'attributes',
Expand Down
17 changes: 13 additions & 4 deletions src/frontend/src/forms/PartForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import { useGlobalSettingsState } from '../states/SettingsState';
* Construct a set of fields for creating / editing a Part instance
*/
export function usePartFields({
create = false
create = false,
part_testable
}: {
create?: boolean;
part_testable?: boolean;
}): ApiFormFieldSet {
const settings = useGlobalSettingsState();

const [testable, setTestable] = useState(part_testable);
return useMemo(() => {
const fields: ApiFormFieldSet = {
category: {
Expand Down Expand Up @@ -55,7 +57,14 @@ export function usePartFields({
component: {},
assembly: {},
is_template: {},
testable: {},
testable: {
onValueChange: (value) => {
setTestable(value);
}
},
complete_build_after_all_required_tests_passed: {
hidden: !testable
},
trackable: {},
purchaseable: {},
salable: {},
Expand Down Expand Up @@ -112,7 +121,7 @@ export function usePartFields({
}

return fields;
}, [create, settings]);
}, [create, settings, testable]);
}

/**
Expand Down
7 changes: 5 additions & 2 deletions src/frontend/src/pages/Index/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ function ApiFormsPlayground() {
fields: fields
});

const createPartFields = usePartFields({ create: true });
const editPartFields = usePartFields({ create: false });
const createPartFields = usePartFields({
create: true,
part_testable: false
});
const editPartFields = usePartFields({ create: false, part_testable: false });

const newPart = useCreateApiFormModal({
url: ApiEndpoints.part_list,
Expand Down
16 changes: 14 additions & 2 deletions src/frontend/src/pages/part/PartDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,12 @@ export default function PartDetail() {
label: t`Testable Part`,
icon: 'test'
},
{
type: 'boolean',
name: 'complete_build_after_all_required_tests_passed',
label: t`Complete build output after tests are passed`,
icon: 'progress'
},
{
type: 'boolean',
name: 'trackable',
Expand Down Expand Up @@ -901,7 +907,10 @@ export default function PartDetail() {
];
}, [part, instanceQuery]);

const partFields = usePartFields({ create: false });
const partFields = usePartFields({
create: false,
part_testable: part.testable
});

const editPart = useEditApiFormModal({
url: ApiEndpoints.part_list,
Expand All @@ -911,7 +920,10 @@ export default function PartDetail() {
onFormSuccess: refreshInstance
});

const createPartFields = usePartFields({ create: true });
const createPartFields = usePartFields({
create: true,
part_testable: part.testable
});

const duplicatePartFields: ApiFormFieldSet = useMemo(() => {
return {
Expand Down

0 comments on commit c2d9a1b

Please sign in to comment.