Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds "ON HOLD" status to order models #7807

Merged
merged 46 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
da59fe6
Add "ON_HOLD" status code for orders
SchrodingersGat Aug 3, 2024
88553cf
Add placeholder buttons for purchase order status change
SchrodingersGat Aug 4, 2024
9b951ac
Adds hooks for introspecting status code enumerations
SchrodingersGat Aug 4, 2024
fafed9e
Refactor status codes for import session
SchrodingersGat Aug 4, 2024
68e8772
Refactor into <PrimaryActionButton />
SchrodingersGat Aug 4, 2024
6018a4b
Cleanup
SchrodingersGat Aug 4, 2024
6d30b1e
more permission checks
SchrodingersGat Aug 4, 2024
1192344
Add placeholder actions for SalesOrder
SchrodingersGat Aug 4, 2024
8e074a6
Placeholder actions for ReturnOrder
SchrodingersGat Aug 4, 2024
414e468
Placeholder actions for build order
SchrodingersGat Aug 4, 2024
99939e1
Actions for "return order"
SchrodingersGat Aug 4, 2024
7b12865
Update actions for return order
SchrodingersGat Aug 4, 2024
6841952
Implement transitions for SalesOrder
SchrodingersGat Aug 4, 2024
7b35c9f
Allow control over SalesOrderLineItemTable
SchrodingersGat Aug 4, 2024
286cd09
Implement PurchaseOrder actions
SchrodingersGat Aug 4, 2024
f10cc8e
Improve API query lookup efficiency
SchrodingersGat Aug 4, 2024
a3fa5a4
UI cleanup
SchrodingersGat Aug 4, 2024
6e57a14
CUI cleanup
SchrodingersGat Aug 4, 2024
1f8d0ae
Build Order Updates
SchrodingersGat Aug 4, 2024
f0c8c18
Increase timeout
SchrodingersGat Aug 4, 2024
20006bb
Merge remote-tracking branch 'origin/master' into order-hold
SchrodingersGat Aug 4, 2024
be31f8d
Bump API version
SchrodingersGat Aug 4, 2024
462dbae
Fix API version
SchrodingersGat Aug 4, 2024
d852367
Fix sales order actions
SchrodingersGat Aug 4, 2024
4d1159e
Merge branch 'master' into order-hold
SchrodingersGat Aug 5, 2024
e977b5b
Update src/backend/InvenTree/order/serializers.py
SchrodingersGat Aug 5, 2024
d3294d3
Adjust build filters
SchrodingersGat Aug 5, 2024
5d90c0f
PUI updates
SchrodingersGat Aug 5, 2024
82ac935
CUI refactoring for purchase orders
SchrodingersGat Aug 5, 2024
ae005c4
Refactor CUI sales order page
SchrodingersGat Aug 5, 2024
5f77fdd
Refactor for return order
SchrodingersGat Aug 5, 2024
d367fbb
Refactor CUI build page
SchrodingersGat Aug 5, 2024
5c08f57
Playwright tests for build order
SchrodingersGat Aug 5, 2024
2619c32
Add playwright test for sales orders
SchrodingersGat Aug 5, 2024
d21ee67
Add playwright test for purchase orders
SchrodingersGat Aug 5, 2024
e66ad6b
js linting
SchrodingersGat Aug 5, 2024
4e222e0
Refactor return order page
SchrodingersGat Aug 5, 2024
9601906
Add missing functions from previous commit
SchrodingersGat Aug 5, 2024
689629a
Fix for "on order" badge on PartDetail page
SchrodingersGat Aug 6, 2024
26965b1
UI tweaks
SchrodingersGat Aug 6, 2024
5ae9ee1
Fix unit tests
SchrodingersGat Aug 6, 2024
85b534e
Update version check script
SchrodingersGat Aug 6, 2024
e62e354
Fix typo
SchrodingersGat Aug 7, 2024
595f353
Enforce integer conversion for BaseEnum class
SchrodingersGat Aug 7, 2024
79e58e2
Unit test updates
SchrodingersGat Aug 7, 2024
67c9511
Update documentation
SchrodingersGat Aug 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/scripts/version_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
GITHUB_API_URL = os.getenv('GITHUB_API_URL', 'https://api.github.com')


