mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +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:
		@@ -1,13 +1,17 @@
 | 
			
		||||
"""InvenTree API version information."""
 | 
			
		||||
 | 
			
		||||
# 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."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    - Extend Info Endpoint to include customizations
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ import report.helpers
 | 
			
		||||
import report.models
 | 
			
		||||
import report.serializers
 | 
			
		||||
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 plugin.builtin.labels.inventree_label import InvenTreeLabelPlugin
 | 
			
		||||
 | 
			
		||||
@@ -309,14 +309,26 @@ class ReportAssetDetail(TemplatePermissionMixin, RetrieveUpdateDestroyAPI):
 | 
			
		||||
    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."""
 | 
			
		||||
 | 
			
		||||
    queryset = report.models.LabelOutput.objects.all()
 | 
			
		||||
    serializer_class = report.serializers.LabelOutputSerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ReportOutputList(TemplatePermissionMixin, BulkDeleteMixin, ListAPI):
 | 
			
		||||
class ReportOutputList(
 | 
			
		||||
    TemplatePermissionMixin, TemplateOutputMixin, BulkDeleteMixin, ListAPI
 | 
			
		||||
):
 | 
			
		||||
    """List endpoint for ReportOutput objects."""
 | 
			
		||||
 | 
			
		||||
    queryset = report.models.ReportOutput.objects.all()
 | 
			
		||||
 
 | 
			
		||||
@@ -193,7 +193,11 @@ class LabelOutputSerializer(BaseOutputSerializer):
 | 
			
		||||
        """Metaclass options."""
 | 
			
		||||
 | 
			
		||||
        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):
 | 
			
		||||
@@ -203,7 +207,11 @@ class ReportOutputSerializer(BaseOutputSerializer):
 | 
			
		||||
        """Metaclass options."""
 | 
			
		||||
 | 
			
		||||
        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):
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@ export function CurrencyTable({
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function CurrencyManagmentPanel() {
 | 
			
		||||
export default function CurrencyManagementPanel() {
 | 
			
		||||
  const [info, setInfo] = useState<any>({});
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
@@ -44,11 +44,13 @@ const TaskManagementPanel = Loadable(
 | 
			
		||||
  lazy(() => import('./TaskManagementPanel'))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const CurrencyManagmentPanel = Loadable(
 | 
			
		||||
  lazy(() => import('./CurrencyManagmentPanel'))
 | 
			
		||||
const CurrencyManagementPanel = Loadable(
 | 
			
		||||
  lazy(() => import('./CurrencyManagementPanel'))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const UnitManagmentPanel = Loadable(lazy(() => import('./UnitManagmentPanel')));
 | 
			
		||||
const UnitManagementPanel = Loadable(
 | 
			
		||||
  lazy(() => import('./UnitManagementPanel'))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const PluginManagementPanel = Loadable(
 | 
			
		||||
  lazy(() => import('./PluginManagementPanel'))
 | 
			
		||||
@@ -137,10 +139,10 @@ export default function AdminCenter() {
 | 
			
		||||
        name: 'currencies',
 | 
			
		||||
        label: t`Currencies`,
 | 
			
		||||
        icon: <IconCoins />,
 | 
			
		||||
        content: <CurrencyManagmentPanel />
 | 
			
		||||
        content: <CurrencyManagementPanel />
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: 'projectcodes',
 | 
			
		||||
        name: 'project-codes',
 | 
			
		||||
        label: t`Project Codes`,
 | 
			
		||||
        icon: <IconListDetails />,
 | 
			
		||||
        content: (
 | 
			
		||||
@@ -151,16 +153,16 @@ export default function AdminCenter() {
 | 
			
		||||
        )
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: 'customstates',
 | 
			
		||||
        name: 'custom-states',
 | 
			
		||||
        label: t`Custom States`,
 | 
			
		||||
        icon: <IconListDetails />,
 | 
			
		||||
        content: <CustomStateTable />
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: 'customunits',
 | 
			
		||||
        name: 'custom-units',
 | 
			
		||||
        label: t`Custom Units`,
 | 
			
		||||
        icon: <IconScale />,
 | 
			
		||||
        content: <UnitManagmentPanel />
 | 
			
		||||
        content: <UnitManagementPanel />
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: 'part-parameters',
 | 
			
		||||
 
 | 
			
		||||
@@ -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 { ModelType } from '../../../../enums/ModelType';
 | 
			
		||||
import { TemplateTable } from '../../../../tables/settings/TemplateTable';
 | 
			
		||||
import {
 | 
			
		||||
  TemplateOutputTable,
 | 
			
		||||
  TemplateTable
 | 
			
		||||
} from '../../../../tables/settings/TemplateTable';
 | 
			
		||||
 | 
			
		||||
export default function LabelTemplatePanel() {
 | 
			
		||||
function LabelTemplateTable() {
 | 
			
		||||
  return (
 | 
			
		||||
    <TemplateTable
 | 
			
		||||
      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>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,16 @@
 | 
			
		||||
import { t } from '@lingui/macro';
 | 
			
		||||
 | 
			
		||||
import { Accordion } from '@mantine/core';
 | 
			
		||||
import { YesNoButton } from '../../../../components/buttons/YesNoButton';
 | 
			
		||||
import { StylishText } from '../../../../components/items/StylishText';
 | 
			
		||||
import { ApiEndpoints } from '../../../../enums/ApiEndpoints';
 | 
			
		||||
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 (
 | 
			
		||||
    <TemplateTable
 | 
			
		||||
      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>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ function AllUnitTable() {
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function UnitManagmentPanel() {
 | 
			
		||||
export default function UnitManagementPanel() {
 | 
			
		||||
  return (
 | 
			
		||||
    <Stack gap='xs'>
 | 
			
		||||
      <Accordion defaultValue='custom'>
 | 
			
		||||
@@ -42,7 +42,7 @@ import { useTable } from '../../hooks/UseTable';
 | 
			
		||||
import { apiUrl } from '../../states/ApiState';
 | 
			
		||||
import { useUserState } from '../../states/UserState';
 | 
			
		||||
import type { TableColumn } from '../Column';
 | 
			
		||||
import { BooleanColumn } from '../ColumnRenderers';
 | 
			
		||||
import { BooleanColumn, DateColumn } from '../ColumnRenderers';
 | 
			
		||||
import type { TableFilter } from '../Filter';
 | 
			
		||||
import { InvenTreeTable } from '../InvenTreeTable';
 | 
			
		||||
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
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user