统一金额分制重构
This commit is contained in:
@@ -29,21 +29,19 @@ func (GameAccount) TableName() string {
|
||||
}
|
||||
|
||||
type RentalListing struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
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"`
|
||||
ReviewStatus string `gorm:"size:32;not null;default:'none'" json:"review_status"`
|
||||
ReviewReason string `gorm:"size:255;not null;default:''" json:"review_reason"`
|
||||
PublishedAt *time.Time `json:"published_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
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"`
|
||||
PriceCent int64 `gorm:"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"`
|
||||
ReviewStatus string `gorm:"size:32;not null;default:'none'" json:"review_status"`
|
||||
ReviewReason string `gorm:"size:255;not null;default:''" json:"review_reason"`
|
||||
PublishedAt *time.Time `json:"published_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (RentalListing) TableName() string {
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -7,37 +7,28 @@ import (
|
||||
)
|
||||
|
||||
type OrderCheckout struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
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"`
|
||||
OwnerAdjustmentReason string `json:"owner_adjustment_reason"`
|
||||
OwnerAdjustedAt *time.Time `json:"owner_adjusted_at"`
|
||||
RenterConfirmedAt *time.Time `json:"renter_confirmed_at"`
|
||||
RenterRejectedAt *time.Time `json:"renter_rejected_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
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"`
|
||||
RentAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
OwnerRentAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
PlatformFeeCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
DepositAmountCent int64 `gorm:"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"`
|
||||
OtherAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
DepositDeductAmountCent int64 `gorm:"not null;default:0" json:"-"`
|
||||
RenterRefundAmountCent int64 `gorm:"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"`
|
||||
OwnerAdjustmentReason string `json:"owner_adjustment_reason"`
|
||||
OwnerAdjustedAt *time.Time `json:"owner_adjusted_at"`
|
||||
RenterConfirmedAt *time.Time `json:"renter_confirmed_at"`
|
||||
RenterRejectedAt *time.Time `json:"renter_rejected_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (OrderCheckout) TableName() string {
|
||||
|
||||
@@ -3,19 +3,18 @@ package model
|
||||
import "time"
|
||||
|
||||
type User struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
Phone string `gorm:"size:32;not null;uniqueIndex" json:"phone"`
|
||||
Nickname string `gorm:"size:64;not null;default:''" json:"nickname"`
|
||||
AvatarURL string `gorm:"size:512;not null;default:''" json:"avatar_url"`
|
||||
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"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
Phone string `gorm:"size:32;not null;uniqueIndex" json:"phone"`
|
||||
Nickname string `gorm:"size:64;not null;default:''" json:"nickname"`
|
||||
AvatarURL string `gorm:"size:512;not null;default:''" json:"avatar_url"`
|
||||
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"`
|
||||
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"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (User) TableName() string {
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -11,14 +11,14 @@ type DashboardDTO struct {
|
||||
}
|
||||
|
||||
type MetricsDTO struct {
|
||||
TotalUsers int64 `json:"total_users"`
|
||||
VerifiedUsers int64 `json:"verified_users"`
|
||||
TotalListings int64 `json:"total_listings"`
|
||||
PublishedListings int64 `json:"published_listings"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
RentingOrders int64 `json:"renting_orders"`
|
||||
TodayOrders int64 `json:"today_orders"`
|
||||
TodayLedgerAmount float64 `json:"today_ledger_amount"`
|
||||
TotalUsers int64 `json:"total_users"`
|
||||
VerifiedUsers int64 `json:"verified_users"`
|
||||
TotalListings int64 `json:"total_listings"`
|
||||
PublishedListings int64 `json:"published_listings"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
RentingOrders int64 `json:"renting_orders"`
|
||||
TodayOrders int64 `json:"today_orders"`
|
||||
TodayLedgerAmountCent int64 `json:"today_ledger_amount_cent"`
|
||||
}
|
||||
|
||||
type PendingDTO struct {
|
||||
@@ -29,15 +29,15 @@ type PendingDTO struct {
|
||||
}
|
||||
|
||||
type RecentOrderDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
Title string `json:"title"`
|
||||
RenterID uint64 `json:"renter_id"`
|
||||
OwnerID uint64 `json:"owner_id"`
|
||||
Status string `json:"status"`
|
||||
RentAmount float64 `json:"rent_amount"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ID uint64 `json:"id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
Title string `json:"title"`
|
||||
RenterID uint64 `json:"renter_id"`
|
||||
OwnerID uint64 `json:"owner_id"`
|
||||
Status string `json:"status"`
|
||||
RentAmountCent int64 `json:"rent_amount_cent"`
|
||||
DepositAmountCent int64 `json:"deposit_amount_cent"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type RecentDisputeDTO struct {
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -26,35 +26,35 @@ type DashboardDTO struct {
|
||||
}
|
||||
|
||||
type FinanceSummaryDTO struct {
|
||||
TotalFlowAmountCent int64 `json:"total_flow_amount_cent"`
|
||||
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"`
|
||||
SuccessfulPayCount int64 `json:"successful_pay_count"`
|
||||
SuccessfulRefundCount int64 `json:"successful_refund_count"`
|
||||
PendingRefundCount int64 `json:"pending_refund_count"`
|
||||
SettledOrderCount int64 `json:"settled_order_count"`
|
||||
FinancialExceptionCount int64 `json:"financial_exception_count"`
|
||||
TotalFlowAmountCent int64 `json:"total_flow_amount_cent"`
|
||||
TotalRefundAmountCent int64 `json:"total_refund_amount_cent"`
|
||||
PendingRefundAmountCent int64 `json:"pending_refund_amount_cent"`
|
||||
ChannelNetAmountCent int64 `json:"channel_net_amount_cent"`
|
||||
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"`
|
||||
SettledOrderCount int64 `json:"settled_order_count"`
|
||||
FinancialExceptionCount int64 `json:"financial_exception_count"`
|
||||
}
|
||||
|
||||
type FinanceDailyDTO struct {
|
||||
Date string `json:"date"`
|
||||
TotalFlowAmountCent int64 `json:"total_flow_amount_cent"`
|
||||
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"`
|
||||
SuccessfulPayCount int64 `json:"successful_pay_count"`
|
||||
SuccessfulRefundCount int64 `json:"successful_refund_count"`
|
||||
PendingRefundCount int64 `json:"pending_refund_count"`
|
||||
SettledOrderCount int64 `json:"settled_order_count"`
|
||||
Date string `json:"date"`
|
||||
TotalFlowAmountCent int64 `json:"total_flow_amount_cent"`
|
||||
TotalRefundAmountCent int64 `json:"total_refund_amount_cent"`
|
||||
PendingRefundAmountCent int64 `json:"pending_refund_amount_cent"`
|
||||
ChannelNetAmountCent int64 `json:"channel_net_amount_cent"`
|
||||
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"`
|
||||
SettledOrderCount int64 `json:"settled_order_count"`
|
||||
}
|
||||
|
||||
type PaginatedResult struct {
|
||||
@@ -65,32 +65,32 @@ type PaginatedResult struct {
|
||||
}
|
||||
|
||||
type FinanceDetailDTO struct {
|
||||
OrderID uint64 `json:"order_id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
OrderStatus string `json:"order_status"`
|
||||
SettlementStatus string `json:"settlement_status"`
|
||||
RefundStatus string `json:"refund_status"`
|
||||
RenterID uint64 `json:"renter_id"`
|
||||
RenterPhone string `json:"renter_phone"`
|
||||
RenterNickname string `json:"renter_nickname"`
|
||||
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"`
|
||||
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"`
|
||||
FinanceStatus string `json:"finance_status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
SettledAt *time.Time `json:"settled_at,omitempty"`
|
||||
OrderID uint64 `json:"order_id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
OrderStatus string `json:"order_status"`
|
||||
SettlementStatus string `json:"settlement_status"`
|
||||
RefundStatus string `json:"refund_status"`
|
||||
RenterID uint64 `json:"renter_id"`
|
||||
RenterPhone string `json:"renter_phone"`
|
||||
RenterNickname string `json:"renter_nickname"`
|
||||
OwnerID uint64 `json:"owner_id"`
|
||||
OwnerPhone string `json:"owner_phone"`
|
||||
OwnerNickname string `json:"owner_nickname"`
|
||||
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"`
|
||||
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,10 +69,10 @@ 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,
|
||||
COUNT(ro.id) AS settled_order_count`).
|
||||
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)).
|
||||
Where("ro.settled_at >= ? AND ro.settled_at <= ?", query.StartDate, query.EndDate).
|
||||
@@ -94,19 +92,19 @@ func (r *Repository) summary(query DashboardQuery) (*FinanceSummaryDTO, error) {
|
||||
}
|
||||
|
||||
return &FinanceSummaryDTO{
|
||||
TotalFlowAmountCent: payment.TotalFlowAmountCent,
|
||||
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),
|
||||
SuccessfulPayCount: payment.SuccessfulPayCount,
|
||||
SuccessfulRefundCount: payment.SuccessfulRefundCount,
|
||||
PendingRefundCount: payment.PendingRefundCount,
|
||||
SettledOrderCount: settlement.SettledOrderCount,
|
||||
FinancialExceptionCount: exceptionCount,
|
||||
TotalFlowAmountCent: payment.TotalFlowAmountCent,
|
||||
TotalRefundAmountCent: payment.TotalRefundAmountCent,
|
||||
PendingRefundAmountCent: payment.PendingRefundAmountCent,
|
||||
ChannelNetAmountCent: payment.TotalFlowAmountCent - payment.TotalRefundAmountCent,
|
||||
PlatformIncomeAmountCent: settlement.PlatformIncomeAmountCent,
|
||||
OwnerShouldIncomeAmountCent: settlement.OwnerShouldIncomeAmountCent,
|
||||
OwnerWalletIncomeAmountCent: settlement.OwnerWalletIncomeAmountCent,
|
||||
SettlementDiffAmountCent: settlement.OwnerShouldIncomeAmountCent - settlement.OwnerWalletIncomeAmountCent,
|
||||
SuccessfulPayCount: payment.SuccessfulPayCount,
|
||||
SuccessfulRefundCount: payment.SuccessfulRefundCount,
|
||||
PendingRefundCount: payment.PendingRefundCount,
|
||||
SettledOrderCount: settlement.SettledOrderCount,
|
||||
FinancialExceptionCount: exceptionCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -130,10 +128,10 @@ 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,
|
||||
COUNT(ro.id) AS settled_order_count`).
|
||||
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)).
|
||||
Where("ro.settled_at >= ? AND ro.settled_at <= ?", query.StartDate, query.EndDate).
|
||||
@@ -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,
|
||||
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,
|
||||
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'
|
||||
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_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_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,10 +281,10 @@ type paymentSummaryRow struct {
|
||||
}
|
||||
|
||||
type settlementSummaryRow struct {
|
||||
PlatformIncomeAmount float64
|
||||
OwnerShouldIncomeAmount float64
|
||||
OwnerWalletIncomeAmount float64
|
||||
SettledOrderCount int64
|
||||
PlatformIncomeAmountCent int64
|
||||
OwnerShouldIncomeAmountCent int64
|
||||
OwnerWalletIncomeAmountCent int64
|
||||
SettledOrderCount int64
|
||||
}
|
||||
|
||||
type dailyPaymentRow struct {
|
||||
@@ -300,81 +298,88 @@ type dailyPaymentRow struct {
|
||||
}
|
||||
|
||||
type dailySettlementRow struct {
|
||||
Date string
|
||||
PlatformIncomeAmount float64
|
||||
OwnerShouldIncomeAmount float64
|
||||
OwnerWalletIncomeAmount float64
|
||||
SettledOrderCount int64
|
||||
Date string
|
||||
PlatformIncomeAmountCent int64
|
||||
OwnerShouldIncomeAmountCent int64
|
||||
OwnerWalletIncomeAmountCent int64
|
||||
SettledOrderCount int64
|
||||
}
|
||||
|
||||
type financeDetailRow struct {
|
||||
OrderID uint64
|
||||
OrderNo string
|
||||
OrderStatus string
|
||||
SettlementStatus string
|
||||
RefundStatus string
|
||||
RenterID uint64
|
||||
RenterPhone string
|
||||
RenterNickname string
|
||||
OwnerID uint64
|
||||
OwnerPhone string
|
||||
OwnerNickname string
|
||||
OrderRentAmount float64
|
||||
OrderDepositAmount float64
|
||||
CheckoutRentAmount float64
|
||||
CheckoutRenterRefund float64
|
||||
CheckoutOwnerIncome float64
|
||||
CheckoutPlatformFee float64
|
||||
OwnerWalletIncomeAmount float64
|
||||
PaidAmountCent int64
|
||||
RefundedAmountCent int64
|
||||
RefundingAmountCent int64
|
||||
FailedRefundAmountCent int64
|
||||
ChannelNetAmountCent int64
|
||||
PlatformNetAmount float64
|
||||
SettlementDiffAmount float64
|
||||
FinanceStatus string
|
||||
CreatedAt time.Time
|
||||
SettledAt *time.Time
|
||||
OrderID uint64
|
||||
OrderNo string
|
||||
OrderStatus string
|
||||
SettlementStatus string
|
||||
RefundStatus string
|
||||
RenterID uint64
|
||||
RenterPhone string
|
||||
RenterNickname string
|
||||
OwnerID uint64
|
||||
OwnerPhone string
|
||||
OwnerNickname string
|
||||
OrderRentAmountCent int64
|
||||
OrderDepositAmountCent int64
|
||||
CheckoutRentAmountCent int64
|
||||
CheckoutRenterRefundCent int64
|
||||
CheckoutOwnerIncomeCent int64
|
||||
CheckoutPlatformFeeCent int64
|
||||
OwnerWalletIncomeAmountCent int64
|
||||
PaidAmountCent int64
|
||||
RefundedAmountCent int64
|
||||
RefundingAmountCent int64
|
||||
FailedRefundAmountCent int64
|
||||
ChannelNetAmountCent int64
|
||||
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{
|
||||
OrderID: r.OrderID,
|
||||
OrderNo: r.OrderNo,
|
||||
OrderStatus: r.OrderStatus,
|
||||
SettlementStatus: r.SettlementStatus,
|
||||
RefundStatus: r.RefundStatus,
|
||||
RenterID: r.RenterID,
|
||||
RenterPhone: r.RenterPhone,
|
||||
RenterNickname: r.RenterNickname,
|
||||
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),
|
||||
PaidAmountCent: r.PaidAmountCent,
|
||||
RefundedAmountCent: r.RefundedAmountCent,
|
||||
RefundingAmountCent: r.RefundingAmountCent,
|
||||
FailedRefundAmountCent: r.FailedRefundAmountCent,
|
||||
ChannelNetAmountCent: r.ChannelNetAmountCent,
|
||||
PlatformNetAmount: money.Round(r.PlatformNetAmount),
|
||||
SettlementDiffAmount: diff,
|
||||
FinanceStatus: status,
|
||||
CreatedAt: r.CreatedAt,
|
||||
SettledAt: r.SettledAt,
|
||||
OrderID: r.OrderID,
|
||||
OrderNo: r.OrderNo,
|
||||
OrderStatus: r.OrderStatus,
|
||||
SettlementStatus: r.SettlementStatus,
|
||||
RefundStatus: r.RefundStatus,
|
||||
RenterID: r.RenterID,
|
||||
RenterPhone: r.RenterPhone,
|
||||
RenterNickname: r.RenterNickname,
|
||||
OwnerID: r.OwnerID,
|
||||
OwnerPhone: r.OwnerPhone,
|
||||
OwnerNickname: r.OwnerNickname,
|
||||
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,
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,30 +3,29 @@ package adminuser
|
||||
import "time"
|
||||
|
||||
type UserDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
Phone string `json:"phone"`
|
||||
Nickname string `json:"nickname"`
|
||||
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"`
|
||||
Status string `json:"status"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
ListingCount int64 `json:"listing_count"`
|
||||
DisputeCount int64 `json:"dispute_count"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint64 `json:"id"`
|
||||
Phone string `json:"phone"`
|
||||
Nickname string `json:"nickname"`
|
||||
RealnameStatus string `json:"realname_status"`
|
||||
RiskStatus string `json:"risk_status"`
|
||||
CreditScore int `json:"credit_score"`
|
||||
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"`
|
||||
DisputeCount int64 `json:"dispute_count"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
type FreezeRequest struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
type DepositFreeQuotaRequest struct {
|
||||
AmountCent int64 `json:"amount_cent"`
|
||||
Amount float64 `json:"amount"`
|
||||
AmountCent int64 `json:"amount_cent"`
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -143,46 +136,39 @@ func (r *Repository) Find(userID uint64) (*UserDTO, error) {
|
||||
|
||||
type userRow struct {
|
||||
model.User
|
||||
OrderCount int64
|
||||
ListingCount int64
|
||||
DisputeCount int64
|
||||
DepositFreeUsed float64
|
||||
OrderCount int64
|
||||
ListingCount int64
|
||||
DisputeCount int64
|
||||
DepositFreeUsedCent int64
|
||||
}
|
||||
|
||||
func (row userRow) toDTO() UserDTO {
|
||||
remaining := roundMoney(row.DepositFreeQuota - row.DepositFreeUsed)
|
||||
remaining := row.DepositFreeQuotaCent - row.DepositFreeUsedCent
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
return UserDTO{
|
||||
ID: row.ID,
|
||||
Phone: row.Phone,
|
||||
Nickname: row.Nickname,
|
||||
RealnameStatus: row.RealnameStatus,
|
||||
RiskStatus: row.RiskStatus,
|
||||
CreditScore: row.CreditScore,
|
||||
DepositFreeQuota: row.DepositFreeQuota,
|
||||
DepositFreeUsed: roundMoney(row.DepositFreeUsed),
|
||||
DepositFreeRemaining: remaining,
|
||||
Status: row.Status,
|
||||
OrderCount: row.OrderCount,
|
||||
ListingCount: row.ListingCount,
|
||||
DisputeCount: row.DisputeCount,
|
||||
LastLoginAt: row.LastLoginAt,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
ID: row.ID,
|
||||
Phone: row.Phone,
|
||||
Nickname: row.Nickname,
|
||||
RealnameStatus: row.RealnameStatus,
|
||||
RiskStatus: row.RiskStatus,
|
||||
CreditScore: row.CreditScore,
|
||||
DepositFreeQuotaCent: row.DepositFreeQuotaCent,
|
||||
DepositFreeUsedCent: row.DepositFreeUsedCent,
|
||||
DepositFreeRemainingCent: remaining,
|
||||
Status: row.Status,
|
||||
OrderCount: row.OrderCount,
|
||||
ListingCount: row.ListingCount,
|
||||
DisputeCount: row.DisputeCount,
|
||||
LastLoginAt: row.LastLoginAt,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
return req.AmountCent
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -35,9 +35,9 @@ type CreateRequest struct {
|
||||
}
|
||||
|
||||
type ArbitrateRequest struct {
|
||||
Result string `json:"result" binding:"required"`
|
||||
Remark string `json:"remark" binding:"required"`
|
||||
Amount float64 `json:"amount"`
|
||||
Result string `json:"result" binding:"required"`
|
||||
Remark string `json:"remark" binding:"required"`
|
||||
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
|
||||
}
|
||||
@@ -281,25 +279,25 @@ func (r *Repository) Arbitrate(adminID uint64, id uint64, req ArbitrateRequest,
|
||||
}
|
||||
disputeID := row.ID
|
||||
if err := appendAuditLog(tx, adminID, "dispute.arbitrate", "dispute", row.ID, meta, map[string]any{
|
||||
"dispute_id": row.ID,
|
||||
"order_id": order.ID,
|
||||
"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,
|
||||
"before_order_status": beforeOrderStatus,
|
||||
"after_order_status": order.Status,
|
||||
"before_handoff_status": beforeHandoffStatus,
|
||||
"after_handoff_status": order.HandoffStatus,
|
||||
"before_settlement_status": beforeSettlementStatus,
|
||||
"after_settlement_status": order.SettlementStatus,
|
||||
"before_listing_status": beforeListingStatus,
|
||||
"after_listing_status": listing.Status,
|
||||
"before_account_status": beforeAccountStatus,
|
||||
"after_account_status": account.Status,
|
||||
"dispute_id": row.ID,
|
||||
"order_id": order.ID,
|
||||
"order_no": order.OrderNo,
|
||||
"result": req.Result,
|
||||
"remark": req.Remark,
|
||||
"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,
|
||||
"after_handoff_status": order.HandoffStatus,
|
||||
"before_settlement_status": beforeSettlementStatus,
|
||||
"after_settlement_status": order.SettlementStatus,
|
||||
"before_listing_status": beforeListingStatus,
|
||||
"after_listing_status": listing.Status,
|
||||
"before_account_status": beforeAccountStatus,
|
||||
"after_account_status": account.Status,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -338,36 +336,29 @@ func (r *Repository) Arbitrate(adminID uint64, id uint64, req ArbitrateRequest,
|
||||
}
|
||||
|
||||
type arbitrationSettlement struct {
|
||||
Entries []wallet.Entry
|
||||
RenterRefundAmount float64
|
||||
OwnerIncomeAmount float64
|
||||
DepositDeductAmount float64
|
||||
Entries []wallet.Entry
|
||||
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) {
|
||||
|
||||
@@ -9,45 +9,45 @@ import (
|
||||
)
|
||||
|
||||
type ListingDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
ListingNo string `json:"listing_no"`
|
||||
AccountID uint64 `json:"account_id"`
|
||||
OwnerID uint64 `json:"owner_id"`
|
||||
OwnerPhone string `json:"owner_phone,omitempty"`
|
||||
OwnerNickname string `json:"owner_nickname,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
GameName string `json:"game_name"`
|
||||
ServerRegion string `json:"server_region"`
|
||||
LoginPlatform string `json:"login_platform"`
|
||||
RankLevel string `json:"rank_level"`
|
||||
HafCoinAmount int64 `json:"haf_coin_amount"`
|
||||
AssetSummary map[string]any `json:"asset_summary,omitempty"`
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
CoverURL string `json:"cover_url"`
|
||||
PriceCent int64 `json:"price_cent"`
|
||||
DepositAmountCent int64 `json:"deposit_amount_cent"`
|
||||
IsAccelerated bool `json:"is_accelerated_sale"`
|
||||
InTransaction bool `json:"in_transaction"`
|
||||
Status string `json:"status"`
|
||||
ReviewStatus string `json:"review_status"`
|
||||
ReviewReason string `json:"review_reason"`
|
||||
PublishedAt *time.Time `json:"published_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint64 `json:"id"`
|
||||
ListingNo string `json:"listing_no"`
|
||||
AccountID uint64 `json:"account_id"`
|
||||
OwnerID uint64 `json:"owner_id"`
|
||||
OwnerPhone string `json:"owner_phone,omitempty"`
|
||||
OwnerNickname string `json:"owner_nickname,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
GameName string `json:"game_name"`
|
||||
ServerRegion string `json:"server_region"`
|
||||
LoginPlatform string `json:"login_platform"`
|
||||
RankLevel string `json:"rank_level"`
|
||||
HafCoinAmount int64 `json:"haf_coin_amount"`
|
||||
AssetSummary map[string]any `json:"asset_summary,omitempty"`
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
CoverURL string `json:"cover_url"`
|
||||
PriceCent int64 `json:"price_cent"`
|
||||
DepositAmountCent int64 `json:"deposit_amount_cent"`
|
||||
IsAccelerated bool `json:"is_accelerated_sale"`
|
||||
InTransaction bool `json:"in_transaction"`
|
||||
Status string `json:"status"`
|
||||
ReviewStatus string `json:"review_status"`
|
||||
ReviewReason string `json:"review_reason"`
|
||||
PublishedAt *time.Time `json:"published_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CreateRequest struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
ServerRegion string `json:"server_region" binding:"required"`
|
||||
LoginPlatform string `json:"login_platform"`
|
||||
RankLevel string `json:"rank_level"`
|
||||
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"`
|
||||
Title string `json:"title" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
ServerRegion string `json:"server_region" binding:"required"`
|
||||
LoginPlatform string `json:"login_platform"`
|
||||
RankLevel string `json:"rank_level"`
|
||||
HafCoinAmount int64 `json:"haf_coin_amount"`
|
||||
AssetSummary map[string]any `json:"asset_summary"`
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
PriceCent int64 `json:"price_cent"`
|
||||
DepositAmountCent int64 `json:"deposit_amount_cent"`
|
||||
|
||||
AgreedVirtualAssetSale bool `json:"agreed_virtual_asset_sale"`
|
||||
AgreedSellerAgreement bool `json:"agreed_seller_agreement"`
|
||||
@@ -60,9 +60,9 @@ type ReviewRequest struct {
|
||||
}
|
||||
|
||||
type AdminPriceAdjustRequest struct {
|
||||
BuyerRatio float64 `json:"buyer_ratio"`
|
||||
BuyerTotalPrice float64 `json:"buyer_total_price"`
|
||||
Reason string `json:"reason"`
|
||||
BuyerRatio float64 `json:"buyer_ratio"`
|
||||
BuyerTotalPriceCent int64 `json:"buyer_total_price_cent"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
type AdminListQuery struct {
|
||||
|
||||
@@ -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 {
|
||||
@@ -525,16 +525,16 @@ func externalAccountToCreateRequest(uploaderName string, uploadTime int64, item
|
||||
},
|
||||
}
|
||||
return CreateRequest{
|
||||
Title: externalUploadTitle(item, insurance, hafCoinM),
|
||||
Description: "开放接口自动上传,等待后台审核。",
|
||||
ServerRegion: serverRegionFromLoginMethod(item.LoginMethod),
|
||||
LoginPlatform: strings.TrimSpace(item.LoginMethod),
|
||||
RankLevel: strings.TrimSpace(item.Rank),
|
||||
HafCoinAmount: int64(math.Round(hafCoinM * 1000000)),
|
||||
AssetSummary: assetSummary,
|
||||
ScreenshotURLS: []string{defaultUploadScreenshot},
|
||||
Price: price,
|
||||
DepositAmount: item.Deposit,
|
||||
Title: externalUploadTitle(item, insurance, hafCoinM),
|
||||
Description: "开放接口自动上传,等待后台审核。",
|
||||
ServerRegion: serverRegionFromLoginMethod(item.LoginMethod),
|
||||
LoginPlatform: strings.TrimSpace(item.LoginMethod),
|
||||
RankLevel: strings.TrimSpace(item.Rank),
|
||||
HafCoinAmount: int64(math.Round(hafCoinM * 1000000)),
|
||||
AssetSummary: assetSummary,
|
||||
ScreenshotURLS: []string{defaultUploadScreenshot},
|
||||
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)
|
||||
}
|
||||
@@ -35,12 +35,12 @@ func TestCalculateAdminAdjustedPriceByTotalPrice(t *testing.T) {
|
||||
|
||||
func TestValidateRequestRequiresDepositAboveConsumables(t *testing.T) {
|
||||
req := CreateRequest{
|
||||
Title: "测试账号",
|
||||
ServerRegion: "烽火地带",
|
||||
Price: 100,
|
||||
DepositAmount: 2,
|
||||
HafCoinAmount: 1000000,
|
||||
ScreenshotURLS: []string{"https://example.com/a.png"},
|
||||
Title: "测试账号",
|
||||
ServerRegion: "烽火地带",
|
||||
PriceCent: 10000,
|
||||
DepositAmountCent: 200,
|
||||
HafCoinAmount: 1000000,
|
||||
ScreenshotURLS: []string{"https://example.com/a.png"},
|
||||
AssetSummary: map[string]any{
|
||||
"resources": []any{
|
||||
map[string]any{"mode": "收费", "quantity": float64(2), "price": "1元/个"},
|
||||
@@ -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"])
|
||||
|
||||
@@ -54,20 +54,20 @@ type SubmitReturnRequest struct {
|
||||
}
|
||||
|
||||
type SubmitCheckoutRequest struct {
|
||||
Content string `json:"content" binding:"required"`
|
||||
ConsumableAmount float64 `json:"consumable_amount"`
|
||||
CoinConsumedM float64 `json:"coin_consumed_m"`
|
||||
OtherAmount float64 `json:"other_amount"`
|
||||
EvidenceURLS []string `json:"evidence_urls"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
ConsumableAmountCent int64 `json:"consumable_amount_cent"`
|
||||
CoinConsumedM float64 `json:"coin_consumed_m"`
|
||||
OtherAmountCent int64 `json:"other_amount_cent"`
|
||||
EvidenceURLS []string `json:"evidence_urls"`
|
||||
}
|
||||
|
||||
type CounterCheckoutRequest struct {
|
||||
ConsumableAmount float64 `json:"consumable_amount"`
|
||||
CoinConsumedM float64 `json:"coin_consumed_m"`
|
||||
OtherAmount float64 `json:"other_amount"`
|
||||
DepositDeductAmount float64 `json:"deposit_deduct_amount"`
|
||||
Reason string `json:"reason" binding:"required"`
|
||||
EvidenceURLS []string `json:"evidence_urls"`
|
||||
ConsumableAmountCent int64 `json:"consumable_amount_cent"`
|
||||
CoinConsumedM float64 `json:"coin_consumed_m"`
|
||||
OtherAmountCent int64 `json:"other_amount_cent"`
|
||||
DepositDeductAmountCent int64 `json:"deposit_deduct_amount_cent"`
|
||||
Reason string `json:"reason" binding:"required"`
|
||||
EvidenceURLS []string `json:"evidence_urls"`
|
||||
}
|
||||
|
||||
type AdminActionRequest struct {
|
||||
|
||||
@@ -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,9 +12,9 @@ type StartPaymentRequest struct {
|
||||
}
|
||||
|
||||
type WalletRechargePaymentRequest struct {
|
||||
Amount float64 `json:"amount"`
|
||||
PayWay string `json:"pay_way"`
|
||||
JSPayFlag string `json:"jspay_flag"`
|
||||
AmountCent int64 `json:"amount_cent"`
|
||||
PayWay string `json:"pay_way"`
|
||||
JSPayFlag string `json:"jspay_flag"`
|
||||
}
|
||||
|
||||
type PaymentDTO struct {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user