def get_existing_release_tags():
def get_existing_release_tags(include_prerelease=True):
"""Request information on existing releases via the GitHub API."""
# Check for github token
token = os.getenv('GITHUB_TOKEN', None)
Expand Down Expand Up @@ -51,6 +51,9 @@ def get_existing_release_tags():
print(f"Version '{tag}' did not match expected pattern")
continue

if not include_prerelease and release['prerelease']:
continue

tags.append([int(x) for x in match.groups()])

return tags
Expand All @@ -74,7 +77,7 @@ def check_version_number(version_string, allow_duplicate=False):
version_tuple = [int(x) for x in match.groups()]

# Look through the existing releases
existing = get_existing_release_tags()
existing = get_existing_release_tags(include_prerelease=False)

# Assume that this is the highest release, unless told otherwise
highest_release = True
Expand Down
9 changes: 5 additions & 4 deletions docs/docs/build/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ Each *Build Order* has an associated *Status* flag, which indicates the state of

| Status | Description |
| ----------- | ----------- |
| `Pending` | Build has been created and build is ready for subpart allocation |
| `Production` | One or more build outputs have been created for this build |
| `Cancelled` | Build has been cancelled |
| `Completed` | Build has been completed |
| `Pending` | Build order has been created, but is not yet in production |
| `Production` | Build order is currently in production |
| `On Hold` | Build order has been placed on hold, but is still active |
| `Cancelled` | Build order has been cancelled |
| `Completed` | Build order has been completed |

**Source Code**

Expand Down
1 change: 1 addition & 0 deletions docs/docs/order/purchase_order.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Each Purchase Order has a specific status code which indicates the current state
| --- | --- |
| Pending | The purchase order has been created, but has not been submitted to the supplier |
| In Progress | The purchase order has been issued to the supplier, and is in progress |
| On Hold | The purchase order has been placed on hold, but is still active |
| Complete | The purchase order has been completed, and is now closed |
| Cancelled | The purchase order was cancelled, and is now closed |
| Lost | The purchase order was lost, and is now closed |
Expand Down
1 change: 1 addition & 0 deletions docs/docs/order/return_order.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Each Return Order has a specific status code, as follows:
| --- | --- |
| Pending | The return order has been created, but not sent to the customer |
| In Progress | The return order has been issued to the customer |
| On Hold | The return order has been placed on hold, but is still active |
| Complete | The return order was marked as complete, and is now closed |
| Cancelled | The return order was cancelled, and is now closed |

Expand Down
1 change: 1 addition & 0 deletions docs/docs/order/sales_order.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Each Sales Order has a specific status code, which represents the state of the o
| --- | --- |
| Pending | The sales order has been created, but has not been finalized or submitted |
| In Progress | The sales order has been issued, and is in progress |
| On Hold | The sales order has been placed on hold, but is still active |
| Shipped | The sales order has been shipped, but is not yet complete |
| Complete | The sales order is fully completed, and is now closed |
| Cancelled | The sales order was cancelled, and is now closed |
Expand Down
8 changes: 7 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
"""InvenTree API version information."""

# InvenTree API version
INVENTREE_API_VERSION = 232
INVENTREE_API_VERSION = 233

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


INVENTREE_API_TEXT = """

v233 - 2024-08-04 : https://github.com/inventree/InvenTree/pull/7807
- Adds new endpoints for managing state of build orders
- Adds new endpoints for managing state of purchase orders
- Adds new endpoints for managing state of sales orders
- Adds new endpoints for managing state of return orders

v232 - 2024-08-03 : https://github.com/inventree/InvenTree/pull/7793
- Allow ordering of SalesOrderShipment API by 'shipment_date' and 'delivery_date'

Expand Down
29 changes: 26 additions & 3 deletions src/backend/InvenTree/build/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,9 +470,19 @@ class BuildFinish(BuildOrderContextMixin, CreateAPI):
"""API endpoint for marking a build as finished (completed)."""

queryset = Build.objects.none()

serializer_class = build.serializers.BuildCompleteSerializer

def get_queryset(self):
"""Return the queryset for the BuildFinish API endpoint."""

queryset = super().get_queryset()
queryset = queryset.prefetch_related(
'build_lines',
'build_lines__allocations'
)

return queryset


class BuildAutoAllocate(BuildOrderContextMixin, CreateAPI):
"""API endpoint for 'automatically' allocating stock against a build order.
Expand All @@ -484,7 +494,6 @@ class BuildAutoAllocate(BuildOrderContextMixin, CreateAPI):
"""

queryset = Build.objects.none()

serializer_class = build.serializers.BuildAutoAllocationSerializer


Expand All @@ -500,10 +509,22 @@ class BuildAllocate(BuildOrderContextMixin, CreateAPI):
"""

