Files
shopship/apps/orders/api/services.py
T

119 lines
5.6 KiB
Python
Raw Normal View History

import random
2026-08-24 15:54:08 +03:30
import re
import string
2026-08-24 15:54:08 +03:30
import uuid
from django.db import transaction
from apps.account.models import User
from apps.banker.models import Contact
from apps.messaging.api.services import BulkMessageService
from apps.messaging.models import Channel, MessageRecipient
2026-08-24 15:54:08 +03:30
from apps.orders.models import Order, OrderStage, OrderStageLog
_FIELD_PATTERN = re.compile(r"\{(.*?)\}")
_TRACKING_CODE_ALPHABET = string.ascii_uppercase + string.digits
def generate_tracking_code() -> str:
"""کد پیگیریِ کوتاه و خوانا برای مشتری — نه شماره‌ی پستیِ واقعی (آن را
وقتی از پستخانه گرفت دستی جایگزین می‌کند)، بلکه یک مرجع سریع تا از
همان مرحله‌ی اول سفارش یک کدِ پیگیری داشته باشد."""
return "TRK-" + "".join(random.choices(_TRACKING_CODE_ALPHABET, k=6))
2026-08-24 15:54:08 +03:30
class OrderStageService:
@staticmethod
def ensure_default_stage(user: User) -> None:
"""اگر کاربر هنوز هیچ مرحله‌ای تعریف نکرده، یک مرحله‌ی پیش‌فرض
می‌سازد تا لیستِ مراحل هیچ‌وقت کاملا خالی نباشد و اولین سفارش از
همان روزِ اول قابل‌پیگیری باشد. بدونِ قالبِ پیامک (کاربر خودش بعدا
یکی وصل می‌کند) — چون قالبِ پیشِ‌فرضی که خودمان بسازیم به احتمال
زیاد با کسب‌وکارش نمی‌خواند."""
if OrderStage.objects.filter(user=user).exists():
return
OrderStage.objects.create(
id=str(uuid.uuid4()),
user=user,
label="ثبت سفارش",
sort_order=0,
)
2026-08-24 15:54:08 +03:30
@staticmethod
def _render(order: Order, stage: OrderStage, extra_input_value: str = "") -> str:
body = stage.template.body if stage.template else ""
values = {
"نام": order.customer_name,
"شماره": order.customer_phone,
"کد پیگیری": order.tracking_code,
"مرحله": stage.label,
"ورودی": extra_input_value,
}
return _FIELD_PATTERN.sub(lambda m: values.get(m.group(1), m.group(0)), body)
@staticmethod
def _upsert_contact(user: User, order: Order) -> Contact:
contact = Contact.objects.filter(user=user, phone=order.customer_phone).first()
if contact:
return contact
return Contact.objects.create(
id=str(uuid.uuid4()), user=user, name=order.customer_name, phone=order.customer_phone
)
@staticmethod
@transaction.atomic
def advance(user: User, order_id: str, extra_input_value: str = "") -> tuple[Order, bool]:
"""برمی‌گرداند: (سفارشِ به‌روزشده، آیا پیامکِ این مرحله واقعا ارسال
شد). قبلا موفقیتِ ثبتِ مرحله را با موفقیتِ ارسالِ پیامک قاطی می‌کرد؛
الان اگر قالب متن نداشته باشد یا کاوه‌نگار ارسال را رد کند، مرحله
همچنان ثبت می‌شود ولی caller می‌تواند به کاربر بگوید که پیامک نرفت
(مثلا چون SENDER_LINE در تنظیمات سرور خالی است)."""
2026-08-24 15:54:08 +03:30
order = Order.objects.select_for_update().get(pk=order_id, user=user)
if order.is_completed:
raise ValueError("این سفارش قبلا به آخرین مرحله رسیده است.")
stages = list(OrderStage.objects.filter(user=user).order_by("sort_order"))
if not stages:
raise ValueError("اول باید حداقل یک مرحله برای پیگیری سفارش تعریف کنی.")
if order.current_stage_id is None:
next_stage = stages[0]
else:
current_index = next((i for i, s in enumerate(stages) if s.id == order.current_stage_id), None)
if current_index is None or current_index + 1 >= len(stages):
raise ValueError("این سفارش قبلا به آخرین مرحله رسیده است.")
next_stage = stages[current_index + 1]
extra_input_value = (extra_input_value or "").strip()
if next_stage.requires_extra_input and not extra_input_value:
label = next_stage.extra_input_label or "ورودی این مرحله"
raise ValueError(f"برای این مرحله باید «{label}» را وارد کنی.")
contact = OrderStageService._upsert_contact(user, order)
rendered = OrderStageService._render(order, next_stage, extra_input_value)
message_sent = False
2026-08-24 15:54:08 +03:30
if rendered:
bulk = BulkMessageService.create_and_dispatch(
2026-08-24 15:54:08 +03:30
user=user,
channels=[Channel.SMS],
contact_ids=[contact.id],
body_override=rendered,
)
message_sent = bulk.recipients.filter(
channel=Channel.SMS, status=MessageRecipient.Status.SENT
).exists()
2026-08-24 15:54:08 +03:30
order.current_stage = next_stage
order.is_completed = next_stage.id == stages[-1].id
order.save(update_fields=["current_stage", "is_completed", "updated_at"])
OrderStageLog.objects.create(
id=str(uuid.uuid4()),
order=order,
stage=next_stage,
stage_label=next_stage.label,
message_body=rendered,
extra_input_value=extra_input_value,
)
return order, message_sent