Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
SergeoLacruz committed Dec 3, 2024
2 parents 7be700f + 1a8b030 commit 8312fe0
Show file tree
Hide file tree
Showing 19 changed files with 175 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ jobs:
- name: Push Docker Images
id: push-docker
if: github.event_name != 'pull_request'
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # pin@v6.9.0
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # pin@v6.10.0
with:
context: .
file: ./contrib/container/Dockerfile
Expand Down
2 changes: 1 addition & 1 deletion contrib/dev_reqs/requirements.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Packages needed for CI/packages
requests==2.32.3
pyyaml==6.0.2
jc==1.25.3
jc==1.25.4
6 changes: 3 additions & 3 deletions contrib/dev_reqs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ idna==3.10 \
--hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
--hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
# via requests
jc==1.25.3 \
--hash=sha256:ea17a8578497f2da92f73924d9d403f4563ba59422fbceff7bb4a16cdf84a54f \
--hash=sha256:fa3140ceda6cba1210d1362f363cd79a0514741e8a1dd6167db2b2e2d5f24f7b
jc==1.25.4 \
--hash=sha256:1e4f45d2e5b72cf9d300b0d9df0578c0d3b553843e3ad37a525d93bb0e94aca1 \
--hash=sha256:a32eaf029c56b582dadae48895f20784d0f84f2fa28a8e2b32f377a8bffa8b39
# via -r contrib/dev_reqs/requirements.in
pygments==2.18.0 \
--hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \
Expand Down
12 changes: 6 additions & 6 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -311,17 +311,17 @@ mkdocs-git-revision-date-localized-plugin==1.3.0 \
--hash=sha256:439e2f14582204050a664c258861c325064d97cdc848c541e48bb034a6c4d0cb \
--hash=sha256:c99377ee119372d57a9e47cff4e68f04cce634a74831c06bc89b33e456e840a1
# via -r docs/requirements.in
mkdocs-include-markdown-plugin==7.1.1 \
--hash=sha256:046a452dea2796e93f1385a1db106209a18bb9417162063ffe0a432a97c9b837 \
--hash=sha256:3ca17da4d5d77cfa5f4da564e65dc74ee2aa6a7368119db23d650fb24d95fce9
mkdocs-include-markdown-plugin==7.1.2 \
--hash=sha256:1b393157b1aa231b0e6c59ba80f52b723f4b7827bb7a1264b505334f8542aaf1 \
--hash=sha256:ff1175d1b4f83dea6a38e200d6f0c3db10308975bf60c197d31172671753dbc4
# via -r docs/requirements.in
mkdocs-macros-plugin==1.3.7 \
--hash=sha256:02432033a5b77fb247d6ec7924e72fc4ceec264165b1644ab8d0dc159c22ce59 \
--hash=sha256:17c7fd1a49b94defcdb502fd453d17a1e730f8836523379d21292eb2be4cb523
# via -r docs/requirements.in
mkdocs-material==9.5.46 \
--hash=sha256:98f0a2039c62e551a68aad0791a8d41324ff90c03a6e6cea381a384b84908b83 \
--hash=sha256:ae2043f4238e572f9a40e0b577f50400d6fc31e2fef8ea141800aebf3bd273d7
mkdocs-material==9.5.47 \
--hash=sha256:53fb9c9624e7865da6ec807d116cd7be24b3cb36ab31b1d1d1a9af58c56009a2 \
--hash=sha256:fc3b7a8e00ad896660bd3a5cc12ca0cb28bdc2bcbe2a946b5714c23ac91b0ede
# via -r docs/requirements.in
mkdocs-material-extensions==1.3.1 \
--hash=sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443 \
Expand Down
6 changes: 5 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
"""InvenTree API version information."""

# InvenTree API version
INVENTREE_API_VERSION = 291
INVENTREE_API_VERSION = 292

"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""


INVENTREE_API_TEXT = """
v292 - 2024-12-03 : https://github.com/inventree/InvenTree/pull/8625
- Add "on_order" and "in_stock" annotations to SupplierPart API
- Enhanced filtering for the SupplierPart API
v291 - 2024-11-30 : https://github.com/inventree/InvenTree/pull/8596
- Allow null / empty values for plugin settings
Expand Down
79 changes: 45 additions & 34 deletions src/backend/InvenTree/company/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ class Meta:
field_name='part__active', label=_('Internal Part is Active')
)

# Filter by 'active' status of linked supplier
supplier_active = rest_filters.BooleanFilter(
field_name='supplier__active', label=_('Supplier is Active')
)
Expand All @@ -293,43 +294,48 @@ class Meta:
lookup_expr='iexact',
)

# Filter by 'manufacturer'
manufacturer = rest_filters.ModelChoiceFilter(
label=_('Manufacturer'),
queryset=Company.objects.all(),
field_name='manufacturer_part__manufacturer',
)

class SupplierPartList(DataExportViewMixin, ListCreateDestroyAPIView):
"""API endpoint for list view of SupplierPart object.
# Filter by 'company' (either manufacturer or supplier)
company = rest_filters.ModelChoiceFilter(
label=_('Company'), queryset=Company.objects.all(), method='filter_company'
)

