diff --git a/enum/fin_user_flow.go b/enum/fin_user_flow.go index c2aa06b..b9d4660 100644 --- a/enum/fin_user_flow.go +++ b/enum/fin_user_flow.go @@ -18,6 +18,8 @@ func FinUserFlowOrderActionString(kind int) string { return "green_coin_double_chain_exchange_for_balance" case md.StoreSubsidyOrdActionForFinUserFlow: return "subsidy" + case md.InstallmentPaymentAutoRepaidForFinUserFlow: + return "installment_payment_auto_repaid" default: return "unknown" } diff --git a/md/fin_user_flow.go b/md/fin_user_flow.go index dac1ba7..88dad8a 100644 --- a/md/fin_user_flow.go +++ b/md/fin_user_flow.go @@ -19,6 +19,7 @@ const ( GreenEnergyExchangeForBalanceTitleForFinUserFlow = "兑换账户余额" BalanceExchangeForGreenEnergyTitleForFinUserFlow = "账户余额兑换" GreenCoinDoubleChainExchangeForBalanceTitleForFinUserFlow = "绿色积分双链兑换账户余额" + InstallmentPaymentAutoRepaidTitleForFinUserFlow = "分期付-自动扣款" ) const ( @@ -30,6 +31,7 @@ const ( GreenEnergyExchangeForBalanceForFinUserFlow = 112 // 兑换账户余额 BalanceExchangeForGreenEnergyForFinUserFlow = 113 // 账户余额兑换 GreenCoinDoubleChainExchangeForBalanceForFinUserFlow = 114 // 绿色积分双链兑换账户余额 + InstallmentPaymentAutoRepaidForFinUserFlow = 116 // 分期付-自动扣款 ) const DealUserAmountRequestIdPrefix = "%s:deal_user_amount:%d" diff --git a/rule/installment_payment/enum/installment_payment_list.go b/rule/installment_payment/enum/installment_payment_list.go new file mode 100644 index 0000000..d7938c0 --- /dev/null +++ b/rule/installment_payment/enum/installment_payment_list.go @@ -0,0 +1,20 @@ +package enum + +// InstallmentPaymentListIsRepaidOff 分期付列表 - 是否已偿还完 +type InstallmentPaymentListIsRepaidOff int + +const ( + InstallmentPaymentListRepaidOff InstallmentPaymentListIsRepaidOff = iota + InstallmentPaymentListRepaidOn +) + +func (i InstallmentPaymentListIsRepaidOff) String() string { + switch i { + case InstallmentPaymentListRepaidOff: + return "未偿还完" + case InstallmentPaymentListRepaidOn: + return "已偿还完" + default: + return "未知" + } +} diff --git a/rule/installment_payment/installment_payment.go b/rule/installment_payment/installment_payment.go new file mode 100644 index 0000000..b576711 --- /dev/null +++ b/rule/installment_payment/installment_payment.go @@ -0,0 +1,164 @@ +package installment_payment + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/enum" + md2 "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/md" + enum2 "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/rule/installment_payment/enum" + "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/rule/installment_payment/md" + "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/svc" + zhios_order_relate_utils "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/utils" + zhios_order_relate_logx "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/utils/logx" + "code.fnuoos.com/go_rely_warehouse/zyos_model.git/src/implement" + "code.fnuoos.com/go_rely_warehouse/zyos_model.git/src/models" + "errors" + "fmt" + "github.com/shopspring/decimal" + "time" + "xorm.io/xorm" +) + +// AddInstallmentPaymentList 新增 "分期付-订单" 记录 +func AddInstallmentPaymentList(engine *xorm.Engine, masterId string, args md.AddInstallmentPaymentListStruct) (err error) { + now := time.Now() + //1、查找对应方案记录 + installmentPaymentSchemeDb := implement.NewInstallmentPaymentSchemeDb(engine) + scheme, err := installmentPaymentSchemeDb.GetInstallmentPaymentSchemeById(args.SchemeId) + if err != nil { + return err + } + if scheme == nil { + return fmt.Errorf("installmentPaymentSchemeDb.InstallmentPaymentSchemeById returned nil") + } + + //2、计算相关数据 + installmentNumsValue := decimal.NewFromInt32(int32(scheme.InstallmentNums)) //分期数 + totalAmount, _ := decimal.NewFromString(args.TotalAmount) //总金额 + repaymentAmountPerInstallment := totalAmount.Div(installmentNumsValue).String() //每期还款金额 + + //3、新增 installment_payment_list 记录 + installmentPaymentListDb := implement.NewInstallmentPaymentListDb(engine) + _, err = installmentPaymentListDb.InsertInstallmentPaymentList(&models.InstallmentPaymentList{ + Uid: args.Uid, + GoodsId: args.GoodsId, + OrdId: args.OrdId, + TotalAmount: args.TotalAmount, + InstallmentNums: scheme.InstallmentNums, + AlreadyRepaidInstallmentNums: 0, + RepaymentAmountPerInstallment: repaymentAmountPerInstallment, + IsRepaidOff: 0, + CreateAt: now.Format("2006-01-02 15:04:05"), + UpdateAt: now.Format("2006-01-02 15:04:05"), + }) + if err != nil { + return err + } + return +} + +// InstallmentPaymentManualRepaid 手动还款 +func InstallmentPaymentManualRepaid(engine *xorm.Engine, masterId string, args md.InstallmentPaymentManualRepaid) (err error) { + now := time.Now() + session := engine.NewSession() + defer func() { + session.Close() + if err := recover(); err != nil { + _ = zhios_order_relate_logx.Error(err) + } + }() + session.Begin() + + //1、查找对应`installment_payment_list`记录 + installmentPaymentListDb := implement.NewInstallmentPaymentListDb(engine) + installmentPaymentList, err := installmentPaymentListDb.GetInstallmentPaymentListById(args.RecordId) + if err != nil { + session.Rollback() + return err + } + if installmentPaymentList == nil { + session.Rollback() + return fmt.Errorf("installmentPaymentListDb.InstallmentPaymentListById returned nil") + } + + //2、计算相关数据 + repaidAmount, _ := decimal.NewFromString(args.RepaidAmount) //还款总金额 + repaymentAmountPerInstallmentValue, _ := decimal.NewFromString(installmentPaymentList.RepaymentAmountPerInstallment) //每期还款金额 + installmentNumsValue := decimal.NewFromInt32(int32(installmentPaymentList.InstallmentNums)) //分期数 + alreadyRepaidInstallmentNumsValue := decimal.NewFromInt32(int32(installmentPaymentList.AlreadyRepaidInstallmentNums)) //已还分期数 + residueRepaidInstallmentNumsValue := installmentNumsValue.Sub(alreadyRepaidInstallmentNumsValue) //剩余分期数 + residueRepaidTotalAmountValue := residueRepaidInstallmentNumsValue.Mul(repaymentAmountPerInstallmentValue) //剩余待还总金额 + if args.RepaidKind == 1 { + //判断还款金额是否正确 + if installmentPaymentList.RepaymentAmountPerInstallment != args.RepaidAmount { + session.Rollback() + return errors.New("还款金额与按期还款金额不一致") + } + //按期还款 + installmentPaymentList.AlreadyRepaidInstallmentNums++ + if installmentPaymentList.AlreadyRepaidInstallmentNums == installmentPaymentList.InstallmentNums { + installmentPaymentList.IsRepaidOff = int(enum2.InstallmentPaymentListRepaidOn) //TODO::已还完 + } + } else { + //判断还款金额是否正确 + if repaidAmount.GreaterThan(residueRepaidTotalAmountValue) { + session.Rollback() + return errors.New("还款金额大于剩余待还总金额") + } + if repaidAmount.LessThan(residueRepaidTotalAmountValue) { + session.Rollback() + return errors.New("还款金额小于剩余待还总金额") + } + //全额还款 + installmentPaymentList.AlreadyRepaidInstallmentNums++ + installmentPaymentList.IsRepaidOff = int(enum2.InstallmentPaymentListRepaidOn) //TODO::已还完 + } + + //3、处理用户余额 + orderType := enum.FinUserFlowOrderActionString(md2.InstallmentPaymentAutoRepaidForFinUserFlow) + var dealUserAmount = md2.DealUserAmount{ + Kind: "sub", + Mid: masterId, + Title: md2.InstallmentPaymentAutoRepaidTitleForFinUserFlow, + OrderType: orderType, + OrdAction: md2.InstallmentPaymentAutoRepaidForFinUserFlow, + OrdId: zhios_order_relate_utils.Int64ToStr(installmentPaymentList.OrdId), + Uid: args.Uid, + Amount: zhios_order_relate_utils.StrToFloat64(args.RepaidAmount), + } + err = svc.DealUserAmount(session, dealUserAmount) + if err != nil { + session.Rollback() + return err + } + + //4、新增 installment_payment_repaid_flow 记录 + installmentPaymentRepaidFlowDb := implement.NewInstallmentPaymentRepaidFlowDb(engine) + _, err = installmentPaymentRepaidFlowDb.InsertInstallmentPaymentRepaidFlowBySession(session, &models.InstallmentPaymentRepaidFlow{ + RecordId: installmentPaymentList.Id, + Uid: installmentPaymentList.Uid, + Amount: installmentPaymentList.RepaymentAmountPerInstallment, + WhichRepaymentPeriod: installmentPaymentList.AlreadyRepaidInstallmentNums, + CreateAt: now.Format("2006-01-02 15:04:05"), + UpdateAt: now.Format("2006-01-02 15:04:05"), + }) + if err != nil { + session.Rollback() + return err + } + + //5、修改`installment_payment_list` 记录 + updateAffected, err := installmentPaymentListDb.UpdateInstallmentPaymentListBySess(session, installmentPaymentList, "already_repaid_installment_nums", "is_repaid_off") + if err != nil { + session.Rollback() + return err + } + if updateAffected <= 0 { + session.Rollback() + return errors.New("更新 `installment_payment_list` 记录失败") + } + err = session.Commit() + if err != nil { + session.Rollback() + return err + } + return +} diff --git a/rule/installment_payment/installment_payment_auto_repaid.go b/rule/installment_payment/installment_payment_auto_repaid.go new file mode 100644 index 0000000..82016b2 --- /dev/null +++ b/rule/installment_payment/installment_payment_auto_repaid.go @@ -0,0 +1,142 @@ +package installment_payment + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_mq.git/rabbit" + "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/db" + "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/enum" + md2 "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/md" + enum2 "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/rule/installment_payment/enum" + "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/rule/installment_payment/md" + "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/svc" + zhios_order_relate_utils "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/utils" + "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/utils/cache" + zhios_order_relate_logx "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/utils/logx" + "code.fnuoos.com/go_rely_warehouse/zyos_model.git/src/implement" + "code.fnuoos.com/go_rely_warehouse/zyos_model.git/src/models" + "errors" + "fmt" + "time" + "xorm.io/xorm" +) + +const LockKey = "installment_payment_auto_repaid_lock_key" + +// InstallmentPaymentAutoRepaid 分期付自动还款 +func InstallmentPaymentAutoRepaid(engine *xorm.Engine, masterId string, ch *rabbit.Channel) (err error) { + //TODO::增加“悲观锁”防止串行 + getString, _ := cache.GetString(LockKey) + if getString != "" { + fmt.Println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", "上一次结算未执行完") + return errors.New("上一次结算未执行完") + } + cache.SetEx(LockKey, "running", 3600*1) //1小时 + + //1、查找出"可用余额"大于"每期还款金额"的用户数据 + var sqlStr = "SELECT ipl.*, up.fin_valid, up.uid FROM `installment_payment_list` ipl " + + "JOIN `user_profile` up ON up.uid = ipl.uid " + + "WHERE ipl.is_repaid_off =0 AND up.fin_valid > ipl.repayment_amount_per_installment;" + nativeStringResultMap, _ := db.QueryNativeString(engine, sqlStr) + + //2、整理用户余额数据 + var userFinValid = map[string]float64{} + for _, resultMap := range nativeStringResultMap { + userFinValid[resultMap["uid"]] = zhios_order_relate_utils.StrToFloat64(resultMap["fin_valid"]) + } + + //2、将符合条件的数据推入mq队列 + for _, resultMap := range nativeStringResultMap { + if userFinValid[resultMap["uid"]] >= zhios_order_relate_utils.StrToFloat64(resultMap["repayment_amount_per_installment"]) { + //TODO::推入rabbitmq 异步处理 + ch.Publish(md.InstallmentPaymentExchange, md.InstallmentPaymentStructForAutoRepaid{ + MasterId: masterId, + Id: zhios_order_relate_utils.StrToInt(resultMap["id"]), + Uid: zhios_order_relate_utils.StrToInt(resultMap["uid"]), + GoodsId: zhios_order_relate_utils.StrToInt(resultMap["goods_id"]), + OrdId: zhios_order_relate_utils.StrToInt64(resultMap["ord_id"]), + TotalAmount: resultMap["total_amount"], + InstallmentNums: zhios_order_relate_utils.StrToInt(resultMap["installment_nums"]), + AlreadyRepaidInstallmentNums: zhios_order_relate_utils.StrToInt(resultMap["already_repaid_installment_nums"]), + RepaymentAmountPerInstallment: resultMap["repayment_amount_per_installment"], + IsRepaidOff: zhios_order_relate_utils.StrToInt(resultMap["is_repaid_off"]), + }, md.InstallmentPaymentRoutKeyForAutoRepaid) + userFinValid[resultMap["uid"]] -= zhios_order_relate_utils.StrToFloat64(resultMap["repayment_amount_per_installment"]) + } + } + + cache.Del(LockKey) //删除悲观锁 + return +} + +// DealInstallmentPaymentAutoRepaid 处理分期付自动还款 +func DealInstallmentPaymentAutoRepaid(engine *xorm.Engine, args md.InstallmentPaymentStructForAutoRepaid, masterId string) (err error) { + now := time.Now() + session := engine.NewSession() + defer func() { + session.Close() + if err := recover(); err != nil { + _ = zhios_order_relate_logx.Error(err) + } + }() + session.Begin() + //1、查询出对应`installment_payment_list` 数据 + installmentPaymentListDb := implement.NewInstallmentPaymentListDb(engine) + installmentPaymentList, err := installmentPaymentListDb.GetInstallmentPaymentListById(args.Id) + if err != nil { + session.Rollback() + return err + } + installmentPaymentList.AlreadyRepaidInstallmentNums++ + if installmentPaymentList.AlreadyRepaidInstallmentNums == installmentPaymentList.InstallmentNums { + installmentPaymentList.IsRepaidOff = int(enum2.InstallmentPaymentListRepaidOn) //TODO::已还完 + } + + //2、处理用户余额 + orderType := enum.FinUserFlowOrderActionString(md2.InstallmentPaymentAutoRepaidForFinUserFlow) + var dealUserAmount = md2.DealUserAmount{ + Kind: "sub", + Mid: masterId, + Title: md2.InstallmentPaymentAutoRepaidTitleForFinUserFlow, + OrderType: orderType, + OrdAction: md2.InstallmentPaymentAutoRepaidForFinUserFlow, + OrdId: zhios_order_relate_utils.Int64ToStr(args.OrdId), + Uid: args.Uid, + Amount: zhios_order_relate_utils.StrToFloat64(installmentPaymentList.RepaymentAmountPerInstallment), + } + err = svc.DealUserAmount(session, dealUserAmount) + if err != nil { + session.Rollback() + return err + } + + //3、新增`installment_payment_repaid_flow`数据 + installmentPaymentRepaidFlowDb := implement.NewInstallmentPaymentRepaidFlowDb(engine) + _, err = installmentPaymentRepaidFlowDb.InsertInstallmentPaymentRepaidFlowBySession(session, &models.InstallmentPaymentRepaidFlow{ + RecordId: installmentPaymentList.Id, + Uid: installmentPaymentList.Uid, + Amount: installmentPaymentList.RepaymentAmountPerInstallment, + WhichRepaymentPeriod: installmentPaymentList.AlreadyRepaidInstallmentNums, + CreateAt: now.Format("2006-01-02 15:04:05"), + UpdateAt: now.Format("2006-01-02 15:04:05"), + }) + if err != nil { + session.Rollback() + return err + } + + //4、修改`installment_payment_list` 记录 + updateAffected, err := installmentPaymentListDb.UpdateInstallmentPaymentListBySess(session, installmentPaymentList, "already_repaid_installment_nums", "is_repaid_off") + if err != nil { + session.Rollback() + return err + } + if updateAffected <= 0 { + session.Rollback() + return errors.New("更新 `installment_payment_list` 记录失败") + } + err = session.Commit() + if err != nil { + session.Rollback() + return err + } + return +} diff --git a/rule/installment_payment/installment_payment_init.go b/rule/installment_payment/installment_payment_init.go new file mode 100644 index 0000000..61048c0 --- /dev/null +++ b/rule/installment_payment/installment_payment_init.go @@ -0,0 +1,14 @@ +package installment_payment + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/md" + "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/utils/cache" +) + +func Init(redisAddr string) (err error) { + if redisAddr != "" { + cache.NewRedis(redisAddr) + } + _, err = cache.SelectDb(md.RedisDataBase) + return +} diff --git a/rule/installment_payment/md/installment_payment_list.go b/rule/installment_payment/md/installment_payment_list.go new file mode 100644 index 0000000..8c26166 --- /dev/null +++ b/rule/installment_payment/md/installment_payment_list.go @@ -0,0 +1,18 @@ +package md + +type AddInstallmentPaymentListStruct struct { + MasterId string `json:"master_id"` + Uid int `json:"uid"` + GoodsId int `json:"goods_id"` + OrdId int64 `json:"ord_id"` + TotalAmount string `json:"total_amount"` //总金额 + SchemeId int `json:"scheme_id"` //分期方案Id +} + +type InstallmentPaymentManualRepaid struct { + MasterId string `json:"master_id"` + Uid int `json:"uid"` + RecordId int `json:"record_id"` //对应 installment_payment_list(分期付-订单列表)id + RepaidAmount string `json:"repaid_amount"` //还款金额 + RepaidKind int32 `json:"repaid_kind"` //还款方式(1:按期还款 2:全额还款) +} diff --git a/rule/installment_payment/md/mq.go b/rule/installment_payment/md/mq.go new file mode 100644 index 0000000..369f72d --- /dev/null +++ b/rule/installment_payment/md/mq.go @@ -0,0 +1,20 @@ +package md + +const InstallmentPaymentExchange = "installment.payment" + +const ( + InstallmentPaymentRoutKeyForAutoRepaid = "auto_repaid" // 自动还款 +) + +type InstallmentPaymentStructForAutoRepaid struct { + MasterId string `json:"master_id"` + Id int `json:"id"` + Uid int `json:"uid"` + GoodsId int `json:"goods_id"` + OrdId int64 `json:"ord_id"` + TotalAmount string `json:"total_amount"` + InstallmentNums int `json:"installment_nums"` + AlreadyRepaidInstallmentNums int `json:"already_repaid_installment_nums"` + RepaymentAmountPerInstallment string `json:"repayment_amount_per_installment"` + IsRepaidOff int `json:"is_repaid_off"` +}