修复代管订单押金赔付待打款金额
This commit is contained in:
@@ -153,7 +153,7 @@ func (r *Repository) Arbitrate(ctx context.Context, adminID uint64, id uint64, r
|
||||
"order_no": order.OrderNo,
|
||||
"result": req.Result,
|
||||
"remark": req.Remark,
|
||||
"input_amount_cent": req.AmountCent,
|
||||
"input_amount_cent": arbitrationInputAmountCent(req),
|
||||
"renter_refund_amount_cent": settlement.RenterRefundAmountCent,
|
||||
"owner_income_amount_cent": settlement.OwnerIncomeAmountCent,
|
||||
"deposit_deduct_amount_cent": settlement.DepositDeductAmountCent,
|
||||
@@ -245,7 +245,7 @@ func buildActualCheckoutArbitrationSettlement(tx *gorm.DB, row model.Dispute, or
|
||||
if err != nil {
|
||||
return settlement, nil, linkedCheckout, 0, err
|
||||
}
|
||||
depositDeductAmountCent, err := arbitrationDepositDeductAmountCent(order, req)
|
||||
depositDeductAmountCent, err := arbitrationDepositDeductAmountCent(order, linkedCheckout, req)
|
||||
if err != nil {
|
||||
return settlement, nil, linkedCheckout, 0, err
|
||||
}
|
||||
@@ -299,21 +299,24 @@ func arbitrationConsumableAmountCent(order model.RentalOrder, checkout *model.Or
|
||||
return 0, ErrInvalidDispute
|
||||
}
|
||||
|
||||
func arbitrationDepositDeductAmountCent(order model.RentalOrder, req ArbitrateRequest) (int64, error) {
|
||||
func arbitrationDepositDeductAmountCent(order model.RentalOrder, checkout *model.OrderCheckout, req ArbitrateRequest) (int64, error) {
|
||||
switch req.Result {
|
||||
case "actual_settlement":
|
||||
if req.AmountCent <= 0 {
|
||||
if req.AmountCent == nil {
|
||||
if checkout != nil {
|
||||
return arbitrationCheckoutDepositDeductCent(*checkout), nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
if req.AmountCent > order.DepositAmountCent {
|
||||
if *req.AmountCent < 0 || *req.AmountCent > order.DepositAmountCent {
|
||||
return 0, ErrInvalidDispute
|
||||
}
|
||||
return req.AmountCent, nil
|
||||
return *req.AmountCent, nil
|
||||
case "release_deposit":
|
||||
return 0, nil
|
||||
case "deduct_deposit", "compensate_owner":
|
||||
deductAmountCent := req.AmountCent
|
||||
if deductAmountCent <= 0 {
|
||||
deductAmountCent := arbitrationInputAmountCent(req)
|
||||
if req.AmountCent == nil || deductAmountCent <= 0 {
|
||||
deductAmountCent = order.DepositAmountCent
|
||||
}
|
||||
if deductAmountCent > order.DepositAmountCent {
|
||||
@@ -325,6 +328,23 @@ func arbitrationDepositDeductAmountCent(order model.RentalOrder, req ArbitrateRe
|
||||
}
|
||||
}
|
||||
|
||||
func arbitrationInputAmountCent(req ArbitrateRequest) int64 {
|
||||
if req.AmountCent == nil {
|
||||
return 0
|
||||
}
|
||||
return *req.AmountCent
|
||||
}
|
||||
|
||||
func arbitrationCheckoutDepositDeductCent(checkout model.OrderCheckout) int64 {
|
||||
if checkout.DepositDeductAmountCent > 0 {
|
||||
return checkout.DepositDeductAmountCent
|
||||
}
|
||||
if checkout.OtherAmountCent > 0 {
|
||||
return checkout.OtherAmountCent
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func applyArbitrationActualSettlement(order *model.RentalOrder, checkout *model.OrderCheckout, settlement ordermodule.ActualCheckoutSettlement, depositDeductAmountCent int64) {
|
||||
order.ActualCoinConsumedM = settlement.CoinConsumedM
|
||||
order.ActualPureCoinAmountCent = settlement.PureCoinAmountCent
|
||||
@@ -350,7 +370,7 @@ func applyArbitrationActualSettlement(order *model.RentalOrder, checkout *model.
|
||||
}
|
||||
|
||||
func isPlatformManagedOrder(order model.RentalOrder) bool {
|
||||
return order.SettlementMode == "platform_managed" || order.HandoffMode == "platform"
|
||||
return order.SettlementMode == "platform_managed"
|
||||
}
|
||||
|
||||
func appendPlatformManagedAdminNotification(tx *gorm.DB, order model.RentalOrder, typ string, title string, content string) error {
|
||||
@@ -459,15 +479,15 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest, r
|
||||
addRenterRefund(totalCent, "仲裁全额退款")
|
||||
settlement.RenterDepositRefundCent = depositAmountCent
|
||||
case "partial_refund":
|
||||
if req.AmountCent <= 0 || req.AmountCent > totalCent {
|
||||
if req.AmountCent == nil || *req.AmountCent <= 0 || *req.AmountCent > totalCent {
|
||||
return settlement, ErrInvalidDispute
|
||||
}
|
||||
addRenterRefund(req.AmountCent, "仲裁部分退款")
|
||||
addRenterRefund(*req.AmountCent, "仲裁部分退款")
|
||||
// 部分退款为合并金额,无法精确拆分租金/押金,按押金优先归类以便暂扣。
|
||||
settlement.RenterDepositRefundCent = money.MinCent(req.AmountCent, depositAmountCent)
|
||||
settlement.RenterDepositRefundCent = money.MinCent(*req.AmountCent, depositAmountCent)
|
||||
// 号主仅拿「未退租金中的号主份额 + 未退押金」,平台加价按未退租金比例预留,避免整笔剩余进号主。
|
||||
addOwnerIncome(
|
||||
partialRefundOwnerIncomeCent(rentAmountCent, ownerRentAmountCent, depositAmountCent, req.AmountCent),
|
||||
partialRefundOwnerIncomeCent(rentAmountCent, ownerRentAmountCent, depositAmountCent, *req.AmountCent),
|
||||
"仲裁剩余金额结算给号主",
|
||||
)
|
||||
case "release_deposit":
|
||||
@@ -475,8 +495,8 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest, r
|
||||
addRenterRefund(depositAmountCent, "仲裁释放押金给租客")
|
||||
settlement.RenterDepositRefundCent = depositAmountCent
|
||||
case "deduct_deposit", "compensate_owner":
|
||||
deductAmountCent := req.AmountCent
|
||||
if deductAmountCent <= 0 {
|
||||
deductAmountCent := arbitrationInputAmountCent(req)
|
||||
if req.AmountCent == nil || deductAmountCent <= 0 {
|
||||
deductAmountCent = depositAmountCent
|
||||
}
|
||||
if deductAmountCent > depositAmountCent {
|
||||
|
||||
@@ -9,40 +9,41 @@ import (
|
||||
)
|
||||
|
||||
type DisputeDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
OrderID uint64 `json:"order_id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
OrderStatus string `json:"order_status"`
|
||||
HandoffStatus string `json:"handoff_status"`
|
||||
SettlementStatus string `json:"settlement_status"`
|
||||
ExtraItemOriginalAmountCent int64 `json:"extra_item_original_amount_cent,omitempty"`
|
||||
ListingNo string `json:"listing_no"`
|
||||
Title string `json:"title"`
|
||||
OwnerID uint64 `json:"owner_id"`
|
||||
RenterID uint64 `json:"renter_id"`
|
||||
OwnerPhone string `json:"owner_phone"`
|
||||
RenterPhone string `json:"renter_phone"`
|
||||
InitiatorID uint64 `json:"initiator_id"`
|
||||
InitiatorType string `json:"initiator_type"`
|
||||
InitiatorAdminID *uint64 `json:"initiator_admin_id,omitempty"`
|
||||
TargetUserID uint64 `json:"target_user_id"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Description string `json:"description"`
|
||||
EvidenceURLS datatypes.JSON `json:"evidence_urls"`
|
||||
PreviousOrderStatus string `json:"previous_order_status"`
|
||||
PreviousHandoffStatus string `json:"previous_handoff_status"`
|
||||
PreviousSettlementStatus string `json:"previous_settlement_status"`
|
||||
CheckoutID *uint64 `json:"checkout_id"`
|
||||
CheckoutCoinConsumedM *float64 `json:"checkout_coin_consumed_m,omitempty"`
|
||||
CheckoutConsumableAmountCent *int64 `json:"checkout_consumable_amount_cent,omitempty"`
|
||||
PreviousCheckoutStatus string `json:"previous_checkout_status"`
|
||||
ArbitrationResult string `json:"arbitration_result"`
|
||||
ArbitrationRemark string `json:"arbitration_remark"`
|
||||
HandledBy *uint64 `json:"handled_by"`
|
||||
HandledAt *time.Time `json:"handled_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint64 `json:"id"`
|
||||
OrderID uint64 `json:"order_id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
OrderStatus string `json:"order_status"`
|
||||
HandoffStatus string `json:"handoff_status"`
|
||||
SettlementStatus string `json:"settlement_status"`
|
||||
ExtraItemOriginalAmountCent int64 `json:"extra_item_original_amount_cent,omitempty"`
|
||||
ListingNo string `json:"listing_no"`
|
||||
Title string `json:"title"`
|
||||
OwnerID uint64 `json:"owner_id"`
|
||||
RenterID uint64 `json:"renter_id"`
|
||||
OwnerPhone string `json:"owner_phone"`
|
||||
RenterPhone string `json:"renter_phone"`
|
||||
InitiatorID uint64 `json:"initiator_id"`
|
||||
InitiatorType string `json:"initiator_type"`
|
||||
InitiatorAdminID *uint64 `json:"initiator_admin_id,omitempty"`
|
||||
TargetUserID uint64 `json:"target_user_id"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Description string `json:"description"`
|
||||
EvidenceURLS datatypes.JSON `json:"evidence_urls"`
|
||||
PreviousOrderStatus string `json:"previous_order_status"`
|
||||
PreviousHandoffStatus string `json:"previous_handoff_status"`
|
||||
PreviousSettlementStatus string `json:"previous_settlement_status"`
|
||||
CheckoutID *uint64 `json:"checkout_id"`
|
||||
CheckoutCoinConsumedM *float64 `json:"checkout_coin_consumed_m,omitempty"`
|
||||
CheckoutConsumableAmountCent *int64 `json:"checkout_consumable_amount_cent,omitempty"`
|
||||
CheckoutDepositDeductAmountCent *int64 `json:"checkout_deposit_deduct_amount_cent,omitempty"`
|
||||
PreviousCheckoutStatus string `json:"previous_checkout_status"`
|
||||
ArbitrationResult string `json:"arbitration_result"`
|
||||
ArbitrationRemark string `json:"arbitration_remark"`
|
||||
HandledBy *uint64 `json:"handled_by"`
|
||||
HandledAt *time.Time `json:"handled_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CreateRequest struct {
|
||||
@@ -61,7 +62,7 @@ type AdminCreateRequest struct {
|
||||
type ArbitrateRequest struct {
|
||||
Result string `json:"result" binding:"required"`
|
||||
Remark string `json:"remark" binding:"required"`
|
||||
AmountCent int64 `json:"amount_cent"`
|
||||
AmountCent *int64 `json:"amount_cent"`
|
||||
ActualCoinConsumedM *float64 `json:"actual_coin_consumed_m"`
|
||||
ActualConsumableAmountCent *int64 `json:"actual_consumable_amount_cent"`
|
||||
}
|
||||
|
||||
@@ -10,57 +10,59 @@ import (
|
||||
|
||||
type disputeRow struct {
|
||||
model.Dispute
|
||||
OrderNo string
|
||||
OrderStatus string
|
||||
HandoffStatus string
|
||||
SettlementStatus string
|
||||
ExtraItemOriginalAmountCent int64
|
||||
ListingNo string
|
||||
Title string
|
||||
OwnerID uint64
|
||||
RenterID uint64
|
||||
OwnerPhone string
|
||||
RenterPhone string
|
||||
CheckoutCoinConsumedM *float64
|
||||
CheckoutConsumableAmountCent *int64
|
||||
OrderNo string
|
||||
OrderStatus string
|
||||
HandoffStatus string
|
||||
SettlementStatus string
|
||||
ExtraItemOriginalAmountCent int64
|
||||
ListingNo string
|
||||
Title string
|
||||
OwnerID uint64
|
||||
RenterID uint64
|
||||
OwnerPhone string
|
||||
RenterPhone string
|
||||
CheckoutCoinConsumedM *float64
|
||||
CheckoutConsumableAmountCent *int64
|
||||
CheckoutDepositDeductAmountCent *int64
|
||||
}
|
||||
|
||||
func (row disputeRow) toDTO() DisputeDTO {
|
||||
return DisputeDTO{
|
||||
ID: row.ID,
|
||||
OrderID: row.OrderID,
|
||||
OrderNo: row.OrderNo,
|
||||
OrderStatus: row.OrderStatus,
|
||||
HandoffStatus: row.HandoffStatus,
|
||||
SettlementStatus: row.SettlementStatus,
|
||||
ExtraItemOriginalAmountCent: row.ExtraItemOriginalAmountCent,
|
||||
ListingNo: row.ListingNo,
|
||||
Title: row.Title,
|
||||
OwnerID: row.OwnerID,
|
||||
RenterID: row.RenterID,
|
||||
OwnerPhone: row.OwnerPhone,
|
||||
RenterPhone: row.RenterPhone,
|
||||
InitiatorID: row.InitiatorID,
|
||||
InitiatorType: effectiveInitiatorType(row.Dispute),
|
||||
InitiatorAdminID: row.InitiatorAdminID,
|
||||
TargetUserID: row.TargetUserID,
|
||||
Type: row.Type,
|
||||
Status: row.Status,
|
||||
Description: row.Description,
|
||||
EvidenceURLS: row.EvidenceURLS,
|
||||
PreviousOrderStatus: row.PreviousOrderStatus,
|
||||
PreviousHandoffStatus: row.PreviousHandoffStatus,
|
||||
PreviousSettlementStatus: row.PreviousSettlementStatus,
|
||||
CheckoutID: row.CheckoutID,
|
||||
CheckoutCoinConsumedM: row.CheckoutCoinConsumedM,
|
||||
CheckoutConsumableAmountCent: row.CheckoutConsumableAmountCent,
|
||||
PreviousCheckoutStatus: row.PreviousCheckoutStatus,
|
||||
ArbitrationResult: row.ArbitrationResult,
|
||||
ArbitrationRemark: row.ArbitrationRemark,
|
||||
HandledBy: row.HandledBy,
|
||||
HandledAt: row.HandledAt,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
ID: row.ID,
|
||||
OrderID: row.OrderID,
|
||||
OrderNo: row.OrderNo,
|
||||
OrderStatus: row.OrderStatus,
|
||||
HandoffStatus: row.HandoffStatus,
|
||||
SettlementStatus: row.SettlementStatus,
|
||||
ExtraItemOriginalAmountCent: row.ExtraItemOriginalAmountCent,
|
||||
ListingNo: row.ListingNo,
|
||||
Title: row.Title,
|
||||
OwnerID: row.OwnerID,
|
||||
RenterID: row.RenterID,
|
||||
OwnerPhone: row.OwnerPhone,
|
||||
RenterPhone: row.RenterPhone,
|
||||
InitiatorID: row.InitiatorID,
|
||||
InitiatorType: effectiveInitiatorType(row.Dispute),
|
||||
InitiatorAdminID: row.InitiatorAdminID,
|
||||
TargetUserID: row.TargetUserID,
|
||||
Type: row.Type,
|
||||
Status: row.Status,
|
||||
Description: row.Description,
|
||||
EvidenceURLS: row.EvidenceURLS,
|
||||
PreviousOrderStatus: row.PreviousOrderStatus,
|
||||
PreviousHandoffStatus: row.PreviousHandoffStatus,
|
||||
PreviousSettlementStatus: row.PreviousSettlementStatus,
|
||||
CheckoutID: row.CheckoutID,
|
||||
CheckoutCoinConsumedM: row.CheckoutCoinConsumedM,
|
||||
CheckoutConsumableAmountCent: row.CheckoutConsumableAmountCent,
|
||||
CheckoutDepositDeductAmountCent: row.CheckoutDepositDeductAmountCent,
|
||||
PreviousCheckoutStatus: row.PreviousCheckoutStatus,
|
||||
ArbitrationResult: row.ArbitrationResult,
|
||||
ArbitrationRemark: row.ArbitrationRemark,
|
||||
HandledBy: row.HandledBy,
|
||||
HandledAt: row.HandledAt,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,8 @@ func (r *Repository) baseQuery(ctx context.Context) *gorm.DB {
|
||||
o.owner_id, o.renter_id, o.extra_item_original_amount_cent,
|
||||
owner.phone AS owner_phone, renter.phone AS renter_phone,
|
||||
l.listing_no, a.title, c.coin_consumed_m AS checkout_coin_consumed_m,
|
||||
c.consumable_amount_cent AS checkout_consumable_amount_cent`)
|
||||
c.consumable_amount_cent AS checkout_consumable_amount_cent,
|
||||
CASE WHEN c.deposit_deduct_amount_cent > 0 THEN c.deposit_deduct_amount_cent ELSE c.other_amount_cent END AS checkout_deposit_deduct_amount_cent`)
|
||||
}
|
||||
|
||||
func (r *Repository) adminFilterQuery(ctx context.Context) *gorm.DB {
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func int64Ptr(value int64) *int64 { return &value }
|
||||
|
||||
func disputePureCoinOrder() model.RentalOrder {
|
||||
return model.RentalOrder{
|
||||
Status: "renting",
|
||||
@@ -322,6 +324,64 @@ func TestPlatformManagedArbitrationUsesOfflineSettlement(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlatformManagedCheckoutArbitrationKeepsExistingDepositCompensation(t *testing.T) {
|
||||
db := setupDisputeTestDB(t)
|
||||
repo := NewRepository(db, Dependencies{RefundStarter: RefundStarterFunc(func(context.Context, uint64, int64, string, string) (string, error) {
|
||||
return "refunded", nil
|
||||
})})
|
||||
adminID := uint64(78)
|
||||
_, renter, order := createDisputeOrderFixture(t, db, model.RentalOrder{
|
||||
Status: "pending_checkout_confirm",
|
||||
HandoffStatus: "pending_owner_checkout",
|
||||
SettlementStatus: "pending",
|
||||
HandoffMode: "platform",
|
||||
SettlementMode: "platform_managed",
|
||||
ManagedAdminID: &adminID,
|
||||
RentAmountCent: 10000,
|
||||
OwnerRentAmountCent: 8000,
|
||||
PlatformFeeCent: 2000,
|
||||
DepositAmountCent: 5000,
|
||||
})
|
||||
checkout := model.OrderCheckout{
|
||||
OrderID: order.ID,
|
||||
InitiatedBy: renter.ID,
|
||||
Status: "submitted",
|
||||
CoinConsumedM: 0,
|
||||
DepositDeductAmountCent: 3000,
|
||||
OtherAmountCent: 3000,
|
||||
}
|
||||
if err := db.Create(&checkout).Error; err != nil {
|
||||
t.Fatalf("创建结账记录失败: %v", err)
|
||||
}
|
||||
created, err := repo.Create(t.Context(), renter.ID, order.ID, CreateRequest{
|
||||
Type: "checkout_amount",
|
||||
Description: "结账押金损耗争议",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("创建结账争议失败: %v", err)
|
||||
}
|
||||
if created.CheckoutDepositDeductAmountCent == nil || *created.CheckoutDepositDeductAmountCent != 3000 {
|
||||
t.Fatalf("争议未带出押金赔付金额: %v", created.CheckoutDepositDeductAmountCent)
|
||||
}
|
||||
actualCoinConsumedM := 0.0
|
||||
actualConsumableAmountCent := int64(0)
|
||||
if _, err := repo.Arbitrate(t.Context(), adminID, created.ID, ArbitrateRequest{
|
||||
Result: "actual_settlement",
|
||||
Remark: "沿用原结账押金赔付",
|
||||
ActualCoinConsumedM: &actualCoinConsumedM,
|
||||
ActualConsumableAmountCent: &actualConsumableAmountCent,
|
||||
}, AuditMeta{}); err != nil {
|
||||
t.Fatalf("仲裁失败: %v", err)
|
||||
}
|
||||
var saved model.RentalOrder
|
||||
if err := db.First(&saved, order.ID).Error; err != nil {
|
||||
t.Fatalf("读取仲裁订单失败: %v", err)
|
||||
}
|
||||
if saved.OfflineSettlementAmountCent != 11000 {
|
||||
t.Fatalf("仲裁后待打款 = %d, want 11000", saved.OfflineSettlementAmountCent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArbitrationCompletedOrderRequiresActualCoinConsumedM(t *testing.T) {
|
||||
db := setupDisputeTestDB(t)
|
||||
repo := NewRepository(db, Dependencies{RefundStarter: RefundStarterFunc(func(context.Context, uint64, int64, string, string) (string, error) {
|
||||
@@ -394,7 +454,7 @@ func TestActualSettlementArbitrationUsesOwnerCompensationAmount(t *testing.T) {
|
||||
settlement, actualSettlement, _, depositDeductAmountCent, err = buildActualCheckoutArbitrationSettlement(nil, model.Dispute{}, order, ArbitrateRequest{
|
||||
Result: "actual_settlement",
|
||||
Remark: "赔付号主",
|
||||
AmountCent: compensationCent,
|
||||
AmountCent: &compensationCent,
|
||||
ActualCoinConsumedM: &actualCoinConsumedM,
|
||||
ActualConsumableAmountCent: &actualConsumableAmountCent,
|
||||
}, 0)
|
||||
@@ -406,6 +466,21 @@ func TestActualSettlementArbitrationUsesOwnerCompensationAmount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestActualSettlementArbitrationInheritsCheckoutDepositCompensation(t *testing.T) {
|
||||
order := disputePureCoinOrder()
|
||||
checkout := &model.OrderCheckout{DepositDeductAmountCent: 3000}
|
||||
amount, err := arbitrationDepositDeductAmountCent(order, checkout, ArbitrateRequest{Result: "actual_settlement"})
|
||||
if err != nil || amount != 3000 {
|
||||
t.Fatalf("omitted arbitration compensation = %d/%v, want 3000/nil", amount, err)
|
||||
}
|
||||
|
||||
zero := int64(0)
|
||||
amount, err = arbitrationDepositDeductAmountCent(order, checkout, ArbitrateRequest{Result: "actual_settlement", AmountCent: &zero})
|
||||
if err != nil || amount != 0 {
|
||||
t.Fatalf("explicit zero arbitration compensation = %d/%v, want 0/nil", amount, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckoutDisputeArbitrationReusesCheckoutCoinConsumedM(t *testing.T) {
|
||||
db := setupDisputeTestDB(t)
|
||||
repo := NewRepository(db, Dependencies{RefundStarter: RefundStarterFunc(func(context.Context, uint64, int64, string, string) (string, error) {
|
||||
@@ -417,11 +492,12 @@ func TestCheckoutDisputeArbitrationReusesCheckoutCoinConsumedM(t *testing.T) {
|
||||
inputOrder.SettlementStatus = "pending"
|
||||
_, renter, order := createDisputeOrderFixture(t, db, inputOrder)
|
||||
checkout := model.OrderCheckout{
|
||||
OrderID: order.ID,
|
||||
InitiatedBy: renter.ID,
|
||||
Status: "submitted",
|
||||
ConsumableAmountCent: 7000,
|
||||
CoinConsumedM: 25,
|
||||
OrderID: order.ID,
|
||||
InitiatedBy: renter.ID,
|
||||
Status: "submitted",
|
||||
ConsumableAmountCent: 7000,
|
||||
CoinConsumedM: 25,
|
||||
DepositDeductAmountCent: 3000,
|
||||
}
|
||||
if err := db.Create(&checkout).Error; err != nil {
|
||||
t.Fatalf("创建结账记录失败: %v", err)
|
||||
@@ -433,8 +509,8 @@ func TestCheckoutDisputeArbitrationReusesCheckoutCoinConsumedM(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("创建结账争议失败: %v", err)
|
||||
}
|
||||
if created.CheckoutID == nil || *created.CheckoutID != checkout.ID || created.CheckoutCoinConsumedM == nil || *created.CheckoutCoinConsumedM != 25 || created.CheckoutConsumableAmountCent == nil || *created.CheckoutConsumableAmountCent != 7000 {
|
||||
t.Fatalf("关联结账/M/额外物品 = %v/%v/%v, want %d/25/7000", created.CheckoutID, created.CheckoutCoinConsumedM, created.CheckoutConsumableAmountCent, checkout.ID)
|
||||
if created.CheckoutID == nil || *created.CheckoutID != checkout.ID || created.CheckoutCoinConsumedM == nil || *created.CheckoutCoinConsumedM != 25 || created.CheckoutConsumableAmountCent == nil || *created.CheckoutConsumableAmountCent != 7000 || created.CheckoutDepositDeductAmountCent == nil || *created.CheckoutDepositDeductAmountCent != 3000 {
|
||||
t.Fatalf("关联结账/M/额外物品/押金赔付 = %v/%v/%v/%v, want %d/25/7000/3000", created.CheckoutID, created.CheckoutCoinConsumedM, created.CheckoutConsumableAmountCent, created.CheckoutDepositDeductAmountCent, checkout.ID)
|
||||
}
|
||||
|
||||
if _, err := repo.Arbitrate(t.Context(), 77, created.ID, ArbitrateRequest{
|
||||
@@ -566,7 +642,7 @@ func TestPartialRefundReservesPlatformFee(t *testing.T) {
|
||||
|
||||
settlement, err := buildArbitrationSettlement(order, ArbitrateRequest{
|
||||
Result: "partial_refund",
|
||||
AmountCent: 5000,
|
||||
AmountCent: int64Ptr(5000),
|
||||
Remark: "没打完 号主登录不上",
|
||||
}, 0)
|
||||
if err != nil {
|
||||
@@ -599,7 +675,7 @@ func TestPartialRefundFullRentRetainedGivesOwnerFullOwnerRent(t *testing.T) {
|
||||
}
|
||||
settlement, err := buildArbitrationSettlement(order, ArbitrateRequest{
|
||||
Result: "partial_refund",
|
||||
AmountCent: 20000,
|
||||
AmountCent: int64Ptr(20000),
|
||||
}, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("buildArbitrationSettlement() error = %v", err)
|
||||
|
||||
@@ -93,6 +93,9 @@ func (s *Service) Arbitrate(ctx context.Context, adminID uint64, id uint64, req
|
||||
if req.ActualCoinConsumedM != nil && (*req.ActualCoinConsumedM < 0 || *req.ActualCoinConsumedM > 1_000_000_000) {
|
||||
return nil, ErrInvalidDispute
|
||||
}
|
||||
if req.AmountCent != nil && (*req.AmountCent < 0 || *req.AmountCent > 100_000_000_000) {
|
||||
return nil, ErrInvalidDispute
|
||||
}
|
||||
if req.ActualConsumableAmountCent != nil && (*req.ActualConsumableAmountCent < 0 || *req.ActualConsumableAmountCent > 100_000_000_000) {
|
||||
return nil, ErrInvalidDispute
|
||||
}
|
||||
|
||||
@@ -289,7 +289,7 @@ func refreshCheckoutSettlementDTO(dto *CheckoutDTO, order model.RentalOrder, che
|
||||
if checkout.Status != checkoutStatusSubmitted && checkout.Status != checkoutStatusCountered {
|
||||
return
|
||||
}
|
||||
settlement := calculateCheckoutSettlement(order, checkout.ConsumableAmountCent, checkout.CoinConsumedM, checkout.DepositDeductAmountCent)
|
||||
settlement := calculateCheckoutSettlement(order, checkout.ConsumableAmountCent, checkout.CoinConsumedM, effectiveCheckoutDepositDeductCent(checkout))
|
||||
rent := settlement.ActualRentAmountCent
|
||||
ownerRent := settlement.OwnerRentIncomeCent
|
||||
platform := settlement.PlatformFeeCent
|
||||
|
||||
@@ -278,7 +278,16 @@ func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, c
|
||||
}
|
||||
|
||||
func buildCheckoutSettlement(order model.RentalOrder, checkout *model.OrderCheckout) checkoutSettlement {
|
||||
return calculateCheckoutSettlement(order, checkout.ConsumableAmountCent, checkout.CoinConsumedM, checkout.DepositDeductAmountCent)
|
||||
return calculateCheckoutSettlement(order, checkout.ConsumableAmountCent, checkout.CoinConsumedM, effectiveCheckoutDepositDeductCent(*checkout))
|
||||
}
|
||||
|
||||
// effectiveCheckoutDepositDeductCent keeps legacy checkouts that only populated
|
||||
// other_amount_cent from losing their deposit compensation at finalization.
|
||||
func effectiveCheckoutDepositDeductCent(checkout model.OrderCheckout) int64 {
|
||||
if checkout.DepositDeductAmountCent > 0 {
|
||||
return checkout.DepositDeductAmountCent
|
||||
}
|
||||
return maxCent(checkout.OtherAmountCent, 0)
|
||||
}
|
||||
|
||||
func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent int64, coinConsumedM float64, depositDeductAmountCent int64) checkoutSettlement {
|
||||
|
||||
@@ -381,6 +381,20 @@ func TestBuildCheckoutSettlementWithDepositDeduct(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCheckoutSettlementFallsBackToLegacyOtherAmount(t *testing.T) {
|
||||
order := model.RentalOrder{
|
||||
RentAmountCent: 24000,
|
||||
OwnerRentAmountCent: 21600,
|
||||
DepositAmountCent: 5000,
|
||||
}
|
||||
checkout := &model.OrderCheckout{OtherAmountCent: 2000}
|
||||
|
||||
settlement := buildCheckoutSettlement(order, checkout)
|
||||
if settlement.DepositCompensationCent != 2000 || settlement.OwnerIncomeCent != 23600 {
|
||||
t.Fatalf("legacy other amount settlement = compensation %d, owner income %d, want 2000/23600", settlement.DepositCompensationCent, settlement.OwnerIncomeCent)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildCheckoutSettlementWithConsumable 测试额外消耗
|
||||
func TestBuildCheckoutSettlementWithConsumable(t *testing.T) {
|
||||
order := model.RentalOrder{
|
||||
|
||||
@@ -184,7 +184,7 @@ function openArbitration(row: Dispute) {
|
||||
activeDispute.value = row
|
||||
result.value = editableArbitrationResult(row.arbitration_result)
|
||||
remark.value = row.arbitration_remark || ''
|
||||
amount.value = undefined
|
||||
amount.value = centToYuan(row.checkout_deposit_deduct_amount_cent ?? 0)
|
||||
actualCoinConsumedM.value = row.checkout_coin_consumed_m ?? undefined
|
||||
actualConsumableAmountYuan.value = centToYuan(
|
||||
row.checkout_consumable_amount_cent ?? row.extra_item_original_amount_cent ?? 0
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface Dispute {
|
||||
checkout_id?: number
|
||||
checkout_coin_consumed_m?: number
|
||||
checkout_consumable_amount_cent?: number
|
||||
checkout_deposit_deduct_amount_cent?: number
|
||||
previous_checkout_status?: string
|
||||
arbitration_result: string
|
||||
arbitration_remark: string
|
||||
|
||||
Reference in New Issue
Block a user