合并仲裁实际消耗结算选项
This commit is contained in:
@@ -224,7 +224,7 @@ func buildArbitrationDecision(tx *gorm.DB, row model.Dispute, order model.Rental
|
|||||||
|
|
||||||
func arbitrationUsesActualCheckout(result string) bool {
|
func arbitrationUsesActualCheckout(result string) bool {
|
||||||
switch result {
|
switch result {
|
||||||
case "release_deposit", "deduct_deposit", "compensate_owner":
|
case "actual_settlement", "release_deposit", "deduct_deposit", "compensate_owner":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -301,6 +301,14 @@ func arbitrationConsumableAmountCent(order model.RentalOrder, checkout *model.Or
|
|||||||
|
|
||||||
func arbitrationDepositDeductAmountCent(order model.RentalOrder, req ArbitrateRequest) (int64, error) {
|
func arbitrationDepositDeductAmountCent(order model.RentalOrder, req ArbitrateRequest) (int64, error) {
|
||||||
switch req.Result {
|
switch req.Result {
|
||||||
|
case "actual_settlement":
|
||||||
|
if req.AmountCent <= 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if req.AmountCent > order.DepositAmountCent {
|
||||||
|
return 0, ErrInvalidDispute
|
||||||
|
}
|
||||||
|
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":
|
||||||
@@ -628,6 +636,8 @@ func buildArbitrationHandoffContent(req ArbitrateRequest, settlement arbitration
|
|||||||
|
|
||||||
func arbitrationResultLabel(result string) string {
|
func arbitrationResultLabel(result string) string {
|
||||||
switch result {
|
switch result {
|
||||||
|
case "actual_settlement":
|
||||||
|
return "按实际消耗结算"
|
||||||
case "full_refund":
|
case "full_refund":
|
||||||
return "全额退款"
|
return "全额退款"
|
||||||
case "partial_refund":
|
case "partial_refund":
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ func TestArbitrationCompletedOrderRequiresActualCoinConsumedM(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = repo.Arbitrate(t.Context(), 77, created.ID, ArbitrateRequest{
|
_, err = repo.Arbitrate(t.Context(), 77, created.ID, ArbitrateRequest{
|
||||||
Result: "release_deposit",
|
Result: "actual_settlement",
|
||||||
Remark: "确认完成订单",
|
Remark: "确认完成订单",
|
||||||
}, AuditMeta{})
|
}, AuditMeta{})
|
||||||
if !errors.Is(err, ErrInvalidDispute) {
|
if !errors.Is(err, ErrInvalidDispute) {
|
||||||
@@ -347,7 +347,7 @@ func TestArbitrationCompletedOrderRequiresActualCoinConsumedM(t *testing.T) {
|
|||||||
actualCoinConsumedM := 50.0
|
actualCoinConsumedM := 50.0
|
||||||
actualConsumableAmountCent := int64(12000)
|
actualConsumableAmountCent := int64(12000)
|
||||||
_, err = repo.Arbitrate(t.Context(), 77, created.ID, ArbitrateRequest{
|
_, err = repo.Arbitrate(t.Context(), 77, created.ID, ArbitrateRequest{
|
||||||
Result: "release_deposit",
|
Result: "actual_settlement",
|
||||||
Remark: "确认实际消耗 50M",
|
Remark: "确认实际消耗 50M",
|
||||||
ActualCoinConsumedM: &actualCoinConsumedM,
|
ActualCoinConsumedM: &actualCoinConsumedM,
|
||||||
}, AuditMeta{})
|
}, AuditMeta{})
|
||||||
@@ -356,7 +356,7 @@ func TestArbitrationCompletedOrderRequiresActualCoinConsumedM(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err := repo.Arbitrate(t.Context(), 77, created.ID, ArbitrateRequest{
|
if _, err := repo.Arbitrate(t.Context(), 77, created.ID, ArbitrateRequest{
|
||||||
Result: "release_deposit",
|
Result: "actual_settlement",
|
||||||
Remark: "确认实际消耗 50M 和额外物品",
|
Remark: "确认实际消耗 50M 和额外物品",
|
||||||
ActualCoinConsumedM: &actualCoinConsumedM,
|
ActualCoinConsumedM: &actualCoinConsumedM,
|
||||||
ActualConsumableAmountCent: &actualConsumableAmountCent,
|
ActualConsumableAmountCent: &actualConsumableAmountCent,
|
||||||
@@ -372,6 +372,40 @@ func TestArbitrationCompletedOrderRequiresActualCoinConsumedM(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestActualSettlementArbitrationUsesOwnerCompensationAmount(t *testing.T) {
|
||||||
|
order := disputePureCoinOrder()
|
||||||
|
actualCoinConsumedM := 50.0
|
||||||
|
actualConsumableAmountCent := int64(12000)
|
||||||
|
|
||||||
|
settlement, actualSettlement, _, depositDeductAmountCent, err := buildActualCheckoutArbitrationSettlement(nil, model.Dispute{}, order, ArbitrateRequest{
|
||||||
|
Result: "actual_settlement",
|
||||||
|
Remark: "按实际消耗结算",
|
||||||
|
ActualCoinConsumedM: &actualCoinConsumedM,
|
||||||
|
ActualConsumableAmountCent: &actualConsumableAmountCent,
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("不填押金赔付的实际结算失败: %v", err)
|
||||||
|
}
|
||||||
|
if depositDeductAmountCent != 0 || actualSettlement.DepositCompensationCent != 0 || settlement.DepositDeductAmountCent != 0 {
|
||||||
|
t.Fatalf("不填押金赔付不应默认扣全押金: input=%d actual=%d settlement=%d", depositDeductAmountCent, actualSettlement.DepositCompensationCent, settlement.DepositDeductAmountCent)
|
||||||
|
}
|
||||||
|
|
||||||
|
compensationCent := int64(3000)
|
||||||
|
settlement, actualSettlement, _, depositDeductAmountCent, err = buildActualCheckoutArbitrationSettlement(nil, model.Dispute{}, order, ArbitrateRequest{
|
||||||
|
Result: "actual_settlement",
|
||||||
|
Remark: "赔付号主",
|
||||||
|
AmountCent: compensationCent,
|
||||||
|
ActualCoinConsumedM: &actualCoinConsumedM,
|
||||||
|
ActualConsumableAmountCent: &actualConsumableAmountCent,
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("填写押金赔付的实际结算失败: %v", err)
|
||||||
|
}
|
||||||
|
if depositDeductAmountCent != compensationCent || actualSettlement.DepositCompensationCent != compensationCent || settlement.DepositDeductAmountCent != compensationCent {
|
||||||
|
t.Fatalf("押金赔付 = %d/%d/%d, want %d", depositDeductAmountCent, actualSettlement.DepositCompensationCent, settlement.DepositDeductAmountCent, compensationCent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ const submitting = ref(false)
|
|||||||
const disputes = ref<Dispute[]>([])
|
const disputes = ref<Dispute[]>([])
|
||||||
const activeDispute = ref<Dispute | null>(null)
|
const activeDispute = ref<Dispute | null>(null)
|
||||||
const evidenceDispute = ref<Dispute | null>(null)
|
const evidenceDispute = ref<Dispute | null>(null)
|
||||||
const result = ref('release_deposit')
|
const actualSettlementResult = 'actual_settlement'
|
||||||
|
const result = ref(actualSettlementResult)
|
||||||
const remark = ref('')
|
const remark = ref('')
|
||||||
const amount = ref<number | undefined>()
|
const amount = ref<number | undefined>()
|
||||||
const actualCoinConsumedM = ref<number | undefined>()
|
const actualCoinConsumedM = ref<number | undefined>()
|
||||||
@@ -34,9 +35,15 @@ const total = ref(0)
|
|||||||
const keywordFilter = ref('')
|
const keywordFilter = ref('')
|
||||||
const statusFilter = ref('')
|
const statusFilter = ref('')
|
||||||
const typeFilter = ref('')
|
const typeFilter = ref('')
|
||||||
const isPartialRefund = computed(() => result.value === 'partial_refund')
|
|
||||||
const isCompletedResult = computed(() =>
|
const isCompletedResult = computed(() =>
|
||||||
['deduct_deposit', 'release_deposit', 'compensate_owner'].includes(result.value)
|
[actualSettlementResult, 'deduct_deposit', 'release_deposit', 'compensate_owner'].includes(
|
||||||
|
result.value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const hasValidOwnerCompensation = computed(
|
||||||
|
() =>
|
||||||
|
amount.value === undefined ||
|
||||||
|
(typeof amount.value === 'number' && Number.isFinite(amount.value) && amount.value >= 0)
|
||||||
)
|
)
|
||||||
const hasValidActualCoinConsumedM = computed(
|
const hasValidActualCoinConsumedM = computed(
|
||||||
() =>
|
() =>
|
||||||
@@ -62,7 +69,7 @@ const cancelledCount = computed(
|
|||||||
const canSubmitArbitration = computed(() => {
|
const canSubmitArbitration = computed(() => {
|
||||||
if (submitting.value || !activeDispute.value) return false
|
if (submitting.value || !activeDispute.value) return false
|
||||||
if (!result.value || !remark.value.trim()) return false
|
if (!result.value || !remark.value.trim()) return false
|
||||||
if (isPartialRefund.value && (!amount.value || amount.value <= 0)) return false
|
if (isCompletedResult.value && !hasValidOwnerCompensation.value) return false
|
||||||
if (
|
if (
|
||||||
isCompletedResult.value &&
|
isCompletedResult.value &&
|
||||||
!activeDispute.value.checkout_id &&
|
!activeDispute.value.checkout_id &&
|
||||||
@@ -93,6 +100,7 @@ function disputeTypeLabel(type: string) {
|
|||||||
|
|
||||||
function arbitrationResultLabel(value: string) {
|
function arbitrationResultLabel(value: string) {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
|
actual_settlement: '按实际消耗结算',
|
||||||
full_refund: '全额退款',
|
full_refund: '全额退款',
|
||||||
partial_refund: '部分退款',
|
partial_refund: '部分退款',
|
||||||
deduct_deposit: '扣押金',
|
deduct_deposit: '扣押金',
|
||||||
@@ -104,6 +112,13 @@ function arbitrationResultLabel(value: string) {
|
|||||||
return map[value] || value || '-'
|
return map[value] || value || '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function editableArbitrationResult(value: string) {
|
||||||
|
if (['partial_refund', 'deduct_deposit', 'release_deposit', 'compensate_owner'].includes(value)) {
|
||||||
|
return actualSettlementResult
|
||||||
|
}
|
||||||
|
return value || actualSettlementResult
|
||||||
|
}
|
||||||
|
|
||||||
function disputeStatusTone(status: string) {
|
function disputeStatusTone(status: string) {
|
||||||
const map: Record<string, 'success' | 'warning' | 'info' | 'danger'> = {
|
const map: Record<string, 'success' | 'warning' | 'info' | 'danger'> = {
|
||||||
open: 'warning',
|
open: 'warning',
|
||||||
@@ -167,7 +182,7 @@ async function queryDisputes() {
|
|||||||
function openArbitration(row: Dispute) {
|
function openArbitration(row: Dispute) {
|
||||||
if (!canArbitrate(row)) return
|
if (!canArbitrate(row)) return
|
||||||
activeDispute.value = row
|
activeDispute.value = row
|
||||||
result.value = row.arbitration_result || 'release_deposit'
|
result.value = editableArbitrationResult(row.arbitration_result)
|
||||||
remark.value = row.arbitration_remark || ''
|
remark.value = row.arbitration_remark || ''
|
||||||
amount.value = undefined
|
amount.value = undefined
|
||||||
actualCoinConsumedM.value = row.checkout_coin_consumed_m ?? undefined
|
actualCoinConsumedM.value = row.checkout_coin_consumed_m ?? undefined
|
||||||
@@ -300,8 +315,8 @@ async function handleArbitrate() {
|
|||||||
ElMessage.warning('请填写客服裁决说明')
|
ElMessage.warning('请填写客服裁决说明')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (isPartialRefund.value && (!amount.value || amount.value <= 0)) {
|
if (isCompletedResult.value && !hasValidOwnerCompensation.value) {
|
||||||
ElMessage.warning('请填写部分退款金额')
|
ElMessage.warning('请填写有效的押金赔付金额')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -321,7 +336,8 @@ async function handleArbitrate() {
|
|||||||
await arbitrateDispute(activeDispute.value.id, {
|
await arbitrateDispute(activeDispute.value.id, {
|
||||||
result: result.value,
|
result: result.value,
|
||||||
remark: remark.value.trim(),
|
remark: remark.value.trim(),
|
||||||
amount_cent: amount.value ? yuanToCent(amount.value) : undefined,
|
amount_cent:
|
||||||
|
isCompletedResult.value && amount.value !== undefined ? yuanToCent(amount.value) : undefined,
|
||||||
actual_coin_consumed_m: isCompletedResult.value ? actualCoinConsumedM.value : undefined,
|
actual_coin_consumed_m: isCompletedResult.value ? actualCoinConsumedM.value : undefined,
|
||||||
actual_consumable_amount_cent: isCompletedResult.value
|
actual_consumable_amount_cent: isCompletedResult.value
|
||||||
? yuanToCent(actualConsumableAmountYuan.value)
|
? yuanToCent(actualConsumableAmountYuan.value)
|
||||||
@@ -512,27 +528,23 @@ async function handleArbitrate() {
|
|||||||
<p>{{ activeDispute.description }}</p>
|
<p>{{ activeDispute.description }}</p>
|
||||||
<el-select v-model="result" class="full-control" placeholder="选择裁决结果">
|
<el-select v-model="result" class="full-control" placeholder="选择裁决结果">
|
||||||
<el-option label="全额退款" value="full_refund" />
|
<el-option label="全额退款" value="full_refund" />
|
||||||
<el-option label="部分退款" value="partial_refund" />
|
<el-option label="按实际消耗结算" :value="actualSettlementResult" />
|
||||||
<el-option label="扣押金" value="deduct_deposit" />
|
|
||||||
<el-option label="释放押金" value="release_deposit" />
|
|
||||||
<el-option label="赔付号主" value="compensate_owner" />
|
|
||||||
<el-option label="关闭订单" value="order_close" />
|
<el-option label="关闭订单" value="order_close" />
|
||||||
<el-option label="标记异常" value="mark_abnormal" />
|
<el-option label="标记异常" value="mark_abnormal" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-input-number
|
<div v-if="isCompletedResult" class="arbitration-field">
|
||||||
v-if="['partial_refund', 'deduct_deposit', 'compensate_owner'].includes(result)"
|
<span>押金赔付给号主(元)</span>
|
||||||
v-model="amount"
|
<el-input-number
|
||||||
class="full-control panel-action"
|
v-model="amount"
|
||||||
:min="0"
|
class="full-control"
|
||||||
:precision="0"
|
:min="0"
|
||||||
:step="10"
|
:precision="2"
|
||||||
placeholder="裁决金额"
|
:step="10"
|
||||||
/>
|
placeholder="押金赔付给号主金额"
|
||||||
<p v-if="result === 'partial_refund'">
|
/>
|
||||||
部分退款金额表示退给租客的金额,剩余冻结金额结算给号主。
|
</div>
|
||||||
</p>
|
<p v-if="isCompletedResult">
|
||||||
<p v-if="['deduct_deposit', 'compensate_owner'].includes(result)">
|
金额表示从押金中赔付给号主的部分;不填或填 0 表示不扣押金赔付。
|
||||||
金额表示从押金中赔付给号主的部分;不填则默认处理全额押金。
|
|
||||||
</p>
|
</p>
|
||||||
<div v-if="isCompletedResult" class="arbitration-field">
|
<div v-if="isCompletedResult" class="arbitration-field">
|
||||||
<span>最终实际哈夫币消耗(M)</span>
|
<span>最终实际哈夫币消耗(M)</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user