Files
zhengchiliu 0863234bc5 feat(backend): 对齐业务服务额度与内容治理口径
仪表盘待办与穿搭概况改为公开/私密/已封禁口径,移除恒为零的待审核统计;补齐额度凭证来源 source=4 与流水 change_type=7 枚举及订单商品类型、额度有效期快照字段;管理员发放与回收额度凭证在同一事务内写入人工调整流水,回收流水使用独立 order_no 避免撞唯一索引,并增加凭证归属校验;对齐 Quota 配置免费额度值,同步更新 README 与配置说明。
2026-09-16 02:58:56 +08:00

200 lines
6.1 KiB
Go

package service
import (
"context"
"strings"
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global"
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/dto"
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqldao"
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqlmodel"
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/pkg/errcode"
)
// OrderService 订单与退款管理服务。
type OrderService struct {
orderDao *mysqldao.OrderDao
refundDao *mysqldao.RefundDao
}
// NewOrderService 创建订单服务。
func NewOrderService() *OrderService {
return &OrderService{
orderDao: mysqldao.NewOrderDao(),
refundDao: mysqldao.NewRefundDao(),
}
}
// List 分页查询订单。
func (s *OrderService) List(ctx context.Context, request *dto.OrderListRequest) (*dto.OrderListResponse, *errcode.Error) {
ctx, span := global.Tracer.Start(ctx, "svc.AdminOrderList")
defer span.End()
page, pageSize := normalizePage(request.Page, request.PageSize)
list, total, err := s.orderDao.OrderList(ctx, mysqldao.OrderListParams{
OrderNo: strings.TrimSpace(request.OrderNo),
TransactionID: strings.TrimSpace(request.TransactionID),
UserID: request.UserID,
Status: request.Status,
Page: page,
Size: pageSize,
})
if err != nil {
return nil, errcode.DatabaseError
}
items := make([]dto.OrderItem, 0, len(list))
for i := range list {
items = append(items, buildOrderItem(&list[i]))
}
return &dto.OrderListResponse{
PageResponse: dto.PageResponse{Total: total, Page: page, PageSize: pageSize},
List: items,
}, nil
}
// Get 订单详情。
func (s *OrderService) Get(ctx context.Context, orderNo string) (*dto.OrderItem, *errcode.Error) {
ctx, span := global.Tracer.Start(ctx, "svc.AdminOrderGet")
defer span.End()
order, err := s.orderDao.OrderGetByOrderNo(ctx, orderNo)
if err != nil {
if isNotFound(err) {
return nil, errcode.OrderNotFound
}
return nil, errcode.DatabaseError
}
item := buildOrderItem(order)
return &item, nil
}
// RefundList 分页查询退款单。
func (s *OrderService) RefundList(ctx context.Context, request *dto.RefundListRequest) (*dto.RefundListResponse, *errcode.Error) {
ctx, span := global.Tracer.Start(ctx, "svc.AdminRefundList")
defer span.End()
page, pageSize := normalizePage(request.Page, request.PageSize)
// status 未传时 query 绑定为 nil,与「待处理=0」天然区分。
withStatus := request.Status != nil
var status int8
if withStatus {
status = *request.Status
}
list, total, err := s.refundDao.RefundList(ctx, mysqldao.RefundListParams{
Status: status,
WithStatus: withStatus,
OrderNo: strings.TrimSpace(request.OrderNo),
Page: page,
Size: pageSize,
})
if err != nil {
return nil, errcode.DatabaseError
}
items := make([]dto.RefundItem, 0, len(list))
for i := range list {
items = append(items, buildRefundItem(&list[i]))
}
return &dto.RefundListResponse{
PageResponse: dto.PageResponse{Total: total, Page: page, PageSize: pageSize},
List: items,
}, nil
}
// RefundApprove 审核通过:待处理(0) → 退款中(1)。
// 实际打款由运营在微信商户平台人工完成,业务服务退款回调负责后续「成功(2)」流转与权益回收。
func (s *OrderService) RefundApprove(ctx context.Context, adminID int64, refundNo string, request *dto.RefundAuditRequest) *errcode.Error {
ctx, span := global.Tracer.Start(ctx, "svc.AdminRefundApprove")
defer span.End()
if _, err := s.refundDao.RefundGetByRefundNo(ctx, refundNo); err != nil {
if isNotFound(err) {
return errcode.RefundNotFound
}
return errcode.DatabaseError
}
ok, err := s.refundDao.RefundTransition(ctx, refundNo, RefundStatusPending, RefundStatusProcessing, "")
if err != nil {
return errcode.DatabaseError
}
if !ok {
return errcode.RefundStatusInvalid.WithDetails("仅待处理状态的退款单可审核")
}
return nil
}
// RefundReject 审核驳回:待处理(0) → 失败(3),记录驳回原因。
func (s *OrderService) RefundReject(ctx context.Context, adminID int64, refundNo string, request *dto.RefundAuditRequest) *errcode.Error {
ctx, span := global.Tracer.Start(ctx, "svc.AdminRefundReject")
defer span.End()
if _, err := s.refundDao.RefundGetByRefundNo(ctx, refundNo); err != nil {
if isNotFound(err) {
return errcode.RefundNotFound
}
return errcode.DatabaseError
}
ok, err := s.refundDao.RefundTransition(ctx, refundNo, RefundStatusPending, RefundStatusFailed, strings.TrimSpace(request.Reason))
if err != nil {
return errcode.DatabaseError
}
if !ok {
return errcode.RefundStatusInvalid.WithDetails("仅待处理状态的退款单可审核")
}
return nil
}
// buildOrderItem 订单模型 → 响应。
func buildOrderItem(o *mysqlmodel.Order) dto.OrderItem {
return dto.OrderItem{
OrderNo: o.OrderNo,
UserID: o.UserID,
GoodsID: o.GoodsID,
GoodsName: o.GoodsName,
UnitPrice: o.UnitPrice,
TotalAmount: o.TotalAmount,
PayAmount: o.PayAmount,
PayChannel: o.PayChannel,
OrderStatus: o.OrderStatus,
TransactionID: o.TransactionID,
MemberDays: o.MemberDays,
GoodsType: o.GoodsType,
QuotaValidDays: o.QuotaValidDays,
UploadQuota: o.CosQuota,
AIRecommendQuota: o.AIRecommendQuota,
ClosetShareQuota: o.ClosetShareQuota,
OutfitShareQuota: o.OutfitShareQuota,
ExpireTime: o.ExpireTime,
PayTime: o.PayTime,
RefundTime: o.RefundTime,
RefundAmount: o.RefundAmount,
Remark: o.Remark,
XPayProductID: o.XPayProductID,
XPayPlatform: o.XPayPlatform,
XPayEnv: o.XPayEnv,
CreatedAt: o.CreatedAt,
}
}
// buildRefundItem 退款单模型 → 响应。
func buildRefundItem(r *mysqlmodel.RefundFlowLog) dto.RefundItem {
return dto.RefundItem{
RefundNo: r.RefundNo,
OrderNo: r.OrderNo,
UserID: r.UserID,
RefundAmount: r.RefundAmount,
RefundReason: r.RefundReason,
Status: r.Status,
ApplyTime: r.ApplyTime,
SuccessTime: r.SuccessTime,
FailReason: r.FailReason,
CreatedAt: r.CreatedAt,
}
}