- GET: Return list of SupplierPart objects
- POST: Create a new SupplierPart object
"""
def filter_company(self, queryset, name, value):
"""Filter the queryset by either manufacturer or supplier."""
return queryset.filter(
Q(manufacturer_part__manufacturer=value) | Q(supplier=value)
).distinct()

has_stock = rest_filters.BooleanFilter(
label=_('Has Stock'), method='filter_has_stock'
)

def filter_has_stock(self, queryset, name, value):
"""Filter the queryset based on whether the SupplierPart has stock available."""
if value:
return queryset.filter(in_stock__gt=0)
else:
return queryset.exclude(in_stock__gt=0)


class SupplierPartMixin:
"""Mixin class for SupplierPart API endpoints."""

queryset = SupplierPart.objects.all().prefetch_related('tags')
filterset_class = SupplierPartFilter
serializer_class = SupplierPartSerializer

def get_queryset(self, *args, **kwargs):
"""Return annotated queryest object for the SupplierPart list."""
queryset = super().get_queryset(*args, **kwargs)
queryset = SupplierPartSerializer.annotate_queryset(queryset)

return queryset

def filter_queryset(self, queryset):
"""Custom filtering for the queryset."""
queryset = super().filter_queryset(queryset)

params = self.request.query_params

# Filter by manufacturer
manufacturer = params.get('manufacturer', None)

if manufacturer is not None:
queryset = queryset.filter(manufacturer_part__manufacturer=manufacturer)

# Filter by EITHER manufacturer or supplier
company = params.get('company', None)

if company is not None:
queryset = queryset.filter(
Q(manufacturer_part__manufacturer=company) | Q(supplier=company)
).distinct()
queryset = queryset.prefetch_related('part', 'part__pricing_data')

return queryset

Expand All @@ -351,7 +357,17 @@ def get_serializer(self, *args, **kwargs):

return self.serializer_class(*args, **kwargs)

serializer_class = SupplierPartSerializer

class SupplierPartList(
DataExportViewMixin, SupplierPartMixin, ListCreateDestroyAPIView
):
"""API endpoint for list view of SupplierPart object.
- GET: Return list of SupplierPart objects
- POST: Create a new SupplierPart object
"""

filterset_class = SupplierPartFilter

filter_backends = SEARCH_ORDER_FILTER_ALIAS

Expand Down Expand Up @@ -391,19 +407,14 @@ def get_serializer(self, *args, **kwargs):
]


class SupplierPartDetail(RetrieveUpdateDestroyAPI):
class SupplierPartDetail(SupplierPartMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for detail view of SupplierPart object.
- GET: Retrieve detail view
- PATCH: Update object
- DELETE: Delete object
"""

queryset = SupplierPart.objects.all()
serializer_class = SupplierPartSerializer

read_only_fields = []


class SupplierPriceBreakFilter(rest_filters.FilterSet):
"""Custom API filters for the SupplierPriceBreak list endpoint."""
Expand Down
36 changes: 36 additions & 0 deletions src/backend/InvenTree/company/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Custom query filters for the Company app."""

from decimal import Decimal

from django.db.models import DecimalField, ExpressionWrapper, F, Q
from django.db.models.functions import Coalesce

from sql_util.utils import SubquerySum

from order.status_codes import PurchaseOrderStatusGroups


def annotate_on_order_quantity():
"""Annotate the 'on_order' quantity for each SupplierPart in a queryset.
- This is the total quantity of parts on order from all open purchase orders
- Takes into account the 'received' quantity for each order line
"""
# Filter only 'active' purhase orders
# Filter only line with outstanding quantity
order_filter = Q(
order__status__in=PurchaseOrderStatusGroups.OPEN, quantity__gt=F('received')
)

return Coalesce(
SubquerySum(
ExpressionWrapper(
F('purchase_order_line_items__quantity')
- F('purchase_order_line_items__received'),
output_field=DecimalField(),
),
filter=order_filter,
),
Decimal(0),
output_field=DecimalField(),
)
8 changes: 8 additions & 0 deletions src/backend/InvenTree/company/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from sql_util.utils import SubqueryCount
from taggit.serializers import TagListSerializerField

import company.filters
import part.filters
import part.serializers as part_serializers
from importer.mixins import DataImportExportSerializerMixin
Expand Down Expand Up @@ -323,6 +324,7 @@ class Meta:
'availability_updated',
'description',
'in_stock',
'on_order',
'link',
'active',
'manufacturer',
Expand Down Expand Up @@ -396,6 +398,8 @@ def __init__(self, *args, **kwargs):
# Annotated field showing total in-stock quantity
in_stock = serializers.FloatField(read_only=True, label=_('In Stock'))

on_order = serializers.FloatField(read_only=True, label=_('On Order'))

available = serializers.FloatField(required=False, label=_('Available'))

pack_quantity_native = serializers.FloatField(read_only=True)
Expand Down Expand Up @@ -442,6 +446,10 @@ def annotate_queryset(queryset):
"""
queryset = queryset.annotate(in_stock=part.filters.annotate_total_stock())

