mirror of
https://github.com/inventree/InvenTree.git
synced 2026-11-03 15:36:05 +00:00
Internal company support (#13017)
* Add internal company flag * Unit tests * Add changelog entry and update API version * add global company table * Expose model_filters for internal settings * Update API version * Update CHANGELOG * update assert --------- Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
1 parent
720ba157bb
commit
9b8fdeb06d
22 files changed
+337
-6
No files matched your search
@@ -1,11 +1,16 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 555
|
||||
INVENTREE_API_VERSION = 556
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v556 -> 2026-10-14 : https://github.com/inventree/InvenTree/pull/13017
|
||||
- Adds 'is_internal' field (and filter) to the Company API endpoint
|
||||
- Adds 'INVENTREE_DEFAULT_INTERNAL_COMPANY' global setting
|
||||
- Exposes 'model_filters' on the global and user settings API endpoints
|
||||
|
||||
v555 -> 2026-10-14 : https://github.com/inventree/InvenTree/pull/13016
|
||||
- Adds "contact" field to the SalesOrderShipment model and API endpoint
|
||||
- Adds "phone" and "email" fields to the Address model and API endpoint
|
||||
|
||||
@@ -179,6 +179,7 @@ class GlobalSettingsSerializer(SettingsSerializer):
|
||||
'units',
|
||||
'choices',
|
||||
'model_name',
|
||||
'model_filters',
|
||||
'api_url',
|
||||
'typ',
|
||||
'read_only',
|
||||
@@ -226,6 +227,7 @@ class UserSettingsSerializer(SettingsSerializer):
|
||||
'units',
|
||||
'choices',
|
||||
'model_name',
|
||||
'model_filters',
|
||||
'api_url',
|
||||
'typ',
|
||||
'confirm',
|
||||
|
||||
@@ -21,6 +21,24 @@ import report.helpers
|
||||
from common.setting.type import InvenTreeSettingsKeyType, SettingFlag
|
||||
|
||||
|
||||
def validate_internal_company(value):
|
||||
"""Validate that the provided value is empty, or references an internal company."""
|
||||
from company.models import Company
|
||||
|
||||
if value in [None, '']:
|
||||
return
|
||||
|
||||
try:
|
||||
pk = int(value)
|
||||
except (TypeError, ValueError):
|
||||
raise ValidationError({'value': _('Invalid company')})
|
||||
|
||||
if not Company.objects.filter(pk=pk, is_internal=True).exists():
|
||||
raise ValidationError({
|
||||
'value': _('Selected company must be marked as an internal company')
|
||||
})
|
||||
|
||||
|
||||
def validate_part_name_format(value):
|
||||
"""Validate part name format.
|
||||
|
||||
@@ -255,6 +273,14 @@ SYSTEM_SETTINGS: dict[str, InvenTreeSettingsKeyType] = {
|
||||
'description': _('Internal company name'),
|
||||
'default': 'My company name',
|
||||
},
|
||||
'INVENTREE_DEFAULT_INTERNAL_COMPANY': {
|
||||
'name': _('Default internal company'),
|
||||
'description': _('Internal company which represents this organization'),
|
||||
'default': '',
|
||||
'model': 'company.company',
|
||||
'model_filters': {'is_internal': True},
|
||||
'validator': validate_internal_company,
|
||||
},
|
||||
'INVENTREE_BASE_URL': {
|
||||
'name': _('Base URL'),
|
||||
'description': _('Base URL for server instance'),
|
||||
|
||||
@@ -756,6 +756,30 @@ class GlobalSettingsApiTest(InvenTreeAPITestCase):
|
||||
# There should be no user sessions now
|
||||
self.assertEqual(self.user.usersession_set.count(), 0)
|
||||
|
||||
def test_model_filters(self):
|
||||
"""Test that model-backed settings expose their model filters."""
|
||||
for key, model_name, filters in [
|
||||
(
|
||||
'INVENTREE_DEFAULT_INTERNAL_COMPANY',
|
||||
'company.company',
|
||||
{'is_internal': True},
|
||||
),
|
||||
(
|
||||
'PURCHASEORDER_DEFAULT_RECEIVE_LOCATION',
|
||||
'stock.stocklocation',
|
||||
{'structural': False},
|
||||
),
|
||||
]:
|
||||
url = reverse('api-global-setting-detail', kwargs={'key': key})
|
||||
response = self.get(url, expected_code=200)
|
||||
|
||||
self.assertEqual(response.data['model_name'], model_name)
|
||||
self.assertEqual(response.data['model_filters'], filters)
|
||||
|
||||
# Settings without a model have no filters
|
||||
url = reverse('api-global-setting-detail', kwargs={'key': 'INVENTREE_INSTANCE'})
|
||||
self.assertIsNone(self.get(url, expected_code=200).data['model_filters'])
|
||||
|
||||
def test_api_detail(self):
|
||||
"""Test that we can access the detail view for a setting based on the <key>."""
|
||||
# These keys are invalid, and should return 404
|
||||
|
||||
@@ -45,7 +45,14 @@ class CompanyFilter(FilterSet):
|
||||
"""Metaclass options."""
|
||||
|
||||
model = Company
|
||||
fields = ['is_customer', 'is_manufacturer', 'is_supplier', 'name', 'active']
|
||||
fields = [
|
||||
'is_customer',
|
||||
'is_manufacturer',
|
||||
'is_supplier',
|
||||
'is_internal',
|
||||
'name',
|
||||
'active',
|
||||
]
|
||||
|
||||
tag_name = common.filters.TagsFilter()
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.2.17 on 2026-10-14 02:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("company", "0082_address_phone_email"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="company",
|
||||
name="is_internal",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Does this company represent your own organization?",
|
||||
verbose_name="Is internal",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -27,6 +27,7 @@ import InvenTree.ready
|
||||
import InvenTree.validators
|
||||
import report.mixins
|
||||
from common.currency import currency_code_default
|
||||
from common.settings import get_global_setting
|
||||
from InvenTree.fields import InvenTreeURLField, RoundingDecimalField
|
||||
from order.status_codes import PurchaseOrderStatusGroups
|
||||
|
||||
@@ -91,6 +92,7 @@ class Company(
|
||||
- A supplier is a company from which parts can be purchased
|
||||
- A customer is a company to which parts can be sold
|
||||
- A manufacturer is a company which manufactures a raw good (they may or may not be a "supplier" also)
|
||||
- An internal company represents your own organization (or one of its entities / sites)
|
||||
|
||||
|
||||
Attributes:
|
||||
@@ -107,6 +109,7 @@ class Company(
|
||||
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
|
||||
is_internal: boolean value, does this company represent your own organization
|
||||
currency_code: Specifies the default currency for the company
|
||||
tax_id: Tax ID for the company
|
||||
"""
|
||||
@@ -129,6 +132,29 @@ class Company(
|
||||
"""Return the API URL associated with the Company model."""
|
||||
return reverse('api-company-list')
|
||||
|
||||
@classmethod
|
||||
def get_default_internal(cls) -> Optional['Company']:
|
||||
"""Return the default internal company for this InvenTree instance.
|
||||
|
||||
- If the INVENTREE_DEFAULT_INTERNAL_COMPANY setting points to an internal company, return it
|
||||
- Otherwise, if exactly one internal company exists, return it
|
||||
- Otherwise, return None
|
||||
"""
|
||||
internal = cls.objects.filter(is_internal=True)
|
||||
|
||||
if pk := get_global_setting(
|
||||
'INVENTREE_DEFAULT_INTERNAL_COMPANY', backup_value=None
|
||||
):
|
||||
try:
|
||||
if company := internal.filter(pk=int(pk)).first():
|
||||
return company
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
companies = list(internal[:2])
|
||||
|
||||
return companies[0] if len(companies) == 1 else None
|
||||
|
||||
def report_context(self) -> CompanyReportContext:
|
||||
"""Generate a dict of context data to provide to the reporting framework."""
|
||||
return {
|
||||
@@ -212,6 +238,12 @@ class Company(
|
||||
help_text=_('Does this company manufacture parts?'),
|
||||
)
|
||||
|
||||
is_internal = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_('Is internal'),
|
||||
help_text=_('Does this company represent your own organization?'),
|
||||
)
|
||||
|
||||
currency = models.CharField(
|
||||
max_length=3,
|
||||
verbose_name=_('Currency'),
|
||||
|
||||
@@ -146,6 +146,7 @@ class CompanySerializer(
|
||||
'is_customer',
|
||||
'is_manufacturer',
|
||||
'is_supplier',
|
||||
'is_internal',
|
||||
'parts_supplied',
|
||||
'parts_manufactured',
|
||||
'primary_address',
|
||||
|
||||
@@ -71,6 +71,35 @@ class CompanyTest(InvenTreeAPITestCase):
|
||||
response = self.get(url, data)
|
||||
self.assertEqual(len(response.data), 2)
|
||||
|
||||
def test_company_internal(self):
|
||||
"""Test the 'is_internal' field and filter."""
|
||||
url = reverse('api-company-list')
|
||||
|
||||
self.assertEqual(len(self.get(url, {'is_internal': True}).data), 0)
|
||||
|
||||
response = self.post(
|
||||
url,
|
||||
{'name': 'Our Company', 'is_internal': True, 'is_supplier': False},
|
||||
expected_code=201,
|
||||
)
|
||||
self.assertTrue(response.data['is_internal'])
|
||||
pk = response.data['pk']
|
||||
|
||||
response = self.get(url, {'is_internal': True})
|
||||
self.assertEqual(len(response.data), 1)
|
||||
self.assertEqual(response.data[0]['pk'], pk)
|
||||
|
||||
self.assertEqual(len(self.get(url, {'is_internal': False}).data), 3)
|
||||
|
||||
# Only an internal company can be selected as the default
|
||||
setting_url = reverse(
|
||||
'api-global-setting-detail',
|
||||
kwargs={'key': 'INVENTREE_DEFAULT_INTERNAL_COMPANY'},
|
||||
)
|
||||
|
||||
self.patch(setting_url, {'value': self.acme.pk}, expected_code=400)
|
||||
self.patch(setting_url, {'value': pk}, expected_code=200)
|
||||
|
||||
def test_company_detail(self):
|
||||
"""Tests for the Company detail endpoint."""
|
||||
url = reverse('api-company-detail', kwargs={'pk': self.acme.pk})
|
||||
|
||||
@@ -5,6 +5,8 @@ import os
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import TestCase
|
||||
|
||||
from common.models import InvenTreeSetting
|
||||
from common.settings import set_global_setting
|
||||
from part.models import Part
|
||||
|
||||
from .models import (
|
||||
@@ -159,6 +161,57 @@ class ContactSimpleTest(TestCase):
|
||||
self.assertEqual(Contact.objects.count(), 0)
|
||||
|
||||
|
||||
class InternalCompanyTest(TestCase):
|
||||
"""Unit tests for internal company support."""
|
||||
|
||||
SETTING = 'INVENTREE_DEFAULT_INTERNAL_COMPANY'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
"""Create some companies to work with."""
|
||||
super().setUpTestData()
|
||||
|
||||
cls.supplier = Company.objects.create(name='Supplier', is_supplier=True)
|
||||
cls.hq = Company.objects.create(name='HQ', is_supplier=False, is_internal=True)
|
||||
|
||||
def test_default(self):
|
||||
"""Test the Company.get_default_internal() helper."""
|
||||
set_global_setting(self.SETTING, '')
|
||||
|
||||
# A single internal company is used as the default
|
||||
self.assertEqual(Company.get_default_internal(), self.hq)
|
||||
|
||||
# Multiple internal companies, with no setting, is ambiguous
|
||||
site = Company.objects.create(name='Site', is_supplier=False, is_internal=True)
|
||||
self.assertIsNone(Company.get_default_internal())
|
||||
|
||||
# The setting selects between them
|
||||
set_global_setting(self.SETTING, site.pk)
|
||||
self.assertEqual(Company.get_default_internal(), site)
|
||||
|
||||
# A setting which no longer points to an internal company is ignored
|
||||
site.is_internal = False
|
||||
site.save()
|
||||
self.assertEqual(Company.get_default_internal(), self.hq)
|
||||
|
||||
# No internal companies at all
|
||||
Company.objects.filter(is_internal=True).update(is_internal=False)
|
||||
self.assertIsNone(Company.get_default_internal())
|
||||
|
||||
def test_setting_validation(self):
|
||||
"""The setting may only reference an internal company."""
|
||||
setting = InvenTreeSetting.get_setting_object(self.SETTING, create=True)
|
||||
|
||||
for value in [self.supplier.pk, 99999, 'abc']:
|
||||
setting.value = value
|
||||
with self.assertRaises(ValidationError):
|
||||
setting.full_clean()
|
||||
|
||||
for value in ['', self.hq.pk]:
|
||||
setting.value = value
|
||||
setting.full_clean()
|
||||
|
||||
|
||||
class AddressTest(TestCase):
|
||||
"""Unit tests for the Address model."""
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class ImporterTest(ImporterMixin, InvenTreeTestCase):
|
||||
|
||||
session.extract_columns()
|
||||
|
||||
self.assertEqual(session.column_mappings.count(), 13)
|
||||
self.assertEqual(session.column_mappings.count(), 14)
|
||||
|
||||
# Check some of the field mappings
|
||||
for field, col in [
|
||||
|
||||
@@ -30,6 +30,7 @@ import report.helpers
|
||||
import report.validators
|
||||
from common.models import DataOutput, RenderChoices, UpdatedUserMixin
|
||||
from common.settings import get_global_setting
|
||||
from company.models import Company
|
||||
from InvenTree.helpers_model import get_base_url
|
||||
from InvenTree.models import MetadataMixin
|
||||
from plugin import InvenTreePlugin, PluginMixinEnum
|
||||
@@ -158,6 +159,7 @@ class BaseContextExtension(TypedDict):
|
||||
template_name: Name of the report template
|
||||
template_revision: Revision of the report template
|
||||
user: User who is creating the report (if available)
|
||||
internal_company: The default internal company (if defined) which represents this organization
|
||||
report_vars: Private dict for the {% get_var %} / {% set_var %} template tags
|
||||
"""
|
||||
|
||||
@@ -169,6 +171,7 @@ class BaseContextExtension(TypedDict):
|
||||
template_name: str
|
||||
template_revision: int
|
||||
user: Optional[AbstractUser]
|
||||
internal_company: Optional[Company]
|
||||
report_vars: dict
|
||||
|
||||
|
||||
@@ -410,6 +413,13 @@ class ReportTemplateBase(
|
||||
"""Return a filter dict which can be applied to the target model."""
|
||||
return report.validators.validate_filters(self.filters, model=self.get_model())
|
||||
|
||||
def get_internal_company(self) -> Optional[Company]:
|
||||
"""Return the default internal company, cached for the lifetime of this template instance."""
|
||||
if not hasattr(self, '_internal_company'):
|
||||
self._internal_company = Company.get_default_internal()
|
||||
|
||||
return self._internal_company
|
||||
|
||||
def base_context(self, **kwargs) -> BaseContextExtension:
|
||||
"""Return base context data (available to all templates)."""
|
||||
return {
|
||||
@@ -421,6 +431,7 @@ class ReportTemplateBase(
|
||||
'template_name': self.name,
|
||||
'template_revision': self.revision,
|
||||
'user': kwargs.get('user'),
|
||||
'internal_company': self.get_internal_company(),
|
||||
# A private, per-render dict for the {% get_var %} / {% set_var %} tags.
|
||||
# This is discarded once rendering of *this* instance completes,
|
||||
# and is never shared between instances, reports, or requests.
|
||||
|
||||
@@ -67,6 +67,23 @@ class ReportTest(InvenTreeAPITestCase):
|
||||
|
||||
return super().setUp()
|
||||
|
||||
def test_internal_company_context(self):
|
||||
"""Test that the default internal company is available in the base context."""
|
||||
from company.models import Company
|
||||
|
||||
template = ReportTemplate.objects.first()
|
||||
self.assertIsNone(template.base_context()['internal_company'])
|
||||
|
||||
company = Company.objects.create(
|
||||
name='Our Company', is_supplier=False, is_internal=True
|
||||
)
|
||||
|
||||
# Value is cached against the template instance
|
||||
self.assertIsNone(template.base_context()['internal_company'])
|
||||
|
||||
template = ReportTemplate.objects.get(pk=template.pk)
|
||||
self.assertEqual(template.base_context()['internal_company'], company)
|
||||
|
||||
def test_list_endpoint(self):
|
||||
"""Test that the LIST endpoint works for each report."""
|
||||
url = reverse('api-report-template-list')
|
||||
|
||||
@@ -189,6 +189,7 @@ export function companyFields({
|
||||
is_supplier: {},
|
||||
is_manufacturer: {},
|
||||
is_customer: {},
|
||||
is_internal: {},
|
||||
active: {},
|
||||
duplicate: DuplicateField({
|
||||
originalId: duplicateCompanyId,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
IconBrandTelegram,
|
||||
IconBuilding,
|
||||
IconBuildingFactory2,
|
||||
IconBuildingSkyscraper,
|
||||
IconBuildingStore,
|
||||
IconBusinessplan,
|
||||
IconCalendar,
|
||||
@@ -158,6 +159,7 @@ const icons: InvenTreeIconType = {
|
||||
manufacturers: IconBuildingFactory2,
|
||||
suppliers: IconBuilding,
|
||||
customers: IconBuildingStore,
|
||||
internal_company: IconBuildingSkyscraper,
|
||||
purchase_orders: IconShoppingCart,
|
||||
return_orders: IconTruckReturn,
|
||||
transfer_orders: IconTransfer,
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { PanelGroupType, PanelType } from '@lib/types/Panel';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Stack } from '@mantine/core';
|
||||
import {
|
||||
IconBuilding,
|
||||
IconCoins,
|
||||
IconCpu,
|
||||
IconDevicesPc,
|
||||
@@ -35,6 +36,7 @@ import { PanelGroup } from '../../../../components/panels/PanelGroup';
|
||||
import { GlobalSettingList } from '../../../../components/settings/SettingList';
|
||||
import { Loadable } from '../../../../functions/loading';
|
||||
import { useUserState } from '../../../../states/UserState';
|
||||
import { CompanyTable } from '../../../../tables/company/CompanyTable';
|
||||
import ParameterTemplateTable from '../../../../tables/general/ParameterTemplateTable';
|
||||
import SelectionListTable from '../../../../tables/settings/SelectionListTable';
|
||||
|
||||
@@ -176,6 +178,17 @@ export default function AdminCenter() {
|
||||
icon: <IconExclamationCircle />,
|
||||
content: <ErrorReportTable />
|
||||
},
|
||||
{
|
||||
name: 'companies',
|
||||
label: t`Companies`,
|
||||
icon: <IconBuilding />,
|
||||
content: (
|
||||
<Stack gap='xs'>
|
||||
<GlobalSettingList keys={['INVENTREE_DEFAULT_INTERNAL_COMPANY']} />
|
||||
<CompanyTable companyType='all' />
|
||||
</Stack>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'currencies',
|
||||
label: t`Currencies`,
|
||||
@@ -299,6 +312,7 @@ export default function AdminCenter() {
|
||||
'barcode-history',
|
||||
'background',
|
||||
'errors',
|
||||
'companies',
|
||||
'currencies',
|
||||
'email'
|
||||
]
|
||||
|
||||
@@ -47,6 +47,7 @@ export default function SystemSettings() {
|
||||
keys={[
|
||||
'INVENTREE_BASE_URL',
|
||||
'INVENTREE_COMPANY_NAME',
|
||||
'INVENTREE_DEFAULT_INTERNAL_COMPANY',
|
||||
'INVENTREE_INSTANCE',
|
||||
'INVENTREE_INSTANCE_TITLE',
|
||||
'INVENTREE_INSTANCE_ID',
|
||||
|
||||
@@ -83,6 +83,13 @@ export function CompanyDetailsPanel({
|
||||
name: 'is_customer',
|
||||
label: t`Customer`,
|
||||
icon: 'customers'
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'is_internal',
|
||||
label: t`Internal`,
|
||||
icon: 'internal_company',
|
||||
hidden: !instance?.is_internal
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -27,6 +27,13 @@ import { usePreviewDrawerState } from '../../states/PreviewDrawerState';
|
||||
import { useUserSettingsState } from '../../states/SettingsStates';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
|
||||
type CompanyTableType =
|
||||
| 'all'
|
||||
| 'internal'
|
||||
| 'supplier'
|
||||
| 'manufacturer'
|
||||
| 'customer';
|
||||
|
||||
/**
|
||||
* A table which displays a list of company records,
|
||||
* based on the provided filter parameters
|
||||
@@ -34,10 +41,12 @@ import { useUserState } from '../../states/UserState';
|
||||
export function CompanyTable({
|
||||
companyType,
|
||||
params,
|
||||
initialData,
|
||||
path
|
||||
}: Readonly<{
|
||||
companyType?: string;
|
||||
companyType?: CompanyTableType;
|
||||
params?: any;
|
||||
initialData?: any;
|
||||
path?: string;
|
||||
}>) {
|
||||
const table = useTable(`company-${companyType ?? 'index'}`, {
|
||||
@@ -77,18 +86,54 @@ export function CompanyTable({
|
||||
sortable: true,
|
||||
switchable: true
|
||||
}),
|
||||
BooleanColumn({
|
||||
accessor: 'is_supplier',
|
||||
filter: 'is_supplier',
|
||||
title: t`Supplier`,
|
||||
sortable: true,
|
||||
switchable: true,
|
||||
hidden: companyType === 'supplier',
|
||||
defaultVisible: false
|
||||
}),
|
||||
BooleanColumn({
|
||||
accessor: 'is_manufacturer',
|
||||
filter: 'is_manufacturer',
|
||||
title: t`Manufacturer`,
|
||||
sortable: true,
|
||||
switchable: true,
|
||||
hidden: companyType === 'manufacturer',
|
||||
defaultVisible: false
|
||||
}),
|
||||
BooleanColumn({
|
||||
accessor: 'is_customer',
|
||||
filter: 'is_customer',
|
||||
title: t`Customer`,
|
||||
sortable: true,
|
||||
switchable: true,
|
||||
hidden: companyType === 'customer',
|
||||
defaultVisible: false
|
||||
}),
|
||||
BooleanColumn({
|
||||
accessor: 'is_internal',
|
||||
filter: 'is_internal',
|
||||
title: t`Internal`,
|
||||
sortable: true,
|
||||
switchable: true,
|
||||
hidden: companyType === 'internal',
|
||||
defaultVisible: false
|
||||
}),
|
||||
{
|
||||
accessor: 'website',
|
||||
sortable: false
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
}, [companyType]);
|
||||
|
||||
const newCompany = useCreateApiFormModal({
|
||||
url: ApiEndpoints.company_list,
|
||||
title: t`Add Company`,
|
||||
fields: companyFields(),
|
||||
initialData: params,
|
||||
initialData: initialData ?? params,
|
||||
follow: true,
|
||||
modelType: ModelType.company,
|
||||
keepOpenOption: true
|
||||
@@ -126,6 +171,11 @@ export function CompanyTable({
|
||||
label: t`Customer`,
|
||||
description: t`Show companies which are customers`
|
||||
},
|
||||
{
|
||||
name: 'is_internal',
|
||||
label: t`Internal`,
|
||||
description: t`Show companies which represent your own organization`
|
||||
},
|
||||
TagsFilter({ modelType: ModelType.company })
|
||||
];
|
||||
}, []);
|
||||
|
||||
Reference in new issue
Block a user