// Copyright 2019 getensh.com. All rights reserved. // Use of this source code is governed by getensh.com. package fee import ( "context" "encoding/json" "fmt" "git.getensh.com/common/gopkgs/database" "git.getensh.com/common/gopkgs/logger" "go.uber.org/zap" "google.golang.org/grpc/status" "gorm.io/gorm" "property-garden/errors" "property-garden/impl/v1/charge_utils" dbmodel "property-garden/model" pb_v1 "property-garden/pb/v1" "property-garden/utils" "time" ) // 计算相差月份 func calcMonthDiff(start, end int64) int { if start > end { return 0 } startTime := time.Unix(start, 0) endTime := time.Unix(end, 0) // 开始时间-结束时间 +1个月 eg:5月到9月 = (9-5) = 4个月 return (endTime.Year()-startTime.Year())*12 + int(endTime.Month()-startTime.Month()) } // 检查账单结束时间是否比计费开始时间更早 func checkEndTime(endTime string, start int64) int64 { t, err := time.ParseInLocation("2006-01", endTime, time.Local) if err != nil { fmt.Println("err:", err) return 0 } if t.Unix() == start { return 0 } // 如果账最后一次单生成时间在计费时间前不生成 if t.Before(time.Unix(start, 0)) { return 0 } return t.Unix() } // 计算上期账单截止时间按费用生效时间 func calcBillEndByChargeEffectiveTime(billPeriod int32, chargeEffectiveTime int64) int64 { now := time.Now() nowTimestamp := now.Unix() // 计费开始时间大于当前时间,不生成 if nowTimestamp < chargeEffectiveTime { return 0 } endTime := "" countMount := 0 switch billPeriod { case charge_utils.BillPeriodMonth: // 月度账单 // 返回本月 endTime = now.Format("2006-01") case charge_utils.BillPeriodThreeMonth: // 季度账单 countMount = 3 case charge_utils.BillPeriodSixMonth: // 半年账单 countMount = 6 case charge_utils.BillPeriodYear: // 一年账单 countMount = 12 default: return 0 } if endTime == "" { diffYear := now.Year() - time.Unix(chargeEffectiveTime, 0).Year() diffMonth := now.Month() - time.Unix(chargeEffectiveTime, 0).Month() totolMount := diffYear*12 + int(diffMonth) if totolMount < countMount { return 0 } diff := totolMount % countMount endTime = now.AddDate(0, -diff, 0).Format("2006-01") } return checkEndTime(endTime, chargeEffectiveTime) } // 计算上期账单截止时间按自然周期 func calcBillEndByNaturePeriod(billPeriod int32, chargeEffectiveTime int64) int64 { now := time.Now() month := now.Month() year := now.Year() nowTimestamp := now.Unix() // 计费开始时间大于当前时间,不生成 if nowTimestamp < chargeEffectiveTime { return 0 } endTime := "" switch billPeriod { case charge_utils.BillPeriodMonth: // 月度账单 // 返回本月 endTime = now.Format("2006-01") case charge_utils.BillPeriodThreeMonth: // 季度账单 // 1-3月生成上年10-12月账单 if month >= time.January && month <= time.March { endTime = fmt.Sprintf("%d-01", year) } else if month >= time.April && month <= time.June { endTime = fmt.Sprintf("%d-04", year) } else if month >= time.July && month <= time.September { endTime = fmt.Sprintf("%d-07", year) } else { endTime = fmt.Sprintf("%d-10", year) } case charge_utils.BillPeriodSixMonth: // 半年账单7,1 // 1-6月生成上年7-12月账单 if month >= time.January && month <= time.June { endTime = fmt.Sprintf("%d-01", year) } else { endTime = fmt.Sprintf("%d-07", year) } case charge_utils.BillPeriodYear: // 一年账单 // 返回本年一月 endTime = fmt.Sprintf("%d-01", year) } return checkEndTime(endTime, chargeEffectiveTime) } // 计算上期账单截止时间 func calcBillEnd(billPeriod, billPeriodType int32, chargeEffectiveTime int64) int64 { if billPeriodType == 1 { return calcBillEndByNaturePeriod(billPeriod, chargeEffectiveTime) } else { return calcBillEndByChargeEffectiveTime(billPeriod, chargeEffectiveTime) } } func calcFee(conf *dbmodel.TChargeConf, bind *dbmodel.TChargeBind, totalMonth int) (int64, string) { billDesc := pb_v1.BillDesc{ChargeBasis: conf.ChargeBasis} if conf.ChargeBasis == charge_utils.ChargeBasisArea || conf.ChargeBasis == charge_utils.ChargeBasisUsedArea || conf.ChargeBasis == charge_utils.ChargeBasisSpaceArea { // 1 按房屋面积 2 按使用面积 3 按车位面积 计算公式为面积*单价+附加费 billDesc.ObjArea = bind.ObjArea billDesc.FixAmount = conf.FixAmount billDesc.FixAmountName = conf.FixAmountName billDesc.UnitPrice = conf.UnitPrice desc, _ := json.Marshal(billDesc) //desc := fmt.Sprintf("面积:%f\n单价:%s\n附加费:%s\n", bind.ObjArea, float64(conf.UnitPrice)/100.00, float64(conf.FixAmount)/100.00) return (int64(float64(conf.UnitPrice)*bind.ObjArea) + conf.FixAmount) * int64(totalMonth), string(desc) } else if conf.ChargeBasis == charge_utils.ChargeBasisFix { // 5 固定费用 ,返回费用配置中固定费用 billDesc.FixAmount = conf.FixAmount desc, _ := json.Marshal(billDesc) //desc := fmt.Sprintf("固定费用:%f\n", float64(conf.FixAmount)/100.00) return conf.FixAmount * int64(totalMonth), string(desc) } else if conf.ChargeBasis == charge_utils.ChargeBasisSelf { // 6 自定义 返回绑定关系中自定义费用 billDesc.CustomFee = bind.CustomFee desc, _ := json.Marshal(billDesc) //desc := fmt.Sprintf("自定义费用:%f\n", float64(bind.CustomFee)/100.00) return bind.CustomFee * int64(totalMonth), string(desc) } return 0, "" } // 生成账单 func generateBill(chargeConf *dbmodel.TChargeConf, chargeBind *dbmodel.TChargeBind, endTime int64, needCheckStart bool) *dbmodel.TChargeBill { // 是否需要检查开始时间,手动生成不需要 if needCheckStart { // 检查是否到计费开始时间 if chargeBind.Start >= endTime { return nil } } // 检查最后生成账单时间是否比账单截止日期大 if chargeBind.BillLastTime >= endTime { return nil } // 账单开始时间 billStart := chargeBind.Start if chargeBind.BillLastTime != 0 { billStart = chargeBind.BillLastTime } totalMonth := calcMonthDiff(billStart, endTime) if totalMonth == 0 { return nil } // 计算费用 fee, desc := calcFee(chargeConf, chargeBind, totalMonth) if fee == 0 { return nil } // TODO 宏定义 bill := &dbmodel.TChargeBill{ ObjType: chargeBind.ObjType, ObjId: chargeBind.ObjId, ObjName: chargeBind.ObjName, ChargeId: chargeBind.ChargeId, ChargeBindId: chargeBind.ID, ChargeTimeType: chargeConf.ChargeTimeType, ChargeDesc: desc, Amount: fee, ChargeType: chargeConf.ChargeType, ChargeName: chargeConf.ChargeName, PayMode: 1, ChargeStart: billStart, ChargeEnd: endTime, HouseId: chargeBind.HouseId, Status: 1, } return bill } // 更新费用项目账单最后时间 func updateChargeLastTime(db *gorm.DB, chargeConf *dbmodel.TChargeConf, chargeBind *dbmodel.TChargeBind, endtime int64, dbname string) error { if chargeConf != nil { // 更新费用项目所有绑定 where := map[string]interface{}{"id": chargeConf.ID, "bill_last_time <": endtime} value := map[string]interface{}{"bill_last_time": endtime} chargeConf.SetTable(dbname) err := chargeConf.Update(db, where, value) if err != nil { return errors.DataBaseError } chargeBind = dbmodel.NewChargeBind(dbname) where = map[string]interface{}{"charge_id": chargeConf.ID, "bill_last_time <": endtime} value = map[string]interface{}{"bill_last_time": endtime} err = chargeBind.Update(db, where, value) if err != nil { return errors.DataBaseError } } else if chargeBind != nil { // 更新绑定费项 where := map[string]interface{}{"id": chargeBind.ID} value := map[string]interface{}{"bill_last_time": endtime} err := chargeBind.Update(db, where, value) if err != nil { return errors.DataBaseError } } return nil } func generateAllBill(req *pb_v1.GenerateBillRequest, db *gorm.DB) (err error) { dbname := utils.GetGardenDbName(req.GardenId) // 获取周期收费费项 chargeConf := dbmodel.NewChargeConf(dbname) where := map[string]interface{}{ "charge_time_type": charge_utils.ChargeTimeTypePeriod, } chargeConfList, err := chargeConf.List(db, where, nil, -1, 0) if err != nil { return errors.DataBaseError } p := dbmodel.NewChargeBill(dbname) for _, chargeConf := range chargeConfList { endTime := calcBillEnd(chargeConf.BillPeriod, chargeConf.BillPeriodType, chargeConf.ChargeEffectiveTime) if endTime == 0 { continue } // 查询费用绑定结束时间(bill_last_time)不为endtime的 changrBind := dbmodel.NewChargeBind(dbname) where := map[string]interface{}{"bill_last_time <": endTime, "charge_id": chargeConf.ID} chargeBindList, err := changrBind.List(db, where, nil, -1, 0) if err != nil { return errors.DataBaseError } billList := []dbmodel.TChargeBill{} // 遍历账单绑定,生成账单 billLen := 0 hasBill := false for _, v := range chargeBindList { bill := generateBill(&chargeConf, &v, endTime, true) if bill == nil { continue } billList = append(billList, *bill) billLen++ hasBill = true if billLen == 1000 { // 1000 条插入一次 // 批量插入账单 err = p.InsertMulti(db, &billList) if err != nil { return errors.DataBaseError } billLen = 0 billList = []dbmodel.TChargeBill{} } } if !hasBill { // 没有账单 continue } if billLen > 0 { // 插入剩余 err = p.InsertMulti(db, &billList) if err != nil { return errors.DataBaseError } } // 更新绑定关系总中账单截止时间 err = updateChargeLastTime(db, &chargeConf, nil, endTime, dbname) if err != nil { return errors.DataBaseError } } return nil } // 生成指定费用项目账单 func generateBillByChargeId(req *pb_v1.GenerateBillRequest, db *gorm.DB) (err error) { dbname := utils.GetGardenDbName(req.GardenId) // 获取周期收费费项 chargeConf := dbmodel.NewChargeConf(dbname) where := map[string]interface{}{ "id": req.ChargeId, } err = chargeConf.Find(db, where) if err != nil { return errors.DataBaseError } if req.EndTime <= chargeConf.BillLastTime { return status.Error(10003, "结束时间不能小于上次账单结束时间") } p := dbmodel.NewChargeBill(dbname) endTime := req.EndTime // 查询费用绑定结束时间(bill_last_time)不为endtime的 changrBind := dbmodel.NewChargeBind(dbname) where = map[string]interface{}{"bill_last_time <": endTime} chargeBindList, err := changrBind.List(db, where, nil, -1, 0) if err != nil { return errors.DataBaseError } billList := []dbmodel.TChargeBill{} // 遍历账单绑定,生成账单 billLen := 0 hasBill := false for _, v := range chargeBindList { bill := generateBill(chargeConf, &v, endTime, false) if bill == nil { continue } billList = append(billList, *bill) billLen++ hasBill = true if billLen == 1000 { // 1000 条插入一次 // 批量插入账单 err = p.InsertMulti(db, &billList) if err != nil { return errors.DataBaseError } billLen = 0 billList = []dbmodel.TChargeBill{} } } if !hasBill { // 没有账单 return nil } if billLen > 0 { // 插入剩余 err = p.InsertMulti(db, &billList) if err != nil { return errors.DataBaseError } } // 更新绑定关系总中账单截止时间 err = updateChargeLastTime(db, chargeConf, nil, endTime, dbname) if err != nil { return errors.DataBaseError } return nil } // 生成一条费用项目绑定账单 func generateBillByChargeBindId(req *pb_v1.GenerateBillRequest, db *gorm.DB) (err error) { dbname := utils.GetGardenDbName(req.GardenId) // 获取绑定费用项目 changrBind := dbmodel.NewChargeBind(dbname) where := map[string]interface{}{"id": req.ChargeBindId} err = changrBind.Find(db, where) if err != nil { return errors.DataBaseError } if req.EndTime <= changrBind.BillLastTime { return status.Error(10003, "结束时间不能小于上次账单结束时间") } // 获取周期收费费项 chargeConf := dbmodel.NewChargeConf(dbname) where = map[string]interface{}{ "id": changrBind.ChargeId, } err = chargeConf.Find(db, where) if err != nil { return errors.DataBaseError } // 生成账单 bill := generateBill(chargeConf, changrBind, req.EndTime, false) if bill == nil { return nil } bill.SetTable(dbname) // 插入账单 err = bill.Insert(db) if err != nil { return errors.DataBaseError } // 更新绑定关系总中账单截止时间 err = updateChargeLastTime(db, nil, changrBind, req.EndTime, dbname) if err != nil { return errors.DataBaseError } return nil } func runBatchBill(req *pb_v1.GenerateBillRequest) error { dbname := utils.GetGardenDbName(req.GardenId) changrBind := dbmodel.NewChargeBind(dbname) changrBindList := []dbmodel.TChargeBind{} for i := 0; i < 10000; i++ { changrBindTemp := dbmodel.TChargeBind{ ObjId: int64(i + 10), ObjName: "1-1-906", HouseId: 1, ChargeId: 7, Start: 1619798400, End: 0, ChargeType: 1, ObjType: 1, CustomFee: 10, BillLastTime: 0, ObjArea: 20.20, } changrBindTemp.UniqFlag.Int32 = 1 changrBindTemp.UniqFlag.Valid = true changrBindList = append(changrBindList, changrBindTemp) if i%2000 == 0 { changrBind.InsertMulti(database.DB(), &changrBindList) changrBindList = []dbmodel.TChargeBind{} } } return nil } // 生成全部账单 func GenerateBill(ctx context.Context, req *pb_v1.GenerateBillRequest) (reply *pb_v1.GenerateBillReply, err error) { reply = &pb_v1.GenerateBillReply{} if req.GardenId <= 0 { return reply, errors.ParamsError } if req.ChargeId > 0 || req.ChargeBindId > 0 { if req.EndTime <= 0 { return reply, errors.ParamsError } else { // 结束时间往后推一个月,包含入参的月份 //req.EndTime = time.Unix(req.EndTime, 0).AddDate(0, 1, 0).Unix() } } db := database.DB().Begin() // 捕获各个task中的异常并返回给调用者 defer func() { if r := recover(); r != nil { db.Rollback() err = fmt.Errorf("%+v", r) e := &status.Status{} if er := json.Unmarshal([]byte(err.Error()), e); er != nil { logger.Error("err", zap.String("system_err", err.Error()), zap.Stack("stacktrace")) } } }() if req.ChargeId > 0 { err = generateBillByChargeId(req, db) } else if req.ChargeBindId > 0 { err = generateBillByChargeBindId(req, db) } else { err = generateAllBill(req, db) } if err != nil { db.Rollback() } else { db.Commit() } return reply, err }