from __future__ import annotations
from typing import TYPE_CHECKING, Any
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
if TYPE_CHECKING: # pragma: no cover
from salesman.orders.models import BaseOrder
[docs]class BaseOrderStatus(models.TextChoices):
"""
Base order status enum, actuall choices must extend this class.
"""
[docs] @classmethod
def get_payable(cls) -> list[str]:
"""
Returns list of statuses from which an order is eligible for payment.
"""
return []
[docs] @classmethod
def get_transitions(cls) -> dict[str, list[str]]:
"""
Returns a dict of statuses with their transitions.
If not specified for status, any transition is valid.
"""
return {}
[docs] @classmethod
def validate_transition(cls, status: str, order: BaseOrder) -> str:
"""
Validate a given status transition for the order.
By default check status is defined in transitions.
Args:
status (str): New status
order (Order): Order instance
Raises:
ValidationError: If transition not valid
Returns:
str: Validated status
"""
transitions = cls.get_transitions().get(order.status, [status])
transitions.append(order.status)
if status not in transitions:
current, new = cls[order.status].label, cls[status].label # type: ignore
msg = _(f"Can't change order with status '{current}' to '{new}'.")
raise ValidationError(msg)
return status
[docs]class OrderStatus(BaseOrderStatus):
"""
Default order choices enum. Can be overriden by providing a
dotted path to a class in ``SALESMAN_ORDER_STATUS`` setting.
Required choices are NEW, CREATED, COMPLETED and REFUNDED.
"""
NEW = "NEW", _("New") # Order with reference created, items are in the basket.
CREATED = "CREATED", _("Created") # Created with items and pending payment.
HOLD = "HOLD", _("Hold") # Stock reduced but still awaiting payment.
FAILED = "FAILED", _("Failed") # Payment failed, retry is available.
CANCELLED = "CANCELLED", _("Cancelled") # Cancelled by seller, stock increased.
PROCESSING = "PROCESSING", _("Processing") # Payment confirmed, processing order.
SHIPPED = "SHIPPED", _("Shipped") # Shipped to customer.
COMPLETED = "COMPLETED", _("Completed") # Completed and received by customer.
REFUNDED = "REFUNDED", _("Refunded") # Fully refunded by seller.
@classmethod
def get_payable(cls) -> list[Any]:
"""
Returns default payable statuses.
"""
return [cls.CREATED, cls.HOLD, cls.FAILED]
@classmethod
def get_transitions(cls) -> dict[str, list[Any]]:
"""
Returns default status transitions.
"""
return {
"NEW": [cls.CREATED],
"CREATED": [cls.HOLD, cls.FAILED, cls.CANCELLED, cls.PROCESSING],
"HOLD": [cls.FAILED, cls.CANCELLED, cls.PROCESSING],
"FAILED": [cls.CANCELLED, cls.PROCESSING],
"CANCELLED": [],
"PROCESSING": [cls.SHIPPED, cls.COMPLETED, cls.REFUNDED],
"SHIPPED": [cls.COMPLETED, cls.REFUNDED],
"COMPLETED": [cls.REFUNDED],
"REFUNDED": [],
}