fix error in gitignore

This commit is contained in:
2026-09-24 12:32:59 +03:30
commit 86c117ee2a
165 changed files with 5963 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
"""Order lifecycle service — ties reservations, weighing, dynamic step
selection, pricing and confirmation together. All financial values are
computed by the backend and stored as immutable snapshots."""
from decimal import Decimal
from django.db import transaction
from django.utils import timezone
from apps.core.errors import ApiError
from apps.orders.models import ProcessingOrder
from apps.pricing.engine import PricingEngine
from apps.workflows.models import Workflow
class OrderService:
def _active_workflow(self) -> Workflow:
workflow = Workflow.objects.filter(status="active", active_version__isnull=False).first()
if workflow is None:
raise ApiError("NO_ACTIVE_WORKFLOW", "No active workflow is configured.")
return workflow
@transaction.atomic
def create_from_reservation(self, reservation, workflow=None) -> ProcessingOrder:
if ProcessingOrder.objects.filter(reservation=reservation).exists():
raise ApiError("ORDER_EXISTS", "An order already exists for this reservation.")
if workflow is None:
workflow = self._active_workflow()
return ProcessingOrder.objects.create(
customer=reservation.customer,
reservation=reservation,
workflow_version=workflow.active_version,
)
@transaction.atomic
def record_weighing(self, order, gross, tare) -> ProcessingOrder:
order = ProcessingOrder.objects.select_for_update().get(pk=order.pk)
if order.net_weight_kg is not None:
raise ApiError("WEIGHING_IMMUTABLE", "Net weight is already recorded and immutable.")
gross = Decimal(str(gross))
tare = Decimal(str(tare))
if gross < tare:
raise ApiError("INVALID_WEIGHT", "Gross weight cannot be less than tare weight.")
order.gross_weight_kg = gross
order.tare_weight_kg = tare
order.net_weight_kg = gross - tare
order.save(update_fields=["gross_weight_kg", "tare_weight_kg", "net_weight_kg"])
return order
@transaction.atomic
def select_steps(self, order, step_keys) -> ProcessingOrder:
order = ProcessingOrder.objects.select_for_update().get(pk=order.pk)
if order.net_weight_kg is None:
raise ApiError("WEIGHING_REQUIRED", "Record weighing before selecting processing.")
version = order.workflow_version
steps = list(version.steps.filter(key__in=step_keys, enabled=True))
keys = {s.key for s in steps}
for required in version.steps.filter(required=True):
if required.key not in keys:
raise ApiError(
"REQUIRED_STEP_MISSING",
f"Required step '{required.key}' must be selected.",
{"step": required.key},
)
order.selected_step_keys = sorted(keys)
order.price_snapshot = PricingEngine().calculate(order.net_weight_kg, steps, order.currency)
order.status = ProcessingOrder.Status.AWAITING_CONFIRMATION
order.save(update_fields=["selected_step_keys", "price_snapshot", "status"])
return order
@transaction.atomic
def confirm(self, order, terms_accepted=True) -> ProcessingOrder:
order = ProcessingOrder.objects.select_for_update().get(pk=order.pk)
if order.status != ProcessingOrder.Status.AWAITING_CONFIRMATION:
raise ApiError("ORDER_NOT_CONFIRMABLE", "Order is not awaiting confirmation.")
if not terms_accepted:
raise ApiError("TERMS_REQUIRED", "Terms must be accepted.")
order.status = ProcessingOrder.Status.CONFIRMED
order.confirmed_at = timezone.now()
order.terms_accepted = True
order.save(update_fields=["status", "confirmed_at", "terms_accepted"])
return order