package renewchange import ( "context" "database/sql" "errors" "fmt" "strings" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" rc "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/renewchange" ) const ( renewFinanceIncomeExpenseIncome = 0 renewFinanceTransactionTypeConsume = 1 renewFinanceTransactionChannelBalance = 3 renewFinanceTransactionStatusPaid = 1 ) type renewBalanceAccountRow struct { ID int64 `orm:"id"` Balance float64 `orm:"balance"` } type renewBalanceDetailRow struct { ID int64 `orm:"id"` DeptID int64 `orm:"dept_id"` SerialNo string `orm:"serial_no"` Amount float64 `orm:"amount"` TransactionStatus int `orm:"transaction_status"` TenantID string `orm:"tenant_id"` CreatedDept int64 `orm:"created_dept"` CreatedBy int64 `orm:"created_by"` } func refundRenewBalanceIfNeeded(ctx context.Context, tx gdb.TX, main *rc.BatchMainRow, contract *ExecutionContract, reason string, now *gtime.Time) error { if main == nil || contract == nil { return nil } if contract.PaymentMode != "balance" || strings.TrimSpace(contract.BalanceDetailSerialNo) == "" || contract.BalanceAmount <= 0 { return nil } if strings.TrimSpace(main.OrderSn) != "" { return nil } refundSerialNo := "REFUND" + strings.TrimSpace(contract.BalanceDetailSerialNo) var existing renewBalanceDetailRow existingRecord, err := tx.Ctx(ctx).Model(rc.TableBalanceDetail). Where("tenant_id", contract.TenantID). Where("serial_no", refundSerialNo). LockUpdate(). One() if err != nil && !errors.Is(err, sql.ErrNoRows) { return gerror.Wrap(err, "query renew refund detail failed") } if existingRecord != nil && !existingRecord.IsEmpty() { if err := existingRecord.Struct(&existing); err != nil { return gerror.Wrap(err, "decode renew refund detail failed") } } if existing.ID != 0 { return nil } var original renewBalanceDetailRow originalRecord, err := tx.Ctx(ctx).Model(rc.TableBalanceDetail). Where("tenant_id", contract.TenantID). Where("serial_no", strings.TrimSpace(contract.BalanceDetailSerialNo)). LockUpdate(). One() if err != nil && !errors.Is(err, sql.ErrNoRows) { return gerror.Wrap(err, "query renew payment detail failed") } if originalRecord != nil && !originalRecord.IsEmpty() { if err := originalRecord.Struct(&original); err != nil { return gerror.Wrap(err, "decode renew payment detail failed") } } if original.ID == 0 { g.Log().Warningf(ctx, "renew balance refund skipped, original payment detail missing, batchNo=%s serialNo=%s", main.BatchNo, contract.BalanceDetailSerialNo) return nil } payerDeptID := contract.PayerDeptID if payerDeptID <= 0 { payerDeptID = original.DeptID } var account renewBalanceAccountRow accountRecord, err := tx.Ctx(ctx).Model(rc.TableAgentBalance). Where("dept_id", payerDeptID). Where("tenant_id", contract.TenantID). LockUpdate(). One() if err != nil && !errors.Is(err, sql.ErrNoRows) { return gerror.Wrap(err, "lock renew refund account failed") } if accountRecord != nil && !accountRecord.IsEmpty() { if err := accountRecord.Struct(&account); err != nil { return gerror.Wrap(err, "decode renew refund account failed") } } if account.ID == 0 { return gerror.New("renew refund account missing") } if now == nil { now = gtime.Now() } amount := roundAmount(original.Amount) before := roundAmount(account.Balance) after := roundAmount(before + amount) if _, err := tx.Ctx(ctx).Model(rc.TableAgentBalance).Where("id", account.ID).Data(g.Map{ "balance": after, "updated_by": contract.OperatorUserID, "updated_at": now, }).Update(); err != nil { return gerror.Wrap(err, "refund renew balance failed") } remark := fmt.Sprintf("SIM卡续费批次失败退款,批次号:%s,原因:%s", main.BatchNo, strings.TrimSpace(reason)) if _, err := tx.Ctx(ctx).Model(rc.TableBalanceDetail).Data(g.Map{ "dept_id": payerDeptID, "serial_no": refundSerialNo, "transaction_time": now, "income_expense_type": renewFinanceIncomeExpenseIncome, "transaction_type": renewFinanceTransactionTypeConsume, "transaction_channel": renewFinanceTransactionChannelBalance, "transaction_status": renewFinanceTransactionStatusPaid, "amount": amount, "before_balance": before, "balance_after": after, "order_sn": main.BatchNo, "remark": remark, "tenant_id": contract.TenantID, "created_dept": original.CreatedDept, "created_by": original.CreatedBy, "created_at": now, }).Insert(); err != nil { return gerror.Wrap(err, "insert renew refund detail failed") } g.Log().Warningf(ctx, "renew balance refunded, batchNo=%s serialNo=%s refundSerialNo=%s amount=%.2f reason=%s", main.BatchNo, contract.BalanceDetailSerialNo, refundSerialNo, amount, reason) return nil } func renewRefundContractFromBatch(main *rc.BatchMainRow) *ExecutionContract { if main == nil || main.BatchInfo == nil || main.BatchInfo.IsNil() { return nil } info := main.BatchInfo contract := &ExecutionContract{ BatchNo: strings.TrimSpace(main.BatchNo), BatchType: main.BatchType, TenantID: strings.TrimSpace(main.TenantId), CustomerAgentID: firstInt64(info, "customerAgentId", "priceSnapshot.customerAgentId"), SellerAgentID: main.DeptId, CreatedDeptID: main.CreatedDept, OperatorUserID: main.CreatedBy, PaymentMode: firstString(info, "paymentMode", "paymentSnapshot.paymentMode"), PayType: firstInt(info, "payType", "paymentSnapshot.payType"), PayAmount: firstAmount(info, "payAmount", "paymentSnapshot.payAmount"), BalanceAmount: firstAmount(info, "balanceAmount", "paymentSnapshot.balanceAmount"), BalanceDetailSerialNo: firstString(info, "balanceDetailSerialNo", "paymentSnapshot.balanceDetailSerialNo"), PayerDeptID: firstInt64(info, "payerDeptId", "paymentSnapshot.payerDeptId"), PayerUserID: firstInt64(info, "payerUserId", "paymentSnapshot.payerUserId"), } contract.normalizePaymentContract() if contract.PaymentMode != "balance" || strings.TrimSpace(contract.BalanceDetailSerialNo) == "" || contract.BalanceAmount <= 0 { return nil } return contract }