generate_bill.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. // Copyright 2019 getensh.com. All rights reserved.
  2. // Use of this source code is governed by getensh.com.
  3. package fee
  4. import (
  5. "context"
  6. "encoding/json"
  7. "fmt"
  8. "git.getensh.com/common/gopkgs/database"
  9. "git.getensh.com/common/gopkgs/logger"
  10. "go.uber.org/zap"
  11. "google.golang.org/grpc/status"
  12. "gorm.io/gorm"
  13. "property-garden/errors"
  14. "property-garden/impl/v1/charge_utils"
  15. dbmodel "property-garden/model"
  16. pb_v1 "property-garden/pb/v1"
  17. "property-garden/utils"
  18. "time"
  19. )
  20. // 计算相差月份
  21. func calcMonthDiff(start, end int64) int {
  22. if start > end {
  23. return 0
  24. }
  25. startTime := time.Unix(start, 0)
  26. endTime := time.Unix(end, 0)
  27. // 开始时间-结束时间 +1个月 eg:5月到9月 = (9-5) = 4个月
  28. return (endTime.Year()-startTime.Year())*12 + int(endTime.Month()-startTime.Month())
  29. }
  30. // 检查账单结束时间是否比计费开始时间更早
  31. func checkEndTime(endTime string, start int64) int64 {
  32. t, err := time.ParseInLocation("2006-01", endTime, time.Local)
  33. if err != nil {
  34. fmt.Println("err:", err)
  35. return 0
  36. }
  37. if t.Unix() == start {
  38. return 0
  39. }
  40. // 如果账最后一次单生成时间在计费时间前不生成
  41. if t.Before(time.Unix(start, 0)) {
  42. return 0
  43. }
  44. return t.Unix()
  45. }
  46. // 计算上期账单截止时间按费用生效时间
  47. func calcBillEndByChargeEffectiveTime(billPeriod int32, chargeEffectiveTime int64) int64 {
  48. now := time.Now()
  49. nowTimestamp := now.Unix()
  50. // 计费开始时间大于当前时间,不生成
  51. if nowTimestamp < chargeEffectiveTime {
  52. return 0
  53. }
  54. endTime := ""
  55. countMount := 0
  56. switch billPeriod {
  57. case charge_utils.BillPeriodMonth:
  58. // 月度账单
  59. // 返回本月
  60. endTime = now.Format("2006-01")
  61. case charge_utils.BillPeriodThreeMonth:
  62. // 季度账单
  63. countMount = 3
  64. case charge_utils.BillPeriodSixMonth:
  65. // 半年账单
  66. countMount = 6
  67. case charge_utils.BillPeriodYear:
  68. // 一年账单
  69. countMount = 12
  70. default:
  71. return 0
  72. }
  73. if endTime == "" {
  74. diffYear := now.Year() - time.Unix(chargeEffectiveTime, 0).Year()
  75. diffMonth := now.Month() - time.Unix(chargeEffectiveTime, 0).Month()
  76. totolMount := diffYear*12 + int(diffMonth)
  77. if totolMount < countMount {
  78. return 0
  79. }
  80. diff := totolMount % countMount
  81. endTime = now.AddDate(0, -diff, 0).Format("2006-01")
  82. }
  83. return checkEndTime(endTime, chargeEffectiveTime)
  84. }
  85. // 计算上期账单截止时间按自然周期
  86. func calcBillEndByNaturePeriod(billPeriod int32, chargeEffectiveTime int64) int64 {
  87. now := time.Now()
  88. month := now.Month()
  89. year := now.Year()
  90. nowTimestamp := now.Unix()
  91. // 计费开始时间大于当前时间,不生成
  92. if nowTimestamp < chargeEffectiveTime {
  93. return 0
  94. }
  95. endTime := ""
  96. switch billPeriod {
  97. case charge_utils.BillPeriodMonth:
  98. // 月度账单
  99. // 返回本月
  100. endTime = now.Format("2006-01")
  101. case charge_utils.BillPeriodThreeMonth:
  102. // 季度账单
  103. // 1-3月生成上年10-12月账单
  104. if month >= time.January && month <= time.March {
  105. endTime = fmt.Sprintf("%d-01", year)
  106. } else if month >= time.April && month <= time.June {
  107. endTime = fmt.Sprintf("%d-04", year)
  108. } else if month >= time.July && month <= time.September {
  109. endTime = fmt.Sprintf("%d-07", year)
  110. } else {
  111. endTime = fmt.Sprintf("%d-10", year)
  112. }
  113. case charge_utils.BillPeriodSixMonth:
  114. // 半年账单7,1
  115. // 1-6月生成上年7-12月账单
  116. if month >= time.January && month <= time.June {
  117. endTime = fmt.Sprintf("%d-01", year)
  118. } else {
  119. endTime = fmt.Sprintf("%d-07", year)
  120. }
  121. case charge_utils.BillPeriodYear:
  122. // 一年账单
  123. // 返回本年一月
  124. endTime = fmt.Sprintf("%d-01", year)
  125. }
  126. return checkEndTime(endTime, chargeEffectiveTime)
  127. }
  128. // 计算上期账单截止时间
  129. func calcBillEnd(billPeriod, billPeriodType int32, chargeEffectiveTime int64) int64 {
  130. if billPeriodType == 1 {
  131. return calcBillEndByNaturePeriod(billPeriod, chargeEffectiveTime)
  132. } else {
  133. return calcBillEndByChargeEffectiveTime(billPeriod, chargeEffectiveTime)
  134. }
  135. }
  136. func calcFee(conf *dbmodel.TChargeConf, bind *dbmodel.TChargeBind, totalMonth int) (int64, string) {
  137. billDesc := pb_v1.BillDesc{ChargeBasis: conf.ChargeBasis}
  138. if conf.ChargeBasis == charge_utils.ChargeBasisArea || conf.ChargeBasis == charge_utils.ChargeBasisUsedArea || conf.ChargeBasis == charge_utils.ChargeBasisSpaceArea {
  139. // 1 按房屋面积 2 按使用面积 3 按车位面积 计算公式为面积*单价+附加费
  140. billDesc.ObjArea = bind.ObjArea
  141. billDesc.FixAmount = conf.FixAmount
  142. billDesc.FixAmountName = conf.FixAmountName
  143. billDesc.UnitPrice = conf.UnitPrice
  144. desc, _ := json.Marshal(billDesc)
  145. //desc := fmt.Sprintf("面积:%f\n单价:%s\n附加费:%s\n", bind.ObjArea, float64(conf.UnitPrice)/100.00, float64(conf.FixAmount)/100.00)
  146. return (int64(float64(conf.UnitPrice)*bind.ObjArea) + conf.FixAmount) * int64(totalMonth), string(desc)
  147. } else if conf.ChargeBasis == charge_utils.ChargeBasisFix {
  148. // 5 固定费用 ,返回费用配置中固定费用
  149. billDesc.FixAmount = conf.FixAmount
  150. desc, _ := json.Marshal(billDesc)
  151. //desc := fmt.Sprintf("固定费用:%f\n", float64(conf.FixAmount)/100.00)
  152. return conf.FixAmount * int64(totalMonth), string(desc)
  153. } else if conf.ChargeBasis == charge_utils.ChargeBasisSelf {
  154. // 6 自定义 返回绑定关系中自定义费用
  155. billDesc.CustomFee = bind.CustomFee
  156. desc, _ := json.Marshal(billDesc)
  157. //desc := fmt.Sprintf("自定义费用:%f\n", float64(bind.CustomFee)/100.00)
  158. return bind.CustomFee * int64(totalMonth), string(desc)
  159. }
  160. return 0, ""
  161. }
  162. // 生成账单
  163. func generateBill(chargeConf *dbmodel.TChargeConf, chargeBind *dbmodel.TChargeBind, endTime int64, needCheckStart bool) *dbmodel.TChargeBill {
  164. // 是否需要检查开始时间,手动生成不需要
  165. if needCheckStart {
  166. // 检查是否到计费开始时间
  167. if chargeBind.Start >= endTime {
  168. return nil
  169. }
  170. }
  171. // 检查最后生成账单时间是否比账单截止日期大
  172. if chargeBind.BillLastTime >= endTime {
  173. return nil
  174. }
  175. // 账单开始时间
  176. billStart := chargeBind.Start
  177. if chargeBind.BillLastTime != 0 {
  178. billStart = chargeBind.BillLastTime
  179. }
  180. totalMonth := calcMonthDiff(billStart, endTime)
  181. if totalMonth == 0 {
  182. return nil
  183. }
  184. // 计算费用
  185. fee, desc := calcFee(chargeConf, chargeBind, totalMonth)
  186. if fee == 0 {
  187. return nil
  188. }
  189. // TODO 宏定义
  190. bill := &dbmodel.TChargeBill{
  191. ObjType: chargeBind.ObjType,
  192. ObjId: chargeBind.ObjId,
  193. ObjName: chargeBind.ObjName,
  194. ChargeId: chargeBind.ChargeId,
  195. ChargeBindId: chargeBind.ID,
  196. ChargeTimeType: chargeConf.ChargeTimeType,
  197. ChargeDesc: desc,
  198. Amount: fee,
  199. ChargeType: chargeConf.ChargeType,
  200. ChargeName: chargeConf.ChargeName,
  201. PayMode: 1,
  202. ChargeStart: billStart,
  203. ChargeEnd: endTime,
  204. HouseId: chargeBind.HouseId,
  205. Status: 1,
  206. }
  207. return bill
  208. }
  209. // 更新费用项目账单最后时间
  210. func updateChargeLastTime(db *gorm.DB, chargeConf *dbmodel.TChargeConf, chargeBind *dbmodel.TChargeBind, endtime int64, dbname string) error {
  211. if chargeConf != nil {
  212. // 更新费用项目所有绑定
  213. where := map[string]interface{}{"id": chargeConf.ID, "bill_last_time <": endtime}
  214. value := map[string]interface{}{"bill_last_time": endtime}
  215. chargeConf.SetTable(dbname)
  216. err := chargeConf.Update(db, where, value)
  217. if err != nil {
  218. return errors.DataBaseError
  219. }
  220. chargeBind = dbmodel.NewChargeBind(dbname)
  221. where = map[string]interface{}{"charge_id": chargeConf.ID, "bill_last_time <": endtime}
  222. value = map[string]interface{}{"bill_last_time": endtime}
  223. err = chargeBind.Update(db, where, value)
  224. if err != nil {
  225. return errors.DataBaseError
  226. }
  227. } else if chargeBind != nil {
  228. // 更新绑定费项
  229. where := map[string]interface{}{"id": chargeBind.ID}
  230. value := map[string]interface{}{"bill_last_time": endtime}
  231. err := chargeBind.Update(db, where, value)
  232. if err != nil {
  233. return errors.DataBaseError
  234. }
  235. }
  236. return nil
  237. }
  238. func generateAllBill(req *pb_v1.GenerateBillRequest, db *gorm.DB) (err error) {
  239. dbname := utils.GetGardenDbName(req.GardenId)
  240. // 获取周期收费费项
  241. chargeConf := dbmodel.NewChargeConf(dbname)
  242. where := map[string]interface{}{
  243. "charge_time_type": charge_utils.ChargeTimeTypePeriod,
  244. }
  245. chargeConfList, err := chargeConf.List(db, where, nil, -1, 0)
  246. if err != nil {
  247. return errors.DataBaseError
  248. }
  249. p := dbmodel.NewChargeBill(dbname)
  250. for _, chargeConf := range chargeConfList {
  251. endTime := calcBillEnd(chargeConf.BillPeriod, chargeConf.BillPeriodType, chargeConf.ChargeEffectiveTime)
  252. if endTime == 0 {
  253. continue
  254. }
  255. // 查询费用绑定结束时间(bill_last_time)不为endtime的
  256. changrBind := dbmodel.NewChargeBind(dbname)
  257. where := map[string]interface{}{"bill_last_time <": endTime, "charge_id": chargeConf.ID}
  258. chargeBindList, err := changrBind.List(db, where, nil, -1, 0)
  259. if err != nil {
  260. return errors.DataBaseError
  261. }
  262. billList := []dbmodel.TChargeBill{}
  263. // 遍历账单绑定,生成账单
  264. billLen := 0
  265. hasBill := false
  266. for _, v := range chargeBindList {
  267. bill := generateBill(&chargeConf, &v, endTime, true)
  268. if bill == nil {
  269. continue
  270. }
  271. billList = append(billList, *bill)
  272. billLen++
  273. hasBill = true
  274. if billLen == 1000 {
  275. // 1000 条插入一次
  276. // 批量插入账单
  277. err = p.InsertMulti(db, &billList)
  278. if err != nil {
  279. return errors.DataBaseError
  280. }
  281. billLen = 0
  282. billList = []dbmodel.TChargeBill{}
  283. }
  284. }
  285. if !hasBill {
  286. // 没有账单
  287. continue
  288. }
  289. if billLen > 0 {
  290. // 插入剩余
  291. err = p.InsertMulti(db, &billList)
  292. if err != nil {
  293. return errors.DataBaseError
  294. }
  295. }
  296. // 更新绑定关系总中账单截止时间
  297. err = updateChargeLastTime(db, &chargeConf, nil, endTime, dbname)
  298. if err != nil {
  299. return errors.DataBaseError
  300. }
  301. }
  302. return nil
  303. }
  304. // 生成指定费用项目账单
  305. func generateBillByChargeId(req *pb_v1.GenerateBillRequest, db *gorm.DB) (err error) {
  306. dbname := utils.GetGardenDbName(req.GardenId)
  307. // 获取周期收费费项
  308. chargeConf := dbmodel.NewChargeConf(dbname)
  309. where := map[string]interface{}{
  310. "id": req.ChargeId,
  311. }
  312. err = chargeConf.Find(db, where)
  313. if err != nil {
  314. return errors.DataBaseError
  315. }
  316. if req.EndTime <= chargeConf.BillLastTime {
  317. return status.Error(10003, "结束时间不能小于上次账单结束时间")
  318. }
  319. p := dbmodel.NewChargeBill(dbname)
  320. endTime := req.EndTime
  321. // 查询费用绑定结束时间(bill_last_time)不为endtime的
  322. changrBind := dbmodel.NewChargeBind(dbname)
  323. where = map[string]interface{}{"bill_last_time <": endTime}
  324. chargeBindList, err := changrBind.List(db, where, nil, -1, 0)
  325. if err != nil {
  326. return errors.DataBaseError
  327. }
  328. billList := []dbmodel.TChargeBill{}
  329. // 遍历账单绑定,生成账单
  330. billLen := 0
  331. hasBill := false
  332. for _, v := range chargeBindList {
  333. bill := generateBill(chargeConf, &v, endTime, false)
  334. if bill == nil {
  335. continue
  336. }
  337. billList = append(billList, *bill)
  338. billLen++
  339. hasBill = true
  340. if billLen == 1000 {
  341. // 1000 条插入一次
  342. // 批量插入账单
  343. err = p.InsertMulti(db, &billList)
  344. if err != nil {
  345. return errors.DataBaseError
  346. }
  347. billLen = 0
  348. billList = []dbmodel.TChargeBill{}
  349. }
  350. }
  351. if !hasBill {
  352. // 没有账单
  353. return nil
  354. }
  355. if billLen > 0 {
  356. // 插入剩余
  357. err = p.InsertMulti(db, &billList)
  358. if err != nil {
  359. return errors.DataBaseError
  360. }
  361. }
  362. // 更新绑定关系总中账单截止时间
  363. err = updateChargeLastTime(db, chargeConf, nil, endTime, dbname)
  364. if err != nil {
  365. return errors.DataBaseError
  366. }
  367. return nil
  368. }
  369. // 生成一条费用项目绑定账单
  370. func generateBillByChargeBindId(req *pb_v1.GenerateBillRequest, db *gorm.DB) (err error) {
  371. dbname := utils.GetGardenDbName(req.GardenId)
  372. // 获取绑定费用项目
  373. changrBind := dbmodel.NewChargeBind(dbname)
  374. where := map[string]interface{}{"id": req.ChargeBindId}
  375. err = changrBind.Find(db, where)
  376. if err != nil {
  377. return errors.DataBaseError
  378. }
  379. if req.EndTime <= changrBind.BillLastTime {
  380. return status.Error(10003, "结束时间不能小于上次账单结束时间")
  381. }
  382. // 获取周期收费费项
  383. chargeConf := dbmodel.NewChargeConf(dbname)
  384. where = map[string]interface{}{
  385. "id": changrBind.ChargeId,
  386. }
  387. err = chargeConf.Find(db, where)
  388. if err != nil {
  389. return errors.DataBaseError
  390. }
  391. // 生成账单
  392. bill := generateBill(chargeConf, changrBind, req.EndTime, false)
  393. if bill == nil {
  394. return nil
  395. }
  396. bill.SetTable(dbname)
  397. // 插入账单
  398. err = bill.Insert(db)
  399. if err != nil {
  400. return errors.DataBaseError
  401. }
  402. // 更新绑定关系总中账单截止时间
  403. err = updateChargeLastTime(db, nil, changrBind, req.EndTime, dbname)
  404. if err != nil {
  405. return errors.DataBaseError
  406. }
  407. return nil
  408. }
  409. func runBatchBill(req *pb_v1.GenerateBillRequest) error {
  410. dbname := utils.GetGardenDbName(req.GardenId)
  411. changrBind := dbmodel.NewChargeBind(dbname)
  412. changrBindList := []dbmodel.TChargeBind{}
  413. for i := 0; i < 10000; i++ {
  414. changrBindTemp := dbmodel.TChargeBind{
  415. ObjId: int64(i + 10),
  416. ObjName: "1-1-906",
  417. HouseId: 1,
  418. ChargeId: 7,
  419. Start: 1619798400,
  420. End: 0,
  421. ChargeType: 1,
  422. ObjType: 1,
  423. CustomFee: 10,
  424. BillLastTime: 0,
  425. ObjArea: 20.20,
  426. }
  427. changrBindTemp.UniqFlag.Int32 = 1
  428. changrBindTemp.UniqFlag.Valid = true
  429. changrBindList = append(changrBindList, changrBindTemp)
  430. if i%2000 == 0 {
  431. changrBind.InsertMulti(database.DB(), &changrBindList)
  432. changrBindList = []dbmodel.TChargeBind{}
  433. }
  434. }
  435. return nil
  436. }
  437. // 生成全部账单
  438. func GenerateBill(ctx context.Context, req *pb_v1.GenerateBillRequest) (reply *pb_v1.GenerateBillReply, err error) {
  439. reply = &pb_v1.GenerateBillReply{}
  440. if req.GardenId <= 0 {
  441. return reply, errors.ParamsError
  442. }
  443. if req.ChargeId > 0 || req.ChargeBindId > 0 {
  444. if req.EndTime <= 0 {
  445. return reply, errors.ParamsError
  446. } else {
  447. // 结束时间往后推一个月,包含入参的月份
  448. //req.EndTime = time.Unix(req.EndTime, 0).AddDate(0, 1, 0).Unix()
  449. }
  450. }
  451. db := database.DB().Begin()
  452. // 捕获各个task中的异常并返回给调用者
  453. defer func() {
  454. if r := recover(); r != nil {
  455. db.Rollback()
  456. err = fmt.Errorf("%+v", r)
  457. e := &status.Status{}
  458. if er := json.Unmarshal([]byte(err.Error()), e); er != nil {
  459. logger.Error("err",
  460. zap.String("system_err", err.Error()),
  461. zap.Stack("stacktrace"))
  462. }
  463. }
  464. }()
  465. if req.ChargeId > 0 {
  466. err = generateBillByChargeId(req, db)
  467. } else if req.ChargeBindId > 0 {
  468. err = generateBillByChargeBindId(req, db)
  469. } else {
  470. err = generateAllBill(req, db)
  471. }
  472. if err != nil {
  473. db.Rollback()
  474. } else {
  475. db.Commit()
  476. }
  477. return reply, err
  478. }