Files
hfb_sys/backend/internal/modules/order/presenter.go
T
yml2213 c92cd03b8c 退款审核功能:待交接取消不再自动退款,改为客服审核后原路退回
- 租客在待交接状态取消订单不再自动退款,改为 refund_status=pending_review
- 新增退款审核页面 /admin/orders/refund-review,客服可逐个审核
- 新增 POST /refund/approve 通过退款、POST /refund/reject 驳回退款
- 新增 GET /orders/refund-pending 查询待审核退款订单列表
- 优化状态标签表述:已取消→租客已取消、已关闭→客服已关闭、异常→客服介入
- 新增 refund_status: pending_review 及其前端标签「待客服审核退款」
- AdminOrderDetailView 退款状态改用统一 refundStatusLabel
2026-06-28 21:14:55 +08:00

276 lines
8.5 KiB
Go

package order
import (
"encoding/json"
"hfb_sys/backend/internal/model"
"gorm.io/datatypes"
)
func adminActionsForOrder(order model.RentalOrder) *AdminActionsDTO {
target, ok := resetTargetForOrder(order)
if !ok {
return nil
}
return &AdminActionsDTO{
ResetHandoff: &AdminActionDTO{
Enabled: true,
Label: target.Label,
},
}
}
func (row orderRow) toAdminDTO() OrderDTO {
rentedAt := row.RentedAt
durationHours := orderDurationHours(row.RentalOrder)
estimatedEndAt := orderEstimatedEndAt(row.RentalOrder)
rentAmountCent := row.RentAmountCent
ownerRentAmountCent := row.OwnerRentAmountCent
platformFeeCent := row.PlatformFeeCent
return OrderDTO{
ID: row.ID,
OrderNo: row.OrderNo,
ListingID: row.ListingID,
ListingNo: row.ListingNo,
AccountID: row.AccountID,
OwnerID: row.OwnerID,
RenterID: row.RenterID,
OwnerPhone: row.OwnerPhone,
RenterPhone: row.RenterPhone,
Title: row.Title,
ServerRegion: row.ServerRegion,
LoginPlatform: row.LoginPlatform,
RentedAt: rentedAt,
EstimatedDurationHours: durationHours,
EstimatedEndAt: estimatedEndAt,
PriceRole: "admin",
DisplayAmountCent: row.RentAmountCent,
RentAmountCent: &rentAmountCent,
OwnerRentAmountCent: &ownerRentAmountCent,
DepositAmountCent: row.DepositAmountCent,
DepositOriginalAmountCent: effectiveDepositOriginalAmountCent(row.RentalOrder),
DepositWaivedAmountCent: row.DepositWaivedAmountCent,
PlatformFeeCent: &platformFeeCent,
AccountSnapshot: row.AccountSnapshot,
Status: row.Status,
HandoffStatus: row.HandoffStatus,
SettlementStatus: row.SettlementStatus,
RefundStatus: row.RefundStatus,
RefundAmountCent: row.RefundAmountCent,
AdminActions: adminActionsForOrder(row.RentalOrder),
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
}
}
func (row orderRow) toDTOForUser(userID uint64) OrderDTO {
dto := row.toAdminDTO()
dto.AdminActions = nil
applyOrderPriceView(&dto, row.RentalOrder, userID)
return dto
}
func effectiveDepositOriginalAmountCent(order model.RentalOrder) int64 {
if order.DepositOriginalAmountCent > 0 {
return order.DepositOriginalAmountCent
}
return order.DepositAmountCent
}
func toCheckoutAdminDTO(checkout model.OrderCheckout) CheckoutDTO {
rentAmountCent := checkout.RentAmountCent
ownerRentAmountCent := checkout.OwnerRentAmountCent
platformFeeCent := checkout.PlatformFeeCent
renterRefundAmountCent := checkout.RenterRefundAmountCent
ownerIncomeAmountCent := checkout.OwnerIncomeAmountCent
return CheckoutDTO{
ID: checkout.ID,
OrderID: checkout.OrderID,
InitiatedBy: checkout.InitiatedBy,
Status: checkout.Status,
PriceRole: "admin",
DisplayAmountCent: rentAmountCent,
RentAmountCent: &rentAmountCent,
OwnerRentAmountCent: &ownerRentAmountCent,
PlatformFeeCent: &platformFeeCent,
DepositAmountCent: checkout.DepositAmountCent,
ConsumableAmountCent: checkout.ConsumableAmountCent,
CoinConsumedM: checkout.CoinConsumedM,
OtherAmountCent: checkout.OtherAmountCent,
DepositDeductAmountCent: checkout.DepositDeductAmountCent,
RenterRefundAmountCent: &renterRefundAmountCent,
OwnerIncomeAmountCent: &ownerIncomeAmountCent,
Content: checkout.Content,
EvidenceURLS: decodeStringList(checkout.EvidenceURLS),
OwnerAdjustmentReason: checkout.OwnerAdjustmentReason,
OwnerAdjustedAt: checkout.OwnerAdjustedAt,
RenterConfirmedAt: checkout.RenterConfirmedAt,
RenterRejectedAt: checkout.RenterRejectedAt,
CreatedAt: checkout.CreatedAt,
UpdatedAt: checkout.UpdatedAt,
}
}
func toCheckoutDTOForUser(checkout model.OrderCheckout, userID uint64, order model.RentalOrder) CheckoutDTO {
dto := toCheckoutAdminDTO(checkout)
applyCheckoutPriceView(&dto, order, userID)
return dto
}
func applyOrderPriceView(dto *OrderDTO, order model.RentalOrder, userID uint64) {
if dto == nil {
return
}
dto.PlatformFeeCent = nil
switch {
case userID == order.OwnerID:
ownerAmountCent := order.OwnerRentAmountCent
if ownerAmountCent <= 0 {
ownerAmountCent = order.RentAmountCent
}
dto.PriceRole = "owner"
dto.DisplayAmountCent = ownerAmountCent
dto.RentAmountCent = nil
dto.OwnerRentAmountCent = &ownerAmountCent
sanitizeOrderSnapshot(&dto.AccountSnapshot, "owner")
case userID == order.RenterID:
rentAmountCent := order.RentAmountCent
dto.PriceRole = "renter"
dto.DisplayAmountCent = rentAmountCent
dto.RentAmountCent = &rentAmountCent
dto.OwnerRentAmountCent = nil
sanitizeOrderSnapshot(&dto.AccountSnapshot, "renter")
default:
dto.PriceRole = ""
dto.DisplayAmountCent = 0
dto.RentAmountCent = nil
dto.OwnerRentAmountCent = nil
sanitizeOrderSnapshot(&dto.AccountSnapshot, "")
}
}
func applyCheckoutPriceView(dto *CheckoutDTO, order model.RentalOrder, userID uint64) {
if dto == nil {
return
}
ownerAmountCent := int64(0)
if dto.OwnerRentAmountCent != nil {
ownerAmountCent = *dto.OwnerRentAmountCent
}
ownerIncomeAmountCent := int64(0)
if dto.OwnerIncomeAmountCent != nil {
ownerIncomeAmountCent = *dto.OwnerIncomeAmountCent
}
rentAmountCent := int64(0)
if dto.RentAmountCent != nil {
rentAmountCent = *dto.RentAmountCent
}
renterRefundAmountCent := int64(0)
if dto.RenterRefundAmountCent != nil {
renterRefundAmountCent = *dto.RenterRefundAmountCent
}
dto.PlatformFeeCent = nil
dto.RenterRefundAmountCent = nil
dto.OwnerIncomeAmountCent = nil
switch {
case userID == order.OwnerID:
dto.PriceRole = "owner"
dto.DisplayAmountCent = ownerAmountCent
dto.RentAmountCent = nil
dto.OwnerRentAmountCent = &ownerAmountCent
dto.OwnerIncomeAmountCent = &ownerIncomeAmountCent
case userID == order.RenterID:
dto.PriceRole = "renter"
dto.DisplayAmountCent = rentAmountCent
dto.RentAmountCent = &rentAmountCent
dto.OwnerRentAmountCent = nil
dto.RenterRefundAmountCent = &renterRefundAmountCent
default:
dto.PriceRole = ""
dto.DisplayAmountCent = 0
dto.RentAmountCent = nil
dto.OwnerRentAmountCent = nil
}
}
func sanitizeOrderSnapshot(snapshot *datatypes.JSON, role string) {
if snapshot == nil || len(*snapshot) == 0 {
return
}
var payload map[string]any
if err := json.Unmarshal(*snapshot, &payload); err != nil {
return
}
rawSummary, ok := payload["asset_summary"]
if !ok {
return
}
var summary map[string]any
switch typed := rawSummary.(type) {
case map[string]any:
summary = typed
case string:
if err := json.Unmarshal([]byte(typed), &summary); err != nil {
return
}
default:
raw, err := json.Marshal(typed)
if err != nil || json.Unmarshal(raw, &summary) != nil {
return
}
}
breakdown, _ := summary["price_breakdown"].(map[string]any)
if role == "owner" && breakdown != nil {
if sellerRatio := readJSONNumber(breakdown["seller_ratio"]); sellerRatio > 0 {
summary["publish_ratio"] = sellerRatio
}
}
delete(summary, "price_breakdown")
payload["asset_summary"] = summary
raw, err := json.Marshal(payload)
if err != nil {
return
}
*snapshot = datatypes.JSON(raw)
}
func marshalStringList(items []string) (datatypes.JSON, error) {
if items == nil {
items = []string{}
}
raw, err := json.Marshal(items)
return datatypes.JSON(raw), err
}
func decodeStringList(raw datatypes.JSON) []string {
if len(raw) == 0 {
return []string{}
}
var items []string
if err := json.Unmarshal(raw, &items); err != nil {
return []string{}
}
return items
}
func makeAccountSnapshot(account model.GameAccount, listing model.RentalListing) (datatypes.JSON, error) {
payload := map[string]any{
"listing_id": listing.ID,
"listing_no": listing.ListingNo,
"account_id": account.ID,
"title": account.Title,
"game_name": account.GameName,
"server_region": account.ServerRegion,
"login_platform": account.LoginPlatform,
"rank_level": account.RankLevel,
"haf_coin_amount": account.HafCoinAmount,
"asset_summary": account.AssetSummary,
"season_tags": account.SeasonTags,
"screenshot_urls": account.ScreenshotURLS,
"snapshot_version": 1,
}
raw, err := json.Marshal(payload)
return datatypes.JSON(raw), err
}