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

Created by (#8848)

* Add 'created_by' field to order API endpoints

* Add 'created_by' filter

* Allow ordering by 'created_by' field

* Update UI tables

- Show "Created By" column
- Column sorting
- Column filtering

* Cleanup order detail pages

* Bump API version

* Refactor table filters

* Fix BuildOrderTable filters
This commit is contained in:
Oliver 2025-01-08 10:07:38 +11:00 committed by GitHub
parent 296c54a1d7
commit 9138e31e58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 185 additions and 114 deletions

View File

@ -1,13 +1,18 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 297
INVENTREE_API_VERSION = 298
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v298 - 2025-01-07 - https://github.com/inventree/InvenTree/pull/8848
- Adds 'created_by' field to PurchaseOrder API endpoints
- Adds 'created_by' field to SalesOrder API endpoints
- Adds 'created_by' field to ReturnOrder API endpoints
v297 - 2024-12-29 - https://github.com/inventree/InvenTree/pull/8438
- Adjustments to the CustomUserState API endpoints and serializers

View File

@ -5,6 +5,7 @@ from typing import cast
from django.conf import settings
from django.contrib.auth import authenticate, login
from django.contrib.auth.models import User
from django.db.models import F, Q
from django.http.response import JsonResponse
from django.urls import include, path, re_path
@ -168,6 +169,10 @@ class OrderFilter(rest_filters.FilterSet):
queryset=Owner.objects.all(), field_name='responsible', label=_('Responsible')
)
created_by = rest_filters.ModelChoiceFilter(
queryset=User.objects.all(), field_name='created_by', label=_('Created By')
)
created_before = InvenTreeDateFilter(
label=_('Created Before'), field_name='creation_date', lookup_expr='lt'
)
@ -328,6 +333,7 @@ class PurchaseOrderList(
ordering_fields = [
'creation_date',
'created_by',
'reference',
'supplier__name',
'target_date',
@ -785,6 +791,7 @@ class SalesOrderList(
ordering_fields = [
'creation_date',
'created_by',
'reference',
'customer__name',
'customer_reference',
@ -1369,6 +1376,7 @@ class ReturnOrderList(
ordering_fields = [
'creation_date',
'created_by',
'reference',
'customer__name',
'customer_reference',

View File

@ -50,6 +50,7 @@ from InvenTree.serializers import (
InvenTreeModelSerializer,
InvenTreeMoneySerializer,
NotesFieldMixin,
UserSerializer,
)
from order.status_codes import (
PurchaseOrderStatusGroups,
@ -158,6 +159,8 @@ class AbstractOrderSerializer(DataImportExportSerializerMixin, serializers.Seria
required=False, allow_null=True, label=_('Creation Date')
)
created_by = UserSerializer(read_only=True)
duplicate = DuplicateOrderSerializer(
label=_('Duplicate Order'),
help_text=_('Specify options for duplicating this order'),
@ -174,6 +177,7 @@ class AbstractOrderSerializer(DataImportExportSerializerMixin, serializers.Seria
def annotate_queryset(queryset):
"""Add extra information to the queryset."""
queryset = queryset.annotate(line_items=SubqueryCount('lines'))
queryset = queryset.select_related('created_by')
return queryset
@ -183,6 +187,7 @@ class AbstractOrderSerializer(DataImportExportSerializerMixin, serializers.Seria
return [
'pk',
'creation_date',
'created_by',
'target_date',
'description',
'line_items',

View File

@ -217,6 +217,13 @@ export default function PurchaseOrderDetail() {
icon: 'reference',
copy: true,
hidden: !order.project_code
},
{
type: 'text',
name: 'responsible',
label: t`Responsible`,
badge: 'owner',
hidden: !order.responsible
}
];
@ -225,6 +232,7 @@ export default function PurchaseOrderDetail() {
type: 'date',
name: 'creation_date',
label: t`Creation Date`,
copy: true,
icon: 'calendar'
},
{
@ -240,6 +248,7 @@ export default function PurchaseOrderDetail() {
name: 'target_date',
label: t`Target Date`,
icon: 'calendar',
copy: true,
hidden: !order.target_date
},
{
@ -249,13 +258,6 @@ export default function PurchaseOrderDetail() {
label: t`Completion Date`,
copy: true,
hidden: !order.complete_date
},
{
type: 'text',
name: 'responsible',
label: t`Responsible`,
badge: 'owner',
hidden: !order.responsible
}
];

View File

@ -187,6 +187,13 @@ export default function ReturnOrderDetail() {
icon: 'reference',
copy: true,
hidden: !order.project_code
},
{
type: 'text',
name: 'responsible',
label: t`Responsible`,
badge: 'owner',
hidden: !order.responsible
}
];
@ -221,13 +228,6 @@ export default function ReturnOrderDetail() {
label: t`Completion Date`,
copy: true,
hidden: !order.complete_date
},
{
type: 'text',
name: 'responsible',
label: t`Responsible`,
badge: 'owner',
hidden: !order.responsible
}
];

View File

@ -199,6 +199,13 @@ export default function SalesOrderDetail() {
icon: 'reference',
copy: true,
hidden: !order.project_code
},
{
type: 'text',
name: 'responsible',
label: t`Responsible`,
badge: 'owner',
hidden: !order.responsible
}
];
@ -231,13 +238,6 @@ export default function SalesOrderDetail() {
label: t`Completion Date`,
hidden: !order.shipment_date,
copy: true
},
{
type: 'text',
name: 'responsible',
label: t`Responsible`,
badge: 'owner',
hidden: !order.responsible
}
];

View File

@ -9,7 +9,7 @@ import { YesNoButton } from '../components/buttons/YesNoButton';
import { Thumbnail } from '../components/images/Thumbnail';
import { ProgressBar } from '../components/items/ProgressBar';
import { TableStatusRenderer } from '../components/render/StatusRenderer';
import { RenderOwner } from '../components/render/User';
import { RenderOwner, RenderUser } from '../components/render/User';
import { formatCurrency, formatDate } from '../defaults/formatters';
import type { ModelType } from '../enums/ModelType';
import { resolveItem } from '../functions/conversion';
@ -202,6 +202,18 @@ export function StatusColumn({
};
}
export function CreatedByColumn(props: TableColumnProps): TableColumn {
return {
accessor: 'created_by',
title: t`Created By`,
sortable: true,
switchable: true,
render: (record: any) =>
record.created_by && RenderUser({ instance: record.created_by }),
...props
};
}
export function ResponsibleColumn(props: TableColumnProps): TableColumn {
return {
accessor: 'responsible',

View File

@ -205,3 +205,47 @@ export function HasProjectCodeFilter(): TableFilter {
description: t`Show orders with an assigned project code`
};
}
export function OrderStatusFilter({
model
}: { model: ModelType }): TableFilter {
return {
name: 'status',
label: t`Status`,
description: t`Filter by order status`,
choiceFunction: StatusFilterOptions(model)
};
}
export function ProjectCodeFilter({
choices
}: { choices: TableFilterChoice[] }): TableFilter {
return {
name: 'project_code',
label: t`Project Code`,
description: t`Filter by project code`,
choices: choices
};
}
export function ResponsibleFilter({
choices
}: { choices: TableFilterChoice[] }): TableFilter {
return {
name: 'assigned_to',
label: t`Responsible`,
description: t`Filter by responsible owner`,
choices: choices
};
}
export function CreatedByFilter({
choices
}: { choices: TableFilterChoice[] }): TableFilter {
return {
name: 'created_by',
label: t`Created By`,
description: t`Filter by user who created the order`,
choices: choices
};
}

View File

@ -8,7 +8,11 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useBuildOrderFields } from '../../forms/BuildForms';
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
import {
useOwnerFilters,
useProjectCodeFilters,
useUserFilters
} from '../../hooks/UseFilter';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
@ -32,8 +36,11 @@ import {
HasProjectCodeFilter,
MaxDateFilter,
MinDateFilter,
OrderStatusFilter,
OutstandingFilter,
OverdueFilter,
StatusFilterOptions,
ProjectCodeFilter,
ResponsibleFilter,
type TableFilter,
TargetDateAfterFilter,
TargetDateBeforeFilter
@ -117,21 +124,12 @@ export function BuildOrderTable({
const projectCodeFilters = useProjectCodeFilters();
const ownerFilters = useOwnerFilters();
const userFilters = useUserFilters();
const tableFilters: TableFilter[] = useMemo(() => {
const filters: TableFilter[] = [
{
name: 'outstanding',
type: 'boolean',
label: t`Outstanding`,
description: t`Show outstanding orders`
},
{
name: 'status',
label: t`Status`,
description: t`Filter by order status`,
choiceFunction: StatusFilterOptions(ModelType.build)
},
OutstandingFilter(),
OrderStatusFilter({ model: ModelType.build }),
OverdueFilter(),
AssignedToMeFilter(),
MinDateFilter(),
@ -142,25 +140,15 @@ export function BuildOrderTable({
TargetDateAfterFilter(),
CompletedBeforeFilter(),
CompletedAfterFilter(),
{
name: 'project_code',
label: t`Project Code`,
description: t`Filter by project code`,
choices: projectCodeFilters.choices
},
ProjectCodeFilter({ choices: projectCodeFilters.choices }),
HasProjectCodeFilter(),
{
name: 'issued_by',
label: t`Issued By`,
description: t`Filter by user who issued this order`,
choices: ownerFilters.choices
choices: userFilters.choices
},
{
name: 'assigned_to',
label: t`Responsible`,
description: t`Filter by responsible owner`,
choices: ownerFilters.choices
}
ResponsibleFilter({ choices: ownerFilters.choices })
];
// If we are filtering on a specific part, we can include the "include variants" filter
@ -174,7 +162,12 @@ export function BuildOrderTable({
}
return filters;
}, [partId, projectCodeFilters.choices, ownerFilters.choices]);
}, [
partId,
projectCodeFilters.choices,
ownerFilters.choices,
userFilters.choices
]);
const user = useUserState();

View File

@ -8,13 +8,18 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
import {
useOwnerFilters,
useProjectCodeFilters,
useUserFilters
} from '../../hooks/UseFilter';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import {
CompletionDateColumn,
CreatedByColumn,
CreationDateColumn,
DescriptionColumn,
LineItemsProgressColumn,
@ -30,12 +35,15 @@ import {
CompletedBeforeFilter,
CreatedAfterFilter,
CreatedBeforeFilter,
CreatedByFilter,
HasProjectCodeFilter,
MaxDateFilter,
MinDateFilter,
OrderStatusFilter,
OutstandingFilter,
OverdueFilter,
StatusFilterOptions,
ProjectCodeFilter,
ResponsibleFilter,
type TableFilter,
TargetDateAfterFilter,
TargetDateBeforeFilter
@ -57,15 +65,11 @@ export function PurchaseOrderTable({
const projectCodeFilters = useProjectCodeFilters();
const responsibleFilters = useOwnerFilters();
const createdByFilters = useUserFilters();
const tableFilters: TableFilter[] = useMemo(() => {
return [
{
name: 'status',
label: t`Status`,
description: t`Filter by order status`,
choiceFunction: StatusFilterOptions(ModelType.purchaseorder)
},
OrderStatusFilter({ model: ModelType.purchaseorder }),
OutstandingFilter(),
OverdueFilter(),
AssignedToMeFilter(),
@ -77,21 +81,16 @@ export function PurchaseOrderTable({
TargetDateAfterFilter(),
CompletedBeforeFilter(),
CompletedAfterFilter(),
{
name: 'project_code',
label: t`Project Code`,
description: t`Filter by project code`,
choices: projectCodeFilters.choices
},
ProjectCodeFilter({ choices: projectCodeFilters.choices }),
HasProjectCodeFilter(),
{
name: 'assigned_to',
label: t`Responsible`,
description: t`Filter by responsible owner`,
choices: responsibleFilters.choices
}
ResponsibleFilter({ choices: responsibleFilters.choices }),
CreatedByFilter({ choices: createdByFilters.choices })
];
}, [projectCodeFilters.choices, responsibleFilters.choices]);
}, [
projectCodeFilters.choices,
responsibleFilters.choices,
createdByFilters.choices
]);
const tableColumns = useMemo(() => {
return [
@ -120,6 +119,7 @@ export function PurchaseOrderTable({
StatusColumn({ model: ModelType.purchaseorder }),
ProjectCodeColumn({}),
CreationDateColumn({}),
CreatedByColumn({}),
TargetDateColumn({}),
CompletionDateColumn({
accessor: 'complete_date'

View File

@ -8,13 +8,18 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useReturnOrderFields } from '../../forms/ReturnOrderForms';
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
import {
useOwnerFilters,
useProjectCodeFilters,
useUserFilters
} from '../../hooks/UseFilter';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import {
CompletionDateColumn,
CreatedByColumn,
CreationDateColumn,
DescriptionColumn,
LineItemsProgressColumn,
@ -30,12 +35,15 @@ import {
CompletedBeforeFilter,
CreatedAfterFilter,
CreatedBeforeFilter,
CreatedByFilter,
HasProjectCodeFilter,
MaxDateFilter,
MinDateFilter,
OrderStatusFilter,
OutstandingFilter,
OverdueFilter,
StatusFilterOptions,
ProjectCodeFilter,
ResponsibleFilter,
type TableFilter,
TargetDateAfterFilter,
TargetDateBeforeFilter
@ -54,15 +62,11 @@ export function ReturnOrderTable({
const projectCodeFilters = useProjectCodeFilters();
const responsibleFilters = useOwnerFilters();
const createdByFilters = useUserFilters();
const tableFilters: TableFilter[] = useMemo(() => {
const filters: TableFilter[] = [
{
name: 'status',
label: t`Status`,
description: t`Filter by order status`,
choiceFunction: StatusFilterOptions(ModelType.returnorder)
},
OrderStatusFilter({ model: ModelType.returnorder }),
OutstandingFilter(),
OverdueFilter(),
AssignedToMeFilter(),
@ -74,19 +78,10 @@ export function ReturnOrderTable({
TargetDateAfterFilter(),
CompletedBeforeFilter(),
CompletedAfterFilter(),
{
name: 'project_code',
label: t`Project Code`,
description: t`Filter by project code`,
choices: projectCodeFilters.choices
},
HasProjectCodeFilter(),
{
name: 'assigned_to',
label: t`Responsible`,
description: t`Filter by responsible owner`,
choices: responsibleFilters.choices
}
ProjectCodeFilter({ choices: projectCodeFilters.choices }),
ResponsibleFilter({ choices: responsibleFilters.choices }),
CreatedByFilter({ choices: createdByFilters.choices })
];
if (!!partId) {
@ -99,7 +94,12 @@ export function ReturnOrderTable({
}
return filters;
}, [partId, projectCodeFilters.choices, responsibleFilters.choices]);
}, [
partId,
projectCodeFilters.choices,
responsibleFilters.choices,
createdByFilters.choices
]);
const tableColumns = useMemo(() => {
return [
@ -128,6 +128,7 @@ export function ReturnOrderTable({
StatusColumn({ model: ModelType.returnorder }),
ProjectCodeColumn({}),
CreationDateColumn({}),
CreatedByColumn({}),
TargetDateColumn({}),
CompletionDateColumn({
accessor: 'complete_date'

View File

@ -9,12 +9,17 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
import {
useOwnerFilters,
useProjectCodeFilters,
useUserFilters
} from '../../hooks/UseFilter';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import {
CreatedByColumn,
CreationDateColumn,
DescriptionColumn,
LineItemsProgressColumn,
@ -31,12 +36,15 @@ import {
CompletedBeforeFilter,
CreatedAfterFilter,
CreatedBeforeFilter,
CreatedByFilter,
HasProjectCodeFilter,
MaxDateFilter,
MinDateFilter,
OrderStatusFilter,
OutstandingFilter,
OverdueFilter,
StatusFilterOptions,
ProjectCodeFilter,
ResponsibleFilter,
type TableFilter,
TargetDateAfterFilter,
TargetDateBeforeFilter
@ -55,15 +63,11 @@ export function SalesOrderTable({
const projectCodeFilters = useProjectCodeFilters();
const responsibleFilters = useOwnerFilters();
const createdByFilters = useUserFilters();
const tableFilters: TableFilter[] = useMemo(() => {
const filters: TableFilter[] = [
{
name: 'status',
label: t`Status`,
description: t`Filter by order status`,
choiceFunction: StatusFilterOptions(ModelType.salesorder)
},
OrderStatusFilter({ model: ModelType.salesorder }),
OutstandingFilter(),
OverdueFilter(),
AssignedToMeFilter(),
@ -75,19 +79,10 @@ export function SalesOrderTable({
TargetDateAfterFilter(),
CompletedBeforeFilter(),
CompletedAfterFilter(),
{
name: 'project_code',
label: t`Project Code`,
description: t`Filter by project code`,
choices: projectCodeFilters.choices
},
HasProjectCodeFilter(),
{
name: 'assigned_to',
label: t`Responsible`,
description: t`Filter by responsible owner`,
choices: responsibleFilters.choices
}
ProjectCodeFilter({ choices: projectCodeFilters.choices }),
ResponsibleFilter({ choices: responsibleFilters.choices }),
CreatedByFilter({ choices: createdByFilters.choices })
];
if (!!partId) {
@ -100,7 +95,12 @@ export function SalesOrderTable({
}
return filters;
}, [partId, projectCodeFilters.choices, responsibleFilters.choices]);
}, [
partId,
projectCodeFilters.choices,
responsibleFilters.choices,
createdByFilters.choices
]);
const salesOrderFields = useSalesOrderFields({});
@ -165,6 +165,7 @@ export function SalesOrderTable({
StatusColumn({ model: ModelType.salesorder }),
ProjectCodeColumn({}),
CreationDateColumn({}),
CreatedByColumn({}),
TargetDateColumn({}),
ShipmentDateColumn({}),
ResponsibleColumn({}),