queryset = Build.objects.none()

serializer_class = build.serializers.BuildAllocationSerializer


class BuildIssue(BuildOrderContextMixin, CreateAPI):
"""API endpoint for issuing a BuildOrder."""

queryset = Build.objects.all()
serializer_class = build.serializers.BuildIssueSerializer


class BuildHold(BuildOrderContextMixin, CreateAPI):
"""API endpoint for placing a BuildOrder on hold."""

queryset = Build.objects.all()
serializer_class = build.serializers.BuildHoldSerializer

class BuildCancel(BuildOrderContextMixin, CreateAPI):
"""API endpoint for cancelling a BuildOrder."""

Expand Down Expand Up @@ -663,6 +684,8 @@ def filter_queryset(self, queryset):
path('create-output/', BuildOutputCreate.as_view(), name='api-build-output-create'),
path('delete-outputs/', BuildOutputDelete.as_view(), name='api-build-output-delete'),
path('scrap-outputs/', BuildOutputScrap.as_view(), name='api-build-output-scrap'),
path('issue/', BuildIssue.as_view(), name='api-build-issue'),
path('hold/', BuildHold.as_view(), name='api-build-hold'),
path('finish/', BuildFinish.as_view(), name='api-build-finish'),
path('cancel/', BuildCancel.as_view(), name='api-build-cancel'),
path('unallocate/', BuildUnallocate.as_view(), name='api-build-unallocate'),
Expand Down
81 changes: 79 additions & 2 deletions src/backend/InvenTree/build/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import decimal
import logging
import os
from datetime import datetime
from django.conf import settings

Expand All @@ -26,6 +25,7 @@
from stock.status_codes import StockStatus, StockHistoryCode

from build.validators import generate_next_build_reference, validate_build_order_reference
from generic.states import StateTransitionMixin

import InvenTree.fields
import InvenTree.helpers
Expand Down Expand Up @@ -56,6 +56,7 @@ class Build(
InvenTree.models.MetadataMixin,
InvenTree.models.PluginValidationMixin,
InvenTree.models.ReferenceIndexingMixin,
StateTransitionMixin,
MPTTModel):
"""A Build object organises the creation of new StockItem objects from other existing StockItem objects.

Expand Down Expand Up @@ -574,6 +575,10 @@ def can_complete(self):
- Completed count must meet the required quantity
- Untracked parts must be allocated
"""

if self.status != BuildStatus.PRODUCTION.value:
return False

if self.incomplete_count > 0:
return False

Expand Down Expand Up @@ -602,8 +607,18 @@ def complete_allocations(self, user):
def complete_build(self, user, trim_allocated_stock=False):
"""Mark this build as complete."""

return self.handle_transition(
self.status, BuildStatus.COMPLETE.value, self, self._action_complete, user=user, trim_allocated_stock=trim_allocated_stock
)

def _action_complete(self, *args, **kwargs):
"""Action to be taken when a build is completed."""

import build.tasks

trim_allocated_stock = kwargs.pop('trim_allocated_stock', False)
user = kwargs.pop('user', None)

if self.incomplete_count > 0:
return

Expand Down Expand Up @@ -665,6 +680,59 @@ def complete_build(self, user, trim_allocated_stock=False):
target_exclude=[user],
)

@transaction.atomic
def issue_build(self):
"""Mark the Build as IN PRODUCTION.

Args:
user: The user who is issuing the build
"""
return self.handle_transition(
self.status, BuildStatus.PENDING.value, self, self._action_issue
)

