examine.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. package order
  2. import (
  3. "app.yhyue.com/moapp/jybase/common"
  4. "app.yhyue.com/moapp/jybase/date"
  5. "context"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "github.com/gogf/gf/v2/database/gdb"
  10. "github.com/gogf/gf/v2/errors/gerror"
  11. "github.com/gogf/gf/v2/frame/g"
  12. "github.com/gogf/gf/v2/util/gconv"
  13. "jyOrderManager/internal/consts"
  14. "jyOrderManager/internal/jyutil"
  15. "jyOrderManager/internal/logic/product"
  16. "jyOrderManager/internal/model"
  17. "jyOrderManager/internal/service"
  18. "log"
  19. "math"
  20. "strings"
  21. "time"
  22. )
  23. // 订单审核状态
  24. const (
  25. OrderUncommitted = 0 // 待提交
  26. OrderPending = 1 // 待审核
  27. OrderFirstPassed = 2 // 一审通过
  28. OrderPassed = 3 // 审核通过
  29. OrderSecondPassed = 4 // 审核通过
  30. OrderFirstReturn = -2 // 一审退回
  31. OrderSecondReturn = -3 // 二审退回
  32. OrderThreeReturn = -4 // 二审退回
  33. ReturnMoney = 1
  34. ReturnProtocol = 2
  35. )
  36. // 表名
  37. const (
  38. TableAuditRecords = "audit_records" // 审核记录表
  39. TableDataExportOrder = "dataexport_order" // 订单表
  40. )
  41. type ExamineDiscountStr struct {
  42. ExamineDiscount map[string][]Discount `json:"examineDiscount"`
  43. }
  44. type Discount struct {
  45. Min int `json:"min"`
  46. Max int `json:"max"`
  47. DiscountRate float64 `json:"discountRate"`
  48. }
  49. var (
  50. GiftDiscount float64
  51. ExamineDiscountConfig map[string][]Discount
  52. )
  53. func init() {
  54. err := g.Cfg().MustGet(context.Background(), "examineDiscount").Scan(&ExamineDiscountConfig)
  55. if err != nil {
  56. log.Println("examineDiscount", err)
  57. }
  58. GiftDiscount = g.Cfg().MustGet(context.Background(), "giftDiscount", 0.6).Float64()
  59. }
  60. // OrdersExamine 订单审核
  61. func OrdersExamine(ctx context.Context, param model.OrdersExamine) error {
  62. //查询订单
  63. orderData, err := g.DB().Ctx(ctx).GetOne(ctx, fmt.Sprintf(`SELECT * FROM dataexport_order WHERE order_code ='%s'`, param.OrderCode))
  64. if err != nil || orderData.IsEmpty() {
  65. return gerror.Wrap(err, "查询订单表异常")
  66. }
  67. oldAuditStatus := gconv.Int(orderData.Map()["audit_status"])
  68. if oldAuditStatus == 3 {
  69. return errors.New("订单审核已完成,无需审批")
  70. }
  71. newAuditStatus := 1
  72. operatorType := 2
  73. //通过
  74. switch param.State == 1 {
  75. case true:
  76. switch oldAuditStatus {
  77. case 1:
  78. newAuditStatus = 2
  79. case 2:
  80. newAuditStatus = 4
  81. operatorType = 3
  82. case 4:
  83. newAuditStatus = 3
  84. operatorType = 5 //三审
  85. }
  86. case false:
  87. switch oldAuditStatus {
  88. case 1:
  89. newAuditStatus = -2
  90. case 2:
  91. newAuditStatus = -3
  92. operatorType = 3
  93. case 4:
  94. newAuditStatus = -4
  95. operatorType = 5
  96. }
  97. }
  98. var (
  99. contractArchiveTime string
  100. auditStatus int
  101. )
  102. //数据库操作
  103. return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
  104. auditStatus = newAuditStatus
  105. var checkAuto bool
  106. productDetail, err := g.DB().Ctx(ctx).Query(ctx, fmt.Sprintf(`SELECT * FROM jy_order_detail WHERE order_code ='%s' and status =1`, param.OrderCode))
  107. if err != nil || productDetail.IsEmpty() {
  108. return err
  109. }
  110. if oldAuditStatus == 1 && param.State == 1 {
  111. //待一审 审核通过校验是否直接通过
  112. if CheckAutoAudit(ctx, orderData.Map(), productDetail.List()) {
  113. newAuditStatus = 3
  114. checkAuto = true
  115. }
  116. }
  117. upData := map[string]interface{}{
  118. "audit_status": newAuditStatus,
  119. }
  120. if newAuditStatus == 3 {
  121. //“协议归档状态”如若为“已归档”,则“订单状态”更新为“已完成”,如若为“未归档”,则订单状态仍为“未完成”
  122. contractData, err := g.DB().Ctx(ctx).GetOne(ctx, fmt.Sprintf(`SELECT contract_archive_status,contract_archive_time FROM contract where order_code = '%s'`, param.OrderCode))
  123. if err == nil && !contractData.IsEmpty() {
  124. if common.IntAll(contractData.Map()["contract_archive_status"]) == 1 {
  125. upData["order_status"] = 1
  126. contractArchiveTime = common.ObjToString(contractData.Map()["contract_archive_time"])
  127. }
  128. }
  129. //0元订单审核通过后直接开通权益
  130. if common.Float64All((orderData.Map())["pay_money"]) == 0 && consts.PhoneRegex.MatchString(common.ObjToString((orderData.Map())["user_phone"])) {
  131. upData["order_status"] = 1
  132. uData, entId, userPositionId, err := jyutil.GetCreateUserData(gconv.String(orderData.Map()["user_phone"]), gconv.String(orderData.Map()["company_name"]), gconv.Int(orderData.Map()["buy_subject"]) == 2)
  133. if err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
  134. // 产品服务开通
  135. for _, m := range productDetail.List() {
  136. if !jyutil.IsServiceOpen(m) {
  137. continue
  138. }
  139. //参数注入
  140. //参数注入
  141. m["userMap"] = map[string]interface{}{
  142. "userData": uData, "entId": entId, "userPositionId": userPositionId,
  143. }
  144. m["phone"] = orderData["user_phone"]
  145. m["order_code"] = param.OrderCode
  146. m["reqCompanyName"] = orderData.Map()["company_name"]
  147. m["amount"] = m["final_price"]
  148. m["reqSubject"] = orderData.Map()["buy_subject"]
  149. m["linked_orderId"] = m["linked_detail_id"]
  150. productCode := gconv.String(m["product_code"])
  151. pFunc, err := product.JyProFunc.GetProductInitFuncByCode(productCode)
  152. if err != nil {
  153. return err
  154. }
  155. pObj, err := pFunc(m)
  156. if err != nil {
  157. return gerror.Wrap(err, fmt.Sprintf("获取%s商品异常", productCode))
  158. }
  159. if err := pObj.OpenService(ctx, time.Now()); err != nil {
  160. return err
  161. }
  162. }
  163. if orderUserId := gconv.String(orderData.Map()["user_id"]); orderUserId == "" || orderUserId != gconv.String(uData["userId"]) {
  164. log.Printf("同步更新订单用户身份:orderUserId:%s,userId:%s,entId:%d\n", orderUserId, uData["userId"], entId)
  165. upData := g.Map{
  166. "user_id": uData["userId"],
  167. }
  168. if entId > 0 { //企业服务
  169. upData["ent_id"] = entId
  170. if personPhone := gconv.String(orderData.Map()["personPhone"]); personPhone != "" {
  171. jyutil.EndAddUser(ctx, entId, gconv.String(orderData.Map()["user_phone"]), personPhone, gconv.String(orderData.Map()["personName"]))
  172. }
  173. }
  174. //更新订单
  175. _, err = g.DB().Update(ctx, consts.OrderListTableName, upData, "order_code=?", param.OrderCode)
  176. if err != nil {
  177. return err
  178. }
  179. }
  180. return nil
  181. }); err != nil {
  182. log.Println(err)
  183. return err
  184. }
  185. }
  186. }
  187. //更新订单
  188. _, err = g.DB().Ctx(ctx).Update(ctx, "dataexport_order", upData, map[string]interface{}{"order_code": param.OrderCode})
  189. if err != nil {
  190. return err
  191. }
  192. //插入审核记录表
  193. tn := time.Now()
  194. insertData := map[string]interface{}{
  195. "operator": jyutil.GetUserMsgFromCtx(ctx).EntUserName, //审核操作人
  196. "create_time": tn.Format(date.Date_Full_Layout),
  197. "operator_type": operatorType,
  198. "audit_status": auditStatus,
  199. "back_reason": param.Reason,
  200. "order_code": param.OrderCode,
  201. "audit_type": 2,
  202. }
  203. _, err = g.DB().Ctx(ctx).Insert(ctx, "audit_records", insertData)
  204. if err != nil {
  205. return err
  206. }
  207. if checkAuto {
  208. // 给定的数据
  209. columns := strings.Split("operator,create_time,operator_type,audit_status,back_reason,order_code,audit_type", ",")
  210. values := []interface{}{"系统自动", time.Now().Add(time.Second).Format("2006-01-02 15:04:05"), 3, OrderSecondPassed, "系统自动审核通过", param.OrderCode, 2,
  211. "系统自动", time.Now().Add(2 * time.Second).Format("2006-01-02 15:04:05"), 5, OrderPassed, "系统自动审核通过", param.OrderCode, 2}
  212. // 构建SQL插入语句
  213. query := fmt.Sprintf("INSERT INTO audit_records (%s) VALUES (?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?)", strings.Join(columns, ","))
  214. g.DB().Ctx(ctx).Exec(ctx, query, values...)
  215. }
  216. if contractArchiveTime != "" {
  217. _ = CommonChange(ctx, param.OrderCode, contractArchiveTime, ReturnProtocol)
  218. }
  219. return nil
  220. })
  221. }
  222. // CheckAutoAudit 校验自动审核
  223. func CheckAutoAudit(ctx context.Context, orderData map[string]interface{}, productList []map[string]interface{}) bool {
  224. if common.Float64All(orderData["order_money"]) == 0 || common.Float64All(orderData["pay_money"]) == 0 {
  225. return false
  226. }
  227. var contractPass bool
  228. //是否支持线上自动生成电子协议
  229. /*
  230. 判断是否符合合同
  231. (1)订单审核状态是“已通过”;
  232. (2)签约主体为“北京剑鱼信息技术有限公司”,注:如签约主体为拓普则线下生成合同;
  233. (3)协议状态为“签协议”(“已签协议”文案修改为“签协议”,涉及:创建订单、订单审核、订单详情)
  234. (4)产品类型是“超级订阅”(且付费类型为“购买”、“续费”),或产品类型是“大会员”且会员套餐为“商机版2.0”、“专家版2.0”(且服务类型为“新购服务”、“延长服务”),注:超级订阅/大会员升级和其他产品类型,线下生成合同。
  235. */
  236. if orderData["signing_subject"] != "h01" {
  237. log.Println("自动审核不通过signing_subject:", orderData["signing_subject"])
  238. return false
  239. }
  240. isEnt := gconv.Int(orderData["buy_subject"]) == 2
  241. var (
  242. insertData []map[string]interface{}
  243. activityProduct = make(map[string][]map[string]interface{})
  244. isXwlp bool
  245. beforePrice, afterPrice float64
  246. giftDiscountRate []float64
  247. )
  248. for _, m := range productList {
  249. productItem, err := service.Product().GetProduct(gconv.String(m["product_code"]))
  250. if err != nil {
  251. return false
  252. }
  253. productClass, err := service.Product().GetProductClass(productItem.ProductClassId)
  254. if err != nil {
  255. return false
  256. }
  257. //外采赠品
  258. // 需“判断订单中常规商品/活动产品的折扣率是否都符合自动审核规则“的同时,
  259. //除外采赠品外的其余产品的整体折扣率不得低于XX%,才能自动审核,否则需要人工审核。
  260. switch productClass.TopClass == "外采赠品" {
  261. case true:
  262. if productItem.ApprovalRules == nil || productItem.ApprovalRules["rate"] == nil ||
  263. gconv.Int(m["final_price"]) > gconv.Int(productItem.ApprovalRules["contract_amount_max"]) ||
  264. gconv.Int(m["final_price"]) < gconv.Int(productItem.ApprovalRules["contract_amount_mix"]) {
  265. return false
  266. }
  267. isXwlp = true
  268. giftDiscountRate = append(giftDiscountRate, gconv.Float64(productItem.ApprovalRules["rate"]))
  269. continue
  270. case false:
  271. beforePrice += gconv.Float64(m["original_price"])
  272. afterPrice += gconv.Float64(m["final_price"])
  273. }
  274. if common.Float64All(m["original_price"]) < 0 {
  275. return false
  276. }
  277. if activityCode := gconv.String(m["activity_code"]); activityCode != "" { //活动产品统一校验
  278. activityProduct[activityCode] = append(activityProduct[activityCode], m)
  279. continue
  280. }
  281. filterMap := make(map[string]interface{})
  282. filterData := common.ObjToString(m["filter"])
  283. if err := json.Unmarshal([]byte(filterData), &filterMap); err != nil {
  284. log.Println("filter unmarshal err:", orderData["order_code"], err.Error())
  285. return false
  286. }
  287. var (
  288. cycleType, cycleCount, monthCount int
  289. examineDiscount float64
  290. )
  291. conditionalMap := make(map[string]interface{})
  292. switch productType := common.InterfaceToStr(m["product_type"]); productType {
  293. case "VIP订阅":
  294. switch common.IntAll(m["service_type"]) {
  295. case 3: //升级
  296. monthCount = GetUserTime(gconv.String(orderData["user_phone"]), gconv.String(m["linked_detail_id"]), true, isEnt)
  297. default:
  298. cycleType = common.If(common.IntAll(filterMap["buy_type"]) > 0, common.IntAll(filterMap["buy_type"]), common.IntAll(filterMap["give_type"])).(int) //1天 2月 3年 4季度
  299. cycleCount = common.IntAll(filterMap["buy_cycle"]) + common.IntAll(filterMap["give_cycle"])
  300. switch cycleType { //(1天 2月 3年 4季度)
  301. case 3:
  302. monthCount = cycleCount * 12
  303. case 2:
  304. monthCount = cycleCount
  305. case 1:
  306. if cycleCount%30 > 0 {
  307. monthCount = cycleCount/30 + 1
  308. } else {
  309. monthCount = cycleCount / 30
  310. }
  311. case 4:
  312. monthCount = cycleCount * 3
  313. }
  314. }
  315. //折扣率满足一定条件
  316. for _, discount := range ExamineDiscountConfig[productType] {
  317. if monthCount >= discount.Min && monthCount < discount.Max {
  318. examineDiscount = discount.DiscountRate
  319. conditionalMap["min"] = discount.Min
  320. conditionalMap["max"] = discount.Max
  321. conditionalMap["product_type"] = productType
  322. conditionalMap["discountRate"] = discount.DiscountRate
  323. break
  324. }
  325. }
  326. case "大会员":
  327. if common.IntAll(filterMap["comboId"]) <= 0 {
  328. return false
  329. }
  330. //大会员 createType:1新建 2补充 3延长
  331. switch common.IntAll(m["service_type"]) {
  332. case 3: //升级
  333. monthCount = GetUserTime(gconv.String(orderData["user_phone"]), gconv.String(orderData["linked_detail_id"]), true, isEnt)
  334. default:
  335. cycleType = common.If(common.IntAll(filterMap["buy_type"]) > 0, common.IntAll(filterMap["buy_type"]), common.IntAll(filterMap["give_type"])).(int) //1天 2月 3年 4季度
  336. cycleCount = common.IntAll(filterMap["buy_cycle"]) + common.IntAll(filterMap["give_cycle"])
  337. switch cycleType { //(1天 2月 3年 4季度)
  338. case 3:
  339. monthCount = cycleCount * 12
  340. case 2:
  341. monthCount = cycleCount
  342. case 1:
  343. if cycleCount%30 > 0 {
  344. monthCount = cycleCount/30 + 1
  345. } else {
  346. monthCount = cycleCount / 30
  347. }
  348. case 4:
  349. monthCount = cycleCount * 3
  350. }
  351. }
  352. //折扣率满足一定条件
  353. for _, discount := range ExamineDiscountConfig[productType] {
  354. switch discount.Max == -1 {
  355. case true:
  356. if monthCount >= discount.Min {
  357. examineDiscount = discount.DiscountRate
  358. conditionalMap["min"] = discount.Min
  359. conditionalMap["max"] = discount.Max
  360. conditionalMap["product_type"] = productType
  361. conditionalMap["discountRate"] = discount.DiscountRate
  362. break
  363. }
  364. case false:
  365. if monthCount >= discount.Min && monthCount < discount.Max {
  366. examineDiscount = discount.DiscountRate
  367. conditionalMap["min"] = discount.Min
  368. conditionalMap["max"] = discount.Max
  369. conditionalMap["product_type"] = productType
  370. conditionalMap["discountRate"] = discount.DiscountRate
  371. break
  372. }
  373. }
  374. }
  375. default:
  376. examineDiscount = productClass.Rate
  377. }
  378. if examineDiscount <= 0 {
  379. log.Println("自动审核商品折扣率获取失败")
  380. return false
  381. }
  382. log.Println("自动审核商品折扣:", m["id"], cycleType, monthCount, roundToTwoDecimalPlaces(common.Float64All(m["final_price"])/common.Float64All(m["original_price"])), examineDiscount)
  383. contractPass = roundToTwoDecimalPlaces(common.Float64All(m["final_price"])/common.Float64All(m["original_price"])) >= examineDiscount
  384. if !contractPass {
  385. return false
  386. }
  387. conditionalStr, _ := json.Marshal(conditionalMap)
  388. insertData = append(insertData, map[string]interface{}{
  389. "create_time": time.Now().Format("2006-01-02 15:04:05"),
  390. "pay_money": gconv.Float64(m["final_price"]),
  391. "original_price": gconv.Float64(m["original_price"]),
  392. "discount_rate": common.Float64All(m["final_price"]) / common.Float64All(m["original_price"]),
  393. "order_code": gconv.Float64(m["order_code"]),
  394. "product_type": common.InterfaceToStr(m["product_type"]),
  395. "monthCount": monthCount,
  396. "conditional_remarks": string(conditionalStr),
  397. })
  398. }
  399. if len(activityProduct) > 0 { //校验活动产品
  400. for activityCode, activityData := range activityProduct {
  401. activity, err := g.DB().Ctx(ctx).GetOne(ctx, fmt.Sprintf(`SELECT rate FROM jy_product_activity WHERE code ='%s'`, activityCode))
  402. if err != nil || activity.IsEmpty() {
  403. return false
  404. }
  405. var (
  406. activityOriginalPrice, activityClosingPrice float64
  407. activityInsertData []map[string]interface{}
  408. )
  409. for _, datum := range activityData { //同种活动 统一计算折扣率
  410. activityOriginalPrice += gconv.Float64(datum["original_price"])
  411. activityClosingPrice += gconv.Float64(datum["final_price"])
  412. activityInsertData = append(activityInsertData, map[string]interface{}{
  413. "create_time": time.Now().Format("2006-01-02 15:04:05"),
  414. "pay_money": gconv.Float64(datum["final_price"]),
  415. "original_price": gconv.Float64(datum["original_price"]),
  416. "discount_rate": common.Float64All(datum["final_price"]) / common.Float64All(datum["original_price"]),
  417. "order_code": gconv.Float64(datum["order_code"]),
  418. "product_type": common.InterfaceToStr(datum["product_type"]),
  419. "monthCount": -1,
  420. "conditional_remarks": fmt.Sprintf("活动产品:活动id:%s 活动折扣率:%f", activityCode, gconv.Float64(activity.Map()["rate"])),
  421. })
  422. }
  423. contractPass = roundToTwoDecimalPlaces(activityClosingPrice/activityOriginalPrice) >= gconv.Float64(activity.Map()["rate"])
  424. if !contractPass {
  425. return false
  426. }
  427. //记录活动自动审核通过记录
  428. insertData = append(insertData, activityInsertData...)
  429. }
  430. }
  431. if isXwlp { //存在赠送产品 每个赠送产品单独计算折扣率
  432. if afterPrice == 0 {
  433. return false
  434. }
  435. for _, f := range giftDiscountRate {
  436. if beforePrice/afterPrice < f {
  437. return false
  438. }
  439. }
  440. }
  441. if len(insertData) > 0 {
  442. g.DB().Save(ctx, "automatic_audit_log", insertData)
  443. }
  444. return true
  445. }
  446. func roundToTwoDecimalPlaces(x float64) float64 {
  447. return math.Round(x*10000) / 10000
  448. }
  449. func GetUserTime(phone, orderId string, isVip, isEnt bool) (monthCount int) {
  450. switch isEnt {
  451. case false:
  452. //查询用户大会员权益剩余服务周期
  453. userData, ok := jyutil.MG.DB().FindOne("user", map[string]interface{}{
  454. "$or": []map[string]interface{}{
  455. {"s_phone": phone},
  456. {"s_m_phone": phone},
  457. },
  458. })
  459. if !ok || userData == nil || len(*userData) == 0 {
  460. return monthCount
  461. }
  462. var startTime, endTime time.Time
  463. if isVip {
  464. startTime = time.Unix(common.Int64All((*userData)["l_vip_starttime"]), 0).Local()
  465. endTime = time.Unix(common.Int64All((*userData)["l_vip_endtime"]), 0).Local()
  466. } else {
  467. startTime = time.Unix(common.Int64All((*userData)["i_member_starttime"]), 0).Local()
  468. endTime = time.Unix(common.Int64All((*userData)["i_member_endtime"]), 0).Local()
  469. }
  470. if startTime.Unix() < time.Now().Unix() {
  471. startTime = time.Now()
  472. }
  473. return GetMonthNum(startTime, endTime)
  474. case true:
  475. waitEmpower, err := g.DB().Ctx(context.Background()).GetOne(context.Background(), fmt.Sprintf(`
  476. SELECT e.start_time,e.end_time FROM entniche_order e
  477. INNER JOIN entniche_wait_empower w on e.wait_empower_id = w.id
  478. WHERE e.order_detail_id = %s`, orderId))
  479. if err == nil || waitEmpower.IsEmpty() {
  480. return monthCount
  481. }
  482. var startTime, endTime time.Time
  483. startTime = jyutil.StrToTime(gconv.String(waitEmpower.Map()["start_time"]), "2006-01-02 15:04:05")
  484. endTime = time.Unix(common.Int64All(waitEmpower.Map()["end_time"]), 0).Local()
  485. if startTime.Unix() < time.Now().Unix() {
  486. startTime = time.Now()
  487. }
  488. return GetMonthNum(startTime, endTime)
  489. }
  490. return monthCount
  491. }
  492. func GetMonthNum(startTime, endTime time.Time) int {
  493. // 定义开始时间和结束时间
  494. //start := time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC)
  495. //end := time.Date(2024, time.March, 30, 0, 0, 0, 0, time.UTC)
  496. // 获取开始时间和结束时间的年份和月份
  497. startYear, startMonth, startDay := startTime.Date()
  498. endYear, endMonth, endDay := endTime.Date()
  499. // 计算月份差异
  500. months := (endYear-startYear)*12 + int(endMonth-startMonth)
  501. if endDay-startDay > 0 {
  502. months += 1
  503. }
  504. //fmt.Printf("月份差异为:%d\n", months)
  505. return months
  506. }