统一金额分制重构
This commit is contained in:
@@ -33,9 +33,7 @@ type RentalListing struct {
|
||||
ListingNo string `gorm:"column:listing_no;size:20;not null;uniqueIndex" json:"listing_no"`
|
||||
AccountID uint64 `gorm:"not null;index" json:"account_id"`
|
||||
OwnerID uint64 `gorm:"not null;index" json:"owner_id"`
|
||||
Price float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
PriceCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
DepositAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
DepositAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
InTransaction bool `gorm:"not null;default:false" json:"in_transaction"`
|
||||
Status string `gorm:"size:32;not null;default:'draft'" json:"status"`
|
||||
|
||||
@@ -15,17 +15,11 @@ type RentalOrder struct {
|
||||
RenterID uint64 `gorm:"not null;index" json:"renter_id"`
|
||||
RentedAt *time.Time `json:"rented_at"`
|
||||
EstimatedDurationHours int `gorm:"not null;default:24" json:"estimated_duration_hours"`
|
||||
RentAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
RentAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
OwnerRentAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
OwnerRentAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
DepositAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
DepositAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
DepositOriginalAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
DepositOriginalAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
DepositWaivedAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
DepositWaivedAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
PlatformFee float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
PlatformFeeCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
AccountSnapshot datatypes.JSON `json:"account_snapshot"`
|
||||
Status string `gorm:"size:32;not null;default:'pending_payment'" json:"status"`
|
||||
|
||||
@@ -11,24 +11,15 @@ type OrderCheckout struct {
|
||||
OrderID uint64 `gorm:"not null;index" json:"order_id"`
|
||||
InitiatedBy uint64 `gorm:"not null" json:"initiated_by"`
|
||||
Status string `gorm:"size:32;not null;default:'submitted'" json:"status"`
|
||||
RentAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
RentAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
OwnerRentAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
OwnerRentAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
PlatformFee float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
PlatformFeeCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
DepositAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
DepositAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
ConsumableAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
ConsumableAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
CoinConsumedM float64 `gorm:"type:decimal(12,2);not null;default:0" json:"coin_consumed_m"`
|
||||
OtherAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
OtherAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
DepositDeductAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
DepositDeductAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
RenterRefundAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
RenterRefundAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
OwnerIncomeAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
OwnerIncomeAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
Content string `json:"content"`
|
||||
EvidenceURLS datatypes.JSON `gorm:"column:evidence_urls" json:"evidence_urls"`
|
||||
|
||||
@@ -10,7 +10,6 @@ type User struct {
|
||||
RealnameStatus string `gorm:"size:32;not null;default:'unverified'" json:"realname_status"`
|
||||
RiskStatus string `gorm:"size:32;not null;default:'normal'" json:"risk_status"`
|
||||
CreditScore int `gorm:"not null;default:100" json:"credit_score"`
|
||||
DepositFreeQuota float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
DepositFreeQuotaCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
Status string `gorm:"size:32;not null;default:'active'" json:"status"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
|
||||
@@ -5,9 +5,7 @@ import "time"
|
||||
type WalletAccount struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
UserID uint64 `gorm:"not null;uniqueIndex" json:"user_id"`
|
||||
AvailableBalance float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
AvailableBalanceCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
FrozenBalance float64 `gorm:"type:decimal(12,2);not null;default:0" json:"-"`
|
||||
FrozenBalanceCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
Status string `gorm:"size:32;not null;default:'active'" json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
@@ -24,9 +22,7 @@ type WalletLedger struct {
|
||||
UserID uint64 `gorm:"not null;index" json:"user_id"`
|
||||
OrderID *uint64 `json:"order_id"`
|
||||
Direction string `gorm:"size:16;not null" json:"direction"`
|
||||
Amount float64 `gorm:"type:decimal(12,2);not null" json:"-"`
|
||||
AmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
BalanceAfter float64 `gorm:"type:decimal(12,2);not null" json:"-"`
|
||||
BalanceAfterCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
BalanceType string `gorm:"size:32;not null" json:"balance_type"`
|
||||
BizType string `gorm:"size:32;not null" json:"biz_type"`
|
||||
|
||||
@@ -6,11 +6,8 @@ type WithdrawalRequest struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
WithdrawNo string `gorm:"size:64;not null;uniqueIndex" json:"withdraw_no"`
|
||||
UserID uint64 `gorm:"not null;index" json:"user_id"`
|
||||
Amount float64 `gorm:"type:decimal(12,2);not null" json:"-"`
|
||||
AmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
Fee float64 `gorm:"type:decimal(12,2);not null;default:0.00" json:"-"`
|
||||
FeeCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
ActualAmount float64 `gorm:"type:decimal(12,2);not null" json:"-"`
|
||||
ActualAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
PaymentAccountID *uint64 `json:"payment_account_id"`
|
||||
AccountType string `gorm:"size:32;not null" json:"account_type"`
|
||||
|
||||
@@ -18,7 +18,7 @@ type MetricsDTO struct {
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
RentingOrders int64 `json:"renting_orders"`
|
||||
TodayOrders int64 `json:"today_orders"`
|
||||
TodayLedgerAmount float64 `json:"today_ledger_amount"`
|
||||
TodayLedgerAmountCent int64 `json:"today_ledger_amount_cent"`
|
||||
}
|
||||
|
||||
type PendingDTO struct {
|
||||
@@ -35,8 +35,8 @@ type RecentOrderDTO struct {
|
||||
RenterID uint64 `json:"renter_id"`
|
||||
OwnerID uint64 `json:"owner_id"`
|
||||
Status string `json:"status"`
|
||||
RentAmount float64 `json:"rent_amount"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
RentAmountCent int64 `json:"rent_amount_cent"`
|
||||
DepositAmountCent int64 `json:"deposit_amount_cent"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
|
||||
@@ -44,9 +44,9 @@ func (r *Repository) Summary() (*DashboardDTO, error) {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.db.Model(&model.WalletLedger{}).
|
||||
Select("COALESCE(SUM(amount), 0)").
|
||||
Select("COALESCE(SUM(amount_cent), 0)").
|
||||
Where("created_at >= ?", today).
|
||||
Scan(&metrics.TodayLedgerAmount).Error; err != nil {
|
||||
Scan(&metrics.TodayLedgerAmountCent).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.db.Model(&model.RentalListing{}).Where("review_status = ?", "pending").Count(&pending.ListingReviews).Error; err != nil {
|
||||
@@ -81,7 +81,7 @@ func (r *Repository) Summary() (*DashboardDTO, error) {
|
||||
func (r *Repository) recentOrders() ([]RecentOrderDTO, error) {
|
||||
rows := make([]RecentOrderDTO, 0)
|
||||
err := r.db.Table("rental_orders AS o").
|
||||
Select("o.id, o.order_no, a.title, o.renter_id, o.owner_id, o.status, o.rent_amount, o.deposit_amount, o.created_at").
|
||||
Select("o.id, o.order_no, a.title, o.renter_id, o.owner_id, o.status, o.rent_amount_cent, o.deposit_amount_cent, o.created_at").
|
||||
Joins("JOIN game_accounts AS a ON a.id = o.account_id").
|
||||
Order("o.id DESC").
|
||||
Limit(8).
|
||||
|
||||
@@ -30,10 +30,10 @@ type FinanceSummaryDTO struct {
|
||||
TotalRefundAmountCent int64 `json:"total_refund_amount_cent"`
|
||||
PendingRefundAmountCent int64 `json:"pending_refund_amount_cent"`
|
||||
ChannelNetAmountCent int64 `json:"channel_net_amount_cent"`
|
||||
PlatformIncomeAmount float64 `json:"platform_income_amount"`
|
||||
OwnerShouldIncomeAmount float64 `json:"owner_should_income_amount"`
|
||||
OwnerWalletIncomeAmount float64 `json:"owner_wallet_income_amount"`
|
||||
SettlementDiffAmount float64 `json:"settlement_diff_amount"`
|
||||
PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"`
|
||||
OwnerShouldIncomeAmountCent int64 `json:"owner_should_income_amount_cent"`
|
||||
OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"`
|
||||
SettlementDiffAmountCent int64 `json:"settlement_diff_amount_cent"`
|
||||
SuccessfulPayCount int64 `json:"successful_pay_count"`
|
||||
SuccessfulRefundCount int64 `json:"successful_refund_count"`
|
||||
PendingRefundCount int64 `json:"pending_refund_count"`
|
||||
@@ -47,10 +47,10 @@ type FinanceDailyDTO struct {
|
||||
TotalRefundAmountCent int64 `json:"total_refund_amount_cent"`
|
||||
PendingRefundAmountCent int64 `json:"pending_refund_amount_cent"`
|
||||
ChannelNetAmountCent int64 `json:"channel_net_amount_cent"`
|
||||
PlatformIncomeAmount float64 `json:"platform_income_amount"`
|
||||
OwnerShouldIncomeAmount float64 `json:"owner_should_income_amount"`
|
||||
OwnerWalletIncomeAmount float64 `json:"owner_wallet_income_amount"`
|
||||
SettlementDiffAmount float64 `json:"settlement_diff_amount"`
|
||||
PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"`
|
||||
OwnerShouldIncomeAmountCent int64 `json:"owner_should_income_amount_cent"`
|
||||
OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"`
|
||||
SettlementDiffAmountCent int64 `json:"settlement_diff_amount_cent"`
|
||||
SuccessfulPayCount int64 `json:"successful_pay_count"`
|
||||
SuccessfulRefundCount int64 `json:"successful_refund_count"`
|
||||
PendingRefundCount int64 `json:"pending_refund_count"`
|
||||
@@ -76,20 +76,20 @@ type FinanceDetailDTO struct {
|
||||
OwnerID uint64 `json:"owner_id"`
|
||||
OwnerPhone string `json:"owner_phone"`
|
||||
OwnerNickname string `json:"owner_nickname"`
|
||||
OrderRentAmount float64 `json:"order_rent_amount"`
|
||||
OrderDepositAmount float64 `json:"order_deposit_amount"`
|
||||
CheckoutRentAmount float64 `json:"checkout_rent_amount"`
|
||||
CheckoutRenterRefund float64 `json:"checkout_renter_refund"`
|
||||
CheckoutOwnerIncome float64 `json:"checkout_owner_income"`
|
||||
CheckoutPlatformFee float64 `json:"checkout_platform_fee"`
|
||||
OwnerWalletIncomeAmount float64 `json:"owner_wallet_income_amount"`
|
||||
OrderRentAmountCent int64 `json:"order_rent_amount_cent"`
|
||||
OrderDepositAmountCent int64 `json:"order_deposit_amount_cent"`
|
||||
CheckoutRentAmountCent int64 `json:"checkout_rent_amount_cent"`
|
||||
CheckoutRenterRefundCent int64 `json:"checkout_renter_refund_cent"`
|
||||
CheckoutOwnerIncomeCent int64 `json:"checkout_owner_income_cent"`
|
||||
CheckoutPlatformFeeCent int64 `json:"checkout_platform_fee_cent"`
|
||||
OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"`
|
||||
PaidAmountCent int64 `json:"paid_amount_cent"`
|
||||
RefundedAmountCent int64 `json:"refunded_amount_cent"`
|
||||
RefundingAmountCent int64 `json:"refunding_amount_cent"`
|
||||
FailedRefundAmountCent int64 `json:"failed_refund_amount_cent"`
|
||||
ChannelNetAmountCent int64 `json:"channel_net_amount_cent"`
|
||||
PlatformNetAmount float64 `json:"platform_net_amount"`
|
||||
SettlementDiffAmount float64 `json:"settlement_diff_amount"`
|
||||
PlatformNetAmountCent int64 `json:"platform_net_amount_cent"`
|
||||
SettlementDiffAmountCent int64 `json:"settlement_diff_amount_cent"`
|
||||
FinanceStatus string `json:"finance_status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
SettledAt *time.Time `json:"settled_at,omitempty"`
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package adminfinance
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/timeutil"
|
||||
"hfb_sys/backend/pkg/money"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -71,9 +69,9 @@ func (r *Repository) summary(query DashboardQuery) (*FinanceSummaryDTO, error) {
|
||||
|
||||
var settlement settlementSummaryRow
|
||||
if err := r.db.Table("rental_orders AS ro").
|
||||
Select(`COALESCE(SUM(oc.platform_fee), 0) AS platform_income_amount,
|
||||
COALESCE(SUM(oc.owner_income_amount), 0) AS owner_should_income_amount,
|
||||
COALESCE(SUM(COALESCE(w.owner_wallet_income_amount, 0)), 0) AS owner_wallet_income_amount,
|
||||
Select(`COALESCE(SUM(oc.platform_fee_cent), 0) AS platform_income_amount_cent,
|
||||
COALESCE(SUM(oc.owner_income_amount_cent), 0) AS owner_should_income_amount_cent,
|
||||
COALESCE(SUM(COALESCE(w.owner_wallet_income_amount_cent, 0)), 0) AS owner_wallet_income_amount_cent,
|
||||
COUNT(ro.id) AS settled_order_count`).
|
||||
Joins("JOIN order_checkouts AS oc ON oc.order_id = ro.id AND oc.status = 'accepted'").
|
||||
Joins("LEFT JOIN (?) AS w ON w.order_id = ro.id", ownerWalletIncomeSubquery(r.db)).
|
||||
@@ -98,10 +96,10 @@ func (r *Repository) summary(query DashboardQuery) (*FinanceSummaryDTO, error) {
|
||||
TotalRefundAmountCent: payment.TotalRefundAmountCent,
|
||||
PendingRefundAmountCent: payment.PendingRefundAmountCent,
|
||||
ChannelNetAmountCent: payment.TotalFlowAmountCent - payment.TotalRefundAmountCent,
|
||||
PlatformIncomeAmount: money.Round(settlement.PlatformIncomeAmount),
|
||||
OwnerShouldIncomeAmount: money.Round(settlement.OwnerShouldIncomeAmount),
|
||||
OwnerWalletIncomeAmount: money.Round(settlement.OwnerWalletIncomeAmount),
|
||||
SettlementDiffAmount: money.Round(settlement.OwnerShouldIncomeAmount - settlement.OwnerWalletIncomeAmount),
|
||||
PlatformIncomeAmountCent: settlement.PlatformIncomeAmountCent,
|
||||
OwnerShouldIncomeAmountCent: settlement.OwnerShouldIncomeAmountCent,
|
||||
OwnerWalletIncomeAmountCent: settlement.OwnerWalletIncomeAmountCent,
|
||||
SettlementDiffAmountCent: settlement.OwnerShouldIncomeAmountCent - settlement.OwnerWalletIncomeAmountCent,
|
||||
SuccessfulPayCount: payment.SuccessfulPayCount,
|
||||
SuccessfulRefundCount: payment.SuccessfulRefundCount,
|
||||
PendingRefundCount: payment.PendingRefundCount,
|
||||
@@ -130,9 +128,9 @@ func (r *Repository) dailyItems(query DashboardQuery) ([]FinanceDailyDTO, error)
|
||||
settlements := make([]dailySettlementRow, 0)
|
||||
if err := r.db.Table("rental_orders AS ro").
|
||||
Select(`DATE(ro.settled_at) AS date,
|
||||
COALESCE(SUM(oc.platform_fee), 0) AS platform_income_amount,
|
||||
COALESCE(SUM(oc.owner_income_amount), 0) AS owner_should_income_amount,
|
||||
COALESCE(SUM(COALESCE(w.owner_wallet_income_amount, 0)), 0) AS owner_wallet_income_amount,
|
||||
COALESCE(SUM(oc.platform_fee_cent), 0) AS platform_income_amount_cent,
|
||||
COALESCE(SUM(oc.owner_income_amount_cent), 0) AS owner_should_income_amount_cent,
|
||||
COALESCE(SUM(COALESCE(w.owner_wallet_income_amount_cent, 0)), 0) AS owner_wallet_income_amount_cent,
|
||||
COUNT(ro.id) AS settled_order_count`).
|
||||
Joins("JOIN order_checkouts AS oc ON oc.order_id = ro.id AND oc.status = 'accepted'").
|
||||
Joins("LEFT JOIN (?) AS w ON w.order_id = ro.id", ownerWalletIncomeSubquery(r.db)).
|
||||
@@ -162,10 +160,10 @@ func (r *Repository) dailyItems(query DashboardQuery) ([]FinanceDailyDTO, error)
|
||||
for _, row := range settlements {
|
||||
item := itemsByDate[row.Date]
|
||||
item.Date = row.Date
|
||||
item.PlatformIncomeAmount = money.Round(row.PlatformIncomeAmount)
|
||||
item.OwnerShouldIncomeAmount = money.Round(row.OwnerShouldIncomeAmount)
|
||||
item.OwnerWalletIncomeAmount = money.Round(row.OwnerWalletIncomeAmount)
|
||||
item.SettlementDiffAmount = money.Round(row.OwnerShouldIncomeAmount - row.OwnerWalletIncomeAmount)
|
||||
item.PlatformIncomeAmountCent = row.PlatformIncomeAmountCent
|
||||
item.OwnerShouldIncomeAmountCent = row.OwnerShouldIncomeAmountCent
|
||||
item.OwnerWalletIncomeAmountCent = row.OwnerWalletIncomeAmountCent
|
||||
item.SettlementDiffAmountCent = row.OwnerShouldIncomeAmountCent - row.OwnerWalletIncomeAmountCent
|
||||
item.SettledOrderCount = row.SettledOrderCount
|
||||
itemsByDate[row.Date] = item
|
||||
}
|
||||
@@ -182,23 +180,23 @@ func (r *Repository) financeDetailBaseQuery(query DetailQuery) *gorm.DB {
|
||||
Select(`ro.id AS order_id, ro.order_no, ro.status AS order_status, ro.settlement_status, ro.refund_status,
|
||||
ro.renter_id, COALESCE(ru.phone, '') AS renter_phone, COALESCE(ru.nickname, '') AS renter_nickname,
|
||||
ro.owner_id, COALESCE(ou.phone, '') AS owner_phone, COALESCE(ou.nickname, '') AS owner_nickname,
|
||||
ro.rent_amount AS order_rent_amount, ro.deposit_amount AS order_deposit_amount,
|
||||
COALESCE(oc.rent_amount, 0) AS checkout_rent_amount,
|
||||
COALESCE(oc.renter_refund_amount, 0) AS checkout_renter_refund,
|
||||
COALESCE(oc.owner_income_amount, 0) AS checkout_owner_income,
|
||||
COALESCE(oc.platform_fee, 0) AS checkout_platform_fee,
|
||||
COALESCE(w.owner_wallet_income_amount, 0) AS owner_wallet_income_amount,
|
||||
ro.rent_amount_cent AS order_rent_amount_cent, ro.deposit_amount_cent AS order_deposit_amount_cent,
|
||||
COALESCE(oc.rent_amount_cent, 0) AS checkout_rent_amount_cent,
|
||||
COALESCE(oc.renter_refund_amount_cent, 0) AS checkout_renter_refund_cent,
|
||||
COALESCE(oc.owner_income_amount_cent, 0) AS checkout_owner_income_cent,
|
||||
COALESCE(oc.platform_fee_cent, 0) AS checkout_platform_fee_cent,
|
||||
COALESCE(w.owner_wallet_income_amount_cent, 0) AS owner_wallet_income_amount_cent,
|
||||
COALESCE(p.paid_amount_cent, 0) AS paid_amount_cent,
|
||||
COALESCE(p.refunded_amount_cent, 0) AS refunded_amount_cent,
|
||||
COALESCE(p.refunding_amount_cent, 0) AS refunding_amount_cent,
|
||||
COALESCE(p.failed_refund_amount_cent, 0) AS failed_refund_amount_cent,
|
||||
COALESCE(p.paid_amount_cent, 0) - COALESCE(p.refunded_amount_cent, 0) AS channel_net_amount_cent,
|
||||
COALESCE(oc.platform_fee, 0) + ((COALESCE(oc.owner_income_amount, 0) - COALESCE(w.owner_wallet_income_amount, 0))) AS platform_net_amount,
|
||||
COALESCE(oc.owner_income_amount, 0) - COALESCE(w.owner_wallet_income_amount, 0) AS settlement_diff_amount,
|
||||
COALESCE(oc.platform_fee_cent, 0) + ((COALESCE(oc.owner_income_amount_cent, 0) - COALESCE(w.owner_wallet_income_amount_cent, 0))) AS platform_net_amount_cent,
|
||||
COALESCE(oc.owner_income_amount_cent, 0) - COALESCE(w.owner_wallet_income_amount_cent, 0) AS settlement_diff_amount_cent,
|
||||
CASE
|
||||
WHEN COALESCE(p.failed_refund_amount_cent, 0) > 0 THEN 'refund_failed'
|
||||
WHEN COALESCE(p.refunding_amount_cent, 0) > 0 OR ro.refund_status = 'refunding' THEN 'refund_pending'
|
||||
WHEN ABS(COALESCE(oc.owner_income_amount, 0) - COALESCE(w.owner_wallet_income_amount, 0)) >= 0.05 THEN 'settlement_diff'
|
||||
WHEN ABS(COALESCE(oc.owner_income_amount_cent, 0) - COALESCE(w.owner_wallet_income_amount_cent, 0)) >= 5 THEN 'settlement_diff'
|
||||
ELSE 'normal'
|
||||
END AS finance_status,
|
||||
ro.created_at, ro.settled_at`).
|
||||
@@ -250,7 +248,7 @@ func orderPaymentSubquery(db *gorm.DB) *gorm.DB {
|
||||
|
||||
func ownerWalletIncomeSubquery(db *gorm.DB) *gorm.DB {
|
||||
return db.Table("wallet_ledger").
|
||||
Select("order_id, COALESCE(SUM(amount), 0) AS owner_wallet_income_amount").
|
||||
Select("order_id, COALESCE(SUM(amount_cent), 0) AS owner_wallet_income_amount_cent").
|
||||
Where("direction = ? AND biz_type IN ? AND order_id IS NOT NULL", "in", []string{"owner_income", "deposit_compensation"}).
|
||||
Group("order_id")
|
||||
}
|
||||
@@ -283,9 +281,9 @@ type paymentSummaryRow struct {
|
||||
}
|
||||
|
||||
type settlementSummaryRow struct {
|
||||
PlatformIncomeAmount float64
|
||||
OwnerShouldIncomeAmount float64
|
||||
OwnerWalletIncomeAmount float64
|
||||
PlatformIncomeAmountCent int64
|
||||
OwnerShouldIncomeAmountCent int64
|
||||
OwnerWalletIncomeAmountCent int64
|
||||
SettledOrderCount int64
|
||||
}
|
||||
|
||||
@@ -301,9 +299,9 @@ type dailyPaymentRow struct {
|
||||
|
||||
type dailySettlementRow struct {
|
||||
Date string
|
||||
PlatformIncomeAmount float64
|
||||
OwnerShouldIncomeAmount float64
|
||||
OwnerWalletIncomeAmount float64
|
||||
PlatformIncomeAmountCent int64
|
||||
OwnerShouldIncomeAmountCent int64
|
||||
OwnerWalletIncomeAmountCent int64
|
||||
SettledOrderCount int64
|
||||
}
|
||||
|
||||
@@ -319,32 +317,32 @@ type financeDetailRow struct {
|
||||
OwnerID uint64
|
||||
OwnerPhone string
|
||||
OwnerNickname string
|
||||
OrderRentAmount float64
|
||||
OrderDepositAmount float64
|
||||
CheckoutRentAmount float64
|
||||
CheckoutRenterRefund float64
|
||||
CheckoutOwnerIncome float64
|
||||
CheckoutPlatformFee float64
|
||||
OwnerWalletIncomeAmount float64
|
||||
OrderRentAmountCent int64
|
||||
OrderDepositAmountCent int64
|
||||
CheckoutRentAmountCent int64
|
||||
CheckoutRenterRefundCent int64
|
||||
CheckoutOwnerIncomeCent int64
|
||||
CheckoutPlatformFeeCent int64
|
||||
OwnerWalletIncomeAmountCent int64
|
||||
PaidAmountCent int64
|
||||
RefundedAmountCent int64
|
||||
RefundingAmountCent int64
|
||||
FailedRefundAmountCent int64
|
||||
ChannelNetAmountCent int64
|
||||
PlatformNetAmount float64
|
||||
SettlementDiffAmount float64
|
||||
PlatformNetAmountCent int64
|
||||
SettlementDiffAmountCent int64
|
||||
FinanceStatus string
|
||||
CreatedAt time.Time
|
||||
SettledAt *time.Time
|
||||
}
|
||||
|
||||
func (r financeDetailRow) toDTO() FinanceDetailDTO {
|
||||
diff := money.Round(r.SettlementDiffAmount)
|
||||
diff := r.SettlementDiffAmountCent
|
||||
status := r.FinanceStatus
|
||||
if status == "" {
|
||||
status = "normal"
|
||||
}
|
||||
if math.Abs(diff) < 0.05 && status == "settlement_diff" {
|
||||
if absCent(diff) < 5 && status == "settlement_diff" {
|
||||
status = "normal"
|
||||
}
|
||||
return FinanceDetailDTO{
|
||||
@@ -359,22 +357,29 @@ func (r financeDetailRow) toDTO() FinanceDetailDTO {
|
||||
OwnerID: r.OwnerID,
|
||||
OwnerPhone: r.OwnerPhone,
|
||||
OwnerNickname: r.OwnerNickname,
|
||||
OrderRentAmount: money.Round(r.OrderRentAmount),
|
||||
OrderDepositAmount: money.Round(r.OrderDepositAmount),
|
||||
CheckoutRentAmount: money.Round(r.CheckoutRentAmount),
|
||||
CheckoutRenterRefund: money.Round(r.CheckoutRenterRefund),
|
||||
CheckoutOwnerIncome: money.Round(r.CheckoutOwnerIncome),
|
||||
CheckoutPlatformFee: money.Round(r.CheckoutPlatformFee),
|
||||
OwnerWalletIncomeAmount: money.Round(r.OwnerWalletIncomeAmount),
|
||||
OrderRentAmountCent: r.OrderRentAmountCent,
|
||||
OrderDepositAmountCent: r.OrderDepositAmountCent,
|
||||
CheckoutRentAmountCent: r.CheckoutRentAmountCent,
|
||||
CheckoutRenterRefundCent: r.CheckoutRenterRefundCent,
|
||||
CheckoutOwnerIncomeCent: r.CheckoutOwnerIncomeCent,
|
||||
CheckoutPlatformFeeCent: r.CheckoutPlatformFeeCent,
|
||||
OwnerWalletIncomeAmountCent: r.OwnerWalletIncomeAmountCent,
|
||||
PaidAmountCent: r.PaidAmountCent,
|
||||
RefundedAmountCent: r.RefundedAmountCent,
|
||||
RefundingAmountCent: r.RefundingAmountCent,
|
||||
FailedRefundAmountCent: r.FailedRefundAmountCent,
|
||||
ChannelNetAmountCent: r.ChannelNetAmountCent,
|
||||
PlatformNetAmount: money.Round(r.PlatformNetAmount),
|
||||
SettlementDiffAmount: diff,
|
||||
PlatformNetAmountCent: r.PlatformNetAmountCent,
|
||||
SettlementDiffAmountCent: diff,
|
||||
FinanceStatus: status,
|
||||
CreatedAt: r.CreatedAt,
|
||||
SettledAt: r.SettledAt,
|
||||
}
|
||||
}
|
||||
|
||||
func absCent(value int64) int64 {
|
||||
if value < 0 {
|
||||
return -value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ type UserDTO struct {
|
||||
RealnameStatus string `json:"realname_status"`
|
||||
RiskStatus string `json:"risk_status"`
|
||||
CreditScore int `json:"credit_score"`
|
||||
DepositFreeQuota float64 `json:"deposit_free_quota"`
|
||||
DepositFreeUsed float64 `json:"deposit_free_used"`
|
||||
DepositFreeRemaining float64 `json:"deposit_free_remaining"`
|
||||
DepositFreeQuotaCent int64 `json:"deposit_free_quota_cent"`
|
||||
DepositFreeUsedCent int64 `json:"deposit_free_used_cent"`
|
||||
DepositFreeRemainingCent int64 `json:"deposit_free_remaining_cent"`
|
||||
Status string `json:"status"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
ListingCount int64 `json:"listing_count"`
|
||||
@@ -26,7 +26,6 @@ type FreezeRequest struct {
|
||||
|
||||
type DepositFreeQuotaRequest struct {
|
||||
AmountCent int64 `json:"amount_cent"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
type PaginatedResult struct {
|
||||
|
||||
@@ -2,11 +2,9 @@ package adminuser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"hfb_sys/backend/internal/auditlog"
|
||||
"hfb_sys/backend/internal/model"
|
||||
"hfb_sys/backend/pkg/money"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
@@ -34,11 +32,11 @@ func (r *Repository) List(page, pageSize int) (*PaginatedResult, error) {
|
||||
COALESCE(o.order_count, 0) AS order_count,
|
||||
COALESCE(l.listing_count, 0) AS listing_count,
|
||||
COALESCE(d.dispute_count, 0) AS dispute_count,
|
||||
COALESCE(df.deposit_free_used, 0) AS deposit_free_used`).
|
||||
COALESCE(df.deposit_free_used_cent, 0) AS deposit_free_used_cent`).
|
||||
Joins("LEFT JOIN (SELECT user_id, COUNT(*) AS order_count FROM (SELECT renter_id AS user_id FROM rental_orders UNION ALL SELECT owner_id AS user_id FROM rental_orders) AS order_users GROUP BY user_id) AS o ON o.user_id = u.id").
|
||||
Joins("LEFT JOIN (SELECT owner_id AS user_id, COUNT(*) AS listing_count FROM rental_listings GROUP BY owner_id) AS l ON l.user_id = u.id").
|
||||
Joins("LEFT JOIN (SELECT user_id, COUNT(*) AS dispute_count FROM (SELECT initiator_id AS user_id FROM disputes UNION ALL SELECT target_user_id AS user_id FROM disputes) AS dispute_users GROUP BY user_id) AS d ON d.user_id = u.id").
|
||||
Joins("LEFT JOIN (SELECT renter_id AS user_id, SUM(deposit_waived_amount) AS deposit_free_used FROM rental_orders WHERE status NOT IN ('completed', 'cancelled', 'closed') GROUP BY renter_id) AS df ON df.user_id = u.id").
|
||||
Joins("LEFT JOIN (SELECT renter_id AS user_id, SUM(deposit_waived_amount_cent) AS deposit_free_used_cent FROM rental_orders WHERE status NOT IN ('completed', 'cancelled', 'closed') GROUP BY renter_id) AS df ON df.user_id = u.id").
|
||||
Order("u.id DESC").
|
||||
Offset(offset).Limit(pageSize).
|
||||
Scan(&rows).Error
|
||||
@@ -65,23 +63,18 @@ func (r *Repository) SetDepositFreeQuota(adminID uint64, userID uint64, req Depo
|
||||
if amountCent < 0 {
|
||||
return nil, ErrInvalidUser
|
||||
}
|
||||
amount := float64(amountCent) / 100
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
var user model.User
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&user, userID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
beforeAmount := user.DepositFreeQuota
|
||||
beforeAmountCent := user.DepositFreeQuotaCent
|
||||
user.DepositFreeQuota = amount
|
||||
user.DepositFreeQuotaCent = amountCent
|
||||
if err := tx.Save(&user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return appendAuditLog(tx, adminID, "admin_user.set_deposit_free_quota", user.ID, meta, map[string]any{
|
||||
"user_id": user.ID,
|
||||
"before_amount": beforeAmount,
|
||||
"after_amount": amount,
|
||||
"before_amount_cent": beforeAmountCent,
|
||||
"after_amount_cent": amountCent,
|
||||
})
|
||||
@@ -127,11 +120,11 @@ func (r *Repository) Find(userID uint64) (*UserDTO, error) {
|
||||
COALESCE(o.order_count, 0) AS order_count,
|
||||
COALESCE(l.listing_count, 0) AS listing_count,
|
||||
COALESCE(d.dispute_count, 0) AS dispute_count,
|
||||
COALESCE(df.deposit_free_used, 0) AS deposit_free_used`).
|
||||
COALESCE(df.deposit_free_used_cent, 0) AS deposit_free_used_cent`).
|
||||
Joins("LEFT JOIN (SELECT user_id, COUNT(*) AS order_count FROM (SELECT renter_id AS user_id FROM rental_orders UNION ALL SELECT owner_id AS user_id FROM rental_orders) AS order_users GROUP BY user_id) AS o ON o.user_id = u.id").
|
||||
Joins("LEFT JOIN (SELECT owner_id AS user_id, COUNT(*) AS listing_count FROM rental_listings GROUP BY owner_id) AS l ON l.user_id = u.id").
|
||||
Joins("LEFT JOIN (SELECT user_id, COUNT(*) AS dispute_count FROM (SELECT initiator_id AS user_id FROM disputes UNION ALL SELECT target_user_id AS user_id FROM disputes) AS dispute_users GROUP BY user_id) AS d ON d.user_id = u.id").
|
||||
Joins("LEFT JOIN (SELECT renter_id AS user_id, SUM(deposit_waived_amount) AS deposit_free_used FROM rental_orders WHERE status NOT IN ('completed', 'cancelled', 'closed') GROUP BY renter_id) AS df ON df.user_id = u.id").
|
||||
Joins("LEFT JOIN (SELECT renter_id AS user_id, SUM(deposit_waived_amount_cent) AS deposit_free_used_cent FROM rental_orders WHERE status NOT IN ('completed', 'cancelled', 'closed') GROUP BY renter_id) AS df ON df.user_id = u.id").
|
||||
Where("u.id = ?", userID).
|
||||
First(&row).Error
|
||||
if err != nil {
|
||||
@@ -146,11 +139,11 @@ type userRow struct {
|
||||
OrderCount int64
|
||||
ListingCount int64
|
||||
DisputeCount int64
|
||||
DepositFreeUsed float64
|
||||
DepositFreeUsedCent int64
|
||||
}
|
||||
|
||||
func (row userRow) toDTO() UserDTO {
|
||||
remaining := roundMoney(row.DepositFreeQuota - row.DepositFreeUsed)
|
||||
remaining := row.DepositFreeQuotaCent - row.DepositFreeUsedCent
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
@@ -161,9 +154,9 @@ func (row userRow) toDTO() UserDTO {
|
||||
RealnameStatus: row.RealnameStatus,
|
||||
RiskStatus: row.RiskStatus,
|
||||
CreditScore: row.CreditScore,
|
||||
DepositFreeQuota: row.DepositFreeQuota,
|
||||
DepositFreeUsed: roundMoney(row.DepositFreeUsed),
|
||||
DepositFreeRemaining: remaining,
|
||||
DepositFreeQuotaCent: row.DepositFreeQuotaCent,
|
||||
DepositFreeUsedCent: row.DepositFreeUsedCent,
|
||||
DepositFreeRemainingCent: remaining,
|
||||
Status: row.Status,
|
||||
OrderCount: row.OrderCount,
|
||||
ListingCount: row.ListingCount,
|
||||
@@ -174,15 +167,8 @@ func (row userRow) toDTO() UserDTO {
|
||||
}
|
||||
}
|
||||
|
||||
func roundMoney(value float64) float64 {
|
||||
return money.Round(value)
|
||||
}
|
||||
|
||||
func depositFreeQuotaAmountCent(req DepositFreeQuotaRequest) int64 {
|
||||
if req.AmountCent != 0 {
|
||||
return req.AmountCent
|
||||
}
|
||||
return int64(math.Round(req.Amount * 100))
|
||||
}
|
||||
|
||||
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
||||
|
||||
@@ -9,18 +9,13 @@ func TestDepositFreeQuotaAmountCent(t *testing.T) {
|
||||
want int64
|
||||
}{
|
||||
{
|
||||
name: "优先使用分字段",
|
||||
req: DepositFreeQuotaRequest{AmountCent: 1234, Amount: 99},
|
||||
want: 1234,
|
||||
},
|
||||
{
|
||||
name: "缺少分字段时回退元字段",
|
||||
req: DepositFreeQuotaRequest{Amount: 12.34},
|
||||
name: "使用分字段",
|
||||
req: DepositFreeQuotaRequest{AmountCent: 1234},
|
||||
want: 1234,
|
||||
},
|
||||
{
|
||||
name: "负分拒绝",
|
||||
req: DepositFreeQuotaRequest{AmountCent: -1, Amount: 12.34},
|
||||
req: DepositFreeQuotaRequest{AmountCent: -1},
|
||||
want: -1,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (s *Service) SetDepositFreeQuota(adminID uint64, userID uint64, req Deposit
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if userID == 0 || req.Amount < 0 {
|
||||
if userID == 0 || req.AmountCent < 0 {
|
||||
return nil, ErrInvalidUser
|
||||
}
|
||||
return s.repo.SetDepositFreeQuota(adminID, userID, req, meta)
|
||||
|
||||
@@ -37,7 +37,7 @@ type CreateRequest struct {
|
||||
type ArbitrateRequest struct {
|
||||
Result string `json:"result" binding:"required"`
|
||||
Remark string `json:"remark" binding:"required"`
|
||||
Amount float64 `json:"amount"`
|
||||
AmountCent int64 `json:"amount_cent"`
|
||||
}
|
||||
type AuditMeta = auditlog.Meta
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/auditlog"
|
||||
@@ -249,9 +248,8 @@ func (r *Repository) Arbitrate(adminID uint64, id uint64, req ArbitrateRequest,
|
||||
if err := wallet.AppendEntries(tx, settlement.Entries...); err != nil {
|
||||
return err
|
||||
}
|
||||
if settlement.RenterRefundAmount > 0 {
|
||||
refundCent := int64(math.Round(settlement.RenterRefundAmount * 100))
|
||||
action, err := r.prepareRefund(&order, refundCent, "arbitration_refund", "仲裁退款原路退还")
|
||||
if settlement.RenterRefundAmountCent > 0 {
|
||||
action, err := r.prepareRefund(&order, settlement.RenterRefundAmountCent, "arbitration_refund", "仲裁退款原路退还")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -286,10 +284,10 @@ func (r *Repository) Arbitrate(adminID uint64, id uint64, req ArbitrateRequest,
|
||||
"order_no": order.OrderNo,
|
||||
"result": req.Result,
|
||||
"remark": req.Remark,
|
||||
"input_amount": req.Amount,
|
||||
"renter_refund_amount": settlement.RenterRefundAmount,
|
||||
"owner_income_amount": settlement.OwnerIncomeAmount,
|
||||
"deposit_deduct_amount": settlement.DepositDeductAmount,
|
||||
"input_amount_cent": req.AmountCent,
|
||||
"renter_refund_amount_cent": settlement.RenterRefundAmountCent,
|
||||
"owner_income_amount_cent": settlement.OwnerIncomeAmountCent,
|
||||
"deposit_deduct_amount_cent": settlement.DepositDeductAmountCent,
|
||||
"before_order_status": beforeOrderStatus,
|
||||
"after_order_status": order.Status,
|
||||
"before_handoff_status": beforeHandoffStatus,
|
||||
@@ -339,35 +337,28 @@ func (r *Repository) Arbitrate(adminID uint64, id uint64, req ArbitrateRequest,
|
||||
|
||||
type arbitrationSettlement struct {
|
||||
Entries []wallet.Entry
|
||||
RenterRefundAmount float64
|
||||
OwnerIncomeAmount float64
|
||||
DepositDeductAmount float64
|
||||
RenterRefundAmountCent int64
|
||||
OwnerIncomeAmountCent int64
|
||||
DepositDeductAmountCent int64
|
||||
}
|
||||
|
||||
func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest, renterFrozenBalance float64) (arbitrationSettlement, error) {
|
||||
rentAmount := float64(order.RentAmountCent) / 100
|
||||
if rentAmount <= 0 {
|
||||
rentAmount = order.RentAmount
|
||||
func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest, renterFrozenBalanceCent int64) (arbitrationSettlement, error) {
|
||||
rentAmountCent := order.RentAmountCent
|
||||
depositAmountCent := order.DepositAmountCent
|
||||
ownerRentAmountCent := order.OwnerRentAmountCent
|
||||
if ownerRentAmountCent <= 0 || ownerRentAmountCent > rentAmountCent {
|
||||
ownerRentAmountCent = rentAmountCent
|
||||
}
|
||||
depositAmount := float64(order.DepositAmountCent) / 100
|
||||
if depositAmount <= 0 {
|
||||
depositAmount = order.DepositAmount
|
||||
}
|
||||
ownerRentAmount := float64(order.OwnerRentAmountCent) / 100
|
||||
if ownerRentAmount <= 0 || ownerRentAmount > rentAmount {
|
||||
ownerRentAmount = rentAmount
|
||||
}
|
||||
total := roundMoney(rentAmount + depositAmount)
|
||||
ownerRentAmount = roundMoney(ownerRentAmount)
|
||||
totalCent := rentAmountCent + depositAmountCent
|
||||
settlement := arbitrationSettlement{}
|
||||
orderID := order.ID
|
||||
releaseFrozenAmount := minMoney(total, roundMoney(renterFrozenBalance))
|
||||
if releaseFrozenAmount > 0 {
|
||||
releaseFrozenAmountCent := money.MinCent(totalCent, renterFrozenBalanceCent)
|
||||
if releaseFrozenAmountCent > 0 {
|
||||
settlement.Entries = append(settlement.Entries, wallet.Entry{
|
||||
UserID: order.RenterID,
|
||||
OrderID: &orderID,
|
||||
Direction: "out",
|
||||
AmountCent: int64(math.Round(releaseFrozenAmount * 100)),
|
||||
AmountCent: releaseFrozenAmountCent,
|
||||
BalanceType: "frozen",
|
||||
BizType: "arbitration_release_frozen",
|
||||
BizNo: order.OrderNo,
|
||||
@@ -375,22 +366,22 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest, r
|
||||
})
|
||||
}
|
||||
|
||||
addRenterRefund := func(amount float64, remark string) {
|
||||
if amount <= 0 {
|
||||
addRenterRefund := func(amountCent int64, remark string) {
|
||||
if amountCent <= 0 {
|
||||
return
|
||||
}
|
||||
settlement.RenterRefundAmount += amount
|
||||
settlement.RenterRefundAmountCent += amountCent
|
||||
}
|
||||
addOwnerIncome := func(amount float64, remark string) {
|
||||
if amount <= 0 {
|
||||
addOwnerIncome := func(amountCent int64, remark string) {
|
||||
if amountCent <= 0 {
|
||||
return
|
||||
}
|
||||
settlement.OwnerIncomeAmount += amount
|
||||
settlement.OwnerIncomeAmountCent += amountCent
|
||||
settlement.Entries = append(settlement.Entries, wallet.Entry{
|
||||
UserID: order.OwnerID,
|
||||
OrderID: &orderID,
|
||||
Direction: "in",
|
||||
AmountCent: int64(math.Round(amount * 100)),
|
||||
AmountCent: amountCent,
|
||||
BalanceType: "available",
|
||||
BizType: "arbitration_owner_income",
|
||||
BizNo: order.OrderNo,
|
||||
@@ -400,28 +391,27 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest, r
|
||||
|
||||
switch req.Result {
|
||||
case "full_refund":
|
||||
addRenterRefund(total, "仲裁全额退款")
|
||||
addRenterRefund(totalCent, "仲裁全额退款")
|
||||
case "partial_refund":
|
||||
req.Amount = roundMoney(req.Amount)
|
||||
if req.Amount <= 0 || req.Amount > total {
|
||||
if req.AmountCent <= 0 || req.AmountCent > totalCent {
|
||||
return settlement, ErrInvalidDispute
|
||||
}
|
||||
addRenterRefund(req.Amount, "仲裁部分退款")
|
||||
addOwnerIncome(minMoney(total-req.Amount, ownerRentAmount+depositAmount), "仲裁剩余金额结算给号主")
|
||||
addRenterRefund(req.AmountCent, "仲裁部分退款")
|
||||
addOwnerIncome(money.MinCent(totalCent-req.AmountCent, ownerRentAmountCent+depositAmountCent), "仲裁剩余金额结算给号主")
|
||||
case "release_deposit":
|
||||
addOwnerIncome(ownerRentAmount, "仲裁确认订单金额结算给号主")
|
||||
addRenterRefund(depositAmount, "仲裁释放押金给租客")
|
||||
addOwnerIncome(ownerRentAmountCent, "仲裁确认订单金额结算给号主")
|
||||
addRenterRefund(depositAmountCent, "仲裁释放押金给租客")
|
||||
case "deduct_deposit", "compensate_owner":
|
||||
deductAmount := roundMoney(req.Amount)
|
||||
if deductAmount <= 0 {
|
||||
deductAmount = depositAmount
|
||||
deductAmountCent := req.AmountCent
|
||||
if deductAmountCent <= 0 {
|
||||
deductAmountCent = depositAmountCent
|
||||
}
|
||||
if deductAmount > depositAmount {
|
||||
if deductAmountCent > depositAmountCent {
|
||||
return settlement, ErrInvalidDispute
|
||||
}
|
||||
settlement.DepositDeductAmount = deductAmount
|
||||
addOwnerIncome(ownerRentAmount+deductAmount, "仲裁订单金额及押金赔付结算给号主")
|
||||
addRenterRefund(depositAmount-deductAmount, "仲裁退回剩余押金给租客")
|
||||
settlement.DepositDeductAmountCent = deductAmountCent
|
||||
addOwnerIncome(ownerRentAmountCent+deductAmountCent, "仲裁订单金额及押金赔付结算给号主")
|
||||
addRenterRefund(depositAmountCent-deductAmountCent, "仲裁退回剩余押金给租客")
|
||||
case "order_close":
|
||||
// Only release frozen funds. No available-balance settlement happens in development mode.
|
||||
case "mark_abnormal":
|
||||
@@ -457,7 +447,7 @@ func (r *Repository) startRefundBestEffort(action *refundAction) {
|
||||
_, _ = r.refundFunc(action.OrderID, action.RefundAmountCent, action.BizType, action.Remark)
|
||||
}
|
||||
|
||||
func renterFrozenBalance(tx *gorm.DB, renterID uint64) (float64, error) {
|
||||
func renterFrozenBalance(tx *gorm.DB, renterID uint64) (int64, error) {
|
||||
var account model.WalletAccount
|
||||
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("user_id = ?", renterID).
|
||||
@@ -468,17 +458,7 @@ func renterFrozenBalance(tx *gorm.DB, renterID uint64) (float64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return account.FrozenBalance, nil
|
||||
}
|
||||
|
||||
// minMoney 返回较小金额(角精度)
|
||||
func minMoney(a float64, b float64) float64 {
|
||||
return money.Min(a, b)
|
||||
}
|
||||
|
||||
// roundMoney 使用统一的角精度(0.1元)
|
||||
func roundMoney(value float64) float64 {
|
||||
return money.Round(value)
|
||||
return account.FrozenBalanceCent, nil
|
||||
}
|
||||
|
||||
func (r *Repository) baseQuery() *gorm.DB {
|
||||
@@ -550,14 +530,14 @@ func buildArbitrationHandoffContent(req ArbitrateRequest, settlement arbitration
|
||||
if req.Remark != "" {
|
||||
content += "\n处理说明:" + req.Remark
|
||||
}
|
||||
if settlement.RenterRefundAmount > 0 {
|
||||
content += fmt.Sprintf("\n退款给租客:¥%.2f", settlement.RenterRefundAmount)
|
||||
if settlement.RenterRefundAmountCent > 0 {
|
||||
content += "\n退款给租客:" + money.FormatWithSymbol(settlement.RenterRefundAmountCent)
|
||||
}
|
||||
if settlement.OwnerIncomeAmount > 0 {
|
||||
content += fmt.Sprintf("\n结算给号主:¥%.2f", settlement.OwnerIncomeAmount)
|
||||
if settlement.OwnerIncomeAmountCent > 0 {
|
||||
content += "\n结算给号主:" + money.FormatWithSymbol(settlement.OwnerIncomeAmountCent)
|
||||
}
|
||||
if settlement.DepositDeductAmount > 0 {
|
||||
content += fmt.Sprintf("\n押金扣除:¥%.2f", settlement.DepositDeductAmount)
|
||||
if settlement.DepositDeductAmountCent > 0 {
|
||||
content += "\n押金扣除:" + money.FormatWithSymbol(settlement.DepositDeductAmountCent)
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
@@ -31,11 +31,11 @@ func TestBuildArbitrationSettlementSkipsFrozenReleaseWhenNoFrozenBalance(t *test
|
||||
t.Fatalf("不应在无冻结余额时生成解冻流水: %+v", entry)
|
||||
}
|
||||
}
|
||||
if settlement.OwnerIncomeAmount != 180 {
|
||||
t.Fatalf("OwnerIncomeAmount = %.1f, want 180.0", settlement.OwnerIncomeAmount)
|
||||
if settlement.OwnerIncomeAmountCent != 18000 {
|
||||
t.Fatalf("OwnerIncomeAmountCent = %d, want 18000", settlement.OwnerIncomeAmountCent)
|
||||
}
|
||||
if settlement.RenterRefundAmount != 100 {
|
||||
t.Fatalf("RenterRefundAmount = %.1f, want 100.0", settlement.RenterRefundAmount)
|
||||
if settlement.RenterRefundAmountCent != 10000 {
|
||||
t.Fatalf("RenterRefundAmountCent = %d, want 10000", settlement.RenterRefundAmountCent)
|
||||
}
|
||||
for _, entry := range settlement.Entries {
|
||||
if entry.UserID == order.RenterID && entry.BizType == "arbitration_renter_refund" {
|
||||
@@ -58,7 +58,7 @@ func TestBuildArbitrationSettlementLimitsFrozenReleaseToExistingBalance(t *testi
|
||||
settlement, err := buildArbitrationSettlement(order, ArbitrateRequest{
|
||||
Result: "order_close",
|
||||
Remark: "测试裁决",
|
||||
}, 120)
|
||||
}, 12000)
|
||||
if err != nil {
|
||||
t.Fatalf("buildArbitrationSettlement() error = %v", err)
|
||||
}
|
||||
@@ -83,15 +83,15 @@ func TestBuildArbitrationHandoffContent(t *testing.T) {
|
||||
Result: "partial_refund",
|
||||
Remark: "账号异常,退还部分租金。",
|
||||
}, arbitrationSettlement{
|
||||
RenterRefundAmount: 80,
|
||||
OwnerIncomeAmount: 120,
|
||||
RenterRefundAmountCent: 8000,
|
||||
OwnerIncomeAmountCent: 12000,
|
||||
})
|
||||
|
||||
wantParts := []string{
|
||||
"客服仲裁结果:部分退款",
|
||||
"处理说明:账号异常,退还部分租金。",
|
||||
"退款给租客:¥80.00",
|
||||
"结算给号主:¥120.00",
|
||||
"退款给租客:¥80.0",
|
||||
"结算给号主:¥120.0",
|
||||
}
|
||||
for _, part := range wantParts {
|
||||
if !strings.Contains(content, part) {
|
||||
|
||||
@@ -46,8 +46,8 @@ type CreateRequest struct {
|
||||
HafCoinAmount int64 `json:"haf_coin_amount"`
|
||||
AssetSummary map[string]any `json:"asset_summary"`
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
Price float64 `json:"price"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
PriceCent int64 `json:"price_cent"`
|
||||
DepositAmountCent int64 `json:"deposit_amount_cent"`
|
||||
|
||||
AgreedVirtualAssetSale bool `json:"agreed_virtual_asset_sale"`
|
||||
AgreedSellerAgreement bool `json:"agreed_seller_agreement"`
|
||||
@@ -61,7 +61,7 @@ type ReviewRequest struct {
|
||||
|
||||
type AdminPriceAdjustRequest struct {
|
||||
BuyerRatio float64 `json:"buyer_ratio"`
|
||||
BuyerTotalPrice float64 `json:"buyer_total_price"`
|
||||
BuyerTotalPriceCent int64 `json:"buyer_total_price_cent"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
|
||||
@@ -79,16 +79,13 @@ func (r *Repository) Create(ownerID uint64, req CreateRequest, reviewRequired bo
|
||||
if err := tx.Create(&account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
price := normalizedListingPrice(req)
|
||||
depositAmount := roundMoney(req.DepositAmount)
|
||||
priceCent := normalizedListingPriceCent(req)
|
||||
listing := model.RentalListing{
|
||||
ListingNo: listingNo,
|
||||
AccountID: account.ID,
|
||||
OwnerID: ownerID,
|
||||
Price: price,
|
||||
PriceCent: int64(math.Round(price * 100)),
|
||||
DepositAmount: depositAmount,
|
||||
DepositAmountCent: int64(math.Round(depositAmount * 100)),
|
||||
PriceCent: priceCent,
|
||||
DepositAmountCent: req.DepositAmountCent,
|
||||
Status: listingStatus,
|
||||
ReviewStatus: reviewStatus,
|
||||
PublishedAt: publishedAt,
|
||||
@@ -149,16 +146,13 @@ func (r *Repository) CreateFromExternalUpload(upload externalUploadCreate, req C
|
||||
if err := tx.Create(&account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
price := normalizedListingPrice(req)
|
||||
depositAmount := roundMoney(req.DepositAmount)
|
||||
priceCent := normalizedListingPriceCent(req)
|
||||
listing := model.RentalListing{
|
||||
ListingNo: listingNo,
|
||||
AccountID: account.ID,
|
||||
OwnerID: owner.ID,
|
||||
Price: price,
|
||||
PriceCent: int64(math.Round(price * 100)),
|
||||
DepositAmount: depositAmount,
|
||||
DepositAmountCent: int64(math.Round(depositAmount * 100)),
|
||||
PriceCent: priceCent,
|
||||
DepositAmountCent: req.DepositAmountCent,
|
||||
Status: "draft",
|
||||
ReviewStatus: "pending",
|
||||
}
|
||||
@@ -216,9 +210,8 @@ func (r *Repository) Update(ownerID uint64, listingID uint64, req UpdateRequest,
|
||||
}
|
||||
account.ScreenshotURLS = screenshots
|
||||
|
||||
price := normalizedListingPrice(req)
|
||||
listing.Price = price
|
||||
listing.DepositAmount = roundMoney(req.DepositAmount)
|
||||
listing.PriceCent = normalizedListingPriceCent(req)
|
||||
listing.DepositAmountCent = req.DepositAmountCent
|
||||
listingStatus, reviewStatus, publishedAt := initialPublishState(reviewRequired)
|
||||
listing.Status = listingStatus
|
||||
listing.ReviewStatus = reviewStatus
|
||||
@@ -521,7 +514,7 @@ func (r *Repository) AdjustReviewPrice(adminID uint64, listingID uint64, req Adm
|
||||
}
|
||||
sellerTotalPrice := readSummaryNumber(breakdown["seller_total_price"])
|
||||
if sellerTotalPrice <= 0 {
|
||||
sellerTotalPrice = math.Max(0, listing.Price-consumablePrice)
|
||||
sellerTotalPrice = math.Max(0, centToYuan(listing.PriceCent)-consumablePrice)
|
||||
}
|
||||
sellerCoinBasePrice := readSummaryNumber(breakdown["seller_coin_base_price"])
|
||||
if sellerCoinBasePrice <= 0 {
|
||||
@@ -536,13 +529,13 @@ func (r *Repository) AdjustReviewPrice(adminID uint64, listingID uint64, req Adm
|
||||
return ErrInvalidPrice
|
||||
}
|
||||
|
||||
beforePrice := listing.Price
|
||||
beforePriceCent := listing.PriceCent
|
||||
beforeRatio := readSummaryNumber(breakdown["buyer_ratio"])
|
||||
if beforeRatio <= 0 && listing.Price > consumablePrice {
|
||||
beforeRatio = roundRatio(coinWan / (listing.Price - consumablePrice))
|
||||
if beforeRatio <= 0 && centToYuan(listing.PriceCent) > consumablePrice {
|
||||
beforeRatio = roundRatio(coinWan / (centToYuan(listing.PriceCent) - consumablePrice))
|
||||
}
|
||||
|
||||
listing.Price = buyerTotalPrice
|
||||
listing.PriceCent = yuanToCent(buyerTotalPrice)
|
||||
summary["publish_ratio"] = buyerRatio
|
||||
breakdown["seller_coin_base_price"] = roundMoney(sellerCoinBasePrice)
|
||||
breakdown["seller_total_price"] = roundMoney(sellerTotalPrice)
|
||||
@@ -572,8 +565,8 @@ func (r *Repository) AdjustReviewPrice(adminID uint64, listingID uint64, req Adm
|
||||
"listing_id": listing.ID,
|
||||
"account_id": account.ID,
|
||||
"owner_id": listing.OwnerID,
|
||||
"before_price": beforePrice,
|
||||
"after_price": listing.Price,
|
||||
"before_price_cent": beforePriceCent,
|
||||
"after_price_cent": listing.PriceCent,
|
||||
"before_buyer_ratio": beforeRatio,
|
||||
"after_buyer_ratio": buyerRatio,
|
||||
"platform_markup": breakdown["platform_markup_amount"],
|
||||
@@ -783,9 +776,9 @@ func canListPublicWithSQL(query PublicListQuery) bool {
|
||||
func applyPublicSQLSort(db *gorm.DB, sortKey string) *gorm.DB {
|
||||
switch sortKey {
|
||||
case "priceAsc":
|
||||
return db.Order("l.price ASC, l.published_at DESC, l.id DESC")
|
||||
return db.Order("l.price_cent ASC, l.published_at DESC, l.id DESC")
|
||||
case "priceDesc":
|
||||
return db.Order("l.price DESC, l.published_at DESC, l.id DESC")
|
||||
return db.Order("l.price_cent DESC, l.published_at DESC, l.id DESC")
|
||||
case "coinDesc":
|
||||
return db.Order("a.haf_coin_amount DESC, l.published_at DESC, l.id DESC")
|
||||
default:
|
||||
@@ -1384,16 +1377,16 @@ func rowsToDTO(rows []listingRow) []ListingDTO {
|
||||
return items
|
||||
}
|
||||
|
||||
func normalizedListingPrice(req CreateRequest) float64 {
|
||||
func normalizedListingPriceCent(req CreateRequest) int64 {
|
||||
if req.AssetSummary != nil {
|
||||
if breakdown, ok := req.AssetSummary["price_breakdown"].(map[string]any); ok {
|
||||
buyerPrice := readSummaryNumber(breakdown["buyer_total_price"])
|
||||
if buyerPrice > 0 {
|
||||
return roundMoney(buyerPrice)
|
||||
return yuanToCent(buyerPrice)
|
||||
}
|
||||
}
|
||||
}
|
||||
return roundMoney(req.Price)
|
||||
return req.PriceCent
|
||||
}
|
||||
|
||||
func publicListings(items []ListingDTO) []ListingDTO {
|
||||
@@ -1620,8 +1613,8 @@ func ensurePriceBreakdown(summary map[string]any) map[string]any {
|
||||
}
|
||||
|
||||
func calculateAdminAdjustedPrice(req AdminPriceAdjustRequest, coinWan float64, consumablePrice float64) (float64, float64, float64) {
|
||||
if req.BuyerTotalPrice > 0 {
|
||||
buyerTotalPrice := roundMoney(req.BuyerTotalPrice)
|
||||
if req.BuyerTotalPriceCent > 0 {
|
||||
buyerTotalPrice := roundMoney(centToYuan(req.BuyerTotalPriceCent))
|
||||
buyerCoinBasePrice := roundMoney(buyerTotalPrice - consumablePrice)
|
||||
if buyerCoinBasePrice <= 0 || coinWan <= 0 {
|
||||
return 0, 0, 0
|
||||
@@ -1643,6 +1636,14 @@ func roundRatio(value float64) float64 {
|
||||
return math.Round(value*10) / 10
|
||||
}
|
||||
|
||||
func yuanToCent(value float64) int64 {
|
||||
return int64(math.Round(roundMoney(value) * 100))
|
||||
}
|
||||
|
||||
func centToYuan(value int64) float64 {
|
||||
return float64(value) / 100
|
||||
}
|
||||
|
||||
func cleanScreenshotURLs(urls []string) []string {
|
||||
cleaned := make([]string, 0, len(urls))
|
||||
seen := make(map[string]struct{}, len(urls))
|
||||
|
||||
@@ -252,7 +252,7 @@ func (s *Service) AdjustReviewPrice(adminID uint64, id uint64, req AdminPriceAdj
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if req.BuyerRatio <= 0 && req.BuyerTotalPrice <= 0 {
|
||||
if req.BuyerRatio <= 0 && req.BuyerTotalPriceCent <= 0 {
|
||||
return nil, ErrInvalidPrice
|
||||
}
|
||||
return s.repo.AdjustReviewPrice(adminID, id, req, meta)
|
||||
@@ -328,13 +328,13 @@ func validateRequest(req CreateRequest, rules publishRules) error {
|
||||
if strings.TrimSpace(req.ServerRegion) == "" {
|
||||
return ErrMissingServerRegion
|
||||
}
|
||||
if normalizedListingPrice(req) <= 0 {
|
||||
if normalizedListingPriceCent(req) <= 0 {
|
||||
return ErrInvalidPrice
|
||||
}
|
||||
if req.DepositAmount < 0 {
|
||||
if req.DepositAmountCent < 0 {
|
||||
return ErrInvalidDeposit
|
||||
}
|
||||
if consumables := consumableValue(req.AssetSummary); consumables > 0 && req.DepositAmount <= consumables {
|
||||
if consumables := consumableValue(req.AssetSummary); consumables > 0 && req.DepositAmountCent <= yuanToCent(consumables) {
|
||||
return ErrDepositTooLow
|
||||
}
|
||||
if req.HafCoinAmount < 0 {
|
||||
@@ -533,8 +533,8 @@ func externalAccountToCreateRequest(uploaderName string, uploadTime int64, item
|
||||
HafCoinAmount: int64(math.Round(hafCoinM * 1000000)),
|
||||
AssetSummary: assetSummary,
|
||||
ScreenshotURLS: []string{defaultUploadScreenshot},
|
||||
Price: price,
|
||||
DepositAmount: item.Deposit,
|
||||
PriceCent: yuanToCent(price),
|
||||
DepositAmountCent: yuanToCent(item.Deposit),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestCalculateAdminAdjustedPriceByRatio(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCalculateAdminAdjustedPriceByTotalPrice(t *testing.T) {
|
||||
base, total, ratio := calculateAdminAdjustedPrice(AdminPriceAdjustRequest{BuyerTotalPrice: 70}, 1000, 20)
|
||||
base, total, ratio := calculateAdminAdjustedPrice(AdminPriceAdjustRequest{BuyerTotalPriceCent: 7000}, 1000, 20)
|
||||
if base != 50 || total != 70 || ratio != 20 {
|
||||
t.Fatalf("unexpected adjusted price: base=%.2f total=%.2f ratio=%.2f", base, total, ratio)
|
||||
}
|
||||
@@ -37,8 +37,8 @@ func TestValidateRequestRequiresDepositAboveConsumables(t *testing.T) {
|
||||
req := CreateRequest{
|
||||
Title: "测试账号",
|
||||
ServerRegion: "烽火地带",
|
||||
Price: 100,
|
||||
DepositAmount: 2,
|
||||
PriceCent: 10000,
|
||||
DepositAmountCent: 200,
|
||||
HafCoinAmount: 1000000,
|
||||
ScreenshotURLS: []string{"https://example.com/a.png"},
|
||||
AssetSummary: map[string]any{
|
||||
@@ -52,7 +52,7 @@ func TestValidateRequestRequiresDepositAboveConsumables(t *testing.T) {
|
||||
t.Fatalf("expected ErrDepositTooLow, got %v", err)
|
||||
}
|
||||
|
||||
req.DepositAmount = 3
|
||||
req.DepositAmountCent = 300
|
||||
if err := validateRequest(req, publishRules{}); err != nil {
|
||||
t.Fatalf("expected valid request, got %v", err)
|
||||
}
|
||||
@@ -159,8 +159,8 @@ func TestExternalAccountToCreateRequestMapsUploadFields(t *testing.T) {
|
||||
if req.HafCoinAmount != 197100000 {
|
||||
t.Fatalf("expected haf coin amount 197100000, got %d", req.HafCoinAmount)
|
||||
}
|
||||
if req.Price != 458 || req.DepositAmount != 400 {
|
||||
t.Fatalf("unexpected price/deposit %.2f/%.2f", req.Price, req.DepositAmount)
|
||||
if req.PriceCent != 45800 || req.DepositAmountCent != 40000 {
|
||||
t.Fatalf("unexpected price/deposit cent %d/%d", req.PriceCent, req.DepositAmountCent)
|
||||
}
|
||||
if req.AssetSummary["season_insurance"] != "3*3" {
|
||||
t.Fatalf("expected 3*3 insurance, got %#v", req.AssetSummary["season_insurance"])
|
||||
|
||||
@@ -55,17 +55,17 @@ type SubmitReturnRequest struct {
|
||||
|
||||
type SubmitCheckoutRequest struct {
|
||||
Content string `json:"content" binding:"required"`
|
||||
ConsumableAmount float64 `json:"consumable_amount"`
|
||||
ConsumableAmountCent int64 `json:"consumable_amount_cent"`
|
||||
CoinConsumedM float64 `json:"coin_consumed_m"`
|
||||
OtherAmount float64 `json:"other_amount"`
|
||||
OtherAmountCent int64 `json:"other_amount_cent"`
|
||||
EvidenceURLS []string `json:"evidence_urls"`
|
||||
}
|
||||
|
||||
type CounterCheckoutRequest struct {
|
||||
ConsumableAmount float64 `json:"consumable_amount"`
|
||||
ConsumableAmountCent int64 `json:"consumable_amount_cent"`
|
||||
CoinConsumedM float64 `json:"coin_consumed_m"`
|
||||
OtherAmount float64 `json:"other_amount"`
|
||||
DepositDeductAmount float64 `json:"deposit_deduct_amount"`
|
||||
OtherAmountCent int64 `json:"other_amount_cent"`
|
||||
DepositDeductAmountCent int64 `json:"deposit_deduct_amount_cent"`
|
||||
Reason string `json:"reason" binding:"required"`
|
||||
EvidenceURLS []string `json:"evidence_urls"`
|
||||
}
|
||||
|
||||
@@ -77,22 +77,22 @@ func orderDurationHours(order model.RentalOrder) int {
|
||||
}
|
||||
|
||||
func buildOrderPricing(listing model.RentalListing, account model.GameAccount) orderPricing {
|
||||
rentAmount := roundMoney(listing.Price)
|
||||
ownerRentAmount := readSnapshotPrice(account.AssetSummary, "seller_total_price")
|
||||
if ownerRentAmount <= 0 || ownerRentAmount > rentAmount {
|
||||
ownerRentAmount = rentAmount
|
||||
rentAmountCent := listing.PriceCent
|
||||
ownerRentAmountCent := int64(math.Round(readSnapshotPrice(account.AssetSummary, "seller_total_price") * 100))
|
||||
if ownerRentAmountCent <= 0 || ownerRentAmountCent > rentAmountCent {
|
||||
ownerRentAmountCent = rentAmountCent
|
||||
}
|
||||
platformFee := readSnapshotPrice(account.AssetSummary, "platform_markup_amount")
|
||||
if platformFee <= 0 || roundMoney(ownerRentAmount+platformFee) != rentAmount {
|
||||
platformFee = roundMoney(rentAmount - ownerRentAmount)
|
||||
platformFeeCent := int64(math.Round(readSnapshotPrice(account.AssetSummary, "platform_markup_amount") * 100))
|
||||
if platformFeeCent <= 0 || ownerRentAmountCent+platformFeeCent != rentAmountCent {
|
||||
platformFeeCent = rentAmountCent - ownerRentAmountCent
|
||||
}
|
||||
if platformFee < 0 {
|
||||
platformFee = 0
|
||||
if platformFeeCent < 0 {
|
||||
platformFeeCent = 0
|
||||
}
|
||||
return orderPricing{
|
||||
RentAmountCent: int64(math.Round(rentAmount * 100)),
|
||||
OwnerRentAmountCent: int64(math.Round(ownerRentAmount * 100)),
|
||||
PlatformFeeCent: int64(math.Round(platformFee * 100)),
|
||||
RentAmountCent: rentAmountCent,
|
||||
OwnerRentAmountCent: ownerRentAmountCent,
|
||||
PlatformFeeCent: platformFeeCent,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,8 +173,8 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
|
||||
}
|
||||
rentHours := internalOrderHours
|
||||
pricing := buildOrderPricing(listing, account)
|
||||
depositOriginalAmount := roundMoney(listing.DepositAmount)
|
||||
paidDepositAmount, waivedDepositAmount, err := r.depositAmountsForOrder(tx, renterID, depositOriginalAmount)
|
||||
depositOriginalAmountCent := listing.DepositAmountCent
|
||||
paidDepositAmountCent, waivedDepositAmountCent, err := r.depositAmountsForOrder(tx, renterID, depositOriginalAmountCent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -185,17 +185,11 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
|
||||
OwnerID: listing.OwnerID,
|
||||
RenterID: renterID,
|
||||
EstimatedDurationHours: rentHours,
|
||||
RentAmount: float64(pricing.RentAmountCent) / 100,
|
||||
RentAmountCent: pricing.RentAmountCent,
|
||||
OwnerRentAmount: float64(pricing.OwnerRentAmountCent) / 100,
|
||||
OwnerRentAmountCent: pricing.OwnerRentAmountCent,
|
||||
DepositAmount: paidDepositAmount,
|
||||
DepositAmountCent: int64(math.Round(paidDepositAmount * 100)),
|
||||
DepositOriginalAmount: depositOriginalAmount,
|
||||
DepositOriginalAmountCent: int64(math.Round(depositOriginalAmount * 100)),
|
||||
DepositWaivedAmount: waivedDepositAmount,
|
||||
DepositWaivedAmountCent: int64(math.Round(waivedDepositAmount * 100)),
|
||||
PlatformFee: float64(pricing.PlatformFeeCent) / 100,
|
||||
DepositAmountCent: paidDepositAmountCent,
|
||||
DepositOriginalAmountCent: depositOriginalAmountCent,
|
||||
DepositWaivedAmountCent: waivedDepositAmountCent,
|
||||
PlatformFeeCent: pricing.PlatformFeeCent,
|
||||
AccountSnapshot: snapshot,
|
||||
Status: "pending_payment",
|
||||
@@ -231,9 +225,8 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
|
||||
return r.FindForUser(renterID, createdID)
|
||||
}
|
||||
|
||||
func (r *Repository) depositAmountsForOrder(tx *gorm.DB, renterID uint64, originalDeposit float64) (float64, float64, error) {
|
||||
originalDeposit = roundMoney(originalDeposit)
|
||||
if originalDeposit <= 0 {
|
||||
func (r *Repository) depositAmountsForOrder(tx *gorm.DB, renterID uint64, originalDepositCent int64) (int64, int64, error) {
|
||||
if originalDepositCent <= 0 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
var user model.User
|
||||
@@ -244,28 +237,27 @@ func (r *Repository) depositAmountsForOrder(tx *gorm.DB, renterID uint64, origin
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
paidDeposit, waivedDeposit := calculateDepositWaiver(originalDeposit, user.DepositFreeQuota, used)
|
||||
paidDeposit, waivedDeposit := calculateDepositWaiver(originalDepositCent, user.DepositFreeQuotaCent, used)
|
||||
return paidDeposit, waivedDeposit, nil
|
||||
}
|
||||
|
||||
func activeDepositFreeUsed(tx *gorm.DB, renterID uint64) (float64, error) {
|
||||
var used float64
|
||||
func activeDepositFreeUsed(tx *gorm.DB, renterID uint64) (int64, error) {
|
||||
var used int64
|
||||
err := tx.Model(&model.RentalOrder{}).
|
||||
Where("renter_id = ? AND status NOT IN ?",
|
||||
renterID,
|
||||
[]string{"completed", "cancelled", "closed"},
|
||||
).
|
||||
Select("COALESCE(SUM(deposit_waived_amount), 0)").
|
||||
Select("COALESCE(SUM(deposit_waived_amount_cent), 0)").
|
||||
Scan(&used).Error
|
||||
return roundMoney(used), err
|
||||
return used, err
|
||||
}
|
||||
|
||||
func calculateDepositWaiver(originalDeposit float64, quota float64, used float64) (float64, float64) {
|
||||
originalDeposit = roundMoney(originalDeposit)
|
||||
remaining := maxMoney(roundMoney(quota)-roundMoney(used), 0)
|
||||
waived := minMoney(originalDeposit, remaining)
|
||||
paid := maxMoney(originalDeposit-waived, 0)
|
||||
return roundMoney(paid), roundMoney(waived)
|
||||
func calculateDepositWaiver(originalDepositCent int64, quotaCent int64, usedCent int64) (int64, int64) {
|
||||
remaining := maxCent(quotaCent-usedCent, 0)
|
||||
waived := minCent(originalDepositCent, remaining)
|
||||
paid := maxCent(originalDepositCent-waived, 0)
|
||||
return paid, waived
|
||||
}
|
||||
|
||||
// Pay 保留旧接口兼容,但真实付款必须走 payment 模块的渠道支付入口。
|
||||
@@ -554,7 +546,7 @@ func (r *Repository) SubmitCheckout(userID uint64, orderID uint64, req SubmitChe
|
||||
if err := tx.Create(&record).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
checkout, err := buildCheckout(order, order.RenterID, "submitted", req.Content, req.EvidenceURLS, req.ConsumableAmount, req.CoinConsumedM, req.OtherAmount, 0, false)
|
||||
checkout, err := buildCheckout(order, order.RenterID, "submitted", req.Content, req.EvidenceURLS, req.ConsumableAmountCent, req.CoinConsumedM, req.OtherAmountCent, 0, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -650,18 +642,22 @@ func (r *Repository) CounterCheckout(userID uint64, orderID uint64, req CounterC
|
||||
First(&checkout).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
next, err := buildCheckout(order, checkout.InitiatedBy, "countered", checkout.Content, req.EvidenceURLS, req.ConsumableAmount, req.CoinConsumedM, req.OtherAmount, req.DepositDeductAmount, true)
|
||||
next, err := buildCheckout(order, checkout.InitiatedBy, "countered", checkout.Content, req.EvidenceURLS, req.ConsumableAmountCent, req.CoinConsumedM, req.OtherAmountCent, req.DepositDeductAmountCent, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
checkout.Status = "countered"
|
||||
checkout.ConsumableAmount = next.ConsumableAmount
|
||||
checkout.RentAmountCent = next.RentAmountCent
|
||||
checkout.OwnerRentAmountCent = next.OwnerRentAmountCent
|
||||
checkout.PlatformFeeCent = next.PlatformFeeCent
|
||||
checkout.DepositAmountCent = next.DepositAmountCent
|
||||
checkout.ConsumableAmountCent = next.ConsumableAmountCent
|
||||
checkout.CoinConsumedM = next.CoinConsumedM
|
||||
checkout.OtherAmount = next.OtherAmount
|
||||
checkout.DepositDeductAmount = next.DepositDeductAmount
|
||||
checkout.RenterRefundAmount = next.RenterRefundAmount
|
||||
checkout.OwnerIncomeAmount = next.OwnerIncomeAmount
|
||||
checkout.OtherAmountCent = next.OtherAmountCent
|
||||
checkout.DepositDeductAmountCent = next.DepositDeductAmountCent
|
||||
checkout.RenterRefundAmountCent = next.RenterRefundAmountCent
|
||||
checkout.OwnerIncomeAmountCent = next.OwnerIncomeAmountCent
|
||||
checkout.OwnerAdjustmentReason = req.Reason
|
||||
checkout.OwnerAdjustedAt = &now
|
||||
checkout.EvidenceURLS = next.EvidenceURLS
|
||||
@@ -697,9 +693,9 @@ func (r *Repository) CounterCheckout(userID uint64, orderID uint64, req CounterC
|
||||
}
|
||||
dto := toCheckoutDTOForUser(*checkout, userID, model.RentalOrder{
|
||||
OwnerID: userID,
|
||||
RentAmountCent: int64(math.Round(checkout.RentAmount * 100)),
|
||||
OwnerRentAmountCent: int64(math.Round(checkout.OwnerRentAmount * 100)),
|
||||
DepositAmountCent: int64(math.Round(checkout.DepositAmount * 100)),
|
||||
RentAmountCent: checkout.RentAmountCent,
|
||||
OwnerRentAmountCent: checkout.OwnerRentAmountCent,
|
||||
DepositAmountCent: checkout.DepositAmountCent,
|
||||
})
|
||||
return &dto, nil
|
||||
}
|
||||
@@ -1095,11 +1091,11 @@ func (r *Repository) finalizeCheckout(tx *gorm.DB, order *model.RentalOrder, che
|
||||
refund = action
|
||||
}
|
||||
|
||||
checkout.RentAmount = float64(settlement.ActualRentAmountCent) / 100
|
||||
checkout.OwnerRentAmount = float64(settlement.OwnerRentIncomeCent) / 100
|
||||
checkout.PlatformFee = float64(settlement.PlatformFeeCent) / 100
|
||||
checkout.RenterRefundAmount = float64(settlement.RenterRefundCent) / 100
|
||||
checkout.OwnerIncomeAmount = float64(settlement.OwnerIncomeCent) / 100
|
||||
checkout.RentAmountCent = settlement.ActualRentAmountCent
|
||||
checkout.OwnerRentAmountCent = settlement.OwnerRentIncomeCent
|
||||
checkout.PlatformFeeCent = settlement.PlatformFeeCent
|
||||
checkout.RenterRefundAmountCent = settlement.RenterRefundCent
|
||||
checkout.OwnerIncomeAmountCent = settlement.OwnerIncomeCent
|
||||
if err := notification.Append(tx,
|
||||
notification.Entry{
|
||||
UserID: order.RenterID,
|
||||
@@ -1229,22 +1225,18 @@ func applyPaymentDeadline(dto *OrderDTO, order model.RentalOrder, timeoutMinutes
|
||||
dto.PaymentDeadlineAt = &deadline
|
||||
}
|
||||
|
||||
func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, content string, evidenceURLS []string, consumableAmount float64, coinConsumedM float64, otherAmount float64, explicitDeduct float64, useExplicitDeduct bool) (model.OrderCheckout, error) {
|
||||
if consumableAmount < 0 || coinConsumedM < 0 || otherAmount < 0 || explicitDeduct < 0 {
|
||||
func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, content string, evidenceURLS []string, consumableAmountCent int64, coinConsumedM float64, otherAmountCent int64, explicitDeductCent int64, useExplicitDeduct bool) (model.OrderCheckout, error) {
|
||||
if consumableAmountCent < 0 || coinConsumedM < 0 || otherAmountCent < 0 || explicitDeductCent < 0 {
|
||||
return model.OrderCheckout{}, ErrInvalidCheckoutAmount
|
||||
}
|
||||
consumableAmount = roundMoney(consumableAmount)
|
||||
otherAmount = roundMoney(otherAmount)
|
||||
explicitDeduct = roundMoney(explicitDeduct)
|
||||
deductAmount := otherAmount
|
||||
deductAmountCent := otherAmountCent
|
||||
if useExplicitDeduct {
|
||||
deductAmount = explicitDeduct
|
||||
deductAmountCent = explicitDeductCent
|
||||
}
|
||||
depositAmountFromCent := float64(order.DepositAmountCent) / 100
|
||||
if deductAmount > depositAmountFromCent {
|
||||
if deductAmountCent > order.DepositAmountCent {
|
||||
return model.OrderCheckout{}, ErrInvalidCheckoutAmount
|
||||
}
|
||||
settlement := calculateCheckoutSettlement(order, consumableAmount, roundQuantity(coinConsumedM), deductAmount)
|
||||
settlement := calculateCheckoutSettlement(order, consumableAmountCent, roundQuantity(coinConsumedM), deductAmountCent)
|
||||
evidence, err := marshalStringList(evidenceURLS)
|
||||
if err != nil {
|
||||
return model.OrderCheckout{}, err
|
||||
@@ -1253,24 +1245,15 @@ func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, c
|
||||
OrderID: order.ID,
|
||||
InitiatedBy: initiatedBy,
|
||||
Status: status,
|
||||
RentAmount: float64(settlement.ActualRentAmountCent) / 100,
|
||||
RentAmountCent: settlement.ActualRentAmountCent,
|
||||
OwnerRentAmount: float64(settlement.OwnerRentIncomeCent) / 100,
|
||||
OwnerRentAmountCent: settlement.OwnerRentIncomeCent,
|
||||
PlatformFee: float64(settlement.PlatformFeeCent) / 100,
|
||||
PlatformFeeCent: settlement.PlatformFeeCent,
|
||||
DepositAmount: depositAmountFromCent,
|
||||
DepositAmountCent: order.DepositAmountCent,
|
||||
ConsumableAmount: consumableAmount,
|
||||
ConsumableAmountCent: int64(math.Round(consumableAmount * 100)),
|
||||
ConsumableAmountCent: consumableAmountCent,
|
||||
CoinConsumedM: roundQuantity(coinConsumedM),
|
||||
OtherAmount: otherAmount,
|
||||
OtherAmountCent: int64(math.Round(otherAmount * 100)),
|
||||
DepositDeductAmount: roundMoney(deductAmount),
|
||||
OtherAmountCent: otherAmountCent,
|
||||
DepositDeductAmountCent: settlement.DepositCompensationCent,
|
||||
RenterRefundAmount: float64(settlement.RenterRefundCent) / 100,
|
||||
RenterRefundAmountCent: settlement.RenterRefundCent,
|
||||
OwnerIncomeAmount: float64(settlement.OwnerIncomeCent) / 100,
|
||||
OwnerIncomeAmountCent: settlement.OwnerIncomeCent,
|
||||
Content: content,
|
||||
EvidenceURLS: evidence,
|
||||
@@ -1278,57 +1261,55 @@ func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, c
|
||||
}
|
||||
|
||||
func buildCheckoutSettlement(order model.RentalOrder, checkout *model.OrderCheckout) checkoutSettlement {
|
||||
return calculateCheckoutSettlement(order, checkout.ConsumableAmount, checkout.CoinConsumedM, checkout.DepositDeductAmount)
|
||||
return calculateCheckoutSettlement(order, checkout.ConsumableAmountCent, checkout.CoinConsumedM, checkout.DepositDeductAmountCent)
|
||||
}
|
||||
|
||||
func calculateCheckoutSettlement(order model.RentalOrder, consumableAmount float64, coinConsumedM float64, depositDeductAmount float64) checkoutSettlement {
|
||||
// 从分字段读取,转为元进行计算(保持现有逻辑兼容性)
|
||||
orderRentAmount := float64(order.RentAmountCent) / 100
|
||||
orderOwnerRentAmount := float64(order.OwnerRentAmountCent) / 100
|
||||
orderDepositAmount := float64(order.DepositAmountCent) / 100
|
||||
func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent int64, coinConsumedM float64, depositDeductAmountCent int64) checkoutSettlement {
|
||||
orderRentAmountCent := order.RentAmountCent
|
||||
orderOwnerRentAmountCent := order.OwnerRentAmountCent
|
||||
orderDepositAmountCent := order.DepositAmountCent
|
||||
|
||||
buyerCoinBasePrice := readOrderSnapshotPrice(order.AccountSnapshot, "buyer_coin_base_price")
|
||||
if buyerCoinBasePrice <= 0 || buyerCoinBasePrice > orderRentAmount {
|
||||
buyerCoinBasePrice = orderRentAmount
|
||||
buyerCoinBasePriceCent := int64(math.Round(readOrderSnapshotPrice(order.AccountSnapshot, "buyer_coin_base_price") * 100))
|
||||
if buyerCoinBasePriceCent <= 0 || buyerCoinBasePriceCent > orderRentAmountCent {
|
||||
buyerCoinBasePriceCent = orderRentAmountCent
|
||||
}
|
||||
sellerCoinBasePrice := readOrderSnapshotPrice(order.AccountSnapshot, "seller_coin_base_price")
|
||||
if sellerCoinBasePrice <= 0 || sellerCoinBasePrice > orderOwnerRentAmount {
|
||||
sellerCoinBasePrice = orderOwnerRentAmount
|
||||
sellerCoinBasePriceCent := int64(math.Round(readOrderSnapshotPrice(order.AccountSnapshot, "seller_coin_base_price") * 100))
|
||||
if sellerCoinBasePriceCent <= 0 || sellerCoinBasePriceCent > orderOwnerRentAmountCent {
|
||||
sellerCoinBasePriceCent = orderOwnerRentAmountCent
|
||||
}
|
||||
prepaidConsumablePrice := readOrderSnapshotPrice(order.AccountSnapshot, "consumable_price")
|
||||
if prepaidConsumablePrice <= 0 || prepaidConsumablePrice > orderRentAmount-buyerCoinBasePrice {
|
||||
prepaidConsumablePrice = maxMoney(orderRentAmount-buyerCoinBasePrice, 0)
|
||||
prepaidConsumablePriceCent := int64(math.Round(readOrderSnapshotPrice(order.AccountSnapshot, "consumable_price") * 100))
|
||||
if prepaidConsumablePriceCent <= 0 || prepaidConsumablePriceCent > orderRentAmountCent-buyerCoinBasePriceCent {
|
||||
prepaidConsumablePriceCent = maxCent(orderRentAmountCent-buyerCoinBasePriceCent, 0)
|
||||
}
|
||||
prepaidOwnerConsumablePrice := maxMoney(orderOwnerRentAmount-sellerCoinBasePrice, 0)
|
||||
prepaidOwnerConsumablePriceCent := maxCent(orderOwnerRentAmountCent-sellerCoinBasePriceCent, 0)
|
||||
totalCoinM := readOrderSnapshotCoinM(order.AccountSnapshot)
|
||||
coinUseRatio := 1.0
|
||||
if totalCoinM > 0 {
|
||||
coinUseRatio = minRatio(maxRatio(roundQuantity(coinConsumedM)/totalCoinM, 0), 1)
|
||||
}
|
||||
usedBuyerCoinPrice := roundMoney(buyerCoinBasePrice * coinUseRatio)
|
||||
usedOwnerCoinPrice := roundMoney(sellerCoinBasePrice * coinUseRatio)
|
||||
usedBuyerConsumablePrice := minMoney(roundMoney(consumableAmount), prepaidConsumablePrice)
|
||||
usedBuyerCoinPriceCent := int64(math.Round(float64(buyerCoinBasePriceCent) * coinUseRatio))
|
||||
usedOwnerCoinPriceCent := int64(math.Round(float64(sellerCoinBasePriceCent) * coinUseRatio))
|
||||
usedBuyerConsumablePriceCent := minCent(consumableAmountCent, prepaidConsumablePriceCent)
|
||||
consumableUseRatio := 1.0
|
||||
if prepaidConsumablePrice > 0 {
|
||||
consumableUseRatio = minRatio(maxRatio(usedBuyerConsumablePrice/prepaidConsumablePrice, 0), 1)
|
||||
if prepaidConsumablePriceCent > 0 {
|
||||
consumableUseRatio = minRatio(maxRatio(float64(usedBuyerConsumablePriceCent)/float64(prepaidConsumablePriceCent), 0), 1)
|
||||
}
|
||||
usedOwnerConsumablePrice := roundMoney(prepaidOwnerConsumablePrice * consumableUseRatio)
|
||||
actualRentAmount := minMoney(roundMoney(usedBuyerCoinPrice+usedBuyerConsumablePrice), orderRentAmount)
|
||||
ownerRentIncome := minMoney(roundMoney(usedOwnerCoinPrice+usedOwnerConsumablePrice), orderOwnerRentAmount)
|
||||
depositCompensation := minMoney(roundMoney(depositDeductAmount), orderDepositAmount)
|
||||
rentRefund := maxMoney(orderRentAmount-actualRentAmount, 0)
|
||||
depositRefund := maxMoney(orderDepositAmount-depositCompensation, 0)
|
||||
usedOwnerConsumablePriceCent := int64(math.Round(float64(prepaidOwnerConsumablePriceCent) * consumableUseRatio))
|
||||
actualRentAmountCent := minCent(usedBuyerCoinPriceCent+usedBuyerConsumablePriceCent, orderRentAmountCent)
|
||||
ownerRentIncomeCent := minCent(usedOwnerCoinPriceCent+usedOwnerConsumablePriceCent, orderOwnerRentAmountCent)
|
||||
depositCompensationCent := minCent(depositDeductAmountCent, orderDepositAmountCent)
|
||||
rentRefundCent := maxCent(orderRentAmountCent-actualRentAmountCent, 0)
|
||||
depositRefundCent := maxCent(orderDepositAmountCent-depositCompensationCent, 0)
|
||||
|
||||
// 最后转换为分返回
|
||||
return checkoutSettlement{
|
||||
OwnerRentIncomeCent: int64(math.Round(ownerRentIncome * 100)),
|
||||
DepositCompensationCent: int64(math.Round(depositCompensation * 100)),
|
||||
OwnerIncomeCent: int64(math.Round((ownerRentIncome + depositCompensation) * 100)),
|
||||
RentRefundCent: int64(math.Round(rentRefund * 100)),
|
||||
DepositRefundCent: int64(math.Round(depositRefund * 100)),
|
||||
RenterRefundCent: int64(math.Round((rentRefund + depositRefund) * 100)),
|
||||
PlatformFeeCent: int64(math.Round(maxMoney(actualRentAmount-ownerRentIncome, 0) * 100)),
|
||||
ActualRentAmountCent: int64(math.Round(actualRentAmount * 100)),
|
||||
OwnerRentIncomeCent: ownerRentIncomeCent,
|
||||
DepositCompensationCent: depositCompensationCent,
|
||||
OwnerIncomeCent: ownerRentIncomeCent + depositCompensationCent,
|
||||
RentRefundCent: rentRefundCent,
|
||||
DepositRefundCent: depositRefundCent,
|
||||
RenterRefundCent: rentRefundCent + depositRefundCent,
|
||||
PlatformFeeCent: maxCent(actualRentAmountCent-ownerRentIncomeCent, 0),
|
||||
ActualRentAmountCent: actualRentAmountCent,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1381,6 +1362,20 @@ func maxMoney(a float64, b float64) float64 {
|
||||
return money.Max(a, b)
|
||||
}
|
||||
|
||||
func minCent(a int64, b int64) int64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func maxCent(a int64, b int64) int64 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func minRatio(a float64, b float64) float64 {
|
||||
if a < b {
|
||||
return a
|
||||
@@ -1522,13 +1517,6 @@ func toHandoffDTO(record model.HandoffRecord) HandoffRecordDTO {
|
||||
}
|
||||
}
|
||||
|
||||
func effectiveDepositOriginalAmount(order model.RentalOrder) float64 {
|
||||
if order.DepositOriginalAmount > 0 {
|
||||
return order.DepositOriginalAmount
|
||||
}
|
||||
return order.DepositAmount
|
||||
}
|
||||
|
||||
func effectiveDepositOriginalAmountCent(order model.RentalOrder) int64 {
|
||||
if order.DepositOriginalAmountCent > 0 {
|
||||
return order.DepositOriginalAmountCent
|
||||
@@ -1537,11 +1525,11 @@ func effectiveDepositOriginalAmountCent(order model.RentalOrder) int64 {
|
||||
}
|
||||
|
||||
func toCheckoutAdminDTO(checkout model.OrderCheckout) CheckoutDTO {
|
||||
rentAmountCent := int64(math.Round(checkout.RentAmount * 100))
|
||||
ownerRentAmountCent := int64(math.Round(checkout.OwnerRentAmount * 100))
|
||||
platformFeeCent := int64(math.Round(checkout.PlatformFee * 100))
|
||||
renterRefundAmountCent := int64(math.Round(checkout.RenterRefundAmount * 100))
|
||||
ownerIncomeAmountCent := int64(math.Round(checkout.OwnerIncomeAmount * 100))
|
||||
rentAmountCent := checkout.RentAmountCent
|
||||
ownerRentAmountCent := checkout.OwnerRentAmountCent
|
||||
platformFeeCent := checkout.PlatformFeeCent
|
||||
renterRefundAmountCent := checkout.RenterRefundAmountCent
|
||||
ownerIncomeAmountCent := checkout.OwnerIncomeAmountCent
|
||||
return CheckoutDTO{
|
||||
ID: checkout.ID,
|
||||
OrderID: checkout.OrderID,
|
||||
@@ -1552,11 +1540,11 @@ func toCheckoutAdminDTO(checkout model.OrderCheckout) CheckoutDTO {
|
||||
RentAmountCent: &rentAmountCent,
|
||||
OwnerRentAmountCent: &ownerRentAmountCent,
|
||||
PlatformFeeCent: &platformFeeCent,
|
||||
DepositAmountCent: int64(math.Round(checkout.DepositAmount * 100)),
|
||||
ConsumableAmountCent: int64(math.Round(checkout.ConsumableAmount * 100)),
|
||||
DepositAmountCent: checkout.DepositAmountCent,
|
||||
ConsumableAmountCent: checkout.ConsumableAmountCent,
|
||||
CoinConsumedM: checkout.CoinConsumedM,
|
||||
OtherAmountCent: int64(math.Round(checkout.OtherAmount * 100)),
|
||||
DepositDeductAmountCent: int64(math.Round(checkout.DepositDeductAmount * 100)),
|
||||
OtherAmountCent: checkout.OtherAmountCent,
|
||||
DepositDeductAmountCent: checkout.DepositDeductAmountCent,
|
||||
RenterRefundAmountCent: &renterRefundAmountCent,
|
||||
OwnerIncomeAmountCent: &ownerIncomeAmountCent,
|
||||
Content: checkout.Content,
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestCalculateCheckoutSettlementRefundsUnusedRent(t *testing.T) {
|
||||
}`)),
|
||||
}
|
||||
|
||||
settlement := calculateCheckoutSettlement(order, 7, 90, 0)
|
||||
settlement := calculateCheckoutSettlement(order, 700, 90, 0)
|
||||
|
||||
// 角精度:243.7 = roundMoney(236.7 + 7)
|
||||
if settlement.ActualRentAmountCent != 24370 {
|
||||
@@ -104,7 +104,7 @@ func TestCalculateCheckoutSettlementAddsDepositCompensation(t *testing.T) {
|
||||
}`)),
|
||||
}
|
||||
|
||||
settlement := calculateCheckoutSettlement(order, 120, 100, 30)
|
||||
settlement := calculateCheckoutSettlement(order, 12000, 100, 3000)
|
||||
|
||||
if settlement.ActualRentAmountCent != 38300 {
|
||||
t.Fatalf("ActualRentAmountCent = %d, want 38300", settlement.ActualRentAmountCent)
|
||||
@@ -126,17 +126,17 @@ func TestCalculateCheckoutSettlementAddsDepositCompensation(t *testing.T) {
|
||||
func TestCalculateDepositWaiverUsesSharedRemainingQuota(t *testing.T) {
|
||||
paid, waived := calculateDepositWaiver(500, 300, 0)
|
||||
if paid != 200 || waived != 300 {
|
||||
t.Fatalf("paid = %.1f, waived = %.1f, want 200.0, 300.0", paid, waived)
|
||||
t.Fatalf("paid = %d, waived = %d, want 200, 300", paid, waived)
|
||||
}
|
||||
|
||||
paid, waived = calculateDepositWaiver(500, 300, 200)
|
||||
if paid != 400 || waived != 100 {
|
||||
t.Fatalf("paid = %.1f, waived = %.1f, want 400.0, 100.0", paid, waived)
|
||||
t.Fatalf("paid = %d, waived = %d, want 400, 100", paid, waived)
|
||||
}
|
||||
|
||||
paid, waived = calculateDepositWaiver(500, 300, 300)
|
||||
if paid != 500 || waived != 0 {
|
||||
t.Fatalf("paid = %.1f, waived = %.1f, want 500.0, 0.0", paid, waived)
|
||||
t.Fatalf("paid = %d, waived = %d, want 500, 0", paid, waived)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ type StartPaymentRequest struct {
|
||||
}
|
||||
|
||||
type WalletRechargePaymentRequest struct {
|
||||
Amount float64 `json:"amount"`
|
||||
AmountCent int64 `json:"amount_cent"`
|
||||
PayWay string `json:"pay_way"`
|
||||
JSPayFlag string `json:"jspay_flag"`
|
||||
}
|
||||
|
||||
@@ -229,8 +229,8 @@ func (r *Repository) Start(userID uint64, orderID uint64, req StartPaymentReques
|
||||
}
|
||||
|
||||
func (r *Repository) StartWalletRecharge(userID uint64, req WalletRechargePaymentRequest, clientIP string) (*PaymentDTO, error) {
|
||||
amountCent := moneyCent(req.Amount)
|
||||
if userID == 0 || req.Amount < MinWalletRechargeAmount || amountCent <= 0 {
|
||||
amountCent := req.AmountCent
|
||||
if userID == 0 || amountCent < moneyCent(MinWalletRechargeAmount) {
|
||||
return nil, ErrPaymentCannotStart
|
||||
}
|
||||
runtimeConfig, err := r.defaultRuntimeConfig()
|
||||
@@ -764,7 +764,7 @@ func (r *Repository) preparePayment(userID uint64, orderID uint64, req StartPaym
|
||||
if row.Status != "pending_payment" {
|
||||
return ErrPaymentCannotStart
|
||||
}
|
||||
amountCent := moneyCent(row.RentAmount + row.DepositAmount)
|
||||
amountCent := row.RentAmountCent + row.DepositAmountCent
|
||||
if amountCent <= 0 {
|
||||
return ErrPaymentCannotStart
|
||||
}
|
||||
|
||||
@@ -58,11 +58,8 @@ func (r *Repository) Create(userID uint64, req CreateWithdrawalRequest) (*Withdr
|
||||
withdrawal := model.WithdrawalRequest{
|
||||
WithdrawNo: withdrawNo,
|
||||
UserID: userID,
|
||||
Amount: float64(req.AmountCent) / 100,
|
||||
AmountCent: req.AmountCent,
|
||||
Fee: float64(feeCent) / 100,
|
||||
FeeCent: feeCent,
|
||||
ActualAmount: float64(actualAmountCent) / 100,
|
||||
ActualAmountCent: actualAmountCent,
|
||||
PaymentAccountID: &req.PaymentAccountID,
|
||||
AccountType: paymentAccount.AccountType,
|
||||
|
||||
@@ -22,7 +22,6 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
realname_status VARCHAR(32) NOT NULL DEFAULT 'unverified' COMMENT '实名状态: unverified未实名, pending审核中, verified已实名, failed失败',
|
||||
risk_status VARCHAR(32) NOT NULL DEFAULT 'normal' COMMENT '风控状态: normal正常, warning警告, frozen冻结',
|
||||
credit_score INT NOT NULL DEFAULT 100 COMMENT '信用分',
|
||||
deposit_free_quota DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '免押总额度',
|
||||
deposit_free_quota_cent BIGINT NOT NULL DEFAULT 0 COMMENT '免押总额度(分)',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '账号状态: active活跃, inactive停用, banned封禁',
|
||||
last_login_at DATETIME NULL COMMENT '最后登录时间',
|
||||
@@ -82,9 +81,7 @@ CREATE TABLE IF NOT EXISTS rental_listings (
|
||||
listing_no VARCHAR(20) NOT NULL COMMENT '商品编号,格式yyyyMMddNNNN',
|
||||
account_id BIGINT UNSIGNED NOT NULL COMMENT '关联的游戏账号ID',
|
||||
owner_id BIGINT UNSIGNED NOT NULL COMMENT '号主ID',
|
||||
price DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '租金(元/小时)',
|
||||
price_cent BIGINT NOT NULL DEFAULT 0 COMMENT '租金(分/小时)',
|
||||
deposit_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '押金金额',
|
||||
deposit_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '押金金额(分)',
|
||||
in_transaction TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否正在交易中: 0否, 1是',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'draft' COMMENT '商品状态: draft草稿, active上架, offline下架, deleted删除',
|
||||
@@ -96,7 +93,7 @@ CREATE TABLE IF NOT EXISTS rental_listings (
|
||||
UNIQUE KEY uk_rental_listings_listing_no (listing_no),
|
||||
KEY idx_rental_listings_account_id (account_id),
|
||||
KEY idx_rental_listings_owner_id (owner_id),
|
||||
KEY idx_rental_listings_filter (status, review_status, in_transaction, price),
|
||||
KEY idx_rental_listings_filter (status, review_status, in_transaction, price_cent),
|
||||
KEY idx_rental_listings_published (status, review_status, published_at DESC)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租号商品列表';
|
||||
|
||||
@@ -122,17 +119,11 @@ CREATE TABLE IF NOT EXISTS rental_orders (
|
||||
renter_id BIGINT UNSIGNED NOT NULL COMMENT '租客ID',
|
||||
rented_at DATETIME NULL COMMENT '租用开始时间',
|
||||
estimated_duration_hours INT NOT NULL DEFAULT 24 COMMENT '预计租用时长(小时)',
|
||||
rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '租金总额',
|
||||
rent_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '租金总额(分)',
|
||||
owner_rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '号主实得租金',
|
||||
owner_rent_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '号主实得租金(分)',
|
||||
deposit_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '实际收取押金金额',
|
||||
deposit_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '实际收取押金(分)',
|
||||
deposit_original_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '商品原始押金金额',
|
||||
deposit_original_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '商品原始押金(分)',
|
||||
deposit_waived_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '本单免押抵扣金额',
|
||||
deposit_waived_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '免押抵扣金额(分)',
|
||||
platform_fee DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '平台手续费',
|
||||
platform_fee_cent BIGINT NOT NULL DEFAULT 0 COMMENT '平台手续费(分)',
|
||||
account_snapshot JSON NULL COMMENT '账号快照(下单时的账号状态)',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'pending_payment' COMMENT '订单状态: pending_payment待支付, active进行中, completed已完成, cancelled已取消, closed已关闭',
|
||||
@@ -161,24 +152,15 @@ CREATE TABLE IF NOT EXISTS order_checkouts (
|
||||
order_id BIGINT UNSIGNED NOT NULL COMMENT '订单ID',
|
||||
initiated_by BIGINT UNSIGNED NOT NULL COMMENT '发起者ID',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'submitted' COMMENT '结算状态: submitted已提交, renter_confirmed租客确认, renter_rejected租客拒绝, completed完成',
|
||||
rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '租金',
|
||||
rent_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '租金(分)',
|
||||
owner_rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '号主实得租金',
|
||||
owner_rent_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '号主实得租金(分)',
|
||||
platform_fee DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '平台手续费',
|
||||
platform_fee_cent BIGINT NOT NULL DEFAULT 0 COMMENT '平台手续费(分)',
|
||||
deposit_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '押金',
|
||||
deposit_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '押金(分)',
|
||||
consumable_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '消耗品扣费',
|
||||
consumable_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '消耗品扣费(分)',
|
||||
coin_consumed_m DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '游戏币消耗(百万为单位)',
|
||||
other_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '其他费用',
|
||||
other_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '其他费用(分)',
|
||||
deposit_deduct_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '押金扣除金额',
|
||||
deposit_deduct_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '押金扣除金额(分)',
|
||||
renter_refund_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '租客退款金额',
|
||||
renter_refund_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '租客退款金额(分)',
|
||||
owner_income_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '号主收入金额',
|
||||
owner_income_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '号主收入金额(分)',
|
||||
content TEXT NULL COMMENT '结算说明',
|
||||
evidence_urls JSON NULL COMMENT '证据截图URL列表',
|
||||
@@ -215,9 +197,7 @@ CREATE TABLE IF NOT EXISTS handoff_records (
|
||||
CREATE TABLE IF NOT EXISTS wallet_accounts (
|
||||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
|
||||
available_balance DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '可用余额',
|
||||
available_balance_cent BIGINT NOT NULL DEFAULT 0 COMMENT '可用余额(分)',
|
||||
frozen_balance DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '冻结余额',
|
||||
frozen_balance_cent BIGINT NOT NULL DEFAULT 0 COMMENT '冻结余额(分)',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '钱包状态: active正常, frozen冻结, closed关闭',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
@@ -232,9 +212,7 @@ CREATE TABLE IF NOT EXISTS wallet_ledger (
|
||||
user_id BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
|
||||
order_id BIGINT UNSIGNED NULL COMMENT '关联订单ID',
|
||||
direction VARCHAR(16) NOT NULL COMMENT '方向: in收入, out支出',
|
||||
amount DECIMAL(12,2) NOT NULL COMMENT '金额',
|
||||
amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '金额(分)',
|
||||
balance_after DECIMAL(12,2) NOT NULL COMMENT '变动后余额',
|
||||
balance_after_cent BIGINT NOT NULL DEFAULT 0 COMMENT '变动后余额(分)',
|
||||
balance_type VARCHAR(32) NOT NULL COMMENT '余额类型: available可用, frozen冻结',
|
||||
biz_type VARCHAR(32) NOT NULL COMMENT '业务类型: rent_payment租金支付, deposit_freeze押金冻结, settlement结算, refund退款等',
|
||||
@@ -839,11 +817,8 @@ CREATE TABLE IF NOT EXISTS withdrawal_requests (
|
||||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
withdraw_no VARCHAR(64) NOT NULL COMMENT '提现单号',
|
||||
user_id BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
|
||||
amount DECIMAL(12,2) NOT NULL COMMENT '提现金额',
|
||||
amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '提现金额(分)',
|
||||
fee DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '手续费',
|
||||
fee_cent BIGINT NOT NULL DEFAULT 0 COMMENT '手续费(分)',
|
||||
actual_amount DECIMAL(12,2) NOT NULL COMMENT '实际到账金额',
|
||||
actual_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '实际到账(分)',
|
||||
|
||||
-- 收款账号信息(快照)
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
-- ============================================
|
||||
-- 金额统一重构:新增分字段(BIGINT)
|
||||
-- 目标:所有金额存储使用整数分,避免浮点精度问题
|
||||
-- 展示:统一到角精度(0.1元)
|
||||
-- ============================================
|
||||
|
||||
-- 用户表:免押额度
|
||||
ALTER TABLE users
|
||||
ADD COLUMN IF NOT EXISTS deposit_free_quota_cent BIGINT NOT NULL DEFAULT 0 COMMENT '免押总额度(分)' AFTER deposit_free_quota;
|
||||
|
||||
-- 商品表:价格和押金
|
||||
ALTER TABLE rental_listings
|
||||
ADD COLUMN IF NOT EXISTS price_cent BIGINT NOT NULL DEFAULT 0 COMMENT '租金(分/小时)' AFTER price,
|
||||
ADD COLUMN IF NOT EXISTS deposit_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '押金金额(分)' AFTER deposit_amount;
|
||||
|
||||
-- 订单表:租金、押金、平台费
|
||||
ALTER TABLE rental_orders
|
||||
ADD COLUMN IF NOT EXISTS rent_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '租金总额(分)' AFTER rent_amount,
|
||||
ADD COLUMN IF NOT EXISTS owner_rent_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '号主实得租金(分)' AFTER owner_rent_amount,
|
||||
ADD COLUMN IF NOT EXISTS deposit_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '实际收取押金(分)' AFTER deposit_amount,
|
||||
ADD COLUMN IF NOT EXISTS deposit_original_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '商品原始押金(分)' AFTER deposit_original_amount,
|
||||
ADD COLUMN IF NOT EXISTS deposit_waived_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '免押抵扣金额(分)' AFTER deposit_waived_amount,
|
||||
ADD COLUMN IF NOT EXISTS platform_fee_cent BIGINT NOT NULL DEFAULT 0 COMMENT '平台手续费(分)' AFTER platform_fee;
|
||||
|
||||
-- 结算记录表:所有金额字段
|
||||
ALTER TABLE order_checkouts
|
||||
ADD COLUMN IF NOT EXISTS rent_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '租金(分)' AFTER rent_amount,
|
||||
ADD COLUMN IF NOT EXISTS owner_rent_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '号主实得租金(分)' AFTER owner_rent_amount,
|
||||
ADD COLUMN IF NOT EXISTS platform_fee_cent BIGINT NOT NULL DEFAULT 0 COMMENT '平台手续费(分)' AFTER platform_fee,
|
||||
ADD COLUMN IF NOT EXISTS deposit_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '押金(分)' AFTER deposit_amount,
|
||||
ADD COLUMN IF NOT EXISTS consumable_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '消耗品扣费(分)' AFTER consumable_amount,
|
||||
ADD COLUMN IF NOT EXISTS other_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '其他费用(分)' AFTER other_amount,
|
||||
ADD COLUMN IF NOT EXISTS deposit_deduct_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '押金扣除金额(分)' AFTER deposit_deduct_amount,
|
||||
ADD COLUMN IF NOT EXISTS renter_refund_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '租客退款金额(分)' AFTER renter_refund_amount,
|
||||
ADD COLUMN IF NOT EXISTS owner_income_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '号主收入金额(分)' AFTER owner_income_amount;
|
||||
|
||||
-- 钱包账户表:余额
|
||||
ALTER TABLE wallet_accounts
|
||||
ADD COLUMN IF NOT EXISTS available_balance_cent BIGINT NOT NULL DEFAULT 0 COMMENT '可用余额(分)' AFTER available_balance,
|
||||
ADD COLUMN IF NOT EXISTS frozen_balance_cent BIGINT NOT NULL DEFAULT 0 COMMENT '冻结余额(分)' AFTER frozen_balance;
|
||||
|
||||
-- 钱包流水表:金额和余额
|
||||
ALTER TABLE wallet_ledger
|
||||
ADD COLUMN IF NOT EXISTS amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '金额(分)' AFTER amount,
|
||||
ADD COLUMN IF NOT EXISTS balance_after_cent BIGINT NOT NULL DEFAULT 0 COMMENT '变动后余额(分)' AFTER balance_after;
|
||||
|
||||
-- 提现表是可选模块;仅在表存在时修改,避免缺表环境迁移失败。
|
||||
SET @withdrawal_requests_exists := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'withdrawal_requests'
|
||||
);
|
||||
SET @sql := IF(@withdrawal_requests_exists > 0,
|
||||
'ALTER TABLE withdrawal_requests
|
||||
ADD COLUMN IF NOT EXISTS amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT ''提现金额(分)'' AFTER amount,
|
||||
ADD COLUMN IF NOT EXISTS fee_cent BIGINT NOT NULL DEFAULT 0 COMMENT ''手续费(分)'' AFTER fee,
|
||||
ADD COLUMN IF NOT EXISTS actual_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT ''实际到账(分)'' AFTER actual_amount',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ============================================
|
||||
-- 数据迁移:从 DECIMAL 复制到 BIGINT(分)
|
||||
-- 注意:ROUND(value * 100) 确保精度
|
||||
-- ============================================
|
||||
|
||||
-- 用户免押额度
|
||||
UPDATE users
|
||||
SET deposit_free_quota_cent = ROUND(deposit_free_quota * 100)
|
||||
WHERE deposit_free_quota > 0;
|
||||
|
||||
-- 商品价格和押金
|
||||
UPDATE rental_listings
|
||||
SET price_cent = ROUND(price * 100),
|
||||
deposit_amount_cent = ROUND(deposit_amount * 100)
|
||||
WHERE price > 0 OR deposit_amount > 0;
|
||||
|
||||
-- 订单金额
|
||||
UPDATE rental_orders
|
||||
SET rent_amount_cent = ROUND(rent_amount * 100),
|
||||
owner_rent_amount_cent = ROUND(owner_rent_amount * 100),
|
||||
deposit_amount_cent = ROUND(deposit_amount * 100),
|
||||
deposit_original_amount_cent = ROUND(deposit_original_amount * 100),
|
||||
deposit_waived_amount_cent = ROUND(deposit_waived_amount * 100),
|
||||
platform_fee_cent = ROUND(platform_fee * 100)
|
||||
WHERE rent_amount > 0 OR owner_rent_amount > 0 OR deposit_amount > 0
|
||||
OR deposit_original_amount > 0 OR deposit_waived_amount > 0 OR platform_fee > 0;
|
||||
|
||||
-- 结算记录
|
||||
UPDATE order_checkouts
|
||||
SET rent_amount_cent = ROUND(rent_amount * 100),
|
||||
owner_rent_amount_cent = ROUND(owner_rent_amount * 100),
|
||||
platform_fee_cent = ROUND(platform_fee * 100),
|
||||
deposit_amount_cent = ROUND(deposit_amount * 100),
|
||||
consumable_amount_cent = ROUND(consumable_amount * 100),
|
||||
other_amount_cent = ROUND(other_amount * 100),
|
||||
deposit_deduct_amount_cent = ROUND(deposit_deduct_amount * 100),
|
||||
renter_refund_amount_cent = ROUND(renter_refund_amount * 100),
|
||||
owner_income_amount_cent = ROUND(owner_income_amount * 100)
|
||||
WHERE rent_amount > 0 OR owner_rent_amount > 0 OR platform_fee > 0
|
||||
OR deposit_amount > 0 OR consumable_amount > 0 OR other_amount > 0
|
||||
OR deposit_deduct_amount > 0 OR renter_refund_amount > 0 OR owner_income_amount > 0;
|
||||
|
||||
-- 钱包余额
|
||||
UPDATE wallet_accounts
|
||||
SET available_balance_cent = ROUND(available_balance * 100),
|
||||
frozen_balance_cent = ROUND(frozen_balance * 100)
|
||||
WHERE available_balance > 0 OR frozen_balance > 0;
|
||||
|
||||
-- 钱包流水
|
||||
UPDATE wallet_ledger
|
||||
SET amount_cent = ROUND(amount * 100),
|
||||
balance_after_cent = ROUND(balance_after * 100)
|
||||
WHERE amount != 0 OR balance_after != 0;
|
||||
|
||||
-- 提现记录
|
||||
SET @sql := IF(@withdrawal_requests_exists > 0,
|
||||
'UPDATE withdrawal_requests
|
||||
SET amount_cent = ROUND(amount * 100),
|
||||
fee_cent = ROUND(fee * 100),
|
||||
actual_amount_cent = ROUND(actual_amount * 100)
|
||||
WHERE amount > 0 OR fee > 0 OR actual_amount > 0',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
-- ============================================
|
||||
-- 回滚:删除所有新增的分字段
|
||||
-- ============================================
|
||||
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS deposit_free_quota_cent;
|
||||
|
||||
ALTER TABLE rental_listings
|
||||
DROP COLUMN IF EXISTS price_cent,
|
||||
DROP COLUMN IF EXISTS deposit_amount_cent;
|
||||
|
||||
ALTER TABLE rental_orders
|
||||
DROP COLUMN IF EXISTS rent_amount_cent,
|
||||
DROP COLUMN IF EXISTS owner_rent_amount_cent,
|
||||
DROP COLUMN IF EXISTS deposit_amount_cent,
|
||||
DROP COLUMN IF EXISTS deposit_original_amount_cent,
|
||||
DROP COLUMN IF EXISTS deposit_waived_amount_cent,
|
||||
DROP COLUMN IF EXISTS platform_fee_cent;
|
||||
|
||||
ALTER TABLE order_checkouts
|
||||
DROP COLUMN IF EXISTS rent_amount_cent,
|
||||
DROP COLUMN IF EXISTS owner_rent_amount_cent,
|
||||
DROP COLUMN IF EXISTS platform_fee_cent,
|
||||
DROP COLUMN IF EXISTS deposit_amount_cent,
|
||||
DROP COLUMN IF EXISTS consumable_amount_cent,
|
||||
DROP COLUMN IF EXISTS other_amount_cent,
|
||||
DROP COLUMN IF EXISTS deposit_deduct_amount_cent,
|
||||
DROP COLUMN IF EXISTS renter_refund_amount_cent,
|
||||
DROP COLUMN IF EXISTS owner_income_amount_cent;
|
||||
|
||||
ALTER TABLE wallet_accounts
|
||||
DROP COLUMN IF EXISTS available_balance_cent,
|
||||
DROP COLUMN IF EXISTS frozen_balance_cent;
|
||||
|
||||
ALTER TABLE wallet_ledger
|
||||
DROP COLUMN IF EXISTS amount_cent,
|
||||
DROP COLUMN IF EXISTS balance_after_cent;
|
||||
|
||||
SET @withdrawal_requests_exists := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'withdrawal_requests'
|
||||
);
|
||||
SET @sql := IF(@withdrawal_requests_exists > 0,
|
||||
'ALTER TABLE withdrawal_requests
|
||||
DROP COLUMN IF EXISTS amount_cent,
|
||||
DROP COLUMN IF EXISTS fee_cent,
|
||||
DROP COLUMN IF EXISTS actual_amount_cent',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
-- +goose StatementEnd
|
||||
+11
-14
@@ -11,26 +11,23 @@ func Round(value float64) float64 {
|
||||
return math.Round(value*10) / 10
|
||||
}
|
||||
|
||||
// ToCent 将角转换为分(整数),用于存储
|
||||
// 12.3角 -> 123分
|
||||
// 12.34角 -> 123分(自动舍入到角)
|
||||
func ToCent(jiao float64) int64 {
|
||||
return int64(math.Round(jiao * 10))
|
||||
// ToCent 将元转换为分(整数),用于存储。
|
||||
// 12.34 元 -> 1234 分
|
||||
func ToCent(yuan float64) int64 {
|
||||
return int64(math.Round(yuan * 100))
|
||||
}
|
||||
|
||||
// ToJiao 将分转换为角(0.1元精度),用于API响应
|
||||
// 123分 -> 12.3角
|
||||
// 1234分 -> 123.4角
|
||||
func ToJiao(cent int64) float64 {
|
||||
return float64(cent) / 10.0
|
||||
// ToDisplayYuan 将分转换为按角精度展示的元值。
|
||||
// 1234 分 -> 12.3 元
|
||||
func ToDisplayYuan(cent int64) float64 {
|
||||
return Round(float64(cent) / 100)
|
||||
}
|
||||
|
||||
// Format 格式化分为字符串(角精度,保留1位小数)
|
||||
// 123分 -> "12.3"
|
||||
// 1230分 -> "123.0"
|
||||
// 1234 分 -> "12.3"
|
||||
// 1230 分 -> "12.3"
|
||||
func Format(cent int64) string {
|
||||
jiao := ToJiao(cent)
|
||||
return fmt.Sprintf("%.1f", jiao)
|
||||
return fmt.Sprintf("%.1f", ToDisplayYuan(cent))
|
||||
}
|
||||
|
||||
// FormatWithSymbol 格式化分为带符号的字符串
|
||||
|
||||
@@ -5,46 +5,44 @@ import "testing"
|
||||
func TestToCent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jiao float64
|
||||
yuan float64
|
||||
want int64
|
||||
}{
|
||||
{"12.3角转123分", 12.3, 123},
|
||||
{"12.34角舍入到123分", 12.34, 123},
|
||||
{"12.36角舍入到124分", 12.36, 124},
|
||||
{"0.1角转1分", 0.1, 1},
|
||||
{"0.05角舍入到1分", 0.05, 1},
|
||||
{"0.04角舍入到0分", 0.04, 0},
|
||||
{"100角转1000分", 100.0, 1000},
|
||||
{"12.3元转1230分", 12.3, 1230},
|
||||
{"12.34元转1234分", 12.34, 1234},
|
||||
{"12.36元转1236分", 12.36, 1236},
|
||||
{"0.1元转10分", 0.1, 10},
|
||||
{"100元转10000分", 100.0, 10000},
|
||||
{"零值", 0.0, 0},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ToCent(tt.jiao)
|
||||
got := ToCent(tt.yuan)
|
||||
if got != tt.want {
|
||||
t.Errorf("ToCent(%v) = %v, want %v", tt.jiao, got, tt.want)
|
||||
t.Errorf("ToCent(%v) = %v, want %v", tt.yuan, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToJiao(t *testing.T) {
|
||||
func TestToDisplayYuan(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cent int64
|
||||
want float64
|
||||
}{
|
||||
{"123分转12.3角", 123, 12.3},
|
||||
{"1234分转123.4角", 1234, 123.4},
|
||||
{"1分转0.1角", 1, 0.1},
|
||||
{"10分转1.0角", 10, 1.0},
|
||||
{"1000分转100.0角", 1000, 100.0},
|
||||
{"123分展示1.2元", 123, 1.2},
|
||||
{"1234分展示12.3元", 1234, 12.3},
|
||||
{"1236分展示12.4元", 1236, 12.4},
|
||||
{"10分展示0.1元", 10, 0.1},
|
||||
{"1000分展示10.0元", 1000, 10.0},
|
||||
{"零值", 0, 0.0},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ToJiao(tt.cent)
|
||||
got := ToDisplayYuan(tt.cent)
|
||||
if got != tt.want {
|
||||
t.Errorf("ToJiao(%v) = %v, want %v", tt.cent, got, tt.want)
|
||||
t.Errorf("ToDisplayYuan(%v) = %v, want %v", tt.cent, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -56,11 +54,11 @@ func TestFormat(t *testing.T) {
|
||||
cent int64
|
||||
want string
|
||||
}{
|
||||
{"123分格式化", 123, "12.3"},
|
||||
{"1234分格式化", 1234, "123.4"},
|
||||
{"10分格式化", 10, "1.0"},
|
||||
{"1分格式化", 1, "0.1"},
|
||||
{"1000分格式化", 1000, "100.0"},
|
||||
{"123分格式化", 123, "1.2"},
|
||||
{"1234分格式化", 1234, "12.3"},
|
||||
{"1236分格式化", 1236, "12.4"},
|
||||
{"10分格式化", 10, "0.1"},
|
||||
{"1000分格式化", 1000, "10.0"},
|
||||
{"零值格式化", 0, "0.0"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -79,8 +77,8 @@ func TestFormatWithSymbol(t *testing.T) {
|
||||
cent int64
|
||||
want string
|
||||
}{
|
||||
{"123分带符号", 123, "¥12.3"},
|
||||
{"1000分带符号", 1000, "¥100.0"},
|
||||
{"123分带符号", 123, "¥1.2"},
|
||||
{"1000分带符号", 1000, "¥10.0"},
|
||||
{"零值带符号", 0, "¥0.0"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -139,21 +137,21 @@ func TestMinMax(t *testing.T) {
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
originalJiao float64
|
||||
expectJiao float64 // 因为角精度,可能会有舍入
|
||||
originalYuan float64
|
||||
expectDisplay float64
|
||||
}{
|
||||
{"12.3角往返", 12.3, 12.3},
|
||||
{"12.34角往返(舍入)", 12.34, 12.3},
|
||||
{"100.0角往返", 100.0, 100.0},
|
||||
{"0.1角往返", 0.1, 0.1},
|
||||
{"12.3元往返", 12.3, 12.3},
|
||||
{"12.34元往返(展示到角)", 12.34, 12.3},
|
||||
{"100.0元往返", 100.0, 100.0},
|
||||
{"0.1元往返", 0.1, 0.1},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cent := ToCent(tt.originalJiao)
|
||||
gotJiao := ToJiao(cent)
|
||||
if gotJiao != tt.expectJiao {
|
||||
t.Errorf("往返转换:%v角 -> %v分 -> %v角, 期望 %v角",
|
||||
tt.originalJiao, cent, gotJiao, tt.expectJiao)
|
||||
cent := ToCent(tt.originalYuan)
|
||||
got := ToDisplayYuan(cent)
|
||||
if got != tt.expectDisplay {
|
||||
t.Errorf("往返转换:%v元 -> %v分 -> %v元, 期望 %v元",
|
||||
tt.originalYuan, cent, got, tt.expectDisplay)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface DashboardMetrics {
|
||||
total_orders: number
|
||||
renting_orders: number
|
||||
today_orders: number
|
||||
today_ledger_amount: number
|
||||
today_ledger_amount_cent: number
|
||||
}
|
||||
|
||||
export interface DashboardPending {
|
||||
@@ -27,8 +27,8 @@ export interface DashboardRecentOrder {
|
||||
renter_id: number
|
||||
owner_id: number
|
||||
status: OrderStatus
|
||||
rent_amount: number
|
||||
deposit_amount: number
|
||||
rent_amount_cent: number
|
||||
deposit_amount_cent: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ export interface FinanceSummary {
|
||||
total_refund_amount_cent: number
|
||||
pending_refund_amount_cent: number
|
||||
channel_net_amount_cent: number
|
||||
platform_income_amount: number
|
||||
owner_should_income_amount: number
|
||||
owner_wallet_income_amount: number
|
||||
settlement_diff_amount: number
|
||||
platform_income_amount_cent: number
|
||||
owner_should_income_amount_cent: number
|
||||
owner_wallet_income_amount_cent: number
|
||||
settlement_diff_amount_cent: number
|
||||
successful_pay_count: number
|
||||
successful_refund_count: number
|
||||
pending_refund_count: number
|
||||
@@ -23,10 +23,10 @@ export interface FinanceDailyItem {
|
||||
total_refund_amount_cent: number
|
||||
pending_refund_amount_cent: number
|
||||
channel_net_amount_cent: number
|
||||
platform_income_amount: number
|
||||
owner_should_income_amount: number
|
||||
owner_wallet_income_amount: number
|
||||
settlement_diff_amount: number
|
||||
platform_income_amount_cent: number
|
||||
owner_should_income_amount_cent: number
|
||||
owner_wallet_income_amount_cent: number
|
||||
settlement_diff_amount_cent: number
|
||||
successful_pay_count: number
|
||||
successful_refund_count: number
|
||||
pending_refund_count: number
|
||||
@@ -51,20 +51,20 @@ export interface FinanceDetail {
|
||||
owner_id: number
|
||||
owner_phone: string
|
||||
owner_nickname: string
|
||||
order_rent_amount: number
|
||||
order_deposit_amount: number
|
||||
checkout_rent_amount: number
|
||||
checkout_renter_refund: number
|
||||
checkout_owner_income: number
|
||||
checkout_platform_fee: number
|
||||
owner_wallet_income_amount: number
|
||||
order_rent_amount_cent: number
|
||||
order_deposit_amount_cent: number
|
||||
checkout_rent_amount_cent: number
|
||||
checkout_renter_refund_cent: number
|
||||
checkout_owner_income_cent: number
|
||||
checkout_platform_fee_cent: number
|
||||
owner_wallet_income_amount_cent: number
|
||||
paid_amount_cent: number
|
||||
refunded_amount_cent: number
|
||||
refunding_amount_cent: number
|
||||
failed_refund_amount_cent: number
|
||||
channel_net_amount_cent: number
|
||||
platform_net_amount: number
|
||||
settlement_diff_amount: number
|
||||
platform_net_amount_cent: number
|
||||
settlement_diff_amount_cent: number
|
||||
finance_status: string
|
||||
created_at: string
|
||||
settled_at?: string
|
||||
|
||||
@@ -10,9 +10,9 @@ export interface AdminUserItem {
|
||||
realname_status: RealnameStatusValue
|
||||
risk_status: RiskStatus
|
||||
credit_score: number
|
||||
deposit_free_quota: number
|
||||
deposit_free_used: number
|
||||
deposit_free_remaining: number
|
||||
deposit_free_quota_cent: number
|
||||
deposit_free_used_cent: number
|
||||
deposit_free_remaining_cent: number
|
||||
status: UserStatus
|
||||
order_count: number
|
||||
listing_count: number
|
||||
@@ -44,10 +44,10 @@ export async function unfreezeAdminUser(id: number) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function setAdminUserDepositFreeQuota(id: number, amount: number) {
|
||||
export async function setAdminUserDepositFreeQuota(id: number, amountCent: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<AdminUserItem>>(
|
||||
`/admin/users/${id}/deposit-free-quota`,
|
||||
{ amount }
|
||||
{ amount_cent: amountCent }
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
@@ -12,9 +12,6 @@ export interface WithdrawalDetail {
|
||||
amount_cent: number
|
||||
fee_cent: number
|
||||
actual_amount_cent: number
|
||||
amount?: number
|
||||
fee?: number
|
||||
actual_amount?: number
|
||||
payment_account_id: number | null
|
||||
account_type: string
|
||||
account_name: string
|
||||
|
||||
@@ -17,12 +17,10 @@ import {
|
||||
} from '@element-plus/icons-vue'
|
||||
import { fetchAdminDashboard, type AdminDashboard } from '@/features/admin/api/adminDashboard'
|
||||
import { useAdminTable } from '@/features/admin/composables/useAdminTable'
|
||||
import { useMoney } from '@/shared/composables/useMoney'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import { disputeStatusLabel, orderStatusLabel } from '@/utils/statusLabels'
|
||||
import { formatDateTime } from '@/utils/time'
|
||||
|
||||
const money = useMoney()
|
||||
|
||||
const {
|
||||
loading,
|
||||
error,
|
||||
@@ -98,7 +96,7 @@ const {
|
||||
</div>
|
||||
<div class="metric-content">
|
||||
<span>今日流水</span>
|
||||
<strong>{{ money(dashboard.metrics.today_ledger_amount) }}</strong>
|
||||
<strong>{{ formatCentWithSymbol(dashboard.metrics.today_ledger_amount_cent) }}</strong>
|
||||
<small>今日订单 {{ dashboard.metrics.today_orders }} 个</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -224,9 +222,9 @@ const {
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="rent_amount" label="金额" width="100">
|
||||
<el-table-column prop="rent_amount_cent" label="金额" width="100">
|
||||
<template #default="{ row }">
|
||||
<span class="amount">{{ money(row.rent_amount) }}</span>
|
||||
<span class="amount">{{ formatCentWithSymbol(row.rent_amount_cent) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" min-width="180">
|
||||
|
||||
@@ -7,6 +7,7 @@ import { fetchAdminFileBlob } from '@/shared/api/files'
|
||||
import { disputeStatusLabel } from '@/utils/statusLabels'
|
||||
import { formatDateTime } from '@/utils/time'
|
||||
import { formatListingNo } from '@/utils/listingDisplay'
|
||||
import { yuanToCent } from '@/shared/utils/money'
|
||||
import AdminTablePagination from '../components/AdminTablePagination.vue'
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -100,7 +101,7 @@ async function handleArbitrate() {
|
||||
await arbitrateDispute(activeDispute.value.id, {
|
||||
result: result.value,
|
||||
remark: remark.value.trim(),
|
||||
amount: amount.value,
|
||||
amount_cent: amount.value ? yuanToCent(amount.value) : undefined,
|
||||
})
|
||||
ElMessage.success('仲裁结果已保存,双方已收到通知')
|
||||
activeDispute.value = null
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type FinanceDashboard,
|
||||
type FinanceDailyItem,
|
||||
} from '@/features/admin/api/adminFinance'
|
||||
import { formatMoneyWithSymbol } from '@/shared/utils/money'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import { formatDateTime } from '@/utils/time'
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -31,11 +31,7 @@ async function loadDashboard() {
|
||||
}
|
||||
|
||||
function moneyCent(value: number) {
|
||||
return formatMoneyWithSymbol(Number(value || 0) / 100)
|
||||
}
|
||||
|
||||
function money(value: number) {
|
||||
return formatMoneyWithSymbol(value)
|
||||
return formatCentWithSymbol(value)
|
||||
}
|
||||
|
||||
function defaultStartDate() {
|
||||
@@ -53,11 +49,11 @@ function formatInputDate(date: Date) {
|
||||
}
|
||||
|
||||
function diffType(value: number) {
|
||||
return Math.abs(Number(value || 0)) >= 0.05 ? 'danger' : 'success'
|
||||
return Math.abs(Number(value || 0)) >= 5 ? 'danger' : 'success'
|
||||
}
|
||||
|
||||
function rowDiffClass(row: FinanceDailyItem) {
|
||||
return Math.abs(Number(row.settlement_diff_amount || 0)) >= 0.05 ? 'amount-danger' : ''
|
||||
return Math.abs(Number(row.settlement_diff_amount_cent || 0)) >= 5 ? 'amount-danger' : ''
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -107,17 +103,17 @@ function rowDiffClass(row: FinanceDailyItem) {
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>平台收入</span>
|
||||
<strong>{{ money(dashboard.summary.platform_income_amount) }}</strong>
|
||||
<strong>{{ moneyCent(dashboard.summary.platform_income_amount_cent) }}</strong>
|
||||
<small>{{ dashboard.summary.settled_order_count }} 个已结算订单</small>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>号主应得</span>
|
||||
<strong>{{ money(dashboard.summary.owner_should_income_amount) }}</strong>
|
||||
<strong>{{ moneyCent(dashboard.summary.owner_should_income_amount_cent) }}</strong>
|
||||
<small>结账单口径</small>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>号主实际入账</span>
|
||||
<strong>{{ money(dashboard.summary.owner_wallet_income_amount) }}</strong>
|
||||
<strong>{{ moneyCent(dashboard.summary.owner_wallet_income_amount_cent) }}</strong>
|
||||
<small>钱包流水口径</small>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
@@ -128,8 +124,8 @@ function rowDiffClass(row: FinanceDailyItem) {
|
||||
<div class="metric-card">
|
||||
<span>结算差异</span>
|
||||
<strong>
|
||||
<el-tag :type="diffType(dashboard.summary.settlement_diff_amount)">
|
||||
{{ money(dashboard.summary.settlement_diff_amount) }}
|
||||
<el-tag :type="diffType(dashboard.summary.settlement_diff_amount_cent)">
|
||||
{{ moneyCent(dashboard.summary.settlement_diff_amount_cent) }}
|
||||
</el-tag>
|
||||
</strong>
|
||||
<small>{{ dashboard.summary.financial_exception_count }} 个异常订单</small>
|
||||
@@ -148,17 +144,17 @@ function rowDiffClass(row: FinanceDailyItem) {
|
||||
<template #default="{ row }">{{ moneyCent(row.channel_net_amount_cent) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="平台收入" width="130">
|
||||
<template #default="{ row }">{{ money(row.platform_income_amount) }}</template>
|
||||
<template #default="{ row }">{{ moneyCent(row.platform_income_amount_cent) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="号主应得" width="130">
|
||||
<template #default="{ row }">{{ money(row.owner_should_income_amount) }}</template>
|
||||
<template #default="{ row }">{{ moneyCent(row.owner_should_income_amount_cent) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="号主入账" width="130">
|
||||
<template #default="{ row }">{{ money(row.owner_wallet_income_amount) }}</template>
|
||||
<template #default="{ row }">{{ moneyCent(row.owner_wallet_income_amount_cent) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算差异" width="130">
|
||||
<template #default="{ row }">
|
||||
<span :class="rowDiffClass(row)">{{ money(row.settlement_diff_amount) }}</span>
|
||||
<span :class="rowDiffClass(row)">{{ moneyCent(row.settlement_diff_amount_cent) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收款/退款/结算" min-width="170">
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
fetchFinanceDetails,
|
||||
type FinanceDetail,
|
||||
} from '@/features/admin/api/adminFinance'
|
||||
import { formatMoneyWithSymbol } from '@/shared/utils/money'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import { orderStatusLabel } from '@/utils/statusLabels'
|
||||
import { formatDateTime } from '@/utils/time'
|
||||
import AdminTablePagination from '../components/AdminTablePagination.vue'
|
||||
@@ -59,12 +59,8 @@ async function handlePageChange() {
|
||||
await loadDetails()
|
||||
}
|
||||
|
||||
function money(value: number) {
|
||||
return formatMoneyWithSymbol(value)
|
||||
}
|
||||
|
||||
function moneyCent(value: number) {
|
||||
return formatMoneyWithSymbol(Number(value || 0) / 100)
|
||||
return formatCentWithSymbol(value)
|
||||
}
|
||||
|
||||
function financeStatusLabel(status: string) {
|
||||
@@ -226,14 +222,14 @@ function formatInputDate(date: Date) {
|
||||
{{ moneyCent(row.refunding_amount_cent) }}
|
||||
</span>
|
||||
<span class="amount-text amount-cell">{{ moneyCent(row.channel_net_amount_cent) }}</span>
|
||||
<span class="amount-text amount-cell">{{ money(row.checkout_platform_fee) }}</span>
|
||||
<span class="amount-text amount-cell">{{ money(row.checkout_owner_income) }}</span>
|
||||
<span class="amount-text amount-cell">{{ money(row.owner_wallet_income_amount) }}</span>
|
||||
<span class="amount-text amount-cell">{{ moneyCent(row.checkout_platform_fee_cent) }}</span>
|
||||
<span class="amount-text amount-cell">{{ moneyCent(row.checkout_owner_income_cent) }}</span>
|
||||
<span class="amount-text amount-cell">{{ moneyCent(row.owner_wallet_income_amount_cent) }}</span>
|
||||
<span
|
||||
class="amount-text amount-cell"
|
||||
:class="{ 'amount-danger': Math.abs(row.settlement_diff_amount) >= 0.05 }"
|
||||
:class="{ 'amount-danger': Math.abs(row.settlement_diff_amount_cent) >= 5 }"
|
||||
>
|
||||
{{ money(row.settlement_diff_amount) }}
|
||||
{{ moneyCent(row.settlement_diff_amount_cent) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { fetchAdminFileBlob } from '@/shared/api/files'
|
||||
import { formatMoneyWithSymbol } from '@/shared/utils/money'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import {
|
||||
adminMarkListingAbnormal,
|
||||
adminOfflineListing,
|
||||
@@ -78,12 +78,12 @@ async function submitAction() {
|
||||
}
|
||||
}
|
||||
|
||||
function money(value: number) {
|
||||
return formatMoneyWithSymbol(value)
|
||||
function moneyCent(value: number) {
|
||||
return formatCentWithSymbol(value)
|
||||
}
|
||||
|
||||
function listingPrice(row: Listing) {
|
||||
return money(row.price)
|
||||
return moneyCent(row.price_cent)
|
||||
}
|
||||
|
||||
function extractObjectKey(url: string) {
|
||||
@@ -150,7 +150,7 @@ function readError(error: unknown, fallback: string) {
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>押金</span>
|
||||
<strong>{{ money(listing.deposit_amount) }}</strong>
|
||||
<strong>{{ moneyCent(listing.deposit_amount_cent) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -172,7 +172,7 @@ function readError(error: unknown, fallback: string) {
|
||||
<p>号主:{{ listing.owner_phone || listing.owner_nickname || listing.owner_id }}</p>
|
||||
<p>号主 ID:{{ listing.owner_id }}</p>
|
||||
<p>价格:{{ listingPrice(listing) }}</p>
|
||||
<p>押金:{{ money(listing.deposit_amount) }}</p>
|
||||
<p>押金:{{ moneyCent(listing.deposit_amount_cent) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
||||
|
||||
import { fetchAdminFileBlob } from '@/shared/api/files'
|
||||
import { formatMoneyWithSymbol } from '@/shared/utils/money'
|
||||
import { centToYuan, formatCent, formatMoneyWithSymbol, yuanToCent } from '@/shared/utils/money'
|
||||
import {
|
||||
adjustListingReviewPrice,
|
||||
approveListing,
|
||||
@@ -185,10 +185,10 @@ async function handleSavePriceAdjust() {
|
||||
reason: priceAdjustForm.reason.trim(),
|
||||
}
|
||||
: {
|
||||
buyer_total_price: Number(priceAdjustForm.buyer_total_price || 0),
|
||||
buyer_total_price_cent: yuanToCent(Number(priceAdjustForm.buyer_total_price || 0)),
|
||||
reason: priceAdjustForm.reason.trim(),
|
||||
}
|
||||
if ((payload.buyer_ratio || payload.buyer_total_price || 0) <= 0) {
|
||||
if ((payload.buyer_ratio || payload.buyer_total_price_cent || 0) <= 0) {
|
||||
ElMessage.warning('请填写有效的加价后比例或价格')
|
||||
return
|
||||
}
|
||||
@@ -298,8 +298,9 @@ function roundPreviewRatio(value: number) {
|
||||
function sellerTotalPrice(row: Listing) {
|
||||
const value = breakdownNumber(row, 'seller_total_price')
|
||||
if (value > 0) return value
|
||||
const fallback = Number(row.price || 0) - getListingConsumablePrice(row)
|
||||
return fallback > 0 ? fallback : Number(row.price || 0)
|
||||
const price = centToYuan(row.price_cent)
|
||||
const fallback = price - getListingConsumablePrice(row)
|
||||
return fallback > 0 ? fallback : price
|
||||
}
|
||||
|
||||
function sellerCoinBasePrice(row: Listing) {
|
||||
@@ -318,7 +319,7 @@ function sellerRatio(row: Listing) {
|
||||
|
||||
function buyerTotalPrice(row: Listing) {
|
||||
const value = breakdownNumber(row, 'buyer_total_price')
|
||||
return value > 0 ? value : Number(row.price || 0)
|
||||
return value > 0 ? value : centToYuan(row.price_cent)
|
||||
}
|
||||
|
||||
function buyerCoinBasePrice(row: Listing) {
|
||||
@@ -463,7 +464,7 @@ function riskItems(row: Listing): RiskItem[] {
|
||||
if (hasBanRecord(row)) items.push({ label: `封禁记录:${banRecordText(row)}`, level: 'danger' })
|
||||
if (
|
||||
getListingConsumablePrice(row) > 0 &&
|
||||
Number(row.deposit_amount || 0) <= getListingConsumablePrice(row)
|
||||
centToYuan(row.deposit_amount_cent) <= getListingConsumablePrice(row)
|
||||
) {
|
||||
items.push({ label: '押金不高于消耗品价值', level: 'danger' })
|
||||
}
|
||||
@@ -646,7 +647,7 @@ function readError(error: unknown, fallback: string) {
|
||||
<span>KD {{ assetNumberText(item, 'secret_kd') }}</span>
|
||||
</div>
|
||||
<div class="queue-footer">
|
||||
<span>{{ money(item.price) }} / 押 {{ money(item.deposit_amount) }}</span>
|
||||
<span>¥{{ formatCent(item.price_cent) }} / 押 ¥{{ formatCent(item.deposit_amount_cent) }}</span>
|
||||
<el-tag v-if="isExternalUpload(item)" size="small" type="info">{{
|
||||
uploaderName(item)
|
||||
}}</el-tag>
|
||||
@@ -733,7 +734,7 @@ function readError(error: unknown, fallback: string) {
|
||||
</div>
|
||||
<div class="price-decision-card deposit">
|
||||
<span>押金与损耗</span>
|
||||
<strong>{{ money(selectedListing.deposit_amount) }}</strong>
|
||||
<strong>¥{{ formatCent(selectedListing.deposit_amount_cent) }}</strong>
|
||||
<p>每日损耗 {{ dailyLossText(selectedListing) }}</p>
|
||||
<small>哈夫币 {{ formatHafCoinM(getCoinWan(selectedListing)) }}</small>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
type Listing,
|
||||
} from '@/features/listings'
|
||||
import { useAdminTable } from '@/features/admin/composables/useAdminTable'
|
||||
import { useMoney } from '@/shared/composables/useMoney'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import {
|
||||
assetRegions,
|
||||
formatEstimatedRentalDuration,
|
||||
@@ -23,8 +23,6 @@ import {
|
||||
getSkinGroup,
|
||||
} from '@/utils/listingDisplay'
|
||||
|
||||
const money = useMoney()
|
||||
|
||||
const filters = reactive<AdminListingQuery>({
|
||||
owner_id: '',
|
||||
status: '',
|
||||
@@ -322,7 +320,7 @@ function readScreenshotError(error: unknown, fallback: string) {
|
||||
}
|
||||
|
||||
function listingPrice(row: Listing) {
|
||||
return money(row.price)
|
||||
return formatCentWithSymbol(row.price_cent)
|
||||
}
|
||||
|
||||
function listingCoinM(row: Listing) {
|
||||
@@ -372,7 +370,7 @@ function characterAndWeaponSkinText(row: Listing) {
|
||||
}
|
||||
|
||||
function rentAndDepositText(row: Listing) {
|
||||
return `${listingPrice(row)}/${money(row.deposit_amount)}`
|
||||
return `${listingPrice(row)}/${formatCentWithSymbol(row.deposit_amount_cent)}`
|
||||
}
|
||||
|
||||
function estimateTitle(row: Listing) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
type RefundStatus,
|
||||
} from '@/features/orders'
|
||||
import { fetchAdminPayments, type AdminPayment } from '@/features/admin/api/adminPayments'
|
||||
import { centToYuan, formatMoney, formatMoneyWithSymbol } from '@/shared/utils/money'
|
||||
import { centToYuan, formatCentWithSymbol, formatMoney } from '@/shared/utils/money'
|
||||
import { handoffStatusLabel, orderStatusLabel } from '@/utils/statusLabels'
|
||||
import { formatDateTime } from '@/utils/time'
|
||||
import { formatListingNo } from '@/utils/listingDisplay'
|
||||
@@ -111,9 +111,9 @@ function money(value: unknown) {
|
||||
return formatMoney(Number(value || 0))
|
||||
}
|
||||
|
||||
function amountYuan(cent: unknown, legacyYuan?: unknown) {
|
||||
function amountYuan(cent: unknown) {
|
||||
if (cent !== undefined && cent !== null) return centToYuan(Number(cent || 0))
|
||||
return Number(legacyYuan || 0)
|
||||
return 0
|
||||
}
|
||||
|
||||
async function handleRefund() {
|
||||
@@ -173,7 +173,7 @@ function paymentBizTypeLabel(type: string) {
|
||||
}
|
||||
|
||||
function moneyCent(value: number) {
|
||||
return formatMoneyWithSymbol(Number(value || 0) / 100)
|
||||
return formatCentWithSymbol(value)
|
||||
}
|
||||
|
||||
function formatHandoffRecordType(type: string) {
|
||||
@@ -232,15 +232,15 @@ function formatHandoffRecordType(type: string) {
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>订单金额</span>
|
||||
<strong>¥{{ money(amountYuan(order.rent_amount_cent, order.rent_amount)) }}</strong>
|
||||
<strong>¥{{ money(amountYuan(order.rent_amount_cent)) }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>平台费用</span>
|
||||
<strong>¥{{ money(amountYuan(order.platform_fee_cent, order.platform_fee)) }}</strong>
|
||||
<strong>¥{{ money(amountYuan(order.platform_fee_cent)) }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>押金</span>
|
||||
<strong>¥{{ money(amountYuan(order.deposit_amount_cent, order.deposit_amount)) }}</strong>
|
||||
<strong>¥{{ money(amountYuan(order.deposit_amount_cent)) }}</strong>
|
||||
</div>
|
||||
<div v-if="refundStatus" class="metric-card">
|
||||
<span>退款状态</span>
|
||||
|
||||
@@ -37,9 +37,9 @@ const filteredOrders = computed(() => {
|
||||
return orders.value.filter(item => item.status === status.value)
|
||||
})
|
||||
|
||||
function amountYuan(cent: unknown, legacyYuan?: unknown) {
|
||||
function amountYuan(cent: unknown) {
|
||||
if (cent !== undefined && cent !== null) return centToYuan(Number(cent || 0))
|
||||
return Number(legacyYuan || 0)
|
||||
return 0
|
||||
}
|
||||
|
||||
function money(value: unknown) {
|
||||
@@ -95,10 +95,10 @@ function money(value: unknown) {
|
||||
<template #default="{ row }">{{ settlementStatusLabel(row.settlement_status) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单金额" width="100">
|
||||
<template #default="{ row }">¥{{ money(amountYuan(row.rent_amount_cent, row.rent_amount)) }}</template>
|
||||
<template #default="{ row }">¥{{ money(amountYuan(row.rent_amount_cent)) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="押金" width="100">
|
||||
<template #default="{ row }">¥{{ money(amountYuan(row.deposit_amount_cent, row.deposit_amount)) }}</template>
|
||||
<template #default="{ row }">¥{{ money(amountYuan(row.deposit_amount_cent)) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" min-width="180">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
type PaymentConfig,
|
||||
} from '@/features/admin/api/paymentConfig'
|
||||
import { formatDateTime } from '@/utils/time'
|
||||
import { formatMoney } from '@/shared/utils/money'
|
||||
import { formatCent } from '@/shared/utils/money'
|
||||
import { readError } from '@/utils/error'
|
||||
import PaymentConfigDialog from '../components/PaymentConfigDialog.vue'
|
||||
|
||||
@@ -231,7 +231,7 @@ function formatEnvironment(env: string) {
|
||||
}
|
||||
|
||||
function formatAmount(amountCent: number) {
|
||||
return formatMoney(amountCent / 100)
|
||||
return formatCent(amountCent)
|
||||
}
|
||||
|
||||
function getStatusType(status: string) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Document, Search } from '@element-plus/icons-vue'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { fetchAdminPayments, type AdminPayment } from '@/features/admin/api/adminPayments'
|
||||
import { formatMoneyWithSymbol } from '@/shared/utils/money'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import { formatDateTime } from '@/utils/time'
|
||||
import AdminTablePagination from '../components/AdminTablePagination.vue'
|
||||
|
||||
@@ -70,7 +70,7 @@ async function handlePageChange() {
|
||||
}
|
||||
|
||||
function moneyCent(value: number) {
|
||||
return formatMoneyWithSymbol(Number(value || 0) / 100)
|
||||
return formatCentWithSymbol(value)
|
||||
}
|
||||
|
||||
function paymentStatusType(status: string) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
unfreezeAdminUser,
|
||||
type AdminUserItem,
|
||||
} from '@/features/admin/api/adminUsers'
|
||||
import { formatMoney } from '@/shared/utils/money'
|
||||
import { centToYuan, formatCent, yuanToCent } from '@/shared/utils/money'
|
||||
import { useAdminPaginatedTable } from '@/features/admin/composables/useAdminPaginatedTable'
|
||||
import { userStatusLabel } from '@/utils/statusLabels'
|
||||
import { formatDateTime } from '@/utils/time'
|
||||
@@ -39,14 +39,14 @@ function openFreeze(row: AdminUserItem) {
|
||||
|
||||
function openDepositQuota(row: AdminUserItem) {
|
||||
quotaUser.value = row
|
||||
quotaAmount.value = Number(row.deposit_free_quota || 0)
|
||||
quotaAmount.value = centToYuan(row.deposit_free_quota_cent)
|
||||
}
|
||||
|
||||
async function handleSetDepositQuota() {
|
||||
if (!quotaUser.value) return
|
||||
submitting.value = true
|
||||
try {
|
||||
await setAdminUserDepositFreeQuota(quotaUser.value.id, quotaAmount.value)
|
||||
await setAdminUserDepositFreeQuota(quotaUser.value.id, yuanToCent(quotaAmount.value))
|
||||
ElMessage.success('免押额度已更新')
|
||||
quotaUser.value = null
|
||||
await loadUsers()
|
||||
@@ -93,8 +93,8 @@ function readError(error: unknown, fallback: string) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
function money(value: number | string | undefined) {
|
||||
return formatMoney(Number(value || 0))
|
||||
function moneyCent(value: number | string | undefined) {
|
||||
return formatCent(Number(value || 0))
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -117,13 +117,13 @@ function money(value: number | string | undefined) {
|
||||
<template #default="{ row }">{{ userStatusLabel(row.status) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="免押额度" width="130">
|
||||
<template #default="{ row }">¥{{ money(row.deposit_free_quota) }}</template>
|
||||
<template #default="{ row }">¥{{ moneyCent(row.deposit_free_quota_cent) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已占用" width="120">
|
||||
<template #default="{ row }">¥{{ money(row.deposit_free_used) }}</template>
|
||||
<template #default="{ row }">¥{{ moneyCent(row.deposit_free_used_cent) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剩余免押" width="120">
|
||||
<template #default="{ row }">¥{{ money(row.deposit_free_remaining) }}</template>
|
||||
<template #default="{ row }">¥{{ moneyCent(row.deposit_free_remaining_cent) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="注册时间" min-width="180">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
@@ -195,8 +195,8 @@ function money(value: number | string | undefined) {
|
||||
<strong>{{ quotaUser.phone }}</strong> · {{ quotaUser.nickname }}
|
||||
</p>
|
||||
<p>
|
||||
已占用 ¥{{ money(quotaUser.deposit_free_used) }},剩余 ¥{{
|
||||
money(quotaUser.deposit_free_remaining)
|
||||
已占用 ¥{{ moneyCent(quotaUser.deposit_free_used_cent) }},剩余 ¥{{
|
||||
moneyCent(quotaUser.deposit_free_remaining_cent)
|
||||
}}
|
||||
</p>
|
||||
<el-input-number
|
||||
|
||||
@@ -47,7 +47,7 @@ export async function fetchAdminDisputes(page = 1, pageSize = 20) {
|
||||
|
||||
export async function arbitrateDispute(
|
||||
id: number,
|
||||
payload: { result: string; remark: string; amount?: number }
|
||||
payload: { result: string; remark: string; amount_cent?: number }
|
||||
) {
|
||||
const { data } = await apiClient.post<ApiResponse<Dispute>>(
|
||||
`/admin/disputes/${id}/arbitrate`,
|
||||
|
||||
@@ -19,8 +19,8 @@ export interface Listing {
|
||||
asset_summary?: Record<string, unknown>
|
||||
screenshot_urls: string[]
|
||||
cover_url: string
|
||||
price: number
|
||||
deposit_amount: number
|
||||
price_cent: number
|
||||
deposit_amount_cent: number
|
||||
is_accelerated_sale?: boolean
|
||||
in_transaction: boolean
|
||||
status: ListingStatus
|
||||
@@ -40,8 +40,8 @@ export interface ListingPayload {
|
||||
haf_coin_amount: number
|
||||
asset_summary?: Record<string, unknown>
|
||||
screenshot_urls: string[]
|
||||
price: number
|
||||
deposit_amount: number
|
||||
price_cent: number
|
||||
deposit_amount_cent: number
|
||||
agreed_virtual_asset_sale: boolean
|
||||
agreed_seller_agreement: boolean
|
||||
}
|
||||
@@ -214,7 +214,7 @@ export async function approveListing(id: number) {
|
||||
|
||||
export interface AdminListingPriceAdjustPayload {
|
||||
buyer_ratio?: number
|
||||
buyer_total_price?: number
|
||||
buyer_total_price_cent?: number
|
||||
reason?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { computed } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import type { Listing } from '@/features/listings'
|
||||
import { formatMoney } from '@/shared/utils/money'
|
||||
import { formatCent, formatMoney } from '@/shared/utils/money'
|
||||
import {
|
||||
formatHafCoinM,
|
||||
getCoinWan,
|
||||
@@ -140,7 +140,7 @@ function formatStatNumber(value: number) {
|
||||
</div>
|
||||
<div class="price-item deposit">
|
||||
<small>押金</small>
|
||||
<span>¥{{ formatMoney(listing.deposit_amount) }}</span>
|
||||
<span>¥{{ formatCent(listing.deposit_amount_cent) }}</span>
|
||||
</div>
|
||||
<div class="price-action">
|
||||
<button class="rent-btn">立即租用</button>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '@/features/orders/api/orders'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
import AuthImage from '@/shared/components/business/AuthImage.vue'
|
||||
import { roundMoney, formatMoney } from '@/shared/utils/money'
|
||||
import { roundMoney, formatMoney, formatCent } from '@/shared/utils/money'
|
||||
import {
|
||||
assetRegions,
|
||||
formatEstimatedRentalDuration,
|
||||
@@ -102,7 +102,7 @@ const detailMetrics = computed(() => {
|
||||
tone: 'coin',
|
||||
},
|
||||
{ label: '价格', value: `¥${orderTotal.value}`, tone: 'price' },
|
||||
{ label: '押金', value: `¥${formatMoney(listing.value.deposit_amount)}`, tone: '' },
|
||||
{ label: '押金', value: `¥${formatCent(listing.value.deposit_amount_cent)}`, tone: '' },
|
||||
]
|
||||
})
|
||||
|
||||
@@ -465,7 +465,7 @@ function listingPrice(item: Listing) {
|
||||
<strong>¥{{ formatMoney(orderPriceBreakdown.consumable) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<em>押金另付 ¥{{ formatMoney(listing.deposit_amount) }}</em>
|
||||
<em>押金另付 ¥{{ formatCent(listing.deposit_amount_cent) }}</em>
|
||||
</div>
|
||||
<dl class="order-check-list">
|
||||
<div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
type ListingPublishOptions,
|
||||
} from '@/features/listings/api/listingOptions'
|
||||
import { fetchListings, type Listing } from '@/features/listings/api/listings'
|
||||
import { formatMoney } from '@/shared/utils/money'
|
||||
import { centToYuan, formatCent, formatMoney } from '@/shared/utils/money'
|
||||
import {
|
||||
defaultHomeAnnouncements,
|
||||
defaultHomeBanners,
|
||||
@@ -347,7 +347,7 @@ function matchesFilters(item: Listing) {
|
||||
if (key === 'price') value = getListingDisplayPrice(item)
|
||||
if (key === 'coin') value = getCoinM(item)
|
||||
if (key === 'secretKd') value = readAssetNumber(item, 'secret_kd')
|
||||
if (key === 'deposit') value = Number(item.deposit_amount || 0)
|
||||
if (key === 'deposit') value = centToYuan(item.deposit_amount_cent)
|
||||
if (key.startsWith('resource_')) {
|
||||
value = getResourceQuantity(item, key.replace('resource_', ''))
|
||||
}
|
||||
@@ -622,7 +622,7 @@ function parseQuantityUnit(price: string) {
|
||||
</div>
|
||||
<div class="resource-price-box">
|
||||
<strong>¥{{ formatMoney(getListingDisplayPrice(item)) }}</strong>
|
||||
<span class="rent-sub">押金¥{{ formatMoney(item.deposit_amount) }}</span>
|
||||
<span class="rent-sub">押金¥{{ formatCent(item.deposit_amount_cent) }}</span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { showToast } from 'vant'
|
||||
import MobileBottomNav from '@/components/MobileBottomNav.vue'
|
||||
|
||||
import { ensureSupportChat } from '@/features/chats/api/chats'
|
||||
import { formatMoney } from '@/shared/utils/money'
|
||||
import { formatCent, formatMoney } from '@/shared/utils/money'
|
||||
import {
|
||||
emptyListingPublishOptions,
|
||||
type ListingPublishOptions,
|
||||
@@ -697,7 +697,7 @@ function chipTone(label: string) {
|
||||
<div class="card-footer">
|
||||
<div class="price-col">
|
||||
<strong>¥{{ formatMoney(getListingDisplayPrice(item)) }}</strong>
|
||||
<span class="rent-sub">押金 ¥{{ formatMoney(item.deposit_amount) }}</span>
|
||||
<span class="rent-sub">押金 ¥{{ formatCent(item.deposit_amount_cent) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '@/features/orders/api/orders'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
import AuthImage from '@/shared/components/business/AuthImage.vue'
|
||||
import { formatMoney } from '@/shared/utils/money'
|
||||
import { formatCent, formatMoney } from '@/shared/utils/money'
|
||||
import {
|
||||
assetRegions,
|
||||
formatHafCoinM,
|
||||
@@ -95,7 +95,7 @@ const detailMetrics = computed(() => {
|
||||
tone: 'coin',
|
||||
},
|
||||
{ label: '价格', value: `¥${formatMoney(getListingDisplayPrice(listing.value))}`, tone: 'price' },
|
||||
{ label: '押金', value: `¥${formatMoney(listing.value.deposit_amount)}`, tone: '' },
|
||||
{ label: '押金', value: `¥${formatCent(listing.value.deposit_amount_cent)}`, tone: '' },
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -26,14 +26,6 @@ export interface Order {
|
||||
deposit_original_amount_cent: number
|
||||
deposit_waived_amount_cent: number
|
||||
platform_fee_cent?: number
|
||||
// Legacy yuan fields may be present from cached/older API payloads during rollout.
|
||||
display_amount?: number
|
||||
rent_amount?: number
|
||||
owner_rent_amount?: number
|
||||
deposit_amount?: number
|
||||
deposit_original_amount?: number
|
||||
deposit_waived_amount?: number
|
||||
platform_fee?: number
|
||||
account_snapshot?: Record<string, unknown>
|
||||
listing_snapshot?: string
|
||||
checkout_info?: string
|
||||
@@ -64,16 +56,6 @@ export interface Checkout {
|
||||
deposit_deduct_amount_cent: number
|
||||
renter_refund_amount_cent?: number
|
||||
owner_income_amount_cent?: number
|
||||
display_amount?: number
|
||||
rent_amount?: number
|
||||
owner_rent_amount?: number
|
||||
platform_fee?: number
|
||||
deposit_amount?: number
|
||||
consumable_amount?: number
|
||||
other_amount?: number
|
||||
deposit_deduct_amount?: number
|
||||
renter_refund_amount?: number
|
||||
owner_income_amount?: number
|
||||
content: string
|
||||
evidence_urls: string[]
|
||||
owner_adjustment_reason: string
|
||||
@@ -319,4 +301,3 @@ export async function adminRefundOrder(id: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<RefundStatus>>(`/admin/orders/${id}/refund`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
|
||||
@@ -557,26 +557,24 @@ function formatHandoffRecordType(type: string) {
|
||||
return typeMap[type] || type
|
||||
}
|
||||
|
||||
function amountYuan(cent: unknown, legacyYuan?: unknown) {
|
||||
function amountYuan(cent: unknown) {
|
||||
if (cent !== undefined && cent !== null) return centToYuan(readNumber(cent))
|
||||
return readNumber(legacyYuan)
|
||||
return 0
|
||||
}
|
||||
|
||||
function orderRentAmount(item: Order) {
|
||||
if (item.owner_id === session.userId)
|
||||
return amountYuan(item.owner_rent_amount_cent, item.owner_rent_amount ?? item.display_amount)
|
||||
return amountYuan(item.owner_rent_amount_cent)
|
||||
if (item.renter_id === session.userId)
|
||||
return amountYuan(item.rent_amount_cent, item.rent_amount ?? item.display_amount)
|
||||
return amountYuan(item.display_amount_cent, item.display_amount)
|
||||
return amountYuan(item.rent_amount_cent)
|
||||
return amountYuan(item.display_amount_cent)
|
||||
}
|
||||
|
||||
function ownerActualIncome(item: Order) {
|
||||
if (item.owner_id !== session.userId) return null
|
||||
const value = item.checkout?.owner_income_amount_cent
|
||||
if (typeof value === 'number') return centToYuan(value)
|
||||
return typeof item.checkout?.owner_income_amount === 'number'
|
||||
? item.checkout.owner_income_amount
|
||||
: null
|
||||
return null
|
||||
}
|
||||
|
||||
function quantity(value: unknown) {
|
||||
@@ -596,16 +594,10 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
function hydrateCounterForm() {
|
||||
if (!order.value?.checkout) return
|
||||
const checkout = order.value.checkout
|
||||
counterForm.value.consumable_amount = amountYuan(
|
||||
checkout.consumable_amount_cent,
|
||||
checkout.consumable_amount
|
||||
)
|
||||
counterForm.value.consumable_amount = amountYuan(checkout.consumable_amount_cent)
|
||||
counterForm.value.coin_consumed_m = checkout.coin_consumed_m
|
||||
counterForm.value.other_amount = amountYuan(checkout.other_amount_cent, checkout.other_amount)
|
||||
counterForm.value.deposit_deduct_amount = amountYuan(
|
||||
checkout.deposit_deduct_amount_cent,
|
||||
checkout.deposit_deduct_amount
|
||||
)
|
||||
counterForm.value.other_amount = amountYuan(checkout.other_amount_cent)
|
||||
counterForm.value.deposit_deduct_amount = amountYuan(checkout.deposit_deduct_amount_cent)
|
||||
}
|
||||
|
||||
function linesToList(value: string) {
|
||||
@@ -696,14 +688,12 @@ async function copyListingCode() {
|
||||
<div class="meta-item">
|
||||
<span class="meta-label">押金</span>
|
||||
<strong class="meta-value"
|
||||
>¥{{ money(amountYuan(order.deposit_amount_cent, order.deposit_amount)) }}</strong
|
||||
>¥{{ money(amountYuan(order.deposit_amount_cent)) }}</strong
|
||||
>
|
||||
<span
|
||||
v-if="amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount) > 0"
|
||||
v-if="amountYuan(order.deposit_waived_amount_cent) > 0"
|
||||
class="meta-note"
|
||||
>已免押 ¥{{
|
||||
money(amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount))
|
||||
}}</span
|
||||
>已免押 ¥{{ money(amountYuan(order.deposit_waived_amount_cent)) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
@@ -956,59 +946,36 @@ async function copyListingCode() {
|
||||
<van-cell-group inset :border="false">
|
||||
<van-cell
|
||||
title="实际结算租金"
|
||||
:value="`¥${money(
|
||||
amountYuan(order.checkout.display_amount_cent, order.checkout.display_amount)
|
||||
)}`"
|
||||
:value="`¥${money(amountYuan(order.checkout.display_amount_cent))}`"
|
||||
/>
|
||||
<van-cell
|
||||
title="押金总额"
|
||||
:label="
|
||||
amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount) > 0
|
||||
? `已免押 ¥${money(
|
||||
amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount)
|
||||
)}`
|
||||
amountYuan(order.deposit_waived_amount_cent) > 0
|
||||
? `已免押 ¥${money(amountYuan(order.deposit_waived_amount_cent))}`
|
||||
: ''
|
||||
"
|
||||
:value="`¥${money(
|
||||
amountYuan(order.checkout.deposit_amount_cent, order.checkout.deposit_amount)
|
||||
)}`"
|
||||
:value="`¥${money(amountYuan(order.checkout.deposit_amount_cent))}`"
|
||||
/>
|
||||
<van-cell
|
||||
title="额外消耗品已用"
|
||||
:value="`¥${money(
|
||||
amountYuan(order.checkout.consumable_amount_cent, order.checkout.consumable_amount)
|
||||
)}`"
|
||||
:value="`¥${money(amountYuan(order.checkout.consumable_amount_cent))}`"
|
||||
/>
|
||||
<van-cell
|
||||
title="押金赔付扣除"
|
||||
:value="`¥${money(
|
||||
amountYuan(
|
||||
order.checkout.deposit_deduct_amount_cent,
|
||||
order.checkout.deposit_deduct_amount
|
||||
)
|
||||
)}`"
|
||||
:value="`¥${money(amountYuan(order.checkout.deposit_deduct_amount_cent))}`"
|
||||
value-class="red-text"
|
||||
/>
|
||||
<van-cell
|
||||
v-if="isRenter"
|
||||
title="退还租客"
|
||||
:value="`¥${money(
|
||||
amountYuan(
|
||||
order.checkout.renter_refund_amount_cent,
|
||||
order.checkout.renter_refund_amount
|
||||
)
|
||||
)}`"
|
||||
:value="`¥${money(amountYuan(order.checkout.renter_refund_amount_cent))}`"
|
||||
value-class="green-text"
|
||||
/>
|
||||
<van-cell
|
||||
v-if="isOwner"
|
||||
title="号主最终收入"
|
||||
:value="`¥${money(
|
||||
amountYuan(
|
||||
order.checkout.owner_income_amount_cent,
|
||||
order.checkout.owner_income_amount
|
||||
)
|
||||
)}`"
|
||||
:value="`¥${money(amountYuan(order.checkout.owner_income_amount_cent))}`"
|
||||
value-class="green-text"
|
||||
/>
|
||||
<van-cell
|
||||
|
||||
@@ -131,21 +131,21 @@ function amountLabel(order: Order) {
|
||||
return isOwner(order) ? '预计租金' : '支付租金'
|
||||
}
|
||||
|
||||
function amountYuan(cent: unknown, legacyYuan?: unknown) {
|
||||
function amountYuan(cent: unknown) {
|
||||
if (cent !== undefined && cent !== null) return centToYuan(Number(cent || 0))
|
||||
return Number(legacyYuan || 0)
|
||||
return 0
|
||||
}
|
||||
|
||||
function orderRentAmount(order: Order) {
|
||||
if (isOwner(order)) return amountYuan(order.owner_rent_amount_cent, order.owner_rent_amount ?? order.display_amount)
|
||||
return amountYuan(order.rent_amount_cent, order.rent_amount ?? order.display_amount)
|
||||
if (isOwner(order)) return amountYuan(order.owner_rent_amount_cent)
|
||||
return amountYuan(order.rent_amount_cent)
|
||||
}
|
||||
|
||||
function ownerActualIncome(order: Order) {
|
||||
if (!isOwner(order)) return null
|
||||
const value = order.checkout?.owner_income_amount_cent
|
||||
if (typeof value === 'number') return centToYuan(value)
|
||||
return typeof order.checkout?.owner_income_amount === 'number' ? order.checkout.owner_income_amount : null
|
||||
return null
|
||||
}
|
||||
|
||||
function formatListingCode(order: Order) {
|
||||
@@ -238,9 +238,9 @@ async function copyListingCode(order: Order) {
|
||||
<div class="price-item">
|
||||
<span class="price-label">押金金额</span>
|
||||
<span class="price-val deposit">
|
||||
¥{{ money(amountYuan(order.deposit_amount_cent, order.deposit_amount)) }}
|
||||
<em v-if="amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount) > 0"
|
||||
>免 ¥{{ money(amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount)) }}</em
|
||||
¥{{ money(amountYuan(order.deposit_amount_cent)) }}
|
||||
<em v-if="amountYuan(order.deposit_waived_amount_cent) > 0"
|
||||
>免 ¥{{ money(amountYuan(order.deposit_waived_amount_cent)) }}</em
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ import QRCode from 'qrcode'
|
||||
import { fetchOrderChat } from '@/features/chats/api/chats'
|
||||
import { createDispute } from '@/features/disputes'
|
||||
import { uploadFile } from '@/shared/api/files'
|
||||
import { centToYuan, formatMoney } from '@/shared/utils/money'
|
||||
import { centToYuan, formatCent, formatMoney } from '@/shared/utils/money'
|
||||
import {
|
||||
acceptCheckout,
|
||||
cancelOrder,
|
||||
@@ -606,26 +606,24 @@ function money(value: unknown) {
|
||||
return formatMoney(readNumber(value))
|
||||
}
|
||||
|
||||
function amountYuan(cent: unknown, legacyYuan?: unknown) {
|
||||
function amountYuan(cent: unknown) {
|
||||
if (cent !== undefined && cent !== null) return centToYuan(readNumber(cent))
|
||||
return readNumber(legacyYuan)
|
||||
return 0
|
||||
}
|
||||
|
||||
function orderRentAmount(item: Order) {
|
||||
if (item.owner_id === session.userId)
|
||||
return amountYuan(item.owner_rent_amount_cent, item.owner_rent_amount ?? item.display_amount)
|
||||
return amountYuan(item.owner_rent_amount_cent)
|
||||
if (item.renter_id === session.userId)
|
||||
return amountYuan(item.rent_amount_cent, item.rent_amount ?? item.display_amount)
|
||||
return amountYuan(item.display_amount_cent, item.display_amount)
|
||||
return amountYuan(item.rent_amount_cent)
|
||||
return amountYuan(item.display_amount_cent)
|
||||
}
|
||||
|
||||
function ownerActualIncome(item: Order) {
|
||||
if (item.owner_id !== session.userId) return null
|
||||
const value = item.checkout?.owner_income_amount_cent
|
||||
if (typeof value === 'number') return centToYuan(value)
|
||||
return typeof item.checkout?.owner_income_amount === 'number'
|
||||
? item.checkout.owner_income_amount
|
||||
: null
|
||||
return null
|
||||
}
|
||||
|
||||
function quantity(value: unknown) {
|
||||
@@ -645,16 +643,10 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
function hydrateCounterForm() {
|
||||
if (!order.value?.checkout) return
|
||||
const checkout = order.value.checkout
|
||||
counterForm.value.consumable_amount = amountYuan(
|
||||
checkout.consumable_amount_cent,
|
||||
checkout.consumable_amount
|
||||
)
|
||||
counterForm.value.consumable_amount = amountYuan(checkout.consumable_amount_cent)
|
||||
counterForm.value.coin_consumed_m = checkout.coin_consumed_m
|
||||
counterForm.value.other_amount = amountYuan(checkout.other_amount_cent, checkout.other_amount)
|
||||
counterForm.value.deposit_deduct_amount = amountYuan(
|
||||
checkout.deposit_deduct_amount_cent,
|
||||
checkout.deposit_deduct_amount
|
||||
)
|
||||
counterForm.value.other_amount = amountYuan(checkout.other_amount_cent)
|
||||
counterForm.value.deposit_deduct_amount = amountYuan(checkout.deposit_deduct_amount_cent)
|
||||
}
|
||||
|
||||
function linesToList(value: string) {
|
||||
@@ -789,13 +781,13 @@ async function copyListingCode() {
|
||||
<div class="metric-card">
|
||||
<span class="metric-label">押金</span>
|
||||
<strong class="metric-value"
|
||||
>¥{{ money(amountYuan(order.deposit_amount_cent, order.deposit_amount)) }}</strong
|
||||
>¥{{ money(amountYuan(order.deposit_amount_cent)) }}</strong
|
||||
>
|
||||
<span
|
||||
v-if="amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount) > 0"
|
||||
v-if="amountYuan(order.deposit_waived_amount_cent) > 0"
|
||||
class="metric-note"
|
||||
>已免押 ¥{{
|
||||
money(amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount))
|
||||
money(amountYuan(order.deposit_waived_amount_cent))
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
@@ -979,81 +971,40 @@ async function copyListingCode() {
|
||||
<div class="summary-row">
|
||||
<span>实际结算租金</span>
|
||||
<strong
|
||||
>¥{{
|
||||
money(
|
||||
amountYuan(order.checkout.display_amount_cent, order.checkout.display_amount)
|
||||
)
|
||||
}}</strong
|
||||
>¥{{ money(amountYuan(order.checkout.display_amount_cent)) }}</strong
|
||||
>
|
||||
</div>
|
||||
<div class="summary-row">
|
||||
<span>预收押金</span>
|
||||
<strong>
|
||||
¥{{
|
||||
money(
|
||||
amountYuan(order.checkout.deposit_amount_cent, order.checkout.deposit_amount)
|
||||
)
|
||||
}}
|
||||
<em
|
||||
v-if="
|
||||
amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount) > 0
|
||||
"
|
||||
>已免押 ¥{{
|
||||
money(amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount))
|
||||
}}</em
|
||||
¥{{ money(amountYuan(order.checkout.deposit_amount_cent)) }}
|
||||
<em v-if="amountYuan(order.deposit_waived_amount_cent) > 0"
|
||||
>已免押 ¥{{ money(amountYuan(order.deposit_waived_amount_cent)) }}</em
|
||||
>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="summary-row">
|
||||
<span>额外消耗品已用</span>
|
||||
<strong class="warning"
|
||||
>¥{{
|
||||
money(
|
||||
amountYuan(
|
||||
order.checkout.consumable_amount_cent,
|
||||
order.checkout.consumable_amount
|
||||
)
|
||||
)
|
||||
}}</strong
|
||||
>¥{{ money(amountYuan(order.checkout.consumable_amount_cent)) }}</strong
|
||||
>
|
||||
</div>
|
||||
<div class="summary-row">
|
||||
<span>押金赔付扣除</span>
|
||||
<strong class="warning"
|
||||
>¥{{
|
||||
money(
|
||||
amountYuan(
|
||||
order.checkout.deposit_deduct_amount_cent,
|
||||
order.checkout.deposit_deduct_amount
|
||||
)
|
||||
)
|
||||
}}</strong
|
||||
>¥{{ money(amountYuan(order.checkout.deposit_deduct_amount_cent)) }}</strong
|
||||
>
|
||||
</div>
|
||||
<div v-if="isRenter" class="summary-row highlight">
|
||||
<span>退还租客(未使用租金 + 剩余押金)</span>
|
||||
<strong class="amount"
|
||||
>¥{{
|
||||
money(
|
||||
amountYuan(
|
||||
order.checkout.renter_refund_amount_cent,
|
||||
order.checkout.renter_refund_amount
|
||||
)
|
||||
)
|
||||
}}</strong
|
||||
>¥{{ money(amountYuan(order.checkout.renter_refund_amount_cent)) }}</strong
|
||||
>
|
||||
</div>
|
||||
<div v-if="isOwner" class="summary-row highlight">
|
||||
<span>号主最终收入(租金 + 押金赔付)</span>
|
||||
<strong class="amount"
|
||||
>¥{{
|
||||
money(
|
||||
amountYuan(
|
||||
order.checkout.owner_income_amount_cent,
|
||||
order.checkout.owner_income_amount
|
||||
)
|
||||
)
|
||||
}}</strong
|
||||
>¥{{ money(amountYuan(order.checkout.owner_income_amount_cent)) }}</strong
|
||||
>
|
||||
</div>
|
||||
<div v-if="order.checkout.content" class="summary-note">
|
||||
@@ -1109,7 +1060,7 @@ async function copyListingCode() {
|
||||
v-model="counterForm.deposit_deduct_amount"
|
||||
class="full-control"
|
||||
:min="0"
|
||||
:max="amountYuan(order.deposit_amount_cent, order.deposit_amount)"
|
||||
:max="amountYuan(order.deposit_amount_cent)"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
/>
|
||||
@@ -1331,7 +1282,7 @@ async function copyListingCode() {
|
||||
<div class="pay-summary">
|
||||
<div class="pay-summary-row">
|
||||
<span>支付金额</span>
|
||||
<strong>¥{{ formatMoney(activePayment.amount_cent / 100) }}</strong>
|
||||
<strong>¥{{ formatCent(activePayment.amount_cent) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="paymentPayURL()" class="pay-qr-section">
|
||||
|
||||
@@ -127,22 +127,22 @@ function amountLabel(order: Order) {
|
||||
return isRenter(order) ? '支付租金' : '预计租金'
|
||||
}
|
||||
|
||||
function amountYuan(cent: unknown, legacyYuan?: unknown) {
|
||||
function amountYuan(cent: unknown) {
|
||||
if (cent !== undefined && cent !== null) return centToYuan(Number(cent || 0))
|
||||
return Number(legacyYuan || 0)
|
||||
return 0
|
||||
}
|
||||
|
||||
function orderRentAmount(order: Order) {
|
||||
if (isOwner(order)) return amountYuan(order.owner_rent_amount_cent, order.owner_rent_amount ?? order.display_amount)
|
||||
if (isRenter(order)) return amountYuan(order.rent_amount_cent, order.rent_amount ?? order.display_amount)
|
||||
return amountYuan(order.display_amount_cent, order.display_amount)
|
||||
if (isOwner(order)) return amountYuan(order.owner_rent_amount_cent)
|
||||
if (isRenter(order)) return amountYuan(order.rent_amount_cent)
|
||||
return amountYuan(order.display_amount_cent)
|
||||
}
|
||||
|
||||
function ownerActualIncome(order: Order) {
|
||||
if (!isOwner(order)) return null
|
||||
const value = order.checkout?.owner_income_amount_cent
|
||||
if (typeof value === 'number') return centToYuan(value)
|
||||
return typeof order.checkout?.owner_income_amount === 'number' ? order.checkout.owner_income_amount : null
|
||||
return null
|
||||
}
|
||||
|
||||
function money(value: unknown) {
|
||||
@@ -269,9 +269,9 @@ function getCountdownMinutes(order: Order) {
|
||||
<el-table-column label="押金" width="100">
|
||||
<template #default="{ row }">
|
||||
<div class="amount-cell">
|
||||
<span class="amount-value">¥{{ money(row.deposit_amount) }}</span>
|
||||
<span v-if="row.deposit_waived_amount > 0" class="amount-label"
|
||||
>免 ¥{{ money(row.deposit_waived_amount) }}</span
|
||||
<span class="amount-value">¥{{ money(amountYuan(row.deposit_amount_cent)) }}</span>
|
||||
<span v-if="amountYuan(row.deposit_waived_amount_cent) > 0" class="amount-label"
|
||||
>免 ¥{{ money(amountYuan(row.deposit_waived_amount_cent)) }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
@@ -357,9 +357,9 @@ function getCountdownMinutes(order: Order) {
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">押金</span>
|
||||
<span class="meta-value">
|
||||
¥{{ money(amountYuan(order.deposit_amount_cent, order.deposit_amount)) }}
|
||||
<em v-if="amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount) > 0"
|
||||
>免 ¥{{ money(amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount)) }}</em
|
||||
¥{{ money(amountYuan(order.deposit_amount_cent)) }}
|
||||
<em v-if="amountYuan(order.deposit_waived_amount_cent) > 0"
|
||||
>免 ¥{{ money(amountYuan(order.deposit_waived_amount_cent)) }}</em
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
writePublishDraft,
|
||||
} from '@/features/seller/composables/usePublishDraft'
|
||||
import type { PublishForm } from '@/types/publish'
|
||||
import { yuanToCent } from '@/shared/utils/money'
|
||||
import { commonOnlineTimes, dailyLossOptions, formatNumber, roundRatio } from '@/utils/pricing'
|
||||
|
||||
const draftSaveDelay = 400
|
||||
@@ -567,8 +568,8 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
haf_coin_amount: pricing.coinMAmount.value * 1000000,
|
||||
asset_summary: buildAssetSummary(),
|
||||
screenshot_urls: pricing.screenshotUrls.value,
|
||||
price: pricing.calculatedFinalPrice.value,
|
||||
deposit_amount: Number(form.deposit_amount),
|
||||
price_cent: yuanToCent(pricing.calculatedFinalPrice.value),
|
||||
deposit_amount_cent: yuanToCent(Number(form.deposit_amount)),
|
||||
agreed_virtual_asset_sale: virtualAssetSaleAgreementChecked.value,
|
||||
agreed_seller_agreement: sellerAgreementChecked.value,
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ import { fetchOrders, type Order } from '@/features/orders'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
import { handoffStatusLabel, orderStatusLabel } from '@/utils/statusLabels'
|
||||
import { formatDateTime } from '@/utils/time'
|
||||
import { formatMoney } from '@/shared/utils/money'
|
||||
import { centToYuan, formatMoney } from '@/shared/utils/money'
|
||||
import { formatListingNo } from '@/utils/listingDisplay'
|
||||
|
||||
const session = useSessionStore()
|
||||
@@ -73,6 +73,10 @@ function actionText(order: Order) {
|
||||
function money(value: unknown) {
|
||||
return formatMoney(Number(value || 0))
|
||||
}
|
||||
|
||||
function sellerAmount(order: Order) {
|
||||
return centToYuan(order.owner_rent_amount_cent ?? order.display_amount_cent)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -122,7 +126,7 @@ function money(value: unknown) {
|
||||
<el-table-column prop="order_no" label="订单号" min-width="220" />
|
||||
<el-table-column prop="title" label="账号" min-width="180" />
|
||||
<el-table-column label="金额" width="120">
|
||||
<template #default="{ row }">¥{{ money(row.display_amount) }}</template>
|
||||
<template #default="{ row }">¥{{ money(sellerAmount(row)) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单状态" width="150">
|
||||
<template #default="{ row }">{{ orderStatusLabel(row.status) }}</template>
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
submitListingReview,
|
||||
type Listing,
|
||||
} from '@/features/listings'
|
||||
import { formatMoney, formatMoneyWithSymbol } from '@/shared/utils/money'
|
||||
import { formatCent, formatMoney, formatMoneyWithSymbol } from '@/shared/utils/money'
|
||||
import { listingReviewStatusLabel, listingStatusLabel } from '@/utils/statusLabels'
|
||||
import { formatListingCode, getListingSellerPrice } from '@/utils/listingDisplay'
|
||||
|
||||
@@ -227,7 +227,7 @@ function isPendingReview(row: Listing) {
|
||||
</div>
|
||||
<div>
|
||||
<span>押金</span>
|
||||
<strong>¥{{ formatMoney(item.deposit_amount) }}</strong>
|
||||
<strong>¥{{ formatCent(item.deposit_amount_cent) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { apiClient } from '@/shared/api/client'
|
||||
import type { ApiResponse, PaginatedResult } from '@/shared/types/types'
|
||||
import type { BalanceType, LedgerDirection, WalletStatus } from '@/shared/types/status'
|
||||
import type { PaymentOrder } from '@/features/orders/api/orders'
|
||||
import { yuanToCent } from '@/shared/utils/money'
|
||||
|
||||
export interface WalletAccount {
|
||||
user_id: number
|
||||
@@ -42,13 +43,13 @@ export async function fetchWalletLedger(page = 1, pageSize = 20) {
|
||||
}
|
||||
|
||||
export async function rechargeWallet(amountYuan: number) {
|
||||
const amount_cent = Math.round(amountYuan * 100)
|
||||
const amount_cent = yuanToCent(amountYuan)
|
||||
const { data } = await apiClient.post<ApiResponse<WalletAccount>>('/wallet/recharge', { amount_cent })
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function startWalletRechargePayment(amountYuan: number) {
|
||||
const amount_cent = Math.round(amountYuan * 100)
|
||||
const amount_cent = yuanToCent(amountYuan)
|
||||
const { data } = await apiClient.post<ApiResponse<PaymentOrder>>('/wallet/recharge/pay', {
|
||||
amount_cent,
|
||||
})
|
||||
|
||||
@@ -402,8 +402,8 @@ function amountPrefix(direction: string) {
|
||||
<span>支付金额</span>
|
||||
<strong>{{
|
||||
activeRechargePayment
|
||||
? formatMoney(activeRechargePayment.amount_cent / 100)
|
||||
: formatMoney(devRechargeAmount)
|
||||
? formatCentWithSymbol(activeRechargePayment.amount_cent)
|
||||
: `¥${formatMoney(devRechargeAmount)}`
|
||||
}}</strong>
|
||||
</div>
|
||||
<div v-if="rechargePayURL()" class="cashier-qr">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Listing } from '@/features/listings/api/listings'
|
||||
import { centToYuan } from '@/shared/utils/money'
|
||||
|
||||
export interface ListingDisplayChip {
|
||||
label: string
|
||||
@@ -41,7 +42,7 @@ export function formatHafCoinM(amountWan: number) {
|
||||
}
|
||||
|
||||
export function getListingDisplayPrice(item: Listing) {
|
||||
return Number(item.price || 0)
|
||||
return centToYuan(item.price_cent)
|
||||
}
|
||||
|
||||
export function getListingRentPrice(item: Listing) {
|
||||
|
||||
@@ -40,12 +40,19 @@ export function centToYuan(cent: number | undefined | null): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分转角(四舍五入到0.1元)
|
||||
* @example centToJiao(12345) -> 123.5
|
||||
* @example centToJiao(12344) -> 123.4
|
||||
* 分转展示用元(四舍五入到0.1元)
|
||||
* @example centToDisplayYuan(12345) -> 123.5
|
||||
* @example centToDisplayYuan(12344) -> 123.4
|
||||
*/
|
||||
export function centToDisplayYuan(cent: number | undefined | null): number {
|
||||
return roundMoney(centToYuan(cent))
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用 centToDisplayYuan。保留导出避免旧导入立刻失效。
|
||||
*/
|
||||
export function centToJiao(cent: number | undefined | null): number {
|
||||
return Math.round(Number(cent || 0) / 10) / 10
|
||||
return centToDisplayYuan(cent)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +61,7 @@ export function centToJiao(cent: number | undefined | null): number {
|
||||
* @example formatCent(12344) -> "123.4"
|
||||
*/
|
||||
export function formatCent(cent: number | undefined | null): string {
|
||||
return centToJiao(cent).toFixed(1)
|
||||
return centToDisplayYuan(cent).toFixed(1)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Listing } from '@/features/listings/api/listings'
|
||||
import { centToYuan } from '@/shared/utils/money'
|
||||
|
||||
export interface ListingDisplayChip {
|
||||
label: string
|
||||
@@ -41,7 +42,7 @@ export function formatHafCoinM(amountWan: number) {
|
||||
}
|
||||
|
||||
export function getListingDisplayPrice(item: Listing) {
|
||||
return Number(item.price || 0)
|
||||
return centToYuan(item.price_cent)
|
||||
}
|
||||
|
||||
export function getListingRentPrice(item: Listing) {
|
||||
|
||||
+22
-21
@@ -53,11 +53,11 @@ BEGIN
|
||||
END IF;
|
||||
|
||||
-- 为用户创建钱包
|
||||
INSERT INTO wallet_accounts (user_id, available_balance, frozen_balance, status)
|
||||
INSERT INTO wallet_accounts (user_id, available_balance_cent, frozen_balance_cent, status)
|
||||
VALUES (
|
||||
LAST_INSERT_ID(),
|
||||
(i % 10) * 100.00,
|
||||
(i % 5) * 50.00,
|
||||
(i % 10) * 10000,
|
||||
(i % 5) * 5000,
|
||||
'active'
|
||||
) ON DUPLICATE KEY UPDATE user_id=user_id;
|
||||
|
||||
@@ -83,8 +83,8 @@ BEGIN
|
||||
DECLARE i INT DEFAULT 1;
|
||||
DECLARE user_id_val BIGINT;
|
||||
DECLARE account_id_val BIGINT;
|
||||
DECLARE price_val DECIMAL(12,2);
|
||||
DECLARE deposit_val DECIMAL(12,2);
|
||||
DECLARE price_val BIGINT;
|
||||
DECLARE deposit_val BIGINT;
|
||||
DECLARE verified_user_min BIGINT;
|
||||
DECLARE verified_user_max BIGINT;
|
||||
DECLARE user_pick BIGINT;
|
||||
@@ -146,11 +146,11 @@ BEGIN
|
||||
SET account_id_val = LAST_INSERT_ID();
|
||||
|
||||
-- 创建租号商品
|
||||
SET price_val = 5.00 + (i % 20) * 0.5;
|
||||
SET deposit_val = 100.00 + (i % 10) * 50.00;
|
||||
SET price_val = 500 + (i % 20) * 50;
|
||||
SET deposit_val = 10000 + (i % 10) * 5000;
|
||||
|
||||
INSERT INTO rental_listings (
|
||||
listing_no, account_id, owner_id, price, deposit_amount,
|
||||
listing_no, account_id, owner_id, price_cent, deposit_amount_cent,
|
||||
in_transaction, status, review_status, published_at
|
||||
) VALUES (
|
||||
CONCAT(
|
||||
@@ -205,8 +205,8 @@ BEGIN
|
||||
DECLARE owner_id_val BIGINT;
|
||||
DECLARE renter_id_val BIGINT;
|
||||
DECLARE order_no_val VARCHAR(64);
|
||||
DECLARE rent_amount_val DECIMAL(12,2);
|
||||
DECLARE deposit_val DECIMAL(12,2);
|
||||
DECLARE rent_amount_val BIGINT;
|
||||
DECLARE deposit_val BIGINT;
|
||||
DECLARE listing_min BIGINT;
|
||||
DECLARE listing_max BIGINT;
|
||||
DECLARE listing_pick BIGINT;
|
||||
@@ -235,7 +235,7 @@ BEGIN
|
||||
-- 近似随机选择一个上架商品,避免 ORDER BY RAND() 全表排序。
|
||||
SET listing_id_val = NULL;
|
||||
SET listing_pick = listing_min + FLOOR(RAND() * (listing_max - listing_min + 1));
|
||||
SELECT rl.id, rl.account_id, rl.owner_id, rl.price, rl.deposit_amount
|
||||
SELECT rl.id, rl.account_id, rl.owner_id, rl.price_cent, rl.deposit_amount_cent
|
||||
INTO listing_id_val, account_id_val, owner_id_val, rent_amount_val, deposit_val
|
||||
FROM rental_listings rl
|
||||
WHERE rl.id >= listing_pick
|
||||
@@ -244,7 +244,7 @@ BEGIN
|
||||
ORDER BY rl.id LIMIT 1;
|
||||
|
||||
IF listing_id_val IS NULL THEN
|
||||
SELECT rl.id, rl.account_id, rl.owner_id, rl.price, rl.deposit_amount
|
||||
SELECT rl.id, rl.account_id, rl.owner_id, rl.price_cent, rl.deposit_amount_cent
|
||||
INTO listing_id_val, account_id_val, owner_id_val, rent_amount_val, deposit_val
|
||||
FROM rental_listings rl
|
||||
WHERE rl.status IN ('published', 'active')
|
||||
@@ -274,8 +274,8 @@ BEGIN
|
||||
|
||||
INSERT INTO rental_orders (
|
||||
order_no, listing_id, account_id, owner_id, renter_id,
|
||||
estimated_duration_hours, rent_amount, owner_rent_amount,
|
||||
deposit_amount, platform_fee, status, handoff_status,
|
||||
estimated_duration_hours, rent_amount_cent, owner_rent_amount_cent,
|
||||
deposit_amount_cent, deposit_original_amount_cent, platform_fee_cent, status, handoff_status,
|
||||
settlement_status, rented_at, created_at
|
||||
) VALUES (
|
||||
order_no_val,
|
||||
@@ -285,9 +285,10 @@ BEGIN
|
||||
renter_id_val,
|
||||
24,
|
||||
rent_amount_val,
|
||||
rent_amount_val * 0.95, -- 号主实得95%
|
||||
ROUND(rent_amount_val * 0.95), -- 号主实得95%
|
||||
deposit_val,
|
||||
rent_amount_val * 0.05, -- 平台5%手续费
|
||||
deposit_val,
|
||||
ROUND(rent_amount_val * 0.05), -- 平台5%手续费
|
||||
CASE (i % 10)
|
||||
WHEN 0 THEN 'pending_payment'
|
||||
WHEN 1 THEN 'cancelled'
|
||||
@@ -331,7 +332,7 @@ BEGIN
|
||||
DECLARE user_id_val BIGINT;
|
||||
DECLARE order_id_val BIGINT;
|
||||
DECLARE ledger_no_val VARCHAR(64);
|
||||
DECLARE amount_val DECIMAL(12,2);
|
||||
DECLARE amount_val BIGINT;
|
||||
DECLARE user_min BIGINT;
|
||||
DECLARE user_max BIGINT;
|
||||
DECLARE order_min BIGINT;
|
||||
@@ -374,18 +375,18 @@ BEGIN
|
||||
END IF;
|
||||
|
||||
SET ledger_no_val = CONCAT('LDG', DATE_FORMAT(NOW(), '%Y%m%d%H%i%s'), LPAD(i, 6, '0'));
|
||||
SET amount_val = (i % 500) + RAND() * 100;
|
||||
SET amount_val = ((i % 500) * 100) + FLOOR(RAND() * 10000);
|
||||
|
||||
INSERT INTO wallet_ledger (
|
||||
ledger_no, user_id, order_id, direction, amount,
|
||||
balance_after, balance_type, biz_type, biz_no, remark, created_at
|
||||
ledger_no, user_id, order_id, direction, amount_cent,
|
||||
balance_after_cent, balance_type, biz_type, biz_no, remark, created_at
|
||||
) VALUES (
|
||||
ledger_no_val,
|
||||
user_id_val,
|
||||
order_id_val,
|
||||
CASE WHEN i % 2 = 0 THEN 'in' ELSE 'out' END,
|
||||
amount_val,
|
||||
1000.00 + (i % 1000),
|
||||
100000 + (i % 1000) * 100,
|
||||
CASE WHEN i % 5 = 0 THEN 'frozen' ELSE 'available' END,
|
||||
CASE (i % 6)
|
||||
WHEN 0 THEN 'rent_payment'
|
||||
|
||||
@@ -69,11 +69,11 @@ BEGIN
|
||||
AND NOT EXISTS (SELECT 1 FROM user_realname WHERE user_id = u.id);
|
||||
|
||||
-- 批量生成钱包账户
|
||||
INSERT INTO wallet_accounts (user_id, available_balance, frozen_balance, status)
|
||||
INSERT INTO wallet_accounts (user_id, available_balance_cent, frozen_balance_cent, status)
|
||||
SELECT
|
||||
u.id,
|
||||
(u.id % 10) * 100.00,
|
||||
(u.id % 5) * 50.00,
|
||||
(u.id % 10) * 10000,
|
||||
(u.id % 5) * 5000,
|
||||
'active'
|
||||
FROM users u
|
||||
WHERE NOT EXISTS (SELECT 1 FROM wallet_accounts WHERE user_id = u.id);
|
||||
@@ -159,7 +159,7 @@ BEGIN
|
||||
|
||||
-- 批量生成租号商品
|
||||
INSERT INTO rental_listings (
|
||||
listing_no, account_id, owner_id, price, deposit_amount,
|
||||
listing_no, account_id, owner_id, price_cent, deposit_amount_cent,
|
||||
in_transaction, status, review_status, published_at
|
||||
)
|
||||
SELECT
|
||||
@@ -169,8 +169,8 @@ BEGIN
|
||||
) as listing_no,
|
||||
ga.id as account_id,
|
||||
ga.owner_id,
|
||||
5.00 + ((ga.id % 20) * 0.5) as price,
|
||||
100.00 + ((ga.id % 10) * 50.00) as deposit_amount,
|
||||
500 + ((ga.id % 20) * 50) as price_cent,
|
||||
10000 + ((ga.id % 10) * 5000) as deposit_amount_cent,
|
||||
CASE WHEN ga.id % 10 = 0 THEN 1 ELSE 0 END as in_transaction,
|
||||
CASE
|
||||
WHEN ga.id % 20 = 0 THEN 'offline'
|
||||
@@ -222,13 +222,13 @@ BEGIN
|
||||
id BIGINT PRIMARY KEY,
|
||||
account_id BIGINT,
|
||||
owner_id BIGINT,
|
||||
price DECIMAL(12,2),
|
||||
deposit_amount DECIMAL(12,2),
|
||||
price_cent BIGINT,
|
||||
deposit_amount_cent BIGINT,
|
||||
row_num INT
|
||||
);
|
||||
|
||||
INSERT INTO tmp_available_listings (id, account_id, owner_id, price, deposit_amount, row_num)
|
||||
SELECT id, account_id, owner_id, price, deposit_amount, (@rn := @rn + 1)
|
||||
INSERT INTO tmp_available_listings (id, account_id, owner_id, price_cent, deposit_amount_cent, row_num)
|
||||
SELECT id, account_id, owner_id, price_cent, deposit_amount_cent, (@rn := @rn + 1)
|
||||
FROM rental_listings, (SELECT @rn := 0) init
|
||||
WHERE status IN ('published', 'active') AND review_status = 'approved'
|
||||
ORDER BY id;
|
||||
@@ -255,8 +255,8 @@ BEGIN
|
||||
WHILE current_batch < batches DO
|
||||
INSERT INTO rental_orders (
|
||||
order_no, listing_id, account_id, owner_id, renter_id,
|
||||
estimated_duration_hours, rent_amount, owner_rent_amount,
|
||||
deposit_amount, platform_fee, status, handoff_status,
|
||||
estimated_duration_hours, rent_amount_cent, owner_rent_amount_cent,
|
||||
deposit_amount_cent, deposit_original_amount_cent, platform_fee_cent, status, handoff_status,
|
||||
settlement_status, rented_at, created_at
|
||||
)
|
||||
SELECT
|
||||
@@ -266,10 +266,11 @@ BEGIN
|
||||
l.owner_id,
|
||||
r.id as renter_id,
|
||||
24 as estimated_duration_hours,
|
||||
l.price * 24 as rent_amount,
|
||||
l.price * 24 * 0.95 as owner_rent_amount,
|
||||
l.deposit_amount,
|
||||
l.price * 24 * 0.05 as platform_fee,
|
||||
l.price_cent * 24 as rent_amount_cent,
|
||||
ROUND(l.price_cent * 24 * 0.95) as owner_rent_amount_cent,
|
||||
l.deposit_amount_cent,
|
||||
l.deposit_amount_cent,
|
||||
ROUND(l.price_cent * 24 * 0.05) as platform_fee_cent,
|
||||
CASE (seq % 10)
|
||||
WHEN 0 THEN 'pending_payment'
|
||||
WHEN 1 THEN 'cancelled'
|
||||
@@ -360,16 +361,16 @@ BEGIN
|
||||
|
||||
WHILE current_batch < batches DO
|
||||
INSERT INTO wallet_ledger (
|
||||
ledger_no, user_id, order_id, direction, amount,
|
||||
balance_after, balance_type, biz_type, biz_no, remark, created_at
|
||||
ledger_no, user_id, order_id, direction, amount_cent,
|
||||
balance_after_cent, balance_type, biz_type, biz_no, remark, created_at
|
||||
)
|
||||
SELECT
|
||||
CONCAT('LDG', DATE_FORMAT(NOW(), '%Y%m%d%H%i%s'), LPAD(seq, 6, '0')) as ledger_no,
|
||||
u.id as user_id,
|
||||
IF(seq % 2 = 0, o.id, NULL) as order_id,
|
||||
CASE WHEN seq % 2 = 0 THEN 'in' ELSE 'out' END as direction,
|
||||
(seq % 500) + (seq * 0.01) as amount,
|
||||
1000.00 + (seq % 1000) as balance_after,
|
||||
((seq % 500) * 100) + seq as amount_cent,
|
||||
100000 + ((seq % 1000) * 100) as balance_after_cent,
|
||||
CASE WHEN seq % 5 = 0 THEN 'frozen' ELSE 'available' END as balance_type,
|
||||
CASE (seq % 6)
|
||||
WHEN 0 THEN 'rent_payment'
|
||||
|
||||
Reference in New Issue
Block a user