queryset = queryset.annotate(
on_order=company.filters.annotate_on_order_quantity()
)

return queryset

def update(self, supplier_part, data):
Expand Down
2 changes: 1 addition & 1 deletion src/backend/InvenTree/part/filters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Custom query filters for the Part models.
"""Custom query filters for the Part app.
The code here makes heavy use of subquery annotations!
Expand Down
6 changes: 3 additions & 3 deletions src/backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1251,9 +1251,9 @@ pydyf==0.10.0 \
# via
# -r src/backend/requirements.in
# weasyprint
pyjwt==2.10.0 \
--hash=sha256:543b77207db656de204372350926bed5a86201c4cbff159f623f79c7bb487a15 \
--hash=sha256:7628a7eb7938959ac1b26e819a1df0fd3259505627b575e4bad6d08f76db695c
pyjwt==2.10.1 \
--hash=sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953 \
--hash=sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb
# via djangorestframework-simplejwt
pyphen==0.17.0 \
--hash=sha256:1d13acd1ce37a384d7612954ae6c7801bb4c5316da0e2b937b2127ba702a3da4 \
Expand Down
37 changes: 37 additions & 0 deletions src/frontend/src/pages/company/SupplierPartDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,25 @@ export default function SupplierPartDetail() {
];

const br: DetailsField[] = [
{
type: 'string',
name: 'in_stock',
label: t`In Stock`,
copy: true,
icon: 'stock'
},
{
type: 'string',
name: 'on_order',
label: t`On Order`,
copy: true,
icon: 'purchase_orders'
},
{
type: 'string',
name: 'available',
label: t`Supplier Availability`,
hidden: !data.availability_updated,
copy: true,
icon: 'packages'
},
Expand Down Expand Up @@ -352,6 +367,28 @@ export default function SupplierPartDetail() {
label={t`Inactive`}
color='red'
visible={supplierPart.active == false}
/>,
<DetailsBadge
label={`${t`In Stock`}: ${supplierPart.in_stock}`}
color={'green'}
visible={
supplierPart?.active &&
supplierPart?.in_stock &&
supplierPart?.in_stock > 0
}
key='in_stock'
/>,
<DetailsBadge
label={t`No Stock`}
color={'red'}
visible={supplierPart.active && supplierPart.in_stock == 0}
key='no_stock'
/>,
<DetailsBadge
label={`${t`On Order`}: ${supplierPart.on_order}`}
color='blue'
visible={supplierPart.on_order > 0}
key='on_order'
/>
];
}, [supplierPart]);
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/tables/Column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type TableColumnProps<T = any> = {
textAlign?: 'left' | 'center' | 'right'; // The text alignment of the column
cellsStyle?: any; // The style of the cells in the column
extra?: any; // Extra data to pass to the render function
noContext?: boolean; // Disable context menu for this column
};

/**
Expand Down
17 changes: 11 additions & 6 deletions src/frontend/src/tables/InvenTreeTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export type InvenTreeTableProps<T = any> = {
modelType?: ModelType;
rowStyle?: (record: T, index: number) => any;
modelField?: string;
onRowContextMenu?: (record: T, event: any) => void;
onCellContextMenu?: (record: T, event: any) => void;
minHeight?: number;
noHeader?: boolean;
};
Expand Down Expand Up @@ -568,16 +568,21 @@ export function InvenTreeTable<T extends Record<string, any>>({
[props.onRowClick, props.onCellClick]
);

// Callback when a row is right-clicked
const handleRowContextMenu = ({
// Callback when a cell is right-clicked
const handleCellContextMenu = ({
record,
column,
event
}: {
record: any;
column: any;
event: any;
}) => {
if (props.onRowContextMenu) {
return props.onRowContextMenu(record, event);
if (column?.noContext === true) {
return;
}
if (props.onCellContextMenu) {
return props.onCellContextMenu(record, event);
} else if (props.rowActions) {
const empty = () => {};
const items = props.rowActions(record).map((action) => ({
Expand Down Expand Up @@ -693,7 +698,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
overflow: 'hidden'
})
}}
onRowContextMenu={handleRowContextMenu}
onCellContextMenu={handleCellContextMenu}
{...optionalParams}
/>
</Box>
Expand Down
Loading

0 comments on commit 8312fe0

Please sign in to comment.