AgentOrderController.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. # -*- encoding: utf-8 -*-
  2. """
  3. @File : AgentOrderController.py
  4. @Time : 2024/3/14 10:53
  5. @Author : stephen
  6. @Email : zhangdongming@asj6.wecom.work
  7. @Software: PyCharm
  8. """
  9. import threading
  10. import time
  11. from datetime import datetime, timedelta
  12. from decimal import Decimal, ROUND_DOWN
  13. from django.http import QueryDict
  14. from django.views import View
  15. from AgentModel.models import AgentDevice, AgentCloudServicePackage, AgentCustomerPackage, AgentDeviceOrder, \
  16. AgentDeviceOrderInstallment, AgentAccount
  17. from Ansjer.config import LOGGER
  18. from Model.models import Order_Model, Store_Meal, UnicomCombo
  19. from Object.RedisObject import RedisObject
  20. from Object.ResponseObject import ResponseObject
  21. from Object.TokenObject import TokenObject
  22. from Service.CommonService import CommonService
  23. class AgentOrderView(View):
  24. def get(self, request, *args, **kwargs):
  25. request.encoding = 'utf-8'
  26. operation = kwargs.get('operation')
  27. return self.validation(request.GET, request, operation)
  28. def post(self, request, *args, **kwargs):
  29. request.encoding = 'utf-8'
  30. operation = kwargs.get('operation')
  31. return self.validation(request.POST, request, operation)
  32. def delete(self, request, *args, **kwargs):
  33. request.encoding = 'utf-8'
  34. operation = kwargs.get('operation')
  35. delete = QueryDict(request.body)
  36. if not delete:
  37. delete = request.GET
  38. return self.validation(delete, request, operation)
  39. def put(self, request, *args, **kwargs):
  40. request.encoding = 'utf-8'
  41. operation = kwargs.get('operation')
  42. put = QueryDict(request.body)
  43. return self.validation(put, request, operation)
  44. def validation(self, request_dict, request, operation):
  45. response = ResponseObject()
  46. if operation == 'settlementOrder': # 季度结算
  47. asy = threading.Thread(target=AgentOrderView.update_periodic_settlement,args=())
  48. asy.start()
  49. return response.json(0)
  50. tko = TokenObject(
  51. request.META.get('HTTP_AUTHORIZATION'),
  52. returntpye='pc')
  53. if operation == 'addOrder': # 添加代理商订单
  54. order_id = request_dict.get('order_id', None)
  55. uid = request_dict.get('uid', None)
  56. order_type = request_dict.get('order_type', None)
  57. package_id = request_dict.get('package_id', None)
  58. self.check_agent_service_package(order_id, uid, int(package_id))
  59. return response.json(0)
  60. else:
  61. return response.json(414)
  62. @classmethod
  63. def check_agent_service_package(cls, order_id, uid, package_id):
  64. """
  65. 检查是否代理服务套餐
  66. @param package_id: 套餐id
  67. @param order_id: 订单ID
  68. @param uid: UID
  69. @return: True | False
  70. """
  71. try:
  72. serial_number = CommonService.get_serial_number_by_uid(uid)
  73. a_device_qs = AgentDevice.objects.filter(serial_number=serial_number) \
  74. .values('ac_id', 'type', 'status')
  75. LOGGER.info(f'检查当前订单是否绑定代理*****orderID:{order_id},serialNumber:{serial_number}')
  76. if not a_device_qs.exists():
  77. return False
  78. LOGGER.info(f'当前设备属于代理商orderID:{order_id},serialNumber:{serial_number}')
  79. asy = threading.Thread(target=cls.save_agent_package,
  80. args=(order_id, serial_number, a_device_qs[0]['ac_id'], package_id))
  81. asy.start()
  82. return True
  83. except Exception as e:
  84. LOGGER.error('*****支付成功保存云服务代理订单异常orderID:{},errLine:{}, errMsg:{}'
  85. .format(order_id, e.__traceback__.tb_lineno, repr(e)))
  86. return False
  87. @classmethod
  88. def save_agent_package(cls, order_id, serial_number, ac_id, package_id):
  89. """
  90. 保存代理套餐
  91. """
  92. try:
  93. order_qs = Order_Model.objects.filter(orderID=order_id, status=1).values('price', 'payTime', 'order_type')
  94. if not order_qs.exists():
  95. LOGGER.info(
  96. f'******save_agent_package当前代理客户未添加此套餐******ac_id:{ac_id},package_id:{package_id}')
  97. return
  98. order_type = order_qs[0]['order_type']
  99. package_type = 2 if order_type in [2, 3, 5] else 1 # 判断订单信息是云存还是4G
  100. package_id = int(package_id)
  101. agent_package_qs = AgentCloudServicePackage.objects.filter(type=package_type, package_id=package_id,
  102. status=1)
  103. if not agent_package_qs.exists():
  104. LOGGER.info(
  105. f'******save_agent_package当前套餐未设置代理******order_id:{order_id},serial_number:{serial_number}')
  106. return
  107. agent_package = agent_package_qs.first() # 代理云服务套餐
  108. LOGGER.info(f'******save_agent_package代理套餐******service_name:{agent_package_qs.first().service_name}')
  109. acp_qs = AgentCustomerPackage.objects.filter(ac_id=ac_id, cs_id=agent_package.id).values('id')
  110. if not acp_qs.exists():
  111. LOGGER.info(
  112. f'******save_agent_package当前代理客户未添加此套餐******ac_id:{ac_id},package_id:{package_id}')
  113. return
  114. # 组装数据
  115. now_time = int(time.time())
  116. pay_price = Decimal(order_qs[0]['price']).quantize(Decimal('0.00'))
  117. profit = cls.calculate_order_profit(agent_package, pay_price)
  118. dict_data = {'ac_id': ac_id, 'serial_number': serial_number, 'csp_id': agent_package.id,
  119. 'order_id': order_id, 'status': 1, 'profit_amount': pay_price, 'profit': profit,
  120. 'pay_time': order_qs[0]['payTime'], 'created_time': now_time, 'updated_time': now_time}
  121. agent_order_obj = AgentDeviceOrder.objects.create(**dict_data)
  122. # 保存分期结算记录
  123. cls.save_order_installment(agent_order_obj.id, package_type, package_id, profit, ac_id,
  124. order_qs[0]['payTime'])
  125. LOGGER.info(f'******save_agent_package代理订单存表结束:{dict_data}')
  126. except Exception as e:
  127. LOGGER.info('*****AgentOrderView.save_agent_package:errLine:{}, errMsg:{}'
  128. .format(e.__traceback__.tb_lineno, repr(e)))
  129. @classmethod
  130. def calculate_order_profit(cls, agent_package, price):
  131. """
  132. 计算利润
  133. @param agent_package: 套餐配置
  134. @param price: 支付价格
  135. @return: 利润
  136. """
  137. profit = 0
  138. price = Decimal(price).quantize(Decimal('0.00'))
  139. if agent_package.profit_type == 1:
  140. profit = agent_package.profit
  141. elif agent_package.profit_type == 2:
  142. profit_value = Decimal(agent_package.profit).quantize(Decimal('0.00'))
  143. cost = Decimal(agent_package.cost).quantize(Decimal('0.00'))
  144. profit = (price - cost) * (profit_value / 100)
  145. profit = profit.quantize(Decimal('0.00'))
  146. return profit
  147. @classmethod
  148. def get_quarterly_settlement_dates(cls, start_date, months):
  149. """
  150. 获取季度结算日期列表,按照以下规则:
  151. 1. 固定在四个季度结算日期(1月1日、4月1日、7月1日、10月1日)进行结算
  152. 2. 从购买时间到结算日不满1个月的不在当前季度结算,累积到下一个季度
  153. 3. 中间季度每季度计算3个月
  154. 4. 最后一个季度计算剩余的时间
  155. :param start_date: 套餐开始日期(datetime)
  156. :param months: 套餐总月数
  157. :return: 包含(结算日期timestamp, 该季度使用月数)的元组列表
  158. """
  159. # 固定的季度结算日期
  160. QUARTER_DATES = [(1, 1), (4, 1), (7, 1), (10, 1)] # (月, 日)
  161. # 计算套餐结束日期
  162. end_date = start_date + timedelta(days=int(months * 30.5))
  163. # 初始化结果列表
  164. result = []
  165. # 找到开始日期后的第一个季度结算日
  166. current_year = start_date.year
  167. current_quarter_idx = 0
  168. # 找到开始日期之后的第一个季度结算日
  169. for i, (month, day) in enumerate(QUARTER_DATES):
  170. quarter_date = datetime(current_year, month, day)
  171. if quarter_date > start_date:
  172. current_quarter_idx = i
  173. break
  174. else:
  175. # 如果当年没有更多季度结算日,则移到下一年的第一个季度结算日
  176. current_year += 1
  177. current_quarter_idx = 0
  178. # 第一个季度的结算日期
  179. month, day = QUARTER_DATES[current_quarter_idx]
  180. first_settlement_date = datetime(current_year, month, day)
  181. # 计算第一个季度的整月数
  182. days_in_first_quarter = (first_settlement_date - start_date).days
  183. whole_months_first_quarter = int(days_in_first_quarter / 30.5)
  184. # 计算剩余的月数(总月数减去第一个季度的整月数,如果第一个季度有整月数)
  185. remaining_months = months
  186. # 如果第一个季度有整月数,则添加第一个季度的结算记录并减去已结算的月数
  187. if whole_months_first_quarter >= 1:
  188. result.append((int(first_settlement_date.timestamp()), whole_months_first_quarter))
  189. remaining_months -= whole_months_first_quarter
  190. else:
  191. # 即使不足1个月,也添加第一个季度的结算记录,但月数为0
  192. # 这样可以确保在7月1日进行第一次结算
  193. result.append((int(first_settlement_date.timestamp()), 0))
  194. # 如果没有剩余月数,直接返回结果
  195. if remaining_months <= 0:
  196. return result
  197. # 特殊处理年套餐(12个月)的情况
  198. if months == 12 and whole_months_first_quarter == 0:
  199. # 确保总共有5次季度结算,最后一次是剩余的月数
  200. # 第一次结算已经添加(7月1日,整月数为0)
  201. # 添加第二次结算(10月1日,整月数为3)
  202. current_quarter_idx = (current_quarter_idx + 1) % len(QUARTER_DATES)
  203. if current_quarter_idx == 0:
  204. current_year += 1
  205. month, day = QUARTER_DATES[current_quarter_idx]
  206. settlement_date = datetime(current_year, month, day)
  207. result.append((int(settlement_date.timestamp()), 3))
  208. # 添加第三次结算(1月1日,整月数为3)
  209. current_quarter_idx = (current_quarter_idx + 1) % len(QUARTER_DATES)
  210. if current_quarter_idx == 0:
  211. current_year += 1
  212. month, day = QUARTER_DATES[current_quarter_idx]
  213. settlement_date = datetime(current_year, month, day)
  214. result.append((int(settlement_date.timestamp()), 3))
  215. # 添加第四次结算(4月1日,整月数为3)
  216. current_quarter_idx = (current_quarter_idx + 1) % len(QUARTER_DATES)
  217. if current_quarter_idx == 0:
  218. current_year += 1
  219. month, day = QUARTER_DATES[current_quarter_idx]
  220. settlement_date = datetime(current_year, month, day)
  221. result.append((int(settlement_date.timestamp()), 3))
  222. # 添加第五次结算(7月1日,整月数为剩余的月数)
  223. current_quarter_idx = (current_quarter_idx + 1) % len(QUARTER_DATES)
  224. if current_quarter_idx == 0:
  225. current_year += 1
  226. month, day = QUARTER_DATES[current_quarter_idx]
  227. settlement_date = datetime(current_year, month, day)
  228. # 剩余的月数为12减去前面已经结算的月数
  229. remaining = 12 - (0 + 3 + 3 + 3)
  230. result.append((int(settlement_date.timestamp()), remaining))
  231. return result
  232. # 非年套餐的处理逻辑
  233. # 计算完整季度的数量(每季度3个月)
  234. full_quarters = int(remaining_months / 3)
  235. # 计算最后一个季度的剩余月数
  236. last_quarter_months = remaining_months % 3
  237. # 当前日期设置为第一个结算日期
  238. current_date = first_settlement_date
  239. # 添加完整季度的结算记录
  240. for _ in range(full_quarters):
  241. # 移到下一个季度结算日
  242. current_quarter_idx = (current_quarter_idx + 1) % len(QUARTER_DATES)
  243. if current_quarter_idx == 0:
  244. current_year += 1
  245. month, day = QUARTER_DATES[current_quarter_idx]
  246. settlement_date = datetime(current_year, month, day)
  247. # 添加完整季度的结算记录(3个月)
  248. result.append((int(settlement_date.timestamp()), 3))
  249. # 更新当前日期
  250. current_date = settlement_date
  251. # 如果有剩余月数,添加最后一个季度的结算记录
  252. if last_quarter_months > 0:
  253. # 移到下一个季度结算日
  254. current_quarter_idx = (current_quarter_idx + 1) % len(QUARTER_DATES)
  255. if current_quarter_idx == 0:
  256. current_year += 1
  257. month, day = QUARTER_DATES[current_quarter_idx]
  258. settlement_date = datetime(current_year, month, day)
  259. # 添加最后一个季度的结算记录(剩余月数)
  260. result.append((int(settlement_date.timestamp()), last_quarter_months))
  261. return result
  262. @staticmethod
  263. def calculate_months_in_period(start_date, end_date):
  264. """
  265. 计算两个日期之间的整月数,不足一个月不计入
  266. :param start_date: 开始日期
  267. :param end_date: 结束日期
  268. :return: 整月数
  269. """
  270. # 计算天数差
  271. days_diff = (end_date - start_date).days
  272. # 转换为整月数(按平均每月30.5天计算)
  273. whole_months = int(days_diff / 30.5)
  274. return whole_months
  275. @staticmethod
  276. def calculate_quarterly_profit(profit, settlement_dates_with_months):
  277. """
  278. 计算季度利润分配,基于整月数
  279. :param profit: 总利润
  280. :param settlement_dates_with_months: 包含(结算日期, 整月数)的元组列表
  281. :return: 每个季度的利润列表
  282. """
  283. profit = Decimal(str(profit)).quantize(Decimal('0.01'))
  284. # 计算总整月数
  285. total_months = sum(months for _, months in settlement_dates_with_months)
  286. # 如果总月数为0,返回空列表
  287. if total_months == 0:
  288. return []
  289. # 计算每月利润
  290. monthly_profit = profit / Decimal(total_months)
  291. # 计算每个季度的利润
  292. quarterly_amounts = []
  293. for _, months in settlement_dates_with_months:
  294. # 计算当前季度的利润(基于整月数)
  295. amount = (monthly_profit * Decimal(months)).quantize(Decimal('0.01'), rounding=ROUND_DOWN)
  296. quarterly_amounts.append(amount)
  297. # 处理舍入误差,确保总和等于总利润
  298. total_allocated = sum(quarterly_amounts)
  299. remainder = profit - total_allocated
  300. # 将剩余的分配到第一个季度
  301. if remainder > Decimal('0') and quarterly_amounts:
  302. quarterly_amounts[0] += remainder
  303. return quarterly_amounts
  304. @classmethod
  305. def save_order_installment(cls, agent_order_id, package_type, package_id, profit, ac_id=None, pay_time=0):
  306. """
  307. 保存代理订单分期信息(季度结算逻辑),不满一个月的时间累积到下一个季度
  308. :param cls: 类方法的约定参数
  309. :param agent_order_id: 代理订单ID
  310. :param package_type: 套餐类型(1:云存, 2:4G)
  311. :param package_id: 套餐ID
  312. :param profit: 利润总额
  313. :param ac_id: 代理客户ID
  314. :param pay_time: 订单支付时间
  315. :return: 无返回值
  316. """
  317. try:
  318. # 转换支付时间为datetime对象
  319. pay_time_dt = datetime.fromtimestamp(pay_time)
  320. # 获取套餐月数
  321. if package_type == 1: # 云存
  322. store = Store_Meal.objects.filter(id=package_id).first()
  323. if not store:
  324. LOGGER.info(f'云存套餐不存在: {package_id}')
  325. return
  326. months = store.expire
  327. else: # 4G
  328. combo = UnicomCombo.objects.filter(id=package_id).first()
  329. if not combo:
  330. LOGGER.info(f'4G套餐不存在: {package_id}')
  331. return
  332. months = int(combo.expiration_days / 30)
  333. if months <= 0 or profit <= 0:
  334. LOGGER.info(f'无效参数: months={months}, profit={profit}')
  335. return
  336. LOGGER.info(
  337. f'开始计算季度结算: 订单ID={agent_order_id}, 开始日期={pay_time_dt}, 套餐月数={months}, 总利润={profit}')
  338. # 获取季度结算日期和每个季度的整月数
  339. settlement_dates_with_months = cls.get_quarterly_settlement_dates(pay_time_dt, months)
  340. # 记录季度结算日期和月数
  341. for i, (date, months_used) in enumerate(settlement_dates_with_months):
  342. date_str = datetime.fromtimestamp(date).strftime('%Y-%m-%d')
  343. LOGGER.info(f'季度{i + 1}结算日期: {date_str}, 整月数: {months_used}')
  344. # 如果没有有效的结算日期,则退出
  345. if not settlement_dates_with_months:
  346. LOGGER.info(f'没有有效的季度结算日期: start_date={pay_time_dt}, months={months}')
  347. return
  348. # 计算每个季度的利润分配
  349. amounts = cls.calculate_quarterly_profit(profit, settlement_dates_with_months)
  350. # 记录每个季度的利润分配
  351. for i, amount in enumerate(amounts):
  352. LOGGER.info(f'季度{i + 1}利润分配: {amount}')
  353. # 创建分期记录
  354. n_time = int(time.time())
  355. installment_list = []
  356. for i, ((settlement_date, months_used), amount) in enumerate(zip(settlement_dates_with_months, amounts)):
  357. # 只有使用满一个月才创建结算记录
  358. if months_used >= 1:
  359. installment_list.append(AgentDeviceOrderInstallment(
  360. ado_id=agent_order_id,
  361. period_number=len(settlement_dates_with_months),
  362. ac_id=ac_id,
  363. amount=amount,
  364. due_date=settlement_date,
  365. status=1,
  366. created_time=n_time,
  367. updated_time=n_time
  368. ))
  369. # 批量创建
  370. if installment_list:
  371. batch_size = 100
  372. for i in range(0, len(installment_list), batch_size):
  373. AgentDeviceOrderInstallment.objects.bulk_create(installment_list[i:i + batch_size])
  374. LOGGER.info(f'季度分期结算记录创建完成: {len(installment_list)}条, 订单ID: {agent_order_id}')
  375. else:
  376. LOGGER.info(f'没有创建季度分期结算记录: 订单ID: {agent_order_id}')
  377. except Exception as e:
  378. LOGGER.error(f'保存季度分期结算记录异常: 行号:{e.__traceback__.tb_lineno}, 错误:{repr(e)}')
  379. @classmethod
  380. def update_periodic_settlement(cls):
  381. """
  382. 更新周期结算信息 - 优化版
  383. 功能:
  384. 1. 使用分布式锁确保同一时间只有一个进程在处理结算
  385. 2. 添加对账机制,确保数据准确性
  386. 3. 增加详细的日志记录
  387. 4. 使用Redis缓存避免重复处理
  388. 返回值:
  389. - 无返回值
  390. """
  391. # 初始化Redis对象
  392. redis_obj = RedisObject()
  393. # 生成唯一的请求ID用于锁
  394. request_id = f"settlement_task_{int(time.time())}"
  395. lock_key = "lock:periodic_settlement"
  396. # 尝试获取分布式锁
  397. if not redis_obj.try_lock(lock_key, request_id, expire=10, time_unit_second=60):
  398. LOGGER.info("周期结算任务已在其他进程中运行,本次跳过")
  399. return
  400. LOGGER.info("开始执行周期结算任务")
  401. try:
  402. n_time = int(time.time())
  403. # 记录开始处理的时间
  404. start_time = time.time()
  405. LOGGER.info(f"开始查询到期的分期结算记录,当前时间戳: {n_time}")
  406. # 根据条件查询需要更新结算信息的订单分期记录
  407. adoi_qs = AgentDeviceOrderInstallment.objects.filter(status=1, due_date__lte=n_time)
  408. if not adoi_qs.exists():
  409. LOGGER.info("没有找到需要结算的记录")
  410. return
  411. # 记录找到的记录数
  412. record_count = adoi_qs.count()
  413. LOGGER.info(f"找到 {record_count} 条需要结算的记录")
  414. # 使用事务处理结算过程
  415. from django.db import transaction
  416. # 准备数据
  417. settlement_records = [] # 用于记录处理的结算记录,后续对账使用
  418. ids = []
  419. a_account_list = []
  420. adoi_set = set()
  421. total_amount = Decimal('0.00')
  422. for item in adoi_qs:
  423. # 准备分期记录的id列表和账户记录列表
  424. ids.append(item.id)
  425. adoi_set.add(item.ado_id)
  426. # 记录结算金额
  427. amount = Decimal(str(item.amount)).quantize(Decimal('0.01'))
  428. total_amount += amount
  429. # 创建账户记录对象
  430. a_account_list.append(AgentAccount(
  431. ac_id=item.ac_id,
  432. amount=amount,
  433. remark=f'周期结算 - 分期ID:{item.id}',
  434. status=1,
  435. created_time=n_time,
  436. updated_time=n_time
  437. ))
  438. # 记录处理的结算记录
  439. settlement_records.append({
  440. 'id': item.id,
  441. 'ac_id': item.ac_id,
  442. 'amount': str(amount),
  443. 'due_date': item.due_date
  444. })
  445. # 缓存结算记录用于对账
  446. settlement_cache_key = f"settlement:records:{n_time}"
  447. redis_obj.set_data(settlement_cache_key, str(settlement_records), expire=86400) # 缓存24小时
  448. LOGGER.info(f"准备处理 {len(ids)} 条分期记录,总金额: {total_amount}")
  449. # 使用事务确保数据一致性
  450. with transaction.atomic():
  451. batch_size = 100
  452. # 分批更新分期记录状态
  453. updated_count = 0
  454. for i in range(0, len(ids), batch_size):
  455. batch_ids = ids[i:i + batch_size]
  456. update_result = AgentDeviceOrderInstallment.objects.filter(id__in=batch_ids) \
  457. .update(status=2, settlement_time=n_time, updated_time=n_time)
  458. updated_count += update_result
  459. LOGGER.info(f"已更新分期记录状态: {updated_count}/{len(ids)}")
  460. # 对账检查 - 确保所有记录都已更新
  461. if updated_count != len(ids):
  462. LOGGER.error(f"对账失败: 应更新 {len(ids)} 条记录,实际更新 {updated_count} 条")
  463. # 在事务中抛出异常将触发回滚
  464. raise Exception(f"结算记录更新不一致: 应更新 {len(ids)} 条,实际更新 {updated_count} 条")
  465. # 分批创建账户记录
  466. created_accounts = 0
  467. for i in range(0, len(a_account_list), batch_size):
  468. batch_accounts = a_account_list[i:i + batch_size]
  469. created_batch = AgentAccount.objects.bulk_create(batch_accounts)
  470. created_accounts += len(created_batch)
  471. LOGGER.info(f"已创建账户记录: {created_accounts}/{len(a_account_list)}")
  472. # 对账检查 - 确保所有账户记录都已创建
  473. if created_accounts != len(a_account_list):
  474. LOGGER.error(f"对账失败: 应创建 {len(a_account_list)} 条账户记录,实际创建 {created_accounts} 条")
  475. raise Exception(
  476. f"账户记录创建不一致: 应创建 {len(a_account_list)} 条,实际创建 {created_accounts} 条")
  477. # 检查是否所有分期都已结算,如果是,则更新订单状态为已结算
  478. updated_orders = 0
  479. for ado_id in adoi_set:
  480. # 检查是否还有未结算的分期
  481. if not AgentDeviceOrderInstallment.objects.filter(ado_id=ado_id, status=1).exists():
  482. # 更新订单状态为已结算
  483. update_result = AgentDeviceOrder.objects.filter(id=ado_id, status=1) \
  484. .update(status=2, settlement_time=n_time, updated_time=n_time)
  485. if update_result > 0:
  486. updated_orders += 1
  487. LOGGER.info(f"订单 {ado_id} 所有分期已结算,已更新订单状态")
  488. LOGGER.info(f"共更新了 {updated_orders} 个订单的状态为已结算")
  489. # 记录结算摘要到Redis,用于后续查询
  490. summary_key = f"settlement:summary:{n_time}"
  491. summary_data = {
  492. 'timestamp': n_time,
  493. 'record_count': record_count,
  494. 'total_amount': str(total_amount),
  495. 'updated_records': updated_count,
  496. 'created_accounts': created_accounts,
  497. 'updated_orders': updated_orders,
  498. 'duration': round(time.time() - start_time, 2)
  499. }
  500. redis_obj.set_hash_data(summary_key, summary_data)
  501. redis_obj.set_expire(summary_key, 86400 * 7) # 保存7天
  502. # 记录完成信息
  503. end_time = time.time()
  504. duration = round(end_time - start_time, 2)
  505. LOGGER.info(f"周期结算任务完成,处理时间: {duration}秒,处理记录: {record_count}条,总金额: {total_amount}")
  506. except Exception as e:
  507. # 记录详细的异常信息
  508. LOGGER.error(
  509. f'周期结算任务异常: 行号:{e.__traceback__.tb_lineno}, 错误类型:{type(e).__name__}, 错误信息:{repr(e)}')
  510. finally:
  511. # 释放分布式锁
  512. redis_obj.release_lock(lock_key, request_id)
  513. LOGGER.info("周期结算任务锁已释放")