PO shipped complete (#7367)

* Add new setting to bypass "shipped" status

* Bypass "shipped" status optionally

* Update setting description string

* Update unit tests

* Refactor location of status_codes

* Link source code into docs

* Add stock status codes

* And the build order too

* Fix import
This commit is contained in:
Oliver 2024-05-30 12:44:57 +10:00 committed by GitHub
parent fb193cae3d
commit 798c0ed322
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 440 additions and 272 deletions

View File

@ -79,6 +79,18 @@ Each *Build Order* has an associated *Status* flag, which indicates the state of
| `Cancelled` | Build has been cancelled |
| `Completed` | Build has been completed |
**Source Code**
Refer to the source code for the Build Order status codes:
::: build.status_codes.BuildStatus
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_source: True
members: []
### Stock Allocations
When a *Build Order* is created, we then have the ability to *allocate* stock items against that build order. The particular parts we need to allocate against the build are specified by the BOM for the part we are assembling.

View File

@ -22,6 +22,20 @@ Each Purchase Order has a specific status code which indicates the current state
| In Progress | The purchase order has been issued to the supplier, and is in progress |
| 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 |
| Returned | The purchase order was returned, and is now closed |
**Source Code**
Refer to the source code for the Purchase Order status codes:
::: order.status_codes.PurchaseOrderStatus
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_source: True
members: []
### Purchase Order Currency

View File

@ -48,6 +48,18 @@ Each Return Order has a specific status code, as follows:
| Complete | The return order was marked as complete, and is now closed |
| Cancelled | The return order was cancelled, and is now closed |
**Source Code**
Refer to the source code for the Return Order status codes:
::: order.status_codes.ReturnOrderStatus
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_source: True
members: []
## Create a Return Order
From the Return Order index, click on <span class='badge inventree add'><span class='fas fa-plus-circle'></span> New Return Order</span> which opens the "Create Return Order" form.

View File

@ -20,8 +20,23 @@ 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 |
| Shipped | The sales order has been completed, and is now closed |
| 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 |
| Lost | The sales order was lost, and is now closed |
| Returned | The sales order was returned, and is now closed |
**Source Code**
Refer to the source code for the Sales Order status codes:
::: order.status_codes.SalesOrderStatus
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_source: True
members: []
### Sales Order Currency
@ -83,7 +98,7 @@ To view all the completed shipment, click on the <span class="badge inventree na
### Complete Order
Once all items in the sales order have been shipped, click on <span class="badge inventree add"><span class='fas fa-check-circle'></span> Complete Order</span> to mark the sales order as complete.
Once all items in the sales order have been shipped, click on <span class="badge inventree add"><span class='fas fa-check-circle'></span> Complete Order</span> to mark the sales order as shipped. Confirm then click on <span class="badge inventree confirm">Submit</span> to complete the order.
### Cancel Order

View File

@ -26,6 +26,18 @@ The *status* of a given stock item is displayed on the stock item detail page:
{% include 'img.html' %}
{% endwith %}
**Source Code**
Refer to the source code for the Stock status codes:
::: stock.status_codes.StockStatus
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_source: True
members: []
### Default Status Code
The default status code for any newly created Stock Item is <span class='badge inventree success'>OK</span>

View File

@ -1,198 +1,9 @@
"""Status codes for InvenTree."""
"""Global import of all status codes.
from django.utils.translation import gettext_lazy as _
This file remains here for backwards compatibility,
as external plugins may import status codes from this file.
"""
from generic.states import StatusCode
class PurchaseOrderStatus(StatusCode):
"""Defines a set of status codes for a PurchaseOrder."""
# Order status codes
PENDING = 10, _('Pending'), 'secondary' # Order is pending (not yet placed)
PLACED = 20, _('Placed'), 'primary' # Order has been placed with supplier
COMPLETE = 30, _('Complete'), 'success' # Order has been completed
CANCELLED = 40, _('Cancelled'), 'danger' # Order was cancelled
LOST = 50, _('Lost'), 'warning' # Order was lost
RETURNED = 60, _('Returned'), 'warning' # Order was returned
class PurchaseOrderStatusGroups:
"""Groups for PurchaseOrderStatus codes."""
# Open orders
OPEN = [PurchaseOrderStatus.PENDING.value, PurchaseOrderStatus.PLACED.value]
# Failed orders
FAILED = [
PurchaseOrderStatus.CANCELLED.value,
PurchaseOrderStatus.LOST.value,
PurchaseOrderStatus.RETURNED.value,
]
class SalesOrderStatus(StatusCode):
"""Defines a set of status codes for a SalesOrder."""
PENDING = 10, _('Pending'), 'secondary' # Order is pending
IN_PROGRESS = (
15,
_('In Progress'),
'primary',
) # Order has been issued, and is in progress
SHIPPED = 20, _('Shipped'), 'success' # Order has been shipped to customer
COMPLETE = 30, _('Complete'), 'success' # Order is complete
CANCELLED = 40, _('Cancelled'), 'danger' # Order has been cancelled
LOST = 50, _('Lost'), 'warning' # Order was lost
RETURNED = 60, _('Returned'), 'warning' # Order was returned
class SalesOrderStatusGroups:
"""Groups for SalesOrderStatus codes."""
# Open orders
OPEN = [SalesOrderStatus.PENDING.value, SalesOrderStatus.IN_PROGRESS.value]
# Completed orders
COMPLETE = [SalesOrderStatus.SHIPPED.value, SalesOrderStatus.COMPLETE.value]
class StockStatus(StatusCode):
"""Status codes for Stock."""
OK = 10, _('OK'), 'success' # Item is OK
ATTENTION = 50, _('Attention needed'), 'warning' # Item requires attention
DAMAGED = 55, _('Damaged'), 'warning' # Item is damaged
DESTROYED = 60, _('Destroyed'), 'danger' # Item is destroyed
REJECTED = 65, _('Rejected'), 'danger' # Item is rejected
LOST = 70, _('Lost'), 'dark' # Item has been lost
QUARANTINED = (
75,
_('Quarantined'),
'info',
) # Item has been quarantined and is unavailable
RETURNED = 85, _('Returned'), 'warning' # Item has been returned from a customer
class StockStatusGroups:
"""Groups for StockStatus codes."""
# The following codes correspond to parts that are 'available' or 'in stock'
AVAILABLE_CODES = [
StockStatus.OK.value,
StockStatus.ATTENTION.value,
StockStatus.DAMAGED.value,
StockStatus.RETURNED.value,
]
class StockHistoryCode(StatusCode):
"""Status codes for StockHistory."""
LEGACY = 0, _('Legacy stock tracking entry')
CREATED = 1, _('Stock item created')
# Manual editing operations
EDITED = 5, _('Edited stock item')
ASSIGNED_SERIAL = 6, _('Assigned serial number')
# Manual stock operations
STOCK_COUNT = 10, _('Stock counted')
STOCK_ADD = 11, _('Stock manually added')
STOCK_REMOVE = 12, _('Stock manually removed')
# Location operations
STOCK_MOVE = 20, _('Location changed')
STOCK_UPDATE = 25, _('Stock updated')
# Installation operations
INSTALLED_INTO_ASSEMBLY = 30, _('Installed into assembly')
REMOVED_FROM_ASSEMBLY = 31, _('Removed from assembly')
INSTALLED_CHILD_ITEM = 35, _('Installed component item')
REMOVED_CHILD_ITEM = 36, _('Removed component item')
# Stock splitting operations
SPLIT_FROM_PARENT = 40, _('Split from parent item')
SPLIT_CHILD_ITEM = 42, _('Split child item')
# Stock merging operations
MERGED_STOCK_ITEMS = 45, _('Merged stock items')
# Convert stock item to variant
CONVERTED_TO_VARIANT = 48, _('Converted to variant')
# Build order codes
BUILD_OUTPUT_CREATED = 50, _('Build order output created')
BUILD_OUTPUT_COMPLETED = 55, _('Build order output completed')
BUILD_OUTPUT_REJECTED = 56, _('Build order output rejected')
BUILD_CONSUMED = 57, _('Consumed by build order')
# Sales order codes
SHIPPED_AGAINST_SALES_ORDER = 60, _('Shipped against Sales Order')
# Purchase order codes
RECEIVED_AGAINST_PURCHASE_ORDER = 70, _('Received against Purchase Order')
# Return order codes
RETURNED_AGAINST_RETURN_ORDER = 80, _('Returned against Return Order')
# Customer actions
SENT_TO_CUSTOMER = 100, _('Sent to customer')
RETURNED_FROM_CUSTOMER = 105, _('Returned from customer')
class BuildStatus(StatusCode):
"""Build status codes."""
PENDING = 10, _('Pending'), 'secondary' # Build is pending / active
PRODUCTION = 20, _('Production'), 'primary' # BuildOrder is in production
CANCELLED = 30, _('Cancelled'), 'danger' # Build was cancelled
COMPLETE = 40, _('Complete'), 'success' # Build is complete
class BuildStatusGroups:
"""Groups for BuildStatus codes."""
ACTIVE_CODES = [BuildStatus.PENDING.value, BuildStatus.PRODUCTION.value]
class ReturnOrderStatus(StatusCode):
"""Defines a set of status codes for a ReturnOrder."""
# Order is pending, waiting for receipt of items
PENDING = 10, _('Pending'), 'secondary'
# Items have been received, and are being inspected
IN_PROGRESS = 20, _('In Progress'), 'primary'
COMPLETE = 30, _('Complete'), 'success'
CANCELLED = 40, _('Cancelled'), 'danger'
class ReturnOrderStatusGroups:
"""Groups for ReturnOrderStatus codes."""
OPEN = [ReturnOrderStatus.PENDING.value, ReturnOrderStatus.IN_PROGRESS.value]
class ReturnOrderLineStatus(StatusCode):
"""Defines a set of status codes for a ReturnOrderLineItem."""
PENDING = 10, _('Pending'), 'secondary'
# Item is to be returned to customer, no other action
RETURN = 20, _('Return'), 'success'
# Item is to be repaired, and returned to customer
REPAIR = 30, _('Repair'), 'primary'
# Item is to be replaced (new item shipped)
REPLACE = 40, _('Replace'), 'warning'
# Item is to be refunded (cannot be repaired)
REFUND = 50, _('Refund'), 'info'
# Item is rejected
REJECT = 60, _('Reject'), 'danger'
from build.status_codes import *
from order.status_codes import *
from stock.status_codes import *

View File

@ -14,7 +14,7 @@ from django_filters import rest_framework as rest_filters
from InvenTree.api import AttachmentMixin, APIDownloadMixin, ListCreateDestroyAPIView, MetadataView
from generic.states.api import StatusView
from InvenTree.helpers import str2bool, isNull, DownloadFile
from InvenTree.status_codes import BuildStatus, BuildStatusGroups
from build.status_codes import BuildStatus, BuildStatusGroups
from InvenTree.mixins import CreateAPI, RetrieveUpdateDestroyAPI, ListCreateAPI
import common.models

View File

@ -22,7 +22,8 @@ from mptt.exceptions import InvalidMove
from rest_framework import serializers
from InvenTree.status_codes import BuildStatus, StockStatus, StockHistoryCode, BuildStatusGroups
from build.status_codes import BuildStatus, BuildStatusGroups
from stock.status_codes import StockStatus, StockHistoryCode
from build.validators import generate_next_build_reference, validate_build_order_reference

View File

@ -18,7 +18,7 @@ from InvenTree.serializers import UserSerializer
import InvenTree.helpers
from InvenTree.serializers import InvenTreeDecimalField
from InvenTree.status_codes import StockStatus
from stock.status_codes import StockStatus
from stock.generators import generate_batch_code
from stock.models import StockItem, StockLocation

View File

@ -0,0 +1,20 @@
"""Build status codes."""
from django.utils.translation import gettext_lazy as _
from generic.states import StatusCode
class BuildStatus(StatusCode):
"""Build status codes."""
PENDING = 10, _('Pending'), 'secondary' # Build is pending / active
PRODUCTION = 20, _('Production'), 'primary' # BuildOrder is in production
CANCELLED = 30, _('Cancelled'), 'danger' # Build was cancelled
COMPLETE = 40, _('Complete'), 'success' # Build is complete
class BuildStatusGroups:
"""Groups for BuildStatus codes."""
ACTIVE_CODES = [BuildStatus.PENDING.value, BuildStatus.PRODUCTION.value]

View File

@ -17,8 +17,8 @@ import InvenTree.email
import InvenTree.helpers
import InvenTree.helpers_model
import InvenTree.tasks
from InvenTree.status_codes import BuildStatusGroups
from InvenTree.ready import isImportingData
from build.status_codes import BuildStatusGroups
import part.models as part_models

View File

@ -10,7 +10,8 @@ from part.models import Part
from build.models import Build, BuildItem
from stock.models import StockItem
from InvenTree.status_codes import BuildStatus, StockStatus
from build.status_codes import BuildStatus
from stock.status_codes import StockStatus
from InvenTree.unit_test import InvenTreeAPITestCase

View File

@ -11,7 +11,7 @@ from InvenTree.unit_test import InvenTreeTestCase
from .models import Build
from stock.models import StockItem
from InvenTree.status_codes import BuildStatus
from build.status_codes import BuildStatus
class BuildTestSimple(InvenTreeTestCase):

View File

@ -5,7 +5,7 @@ from django.views.generic import DetailView, ListView
from .models import Build
from InvenTree.views import InvenTreeRoleMixin
from InvenTree.status_codes import BuildStatus
from build.status_codes import BuildStatus
from plugin.views import InvenTreePluginViewMixin

View File

@ -1874,6 +1874,14 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'default': False,
'validator': bool,
},
'SALESORDER_SHIP_COMPLETE': {
'name': _('Mark Shipped Orders as Complete'),
'description': _(
'Sales orders marked as shipped will automatically be completed, bypassing the "shipped" status'
),
'default': False,
'validator': bool,
},
'PURCHASEORDER_REFERENCE_PATTERN': {
'name': _('Purchase Order Reference Pattern'),
'description': _(

View File

@ -31,7 +31,7 @@ import InvenTree.tasks
import InvenTree.validators
from common.settings import currency_code_default
from InvenTree.fields import InvenTreeURLField, RoundingDecimalField
from InvenTree.status_codes import PurchaseOrderStatusGroups
from order.status_codes import PurchaseOrderStatusGroups
def rename_company_image(instance, filename):

View File

@ -30,14 +30,6 @@ from InvenTree.filters import SEARCH_ORDER_FILTER, SEARCH_ORDER_FILTER_ALIAS
from InvenTree.helpers import DownloadFile, str2bool
from InvenTree.helpers_model import construct_absolute_url, get_base_url
from InvenTree.mixins import CreateAPI, ListAPI, ListCreateAPI, RetrieveUpdateDestroyAPI
from InvenTree.status_codes import (
PurchaseOrderStatus,
PurchaseOrderStatusGroups,
ReturnOrderLineStatus,
ReturnOrderStatus,
SalesOrderStatus,
SalesOrderStatusGroups,
)
from order import models, serializers
from order.admin import (
PurchaseOrderExtraLineResource,
@ -48,6 +40,14 @@ from order.admin import (
SalesOrderLineItemResource,
SalesOrderResource,
)
from order.status_codes import (
PurchaseOrderStatus,
PurchaseOrderStatusGroups,
ReturnOrderLineStatus,
ReturnOrderStatus,
SalesOrderStatus,
SalesOrderStatusGroups,
)
from part.models import Part
from users.models import Owner

View File

@ -3,7 +3,7 @@
from django.db import migrations
from InvenTree.status_codes import SalesOrderStatus
from order.status_codes import SalesOrderStatus
def add_shipment(apps, schema_editor):

View File

@ -2,7 +2,7 @@
from django.db import migrations
from InvenTree.status_codes import SalesOrderStatus
from order.status_codes import SalesOrderStatus
def calculate_shipped_quantity(apps, schema_editor):

View File

@ -1,7 +1,7 @@
# Generated by Django 3.2.19 on 2023-06-04 17:43
from django.db import migrations, models
import InvenTree.status_codes
import order.status_codes
class Migration(migrations.Migration):
@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='returnorderlineitem',
name='outcome',
field=models.PositiveIntegerField(choices=InvenTree.status_codes.ReturnOrderLineStatus.items(), default=10, help_text='Outcome for this line item', verbose_name='Outcome'),
field=models.PositiveIntegerField(choices=order.status_codes.ReturnOrderLineStatus.items(), default=10, help_text='Outcome for this line item', verbose_name='Outcome'),
),
]

View File

@ -2,7 +2,7 @@
import django.core.validators
from django.db import migrations, models
import InvenTree.status_codes
import order.status_codes
class Migration(migrations.Migration):
@ -15,6 +15,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='salesorder',
name='status',
field=models.PositiveIntegerField(choices=InvenTree.status_codes.SalesOrderStatus.items(), default=10, help_text='Purchase order status', verbose_name='Status'),
field=models.PositiveIntegerField(choices=order.status_codes.SalesOrderStatus.items(), default=10, help_text='Purchase order status', verbose_name='Status'),
),
]

