From 2fe0eefa8fea5b78be0880a6c8a7ad395064b29d Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 20 Apr 2024 23:18:25 +1000 Subject: [PATCH] Adds "active" field for Company model (#7024) * Add "active" field to Company model * Expose 'active' parameter to API * Fix default value * Add 'active' column to PUI * Update PUI table * Update company detail pages * Update API filters for SupplierPart and ManufacturerPart * Bump API version * Update order forms * Add edit action to SalesOrderDetail page * Enable editing of ReturnOrder * Typo fix * Adds explicit "active" field to SupplierPart model * More updates - Add "inactive" badge to SupplierPart page - Update SupplierPartTable - Update backend API fields * Update ReturnOrderTable - Also some refactoring * Impove usePurchaseOrderLineItemFields hook * Cleanup * Implement duplicate action for SupplierPart * Fix for ApiForm - Only override initialValues for specified fields * Allow edit and duplicate of StockItem * Fix for ApiForm - Default values were overriding initial data * Add duplicate part option * Cleanup ApiForm - Cache props.fields * Fix unused import * More fixes * Add unit tests * Allow ordering company by 'active' status * Update docs * Merge migrations * Fix for serializers.py * Force new form value * Remove debug call * Further unit test fixes * Update default CSRF_TRUSTED_ORIGINS values * Reduce debug output --- docs/README.md | 3 +- .../assets/images/order/company_disable.png | Bin 0 -> 28399 bytes .../images/order/disable_supplier_part.png | Bin 0 -> 56837 bytes .../order/disable_supplier_part_edit.png | Bin 0 -> 27147 bytes docs/docs/order/company.md | 34 +++++- .../InvenTree/InvenTree/api_version.py | 6 +- src/backend/InvenTree/InvenTree/settings.py | 17 +-- src/backend/InvenTree/company/api.py | 30 ++++- .../company/migrations/0069_company_active.py | 23 ++++ src/backend/InvenTree/company/models.py | 12 ++ src/backend/InvenTree/company/serializers.py | 20 ++- .../templates/company/company_base.html | 6 + src/backend/InvenTree/company/test_api.py | 71 +++++++++++ .../templates/js/translated/company.js | 12 +- .../templates/js/translated/table_filters.js | 4 + src/frontend/src/components/forms/ApiForm.tsx | 67 ++++++---- .../components/forms/fields/ApiFormField.tsx | 3 +- .../src/components/render/StatusRenderer.tsx | 8 +- src/frontend/src/forms/BuildForms.tsx | 99 ++++++++------- src/frontend/src/forms/CompanyForms.tsx | 56 ++++----- src/frontend/src/forms/PurchaseOrderForms.tsx | 109 +++++++++-------- src/frontend/src/forms/SalesOrderForms.tsx | 115 ++++++++++++------ src/frontend/src/forms/StockForms.tsx | 33 +---- src/frontend/src/pages/build/BuildDetail.tsx | 7 +- .../src/pages/company/CompanyDetail.tsx | 10 +- .../src/pages/company/SupplierPartDetail.tsx | 49 ++++++-- src/frontend/src/pages/part/PartDetail.tsx | 47 ++++--- .../pages/purchasing/PurchaseOrderDetail.tsx | 19 ++- .../src/pages/sales/ReturnOrderDetail.tsx | 53 +++++++- .../src/pages/sales/SalesOrderDetail.tsx | 53 +++++++- src/frontend/src/pages/stock/StockDetail.tsx | 67 +++++++--- .../src/tables/build/BuildOrderTable.tsx | 6 +- .../src/tables/company/CompanyTable.tsx | 36 +++++- .../src/tables/company/ContactTable.tsx | 4 +- .../purchasing/PurchaseOrderLineItemTable.tsx | 43 +++++-- .../tables/purchasing/PurchaseOrderTable.tsx | 6 +- .../tables/purchasing/SupplierPartTable.tsx | 113 +++++++++++------ .../src/tables/sales/ReturnOrderTable.tsx | 61 ++++++---- .../src/tables/sales/SalesOrderTable.tsx | 6 +- .../tables/stock/StockItemTestResultTable.tsx | 4 +- src/frontend/tests/pui_stock.spec.ts | 5 + 41 files changed, 927 insertions(+), 390 deletions(-) create mode 100644 docs/docs/assets/images/order/company_disable.png create mode 100644 docs/docs/assets/images/order/disable_supplier_part.png create mode 100644 docs/docs/assets/images/order/disable_supplier_part_edit.png create mode 100644 src/backend/InvenTree/company/migrations/0069_company_active.py diff --git a/docs/README.md b/docs/README.md index 989183c832..578afc13e8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,8 +12,7 @@ Run the following commands from the top-level project directory: ``` $ git clone https://github.com/inventree/inventree -$ cd inventree/docs -$ pip install -r requirements.txt +$ pip install -r docs/requirements.txt ``` ## Serve Locally diff --git a/docs/docs/assets/images/order/company_disable.png b/docs/docs/assets/images/order/company_disable.png new file mode 100644 index 0000000000000000000000000000000000000000..2c3ab4f49f07ef174f8d763094dcadc767379caa GIT binary patch literal 28399 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 !PifS>k*y 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 literal 0 HcmV?d00001 diff --git a/docs/docs/assets/images/order/disable_supplier_part.png b/docs/docs/assets/images/order/disable_supplier_part.png new file mode 100644 index 0000000000000000000000000000000000000000..a0e5f9bde8247848a8bfa3160c508ab7dbeddbab GIT binary patch literal 56837 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 literal 0 HcmV?d00001 diff --git a/docs/docs/assets/images/order/disable_supplier_part_edit.png b/docs/docs/assets/images/order/disable_supplier_part_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..2592f5ed91cc2657a24042f9bf488348a76a9a69 GIT binary patch literal 27147 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 m7ET(vQ7|2q6=Wt^khe>YTi&?z@(9>>Edit Company ic !!! warning "Permission Required" The edit button will not be available to users who do not have the required permissions to edit the company +### Disable Company + +Rather than deleting a company, it is possible to disable it. This will prevent the company from being used in new orders, but will not remove it from the database. Additionally, any existing orders associated with the company (and other linked items such as supplier parts, for a supplier) will remain intact. Unless the company is re-enabled, it will not be available for selection in new orders. + +It is recommended to disable a company rather than deleting it, as this will preserve the integrity of historical data. + +To disable a company, simply edit the company details and set the `active` attribute to `False`: + +{% with id="company_disable", url="order/company_disable.png", description="Disable Company" %} +{% include "img.html" %} +{% endwith %} + +To re-enable a company, simply follow the same process and set the `active` attribute to `True`. + ### Delete Company To delete a company, click on the icon under the actions menu. Confirm the deletion using the checkbox then click on Submit @@ -193,6 +207,24 @@ To edit a supplier part, first access the supplier part detail page with one of After the supplier part details are loaded, click on the icon next to the supplier part image. Edit the supplier part information then click on Submit +#### Disable Supplier Part + +Supplier parts can be individually disabled - for example, if a supplier part is no longer available for purchase. By disabling the part in the InvenTree system, it will no longer be available for selection in new purchase orders. However, any existing purchase orders which reference the supplier part will remain intact. + +The "active" status of a supplier part is clearly visible within the user interface: + +{% with id="supplier_part_disable", url="order/disable_supplier_part.png", description="Disable Supplier Part" %} +{% include "img.html" %} +{% endwith %} + +To change the "active" status of a supplier part, simply edit the supplier part details and set the `active` attribute: + +{% with id="supplier_part_disable_edit", url="order/disable_supplier_part_edit.png", description="Disable Supplier Part" %} +{% include "img.html" %} +{% endwith %} + +It is recommended to disable a supplier part rather than deleting it, as this will preserve the integrity of historical data. + #### Delete Supplier Part To delete a supplier part, first access the supplier part detail page like in the [Edit Supplier Part](#edit-supplier-part) section. diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 7db9dc01fb..94aa80c38e 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,11 +1,15 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 189 +INVENTREE_API_VERSION = 190 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v190 - 2024-04-19 : https://github.com/inventree/InvenTree/pull/7024 + - Adds "active" field to the Company API endpoints + - Allow company list to be filtered by "active" status + v189 - 2024-04-19 : https://github.com/inventree/InvenTree/pull/7066 - Adds "currency" field to CompanyBriefSerializer class diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index a32a2fbd77..7bda362b49 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1087,14 +1087,17 @@ CSRF_TRUSTED_ORIGINS = get_setting( if SITE_URL and SITE_URL not in CSRF_TRUSTED_ORIGINS: CSRF_TRUSTED_ORIGINS.append(SITE_URL) -if not TESTING and len(CSRF_TRUSTED_ORIGINS) == 0: - if DEBUG: - logger.warning( - 'No CSRF_TRUSTED_ORIGINS specified. Defaulting to http://* for debug mode. This is not recommended for production use' - ) - CSRF_TRUSTED_ORIGINS = ['http://*'] +if DEBUG: + for origin in [ + 'http://localhost', + 'http://*.localhost' 'http://*localhost:8000', + 'http://*localhost:5173', + ]: + if origin not in CSRF_TRUSTED_ORIGINS: + CSRF_TRUSTED_ORIGINS.append(origin) - elif isInMainThread(): +if not TESTING and len(CSRF_TRUSTED_ORIGINS) == 0: + if isInMainThread(): # Server thread cannot run without CSRF_TRUSTED_ORIGINS logger.error( 'No CSRF_TRUSTED_ORIGINS specified. Please provide a list of trusted origins, or specify INVENTREE_SITE_URL' diff --git a/src/backend/InvenTree/company/api.py b/src/backend/InvenTree/company/api.py index 88b2c640bd..aea8d5dc5e 100644 --- a/src/backend/InvenTree/company/api.py +++ b/src/backend/InvenTree/company/api.py @@ -2,6 +2,7 @@ from django.db.models import Q from django.urls import include, path, re_path +from django.utils.translation import gettext_lazy as _ from django_filters import rest_framework as rest_filters @@ -58,11 +59,17 @@ class CompanyList(ListCreateAPI): filter_backends = SEARCH_ORDER_FILTER - filterset_fields = ['is_customer', 'is_manufacturer', 'is_supplier', 'name'] + filterset_fields = [ + 'is_customer', + 'is_manufacturer', + 'is_supplier', + 'name', + 'active', + ] search_fields = ['name', 'description', 'website'] - ordering_fields = ['name', 'parts_supplied', 'parts_manufactured'] + ordering_fields = ['active', 'name', 'parts_supplied', 'parts_manufactured'] ordering = 'name' @@ -153,7 +160,13 @@ class ManufacturerPartFilter(rest_filters.FilterSet): fields = ['manufacturer', 'MPN', 'part', 'tags__name', 'tags__slug'] # Filter by 'active' status of linked part - active = rest_filters.BooleanFilter(field_name='part__active') + part_active = rest_filters.BooleanFilter( + field_name='part__active', label=_('Part is Active') + ) + + manufacturer_active = rest_filters.BooleanFilter( + field_name='manufacturer__active', label=_('Manufacturer is Active') + ) class ManufacturerPartList(ListCreateDestroyAPIView): @@ -301,8 +314,16 @@ class SupplierPartFilter(rest_filters.FilterSet): 'tags__slug', ] + active = rest_filters.BooleanFilter(label=_('Supplier Part is Active')) + # Filter by 'active' status of linked part - active = rest_filters.BooleanFilter(field_name='part__active') + part_active = rest_filters.BooleanFilter( + field_name='part__active', label=_('Internal Part is Active') + ) + + supplier_active = rest_filters.BooleanFilter( + field_name='supplier__active', label=_('Supplier is Active') + ) # Filter by the 'MPN' of linked manufacturer part MPN = rest_filters.CharFilter( @@ -378,6 +399,7 @@ class SupplierPartList(ListCreateDestroyAPIView): 'part', 'supplier', 'manufacturer', + 'active', 'MPN', 'packaging', 'pack_quantity', diff --git a/src/backend/InvenTree/company/migrations/0069_company_active.py b/src/backend/InvenTree/company/migrations/0069_company_active.py new file mode 100644 index 0000000000..120046f6fc --- /dev/null +++ b/src/backend/InvenTree/company/migrations/0069_company_active.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.11 on 2024-04-15 14:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0068_auto_20231120_1108'), + ] + + operations = [ + migrations.AddField( + model_name='company', + name='active', + field=models.BooleanField(default=True, help_text='Is this company active?', verbose_name='Active'), + ), + migrations.AddField( + model_name='supplierpart', + name='active', + field=models.BooleanField(default=True, help_text='Is this supplier part active?', verbose_name='Active'), + ), + ] diff --git a/src/backend/InvenTree/company/models.py b/src/backend/InvenTree/company/models.py index dc21dc62b1..b891b5b065 100644 --- a/src/backend/InvenTree/company/models.py +++ b/src/backend/InvenTree/company/models.py @@ -81,6 +81,7 @@ class Company( link: Secondary URL e.g. for link to internal Wiki page image: Company image / logo notes: Extra notes about the company + active: boolean value, is this company active is_customer: boolean value, is this company a customer is_supplier: boolean value, is this company a supplier is_manufacturer: boolean value, is this company a manufacturer @@ -155,6 +156,10 @@ class Company( verbose_name=_('Image'), ) + active = models.BooleanField( + default=True, verbose_name=_('Active'), help_text=_('Is this company active?') + ) + is_customer = models.BooleanField( default=False, verbose_name=_('is customer'), @@ -654,6 +659,7 @@ class SupplierPart( part: Link to the master Part (Obsolete) source_item: The sourcing item linked to this SupplierPart instance supplier: Company that supplies this SupplierPart object + active: Boolean value, is this supplier part active SKU: Stock keeping unit (supplier part number) link: Link to external website for this supplier part description: Descriptive notes field @@ -802,6 +808,12 @@ class SupplierPart( help_text=_('Supplier stock keeping unit'), ) + active = models.BooleanField( + default=True, + verbose_name=_('Active'), + help_text=_('Is this supplier part active?'), + ) + manufacturer_part = models.ForeignKey( ManufacturerPart, on_delete=models.CASCADE, diff --git a/src/backend/InvenTree/company/serializers.py b/src/backend/InvenTree/company/serializers.py index 7e3f86da78..e329ea44e6 100644 --- a/src/backend/InvenTree/company/serializers.py +++ b/src/backend/InvenTree/company/serializers.py @@ -42,12 +42,17 @@ class CompanyBriefSerializer(InvenTreeModelSerializer): """Metaclass options.""" model = Company - fields = ['pk', 'url', 'name', 'description', 'image', 'thumbnail', 'currency'] - + fields = [ + 'pk', + 'active', + 'name', + 'description', + 'image', + 'thumbnail', + 'currency', + ] read_only_fields = ['currency'] - url = serializers.CharField(source='get_absolute_url', read_only=True) - image = InvenTreeImageSerializerField(read_only=True) thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True) @@ -118,6 +123,7 @@ class CompanySerializer(RemoteImageMixin, InvenTreeModelSerializer): 'contact', 'link', 'image', + 'active', 'is_customer', 'is_manufacturer', 'is_supplier', @@ -308,6 +314,7 @@ class SupplierPartSerializer(InvenTreeTagModelSerializer): 'description', 'in_stock', 'link', + 'active', 'manufacturer', 'manufacturer_detail', 'manufacturer_part', @@ -371,8 +378,9 @@ class SupplierPartSerializer(InvenTreeTagModelSerializer): self.fields.pop('pretty_name') # Annotated field showing total in-stock quantity - in_stock = serializers.FloatField(read_only=True) - available = serializers.FloatField(required=False) + in_stock = serializers.FloatField(read_only=True, label=_('In Stock')) + + available = serializers.FloatField(required=False, label=_('Available')) pack_quantity_native = serializers.FloatField(read_only=True) diff --git a/src/backend/InvenTree/company/templates/company/company_base.html b/src/backend/InvenTree/company/templates/company/company_base.html index 87f8cf10c9..c7c7efde12 100644 --- a/src/backend/InvenTree/company/templates/company/company_base.html +++ b/src/backend/InvenTree/company/templates/company/company_base.html @@ -10,6 +10,12 @@ {% block heading %} {% trans "Company" %}: {{ company.name }} +{% if not company.active %} +  +
+ {% trans 'Inactive' %} +
+{% endif %} {% endblock heading %} {% block actions %} diff --git a/src/backend/InvenTree/company/test_api.py b/src/backend/InvenTree/company/test_api.py index dcb8dc81dc..332292ca3c 100644 --- a/src/backend/InvenTree/company/test_api.py +++ b/src/backend/InvenTree/company/test_api.py @@ -5,6 +5,7 @@ from django.urls import reverse from rest_framework import status from InvenTree.unit_test import InvenTreeAPITestCase +from part.models import Part from .models import Address, Company, Contact, ManufacturerPart, SupplierPart @@ -131,6 +132,32 @@ class CompanyTest(InvenTreeAPITestCase): self.assertTrue('currency' in response.data) + def test_company_active(self): + """Test that the 'active' value and filter works.""" + Company.objects.filter(active=False).update(active=True) + n = Company.objects.count() + + url = reverse('api-company-list') + + self.assertEqual( + len(self.get(url, data={'active': True}, expected_code=200).data), n + ) + self.assertEqual( + len(self.get(url, data={'active': False}, expected_code=200).data), 0 + ) + + # Set one company to inactive + c = Company.objects.first() + c.active = False + c.save() + + self.assertEqual( + len(self.get(url, data={'active': True}, expected_code=200).data), n - 1 + ) + self.assertEqual( + len(self.get(url, data={'active': False}, expected_code=200).data), 1 + ) + class ContactTest(InvenTreeAPITestCase): """Tests for the Contact models.""" @@ -528,6 +555,50 @@ class SupplierPartTest(InvenTreeAPITestCase): self.assertEqual(sp.available, 999) self.assertIsNotNone(sp.availability_updated) + def test_active(self): + """Test that 'active' status filtering works correctly.""" + url = reverse('api-supplier-part-list') + + # Create a new company, which is inactive + company = Company.objects.create( + name='Inactive Company', is_supplier=True, active=False + ) + + part = Part.objects.filter(purchaseable=True).first() + + # Create some new supplier part objects, *some* of which are inactive + for idx in range(10): + SupplierPart.objects.create( + part=part, + supplier=company, + SKU=f'CMP-{company.pk}-SKU-{idx}', + active=(idx % 2 == 0), + ) + + n = SupplierPart.objects.count() + + # List *all* supplier parts + self.assertEqual(len(self.get(url, data={}, expected_code=200).data), n) + + # List only active supplier parts (all except 5 from the new supplier) + self.assertEqual( + len(self.get(url, data={'active': True}, expected_code=200).data), n - 5 + ) + + # List only from 'active' suppliers (all except this new supplier) + self.assertEqual( + len(self.get(url, data={'supplier_active': True}, expected_code=200).data), + n - 10, + ) + + # List active parts from inactive suppliers (only 5 from the new supplier) + response = self.get( + url, data={'supplier_active': False, 'active': True}, expected_code=200 + ) + self.assertEqual(len(response.data), 5) + for result in response.data: + self.assertEqual(result['supplier'], company.pk) + class CompanyMetadataAPITest(InvenTreeAPITestCase): """Unit tests for the various metadata endpoints of API.""" diff --git a/src/backend/InvenTree/templates/js/translated/company.js b/src/backend/InvenTree/templates/js/translated/company.js index 424f5a27fa..a008da2041 100644 --- a/src/backend/InvenTree/templates/js/translated/company.js +++ b/src/backend/InvenTree/templates/js/translated/company.js @@ -426,7 +426,8 @@ function companyFormFields() { }, is_supplier: {}, is_manufacturer: {}, - is_customer: {} + is_customer: {}, + active: {}, }; } @@ -517,6 +518,15 @@ function loadCompanyTable(table, url, options={}) { field: 'description', title: '{% trans "Description" %}', }, + { + field: 'active', + title: '{% trans "Active" %}', + sortable: true, + switchable: true, + formatter: function(value) { + return yesNoLabel(value); + } + }, { field: 'website', title: '{% trans "Website" %}', diff --git a/src/backend/InvenTree/templates/js/translated/table_filters.js b/src/backend/InvenTree/templates/js/translated/table_filters.js index 91786c3e59..0c10b06f33 100644 --- a/src/backend/InvenTree/templates/js/translated/table_filters.js +++ b/src/backend/InvenTree/templates/js/translated/table_filters.js @@ -791,6 +791,10 @@ function getContactFilters() { // Return a dictionary of filters for the "company" table function getCompanyFilters() { return { + active: { + type: 'bool', + title: '{% trans "Active" %}' + }, is_manufacturer: { type: 'bool', title: '{% trans "Manufacturer" %}', diff --git a/src/frontend/src/components/forms/ApiForm.tsx b/src/frontend/src/components/forms/ApiForm.tsx index 4d02009d29..315121efef 100644 --- a/src/frontend/src/components/forms/ApiForm.tsx +++ b/src/frontend/src/components/forms/ApiForm.tsx @@ -107,6 +107,8 @@ export function OptionsApiForm({ const optionsQuery = useQuery({ enabled: true, + refetchOnMount: false, + refetchOnWindowFocus: false, queryKey: [ 'form-options-data', id, @@ -181,21 +183,26 @@ export function ApiForm({ props: ApiFormProps; optionsLoading: boolean; }) { + const fields: ApiFormFieldSet = useMemo(() => { + return props.fields ?? {}; + }, [props.fields]); + const defaultValues: FieldValues = useMemo(() => { - let defaultValuesMap = mapFields(props.fields ?? {}, (_path, field) => { + let defaultValuesMap = mapFields(fields ?? {}, (_path, field) => { return field.value ?? field.default ?? undefined; }); - // If the user has specified initial data, use that instead + // If the user has specified initial data, that overrides default values + // But, *only* for the fields we have specified if (props.initialData) { - defaultValuesMap = { - ...defaultValuesMap, - ...props.initialData - }; + Object.keys(props.initialData).map((key) => { + if (key in defaultValuesMap) { + defaultValuesMap[key] = + props?.initialData?.[key] ?? defaultValuesMap[key]; + } + }); } - // Update the form values, but only for the fields specified for this form - return defaultValuesMap; }, [props.fields, props.initialData]); @@ -260,14 +267,22 @@ export function ApiForm({ }; // Process API response - const initialData: any = processFields( - props.fields ?? {}, - response.data - ); + const initialData: any = processFields(fields, response.data); // Update form values, but only for the fields specified for this form form.reset(initialData); + // Update the field references, too + Object.keys(fields).forEach((fieldName) => { + if (fieldName in initialData) { + let field = fields[fieldName] ?? {}; + fields[fieldName] = { + ...field, + value: initialData[fieldName] + }; + } + }); + return response; } catch (error) { console.error('Error fetching initial data:', error); @@ -301,12 +316,12 @@ export function ApiForm({ initialDataQuery.isFetching || optionsLoading || isSubmitting || - !props.fields, + !fields, [ isFormLoading, initialDataQuery.isFetching, isSubmitting, - props.fields, + fields, optionsLoading ] ); @@ -319,7 +334,7 @@ export function ApiForm({ if (!focusField) { // If a focus field is not specified, then focus on the first available field - Object.entries(props.fields ?? {}).forEach(([fieldName, field]) => { + Object.entries(fields).forEach(([fieldName, field]) => { if (focusField || field.read_only || field.disabled || field.hidden) { return; } @@ -334,7 +349,7 @@ export function ApiForm({ form.setFocus(focusField); setInitialFocus(focusField); - }, [props.focus, props.fields, form.setFocus, isLoading, initialFocus]); + }, [props.focus, fields, form.setFocus, isLoading, initialFocus]); const submitForm: SubmitHandler = async (data) => { setNonFieldErrors([]); @@ -342,7 +357,7 @@ export function ApiForm({ let method = props.method?.toLowerCase() ?? 'get'; let hasFiles = false; - mapFields(props.fields ?? {}, (_path, field) => { + mapFields(fields, (_path, field) => { if (field.field_type === 'file upload') { hasFiles = true; } @@ -474,16 +489,14 @@ export function ApiForm({ {!optionsLoading && - Object.entries(props.fields ?? {}).map( - ([fieldName, field]) => ( - - ) - )} + Object.entries(fields).map(([fieldName, field]) => ( + + ))} {props.postFormContent} diff --git a/src/frontend/src/components/forms/fields/ApiFormField.tsx b/src/frontend/src/components/forms/fields/ApiFormField.tsx index 70e73ca5d6..9d098f0abb 100644 --- a/src/frontend/src/components/forms/fields/ApiFormField.tsx +++ b/src/frontend/src/components/forms/fields/ApiFormField.tsx @@ -15,6 +15,7 @@ import { useMemo } from 'react'; import { Control, FieldValues, useController } from 'react-hook-form'; import { ModelType } from '../../../enums/ModelType'; +import { isTrue } from '../../../functions/conversion'; import { ChoiceField } from './ChoiceField'; import DateField from './DateField'; import { NestedObjectField } from './NestedObjectField'; @@ -210,7 +211,7 @@ export function ApiFormField({ id={fieldId} radius="lg" size="sm" - checked={value ?? false} + checked={isTrue(value)} error={error?.message} onChange={(event) => onChange(event.currentTarget.checked)} /> diff --git a/src/frontend/src/components/render/StatusRenderer.tsx b/src/frontend/src/components/render/StatusRenderer.tsx index c737c7e905..78fb3f9414 100644 --- a/src/frontend/src/components/render/StatusRenderer.tsx +++ b/src/frontend/src/components/render/StatusRenderer.tsx @@ -74,13 +74,7 @@ export const StatusRenderer = ({ }) => { const statusCodeList = useGlobalStatusState.getState().status; - if (status === undefined) { - console.log('StatusRenderer: status is undefined'); - return null; - } - - if (statusCodeList === undefined) { - console.log('StatusRenderer: statusCodeList is undefined'); + if (status === undefined || statusCodeList === undefined) { return null; } diff --git a/src/frontend/src/forms/BuildForms.tsx b/src/frontend/src/forms/BuildForms.tsx index b89bed64fd..dd4523a6bc 100644 --- a/src/frontend/src/forms/BuildForms.tsx +++ b/src/frontend/src/forms/BuildForms.tsx @@ -7,57 +7,64 @@ import { IconUser, IconUsersGroup } from '@tabler/icons-react'; +import { useMemo } from 'react'; import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField'; /** * Field set for BuildOrder forms */ -export function buildOrderFields(): ApiFormFieldSet { - return { - reference: {}, - part: { - filters: { - assembly: true, - virtual: false +export function useBuildOrderFields({ + create +}: { + create: boolean; +}): ApiFormFieldSet { + return useMemo(() => { + return { + reference: {}, + part: { + filters: { + assembly: true, + virtual: false + } + }, + title: {}, + quantity: {}, + project_code: { + icon: + }, + priority: {}, + parent: { + icon: , + filters: { + part_detail: true + } + }, + sales_order: { + icon: + }, + batch: {}, + target_date: { + icon: + }, + take_from: {}, + destination: { + filters: { + structural: false + } + }, + link: { + icon: + }, + issued_by: { + icon: + }, + responsible: { + icon: , + filters: { + is_active: true + } } - }, - title: {}, - quantity: {}, - project_code: { - icon: - }, - priority: {}, - parent: { - icon: , - filters: { - part_detail: true - } - }, - sales_order: { - icon: - }, - batch: {}, - target_date: { - icon: - }, - take_from: {}, - destination: { - filters: { - structural: false - } - }, - link: { - icon: - }, - issued_by: { - icon: - }, - responsible: { - icon: , - filters: { - is_active: true - } - } - }; + }; + }, [create]); } diff --git a/src/frontend/src/forms/CompanyForms.tsx b/src/frontend/src/forms/CompanyForms.tsx index 08212b3773..50b5d77190 100644 --- a/src/frontend/src/forms/CompanyForms.tsx +++ b/src/frontend/src/forms/CompanyForms.tsx @@ -10,34 +10,21 @@ import { } from '@tabler/icons-react'; import { useEffect, useMemo, useState } from 'react'; -import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField'; +import { + ApiFormAdjustFilterType, + ApiFormFieldSet +} from '../components/forms/fields/ApiFormField'; /** * Field set for SupplierPart instance */ -export function useSupplierPartFields({ - partPk, - supplierPk, - hidePart -}: { - partPk?: number; - supplierPk?: number; - hidePart?: boolean; -}) { - const [part, setPart] = useState(partPk); - - useEffect(() => { - setPart(partPk); - }, [partPk]); - +export function useSupplierPartFields() { return useMemo(() => { const fields: ApiFormFieldSet = { part: { - hidden: hidePart, - value: part, - onValueChange: setPart, filters: { - purchaseable: true + purchaseable: true, + active: true } }, manufacturer_part: { @@ -45,15 +32,18 @@ export function useSupplierPartFields({ part_detail: true, manufacturer_detail: true }, - adjustFilters: (filters: any) => { - if (part) { - filters.part = part; - } - - return filters; + adjustFilters: (adjust: ApiFormAdjustFilterType) => { + return { + ...adjust.filters, + part: adjust.data.part + }; + } + }, + supplier: { + filters: { + active: true } }, - supplier: {}, SKU: { icon: }, @@ -67,15 +57,12 @@ export function useSupplierPartFields({ pack_quantity: {}, packaging: { icon: - } + }, + active: {} }; - if (supplierPk !== undefined) { - fields.supplier.value = supplierPk; - } - return fields; - }, [part]); + }, []); } export function useManufacturerPartFields() { @@ -125,6 +112,7 @@ export function companyFields(): ApiFormFieldSet { }, is_supplier: {}, is_manufacturer: {}, - is_customer: {} + is_customer: {}, + active: {} }; } diff --git a/src/frontend/src/forms/PurchaseOrderForms.tsx b/src/frontend/src/forms/PurchaseOrderForms.tsx index b89fe2af23..930e729571 100644 --- a/src/frontend/src/forms/PurchaseOrderForms.tsx +++ b/src/frontend/src/forms/PurchaseOrderForms.tsx @@ -37,8 +37,12 @@ import { apiUrl } from '../states/ApiState'; * Construct a set of fields for creating / editing a PurchaseOrderLineItem instance */ export function usePurchaseOrderLineItemFields({ + supplierId, + orderId, create }: { + supplierId?: number; + orderId?: number; create?: boolean; }) { const [purchasePrice, setPurchasePrice] = useState(''); @@ -60,16 +64,20 @@ export function usePurchaseOrderLineItemFields({ filters: { supplier_detail: true }, - hidden: true + disabled: true }, part: { filters: { part_detail: true, - supplier_detail: true + supplier_detail: true, + active: true, + part_active: true }, - adjustFilters: (value: ApiFormAdjustFilterType) => { - // TODO: Adjust part based on the supplier associated with the supplier - return value.filters; + adjustFilters: (adjust: ApiFormAdjustFilterType) => { + return { + ...adjust.filters, + supplier: supplierId + }; } }, quantity: {}, @@ -105,7 +113,7 @@ export function usePurchaseOrderLineItemFields({ } return fields; - }, [create, autoPricing, purchasePrice]); + }, [create, orderId, supplierId, autoPricing, purchasePrice]); return fields; } @@ -113,50 +121,53 @@ export function usePurchaseOrderLineItemFields({ /** * Construct a set of fields for creating / editing a PurchaseOrder instance */ -export function purchaseOrderFields(): ApiFormFieldSet { - return { - reference: { - icon: - }, - description: {}, - supplier: { - filters: { - is_supplier: true +export function usePurchaseOrderFields(): ApiFormFieldSet { + return useMemo(() => { + return { + reference: { + icon: + }, + description: {}, + supplier: { + filters: { + is_supplier: true, + active: true + } + }, + supplier_reference: {}, + project_code: { + icon: + }, + order_currency: { + icon: + }, + target_date: { + icon: + }, + link: {}, + contact: { + icon: , + adjustFilters: (value: ApiFormAdjustFilterType) => { + return { + ...value.filters, + company: value.data.supplier + }; + } + }, + address: { + icon: , + adjustFilters: (value: ApiFormAdjustFilterType) => { + return { + ...value.filters, + company: value.data.supplier + }; + } + }, + responsible: { + icon: } - }, - supplier_reference: {}, - project_code: { - icon: - }, - order_currency: { - icon: - }, - target_date: { - icon: - }, - link: {}, - contact: { - icon: , - adjustFilters: (value: ApiFormAdjustFilterType) => { - return { - ...value.filters, - company: value.data.supplier - }; - } - }, - address: { - icon: , - adjustFilters: (value: ApiFormAdjustFilterType) => { - return { - ...value.filters, - company: value.data.supplier - }; - } - }, - responsible: { - icon: - } - }; + }; + }, []); } /** diff --git a/src/frontend/src/forms/SalesOrderForms.tsx b/src/frontend/src/forms/SalesOrderForms.tsx index fcaa25f94f..9c97f13201 100644 --- a/src/frontend/src/forms/SalesOrderForms.tsx +++ b/src/frontend/src/forms/SalesOrderForms.tsx @@ -1,44 +1,89 @@ import { IconAddressBook, IconUser, IconUsers } from '@tabler/icons-react'; +import { useMemo } from 'react'; import { ApiFormAdjustFilterType, ApiFormFieldSet } from '../components/forms/fields/ApiFormField'; -export function salesOrderFields(): ApiFormFieldSet { - return { - reference: {}, - description: {}, - customer: { - filters: { - is_customer: true +export function useSalesOrderFields(): ApiFormFieldSet { + return useMemo(() => { + return { + reference: {}, + description: {}, + customer: { + filters: { + is_customer: true, + active: true + } + }, + customer_reference: {}, + project_code: {}, + order_currency: {}, + target_date: {}, + link: {}, + contact: { + icon: , + adjustFilters: (value: ApiFormAdjustFilterType) => { + return { + ...value.filters, + company: value.data.customer + }; + } + }, + address: { + icon: , + adjustFilters: (value: ApiFormAdjustFilterType) => { + return { + ...value.filters, + company: value.data.customer + }; + } + }, + responsible: { + icon: } - }, - customer_reference: {}, - project_code: {}, - order_currency: {}, - target_date: {}, - link: {}, - contact: { - icon: , - adjustFilters: (value: ApiFormAdjustFilterType) => { - return { - ...value.filters, - company: value.data.customer - }; - } - }, - address: { - icon: , - adjustFilters: (value: ApiFormAdjustFilterType) => { - return { - ...value.filters, - company: value.data.customer - }; - } - }, - responsible: { - icon: - } - }; + }; + }, []); +} + +export function useReturnOrderFields(): ApiFormFieldSet { + return useMemo(() => { + return { + reference: {}, + description: {}, + customer: { + filters: { + is_customer: true, + active: true + } + }, + customer_reference: {}, + project_code: {}, + order_currency: {}, + target_date: {}, + link: {}, + contact: { + icon: , + adjustFilters: (value: ApiFormAdjustFilterType) => { + return { + ...value.filters, + company: value.data.customer + }; + } + }, + address: { + icon: , + adjustFilters: (value: ApiFormAdjustFilterType) => { + return { + ...value.filters, + company: value.data.customer + }; + } + }, + responsible: { + icon: + } + }; + }, []); } diff --git a/src/frontend/src/forms/StockForms.tsx b/src/frontend/src/forms/StockForms.tsx index 9325d97b0c..fc023688af 100644 --- a/src/frontend/src/forms/StockForms.tsx +++ b/src/frontend/src/forms/StockForms.tsx @@ -39,7 +39,7 @@ export function useStockFields({ const fields: ApiFormFieldSet = { part: { value: part, - hidden: !create, + disabled: !create, onValueChange: (change) => { setPart(change); // TODO: implement remaining functionality from old stock.py @@ -57,12 +57,12 @@ export function useStockFields({ supplier_detail: true, ...(part ? { part } : {}) }, - adjustFilters: (value: ApiFormAdjustFilterType) => { - if (value.data.part) { - value.filters['part'] = value.data.part; + adjustFilters: (adjust: ApiFormAdjustFilterType) => { + if (adjust.data.part) { + adjust.filters['part'] = adjust.data.part; } - return value.filters; + return adjust.filters; } }, use_pack_size: { @@ -137,29 +137,6 @@ export function useCreateStockItem() { }); } -/** - * Launch a form to edit an existing StockItem instance - * @param item : primary key of the StockItem to edit - */ -export function useEditStockItem({ - item_id, - callback -}: { - item_id: number; - callback?: () => void; -}) { - const fields = useStockFields({ create: false }); - - return useEditApiFormModal({ - url: ApiEndpoints.stock_item_list, - pk: item_id, - fields: fields, - title: t`Edit Stock Item`, - successMessage: t`Stock item updated`, - onFormSuccess: callback - }); -} - function StockItemDefaultMove({ stockItem, value diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx index 52695ff463..8ec7b03444 100644 --- a/src/frontend/src/pages/build/BuildDetail.tsx +++ b/src/frontend/src/pages/build/BuildDetail.tsx @@ -35,8 +35,7 @@ import { NotesEditor } from '../../components/widgets/MarkdownEditor'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; -import { buildOrderFields } from '../../forms/BuildForms'; -import { partCategoryFields } from '../../forms/PartForms'; +import { useBuildOrderFields } from '../../forms/BuildForms'; import { useEditApiFormModal } from '../../hooks/UseForm'; import { useInstance } from '../../hooks/UseInstance'; import { apiUrl } from '../../states/ApiState'; @@ -280,11 +279,13 @@ export default function BuildDetail() { ]; }, [build, id]); + const buildOrderFields = useBuildOrderFields({ create: false }); + const editBuild = useEditApiFormModal({ url: ApiEndpoints.build_order_list, pk: build.pk, title: t`Edit Build Order`, - fields: buildOrderFields(), + fields: buildOrderFields, onFormSuccess: () => { refreshInstance(); } diff --git a/src/frontend/src/pages/company/CompanyDetail.tsx b/src/frontend/src/pages/company/CompanyDetail.tsx index eed845e9ca..5244a3f648 100644 --- a/src/frontend/src/pages/company/CompanyDetail.tsx +++ b/src/frontend/src/pages/company/CompanyDetail.tsx @@ -15,10 +15,11 @@ import { IconTruckReturn, IconUsersGroup } from '@tabler/icons-react'; -import { useMemo } from 'react'; +import { ReactNode, useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { DetailsField, DetailsTable } from '../../components/details/Details'; +import DetailsBadge from '../../components/details/DetailsBadge'; import { DetailsImage } from '../../components/details/DetailsImage'; import { ItemDetailsGrid } from '../../components/details/ItemDetails'; import { @@ -293,6 +294,12 @@ export default function CompanyDetail(props: CompanyDetailProps) { ]; }, [id, company, user]); + const badges: ReactNode[] = useMemo(() => { + return [ + + ]; + }, [company]); + return ( <> {editCompany.modal} @@ -304,6 +311,7 @@ export default function CompanyDetail(props: CompanyDetailProps) { actions={companyActions} imageUrl={company.image} breadcrumbs={props.breadcrumbs} + badges={badges} /> diff --git a/src/frontend/src/pages/company/SupplierPartDetail.tsx b/src/frontend/src/pages/company/SupplierPartDetail.tsx index 64731aefc1..49edc7f66f 100644 --- a/src/frontend/src/pages/company/SupplierPartDetail.tsx +++ b/src/frontend/src/pages/company/SupplierPartDetail.tsx @@ -7,10 +7,11 @@ import { IconPackages, IconShoppingCart } from '@tabler/icons-react'; -import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; +import { ReactNode, useMemo } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; import { DetailsField, DetailsTable } from '../../components/details/Details'; +import DetailsBadge from '../../components/details/DetailsBadge'; import { DetailsImage } from '../../components/details/DetailsImage'; import { ItemDetailsGrid } from '../../components/details/ItemDetails'; import { @@ -25,7 +26,11 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; import { useSupplierPartFields } from '../../forms/CompanyForms'; -import { useEditApiFormModal } from '../../hooks/UseForm'; +import { getDetailUrl } from '../../functions/urls'; +import { + useCreateApiFormModal, + useEditApiFormModal +} from '../../hooks/UseForm'; import { useInstance } from '../../hooks/UseInstance'; import { apiUrl } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; @@ -38,6 +43,8 @@ export default function SupplierPartDetail() { const user = useUserState(); + const navigate = useNavigate(); + const { instance: supplierPart, instanceQuery, @@ -245,7 +252,8 @@ export default function SupplierPartDetail() { icon={} actions={[ DuplicateItemAction({ - hidden: !user.hasAddRole(UserRoles.purchase_order) + hidden: !user.hasAddRole(UserRoles.purchase_order), + onClick: () => duplicateSupplierPart.open() }), EditItemAction({ hidden: !user.hasChangeRole(UserRoles.purchase_order), @@ -259,19 +267,30 @@ export default function SupplierPartDetail() { ]; }, [user]); - const editSupplierPartFields = useSupplierPartFields({ - hidePart: true, - partPk: supplierPart?.pk - }); + const supplierPartFields = useSupplierPartFields(); const editSuppliertPart = useEditApiFormModal({ url: ApiEndpoints.supplier_part_list, pk: supplierPart?.pk, title: t`Edit Supplier Part`, - fields: editSupplierPartFields, + fields: supplierPartFields, onFormSuccess: refreshInstance }); + const duplicateSupplierPart = useCreateApiFormModal({ + url: ApiEndpoints.supplier_part_list, + title: t`Add Supplier Part`, + fields: supplierPartFields, + initialData: { + ...supplierPart + }, + onFormSuccess: (response: any) => { + if (response.pk) { + navigate(getDetailUrl(ModelType.supplierpart, response.pk)); + } + } + }); + const breadcrumbs = useMemo(() => { return [ { @@ -285,15 +304,27 @@ export default function SupplierPartDetail() { ]; }, [supplierPart]); + const badges: ReactNode[] = useMemo(() => { + return [ + + ]; + }, [supplierPart]); + return ( <> {editSuppliertPart.modal} + {duplicateSupplierPart.modal} diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx index 1e76fb46e0..09a1dfa36b 100644 --- a/src/frontend/src/pages/part/PartDetail.tsx +++ b/src/frontend/src/pages/part/PartDetail.tsx @@ -1,13 +1,5 @@ import { t } from '@lingui/macro'; -import { - Badge, - Grid, - Group, - LoadingOverlay, - Skeleton, - Stack, - Text -} from '@mantine/core'; +import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core'; import { IconBookmarks, IconBuilding, @@ -32,13 +24,11 @@ import { } from '@tabler/icons-react'; import { useSuspenseQuery } from '@tanstack/react-query'; import { ReactNode, useMemo, useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { api } from '../../App'; import { DetailsField, DetailsTable } from '../../components/details/Details'; -import DetailsBadge, { - DetailsBadgeProps -} from '../../components/details/DetailsBadge'; +import DetailsBadge from '../../components/details/DetailsBadge'; import { DetailsImage } from '../../components/details/DetailsImage'; import { ItemDetailsGrid } from '../../components/details/ItemDetails'; import { PartIcons } from '../../components/details/PartIcons'; @@ -68,7 +58,10 @@ import { } from '../../forms/StockForms'; import { InvenTreeIcon } from '../../functions/icons'; import { getDetailUrl } from '../../functions/urls'; -import { useEditApiFormModal } from '../../hooks/UseForm'; +import { + useCreateApiFormModal, + useEditApiFormModal +} from '../../hooks/UseForm'; import { useInstance } from '../../hooks/UseInstance'; import { apiUrl } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; @@ -93,6 +86,7 @@ export default function PartDetail() { const { id } = useParams(); const user = useUserState(); + const navigate = useNavigate(); const [treeOpen, setTreeOpen] = useState(false); @@ -664,7 +658,8 @@ export default function PartDetail() { label={t`In Production` + `: ${part.building}`} color="blue" visible={part.building > 0} - /> + />, + ]; }, [part, instanceQuery]); @@ -678,6 +673,20 @@ export default function PartDetail() { onFormSuccess: refreshInstance }); + const duplicatePart = useCreateApiFormModal({ + url: ApiEndpoints.part_list, + title: t`Add Part`, + fields: partFields, + initialData: { + ...part + }, + onFormSuccess: (response: any) => { + if (response.pk) { + navigate(getDetailUrl(ModelType.part, response.pk)); + } + } + }); + const stockActionProps: StockOperationProps = useMemo(() => { return { pk: part.pk, @@ -695,10 +704,10 @@ export default function PartDetail() { actions={[ ViewBarcodeAction({}), LinkBarcodeAction({ - hidden: part?.barcode_hash + hidden: part?.barcode_hash || !user.hasChangeRole(UserRoles.part) }), UnlinkBarcodeAction({ - hidden: !part?.barcode_hash + hidden: !part?.barcode_hash || !user.hasChangeRole(UserRoles.part) }) ]} />, @@ -737,7 +746,8 @@ export default function PartDetail() { icon={} actions={[ DuplicateItemAction({ - hidden: !user.hasAddRole(UserRoles.part) + hidden: !user.hasAddRole(UserRoles.part), + onClick: () => duplicatePart.open() }), EditItemAction({ hidden: !user.hasChangeRole(UserRoles.part), @@ -753,6 +763,7 @@ export default function PartDetail() { return ( <> + {duplicatePart.modal} {editPart.modal} diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx index ecf43cd7bb..a982cf3b23 100644 --- a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx +++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx @@ -30,7 +30,7 @@ import { NotesEditor } from '../../components/widgets/MarkdownEditor'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; -import { purchaseOrderFields } from '../../forms/PurchaseOrderForms'; +import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms'; import { useEditApiFormModal } from '../../hooks/UseForm'; import { useInstance } from '../../hooks/UseInstance'; import { apiUrl } from '../../states/ApiState'; @@ -60,11 +60,13 @@ export default function PurchaseOrderDetail() { refetchOnMount: true }); + const purchaseOrderFields = usePurchaseOrderFields(); + const editPurchaseOrder = useEditApiFormModal({ url: ApiEndpoints.purchase_order_list, pk: id, title: t`Edit Purchase Order`, - fields: purchaseOrderFields(), + fields: purchaseOrderFields, onFormSuccess: () => { refreshInstance(); } @@ -227,7 +229,12 @@ export default function PurchaseOrderDetail() { name: 'line-items', label: t`Line Items`, icon: , - content: + content: ( + + ) }, { name: 'received-stock', @@ -269,7 +276,6 @@ export default function PurchaseOrderDetail() { }, [order, id]); const poActions = useMemo(() => { - // TODO: Disable certain actions based on user permissions return [ } actions={[ EditItemAction({ + hidden: !user.hasChangeRole(UserRoles.purchase_order), onClick: () => { editPurchaseOrder.open(); } }), - DeleteItemAction({}) + DeleteItemAction({ + hidden: !user.hasDeleteRole(UserRoles.purchase_order) + }) ]} /> ]; diff --git a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx index 2455488673..c54b7d085c 100644 --- a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx +++ b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx @@ -1,6 +1,7 @@ import { t } from '@lingui/macro'; import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core'; import { + IconDots, IconInfoCircle, IconList, IconNotes, @@ -12,6 +13,11 @@ import { useParams } from 'react-router-dom'; import { DetailsField, DetailsTable } from '../../components/details/Details'; import { DetailsImage } from '../../components/details/DetailsImage'; import { ItemDetailsGrid } from '../../components/details/ItemDetails'; +import { + ActionDropdown, + DeleteItemAction, + EditItemAction +} from '../../components/items/ActionDropdown'; import { PageDetail } from '../../components/nav/PageDetail'; import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; import { StatusRenderer } from '../../components/render/StatusRenderer'; @@ -19,8 +25,11 @@ import { NotesEditor } from '../../components/widgets/MarkdownEditor'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; +import { useReturnOrderFields } from '../../forms/SalesOrderForms'; +import { useEditApiFormModal } from '../../hooks/UseForm'; import { useInstance } from '../../hooks/UseInstance'; import { apiUrl } from '../../states/ApiState'; +import { useUserState } from '../../states/UserState'; import { AttachmentTable } from '../../tables/general/AttachmentTable'; /** @@ -29,7 +38,13 @@ import { AttachmentTable } from '../../tables/general/AttachmentTable'; export default function ReturnOrderDetail() { const { id } = useParams(); - const { instance: order, instanceQuery } = useInstance({ + const user = useUserState(); + + const { + instance: order, + instanceQuery, + refreshInstance + } = useInstance({ endpoint: ApiEndpoints.return_order_list, pk: id, params: { @@ -233,8 +248,43 @@ export default function ReturnOrderDetail() { ]; }, [order, instanceQuery]); + const returnOrderFields = useReturnOrderFields(); + + const editReturnOrder = useEditApiFormModal({ + url: ApiEndpoints.return_order_list, + pk: order.pk, + title: t`Edit Return Order`, + fields: returnOrderFields, + onFormSuccess: () => { + refreshInstance(); + } + }); + + const orderActions = useMemo(() => { + return [ + } + actions={[ + EditItemAction({ + hidden: !user.hasChangeRole(UserRoles.return_order), + onClick: () => { + editReturnOrder.open(); + } + }), + DeleteItemAction({ + hidden: !user.hasDeleteRole(UserRoles.return_order) + // TODO: Delete? + }) + ]} + /> + ]; + }, [user]); + return ( <> + {editReturnOrder.modal} diff --git a/src/frontend/src/pages/sales/SalesOrderDetail.tsx b/src/frontend/src/pages/sales/SalesOrderDetail.tsx index 182c0fbfde..783551d860 100644 --- a/src/frontend/src/pages/sales/SalesOrderDetail.tsx +++ b/src/frontend/src/pages/sales/SalesOrderDetail.tsx @@ -1,6 +1,7 @@ import { t } from '@lingui/macro'; import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core'; import { + IconDots, IconInfoCircle, IconList, IconNotes, @@ -15,6 +16,11 @@ import { useParams } from 'react-router-dom'; import { DetailsField, DetailsTable } from '../../components/details/Details'; import { DetailsImage } from '../../components/details/DetailsImage'; import { ItemDetailsGrid } from '../../components/details/ItemDetails'; +import { + ActionDropdown, + DeleteItemAction, + EditItemAction +} from '../../components/items/ActionDropdown'; import { PageDetail } from '../../components/nav/PageDetail'; import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; import { StatusRenderer } from '../../components/render/StatusRenderer'; @@ -22,8 +28,11 @@ import { NotesEditor } from '../../components/widgets/MarkdownEditor'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; +import { useSalesOrderFields } from '../../forms/SalesOrderForms'; +import { useEditApiFormModal } from '../../hooks/UseForm'; import { useInstance } from '../../hooks/UseInstance'; import { apiUrl } from '../../states/ApiState'; +import { useUserState } from '../../states/UserState'; import { BuildOrderTable } from '../../tables/build/BuildOrderTable'; import { AttachmentTable } from '../../tables/general/AttachmentTable'; @@ -33,7 +42,13 @@ import { AttachmentTable } from '../../tables/general/AttachmentTable'; export default function SalesOrderDetail() { const { id } = useParams(); - const { instance: order, instanceQuery } = useInstance({ + const user = useUserState(); + + const { + instance: order, + instanceQuery, + refreshInstance + } = useInstance({ endpoint: ApiEndpoints.sales_order_list, pk: id, params: { @@ -185,6 +200,18 @@ export default function SalesOrderDetail() { ); }, [order, instanceQuery]); + const salesOrderFields = useSalesOrderFields(); + + const editSalesOrder = useEditApiFormModal({ + url: ApiEndpoints.sales_order_list, + pk: order.pk, + title: t`Edit Sales Order`, + fields: salesOrderFields, + onFormSuccess: () => { + refreshInstance(); + } + }); + const orderPanels: PanelType[] = useMemo(() => { return [ { @@ -245,6 +272,28 @@ export default function SalesOrderDetail() { ]; }, [order, id]); + const soActions = useMemo(() => { + return [ + } + actions={[ + EditItemAction({ + hidden: !user.hasChangeRole(UserRoles.sales_order), + onClick: () => { + editSalesOrder.open(); + } + }), + DeleteItemAction({ + hidden: !user.hasDeleteRole(UserRoles.sales_order) + // TODO: Delete? + }) + ]} + /> + ]; + }, [user]); + const orderBadges: ReactNode[] = useMemo(() => { return instanceQuery.isLoading ? [] @@ -259,6 +308,7 @@ export default function SalesOrderDetail() { return ( <> + {editSalesOrder.modal} diff --git a/src/frontend/src/pages/stock/StockDetail.tsx b/src/frontend/src/pages/stock/StockDetail.tsx index 072b881823..7a48713bfe 100644 --- a/src/frontend/src/pages/stock/StockDetail.tsx +++ b/src/frontend/src/pages/stock/StockDetail.tsx @@ -23,7 +23,7 @@ import { IconSitemap } from '@tabler/icons-react'; import { ReactNode, useMemo, useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { DetailsField, DetailsTable } from '../../components/details/Details'; import DetailsBadge from '../../components/details/DetailsBadge'; @@ -33,6 +33,7 @@ import { ActionDropdown, BarcodeActionDropdown, DeleteItemAction, + DuplicateItemAction, EditItemAction, LinkBarcodeAction, UnlinkBarcodeAction, @@ -50,12 +51,16 @@ import { StockOperationProps, useAddStockItem, useCountStockItem, - useEditStockItem, useRemoveStockItem, + useStockFields, useTransferStockItem } from '../../forms/StockForms'; import { InvenTreeIcon } from '../../functions/icons'; import { getDetailUrl } from '../../functions/urls'; +import { + useCreateApiFormModal, + useEditApiFormModal +} from '../../hooks/UseForm'; import { useInstance } from '../../hooks/UseInstance'; import { apiUrl } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; @@ -69,6 +74,8 @@ export default function StockDetail() { const user = useUserState(); + const navigate = useNavigate(); + const [treeOpen, setTreeOpen] = useState(false); const { @@ -349,9 +356,30 @@ export default function StockDetail() { [stockitem] ); - const editStockItem = useEditStockItem({ - item_id: stockitem.pk, - callback: () => refreshInstance() + const editStockItemFields = useStockFields({ create: false }); + + const editStockItem = useEditApiFormModal({ + url: ApiEndpoints.stock_item_list, + pk: stockitem.pk, + title: t`Edit Stock Item`, + fields: editStockItemFields, + onFormSuccess: refreshInstance + }); + + const duplicateStockItemFields = useStockFields({ create: true }); + + const duplicateStockItem = useCreateApiFormModal({ + url: ApiEndpoints.stock_item_list, + title: t`Add Stock Item`, + fields: duplicateStockItemFields, + initialData: { + ...stockitem + }, + onFormSuccess: (response: any) => { + if (response.pk) { + navigate(getDetailUrl(ModelType.stockitem, response.pk)); + } + } }); const stockActionProps: StockOperationProps = useMemo(() => { @@ -368,15 +396,17 @@ export default function StockDetail() { const transferStockItem = useTransferStockItem(stockActionProps); const stockActions = useMemo( - () => /* TODO: Disable actions based on user permissions*/ [ + () => [ , @@ -425,16 +455,20 @@ export default function StockDetail() { />, } actions={[ - { - name: t`Duplicate`, - tooltip: t`Duplicate stock item`, - icon: - }, - EditItemAction({}), - DeleteItemAction({}) + DuplicateItemAction({ + hidden: !user.hasAddRole(UserRoles.stock), + onClick: () => duplicateStockItem.open() + }), + EditItemAction({ + hidden: !user.hasChangeRole(UserRoles.stock), + onClick: () => editStockItem.open() + }), + DeleteItemAction({ + hidden: !user.hasDeleteRole(UserRoles.stock) + }) ]} /> ], @@ -489,6 +523,7 @@ export default function StockDetail() { /> {editStockItem.modal} + {duplicateStockItem.modal} {countStockItem.modal} {addStockItem.modal} {removeStockItem.modal} diff --git a/src/frontend/src/tables/build/BuildOrderTable.tsx b/src/frontend/src/tables/build/BuildOrderTable.tsx index 66a15af959..19f9438380 100644 --- a/src/frontend/src/tables/build/BuildOrderTable.tsx +++ b/src/frontend/src/tables/build/BuildOrderTable.tsx @@ -10,7 +10,7 @@ import { renderDate } from '../../defaults/formatters'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; -import { buildOrderFields } from '../../forms/BuildForms'; +import { useBuildOrderFields } from '../../forms/BuildForms'; import { getDetailUrl } from '../../functions/urls'; import { useCreateApiFormModal } from '../../hooks/UseForm'; import { useTable } from '../../hooks/UseTable'; @@ -135,10 +135,12 @@ export function BuildOrderTable({ const table = useTable('buildorder'); + const buildOrderFields = useBuildOrderFields({ create: true }); + const newBuild = useCreateApiFormModal({ url: ApiEndpoints.build_order_list, title: t`Add Build Order`, - fields: buildOrderFields(), + fields: buildOrderFields, initialData: { part: partId, sales_order: salesOrderId, diff --git a/src/frontend/src/tables/company/CompanyTable.tsx b/src/frontend/src/tables/company/CompanyTable.tsx index 82cef9f7f5..9199a2248c 100644 --- a/src/frontend/src/tables/company/CompanyTable.tsx +++ b/src/frontend/src/tables/company/CompanyTable.tsx @@ -1,5 +1,6 @@ import { t } from '@lingui/macro'; import { Group, Text } from '@mantine/core'; +import { access } from 'fs'; import { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -13,7 +14,8 @@ import { useCreateApiFormModal } from '../../hooks/UseForm'; import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; -import { DescriptionColumn } from '../ColumnRenderers'; +import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers'; +import { TableFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; /** @@ -51,6 +53,12 @@ export function CompanyTable({ } }, DescriptionColumn({}), + BooleanColumn({ + accessor: 'active', + title: t`Active`, + sortable: true, + switchable: true + }), { accessor: 'website', sortable: false @@ -73,6 +81,31 @@ export function CompanyTable({ } }); + const tableFilters: TableFilter[] = useMemo(() => { + return [ + { + name: 'active', + label: t`Active`, + description: t`Show active companies` + }, + { + name: 'is_supplier', + label: t`Supplier`, + description: t`Show companies which are suppliers` + }, + { + name: 'is_manufacturer', + label: t`Manufacturer`, + description: t`Show companies which are manufacturers` + }, + { + name: 'is_customer', + label: t`Customer`, + description: t`Show companies which are customers` + } + ]; + }, []); + const tableActions = useMemo(() => { const can_add = user.hasAddRole(UserRoles.purchase_order) || @@ -98,6 +131,7 @@ export function CompanyTable({ params: { ...params }, + tableFilters: tableFilters, tableActions: tableActions, onRowClick: (row: any) => { if (row.pk) { diff --git a/src/frontend/src/tables/company/ContactTable.tsx b/src/frontend/src/tables/company/ContactTable.tsx index 6641131e39..83e4e6ebf8 100644 --- a/src/frontend/src/tables/company/ContactTable.tsx +++ b/src/frontend/src/tables/company/ContactTable.tsx @@ -63,9 +63,7 @@ export function ContactTable({ }; }, []); - const [selectedContact, setSelectedContact] = useState( - undefined - ); + const [selectedContact, setSelectedContact] = useState(0); const editContact = useEditApiFormModal({ url: ApiEndpoints.contact_list, diff --git a/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx b/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx index ecac9aef13..9f85529353 100644 --- a/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx +++ b/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx @@ -44,9 +44,11 @@ import { TableHoverCard } from '../TableHoverCard'; */ export function PurchaseOrderLineItemTable({ orderId, + supplierId, params }: { orderId: number; + supplierId?: number; params?: any; }) { const table = useTable('purchase-order-line-item'); @@ -67,7 +69,7 @@ export function PurchaseOrderLineItemTable({ return [ { accessor: 'part', - title: t`Part`, + title: t`Internal Part`, sortable: true, switchable: false, render: (record: any) => { @@ -183,25 +185,35 @@ export function PurchaseOrderLineItemTable({ ]; }, [orderId, user]); + const addPurchaseOrderFields = usePurchaseOrderLineItemFields({ + create: true, + orderId: orderId, + supplierId: supplierId + }); + + const [initialData, setInitialData] = useState({}); + const newLine = useCreateApiFormModal({ url: ApiEndpoints.purchase_order_line_list, title: t`Add Line Item`, - fields: usePurchaseOrderLineItemFields({ create: true }), - initialData: { - order: orderId - }, + fields: addPurchaseOrderFields, + initialData: initialData, onFormSuccess: table.refreshTable }); - const [selectedLine, setSelectedLine] = useState( - undefined - ); + const [selectedLine, setSelectedLine] = useState(0); + + const editPurchaseOrderFields = usePurchaseOrderLineItemFields({ + create: false, + orderId: orderId, + supplierId: supplierId + }); const editLine = useEditApiFormModal({ url: ApiEndpoints.purchase_order_line_list, pk: selectedLine, title: t`Edit Line Item`, - fields: usePurchaseOrderLineItemFields({}), + fields: editPurchaseOrderFields, onFormSuccess: table.refreshTable }); @@ -235,7 +247,11 @@ export function PurchaseOrderLineItemTable({ } }), RowDuplicateAction({ - hidden: !user.hasAddRole(UserRoles.purchase_order) + hidden: !user.hasAddRole(UserRoles.purchase_order), + onClick: () => { + setInitialData({ ...record }); + newLine.open(); + } }), RowDeleteAction({ hidden: !user.hasDeleteRole(UserRoles.purchase_order), @@ -254,7 +270,12 @@ export function PurchaseOrderLineItemTable({ return [ newLine.open()} + onClick={() => { + setInitialData({ + order: orderId + }); + newLine.open(); + }} hidden={!user?.hasAddRole(UserRoles.purchase_order)} />, record?.manufacturer_part_detail?.MPN }, + BooleanColumn({ + accessor: 'active', + title: t`Active`, + sortable: true, + switchable: true + }), { accessor: 'in_stock', sortable: true @@ -145,35 +157,67 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode { ]; }, [params]); - const addSupplierPartFields = useSupplierPartFields({ - partPk: params?.part, - supplierPk: params?.supplier, - hidePart: true + const supplierPartFields = useSupplierPartFields(); + + const addSupplierPart = useCreateApiFormModal({ + url: ApiEndpoints.supplier_part_list, + title: t`Add Supplier Part`, + fields: supplierPartFields, + initialData: { + part: params?.part, + supplier: params?.supplier + }, + onFormSuccess: table.refreshTable, + successMessage: t`Supplier part created` }); - const { modal: addSupplierPartModal, open: openAddSupplierPartForm } = - useCreateApiFormModal({ - url: ApiEndpoints.supplier_part_list, - title: t`Add Supplier Part`, - fields: addSupplierPartFields, - onFormSuccess: table.refreshTable, - successMessage: t`Supplier part created` - }); - // Table actions const tableActions = useMemo(() => { - // TODO: Hide actions based on user permissions - return [ addSupplierPart.open()} + hidden={!user.hasAddRole(UserRoles.purchase_order)} /> ]; }, [user]); - const editSupplierPartFields = useSupplierPartFields({ - hidePart: true, - partPk: params?.part + const tableFilters: TableFilter[] = useMemo(() => { + return [ + { + name: 'active', + label: t`Active`, + description: t`Show active supplier parts` + }, + { + name: 'part_active', + label: t`Active Part`, + description: t`Show active internal parts` + }, + { + name: 'supplier_active', + label: t`Active Supplier`, + description: t`Show active suppliers` + } + ]; + }, []); + + const editSupplierPartFields = useSupplierPartFields(); + + const [selectedSupplierPart, setSelectedSupplierPart] = useState(0); + + const editSupplierPart = useEditApiFormModal({ + url: ApiEndpoints.supplier_part_list, + pk: selectedSupplierPart, + title: t`Edit Supplier Part`, + fields: editSupplierPartFields, + onFormSuccess: () => table.refreshTable() + }); + + const deleteSupplierPart = useDeleteApiFormModal({ + url: ApiEndpoints.supplier_part_list, + pk: selectedSupplierPart, + title: t`Delete Supplier Part`, + onFormSuccess: () => table.refreshTable() }); // Row action callback @@ -183,29 +227,15 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode { RowEditAction({ hidden: !user.hasChangeRole(UserRoles.purchase_order), onClick: () => { - record.pk && - openEditApiForm({ - url: ApiEndpoints.supplier_part_list, - pk: record.pk, - title: t`Edit Supplier Part`, - fields: editSupplierPartFields, - onFormSuccess: table.refreshTable, - successMessage: t`Supplier part updated` - }); + setSelectedSupplierPart(record.pk); + editSupplierPart.open(); } }), RowDeleteAction({ hidden: !user.hasDeleteRole(UserRoles.purchase_order), onClick: () => { - record.pk && - openDeleteApiForm({ - url: ApiEndpoints.supplier_part_list, - pk: record.pk, - title: t`Delete Supplier Part`, - successMessage: t`Supplier part deleted`, - onFormSuccess: table.refreshTable, - preFormWarning: t`Are you sure you want to remove this supplier part?` - }); + setSelectedSupplierPart(record.pk); + deleteSupplierPart.open(); } }) ]; @@ -215,7 +245,9 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode { return ( <> - {addSupplierPartModal} + {addSupplierPart.modal} + {editSupplierPart.modal} + {deleteSupplierPart.modal} diff --git a/src/frontend/src/tables/sales/ReturnOrderTable.tsx b/src/frontend/src/tables/sales/ReturnOrderTable.tsx index 0da8649b53..c9c98564d5 100644 --- a/src/frontend/src/tables/sales/ReturnOrderTable.tsx +++ b/src/frontend/src/tables/sales/ReturnOrderTable.tsx @@ -1,5 +1,6 @@ import { t } from '@lingui/macro'; import { useCallback, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import { AddItemButton } from '../../components/buttons/AddItemButton'; import { Thumbnail } from '../../components/images/Thumbnail'; @@ -7,7 +8,10 @@ import { formatCurrency } from '../../defaults/formatters'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; +import { useReturnOrderFields } from '../../forms/SalesOrderForms'; import { notYetImplemented } from '../../functions/notifications'; +import { getDetailUrl } from '../../functions/urls'; +import { useCreateApiFormModal } from '../../hooks/UseForm'; import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; @@ -33,6 +37,7 @@ import { InvenTreeTable } from '../InvenTreeTable'; export function ReturnOrderTable({ params }: { params?: any }) { const table = useTable('return-orders'); const user = useUserState(); + const navigate = useNavigate(); const tableFilters: TableFilter[] = useMemo(() => { return [ @@ -48,10 +53,6 @@ export function ReturnOrderTable({ params }: { params?: any }) { ]; }, []); - // TODO: Row actions - - // TODO: Table actions (e.g. create new return order) - const tableColumns = useMemo(() => { return [ ReferenceColumn(), @@ -94,34 +95,48 @@ export function ReturnOrderTable({ params }: { params?: any }) { ]; }, []); - const addReturnOrder = useCallback(() => { - notYetImplemented(); - }, []); + const returnOrderFields = useReturnOrderFields(); + + const newReturnOrder = useCreateApiFormModal({ + url: ApiEndpoints.return_order_list, + title: t`Add Return Order`, + fields: returnOrderFields, + onFormSuccess: (response) => { + if (response.pk) { + navigate(getDetailUrl(ModelType.returnorder, response.pk)); + } else { + table.refreshTable(); + } + } + }); const tableActions = useMemo(() => { return [