@property
def can_issue(self):
"""Returns True if this BuildOrder can be issued."""
return self.status in [
BuildStatus.PENDING.value,
BuildStatus.ON_HOLD.value,
]

def _action_issue(self, *args, **kwargs):
"""Perform the action to mark this order as PRODUCTION."""

if self.can_issue:
self.status = BuildStatus.PRODUCTION.value
self.save()

trigger_event('build.issued', id=self.pk)

@transaction.atomic
def hold_build(self):
"""Mark the Build as ON HOLD."""

return self.handle_transition(
self.status, BuildStatus.ON_HOLD.value, self, self._action_hold
)

@property
def can_hold(self):
"""Returns True if this BuildOrder can be placed on hold"""
return self.status in [
BuildStatus.PENDING.value,
BuildStatus.PRODUCTION.value,
]

def _action_hold(self, *args, **kwargs):
"""Action to be taken when a build is placed on hold."""

if self.can_hold:
self.status = BuildStatus.ON_HOLD.value
self.save()

trigger_event('build.hold', id=self.pk)

@transaction.atomic
def cancel_build(self, user, **kwargs):
"""Mark the Build as CANCELLED.
Expand All @@ -674,8 +742,17 @@ def cancel_build(self, user, **kwargs):
- Save the Build object
"""

return self.handle_transition(
self.status, BuildStatus.CANCELLED.value, self, self._action_cancel, user=user, **kwargs
)

def _action_cancel(self, *args, **kwargs):
"""Action to be taken when a build is cancelled."""

import build.tasks

user = kwargs.pop('user', None)

remove_allocated_stock = kwargs.get('remove_allocated_stock', False)
remove_incomplete_outputs = kwargs.get('remove_incomplete_outputs', False)

Expand Down Expand Up @@ -1276,7 +1353,7 @@ def is_active(self):
@property
def is_complete(self):
"""Returns True if the build status is COMPLETE."""
return self.status == BuildStatus.COMPLETE
return self.status == BuildStatus.COMPLETE.value

@transaction.atomic
def create_build_line_items(self, prevent_duplicates=True):
Expand Down
31 changes: 31 additions & 0 deletions src/backend/InvenTree/build/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from users.serializers import OwnerSerializer

from .models import Build, BuildLine, BuildItem
from .status_codes import BuildStatus


class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer):
Expand Down Expand Up @@ -597,6 +598,33 @@ def save(self):
)


class BuildIssueSerializer(serializers.Serializer):
"""DRF serializer for issuing a build order."""

class Meta:
"""Serializer metaclass"""
fields = []

def save(self):
"""Issue the specified build order"""
build = self.context['build']
build.issue_build()


class BuildHoldSerializer(serializers.Serializer):
"""DRF serializer for placing a BuildOrder on hold."""

class Meta:
"""Serializer metaclass."""
fields = []

def save(self):
"""Place the specified build on hold."""
build = self.context['build']

build.hold_build()


class BuildCancelSerializer(serializers.Serializer):
"""DRF serializer class for cancelling an active BuildOrder"""

Expand Down Expand Up @@ -737,6 +765,9 @@ def validate(self, data):
"""Perform validation of this serializer prior to saving"""
build = self.context['build']

if build.status != BuildStatus.PRODUCTION.value:
raise ValidationError(_("Build order must be in production state"))

if build.incomplete_count > 0:
raise ValidationError(_("Build order has incomplete outputs"))

Expand Down
2 changes: 2 additions & 0 deletions src/backend/InvenTree/build/status_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class BuildStatus(StatusCode):

PENDING = 10, _('Pending'), 'secondary' # Build is pending / active
PRODUCTION = 20, _('Production'), 'primary' # Build is in production
ON_HOLD = 25, _('On Hold'), 'warning' # Build is on hold
CANCELLED = 30, _('Cancelled'), 'danger' # Build was cancelled
COMPLETE = 40, _('Complete'), 'success' # Build is complete

Expand All @@ -19,5 +20,6 @@ class BuildStatusGroups:

ACTIVE_CODES = [
BuildStatus.PENDING.value,
BuildStatus.ON_HOLD.value,
BuildStatus.PRODUCTION.value,
]
Loading
Loading