View File

@ -45,7 +45,7 @@ from InvenTree.fields import (
)
from InvenTree.helpers import decimal2string, pui_url
from InvenTree.helpers_model import getSetting, notify_responsible
from InvenTree.status_codes import (
from order.status_codes import (
PurchaseOrderStatus,
PurchaseOrderStatusGroups,
ReturnOrderLineStatus,
@ -53,11 +53,10 @@ from InvenTree.status_codes import (
ReturnOrderStatusGroups,
SalesOrderStatus,
SalesOrderStatusGroups,
StockHistoryCode,
StockStatus,
)
from part import models as PartModels
from plugin.events import trigger_event
from stock.status_codes import StockHistoryCode, StockStatus
logger = logging.getLogger('inventree')
@ -1024,6 +1023,12 @@ class SalesOrder(TotalPriceMixin, Order):
Throws a ValidationError if cannot be completed.
"""
try:
if self.status == SalesOrderStatus.COMPLETE.value:
raise ValidationError(_('Order is already complete'))
if self.status == SalesOrderStatus.CANCELLED.value:
raise ValidationError(_('Order is already cancelled'))
# Only an open order can be marked as shipped
if self.is_open and not self.is_completed:
raise ValidationError(_('Only an open order can be marked as complete'))
@ -1067,7 +1072,11 @@ class SalesOrder(TotalPriceMixin, Order):
if not self.can_complete(**kwargs):
return False
if self.status == SalesOrderStatus.SHIPPED:
bypass_shipped = InvenTree.helpers.str2bool(
common_models.InvenTreeSetting.get_setting('SALESORDER_SHIP_COMPLETE')
)
if bypass_shipped or self.status == SalesOrderStatus.SHIPPED:
self.status = SalesOrderStatus.COMPLETE.value
else:
self.status = SalesOrderStatus.SHIPPED.value

View File

@ -48,14 +48,14 @@ from InvenTree.serializers import (
InvenTreeModelSerializer,
InvenTreeMoneySerializer,
)
from InvenTree.status_codes import (
from order.status_codes import (
PurchaseOrderStatusGroups,
ReturnOrderLineStatus,
ReturnOrderStatus,
SalesOrderStatusGroups,
StockStatus,
)
from part.serializers import PartBriefSerializer
from stock.status_codes import StockStatus
from users.serializers import OwnerSerializer

View File

@ -0,0 +1,97 @@
"""Order status codes."""
from django.utils.translation import gettext_lazy as _
from generic.states import StatusCode
class PurchaseOrderStatus(StatusCode):
"""Defines a set of status codes for a PurchaseOrder."""
# Order status codes
PENDING = 10, _('Pending'), 'secondary' # Order is pending (not yet placed)
PLACED = 20, _('Placed'), 'primary' # Order has been placed with supplier
COMPLETE = 30, _('Complete'), 'success' # Order has been completed
CANCELLED = 40, _('Cancelled'), 'danger' # Order was cancelled
LOST = 50, _('Lost'), 'warning' # Order was lost
RETURNED = 60, _('Returned'), 'warning' # Order was returned
class PurchaseOrderStatusGroups:
"""Groups for PurchaseOrderStatus codes."""
# Open orders
OPEN = [PurchaseOrderStatus.PENDING.value, PurchaseOrderStatus.PLACED.value]
# Failed orders
FAILED = [
PurchaseOrderStatus.CANCELLED.value,
PurchaseOrderStatus.LOST.value,
PurchaseOrderStatus.RETURNED.value,
]
class SalesOrderStatus(StatusCode):
"""Defines a set of status codes for a SalesOrder."""
PENDING = 10, _('Pending'), 'secondary' # Order is pending
IN_PROGRESS = (
15,
_('In Progress'),
'primary',
) # Order has been issued, and is in progress
SHIPPED = 20, _('Shipped'), 'success' # Order has been shipped to customer
COMPLETE = 30, _('Complete'), 'success' # Order is complete
CANCELLED = 40, _('Cancelled'), 'danger' # Order has been cancelled
LOST = 50, _('Lost'), 'warning' # Order was lost
RETURNED = 60, _('Returned'), 'warning' # Order was returned
class SalesOrderStatusGroups:
"""Groups for SalesOrderStatus codes."""
# Open orders
OPEN = [SalesOrderStatus.PENDING.value, SalesOrderStatus.IN_PROGRESS.value]
# Completed orders
COMPLETE = [SalesOrderStatus.SHIPPED.value, SalesOrderStatus.COMPLETE.value]
class ReturnOrderStatus(StatusCode):
"""Defines a set of status codes for a ReturnOrder."""
# Order is pending, waiting for receipt of items
PENDING = 10, _('Pending'), 'secondary'
# Items have been received, and are being inspected
IN_PROGRESS = 20, _('In Progress'), 'primary'
COMPLETE = 30, _('Complete'), 'success'
CANCELLED = 40, _('Cancelled'), 'danger'
class ReturnOrderStatusGroups:
"""Groups for ReturnOrderStatus codes."""
OPEN = [ReturnOrderStatus.PENDING.value, ReturnOrderStatus.IN_PROGRESS.value]
class ReturnOrderLineStatus(StatusCode):
"""Defines a set of status codes for a ReturnOrderLineItem."""
PENDING = 10, _('Pending'), 'secondary'
# Item is to be returned to customer, no other action
RETURN = 20, _('Return'), 'success'
# Item is to be repaired, and returned to customer
REPAIR = 30, _('Repair'), 'primary'
# Item is to be replaced (new item shipped)
REPLACE = 40, _('Replace'), 'warning'
# Item is to be refunded (cannot be repaired)
REFUND = 50, _('Refund'), 'info'
# Item is rejected
REJECT = 60, _('Reject'), 'danger'

View File

@ -7,8 +7,8 @@ from django.utils.translation import gettext_lazy as _
import common.notifications
import InvenTree.helpers_model
import order.models
from InvenTree.status_codes import PurchaseOrderStatusGroups, SalesOrderStatusGroups
from InvenTree.tasks import ScheduledTask, scheduled_task
from order.status_codes import PurchaseOrderStatusGroups, SalesOrderStatusGroups
from plugin.events import trigger_event

View File

@ -16,18 +16,18 @@ from rest_framework import status
from common.models import InvenTreeSetting
from common.settings import currency_codes
from company.models import Company, SupplierPart, SupplierPriceBreak
from InvenTree.status_codes import (
from InvenTree.unit_test import InvenTreeAPITestCase
from order import models
from order.status_codes import (
PurchaseOrderStatus,
ReturnOrderLineStatus,
ReturnOrderStatus,
SalesOrderStatus,
SalesOrderStatusGroups,
StockStatus,
)
from InvenTree.unit_test import InvenTreeAPITestCase
from order import models
from part.models import Part
from stock.models import StockItem
from stock.status_codes import StockStatus
from users.models import Owner
@ -1476,6 +1476,77 @@ class SalesOrderTest(OrderTest):
expected_fn=f'InvenTree_SalesOrders.{fmt}',
)
def test_sales_order_complete(self):
"""Tests for marking a SalesOrder as complete."""
self.assignRole('sales_order.add')
# Let's create a SalesOrder
customer = Company.objects.filter(is_customer=True).first()
so = models.SalesOrder.objects.create(
customer=customer, reference='SO-12345', description='Test SO'
)
self.assertEqual(so.status, SalesOrderStatus.PENDING.value)
# Create a line item
part = Part.objects.filter(salable=True).first()
line = models.SalesOrderLineItem.objects.create(
order=so, part=part, quantity=10, sale_price=Money(10, 'USD')
)
shipment = so.shipments.first()
if shipment is None:
shipment = models.SalesOrderShipment.objects.create(
order=so, reference='SHIP-12345'
)
# Allocate some stock
item = StockItem.objects.create(part=part, quantity=100, location=None)
models.SalesOrderAllocation.objects.create(
quantity=10, line=line, item=item, shipment=shipment
)
# Ship the shipment
shipment.complete_shipment(self.user)
# Ok, now we should be able to "complete" the shipment via the API
# The 'SALESORDER_SHIP_COMPLETE' setting determines if the outcome is "SHIPPED" or "COMPLETE"
InvenTreeSetting.set_setting('SALESORDER_SHIP_COMPLETE', False)
url = reverse('api-so-complete', kwargs={'pk': so.pk})
self.post(url, {}, expected_code=201)
so.refresh_from_db()
self.assertEqual(so.status, SalesOrderStatus.SHIPPED.value)
# Now, let's try to "complete" the shipment again
# This time it should get marked as "COMPLETE"
self.post(url, {}, expected_code=201)
so.refresh_from_db()
self.assertEqual(so.status, SalesOrderStatus.COMPLETE.value)
# Now, let's try *again* (it should fail as the order is already complete)
response = self.post(url, {}, expected_code=400)
self.assertIn('Order is already complete', str(response.data))
# Next, we'll change the setting so that the order status jumps straight to "complete"
so.status = SalesOrderStatus.PENDING.value
so.save()
so.refresh_from_db()
self.assertEqual(so.status, SalesOrderStatus.PENDING.value)
InvenTreeSetting.set_setting('SALESORDER_SHIP_COMPLETE', True)
self.post(url, {}, expected_code=201)
# The orders status should now be "complete" (not "shipped")
so.refresh_from_db()
self.assertEqual(so.status, SalesOrderStatus.COMPLETE.value)
class SalesOrderLineItemTest(OrderTest):
"""Tests for the SalesOrderLineItem API."""

View File

@ -2,7 +2,7 @@
from django_test_migrations.contrib.unittest_case import MigratorTestCase
from InvenTree.status_codes import SalesOrderStatus
from order.status_codes import SalesOrderStatus
class TestRefIntMigrations(MigratorTestCase):

View File

@ -14,7 +14,7 @@ from djmoney.money import Money
import common.models
import order.tasks
from company.models import Company, SupplierPart
from InvenTree.status_codes import PurchaseOrderStatus
from order.status_codes import PurchaseOrderStatus
from part.models import Part
from stock.models import StockItem, StockLocation
from users.models import Owner

View File

@ -18,6 +18,7 @@ from rest_framework.response import Response
import order.models
import part.filters
from build.models import Build, BuildItem
from build.status_codes import BuildStatusGroups
from InvenTree.api import (
APIDownloadMixin,
AttachmentMixin,
@ -45,11 +46,7 @@ from InvenTree.mixins import (
)
from InvenTree.permissions import RolePermission
from InvenTree.serializers import EmptySerializer
from InvenTree.status_codes import (
BuildStatusGroups,
PurchaseOrderStatusGroups,
SalesOrderStatusGroups,
)
from order.status_codes import PurchaseOrderStatusGroups, SalesOrderStatusGroups
from part.admin import PartCategoryResource, PartResource
from stock.models import StockLocation

View File

@ -40,11 +40,8 @@ from sql_util.utils import SubquerySum
import part.models
import stock.models
from InvenTree.status_codes import (
BuildStatusGroups,
PurchaseOrderStatusGroups,
SalesOrderStatusGroups,
)
from build.status_codes import BuildStatusGroups
from order.status_codes import PurchaseOrderStatusGroups, SalesOrderStatusGroups
def annotate_in_production_quantity(reference=''):

View File

@ -46,20 +46,20 @@ import part.settings as part_settings
import report.mixins
import users.models
from build import models as BuildModels
from build.status_codes import BuildStatusGroups
from common.models import InvenTreeSetting
from common.settings import currency_code_default
from company.models import SupplierPart
from InvenTree import helpers, validators
from InvenTree.fields import InvenTreeURLField
from InvenTree.helpers import decimal2money, decimal2string, normalize, str2bool
from InvenTree.status_codes import (
BuildStatusGroups,
from order import models as OrderModels
from order.status_codes import (
PurchaseOrderStatus,
PurchaseOrderStatusGroups,
SalesOrderStatus,
SalesOrderStatusGroups,
)
from order import models as OrderModels
from stock import models as StockModels
logger = logging.getLogger('inventree')

View File

@ -33,7 +33,7 @@ import part.stocktake
import part.tasks
import stock.models
import users.models
from InvenTree.status_codes import BuildStatusGroups
from build.status_codes import BuildStatusGroups
from InvenTree.tasks import offload_task
from .models import (

View File

@ -19,11 +19,12 @@ from rest_framework.test import APIClient
import build.models
import company.models
import order.models
from build.status_codes import BuildStatus
from common.models import InvenTreeSetting
from company.models import Company, SupplierPart
from InvenTree.settings import BASE_DIR
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatusGroups, StockStatus
from InvenTree.unit_test import InvenTreeAPITestCase
from order.status_codes import PurchaseOrderStatusGroups
from part.models import (
BomItem,
BomItemSubstitute,
@ -37,6 +38,7 @@ from part.models import (
PartTestTemplate,
)
from stock.models import StockItem, StockLocation
from stock.status_codes import StockStatus
class PartCategoryAPITest(InvenTreeAPITestCase):

View File

@ -11,8 +11,8 @@ import company.models
import order.models
import part.models
import stock.models
from InvenTree.status_codes import PurchaseOrderStatus
from InvenTree.unit_test import InvenTreeTestCase
from order.status_codes import PurchaseOrderStatus
class PartPricingTests(InvenTreeTestCase):

View File

@ -7,7 +7,7 @@ from rest_framework import serializers
import order.models
import stock.models
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
from order.status_codes import PurchaseOrderStatus, SalesOrderStatus
from plugin.builtin.barcodes.inventree_barcode import InvenTreeInternalBarcodePlugin

View File

@ -2,8 +2,8 @@
from common.notifications import trigger_notification
from generic.states import TransitionMethod
from InvenTree.status_codes import ReturnOrderStatus
from order.models import ReturnOrder
from order.status_codes import ReturnOrderStatus
from plugin import InvenTreePlugin

View File

@ -56,7 +56,6 @@ from InvenTree.mixins import (
RetrieveAPI,
RetrieveUpdateDestroyAPI,
)
from InvenTree.status_codes import StockHistoryCode, StockStatus
from order.models import PurchaseOrder, ReturnOrder, SalesOrder, SalesOrderAllocation
from order.serializers import (
PurchaseOrderSerializer,
@ -75,6 +74,7 @@ from stock.models import (
StockLocation,
StockLocationType,
)
from stock.status_codes import StockHistoryCode, StockStatus
class GenerateBatchCode(GenericAPIView):

View File

@ -4,7 +4,7 @@ import re
from django.db import migrations
from InvenTree.status_codes import StockHistoryCode
from stock.status_codes import StockHistoryCode
def update_history(apps, schema_editor):

View File

@ -15,7 +15,7 @@ def update_stock_history(apps, schema_editor):
- Add the appropriate history!
"""
from InvenTree.status_codes import StockHistoryCode
from stock.status_codes import StockHistoryCode
StockItem = apps.get_model('stock', 'stockitem')
StockItemTracking = apps.get_model('stock', 'stockitemtracking')

View File

@ -2,7 +2,7 @@
import django.core.validators
from django.db import migrations, models
import InvenTree.status_codes
import stock.status_codes
class Migration(migrations.Migration):
@ -15,6 +15,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='stockitem',
name='status',
field=models.PositiveIntegerField(choices=InvenTree.status_codes.StockStatus.items(), default=10, validators=[django.core.validators.MinValueValidator(0)]),
field=models.PositiveIntegerField(choices=stock.status_codes.StockStatus.items(), default=10, validators=[django.core.validators.MinValueValidator(0)]),
),
]

