修复代管订单押金赔付待打款金额
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,
|
"order_no": order.OrderNo,
|
||||||
"result": req.Result,
|
"result": req.Result,
|
||||||
"remark": req.Remark,
|
"remark": req.Remark,
|
||||||
"input_amount_cent": req.AmountCent,
|
"input_amount_cent": arbitrationInputAmountCent(req),
|
||||||
"renter_refund_amount_cent": settlement.RenterRefundAmountCent,
|
"renter_refund_amount_cent": settlement.RenterRefundAmountCent,
|
||||||
"owner_income_amount_cent": settlement.OwnerIncomeAmountCent,
|
"owner_income_amount_cent": settlement.OwnerIncomeAmountCent,
|
||||||
"deposit_deduct_amount_cent": settlement.DepositDeductAmountCent,
|
"deposit_deduct_amount_cent": settlement.DepositDeductAmountCent,
|
||||||
@@ -245,7 +245,7 @@ func buildActualCheckoutArbitrationSettlement(tx *gorm.DB, row model.Dispute, or
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return settlement, nil, linkedCheckout, 0, err
|
return settlement, nil, linkedCheckout, 0, err
|
||||||
}
|
}
|
||||||
depositDeductAmountCent, err := arbitrationDepositDeductAmountCent(order, req)
|
depositDeductAmountCent, err := arbitrationDepositDeductAmountCent(order, linkedCheckout, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settlement, nil, linkedCheckout, 0, err
|
return settlement, nil, linkedCheckout, 0, err
|
||||||
}
|
}
|
||||||
@@ -299,21 +299,24 @@ func arbitrationConsumableAmountCent(order model.RentalOrder, checkout *model.Or
|
|||||||
return 0, ErrInvalidDispute
|
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 {
|
switch req.Result {
|
||||||
case "actual_settlement":
|
case "actual_settlement":
|
||||||
if req.AmountCent <= 0 {
|
if req.AmountCent == nil {
|
||||||
|
if checkout != nil {
|
||||||
|
return arbitrationCheckoutDepositDeductCent(*checkout), nil
|
||||||
|
}
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
if req.AmountCent > order.DepositAmountCent {
|
if *req.AmountCent < 0 || *req.AmountCent > order.DepositAmountCent {
|
||||||
return 0, ErrInvalidDispute
|
return 0, ErrInvalidDispute
|
||||||
}
|
}
|
||||||
return req.AmountCent, nil
|
return *req.AmountCent, nil
|
||||||
case "release_deposit":
|
case "release_deposit":
|
||||||
return 0, nil
|
return 0, nil
|
||||||
case "deduct_deposit", "compensate_owner":
|
case "deduct_deposit", "compensate_owner":
|
||||||
deductAmountCent := req.AmountCent
|
deductAmountCent := arbitrationInputAmountCent(req)
|
||||||
if deductAmountCent <= 0 {
|
if req.AmountCent == nil || deductAmountCent <= 0 {
|
||||||
deductAmountCent = order.DepositAmountCent
|
deductAmountCent = order.DepositAmountCent
|
||||||
}
|
}
|
||||||
if 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) {
|
func applyArbitrationActualSettlement(order *model.RentalOrder, checkout *model.OrderCheckout, settlement ordermodule.ActualCheckoutSettlement, depositDeductAmountCent int64) {
|
||||||
order.ActualCoinConsumedM = settlement.CoinConsumedM
|
order.ActualCoinConsumedM = settlement.CoinConsumedM
|
||||||
order.ActualPureCoinAmountCent = settlement.PureCoinAmountCent
|
order.ActualPureCoinAmountCent = settlement.PureCoinAmountCent
|
||||||
@@ -350,7 +370,7 @@ func applyArbitrationActualSettlement(order *model.RentalOrder, checkout *model.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isPlatformManagedOrder(order model.RentalOrder) bool {
|
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 {
|
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, "仲裁全额退款")
|
addRenterRefund(totalCent, "仲裁全额退款")
|
||||||
settlement.RenterDepositRefundCent = depositAmountCent
|
settlement.RenterDepositRefundCent = depositAmountCent
|
||||||
case "partial_refund":
|
case "partial_refund":
|
||||||
if req.AmountCent <= 0 || req.AmountCent > totalCent {
|
if req.AmountCent == nil || *req.AmountCent <= 0 || *req.AmountCent > totalCent {
|
||||||
return settlement, ErrInvalidDispute
|
return settlement, ErrInvalidDispute
|
||||||
}
|
}
|
||||||
addRenterRefund(req.AmountCent, "仲裁部分退款")
|
addRenterRefund(*req.AmountCent, "仲裁部分退款")
|
||||||
// 部分退款为合并金额,无法精确拆分租金/押金,按押金优先归类以便暂扣。
|
// 部分退款为合并金额,无法精确拆分租金/押金,按押金优先归类以便暂扣。
|
||||||
settlement.RenterDepositRefundCent = money.MinCent(req.AmountCent, depositAmountCent)
|
settlement.RenterDepositRefundCent = money.MinCent(*req.AmountCent, depositAmountCent)
|
||||||
// 号主仅拿「未退租金中的号主份额 + 未退押金」,平台加价按未退租金比例预留,避免整笔剩余进号主。
|
// 号主仅拿「未退租金中的号主份额 + 未退押金」,平台加价按未退租金比例预留,避免整笔剩余进号主。
|
||||||
addOwnerIncome(
|
addOwnerIncome(
|
||||||
partialRefundOwnerIncomeCent(rentAmountCent, ownerRentAmountCent, depositAmountCent, req.AmountCent),
|
partialRefundOwnerIncomeCent(rentAmountCent, ownerRentAmountCent, depositAmountCent, *req.AmountCent),
|
||||||
"仲裁剩余金额结算给号主",
|
"仲裁剩余金额结算给号主",
|
||||||
)
|
)
|
||||||
case "release_deposit":
|
case "release_deposit":
|
||||||
@@ -475,8 +495,8 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest, r
|
|||||||
addRenterRefund(depositAmountCent, "仲裁释放押金给租客")
|
addRenterRefund(depositAmountCent, "仲裁释放押金给租客")
|
||||||
settlement.RenterDepositRefundCent = depositAmountCent
|
settlement.RenterDepositRefundCent = depositAmountCent
|
||||||
case "deduct_deposit", "compensate_owner":
|
case "deduct_deposit", "compensate_owner":
|
||||||
deductAmountCent := req.AmountCent
|
deductAmountCent := arbitrationInputAmountCent(req)
|
||||||
if deductAmountCent <= 0 {
|
if req.AmountCent == nil || deductAmountCent <= 0 {
|
||||||
deductAmountCent = depositAmountCent
|
deductAmountCent = depositAmountCent
|
||||||
}
|
}
|
||||||
if deductAmountCent > depositAmountCent {
|
if deductAmountCent > depositAmountCent {
|
||||||
|
|||||||
@@ -9,40 +9,41 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DisputeDTO struct {
|
type DisputeDTO struct {
|
||||||
ID uint64 `json:"id"`
|
ID uint64 `json:"id"`
|
||||||
OrderID uint64 `json:"order_id"`
|
OrderID uint64 `json:"order_id"`
|
||||||
OrderNo string `json:"order_no"`
|
OrderNo string `json:"order_no"`
|
||||||
OrderStatus string `json:"order_status"`
|
OrderStatus string `json:"order_status"`
|
||||||
HandoffStatus string `json:"handoff_status"`
|
HandoffStatus string `json:"handoff_status"`
|
||||||
SettlementStatus string `json:"settlement_status"`
|
SettlementStatus string `json:"settlement_status"`
|
||||||
ExtraItemOriginalAmountCent int64 `json:"extra_item_original_amount_cent,omitempty"`
|
ExtraItemOriginalAmountCent int64 `json:"extra_item_original_amount_cent,omitempty"`
|
||||||
ListingNo string `json:"listing_no"`
|
ListingNo string `json:"listing_no"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
OwnerID uint64 `json:"owner_id"`
|
OwnerID uint64 `json:"owner_id"`
|
||||||
RenterID uint64 `json:"renter_id"`
|
RenterID uint64 `json:"renter_id"`
|
||||||
OwnerPhone string `json:"owner_phone"`
|
OwnerPhone string `json:"owner_phone"`
|
||||||
RenterPhone string `json:"renter_phone"`
|
RenterPhone string `json:"renter_phone"`
|
||||||
InitiatorID uint64 `json:"initiator_id"`
|
InitiatorID uint64 `json:"initiator_id"`
|
||||||
InitiatorType string `json:"initiator_type"`
|
InitiatorType string `json:"initiator_type"`
|
||||||
InitiatorAdminID *uint64 `json:"initiator_admin_id,omitempty"`
|
InitiatorAdminID *uint64 `json:"initiator_admin_id,omitempty"`
|
||||||
TargetUserID uint64 `json:"target_user_id"`
|
TargetUserID uint64 `json:"target_user_id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
EvidenceURLS datatypes.JSON `json:"evidence_urls"`
|
EvidenceURLS datatypes.JSON `json:"evidence_urls"`
|
||||||
PreviousOrderStatus string `json:"previous_order_status"`
|
PreviousOrderStatus string `json:"previous_order_status"`
|
||||||
PreviousHandoffStatus string `json:"previous_handoff_status"`
|
PreviousHandoffStatus string `json:"previous_handoff_status"`
|
||||||
PreviousSettlementStatus string `json:"previous_settlement_status"`
|
PreviousSettlementStatus string `json:"previous_settlement_status"`
|
||||||
CheckoutID *uint64 `json:"checkout_id"`
|
CheckoutID *uint64 `json:"checkout_id"`
|
||||||
CheckoutCoinConsumedM *float64 `json:"checkout_coin_consumed_m,omitempty"`
|
CheckoutCoinConsumedM *float64 `json:"checkout_coin_consumed_m,omitempty"`
|
||||||
CheckoutConsumableAmountCent *int64 `json:"checkout_consumable_amount_cent,omitempty"`
|
CheckoutConsumableAmountCent *int64 `json:"checkout_consumable_amount_cent,omitempty"`
|
||||||
PreviousCheckoutStatus string `json:"previous_checkout_status"`
|
CheckoutDepositDeductAmountCent *int64 `json:"checkout_deposit_deduct_amount_cent,omitempty"`
|
||||||
ArbitrationResult string `json:"arbitration_result"`
|
PreviousCheckoutStatus string `json:"previous_checkout_status"`
|
||||||
ArbitrationRemark string `json:"arbitration_remark"`
|
ArbitrationResult string `json:"arbitration_result"`
|
||||||
HandledBy *uint64 `json:"handled_by"`
|
ArbitrationRemark string `json:"arbitration_remark"`
|
||||||
HandledAt *time.Time `json:"handled_at"`
|
HandledBy *uint64 `json:"handled_by"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
HandledAt *time.Time `json:"handled_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateRequest struct {
|
type CreateRequest struct {
|
||||||
@@ -61,7 +62,7 @@ type AdminCreateRequest struct {
|
|||||||
type ArbitrateRequest struct {
|
type ArbitrateRequest struct {
|
||||||
Result string `json:"result" binding:"required"`
|
Result string `json:"result" binding:"required"`
|
||||||
Remark string `json:"remark" 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"`
|
ActualCoinConsumedM *float64 `json:"actual_coin_consumed_m"`
|
||||||
ActualConsumableAmountCent *int64 `json:"actual_consumable_amount_cent"`
|
ActualConsumableAmountCent *int64 `json:"actual_consumable_amount_cent"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,57 +10,59 @@ import (
|
|||||||
|
|
||||||
type disputeRow struct {
|
type disputeRow struct {
|
||||||
model.Dispute
|
model.Dispute
|
||||||
OrderNo string
|
OrderNo string
|
||||||
OrderStatus string
|
OrderStatus string
|
||||||
HandoffStatus string
|
HandoffStatus string
|
||||||
SettlementStatus string
|
SettlementStatus string
|
||||||
ExtraItemOriginalAmountCent int64
|
ExtraItemOriginalAmountCent int64
|
||||||
ListingNo string
|
ListingNo string
|
||||||
Title string
|
Title string
|
||||||
OwnerID uint64
|
OwnerID uint64
|
||||||
RenterID uint64
|
RenterID uint64
|
||||||
OwnerPhone string
|
OwnerPhone string
|
||||||
RenterPhone string
|
RenterPhone string
|
||||||
CheckoutCoinConsumedM *float64
|
CheckoutCoinConsumedM *float64
|
||||||
CheckoutConsumableAmountCent *int64
|
CheckoutConsumableAmountCent *int64
|
||||||
|
CheckoutDepositDeductAmountCent *int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (row disputeRow) toDTO() DisputeDTO {
|
func (row disputeRow) toDTO() DisputeDTO {
|
||||||
return DisputeDTO{
|
return DisputeDTO{
|
||||||
ID: row.ID,
|
ID: row.ID,
|
||||||
OrderID: row.OrderID,
|
OrderID: row.OrderID,
|
||||||
OrderNo: row.OrderNo,
|
OrderNo: row.OrderNo,
|
||||||
OrderStatus: row.OrderStatus,
|
OrderStatus: row.OrderStatus,
|
||||||
HandoffStatus: row.HandoffStatus,
|
HandoffStatus: row.HandoffStatus,
|
||||||
SettlementStatus: row.SettlementStatus,
|
SettlementStatus: row.SettlementStatus,
|
||||||
ExtraItemOriginalAmountCent: row.ExtraItemOriginalAmountCent,
|
ExtraItemOriginalAmountCent: row.ExtraItemOriginalAmountCent,
|
||||||
ListingNo: row.ListingNo,
|
ListingNo: row.ListingNo,
|
||||||
Title: row.Title,
|
Title: row.Title,
|
||||||
OwnerID: row.OwnerID,
|
OwnerID: row.OwnerID,
|
||||||
RenterID: row.RenterID,
|
RenterID: row.RenterID,
|
||||||
OwnerPhone: row.OwnerPhone,
|
OwnerPhone: row.OwnerPhone,
|
||||||
RenterPhone: row.RenterPhone,
|
RenterPhone: row.RenterPhone,
|
||||||
InitiatorID: row.InitiatorID,
|
InitiatorID: row.InitiatorID,
|
||||||
InitiatorType: effectiveInitiatorType(row.Dispute),
|
InitiatorType: effectiveInitiatorType(row.Dispute),
|
||||||
InitiatorAdminID: row.InitiatorAdminID,
|
InitiatorAdminID: row.InitiatorAdminID,
|
||||||
TargetUserID: row.TargetUserID,
|
TargetUserID: row.TargetUserID,
|
||||||
Type: row.Type,
|
Type: row.Type,
|
||||||
Status: row.Status,
|
Status: row.Status,
|
||||||
Description: row.Description,
|
Description: row.Description,
|
||||||
EvidenceURLS: row.EvidenceURLS,
|
EvidenceURLS: row.EvidenceURLS,
|
||||||
PreviousOrderStatus: row.PreviousOrderStatus,
|
PreviousOrderStatus: row.PreviousOrderStatus,
|
||||||
PreviousHandoffStatus: row.PreviousHandoffStatus,
|
PreviousHandoffStatus: row.PreviousHandoffStatus,
|
||||||
PreviousSettlementStatus: row.PreviousSettlementStatus,
|
PreviousSettlementStatus: row.PreviousSettlementStatus,
|
||||||
CheckoutID: row.CheckoutID,
|
CheckoutID: row.CheckoutID,
|
||||||
CheckoutCoinConsumedM: row.CheckoutCoinConsumedM,
|
CheckoutCoinConsumedM: row.CheckoutCoinConsumedM,
|
||||||
CheckoutConsumableAmountCent: row.CheckoutConsumableAmountCent,
|
CheckoutConsumableAmountCent: row.CheckoutConsumableAmountCent,
|
||||||
PreviousCheckoutStatus: row.PreviousCheckoutStatus,
|
CheckoutDepositDeductAmountCent: row.CheckoutDepositDeductAmountCent,
|
||||||
ArbitrationResult: row.ArbitrationResult,
|
PreviousCheckoutStatus: row.PreviousCheckoutStatus,
|
||||||
ArbitrationRemark: row.ArbitrationRemark,
|
ArbitrationResult: row.ArbitrationResult,
|
||||||
HandledBy: row.HandledBy,
|
ArbitrationRemark: row.ArbitrationRemark,
|
||||||
HandledAt: row.HandledAt,
|
HandledBy: row.HandledBy,
|
||||||
CreatedAt: row.CreatedAt,
|
HandledAt: row.HandledAt,
|
||||||
UpdatedAt: row.UpdatedAt,
|
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,
|
o.owner_id, o.renter_id, o.extra_item_original_amount_cent,
|
||||||
owner.phone AS owner_phone, renter.phone AS renter_phone,
|
owner.phone AS owner_phone, renter.phone AS renter_phone,
|
||||||
l.listing_no, a.title, c.coin_consumed_m AS checkout_coin_consumed_m,
|
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 {
|
func (r *Repository) adminFilterQuery(ctx context.Context) *gorm.DB {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func int64Ptr(value int64) *int64 { return &value }
|
||||||
|
|
||||||
func disputePureCoinOrder() model.RentalOrder {
|
func disputePureCoinOrder() model.RentalOrder {
|
||||||
return model.RentalOrder{
|
return model.RentalOrder{
|
||||||
Status: "renting",
|
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) {
|
func TestArbitrationCompletedOrderRequiresActualCoinConsumedM(t *testing.T) {
|
||||||
db := setupDisputeTestDB(t)
|
db := setupDisputeTestDB(t)
|
||||||
repo := NewRepository(db, Dependencies{RefundStarter: RefundStarterFunc(func(context.Context, uint64, int64, string, string) (string, error) {
|
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{
|
settlement, actualSettlement, _, depositDeductAmountCent, err = buildActualCheckoutArbitrationSettlement(nil, model.Dispute{}, order, ArbitrateRequest{
|
||||||
Result: "actual_settlement",
|
Result: "actual_settlement",
|
||||||
Remark: "赔付号主",
|
Remark: "赔付号主",
|
||||||
AmountCent: compensationCent,
|
AmountCent: &compensationCent,
|
||||||
ActualCoinConsumedM: &actualCoinConsumedM,
|
ActualCoinConsumedM: &actualCoinConsumedM,
|
||||||
ActualConsumableAmountCent: &actualConsumableAmountCent,
|
ActualConsumableAmountCent: &actualConsumableAmountCent,
|
||||||
}, 0)
|
}, 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) {
|
func TestCheckoutDisputeArbitrationReusesCheckoutCoinConsumedM(t *testing.T) {
|
||||||
db := setupDisputeTestDB(t)
|
db := setupDisputeTestDB(t)
|
||||||
repo := NewRepository(db, Dependencies{RefundStarter: RefundStarterFunc(func(context.Context, uint64, int64, string, string) (string, error) {
|
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"
|
inputOrder.SettlementStatus = "pending"
|
||||||
_, renter, order := createDisputeOrderFixture(t, db, inputOrder)
|
_, renter, order := createDisputeOrderFixture(t, db, inputOrder)
|
||||||
checkout := model.OrderCheckout{
|
checkout := model.OrderCheckout{
|
||||||
OrderID: order.ID,
|
OrderID: order.ID,
|
||||||
InitiatedBy: renter.ID,
|
InitiatedBy: renter.ID,
|
||||||
Status: "submitted",
|
Status: "submitted",
|
||||||
ConsumableAmountCent: 7000,
|
ConsumableAmountCent: 7000,
|
||||||
CoinConsumedM: 25,
|
CoinConsumedM: 25,
|
||||||
|
DepositDeductAmountCent: 3000,
|
||||||
}
|
}
|
||||||
if err := db.Create(&checkout).Error; err != nil {
|
if err := db.Create(&checkout).Error; err != nil {
|
||||||
t.Fatalf("创建结账记录失败: %v", err)
|
t.Fatalf("创建结账记录失败: %v", err)
|
||||||
@@ -433,8 +509,8 @@ func TestCheckoutDisputeArbitrationReusesCheckoutCoinConsumedM(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("创建结账争议失败: %v", err)
|
t.Fatalf("创建结账争议失败: %v", err)
|
||||||
}
|
}
|
||||||
if created.CheckoutID == nil || *created.CheckoutID != checkout.ID || created.CheckoutCoinConsumedM == nil || *created.CheckoutCoinConsumedM != 25 || created.CheckoutConsumableAmountCent == nil || *created.CheckoutConsumableAmountCent != 7000 {
|
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, want %d/25/7000", created.CheckoutID, created.CheckoutCoinConsumedM, created.CheckoutConsumableAmountCent, checkout.ID)
|
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{
|
if _, err := repo.Arbitrate(t.Context(), 77, created.ID, ArbitrateRequest{
|
||||||
@@ -566,7 +642,7 @@ func TestPartialRefundReservesPlatformFee(t *testing.T) {
|
|||||||
|
|
||||||
settlement, err := buildArbitrationSettlement(order, ArbitrateRequest{
|
settlement, err := buildArbitrationSettlement(order, ArbitrateRequest{
|
||||||
Result: "partial_refund",
|
Result: "partial_refund",
|
||||||
AmountCent: 5000,
|
AmountCent: int64Ptr(5000),
|
||||||
Remark: "没打完 号主登录不上",
|
Remark: "没打完 号主登录不上",
|
||||||
}, 0)
|
}, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -599,7 +675,7 @@ func TestPartialRefundFullRentRetainedGivesOwnerFullOwnerRent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
settlement, err := buildArbitrationSettlement(order, ArbitrateRequest{
|
settlement, err := buildArbitrationSettlement(order, ArbitrateRequest{
|
||||||
Result: "partial_refund",
|
Result: "partial_refund",
|
||||||
AmountCent: 20000,
|
AmountCent: int64Ptr(20000),
|
||||||
}, 0)
|
}, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("buildArbitrationSettlement() error = %v", err)
|
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) {
|
if req.ActualCoinConsumedM != nil && (*req.ActualCoinConsumedM < 0 || *req.ActualCoinConsumedM > 1_000_000_000) {
|
||||||
return nil, ErrInvalidDispute
|
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) {
|
if req.ActualConsumableAmountCent != nil && (*req.ActualConsumableAmountCent < 0 || *req.ActualConsumableAmountCent > 100_000_000_000) {
|
||||||
return nil, ErrInvalidDispute
|
return nil, ErrInvalidDispute
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ func refreshCheckoutSettlementDTO(dto *CheckoutDTO, order model.RentalOrder, che
|
|||||||
if checkout.Status != checkoutStatusSubmitted && checkout.Status != checkoutStatusCountered {
|
if checkout.Status != checkoutStatusSubmitted && checkout.Status != checkoutStatusCountered {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
settlement := calculateCheckoutSettlement(order, checkout.ConsumableAmountCent, checkout.CoinConsumedM, checkout.DepositDeductAmountCent)
|
settlement := calculateCheckoutSettlement(order, checkout.ConsumableAmountCent, checkout.CoinConsumedM, effectiveCheckoutDepositDeductCent(checkout))
|
||||||
rent := settlement.ActualRentAmountCent
|
rent := settlement.ActualRentAmountCent
|
||||||
ownerRent := settlement.OwnerRentIncomeCent
|
ownerRent := settlement.OwnerRentIncomeCent
|
||||||
platform := settlement.PlatformFeeCent
|
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 {
|
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 {
|
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 测试额外消耗
|
// TestBuildCheckoutSettlementWithConsumable 测试额外消耗
|
||||||
func TestBuildCheckoutSettlementWithConsumable(t *testing.T) {
|
func TestBuildCheckoutSettlementWithConsumable(t *testing.T) {
|
||||||
order := model.RentalOrder{
|
order := model.RentalOrder{
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ function openArbitration(row: Dispute) {
|
|||||||
activeDispute.value = row
|
activeDispute.value = row
|
||||||
result.value = editableArbitrationResult(row.arbitration_result)
|
result.value = editableArbitrationResult(row.arbitration_result)
|
||||||
remark.value = row.arbitration_remark || ''
|
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
|
actualCoinConsumedM.value = row.checkout_coin_consumed_m ?? undefined
|
||||||
actualConsumableAmountYuan.value = centToYuan(
|
actualConsumableAmountYuan.value = centToYuan(
|
||||||
row.checkout_consumable_amount_cent ?? row.extra_item_original_amount_cent ?? 0
|
row.checkout_consumable_amount_cent ?? row.extra_item_original_amount_cent ?? 0
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export interface Dispute {
|
|||||||
checkout_id?: number
|
checkout_id?: number
|
||||||
checkout_coin_consumed_m?: number
|
checkout_coin_consumed_m?: number
|
||||||
checkout_consumable_amount_cent?: number
|
checkout_consumable_amount_cent?: number
|
||||||
|
checkout_deposit_deduct_amount_cent?: number
|
||||||
previous_checkout_status?: string
|
previous_checkout_status?: string
|
||||||
arbitration_result: string
|
arbitration_result: string
|
||||||
arbitration_remark: string
|
arbitration_remark: string
|
||||||
|
|||||||
Reference in New Issue
Block a user