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

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 | | `Cancelled` | Build has been cancelled |
| `Completed` | Build has been completed | | `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 ### 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. 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 | | 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 | | Complete | The purchase order has been completed, and is now closed |
| Cancelled | The purchase order was cancelled, 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 ### 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 | | Complete | The return order was marked as complete, and is now closed |
| Cancelled | The return order was cancelled, 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 ## 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. 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 | | 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 | | 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 | | 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 ### Sales Order Currency
@ -83,7 +98,7 @@ To view all the completed shipment, click on the <span class="badge inventree na
### Complete Order ### 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 ### 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' %} {% include 'img.html' %}
{% endwith %} {% 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 ### Default Status Code
The default status code for any newly created Stock Item is <span class='badge inventree success'>OK</span> 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 from build.status_codes import *
from order.status_codes import *
from stock.status_codes import *
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'

View File

@ -14,7 +14,7 @@ from django_filters import rest_framework as rest_filters
from InvenTree.api import AttachmentMixin, APIDownloadMixin, ListCreateDestroyAPIView, MetadataView from InvenTree.api import AttachmentMixin, APIDownloadMixin, ListCreateDestroyAPIView, MetadataView
from generic.states.api import StatusView from generic.states.api import StatusView
from InvenTree.helpers import str2bool, isNull, DownloadFile 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 from InvenTree.mixins import CreateAPI, RetrieveUpdateDestroyAPI, ListCreateAPI
import common.models import common.models

View File

@ -22,7 +22,8 @@ from mptt.exceptions import InvalidMove
from rest_framework import serializers 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 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 import InvenTree.helpers
from InvenTree.serializers import InvenTreeDecimalField 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.generators import generate_batch_code
from stock.models import StockItem, StockLocation 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
import InvenTree.helpers_model import InvenTree.helpers_model
import InvenTree.tasks import InvenTree.tasks
from InvenTree.status_codes import BuildStatusGroups
from InvenTree.ready import isImportingData from InvenTree.ready import isImportingData
from build.status_codes import BuildStatusGroups
import part.models as part_models import part.models as part_models

View File

@ -10,7 +10,8 @@ from part.models import Part
from build.models import Build, BuildItem from build.models import Build, BuildItem
from stock.models import StockItem 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 from InvenTree.unit_test import InvenTreeAPITestCase

View File

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

View File

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

View File

@ -1874,6 +1874,14 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'default': False, 'default': False,
'validator': bool, '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': { 'PURCHASEORDER_REFERENCE_PATTERN': {
'name': _('Purchase Order Reference Pattern'), 'name': _('Purchase Order Reference Pattern'),
'description': _( 'description': _(

View File

@ -31,7 +31,7 @@ import InvenTree.tasks
import InvenTree.validators import InvenTree.validators
from common.settings import currency_code_default from common.settings import currency_code_default
from InvenTree.fields import InvenTreeURLField, RoundingDecimalField from InvenTree.fields import InvenTreeURLField, RoundingDecimalField
from InvenTree.status_codes import PurchaseOrderStatusGroups from order.status_codes import PurchaseOrderStatusGroups
def rename_company_image(instance, filename): 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 import DownloadFile, str2bool
from InvenTree.helpers_model import construct_absolute_url, get_base_url from InvenTree.helpers_model import construct_absolute_url, get_base_url
from InvenTree.mixins import CreateAPI, ListAPI, ListCreateAPI, RetrieveUpdateDestroyAPI 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 import models, serializers
from order.admin import ( from order.admin import (
PurchaseOrderExtraLineResource, PurchaseOrderExtraLineResource,
@ -48,6 +40,14 @@ from order.admin import (
SalesOrderLineItemResource, SalesOrderLineItemResource,
SalesOrderResource, SalesOrderResource,
) )
from order.status_codes import (
PurchaseOrderStatus,
PurchaseOrderStatusGroups,
ReturnOrderLineStatus,
ReturnOrderStatus,
SalesOrderStatus,
SalesOrderStatusGroups,
)
from part.models import Part from part.models import Part
from users.models import Owner from users.models import Owner

View File

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

View File

@ -2,7 +2,7 @@
from django.db import migrations from django.db import migrations
from InvenTree.status_codes import SalesOrderStatus from order.status_codes import SalesOrderStatus
def calculate_shipped_quantity(apps, schema_editor): 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 # Generated by Django 3.2.19 on 2023-06-04 17:43
from django.db import migrations, models from django.db import migrations, models
import InvenTree.status_codes import order.status_codes
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='returnorderlineitem', model_name='returnorderlineitem',
name='outcome', 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 import django.core.validators
from django.db import migrations, models from django.db import migrations, models
import InvenTree.status_codes import order.status_codes
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -15,6 +15,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='salesorder', model_name='salesorder',
name='status', 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 import decimal2string, pui_url
from InvenTree.helpers_model import getSetting, notify_responsible from InvenTree.helpers_model import getSetting, notify_responsible
from InvenTree.status_codes import ( from order.status_codes import (
PurchaseOrderStatus, PurchaseOrderStatus,
PurchaseOrderStatusGroups, PurchaseOrderStatusGroups,
ReturnOrderLineStatus, ReturnOrderLineStatus,
@ -53,11 +53,10 @@ from InvenTree.status_codes import (
ReturnOrderStatusGroups, ReturnOrderStatusGroups,
SalesOrderStatus, SalesOrderStatus,
SalesOrderStatusGroups, SalesOrderStatusGroups,
StockHistoryCode,
StockStatus,
) )
from part import models as PartModels from part import models as PartModels
from plugin.events import trigger_event from plugin.events import trigger_event
from stock.status_codes import StockHistoryCode, StockStatus
logger = logging.getLogger('inventree') logger = logging.getLogger('inventree')
@ -1024,6 +1023,12 @@ class SalesOrder(TotalPriceMixin, Order):
Throws a ValidationError if cannot be completed. Throws a ValidationError if cannot be completed.
""" """
try: 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 # Only an open order can be marked as shipped
if self.is_open and not self.is_completed: if self.is_open and not self.is_completed:
raise ValidationError(_('Only an open order can be marked as complete')) 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): if not self.can_complete(**kwargs):
return False 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 self.status = SalesOrderStatus.COMPLETE.value
else: else:
self.status = SalesOrderStatus.SHIPPED.value self.status = SalesOrderStatus.SHIPPED.value

View File

@ -48,14 +48,14 @@ from InvenTree.serializers import (
InvenTreeModelSerializer, InvenTreeModelSerializer,
InvenTreeMoneySerializer, InvenTreeMoneySerializer,
) )
from InvenTree.status_codes import ( from order.status_codes import (
PurchaseOrderStatusGroups, PurchaseOrderStatusGroups,
ReturnOrderLineStatus, ReturnOrderLineStatus,
ReturnOrderStatus, ReturnOrderStatus,
SalesOrderStatusGroups, SalesOrderStatusGroups,
StockStatus,
) )
from part.serializers import PartBriefSerializer from part.serializers import PartBriefSerializer
from stock.status_codes import StockStatus
from users.serializers import OwnerSerializer 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 common.notifications
import InvenTree.helpers_model import InvenTree.helpers_model
import order.models import order.models
from InvenTree.status_codes import PurchaseOrderStatusGroups, SalesOrderStatusGroups
from InvenTree.tasks import ScheduledTask, scheduled_task from InvenTree.tasks import ScheduledTask, scheduled_task
from order.status_codes import PurchaseOrderStatusGroups, SalesOrderStatusGroups
from plugin.events import trigger_event from plugin.events import trigger_event

View File

@ -16,18 +16,18 @@ from rest_framework import status
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
from common.settings import currency_codes from common.settings import currency_codes
from company.models import Company, SupplierPart, SupplierPriceBreak 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, PurchaseOrderStatus,
ReturnOrderLineStatus, ReturnOrderLineStatus,
ReturnOrderStatus, ReturnOrderStatus,
SalesOrderStatus, SalesOrderStatus,
SalesOrderStatusGroups, SalesOrderStatusGroups,
StockStatus,
) )
from InvenTree.unit_test import InvenTreeAPITestCase
from order import models
from part.models import Part from part.models import Part
from stock.models import StockItem from stock.models import StockItem
from stock.status_codes import StockStatus
from users.models import Owner from users.models import Owner
@ -1476,6 +1476,77 @@ class SalesOrderTest(OrderTest):
expected_fn=f'InvenTree_SalesOrders.{fmt}', 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): class SalesOrderLineItemTest(OrderTest):
"""Tests for the SalesOrderLineItem API.""" """Tests for the SalesOrderLineItem API."""

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ from rest_framework import serializers
import order.models import order.models
import stock.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 from plugin.builtin.barcodes.inventree_barcode import InvenTreeInternalBarcodePlugin

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
import django.core.validators import django.core.validators
from django.db import migrations, models from django.db import migrations, models
import InvenTree.status_codes import stock.status_codes
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -15,6 +15,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='stockitem', model_name='stockitem',
name='status', 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 import report.models
from company import models as CompanyModels from company import models as CompanyModels
from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField
from InvenTree.status_codes import ( from order.status_codes import SalesOrderStatusGroups
SalesOrderStatusGroups,
StockHistoryCode,
StockStatus,
StockStatusGroups,
)
from part import models as PartModels from part import models as PartModels
from plugin.events import trigger_event from plugin.events import trigger_event
from stock.generators import generate_batch_code from stock.generators import generate_batch_code
from stock.status_codes import StockHistoryCode, StockStatus, StockStatusGroups
from users.models import Owner from users.models import Owner
logger = logging.getLogger('inventree') logger = logging.getLogger('inventree')
@ -348,7 +344,7 @@ class StockItem(
stocktake_user: User that performed the most recent stocktake stocktake_user: User that performed the most recent stocktake
review_needed: Flag if StockItem needs review review_needed: Flag if StockItem needs review
delete_on_deplete: If True, StockItem will be deleted when the stock level gets to zero 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 notes: Extra notes field
build: Link to a Build (if this stock item was created from a build) 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") 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 company.models
import InvenTree.helpers import InvenTree.helpers
import InvenTree.serializers import InvenTree.serializers
import InvenTree.status_codes
import order.models import order.models
import part.filters as part_filters import part.filters as part_filters
import part.models as part_models import part.models as part_models
import stock.filters import stock.filters
import stock.status_codes
from company.serializers import SupplierPartSerializer from company.serializers import SupplierPartSerializer
from InvenTree.serializers import InvenTreeCurrencySerializer, InvenTreeDecimalField from InvenTree.serializers import InvenTreeCurrencySerializer, InvenTreeDecimalField
from part.serializers import PartBriefSerializer, PartTestTemplateSerializer from part.serializers import PartBriefSerializer, PartTestTemplateSerializer
@ -925,8 +925,8 @@ class StockChangeStatusSerializer(serializers.Serializer):
return items return items
status = serializers.ChoiceField( status = serializers.ChoiceField(
choices=InvenTree.status_codes.StockStatus.items(), choices=stock.status_codes.StockStatus.items(),
default=InvenTree.status_codes.StockStatus.OK.value, default=stock.status_codes.StockStatus.OK.value,
label=_('Status'), label=_('Status'),
) )
@ -973,7 +973,7 @@ class StockChangeStatusSerializer(serializers.Serializer):
transaction_notes.append( transaction_notes.append(
StockItemTracking( StockItemTracking(
item=item, item=item,
tracking_type=InvenTree.status_codes.StockHistoryCode.EDITED.value, tracking_type=stock.status_codes.StockHistoryCode.EDITED.value,
date=now, date=now,
deltas=deltas, deltas=deltas,
user=user, user=user,
@ -1419,7 +1419,7 @@ def stock_item_adjust_status_options():
In particular, include a Null option for the status field. 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): 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 company.models
import part.models import part.models
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
from InvenTree.status_codes import StockHistoryCode, StockStatus
from InvenTree.unit_test import InvenTreeAPITestCase from InvenTree.unit_test import InvenTreeAPITestCase
from part.models import Part, PartTestTemplate from part.models import Part, PartTestTemplate
from stock.models import ( from stock.models import (
@ -26,6 +25,7 @@ from stock.models import (
StockLocation, StockLocation,
StockLocationType, StockLocationType,
) )
from stock.status_codes import StockHistoryCode, StockStatus
class StockAPITestCase(InvenTreeAPITestCase): class StockAPITestCase(InvenTreeAPITestCase):

View File

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

View File

@ -10,10 +10,10 @@ from django.test import override_settings
from build.models import Build from build.models import Build
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
from company.models import Company from company.models import Company
from InvenTree.status_codes import StockHistoryCode
from InvenTree.unit_test import InvenTreeTestCase from InvenTree.unit_test import InvenTreeTestCase
from order.models import SalesOrder from order.models import SalesOrder
from part.models import Part, PartTestTemplate from part.models import Part, PartTestTemplate
from stock.status_codes import StockHistoryCode
from .models import StockItem, StockItemTestResult, StockItemTracking, StockLocation 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_REQUIRE_RESPONSIBLE" %}
{% include "InvenTree/settings/setting.html" with key="SALESORDER_DEFAULT_SHIPMENT" icon="fa-truck-loading" %} {% 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_EDIT_COMPLETED_ORDERS" icon='fa-edit' %}
{% include "InvenTree/settings/setting.html" with key="SALESORDER_SHIP_COMPLETE" %}
</tbody> </tbody>
</table> </table>

View File

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