feat: implement WeChat cross-batch refund reconciliation and fix misc issues
WeChat cross-batch refund reconciliation: - Add OriginalAmount field to CleanedBill for accurate cumulative refund math - DeduplicateRawFile detects WeChat status-update rows (已退款/已全额退款) and emits WechatRefundUpdates for Go-side reconciliation (Scenario 1) - WechatPy cleaner surfaces -退款 income rows with no same-batch expense match as unresolved_refunds for Go ReconcileRefund (Scenario 2) - Add ReconcileWechatRefund to repository interface and MongoDB implementation - upload.go step 15 iterates WechatRefundUpdates and reconciles against bills_cleaned Bug fixes: - ReviewStats: add nil repo check to prevent panic when DB is not connected - JWT: remove hardcoded fallback secret; return 500/401 if JWTSecret not configured - Remove unused parsePageParam dead code and its strconv import - BillDetailDrawer: show 不计收支 amount in muted gray instead of red - test_jd_cleaner.py: replace hardcoded D:\Projects\BillAI path with dynamic __file__ resolution Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,13 +23,21 @@ func getRepo() repository.BillRepository {
|
||||
return repository.GetRepository()
|
||||
}
|
||||
|
||||
// WechatRefundUpdate 微信重复行中携带的退款信息(用于跨批次退款核销)
|
||||
type WechatRefundUpdate struct {
|
||||
TransactionID string // 原消费行的交易单号
|
||||
FullRefund bool // 是否全额退款(已全额退款)
|
||||
CumulativeRefundAmount float64 // 累计退款金额(已退款(¥X)中的 X,与原始金额相减得剩余)
|
||||
}
|
||||
|
||||
// DeduplicateResult 去重结果
|
||||
type DeduplicateResult struct {
|
||||
OriginalCount int // 原始记录数
|
||||
DuplicateCount int // 重复记录数
|
||||
NewCount int // 新记录数
|
||||
DedupFilePath string // 去重后的文件路径(如果有去重则生成新文件)
|
||||
BillType string // 检测到的账单类型
|
||||
OriginalCount int // 原始记录数
|
||||
DuplicateCount int // 重复记录数
|
||||
NewCount int // 新记录数
|
||||
DedupFilePath string // 去重后的文件路径(如果有去重则生成新文件)
|
||||
BillType string // 检测到的账单类型
|
||||
WechatRefundUpdates []WechatRefundUpdate // 微信重复行中检测到的退款状态(用于跨批次核销)
|
||||
}
|
||||
|
||||
// DeduplicateRawFile 对原始文件进行去重检查,返回去重后的文件路径
|
||||
@@ -75,6 +83,17 @@ func DeduplicateRawFile(filePath, uploadBatch string) (*DeduplicateResult, error
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 对于微信账单,找到"当前状态"列的索引,用于检测退款状态
|
||||
wechatStatusIdx := -1
|
||||
if billType == "wechat" {
|
||||
for i, col := range header {
|
||||
if col == "当前状态" {
|
||||
wechatStatusIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查每行是否重复
|
||||
var newRows [][]string
|
||||
for _, row := range dataRows {
|
||||
@@ -101,6 +120,24 @@ func DeduplicateRawFile(filePath, uploadBatch string) (*DeduplicateResult, error
|
||||
newRows = append(newRows, row)
|
||||
} else {
|
||||
result.DuplicateCount++
|
||||
// 微信账单:检查重复行是否携带退款状态(跨批次退款核销)
|
||||
if wechatStatusIdx >= 0 && len(row) > wechatStatusIdx {
|
||||
status := strings.TrimSpace(row[wechatStatusIdx])
|
||||
if strings.Contains(status, "已全额退款") {
|
||||
result.WechatRefundUpdates = append(result.WechatRefundUpdates, WechatRefundUpdate{
|
||||
TransactionID: transactionID,
|
||||
FullRefund: true,
|
||||
})
|
||||
} else if strings.Contains(status, "已退款") {
|
||||
if amount := extractWechatRefundAmount(status); amount > 0 {
|
||||
result.WechatRefundUpdates = append(result.WechatRefundUpdates, WechatRefundUpdate{
|
||||
TransactionID: transactionID,
|
||||
FullRefund: false,
|
||||
CumulativeRefundAmount: amount,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +374,7 @@ func saveCleanedBillsFromCSV(filePath, billType, sourceFile, uploadBatch string)
|
||||
bill.ReviewLevel = row[idx]
|
||||
}
|
||||
|
||||
bill.OriginalAmount = bill.Amount
|
||||
bills = append(bills, bill)
|
||||
}
|
||||
|
||||
@@ -431,6 +469,7 @@ func saveCleanedBillsFromJSON(filePath, billType, sourceFile, uploadBatch string
|
||||
bill.ReviewLevel = v
|
||||
}
|
||||
|
||||
bill.OriginalAmount = bill.Amount
|
||||
bills = append(bills, bill)
|
||||
}
|
||||
|
||||
@@ -512,3 +551,28 @@ func parseAmount(s string) float64 {
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// extractWechatRefundAmount 从微信"当前状态"字段中提取累计退款金额
|
||||
// 支持格式: "已退款(¥3.00)"、"已退款(¥3.00)"、"已退款¥3.00"
|
||||
func extractWechatRefundAmount(status string) float64 {
|
||||
start := strings.Index(status, "(")
|
||||
end := strings.LastIndex(status, ")")
|
||||
var inner string
|
||||
if start >= 0 && end > start {
|
||||
inner = status[start+1 : end]
|
||||
} else {
|
||||
idx := strings.Index(status, "已退款")
|
||||
if idx < 0 {
|
||||
return 0
|
||||
}
|
||||
inner = status[idx+len("已退款"):]
|
||||
}
|
||||
inner = strings.TrimPrefix(inner, "¥")
|
||||
inner = strings.TrimPrefix(inner, "¥")
|
||||
inner = strings.TrimSpace(inner)
|
||||
v, err := strconv.ParseFloat(inner, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user