This commit is contained in:
@@ -7,6 +7,7 @@ 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, AdminPhoneNumber
|
||||
from .admin_actions import export_orders_excel
|
||||
import qrcode
|
||||
from io import BytesIO
|
||||
import base64
|
||||
@@ -402,6 +403,7 @@ class OrderAdmin(ModelAdmin):
|
||||
list_filter = ('status', 'salesperson', 'distributor', 'created_at')
|
||||
search_fields = ('id', 'customer_name', 'phone_number', 'wechat_trade_no')
|
||||
readonly_fields = ('total_price', 'created_at', 'wechat_trade_no')
|
||||
actions = [export_orders_excel]
|
||||
|
||||
def get_item_name(self, obj):
|
||||
if obj.config:
|
||||
|
||||
170
backend/shop/admin_actions.py
Normal file
170
backend/shop/admin_actions.py
Normal file
@@ -0,0 +1,170 @@
|
||||
import csv
|
||||
import json
|
||||
import datetime
|
||||
from django.http import HttpResponse
|
||||
from django.utils.encoding import escape_uri_path
|
||||
|
||||
def flatten_json(y):
|
||||
"""
|
||||
Flatten a nested json object
|
||||
"""
|
||||
out = {}
|
||||
|
||||
def flatten(x, name=''):
|
||||
if type(x) is dict:
|
||||
for a in x:
|
||||
flatten(x[a], name + a + '_')
|
||||
elif type(x) is list:
|
||||
i = 0
|
||||
for a in x:
|
||||
flatten(a, name + str(i) + '_')
|
||||
i += 1
|
||||
else:
|
||||
out[name[:-1]] = x
|
||||
|
||||
flatten(y)
|
||||
return out
|
||||
|
||||
def get_order_signup_keys(queryset):
|
||||
"""
|
||||
Collect all unique keys from the signup_info JSON of ActivitySignups related to the Order queryset
|
||||
"""
|
||||
keys = set()
|
||||
for order in queryset:
|
||||
# Check if order has related activity signups
|
||||
# Assuming reverse relation name is 'activity_signups' based on ActivitySignup model
|
||||
signups = order.activity_signups.all()
|
||||
for signup in signups:
|
||||
if signup.signup_info and isinstance(signup.signup_info, dict):
|
||||
flat_info = flatten_json(signup.signup_info)
|
||||
keys.update(flat_info.keys())
|
||||
return sorted(list(keys))
|
||||
|
||||
def get_order_row_data(order):
|
||||
"""
|
||||
Build a dictionary of all exportable data for a single Order object
|
||||
"""
|
||||
data = {}
|
||||
|
||||
# 1. 订单基础信息
|
||||
data['订单ID'] = str(order.id)
|
||||
data['商户订单号'] = order.out_trade_no or ''
|
||||
data['微信支付单号'] = order.wechat_trade_no or ''
|
||||
data['订单状态'] = order.get_status_display()
|
||||
data['订单总价'] = str(order.total_price)
|
||||
data['购买数量'] = str(order.quantity)
|
||||
data['创建时间'] = order.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 商品信息
|
||||
if order.config:
|
||||
data['商品类型'] = '硬件'
|
||||
data['商品名称'] = order.config.name
|
||||
elif order.course:
|
||||
data['商品类型'] = '课程'
|
||||
data['商品名称'] = order.course.title
|
||||
elif order.activity:
|
||||
data['商品类型'] = '活动'
|
||||
data['商品名称'] = order.activity.title
|
||||
else:
|
||||
data['商品类型'] = '未知'
|
||||
data['商品名称'] = '未知'
|
||||
|
||||
# 2. 发货/收货信息
|
||||
data['收货人姓名'] = order.customer_name
|
||||
data['收货电话'] = order.phone_number
|
||||
data['收货地址'] = order.shipping_address
|
||||
data['快递公司'] = order.courier_name or ''
|
||||
data['快递单号'] = order.tracking_number or ''
|
||||
|
||||
# 3. 下单用户信息
|
||||
if order.wechat_user:
|
||||
data['用户昵称'] = order.wechat_user.nickname
|
||||
data['用户ID'] = str(order.wechat_user.id)
|
||||
data['用户OpenID'] = order.wechat_user.openid
|
||||
data['用户绑定手机'] = order.wechat_user.phone_number or ''
|
||||
else:
|
||||
data['用户昵称'] = 'Unknown'
|
||||
data['用户ID'] = ''
|
||||
data['用户OpenID'] = ''
|
||||
data['用户绑定手机'] = ''
|
||||
|
||||
# 4. 销售/分销信息
|
||||
data['销售员'] = order.salesperson.name if order.salesperson else ''
|
||||
data['分销员'] = order.distributor.user.nickname if order.distributor else ''
|
||||
|
||||
return data
|
||||
|
||||
def get_order_signup_data(order):
|
||||
"""
|
||||
Get flattened signup info for the order.
|
||||
If multiple signups exist, we only take the first one for simplicity in flat export,
|
||||
or we could try to merge them but keys might conflict.
|
||||
Given the context, usually 1 order -> 1 signup for activity.
|
||||
"""
|
||||
flat_info = {}
|
||||
signup = order.activity_signups.first()
|
||||
if signup and signup.signup_info and isinstance(signup.signup_info, dict):
|
||||
flat_info = flatten_json(signup.signup_info)
|
||||
return flat_info
|
||||
|
||||
def export_orders_excel(modeladmin, request, queryset):
|
||||
"""
|
||||
Export selected orders to Excel, including related Activity Signup JSON info
|
||||
"""
|
||||
try:
|
||||
from openpyxl import Workbook
|
||||
except ImportError:
|
||||
modeladmin.message_user(request, "请先安装 openpyxl 库以使用 Excel 导出功能", level='error')
|
||||
return
|
||||
|
||||
opts = modeladmin.model._meta
|
||||
filename = f"Orders_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
|
||||
|
||||
response = HttpResponse(
|
||||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
)
|
||||
response['Content-Disposition'] = f'attachment; filename={escape_uri_path(filename)}'
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "订单导出"
|
||||
|
||||
# Fixed headers
|
||||
fixed_headers = [
|
||||
'订单ID', '商户订单号', '微信支付单号', '订单状态', '订单总价', '购买数量', '创建时间',
|
||||
'商品类型', '商品名称',
|
||||
'收货人姓名', '收货电话', '收货地址', '快递公司', '快递单号',
|
||||
'用户ID', '用户昵称', '用户OpenID', '用户绑定手机',
|
||||
'销售员', '分销员'
|
||||
]
|
||||
|
||||
# Dynamic headers from ActivitySignup (only if orders contain activity orders)
|
||||
json_keys = get_order_signup_keys(queryset)
|
||||
|
||||
# Write header
|
||||
# Add a prefix to json keys to distinguish them? Or keep as is.
|
||||
# Let's keep as is but maybe add a note in header if needed.
|
||||
ws.append(fixed_headers + json_keys)
|
||||
|
||||
# Write data
|
||||
for order in queryset:
|
||||
row_data = get_order_row_data(order)
|
||||
signup_data = get_order_signup_data(order)
|
||||
|
||||
row = []
|
||||
for header in fixed_headers:
|
||||
val = row_data.get(header, '')
|
||||
row.append(str(val)) # Convert everything to string for safety
|
||||
|
||||
for key in json_keys:
|
||||
val = signup_data.get(key, '')
|
||||
if val is None:
|
||||
val = ''
|
||||
row.append(str(val))
|
||||
|
||||
ws.append(row)
|
||||
|
||||
wb.save(response)
|
||||
return response
|
||||
|
||||
export_orders_excel.short_description = "导出选中订单为 Excel (含报名信息)"
|
||||
Reference in New Issue
Block a user