mirror of
https://github.com/zhinianboke/xianyu-auto-reply.git
synced 2025-08-30 01:27:35 +08:00
支持多数量发货
This commit is contained in:
parent
042c09329c
commit
d5ae0488b5
@ -522,10 +522,56 @@ class XianyuLive:
|
|||||||
|
|
||||||
logger.info(f"【{self.cookie_id}】准备自动发货: item_id={item_id}, item_title={item_title}")
|
logger.info(f"【{self.cookie_id}】准备自动发货: item_id={item_id}, item_title={item_title}")
|
||||||
|
|
||||||
# 调用自动发货方法(包含自动确认发货)
|
# 检查是否需要多数量发货
|
||||||
delivery_content = await self._auto_delivery(item_id, item_title, order_id, send_user_id)
|
from db_manager import db_manager
|
||||||
|
quantity_to_send = 1 # 默认发送1个
|
||||||
|
|
||||||
if delivery_content:
|
# 检查商品是否开启了多数量发货
|
||||||
|
multi_quantity_delivery = db_manager.get_item_multi_quantity_delivery_status(self.cookie_id, item_id)
|
||||||
|
|
||||||
|
if multi_quantity_delivery and order_id:
|
||||||
|
logger.info(f"商品 {item_id} 开启了多数量发货,获取订单详情...")
|
||||||
|
try:
|
||||||
|
# 使用现有方法获取订单详情
|
||||||
|
order_detail = await self.fetch_order_detail_info(order_id, item_id, send_user_id)
|
||||||
|
if order_detail and order_detail.get('quantity'):
|
||||||
|
try:
|
||||||
|
order_quantity = int(order_detail['quantity'])
|
||||||
|
if order_quantity > 1:
|
||||||
|
quantity_to_send = order_quantity
|
||||||
|
logger.info(f"从订单详情获取数量: {order_quantity},将发送 {quantity_to_send} 个卡券")
|
||||||
|
else:
|
||||||
|
logger.info(f"订单数量为 {order_quantity},发送单个卡券")
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
logger.warning(f"订单数量格式无效: {order_detail.get('quantity')},发送单个卡券")
|
||||||
|
else:
|
||||||
|
logger.info(f"未获取到订单数量信息,发送单个卡券")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取订单详情失败: {self._safe_str(e)},发送单个卡券")
|
||||||
|
elif not multi_quantity_delivery:
|
||||||
|
logger.info(f"商品 {item_id} 未开启多数量发货,发送单个卡券")
|
||||||
|
else:
|
||||||
|
logger.info(f"无订单ID,发送单个卡券")
|
||||||
|
|
||||||
|
# 多次调用自动发货方法,每次获取不同的内容
|
||||||
|
delivery_contents = []
|
||||||
|
success_count = 0
|
||||||
|
|
||||||
|
for i in range(quantity_to_send):
|
||||||
|
try:
|
||||||
|
# 每次调用都可能获取不同的内容(API卡券、批量数据等)
|
||||||
|
delivery_content = await self._auto_delivery(item_id, item_title, order_id, send_user_id)
|
||||||
|
if delivery_content:
|
||||||
|
delivery_contents.append(delivery_content)
|
||||||
|
success_count += 1
|
||||||
|
if quantity_to_send > 1:
|
||||||
|
logger.info(f"第 {i+1}/{quantity_to_send} 个卡券内容获取成功")
|
||||||
|
else:
|
||||||
|
logger.warning(f"第 {i+1}/{quantity_to_send} 个卡券内容获取失败")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"第 {i+1}/{quantity_to_send} 个卡券获取异常: {self._safe_str(e)}")
|
||||||
|
|
||||||
|
if delivery_contents:
|
||||||
# 标记已发货(防重复)- 基于订单ID
|
# 标记已发货(防重复)- 基于订单ID
|
||||||
self.mark_delivery_sent(order_id)
|
self.mark_delivery_sent(order_id)
|
||||||
|
|
||||||
@ -541,35 +587,55 @@ class XianyuLive:
|
|||||||
delay_task = asyncio.create_task(self._delayed_lock_release(lock_key, delay_minutes=10))
|
delay_task = asyncio.create_task(self._delayed_lock_release(lock_key, delay_minutes=10))
|
||||||
self._lock_hold_info[lock_key]['task'] = delay_task
|
self._lock_hold_info[lock_key]['task'] = delay_task
|
||||||
|
|
||||||
# 检查是否是图片发送标记
|
# 发送所有获取到的发货内容
|
||||||
if delivery_content.startswith("__IMAGE_SEND__"):
|
for i, delivery_content in enumerate(delivery_contents):
|
||||||
# 提取卡券ID和图片URL
|
|
||||||
image_data = delivery_content.replace("__IMAGE_SEND__", "")
|
|
||||||
if "|" in image_data:
|
|
||||||
card_id_str, image_url = image_data.split("|", 1)
|
|
||||||
try:
|
|
||||||
card_id = int(card_id_str)
|
|
||||||
except ValueError:
|
|
||||||
logger.error(f"无效的卡券ID: {card_id_str}")
|
|
||||||
card_id = None
|
|
||||||
else:
|
|
||||||
# 兼容旧格式(没有卡券ID)
|
|
||||||
card_id = None
|
|
||||||
image_url = image_data
|
|
||||||
|
|
||||||
# 发送图片消息
|
|
||||||
try:
|
try:
|
||||||
await self.send_image_msg(websocket, chat_id, send_user_id, image_url, card_id=card_id)
|
# 检查是否是图片发送标记
|
||||||
logger.info(f'[{msg_time}] 【自动发货图片】已向 {user_url} 发送图片: {image_url}')
|
if delivery_content.startswith("__IMAGE_SEND__"):
|
||||||
await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, "发货成功")
|
# 提取卡券ID和图片URL
|
||||||
|
image_data = delivery_content.replace("__IMAGE_SEND__", "")
|
||||||
|
if "|" in image_data:
|
||||||
|
card_id_str, image_url = image_data.split("|", 1)
|
||||||
|
try:
|
||||||
|
card_id = int(card_id_str)
|
||||||
|
except ValueError:
|
||||||
|
logger.error(f"无效的卡券ID: {card_id_str}")
|
||||||
|
card_id = None
|
||||||
|
else:
|
||||||
|
# 兼容旧格式(没有卡券ID)
|
||||||
|
card_id = None
|
||||||
|
image_url = image_data
|
||||||
|
|
||||||
|
# 发送图片消息
|
||||||
|
await self.send_image_msg(websocket, chat_id, send_user_id, image_url, card_id=card_id)
|
||||||
|
if len(delivery_contents) > 1:
|
||||||
|
logger.info(f'[{msg_time}] 【多数量自动发货图片】第 {i+1}/{len(delivery_contents)} 张已向 {user_url} 发送图片: {image_url}')
|
||||||
|
else:
|
||||||
|
logger.info(f'[{msg_time}] 【自动发货图片】已向 {user_url} 发送图片: {image_url}')
|
||||||
|
|
||||||
|
# 多数量发货时,消息间隔1秒
|
||||||
|
if len(delivery_contents) > 1 and i < len(delivery_contents) - 1:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 普通文本发货内容
|
||||||
|
await self.send_msg(websocket, chat_id, send_user_id, delivery_content)
|
||||||
|
if len(delivery_contents) > 1:
|
||||||
|
logger.info(f'[{msg_time}] 【多数量自动发货】第 {i+1}/{len(delivery_contents)} 条已向 {user_url} 发送发货内容')
|
||||||
|
else:
|
||||||
|
logger.info(f'[{msg_time}] 【自动发货】已向 {user_url} 发送发货内容')
|
||||||
|
|
||||||
|
# 多数量发货时,消息间隔1秒
|
||||||
|
if len(delivery_contents) > 1 and i < len(delivery_contents) - 1:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"自动发货图片失败: {self._safe_str(e)}")
|
logger.error(f"发送第 {i+1} 条消息失败: {self._safe_str(e)}")
|
||||||
await self.send_msg(websocket, chat_id, send_user_id, "抱歉,图片发送失败,请联系客服。")
|
|
||||||
await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, "图片发送失败")
|
# 发送成功通知
|
||||||
|
if len(delivery_contents) > 1:
|
||||||
|
await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, f"多数量发货成功,共发送 {len(delivery_contents)} 个卡券")
|
||||||
else:
|
else:
|
||||||
# 普通文本发货内容
|
|
||||||
await self.send_msg(websocket, chat_id, send_user_id, delivery_content)
|
|
||||||
logger.info(f'[{msg_time}] 【自动发货】已向 {user_url} 发送发货内容')
|
|
||||||
await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, "发货成功")
|
await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, "发货成功")
|
||||||
else:
|
else:
|
||||||
logger.warning(f'[{msg_time}] 【自动发货】未找到匹配的发货规则或获取发货内容失败')
|
logger.warning(f'[{msg_time}] 【自动发货】未找到匹配的发货规则或获取发货内容失败')
|
||||||
@ -2530,6 +2596,8 @@ class XianyuLive:
|
|||||||
logger.error(f"自动发货失败: {self._safe_str(e)}")
|
logger.error(f"自动发货失败: {self._safe_str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _process_delivery_content_with_description(self, delivery_content: str, card_description: str) -> str:
|
def _process_delivery_content_with_description(self, delivery_content: str, card_description: str) -> str:
|
||||||
"""处理发货内容和备注信息,实现变量替换"""
|
"""处理发货内容和备注信息,实现变量替换"""
|
||||||
try:
|
try:
|
||||||
|
@ -660,6 +660,14 @@ class DBManager:
|
|||||||
self._execute_sql(cursor, "ALTER TABLE item_info ADD COLUMN is_multi_spec BOOLEAN DEFAULT FALSE")
|
self._execute_sql(cursor, "ALTER TABLE item_info ADD COLUMN is_multi_spec BOOLEAN DEFAULT FALSE")
|
||||||
logger.info("为item_info表添加多规格字段")
|
logger.info("为item_info表添加多规格字段")
|
||||||
|
|
||||||
|
# 为item_info表添加多数量发货字段(如果不存在)
|
||||||
|
try:
|
||||||
|
self._execute_sql(cursor, "SELECT multi_quantity_delivery FROM item_info LIMIT 1")
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
# 多数量发货字段不存在,需要添加
|
||||||
|
self._execute_sql(cursor, "ALTER TABLE item_info ADD COLUMN multi_quantity_delivery BOOLEAN DEFAULT FALSE")
|
||||||
|
logger.info("为item_info表添加多数量发货字段")
|
||||||
|
|
||||||
# 处理keywords表的唯一约束问题
|
# 处理keywords表的唯一约束问题
|
||||||
# 由于SQLite不支持直接修改约束,我们需要重建表
|
# 由于SQLite不支持直接修改约束,我们需要重建表
|
||||||
self._migrate_keywords_table_constraints(cursor)
|
self._migrate_keywords_table_constraints(cursor)
|
||||||
@ -3613,6 +3621,49 @@ class DBManager:
|
|||||||
logger.error(f"获取商品多规格状态失败: {e}")
|
logger.error(f"获取商品多规格状态失败: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def update_item_multi_quantity_delivery_status(self, cookie_id: str, item_id: str, multi_quantity_delivery: bool) -> bool:
|
||||||
|
"""更新商品的多数量发货状态"""
|
||||||
|
try:
|
||||||
|
with self.lock:
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
UPDATE item_info
|
||||||
|
SET multi_quantity_delivery = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE cookie_id = ? AND item_id = ?
|
||||||
|
''', (multi_quantity_delivery, cookie_id, item_id))
|
||||||
|
|
||||||
|
if cursor.rowcount > 0:
|
||||||
|
self.conn.commit()
|
||||||
|
logger.info(f"更新商品多数量发货状态成功: {item_id} -> {multi_quantity_delivery}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning(f"未找到要更新的商品: {item_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"更新商品多数量发货状态失败: {e}")
|
||||||
|
self.conn.rollback()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_item_multi_quantity_delivery_status(self, cookie_id: str, item_id: str) -> bool:
|
||||||
|
"""获取商品的多数量发货状态"""
|
||||||
|
try:
|
||||||
|
with self.lock:
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT multi_quantity_delivery FROM item_info
|
||||||
|
WHERE cookie_id = ? AND item_id = ?
|
||||||
|
''', (cookie_id, item_id))
|
||||||
|
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
return bool(row[0]) if row[0] is not None else False
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取商品多数量发货状态失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def get_items_by_cookie(self, cookie_id: str) -> List[Dict]:
|
def get_items_by_cookie(self, cookie_id: str) -> List[Dict]:
|
||||||
"""获取指定Cookie的所有商品信息
|
"""获取指定Cookie的所有商品信息
|
||||||
|
|
||||||
|
@ -4041,6 +4041,26 @@ def update_item_multi_spec(cookie_id: str, item_id: str, spec_data: dict, _: Non
|
|||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
# 商品多数量发货管理API
|
||||||
|
@app.put("/items/{cookie_id}/{item_id}/multi-quantity-delivery")
|
||||||
|
def update_item_multi_quantity_delivery(cookie_id: str, item_id: str, delivery_data: dict, _: None = Depends(require_auth)):
|
||||||
|
"""更新商品的多数量发货状态"""
|
||||||
|
try:
|
||||||
|
from db_manager import db_manager
|
||||||
|
|
||||||
|
multi_quantity_delivery = delivery_data.get('multi_quantity_delivery', False)
|
||||||
|
|
||||||
|
success = db_manager.update_item_multi_quantity_delivery_status(cookie_id, item_id, multi_quantity_delivery)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
return {"message": f"商品多数量发货状态已{'开启' if multi_quantity_delivery else '关闭'}"}
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=404, detail="商品不存在")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
# 移除自动启动,由Start.py或手动启动
|
# 移除自动启动,由Start.py或手动启动
|
||||||
# if __name__ == "__main__":
|
# if __name__ == "__main__":
|
||||||
# uvicorn.run(app, host="0.0.0.0", port=8080)
|
# uvicorn.run(app, host="0.0.0.0", port=8080)
|
@ -399,19 +399,20 @@
|
|||||||
<th style="width: 5%">
|
<th style="width: 5%">
|
||||||
<input type="checkbox" id="selectAllItems" onchange="toggleSelectAll(this)">
|
<input type="checkbox" id="selectAllItems" onchange="toggleSelectAll(this)">
|
||||||
</th>
|
</th>
|
||||||
<th style="width: 12%">账号ID</th>
|
<th style="width: 10%">账号ID</th>
|
||||||
<th style="width: 12%">商品ID</th>
|
<th style="width: 10%">商品ID</th>
|
||||||
<th style="width: 18%">商品标题</th>
|
<th style="width: 16%">商品标题</th>
|
||||||
<th style="width: 20%">商品详情</th>
|
<th style="width: 18%">商品详情</th>
|
||||||
<th style="width: 20%">商品价格</th>
|
<th style="width: 18%">商品价格</th>
|
||||||
<th style="width: 8%">多规格</th>
|
<th style="width: 8%">多规格</th>
|
||||||
|
<th style="width: 8%">多数量发货</th>
|
||||||
<th style="width: 10%">更新时间</th>
|
<th style="width: 10%">更新时间</th>
|
||||||
<th style="width: 15%">操作</th>
|
<th style="width: 15%">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="itemsTableBody">
|
<tbody id="itemsTableBody">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="9" class="text-center text-muted">加载中...</td>
|
<td colspan="10" class="text-center text-muted">加载中...</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -4966,6 +4966,34 @@ async function toggleItemMultiSpec(cookieId, itemId, isMultiSpec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换商品多数量发货状态
|
||||||
|
async function toggleItemMultiQuantityDelivery(cookieId, itemId, multiQuantityDelivery) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${apiBase}/items/${encodeURIComponent(cookieId)}/${encodeURIComponent(itemId)}/multi-quantity-delivery`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
multi_quantity_delivery: multiQuantityDelivery
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
showToast(`${multiQuantityDelivery ? '开启' : '关闭'}多数量发货成功`, 'success');
|
||||||
|
// 刷新商品列表
|
||||||
|
await refreshItemsData();
|
||||||
|
} else {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(errorData.error || '操作失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('切换多数量发货状态失败:', error);
|
||||||
|
showToast(`切换多数量发货状态失败: ${error.message}`, 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 加载商品列表
|
// 加载商品列表
|
||||||
async function loadItems() {
|
async function loadItems() {
|
||||||
try {
|
try {
|
||||||
@ -5181,7 +5209,7 @@ function displayCurrentPageItems() {
|
|||||||
const tbody = document.getElementById('itemsTableBody');
|
const tbody = document.getElementById('itemsTableBody');
|
||||||
|
|
||||||
if (!filteredItemsData || filteredItemsData.length === 0) {
|
if (!filteredItemsData || filteredItemsData.length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="9" class="text-center text-muted">暂无商品数据</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="10" class="text-center text-muted">暂无商品数据</td></tr>';
|
||||||
resetItemsSelection();
|
resetItemsSelection();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -5211,6 +5239,12 @@ function displayCurrentPageItems() {
|
|||||||
'<span class="badge bg-success">多规格</span>' :
|
'<span class="badge bg-success">多规格</span>' :
|
||||||
'<span class="badge bg-secondary">普通</span>';
|
'<span class="badge bg-secondary">普通</span>';
|
||||||
|
|
||||||
|
// 多数量发货状态显示
|
||||||
|
const isMultiQuantityDelivery = item.multi_quantity_delivery;
|
||||||
|
const multiQuantityDeliveryDisplay = isMultiQuantityDelivery ?
|
||||||
|
'<span class="badge bg-success">已开启</span>' :
|
||||||
|
'<span class="badge bg-secondary">已关闭</span>';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@ -5225,6 +5259,7 @@ function displayCurrentPageItems() {
|
|||||||
<td title="${escapeHtml(getItemDetailText(item.item_detail || ''))}">${escapeHtml(itemDetailDisplay)}</td>
|
<td title="${escapeHtml(getItemDetailText(item.item_detail || ''))}">${escapeHtml(itemDetailDisplay)}</td>
|
||||||
<td>${escapeHtml(item.item_price || '未设置')}</td>
|
<td>${escapeHtml(item.item_price || '未设置')}</td>
|
||||||
<td>${multiSpecDisplay}</td>
|
<td>${multiSpecDisplay}</td>
|
||||||
|
<td>${multiQuantityDeliveryDisplay}</td>
|
||||||
<td>${formatDateTime(item.updated_at)}</td>
|
<td>${formatDateTime(item.updated_at)}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
@ -5237,6 +5272,9 @@ function displayCurrentPageItems() {
|
|||||||
<button class="btn btn-sm ${isMultiSpec ? 'btn-warning' : 'btn-success'}" onclick="toggleItemMultiSpec('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}', ${!isMultiSpec})" title="${isMultiSpec ? '关闭多规格' : '开启多规格'}">
|
<button class="btn btn-sm ${isMultiSpec ? 'btn-warning' : 'btn-success'}" onclick="toggleItemMultiSpec('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}', ${!isMultiSpec})" title="${isMultiSpec ? '关闭多规格' : '开启多规格'}">
|
||||||
<i class="bi ${isMultiSpec ? 'bi-toggle-on' : 'bi-toggle-off'}"></i>
|
<i class="bi ${isMultiSpec ? 'bi-toggle-on' : 'bi-toggle-off'}"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-sm ${isMultiQuantityDelivery ? 'btn-warning' : 'btn-success'}" onclick="toggleItemMultiQuantityDelivery('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}', ${!isMultiQuantityDelivery})" title="${isMultiQuantityDelivery ? '关闭多数量发货' : '开启多数量发货'}">
|
||||||
|
<i class="bi ${isMultiQuantityDelivery ? 'bi-box-arrow-down' : 'bi-box-arrow-up'}"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user