2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

[UI] Report ouputs (#9003)

* Typo fixes

* Display table of generated reports

* Display generated label outputs

* Translation

* Allow sorting of API Endpoints

* Add template detail to output serializers

* Add extra table column
This commit is contained in:
Oliver 2025-02-01 22:44:52 +11:00 committed by GitHub
parent bef6270ff6
commit eba004d835
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 182 additions and 21 deletions

View File

@ -1,13 +1,17 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 307 INVENTREE_API_VERSION = 308
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ INVENTREE_API_TEXT = """
v308 - 2025-02-01 : https://github.com/inventree/InvenTree/pull/9003
- Adds extra detail to the ReportOutput and LabelOutput API endpoints
- Allows ordering of output list endpoints
v307 - 2025-01-29 : https://github.com/inventree/InvenTree/pull/8969 v307 - 2025-01-29 : https://github.com/inventree/InvenTree/pull/8969
- Extend Info Endpoint to include customizations - Extend Info Endpoint to include customizations

View File

@ -19,7 +19,7 @@ import report.helpers
import report.models import report.models
import report.serializers import report.serializers
from InvenTree.api import BulkDeleteMixin, MetadataView from InvenTree.api import BulkDeleteMixin, MetadataView
from InvenTree.filters import InvenTreeSearchFilter from InvenTree.filters import InvenTreeOrderingFilter, InvenTreeSearchFilter
from InvenTree.mixins import ListAPI, ListCreateAPI, RetrieveUpdateDestroyAPI from InvenTree.mixins import ListAPI, ListCreateAPI, RetrieveUpdateDestroyAPI
from plugin.builtin.labels.inventree_label import InvenTreeLabelPlugin from plugin.builtin.labels.inventree_label import InvenTreeLabelPlugin
@ -309,14 +309,26 @@ class ReportAssetDetail(TemplatePermissionMixin, RetrieveUpdateDestroyAPI):
serializer_class = report.serializers.ReportAssetSerializer serializer_class = report.serializers.ReportAssetSerializer
class LabelOutputList(TemplatePermissionMixin, BulkDeleteMixin, ListAPI): class TemplateOutputMixin:
"""Mixin class for template output API endpoints."""
filter_backends = [InvenTreeOrderingFilter]
ordering_fields = ['created', 'model_type', 'user']
ordering_field_aliases = {'model_type': 'template__model_type'}
class LabelOutputList(
TemplatePermissionMixin, TemplateOutputMixin, BulkDeleteMixin, ListAPI
):
"""List endpoint for LabelOutput objects.""" """List endpoint for LabelOutput objects."""
queryset = report.models.LabelOutput.objects.all() queryset = report.models.LabelOutput.objects.all()
serializer_class = report.serializers.LabelOutputSerializer serializer_class = report.serializers.LabelOutputSerializer
class ReportOutputList(TemplatePermissionMixin, BulkDeleteMixin, ListAPI): class ReportOutputList(
TemplatePermissionMixin, TemplateOutputMixin, BulkDeleteMixin, ListAPI
):
"""List endpoint for ReportOutput objects.""" """List endpoint for ReportOutput objects."""
queryset = report.models.ReportOutput.objects.all() queryset = report.models.ReportOutput.objects.all()

View File

@ -193,7 +193,11 @@ class LabelOutputSerializer(BaseOutputSerializer):
"""Metaclass options.""" """Metaclass options."""
model = report.models.LabelOutput model = report.models.LabelOutput
fields = [*BaseOutputSerializer.base_fields(), 'plugin'] fields = [*BaseOutputSerializer.base_fields(), 'plugin', 'template_detail']
template_detail = LabelTemplateSerializer(
source='template', many=False, read_only=True
)
class ReportOutputSerializer(BaseOutputSerializer): class ReportOutputSerializer(BaseOutputSerializer):
@ -203,7 +207,11 @@ class ReportOutputSerializer(BaseOutputSerializer):
"""Metaclass options.""" """Metaclass options."""
model = report.models.ReportOutput model = report.models.ReportOutput
fields = BaseOutputSerializer.base_fields() fields = [*BaseOutputSerializer.base_fields(), 'template_detail']
template_detail = ReportTemplateSerializer(
source='template', many=False, read_only=True
)
class ReportSnippetSerializer(InvenTreeModelSerializer): class ReportSnippetSerializer(InvenTreeModelSerializer):

View File

@ -89,7 +89,7 @@ export function CurrencyTable({
); );
} }
export default function CurrencyManagmentPanel() { export default function CurrencyManagementPanel() {
const [info, setInfo] = useState<any>({}); const [info, setInfo] = useState<any>({});
return ( return (

View File

@ -44,11 +44,13 @@ const TaskManagementPanel = Loadable(
lazy(() => import('./TaskManagementPanel')) lazy(() => import('./TaskManagementPanel'))
); );
const CurrencyManagmentPanel = Loadable( const CurrencyManagementPanel = Loadable(
lazy(() => import('./CurrencyManagmentPanel')) lazy(() => import('./CurrencyManagementPanel'))
); );
const UnitManagmentPanel = Loadable(lazy(() => import('./UnitManagmentPanel'))); const UnitManagementPanel = Loadable(
lazy(() => import('./UnitManagementPanel'))
);
const PluginManagementPanel = Loadable( const PluginManagementPanel = Loadable(
lazy(() => import('./PluginManagementPanel')) lazy(() => import('./PluginManagementPanel'))
@ -137,10 +139,10 @@ export default function AdminCenter() {
name: 'currencies', name: 'currencies',
label: t`Currencies`, label: t`Currencies`,
icon: <IconCoins />, icon: <IconCoins />,
content: <CurrencyManagmentPanel /> content: <CurrencyManagementPanel />
}, },
{ {
name: 'projectcodes', name: 'project-codes',
label: t`Project Codes`, label: t`Project Codes`,
icon: <IconListDetails />, icon: <IconListDetails />,
content: ( content: (
@ -151,16 +153,16 @@ export default function AdminCenter() {
) )
}, },
{ {
name: 'customstates', name: 'custom-states',
label: t`Custom States`, label: t`Custom States`,
icon: <IconListDetails />, icon: <IconListDetails />,
content: <CustomStateTable /> content: <CustomStateTable />
}, },
{ {
name: 'customunits', name: 'custom-units',
label: t`Custom Units`, label: t`Custom Units`,
icon: <IconScale />, icon: <IconScale />,
content: <UnitManagmentPanel /> content: <UnitManagementPanel />
}, },
{ {
name: 'part-parameters', name: 'part-parameters',

View File

@ -1,8 +1,14 @@
import { t } from '@lingui/macro';
import { Accordion } from '@mantine/core';
import { StylishText } from '../../../../components/items/StylishText';
import { ApiEndpoints } from '../../../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../../../enums/ApiEndpoints';
import { ModelType } from '../../../../enums/ModelType'; import { ModelType } from '../../../../enums/ModelType';
import { TemplateTable } from '../../../../tables/settings/TemplateTable'; import {
TemplateOutputTable,
TemplateTable
} from '../../../../tables/settings/TemplateTable';
export default function LabelTemplatePanel() { function LabelTemplateTable() {
return ( return (
<TemplateTable <TemplateTable
templateProps={{ templateProps={{
@ -17,3 +23,29 @@ export default function LabelTemplatePanel() {
/> />
); );
} }
export default function LabelTemplatePanel() {
return (
<Accordion defaultValue={['templates']} multiple>
<Accordion.Item value='templates'>
<Accordion.Control>
<StylishText size='lg'>{t`Label Templates`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<LabelTemplateTable />
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value='outputs'>
<Accordion.Control>
<StylishText size='lg'>{t`Generated Labels`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<TemplateOutputTable
endpoint={ApiEndpoints.label_output}
withPlugins
/>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
);
}

View File

@ -1,11 +1,16 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Accordion } from '@mantine/core';
import { YesNoButton } from '../../../../components/buttons/YesNoButton'; import { YesNoButton } from '../../../../components/buttons/YesNoButton';
import { StylishText } from '../../../../components/items/StylishText';
import { ApiEndpoints } from '../../../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../../../enums/ApiEndpoints';
import { ModelType } from '../../../../enums/ModelType'; import { ModelType } from '../../../../enums/ModelType';
import { TemplateTable } from '../../../../tables/settings/TemplateTable'; import {
TemplateOutputTable,
TemplateTable
} from '../../../../tables/settings/TemplateTable';
export default function ReportTemplateTable() { function ReportTemplateTable() {
return ( return (
<TemplateTable <TemplateTable
templateProps={{ templateProps={{
@ -33,3 +38,26 @@ export default function ReportTemplateTable() {
/> />
); );
} }
export default function ReportTemplatePanel() {
return (
<Accordion defaultValue={['templates']} multiple>
<Accordion.Item value='templates'>
<Accordion.Control>
<StylishText size='lg'>{t`Report Templates`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<ReportTemplateTable />
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value='outputs'>
<Accordion.Control>
<StylishText size='lg'>{t`Generated Reports`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<TemplateOutputTable endpoint={ApiEndpoints.report_output} />
</Accordion.Panel>
</Accordion.Item>
</Accordion>
);
}

View File

@ -48,7 +48,7 @@ function AllUnitTable() {
); );
} }
export default function UnitManagmentPanel() { export default function UnitManagementPanel() {
return ( return (
<Stack gap='xs'> <Stack gap='xs'>
<Accordion defaultValue='custom'> <Accordion defaultValue='custom'>

View File

@ -42,7 +42,7 @@ import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState'; import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import type { TableColumn } from '../Column'; import type { TableColumn } from '../Column';
import { BooleanColumn } from '../ColumnRenderers'; import { BooleanColumn, DateColumn } from '../ColumnRenderers';
import type { TableFilter } from '../Filter'; import type { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable'; import { InvenTreeTable } from '../InvenTreeTable';
import { import {
@ -401,3 +401,78 @@ export function TemplateTable({
</> </>
); );
} }
export function TemplateOutputTable({
endpoint,
withPlugins = false
}: {
endpoint: ApiEndpoints;
withPlugins?: boolean;
}) {
const table = useTable(`${endpoint}-output`);
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'output',
sortable: false,
switchable: false,
title: t`Report Output`,
noWrap: true,
noContext: true,
render: (record: any) => {
if (record.output) {
return <AttachmentLink attachment={record.output} />;
} else {
return '-';
}
}
},
{
accessor: 'template_detail.name',
sortable: false,
switchable: false,
title: t`Template`
},
{
accessor: 'model_type',
sortable: true,
switchable: false,
title: t`Model Type`
},
DateColumn({
accessor: 'created',
title: t`Creation Date`,
switchable: false,
sortable: true
}),
{
accessor: 'plugin',
title: t`Plugin`,
hidden: !withPlugins
},
{
accessor: 'user_detail.username',
sortable: true,
ordering: 'user',
title: t`Created By`
}
];
}, [withPlugins]);
return (
<>
<InvenTreeTable
url={apiUrl(endpoint)}
tableState={table}
columns={tableColumns}
props={{
enableSearch: false,
enableColumnSwitching: false,
enableSelection: true,
enableBulkDelete: true
}}
/>
</>
);
}