View File

@ -34,15 +34,11 @@ import report.mixins
import report.models
from company import models as CompanyModels
from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField
from InvenTree.status_codes import (
SalesOrderStatusGroups,
StockHistoryCode,
StockStatus,
StockStatusGroups,
)
from order.status_codes import SalesOrderStatusGroups
from part import models as PartModels
from plugin.events import trigger_event
from stock.generators import generate_batch_code
from stock.status_codes import StockHistoryCode, StockStatus, StockStatusGroups
from users.models import Owner
logger = logging.getLogger('inventree')
@ -348,7 +344,7 @@ class StockItem(
stocktake_user: User that performed the most recent stocktake
review_needed: Flag if StockItem needs review
delete_on_deplete: If True, StockItem will be deleted when the stock level gets to zero
status: Status of this StockItem (ref: InvenTree.status_codes.StockStatus)
status: Status of this StockItem (ref: stock.status_codes.StockStatus)
notes: Extra notes field
build: Link to a Build (if this stock item was created from a build)
is_building: Boolean field indicating if this stock item is currently being built (or is "in production")

View File

@ -20,11 +20,11 @@ import common.models
import company.models
import InvenTree.helpers
import InvenTree.serializers
import InvenTree.status_codes
import order.models
import part.filters as part_filters
import part.models as part_models
import stock.filters
import stock.status_codes
from company.serializers import SupplierPartSerializer
from InvenTree.serializers import InvenTreeCurrencySerializer, InvenTreeDecimalField
from part.serializers import PartBriefSerializer, PartTestTemplateSerializer
@ -925,8 +925,8 @@ class StockChangeStatusSerializer(serializers.Serializer):
return items
status = serializers.ChoiceField(
choices=InvenTree.status_codes.StockStatus.items(),
default=InvenTree.status_codes.StockStatus.OK.value,
choices=stock.status_codes.StockStatus.items(),
default=stock.status_codes.StockStatus.OK.value,
label=_('Status'),
)
@ -973,7 +973,7 @@ class StockChangeStatusSerializer(serializers.Serializer):
transaction_notes.append(
StockItemTracking(
item=item,
tracking_type=InvenTree.status_codes.StockHistoryCode.EDITED.value,
tracking_type=stock.status_codes.StockHistoryCode.EDITED.value,
date=now,
deltas=deltas,
user=user,
@ -1419,7 +1419,7 @@ def stock_item_adjust_status_options():
In particular, include a Null option for the status field.
"""
return [(None, _('No Change'))] + InvenTree.status_codes.StockStatus.items()
return [(None, _('No Change'))] + stock.status_codes.StockStatus.items()
class StockAdjustmentItemSerializer(serializers.Serializer):

View File

@ -0,0 +1,91 @@
"""Stock status codes."""
from django.utils.translation import gettext_lazy as _
from generic.states import StatusCode
class StockStatus(StatusCode):
"""Status codes for Stock."""
OK = 10, _('OK'), 'success' # Item is OK
ATTENTION = 50, _('Attention needed'), 'warning' # Item requires attention
DAMAGED = 55, _('Damaged'), 'warning' # Item is damaged
DESTROYED = 60, _('Destroyed'), 'danger' # Item is destroyed
REJECTED = 65, _('Rejected'), 'danger' # Item is rejected
LOST = 70, _('Lost'), 'dark' # Item has been lost
QUARANTINED = (
75,
_('Quarantined'),
'info',
) # Item has been quarantined and is unavailable
RETURNED = 85, _('Returned'), 'warning' # Item has been returned from a customer
class StockStatusGroups:
"""Groups for StockStatus codes."""
# The following codes correspond to parts that are 'available' or 'in stock'
AVAILABLE_CODES = [
StockStatus.OK.value,
StockStatus.ATTENTION.value,
StockStatus.DAMAGED.value,
StockStatus.RETURNED.value,
]
class StockHistoryCode(StatusCode):
"""Status codes for StockHistory."""
LEGACY = 0, _('Legacy stock tracking entry')
CREATED = 1, _('Stock item created')
# Manual editing operations
EDITED = 5, _('Edited stock item')
ASSIGNED_SERIAL = 6, _('Assigned serial number')
# Manual stock operations
STOCK_COUNT = 10, _('Stock counted')
STOCK_ADD = 11, _('Stock manually added')
STOCK_REMOVE = 12, _('Stock manually removed')
# Location operations
STOCK_MOVE = 20, _('Location changed')
STOCK_UPDATE = 25, _('Stock updated')
# Installation operations
INSTALLED_INTO_ASSEMBLY = 30, _('Installed into assembly')
REMOVED_FROM_ASSEMBLY = 31, _('Removed from assembly')
INSTALLED_CHILD_ITEM = 35, _('Installed component item')
REMOVED_CHILD_ITEM = 36, _('Removed component item')
# Stock splitting operations
SPLIT_FROM_PARENT = 40, _('Split from parent item')
SPLIT_CHILD_ITEM = 42, _('Split child item')
# Stock merging operations
MERGED_STOCK_ITEMS = 45, _('Merged stock items')
# Convert stock item to variant
CONVERTED_TO_VARIANT = 48, _('Converted to variant')
# Build order codes
BUILD_OUTPUT_CREATED = 50, _('Build order output created')
BUILD_OUTPUT_COMPLETED = 55, _('Build order output completed')
BUILD_OUTPUT_REJECTED = 56, _('Build order output rejected')
BUILD_CONSUMED = 57, _('Consumed by build order')
# Sales order codes
SHIPPED_AGAINST_SALES_ORDER = 60, _('Shipped against Sales Order')
# Purchase order codes
RECEIVED_AGAINST_PURCHASE_ORDER = 70, _('Received against Purchase Order')
# Return order codes
RETURNED_AGAINST_RETURN_ORDER = 80, _('Returned against Return Order')
# Customer actions
SENT_TO_CUSTOMER = 100, _('Sent to customer')
RETURNED_FROM_CUSTOMER = 105, _('Returned from customer')

View File

@ -17,7 +17,6 @@ import build.models
import company.models
import part.models
from common.models import InvenTreeSetting
from InvenTree.status_codes import StockHistoryCode, StockStatus
from InvenTree.unit_test import InvenTreeAPITestCase
from part.models import Part, PartTestTemplate
from stock.models import (
@ -26,6 +25,7 @@ from stock.models import (
StockLocation,
StockLocationType,
)
from stock.status_codes import StockHistoryCode, StockStatus
class StockAPITestCase(InvenTreeAPITestCase):

View File

@ -5,9 +5,9 @@ from django.test import tag
from django.urls import reverse
from common.models import InvenTreeSetting
from InvenTree.status_codes import StockStatus
from InvenTree.unit_test import InvenTreeTestCase
from stock.models import StockItem, StockLocation
from stock.status_codes import StockStatus
from users.models import Owner

View File

@ -10,10 +10,10 @@ from django.test import override_settings
from build.models import Build
from common.models import InvenTreeSetting
from company.models import Company
from InvenTree.status_codes import StockHistoryCode
from InvenTree.unit_test import InvenTreeTestCase
from order.models import SalesOrder
from part.models import Part, PartTestTemplate
from stock.status_codes import StockHistoryCode
from .models import StockItem, StockItemTestResult, StockItemTracking, StockLocation

View File

@ -15,6 +15,7 @@
{% include "InvenTree/settings/setting.html" with key="SALESORDER_REQUIRE_RESPONSIBLE" %}
{% include "InvenTree/settings/setting.html" with key="SALESORDER_DEFAULT_SHIPMENT" icon="fa-truck-loading" %}
{% include "InvenTree/settings/setting.html" with key="SALESORDER_EDIT_COMPLETED_ORDERS" icon='fa-edit' %}
{% include "InvenTree/settings/setting.html" with key="SALESORDER_SHIP_COMPLETE" %}
</tbody>
</table>

View File

@ -263,7 +263,8 @@ export default function SystemSettings() {
'SALESORDER_REFERENCE_PATTERN',
'SALESORDER_REQUIRE_RESPONSIBLE',
'SALESORDER_DEFAULT_SHIPMENT',
'SALESORDER_EDIT_COMPLETED_ORDERS'
'SALESORDER_EDIT_COMPLETED_ORDERS',
'SALESORDER_SHIP_COMPLETE'
]}
/>
)