This commit is contained in:
@@ -6,7 +6,7 @@ from django.urls import path, reverse
|
||||
from django.shortcuts import redirect
|
||||
from unfold.admin import ModelAdmin, TabularInline
|
||||
from unfold.decorators import display
|
||||
from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, VCCourse, ProductFeature, CommissionLog, WeChatUser, Distributor, Withdrawal, ServiceOrder, CourseEnrollment
|
||||
from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, VCCourse, ProductFeature, CommissionLog, WeChatUser, Distributor, Withdrawal, ServiceOrder, CourseEnrollment, AdminPhoneNumber
|
||||
import qrcode
|
||||
from io import BytesIO
|
||||
import base64
|
||||
@@ -478,3 +478,9 @@ class WithdrawalAdmin(ModelAdmin):
|
||||
'fields': ('created_at', 'updated_at')
|
||||
}),
|
||||
)
|
||||
|
||||
@admin.register(AdminPhoneNumber)
|
||||
class AdminPhoneNumberAdmin(ModelAdmin):
|
||||
list_display = ('name', 'phone_number', 'is_active', 'created_at')
|
||||
list_filter = ('is_active',)
|
||||
search_fields = ('name', 'phone_number')
|
||||
|
||||
@@ -3,3 +3,7 @@ from django.apps import AppConfig
|
||||
|
||||
class ShopConfig(AppConfig):
|
||||
name = 'shop'
|
||||
verbose_name = "商城管理"
|
||||
|
||||
def ready(self):
|
||||
import shop.signals
|
||||
|
||||
27
backend/shop/migrations/0031_adminphonenumber.py
Normal file
27
backend/shop/migrations/0031_adminphonenumber.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 6.0.1 on 2026-02-16 11:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('shop', '0030_alter_esp32config_options_alter_service_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AdminPhoneNumber',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50, verbose_name='管理员姓名')),
|
||||
('phone_number', models.CharField(max_length=20, verbose_name='手机号')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='是否接收通知')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '管理员通知手机号',
|
||||
'verbose_name_plural': '管理员通知手机号',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -402,3 +402,21 @@ class CourseEnrollment(models.Model):
|
||||
class Meta:
|
||||
verbose_name = "课程报名"
|
||||
verbose_name_plural = "课程报名管理"
|
||||
|
||||
|
||||
class AdminPhoneNumber(models.Model):
|
||||
"""
|
||||
管理员通知手机号配置
|
||||
用于接收订单支付成功等重要通知
|
||||
"""
|
||||
name = models.CharField(max_length=50, verbose_name="管理员姓名")
|
||||
phone_number = models.CharField(max_length=20, verbose_name="手机号")
|
||||
is_active = models.BooleanField(default=True, verbose_name="是否接收通知")
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} - {self.phone_number}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "管理员通知手机号"
|
||||
verbose_name_plural = "管理员通知手机号"
|
||||
|
||||
54
backend/shop/signals.py
Normal file
54
backend/shop/signals.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from django.db.models.signals import pre_save, post_save
|
||||
from django.dispatch import receiver
|
||||
from .models import Order
|
||||
from .sms_utils import notify_admins_order_paid, notify_user_order_paid, notify_user_order_shipped
|
||||
|
||||
@receiver(pre_save, sender=Order)
|
||||
def track_order_changes(sender, instance, **kwargs):
|
||||
"""
|
||||
在保存之前检查状态变化
|
||||
"""
|
||||
if instance.pk:
|
||||
try:
|
||||
old_instance = Order.objects.get(pk=instance.pk)
|
||||
|
||||
# 检查是否从非支付状态变为支付状态
|
||||
if old_instance.status != 'paid' and instance.status == 'paid':
|
||||
instance._was_paid = True
|
||||
|
||||
# 检查是否发货 (状态变为 shipped 且有单号)
|
||||
# 或者已经是 shipped 状态但刚填入单号
|
||||
if instance.status == 'shipped' and instance.tracking_number:
|
||||
if old_instance.status != 'shipped' or not old_instance.tracking_number:
|
||||
instance._was_shipped = True
|
||||
|
||||
except Order.DoesNotExist:
|
||||
pass
|
||||
|
||||
@receiver(post_save, sender=Order)
|
||||
def send_order_notifications(sender, instance, created, **kwargs):
|
||||
"""
|
||||
在保存之后发送通知
|
||||
"""
|
||||
if created:
|
||||
return
|
||||
|
||||
# 1. 处理支付成功通知
|
||||
if getattr(instance, '_was_paid', False):
|
||||
try:
|
||||
print(f"订单 {instance.id} 支付成功,触发短信通知流程...")
|
||||
notify_admins_order_paid(instance)
|
||||
notify_user_order_paid(instance)
|
||||
# 清除标记防止重复发送 (虽然实例通常是新的,但保险起见)
|
||||
instance._was_paid = False
|
||||
except Exception as e:
|
||||
print(f"发送支付成功短信失败: {str(e)}")
|
||||
|
||||
# 2. 处理发货通知
|
||||
if getattr(instance, '_was_shipped', False):
|
||||
try:
|
||||
print(f"订单 {instance.id} 已发货,触发短信通知流程...")
|
||||
notify_user_order_shipped(instance)
|
||||
instance._was_shipped = False
|
||||
except Exception as e:
|
||||
print(f"发送发货短信失败: {str(e)}")
|
||||
98
backend/shop/sms_utils.py
Normal file
98
backend/shop/sms_utils.py
Normal file
@@ -0,0 +1,98 @@
|
||||
import requests
|
||||
import threading
|
||||
import json
|
||||
from .models import AdminPhoneNumber
|
||||
|
||||
# SMS API Configuration
|
||||
SMS_API_URL = "https://data.tangledup-ai.com/api/send-sms/diy"
|
||||
SIGN_NAME = "叠加态科技云南"
|
||||
|
||||
def send_sms(phone_number, template_code, template_params):
|
||||
"""
|
||||
通用发送短信函数 (异步)
|
||||
"""
|
||||
def _send():
|
||||
try:
|
||||
payload = {
|
||||
"phone_number": phone_number,
|
||||
"template_code": template_code,
|
||||
"sign_name": SIGN_NAME,
|
||||
"additionalProp1": template_params
|
||||
}
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"accept": "application/json"
|
||||
}
|
||||
# print(f"Sending SMS to {phone_number} with params: {template_params}")
|
||||
response = requests.post(SMS_API_URL, json=payload, headers=headers, timeout=15)
|
||||
print(f"SMS Response for {phone_number}: {response.status_code} - {response.text}")
|
||||
except Exception as e:
|
||||
print(f"发送短信异常: {str(e)}")
|
||||
|
||||
threading.Thread(target=_send).start()
|
||||
|
||||
def notify_admins_order_paid(order):
|
||||
"""
|
||||
通知管理员有新订单支付成功
|
||||
"""
|
||||
# 获取激活的管理员手机号,最多3个
|
||||
admins = AdminPhoneNumber.objects.filter(is_active=True)[:3]
|
||||
if not admins.exists():
|
||||
print("未配置管理员手机号,跳过管理员通知")
|
||||
return
|
||||
|
||||
# 构造参数
|
||||
# 模板变量: consignee, order_id, address
|
||||
# order_id 格式要求: "订单编号/电话号码"
|
||||
params = {
|
||||
"consignee": order.customer_name or "未填写",
|
||||
"order_id": f"{order.id}/{order.phone_number}",
|
||||
"address": order.shipping_address or "无地址"
|
||||
}
|
||||
|
||||
print(f"准备发送管理员通知,共 {admins.count()} 人")
|
||||
for admin in admins:
|
||||
send_sms(admin.phone_number, "SMS_501735480", params)
|
||||
|
||||
def notify_user_order_paid(order):
|
||||
"""
|
||||
通知用户下单成功 (支付成功)
|
||||
"""
|
||||
if not order.phone_number:
|
||||
return
|
||||
|
||||
# 模板变量: user_nick, address
|
||||
# 尝试获取用户昵称,如果没有则使用收货人姓名
|
||||
user_nick = order.customer_name
|
||||
if order.wechat_user and order.wechat_user.nickname:
|
||||
user_nick = order.wechat_user.nickname
|
||||
|
||||
params = {
|
||||
"user_nick": user_nick or "用户",
|
||||
"address": order.shipping_address or "无地址"
|
||||
}
|
||||
|
||||
print(f"准备发送用户支付成功通知: {order.phone_number}")
|
||||
send_sms(order.phone_number, "SMS_501850529", params)
|
||||
|
||||
def notify_user_order_shipped(order):
|
||||
"""
|
||||
通知用户已发货
|
||||
"""
|
||||
if not order.phone_number:
|
||||
return
|
||||
|
||||
# 模板变量: user_nick, address, delivery_company, order_id (这里指快递单号)
|
||||
user_nick = order.customer_name
|
||||
if order.wechat_user and order.wechat_user.nickname:
|
||||
user_nick = order.wechat_user.nickname
|
||||
|
||||
params = {
|
||||
"user_nick": user_nick or "用户",
|
||||
"address": order.shipping_address or "无地址",
|
||||
"delivery_company": order.courier_name or "快递",
|
||||
"order_id": order.tracking_number or "暂无单号"
|
||||
}
|
||||
|
||||
print(f"准备发送用户发货通知: {order.phone_number}")
|
||||
send_sms(order.phone_number, "SMS_501650557", params)
|
||||
Reference in New Issue
Block a user