When a refund row in an uploaded Alipay bill has no matching expense row in the same batch (because the original purchase was uploaded in a prior batch), the refund is now reconciled against the stored record in bills_cleaned rather than being silently discarded. Changes: - analyzer/cleaners/base.py: add unresolved_refunds list to BaseCleaner - analyzer/cleaners/alipay.py: _aggregate_refunds stores full refund metadata (dict); _process_expenses tracks matched keys and populates self.unresolved_refunds for unmatched refunds - analyzer/server.py: thread unresolved_refunds through do_clean, CleanResponse, and both /clean endpoints - server/adapter/adapter.go: add UnresolvedRefund type and field to CleanResult - server/adapter/http/cleaner.go: deserialize unresolved_refunds from Python response and populate CleanResult - server/repository/repository.go: add ReconcileRefund to BillRepository interface - server/repository/mongo/repository.go: implement ReconcileRefund — full refund soft-deletes the bill, partial refund reduces amount and appends remark with original amount and refund order number - server/handler/upload.go: capture clean result and call ReconcileRefund for each unresolved refund after saving cleaned bills - server/model/response.go: add ReconciledRefundCount to UploadData Also: add CLAUDE.md (@AGENTS.md), update AGENTS.md, fix DailyTrendChart missing-date gap by filling zero-expense dates in daily map. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
52 lines
1.9 KiB
Go
52 lines
1.9 KiB
Go
// Package adapter 定义与外部系统交互的抽象接口
|
||
// 这样可以方便后续更换通信方式(如从子进程调用改为 HTTP/gRPC/消息队列等)
|
||
package adapter
|
||
|
||
// CleanOptions 清洗选项
|
||
type CleanOptions struct {
|
||
Year string // 年份筛选
|
||
Month string // 月份筛选
|
||
Start string // 起始日期
|
||
End string // 结束日期
|
||
Format string // 输出格式: csv/json
|
||
}
|
||
|
||
// CleanResult 清洗结果
|
||
type CleanResult struct {
|
||
BillType string // 检测到的账单类型: alipay/wechat/jd
|
||
Output string // 脚本输出信息
|
||
UnresolvedRefunds []UnresolvedRefund // 本次清洗未在同批次内匹配到对应支出的退款
|
||
}
|
||
|
||
// UnresolvedRefund 本次清洗未在同批次内匹配到对应支出的退款
|
||
type UnresolvedRefund struct {
|
||
OrderNo string // 原订单号(去除退款后缀)
|
||
MerchantOrderNo string // 商家订单号(备用匹配字段)
|
||
RefundOrderNo string // 退款行自身的完整订单号(用于备注追溯)
|
||
Amount float64 // 退款金额
|
||
Time string // 退款时间
|
||
Merchant string // 交易对方
|
||
Description string // 商品说明
|
||
}
|
||
|
||
// ConvertResult 格式转换结果
|
||
type ConvertResult struct {
|
||
OutputPath string // 转换后的文件路径
|
||
BillType string // 检测到的账单类型: alipay/wechat/jd
|
||
}
|
||
|
||
// Cleaner 账单清洗器接口
|
||
// 负责将原始账单数据清洗为标准格式
|
||
type Cleaner interface {
|
||
// Clean 执行账单清洗
|
||
// inputPath: 输入文件路径
|
||
// outputPath: 输出文件路径
|
||
// opts: 清洗选项
|
||
Clean(inputPath, outputPath string, opts *CleanOptions) (*CleanResult, error)
|
||
|
||
// Convert 转换账单文件格式(xlsx -> csv,处理 GBK 编码等)
|
||
// inputPath: 输入文件路径
|
||
// 返回: 转换后的文件路径, 检测到的账单类型, 错误
|
||
Convert(inputPath string) (outputPath string, billType string, err error)
|
||
}
|