1
0
forked from erp-dev/erp

feat: big

This commit is contained in:
2026-07-01 11:51:13 +08:00
parent 7c9b77afb0
commit 5170700234
28 changed files with 933 additions and 37 deletions

View File

@@ -5,8 +5,8 @@ from mission.models import Mission, MissionCategory, MissionParticipant, Mission
@admin.register(MissionCategory)
class MissionCategoryAdmin(admin.ModelAdmin):
list_display = ["id", "merchant", "name", "payload_processor", "created_at"]
list_filter = ["merchant", "payload_processor", "created_at"]
list_display = ["id", "merchant", "name", "payload_processor", "speech_enabled", "created_at"]
list_filter = ["merchant", "payload_processor", "speech_enabled", "created_at"]
search_fields = ["name"]
readonly_fields = ["created_at", "updated_at"]

View File

@@ -1,5 +1,6 @@
import logging
from mission.notifications import enqueue_mission_created_speech
from mission.payload_processors import apply_mission_payload_processor
from notifier.models import NotificationEventKeyEnum
from notifier.services import enqueue_notification_event
@@ -74,6 +75,7 @@ def on_mission_created(sender, instance, created_by=None, **kwargs):
merchant_id=instance.merchant_id,
payload=payload,
)
enqueue_mission_created_speech(mission=instance)
def on_mission_replied(sender, instance, mission=None, responder=None, **kwargs):

View File

@@ -0,0 +1,16 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("mission", "0007_missioncategory_payload_processor"),
]
operations = [
migrations.AddField(
model_name="missioncategory",
name="speech_enabled",
field=models.BooleanField(default=False, verbose_name="是否播报"),
),
]

View File

@@ -28,6 +28,7 @@ class MissionCategory(ModelBase):
choices=MissionPayloadProcessorEnum.choices,
verbose_name="payload 增强器",
)
speech_enabled = models.BooleanField(default=False, verbose_name="是否播报")
def __str__(self):
return self.name

36
mission/notifications.py Normal file
View File

@@ -0,0 +1,36 @@
import logging
from django.conf import settings
logger = logging.getLogger(__name__)
def enqueue_mission_created_speech(*, mission) -> None:
if not getattr(settings, "SPEECH_ENABLED", True):
return
if not getattr(settings, "MISSION_SPEECH_NOTIFY_ENABLED", True):
return
category = getattr(mission, "category", None)
if not getattr(category, "speech_enabled", False):
return
text = _build_mission_created_speech_text(mission=mission)
try:
from mission.tasks import notify_mission_speech
notify_mission_speech.delay(text=text)
except Exception:
logger.exception("[mission.notifications] 投递任务语音播报任务失败(已忽略)")
def _build_mission_created_speech_text(*, mission) -> str:
category_name = getattr(getattr(mission, "category", None), "name", "任务") or "任务"
creator_name = getattr(getattr(mission, "creator", None), "name", "") or ""
prefix = f"{creator_name}创建了" if creator_name else "创建了"
description = (getattr(mission, "description", "") or "").strip()
if len(description) > 60:
description = f"{description[:60]}..."
if description:
return f"{prefix}{category_name}任务,{description}"
return f"{prefix}{category_name}任务"

27
mission/tasks.py Normal file
View File

@@ -0,0 +1,27 @@
import logging
from typing import Any, Dict
from celery import shared_task
logger = logging.getLogger(__name__)
@shared_task(bind=True)
def notify_mission_speech(self, *, text: str) -> Dict[str, Any]:
try:
from flower.utils import play_speech
response = play_speech(text=text, timeout_seconds=3.0)
return {
"status": "sent",
"task_id": self.request.id,
"status_code": response.status_code,
"raw": response.raw,
}
except Exception as exc:
logger.exception("[mission.tasks] 任务语音播报失败(已忽略)")
return {
"status": "error",
"task_id": self.request.id,
"error": str(exc),
}

View File

@@ -580,6 +580,28 @@ class MissionModelTestCase(TestCase):
self.assertEqual(mock_enqueue.call_args.kwargs["merchant_id"], self.merchant.id)
self.assertEqual(mock_enqueue.call_args.kwargs["payload"]["mission_id"], mission.id)
@patch("mission.tasks.notify_mission_speech.delay")
@patch("mission.handlers.enqueue_notification_event")
def test_mission_created_speech_requires_category_enabled(self, mock_enqueue, mock_speech_delay):
with self.captureOnCommitCallbacks(execute=True):
create_mission(creator=self.creator, description="不播报任务")
mock_enqueue.assert_called_once()
mock_speech_delay.assert_not_called()
@patch("mission.tasks.notify_mission_speech.delay")
@patch("mission.handlers.enqueue_notification_event")
def test_mission_created_speech_enqueues_when_category_enabled(self, mock_enqueue, mock_speech_delay):
self.default_category.speech_enabled = True
self.default_category.save(update_fields=["speech_enabled", "updated_at"])
with self.captureOnCommitCallbacks(execute=True):
mission = create_mission(creator=self.creator, description="需要播报任务")
mock_enqueue.assert_called_once()
mock_speech_delay.assert_called_once()
self.assertIn(str(mission.description), mock_speech_delay.call_args.kwargs["text"])
@patch("mission.handlers.enqueue_notification_event")
def test_mission_created_handler_applies_category_payload_processor(self, mock_enqueue):
self.default_category.payload_processor = "structured_description_v1"