diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py
index 76907a626a..df82fc361e 100644
--- a/src/backend/InvenTree/InvenTree/api_version.py
+++ b/src/backend/InvenTree/InvenTree/api_version.py
@@ -1,13 +1,20 @@
"""InvenTree API version information."""
# InvenTree API version
-INVENTREE_API_VERSION = 283
+INVENTREE_API_VERSION = 284
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
+v284 - 2024-11-25 : https://github.com/inventree/InvenTree/pull/8544
+ - Adds new date filters to the StockItem API
+ - Adds new date filters to the BuildOrder API
+ - Adds new date filters to the SalesOrder API
+ - Adds new date filters to the PurchaseOrder API
+ - Adds new date filters to the ReturnOrder API
+
v283 - 2024-11-20 : https://github.com/inventree/InvenTree/pull/8524
- Adds "note" field to the PartRelated API endpoint
diff --git a/src/backend/InvenTree/build/api.py b/src/backend/InvenTree/build/api.py
index 096a42683e..0eeb17ed61 100644
--- a/src/backend/InvenTree/build/api.py
+++ b/src/backend/InvenTree/build/api.py
@@ -23,7 +23,7 @@ import build.serializers
from build.models import Build, BuildLine, BuildItem
import part.models
from users.models import Owner
-from InvenTree.filters import SEARCH_ORDER_FILTER_ALIAS
+from InvenTree.filters import InvenTreeDateFilter, SEARCH_ORDER_FILTER_ALIAS
class BuildFilter(rest_filters.FilterSet):
@@ -179,6 +179,36 @@ class BuildFilter(rest_filters.FilterSet):
return queryset.exclude(project_code=None)
return queryset.filter(project_code=None)
+ created_before = InvenTreeDateFilter(
+ label=_('Created before'),
+ field_name='creation_date', lookup_expr='lt'\
+ )
+
+ created_after = InvenTreeDateFilter(
+ label=_('Created after'),
+ field_name='creation_date', lookup_expr='gt'
+ )
+
+ target_date_before = InvenTreeDateFilter(
+ label=_('Target date before'),
+ field_name='target_date', lookup_expr='lt'
+ )
+
+ target_date_after = InvenTreeDateFilter(
+ label=_('Target date after'),
+ field_name='target_date', lookup_expr='gt'
+ )
+
+ completed_before = InvenTreeDateFilter(
+ label=_('Completed before'),
+ field_name='completion_date', lookup_expr='lt'
+ )
+
+ completed_after = InvenTreeDateFilter(
+ label=_('Completed after'),
+ field_name='completion_date', lookup_expr='gt'
+ )
+
class BuildMixin:
"""Mixin class for Build API endpoints."""
diff --git a/src/backend/InvenTree/order/api.py b/src/backend/InvenTree/order/api.py
index b5671c5952..dbbe60c0b8 100644
--- a/src/backend/InvenTree/order/api.py
+++ b/src/backend/InvenTree/order/api.py
@@ -21,7 +21,11 @@ import company.models
from generic.states.api import StatusView
from importer.mixins import DataExportViewMixin
from InvenTree.api import ListCreateDestroyAPIView, MetadataView
-from InvenTree.filters import SEARCH_ORDER_FILTER, SEARCH_ORDER_FILTER_ALIAS
+from InvenTree.filters import (
+ SEARCH_ORDER_FILTER,
+ SEARCH_ORDER_FILTER_ALIAS,
+ InvenTreeDateFilter,
+)
from InvenTree.helpers import str2bool
from InvenTree.helpers_model import construct_absolute_url, get_base_url
from InvenTree.mixins import CreateAPI, ListAPI, ListCreateAPI, RetrieveUpdateDestroyAPI
@@ -140,6 +144,22 @@ class OrderFilter(rest_filters.FilterSet):
queryset=Owner.objects.all(), field_name='responsible', label=_('Responsible')
)
+ created_before = InvenTreeDateFilter(
+ label=_('Created Before'), field_name='creation_date', lookup_expr='lt'
+ )
+
+ created_after = InvenTreeDateFilter(
+ label=_('Created After'), field_name='creation_date', lookup_expr='gt'
+ )
+
+ target_date_before = InvenTreeDateFilter(
+ label=_('Target Date Before'), field_name='target_date', lookup_expr='lt'
+ )
+
+ target_date_after = InvenTreeDateFilter(
+ label=_('Target Date After'), field_name='target_date', lookup_expr='gt'
+ )
+
class LineItemFilter(rest_filters.FilterSet):
"""Base class for custom API filters for order line item list(s)."""
@@ -171,6 +191,41 @@ class PurchaseOrderFilter(OrderFilter):
model = models.PurchaseOrder
fields = ['supplier']
+ part = rest_filters.ModelChoiceFilter(
+ queryset=Part.objects.all(),
+ field_name='part',
+ label=_('Part'),
+ method='filter_part',
+ )
+
+ def filter_part(self, queryset, name, part: Part):
+ """Filter by provided Part instance."""
+ orders = part.purchase_orders()
+
+ return queryset.filter(pk__in=[o.pk for o in orders])
+
+ supplier_part = rest_filters.ModelChoiceFilter(
+ queryset=company.models.SupplierPart.objects.all(),
+ label=_('Supplier Part'),
+ method='filter_supplier_part',
+ )
+
+ def filter_supplier_part(
+ self, queryset, name, supplier_part: company.models.SupplierPart
+ ):
+ """Filter by provided SupplierPart instance."""
+ orders = supplier_part.purchase_orders()
+
+ return queryset.filter(pk__in=[o.pk for o in orders])
+
+ completed_before = InvenTreeDateFilter(
+ label=_('Completed Before'), field_name='complete_date', lookup_expr='lt'
+ )
+
+ completed_after = InvenTreeDateFilter(
+ label=_('Completed After'), field_name='complete_date', lookup_expr='gt'
+ )
+
class PurchaseOrderMixin:
"""Mixin class for PurchaseOrder endpoints."""
@@ -221,32 +276,6 @@ class PurchaseOrderList(PurchaseOrderMixin, DataExportViewMixin, ListCreateAPI):
params = self.request.query_params
- # Attempt to filter by part
- part = params.get('part', None)
-
- if part is not None:
- try:
- part = Part.objects.get(pk=part)
- queryset = queryset.filter(
- id__in=[p.id for p in part.purchase_orders()]
- )
- except (Part.DoesNotExist, ValueError):
- pass
-
- # Attempt to filter by supplier part
- supplier_part = params.get('supplier_part', None)
-
- if supplier_part is not None:
- try:
- supplier_part = company.models.SupplierPart.objects.get(
- pk=supplier_part
- )
- queryset = queryset.filter(
- id__in=[p.id for p in supplier_part.purchase_orders()]
- )
- except (ValueError, company.models.SupplierPart.DoesNotExist):
- pass
-
# Filter by 'date range'
min_date = params.get('min_date', None)
max_date = params.get('max_date', None)
@@ -276,6 +305,7 @@ class PurchaseOrderList(PurchaseOrderMixin, DataExportViewMixin, ListCreateAPI):
'reference',
'supplier__name',
'target_date',
+ 'complete_date',
'line_items',
'status',
'responsible',
@@ -648,6 +678,14 @@ class SalesOrderFilter(OrderFilter):
# Now we have a list of matching IDs, filter the queryset
return queryset.filter(pk__in=sales_orders)
+ completed_before = InvenTreeDateFilter(
+ label=_('Completed Before'), field_name='shipment_date', lookup_expr='lt'
+ )
+
+ completed_after = InvenTreeDateFilter(
+ label=_('Completed After'), field_name='shipment_date', lookup_expr='gt'
+ )
+
class SalesOrderMixin:
"""Mixin class for SalesOrder endpoints."""
@@ -1257,6 +1295,14 @@ class ReturnOrderFilter(OrderFilter):
# Now we have a list of matching IDs, filter the queryset
return queryset.filter(pk__in=return_orders)
+ completed_before = InvenTreeDateFilter(
+ label=_('Completed Before'), field_name='complete_date', lookup_expr='lt'
+ )
+
+ completed_after = InvenTreeDateFilter(
+ label=_('Completed After'), field_name='complete_date', lookup_expr='gt'
+ )
+
class ReturnOrderMixin:
"""Mixin class for ReturnOrder endpoints."""
@@ -1325,6 +1371,7 @@ class ReturnOrderList(ReturnOrderMixin, DataExportViewMixin, ListCreateAPI):
'line_items',
'status',
'target_date',
+ 'complete_date',
'project_code',
]
diff --git a/src/backend/InvenTree/part/api.py b/src/backend/InvenTree/part/api.py
index 36663a7847..4f62be6d1d 100644
--- a/src/backend/InvenTree/part/api.py
+++ b/src/backend/InvenTree/part/api.py
@@ -1159,10 +1159,10 @@ class PartFilter(rest_filters.FilterSet):
# Created date filters
created_before = InvenTreeDateFilter(
- label='Updated before', field_name='creation_date', lookup_expr='lte'
+ label='Updated before', field_name='creation_date', lookup_expr='lt'
)
created_after = InvenTreeDateFilter(
- label='Updated after', field_name='creation_date', lookup_expr='gte'
+ label='Updated after', field_name='creation_date', lookup_expr='gt'
)
diff --git a/src/backend/InvenTree/stock/api.py b/src/backend/InvenTree/stock/api.py
index 7734e101ab..42bc628d96 100644
--- a/src/backend/InvenTree/stock/api.py
+++ b/src/backend/InvenTree/stock/api.py
@@ -807,19 +807,28 @@ class StockFilter(rest_filters.FilterSet):
# Update date filters
updated_before = InvenTreeDateFilter(
- label='Updated before', field_name='updated', lookup_expr='lte'
+ label=_('Updated before'), field_name='updated', lookup_expr='lt'
)
+
updated_after = InvenTreeDateFilter(
- label='Updated after', field_name='updated', lookup_expr='gte'
+ label=_('Updated after'), field_name='updated', lookup_expr='gt'
+ )
+
+ stocktake_before = InvenTreeDateFilter(
+ label=_('Stocktake Before'), field_name='stocktake_date', lookup_expr='lt'
+ )
+
+ stocktake_after = InvenTreeDateFilter(
+ label=_('Stocktake After'), field_name='stocktake_date', lookup_expr='gt'
)
# Stock "expiry" filters
- expiry_date_lte = InvenTreeDateFilter(
- label=_('Expiry date before'), field_name='expiry_date', lookup_expr='lte'
+ expiry_before = InvenTreeDateFilter(
+ label=_('Expiry date before'), field_name='expiry_date', lookup_expr='lt'
)
- expiry_date_gte = InvenTreeDateFilter(
- label=_('Expiry date after'), field_name='expiry_date', lookup_expr='gte'
+ expiry_after = InvenTreeDateFilter(
+ label=_('Expiry date after'), field_name='expiry_date', lookup_expr='gt'
)
stale = rest_filters.BooleanFilter(label=_('Stale'), method='filter_stale')
diff --git a/src/backend/InvenTree/templates/js/translated/table_filters.js b/src/backend/InvenTree/templates/js/translated/table_filters.js
index 4706b493b5..a74ceaee30 100644
--- a/src/backend/InvenTree/templates/js/translated/table_filters.js
+++ b/src/backend/InvenTree/templates/js/translated/table_filters.js
@@ -421,11 +421,11 @@ function getStockTableFilters() {
title: '{% trans "Has purchase price" %}',
description: '{% trans "Show stock items which have a purchase price set" %}',
},
- expiry_date_lte: {
+ expiry_before: {
type: 'date',
title: '{% trans "Expiry Date before" %}',
},
- expiry_date_gte: {
+ expiry_after: {
type: 'date',
title: '{% trans "Expiry Date after" %}',
},
diff --git a/src/frontend/src/pages/stock/LocationDetail.tsx b/src/frontend/src/pages/stock/LocationDetail.tsx
index 9aaf256c2a..739ad8ba1f 100644
--- a/src/frontend/src/pages/stock/LocationDetail.tsx
+++ b/src/frontend/src/pages/stock/LocationDetail.tsx
@@ -3,8 +3,6 @@ import { Group, Skeleton, Stack, Text } from '@mantine/core';
import { IconInfoCircle, IconPackages, IconSitemap } from '@tabler/icons-react';
import { useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
-
-import { ActionButton } from '../../components/buttons/ActionButton';
import AdminButton from '../../components/buttons/AdminButton';
import { PrintingActions } from '../../components/buttons/PrintingActions';
import {
@@ -278,12 +276,6 @@ export default function Stock() {
() => [
,
,
- }
- onClick={notYetImplemented}
- variant='outline'
- size='lg'
- />,
location.pk ? (
void;
+}) {
+ const setDateValue = useCallback(
+ (value: DateValue) => {
+ if (value) {
+ const date = value.toString();
+ onValueChange(dayjs(date).format('YYYY-MM-DD'));
+ } else {
+ onValueChange('');
+ }
+ },
+ [onValueChange]
+ );
+
+ const [textValue, setTextValue] = useState('');
+
+ switch (filterType) {
+ case 'text':
+ return (
+ onValueChange(textValue)}
+ >
+
+
+ }
+ onChange={(e) => setTextValue(e.currentTarget.value)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ onValueChange(textValue);
+ }
+ }}
+ />
+ );
+ case 'date':
+ return (
+
+ );
+ case 'choice':
+ case 'boolean':
+ default:
+ return (
+