Add refund received timestamp column, record time when marking refund

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-06 02:01:37 +08:00
parent 688d200d2c
commit 3be4b114a5
5 changed files with 34 additions and 20 deletions

View File

@@ -106,18 +106,20 @@ class DatabaseManager:
proxy_share TEXT DEFAULT 'exclusive', proxy_share TEXT DEFAULT 'exclusive',
proxy_purchase_date TEXT DEFAULT '', proxy_purchase_date TEXT DEFAULT '',
refund_received TEXT DEFAULT '0', refund_received TEXT DEFAULT '0',
refund_received_at TEXT DEFAULT '',
checked_at TEXT checked_at TEXT
) )
''') ''')
# 兼容旧表:动态添加缺少的列 # 兼容旧表:动态添加缺少的列
for col in ['suspended_time', 'title', 'remark', 'card_number', 'proxy', 'proxy_expire_days', 'proxy_share', 'proxy_purchase_date', 'refund_received']: for col in ['suspended_time', 'title', 'remark', 'card_number', 'proxy', 'proxy_expire_days', 'proxy_share', 'proxy_purchase_date', 'refund_received', 'refund_received_at']:
try: try:
cursor.execute(f"SELECT {col} FROM claude_payment_status LIMIT 1") cursor.execute(f"SELECT {col} FROM claude_payment_status LIMIT 1")
except sqlite3.OperationalError: except sqlite3.OperationalError:
defaults = {'title': "DEFAULT ''", 'remark': "DEFAULT ''", 'card_number': "DEFAULT ''", 'proxy': "DEFAULT ''", defaults = {'title': "DEFAULT ''", 'remark': "DEFAULT ''", 'card_number': "DEFAULT ''", 'proxy': "DEFAULT ''",
'proxy_expire_days': "DEFAULT 30", 'proxy_share': "DEFAULT 'exclusive'", 'proxy_purchase_date': "DEFAULT ''", 'proxy_expire_days': "DEFAULT 30", 'proxy_share': "DEFAULT 'exclusive'", 'proxy_purchase_date': "DEFAULT ''",
'refund_received': "DEFAULT '0'"} 'refund_received': "DEFAULT '0'",
'refund_received_at': "DEFAULT ''"}
default = defaults.get(col, '') default = defaults.get(col, '')
cursor.execute(f"ALTER TABLE claude_payment_status ADD COLUMN {col} TEXT {default}") cursor.execute(f"ALTER TABLE claude_payment_status ADD COLUMN {col} TEXT {default}")
logger.info(f"已为 claude_payment_status 添加 {col}") logger.info(f"已为 claude_payment_status 添加 {col}")
@@ -527,7 +529,7 @@ class DatabaseManager:
cursor = conn.cursor() cursor = conn.cursor()
checked_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S') checked_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 先查已有的 remark/card_number避免被覆盖 # 先查已有的 remark/card_number避免被覆盖
cursor.execute('SELECT title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, refund_received FROM claude_payment_status WHERE email = ?', (email,)) cursor.execute('SELECT title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, refund_received, refund_received_at FROM claude_payment_status WHERE email = ?', (email,))
existing = cursor.fetchone() existing = cursor.fetchone()
old_title = existing['title'] if existing else '' old_title = existing['title'] if existing else ''
old_remark = existing['remark'] if existing else '' old_remark = existing['remark'] if existing else ''
@@ -537,10 +539,11 @@ class DatabaseManager:
old_share = existing['proxy_share'] if existing else 'exclusive' old_share = existing['proxy_share'] if existing else 'exclusive'
old_purchase = existing['proxy_purchase_date'] if existing else '' old_purchase = existing['proxy_purchase_date'] if existing else ''
old_refund_received = existing['refund_received'] if existing else '0' old_refund_received = existing['refund_received'] if existing else '0'
old_refund_received_at = existing['refund_received_at'] if existing else ''
cursor.execute(''' cursor.execute('''
INSERT OR REPLACE INTO claude_payment_status (email, status, payment_time, refund_time, suspended_time, title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, refund_received, checked_at) INSERT OR REPLACE INTO claude_payment_status (email, status, payment_time, refund_time, suspended_time, title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, refund_received, refund_received_at, checked_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (email, status, payment_time, refund_time, suspended_time, old_title or '', old_remark or '', old_card or '', old_proxy or '', old_expire or 30, old_share or 'exclusive', old_purchase or '', old_refund_received or '0', checked_at)) ''', (email, status, payment_time, refund_time, suspended_time, old_title or '', old_remark or '', old_card or '', old_proxy or '', old_expire or 30, old_share or 'exclusive', old_purchase or '', old_refund_received or '0', old_refund_received_at or '', checked_at))
conn.commit() conn.commit()
return True return True
except Exception as e: except Exception as e:
@@ -554,7 +557,7 @@ class DatabaseManager:
def _sync_get(): def _sync_get():
conn = self.get_connection() conn = self.get_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute('SELECT status, payment_time, refund_time, suspended_time, title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, refund_received, checked_at FROM claude_payment_status WHERE email = ?', (email,)) cursor.execute('SELECT status, payment_time, refund_time, suspended_time, title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, refund_received, refund_received_at, checked_at FROM claude_payment_status WHERE email = ?', (email,))
row = cursor.fetchone() row = cursor.fetchone()
if row: if row:
return { return {
@@ -570,6 +573,7 @@ class DatabaseManager:
'proxy_share': row['proxy_share'] or 'exclusive', 'proxy_share': row['proxy_share'] or 'exclusive',
'proxy_purchase_date': row['proxy_purchase_date'] or '', 'proxy_purchase_date': row['proxy_purchase_date'] or '',
'refund_received': row['refund_received'] or '0', 'refund_received': row['refund_received'] or '0',
'refund_received_at': row['refund_received_at'] or '',
'checked_at': row['checked_at'] 'checked_at': row['checked_at']
} }
return None return None
@@ -581,7 +585,7 @@ class DatabaseManager:
def _sync_get(): def _sync_get():
conn = self.get_connection() conn = self.get_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute('SELECT email, status, payment_time, refund_time, suspended_time, title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, refund_received, checked_at FROM claude_payment_status') cursor.execute('SELECT email, status, payment_time, refund_time, suspended_time, title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, refund_received, refund_received_at, checked_at FROM claude_payment_status')
rows = cursor.fetchall() rows = cursor.fetchall()
result = {} result = {}
for row in rows: for row in rows:
@@ -598,6 +602,7 @@ class DatabaseManager:
'proxy_share': row['proxy_share'] or 'exclusive', 'proxy_share': row['proxy_share'] or 'exclusive',
'proxy_purchase_date': row['proxy_purchase_date'] or '', 'proxy_purchase_date': row['proxy_purchase_date'] or '',
'refund_received': row['refund_received'] or '0', 'refund_received': row['refund_received'] or '0',
'refund_received_at': row['refund_received_at'] or '',
'checked_at': row['checked_at'] 'checked_at': row['checked_at']
} }
return result return result
@@ -606,7 +611,7 @@ class DatabaseManager:
async def update_claude_payment_note(self, email: str, **kwargs) -> bool: async def update_claude_payment_note(self, email: str, **kwargs) -> bool:
"""更新备注、卡号、代理等字段""" """更新备注、卡号、代理等字段"""
allowed = {'title', 'remark', 'card_number', 'proxy', 'proxy_expire_days', 'proxy_share', 'proxy_purchase_date', 'refund_received'} allowed = {'title', 'remark', 'card_number', 'proxy', 'proxy_expire_days', 'proxy_share', 'proxy_purchase_date', 'refund_received', 'refund_received_at'}
def _sync_update(): def _sync_update():
try: try:
conn = self.get_connection() conn = self.get_connection()

View File

@@ -1278,10 +1278,11 @@ async def toggle_refund_received(email: str) -> ApiResponse:
status = await db_manager.get_claude_payment_status(email) status = await db_manager.get_claude_payment_status(email)
current = status.get('refund_received', '0') if status else '0' current = status.get('refund_received', '0') if status else '0'
new_val = '0' if current == '1' else '1' new_val = '0' if current == '1' else '1'
ok = await db_manager.update_claude_payment_note(email, refund_received=new_val) now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') if new_val == '1' else ''
ok = await db_manager.update_claude_payment_note(email, refund_received=new_val, refund_received_at=now)
if ok: if ok:
await cache.invalidate_payment() await cache.invalidate_payment()
return ApiResponse(success=True, data={"refund_received": new_val}) return ApiResponse(success=True, data={"refund_received": new_val, "refund_received_at": now})
return ApiResponse(success=False, message="更新失败") return ApiResponse(success=False, message="更新失败")
except Exception as e: except Exception as e:
logger.error(f"切换退款状态失败: {e}") logger.error(f"切换退款状态失败: {e}")

View File

@@ -98,6 +98,7 @@
<th>支付时间</th> <th>支付时间</th>
<th>退款时间</th> <th>退款时间</th>
<th>封号时间</th> <th>封号时间</th>
<th>到账时间</th>
<th>备注/卡号</th> <th>备注/卡号</th>
<th>代理</th> <th>代理</th>
<th>操作</th> <th>操作</th>
@@ -105,7 +106,7 @@
</thead> </thead>
<tbody id="accountTableBody"> <tbody id="accountTableBody">
<tr> <tr>
<td colspan="12"> <td colspan="13">
<div class="no-data"> <div class="no-data">
<i class="bi bi-inbox"></i> <i class="bi bi-inbox"></i>
<div>暂无邮箱数据,点击"粘贴导入"添加</div> <div>暂无邮箱数据,点击"粘贴导入"添加</div>

View File

@@ -280,7 +280,7 @@ class MailManager {
async loadAccounts() { async loadAccounts() {
const tbody = document.getElementById('accountTableBody'); const tbody = document.getElementById('accountTableBody');
tbody.innerHTML = `<tr><td colspan="12"><div class="table-loading"><div class="spinner-border spinner-border-sm"></div> 加载中...</div></td></tr>`; tbody.innerHTML = `<tr><td colspan="13"><div class="table-loading"><div class="spinner-border spinner-border-sm"></div> 加载中...</div></td></tr>`;
try { try {
// 有筛选时加载全部数据,否则分页加载 // 有筛选时加载全部数据,否则分页加载
@@ -300,11 +300,11 @@ class MailManager {
await this.loadClaudePaymentStatuses(); await this.loadClaudePaymentStatuses();
this.applyFilterAndRender(); this.applyFilterAndRender();
} else { } else {
tbody.innerHTML = `<tr><td colspan="12"><div class="no-data"><i class="bi bi-exclamation-circle"></i><div>${this.escapeHtml(result.message || '加载失败')}</div></div></td></tr>`; tbody.innerHTML = `<tr><td colspan="13"><div class="no-data"><i class="bi bi-exclamation-circle"></i><div>${this.escapeHtml(result.message || '加载失败')}</div></div></td></tr>`;
} }
} catch (err) { } catch (err) {
console.error('加载账号失败:', err); console.error('加载账号失败:', err);
tbody.innerHTML = `<tr><td colspan="12"><div class="no-data"><i class="bi bi-wifi-off"></i><div>网络错误</div></div></td></tr>`; tbody.innerHTML = `<tr><td colspan="13"><div class="no-data"><i class="bi bi-wifi-off"></i><div>网络错误</div></div></td></tr>`;
} }
} }
@@ -339,7 +339,7 @@ class MailManager {
renderAccounts() { renderAccounts() {
const tbody = document.getElementById('accountTableBody'); const tbody = document.getElementById('accountTableBody');
if (!this.accounts.length) { if (!this.accounts.length) {
tbody.innerHTML = `<tr><td colspan="12"><div class="no-data"><i class="bi bi-inbox"></i><div>暂无邮箱数据</div></div></td></tr>`; tbody.innerHTML = `<tr><td colspan="13"><div class="no-data"><i class="bi bi-inbox"></i><div>暂无邮箱数据</div></div></td></tr>`;
return; return;
} }
@@ -777,9 +777,9 @@ class MailManager {
const resp = await fetch(`/api/tools/refund-received/${encodeURIComponent(email)}`, { method: 'POST' }); const resp = await fetch(`/api/tools/refund-received/${encodeURIComponent(email)}`, { method: 'POST' });
const result = await resp.json(); const result = await resp.json();
if (result.success) { if (result.success) {
// 更新本地状态
if (this.claudePaymentStatuses[email]) { if (this.claudePaymentStatuses[email]) {
this.claudePaymentStatuses[email].refund_received = result.data.refund_received; this.claudePaymentStatuses[email].refund_received = result.data.refund_received;
this.claudePaymentStatuses[email].refund_received_at = result.data.refund_received_at || '';
} }
this.renderAccounts(); this.renderAccounts();
this.showToast(result.data.refund_received === '1' ? '已标记到账' : '已取消到账', 'success'); this.showToast(result.data.refund_received === '1' ? '已标记到账' : '已取消到账', 'success');
@@ -809,6 +809,7 @@ class MailManager {
<td class="claude-time">-</td> <td class="claude-time">-</td>
<td class="claude-time">-</td> <td class="claude-time">-</td>
<td class="claude-time">-</td> <td class="claude-time">-</td>
<td class="claude-time">-</td>
<td><button class="note-cell-btn" data-note-email="${this.escapeHtml(email)}"><i class="bi bi-pencil"></i> 编辑</button></td> <td><button class="note-cell-btn" data-note-email="${this.escapeHtml(email)}"><i class="bi bi-pencil"></i> 编辑</button></td>
<td><button class="proxy-btn" data-email="${this.escapeHtml(email)}"><i class="bi bi-globe"></i> 设置</button></td>`; <td><button class="proxy-btn" data-email="${this.escapeHtml(email)}"><i class="bi bi-globe"></i> 设置</button></td>`;
@@ -843,11 +844,16 @@ class MailManager {
? `<span class="claude-badge claude-suspended">${fmtTime(info.suspended_time)}</span>` ? `<span class="claude-badge claude-suspended">${fmtTime(info.suspended_time)}</span>`
: '-'; : '-';
const refundReceivedTime = info.refund_received === '1' && info.refund_received_at
? fmtTime(info.refund_received_at)
: '-';
return `<td>${paidBadge}</td> return `<td>${paidBadge}</td>
<td>${refundBadge}</td> <td>${refundBadge}</td>
<td class="claude-time">${fmtTime(info.payment_time)}</td> <td class="claude-time">${fmtTime(info.payment_time)}</td>
<td class="claude-time">${fmtTime(info.refund_time)}</td> <td class="claude-time">${fmtTime(info.refund_time)}</td>
<td>${suspendedHtml}</td> <td>${suspendedHtml}</td>
<td class="claude-time">${refundReceivedTime}</td>
<td>${this.renderNoteCell(email, info)}</td> <td>${this.renderNoteCell(email, info)}</td>
<td class="proxy-cell"> <td class="proxy-cell">
<div class="proxy-cell-inner"> <div class="proxy-cell-inner">

View File

@@ -445,9 +445,10 @@ body {
.data-table th:nth-child(7) { width: 85px; } /* 支付时间 */ .data-table th:nth-child(7) { width: 85px; } /* 支付时间 */
.data-table th:nth-child(8) { width: 85px; } /* 退款时间 */ .data-table th:nth-child(8) { width: 85px; } /* 退款时间 */
.data-table th:nth-child(9) { width: 85px; } /* 封号时间 */ .data-table th:nth-child(9) { width: 85px; } /* 封号时间 */
.data-table th:nth-child(10) { width: 120px; } /* 备注/卡号 */ .data-table th:nth-child(10) { width: 85px; } /* 到账时间 */
.data-table th:nth-child(11) { width: 150px; } /* 代理 */ .data-table th:nth-child(11) { width: 120px; } /* 备注/卡号 */
.data-table th:nth-child(12) { width: 220px; } /* 操作 */ .data-table th:nth-child(12) { width: 150px; } /* 代理 */
.data-table th:nth-child(13) { width: 220px; } /* 操作 */
/* 单元格内容 */ /* 单元格内容 */
.value-container { .value-container {