bussiness.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. # coding=utf-8
  2. import json
  3. import datetime
  4. from django.utils import timezone
  5. from django.db.models import Q
  6. from apps.rebate.models import CashRebate, PointRebate, CashRebateLog, PointRebateLog, PointGive, PointGiveLog, TransferCashRebate, TransferCashRebateDetail, CashTransferLog
  7. from apps.customer.models import SuperiorDistributor
  8. from apps.order.models import Order
  9. from apps.config.models import Config
  10. from apps.WechatApplet.models import WechatApplet
  11. from apps.customer.models import CustomerWechat
  12. from utils.wechatpayv3.transfer import Transfer
  13. from utils.exceptions import CustomError
  14. class CustomerRebate(object):
  15. def __init__(self, pay):
  16. self.pay = pay
  17. self.order = Order.objects.filter(pay=self.pay).first()
  18. def cash_rebate(self, ratio, amount, customer):
  19. '''
  20. 计算现金返利 1、2、3级分销商 返现金
  21. :param ratio:
  22. :param amount:
  23. :param customer:
  24. :return:
  25. '''
  26. rebate = CashRebate.objects.create(
  27. order=self.order,
  28. ratio=ratio,
  29. amount=amount,
  30. customer=customer,
  31. create_time=timezone.now()
  32. )
  33. CashRebateLog.addnew(rebate)
  34. def point_rebate(self, ratio, amount, customer):
  35. '''
  36. 计算积分返利 4、5级分销商 返积分
  37. :param ratio:
  38. :param amount:
  39. :param customer:
  40. :return:
  41. '''
  42. rebate = PointRebate.objects.create(
  43. order=self.order,
  44. ratio=ratio,
  45. amount=amount,
  46. customer=customer,
  47. create_time=timezone.now()
  48. )
  49. PointRebateLog.addnew(rebate)
  50. def point_give(self, ratio, amount, customer):
  51. '''
  52. 计算购买商品赠送的积分 购买商品时根据后台设置的比例赠送相应的积分给购买者
  53. :param ratio:
  54. :param amount:
  55. :param customer:
  56. :return:
  57. '''
  58. give = PointGive.objects.create(
  59. order=self.order,
  60. ratio=ratio,
  61. amount=amount,
  62. customer=customer,
  63. create_time=timezone.now()
  64. )
  65. PointGiveLog.addnew(give)
  66. def calcul_rebate(self):
  67. '''
  68. 计算返利并保存log
  69. :return:
  70. '''
  71. if not self.order:
  72. return
  73. if self.order.total_amount <= 0:
  74. return
  75. if self.order.status == Order.WAIT_PAY or self.order.status == Order.CANCEL:
  76. return
  77. customer = self.order.customer
  78. sd = SuperiorDistributor.objects.filter(customer=customer).first()
  79. if not sd:
  80. return
  81. # 判断是首次购买还是再次购买 首次购买与再次购买返利的比例不同
  82. count = Order.objects.filter(customer=customer, status__in=[Order.WAIT_DISPATCH, Order.CONFIRM_DISPATCH]).count()
  83. rebate_dic = {}
  84. if count > 1:
  85. rebate_dic = Config.get_rebate(False)
  86. elif count == 1:
  87. rebate_dic = Config.get_rebate(True)
  88. # 现金保留两位小数, 积分整数
  89. total_amount = float(self.order.total_amount or 0)
  90. if sd.one_level and sd.one_level.is_distributor and rebate_dic['one_level'] > 0:
  91. amount = int(round(total_amount * rebate_dic['one_level'] / 100.0, 0))
  92. self.cash_rebate(rebate_dic['one_level'], amount, sd.one_level)
  93. if sd.two_level and sd.two_level.is_distributor and rebate_dic['two_level'] > 0:
  94. amount = int(round(total_amount * rebate_dic['two_level']/ 100.0, 0))
  95. self.cash_rebate(rebate_dic['two_level'], amount, sd.two_level)
  96. if sd.three_level and sd.three_level.is_distributor and rebate_dic['three_level'] > 0:
  97. amount = int(round(total_amount * rebate_dic['three_level'] / 100.0, 0))
  98. self.cash_rebate(rebate_dic['three_level'], amount, sd.three_level)
  99. if sd.four_level and sd.four_level.is_distributor and rebate_dic['four_level'] > 0:
  100. amount = int(round((total_amount / 100.0) * (rebate_dic['four_level'] / 100.0), 0))
  101. self.point_rebate(rebate_dic['four_level'], amount, sd.four_level)
  102. if sd.five_level and sd.five_level.is_distributor and rebate_dic['five_level'] > 0:
  103. amount = int(round((total_amount / 100.0) * (rebate_dic['five_level'] / 100.0), 0))
  104. self.point_rebate(rebate_dic['five_level'], amount, sd.five_level)
  105. # 计算购买商品 赠送积分
  106. rule = Config.get_value(Config.KEY_POINT_RULE)
  107. if rule > 0:
  108. amount = int(round((total_amount / 100.0) * (rule / 100.0), 0))
  109. self.point_give(rule, amount, customer)
  110. class CustomerRebateTransfer(object):
  111. def __init__(self, order):
  112. wx = WechatApplet.objects.filter().first()
  113. if not wx:
  114. raise Exception(u'小程序appid认证失败!')
  115. self.order = order
  116. self.wx = wx
  117. def apply_transfer_again(self, instance):
  118. '''转账批次明细失败 重新申请新的批次进行转账 要判断该明细有没有已经申请重新转账 和 判断用户余额'''
  119. count = TransferCashRebateDetail.objects.filter(Q(rebate=instance.rebate), ~Q(status=TransferCashRebateDetail.FAIL)).count()
  120. if count:
  121. raise CustomError(u'该转账明细,正在支付,禁止重复申请!')
  122. if instance.customer.balance < instance.amount:
  123. raise CustomError(u'申请转账金额超出客户[{}]余额,禁止申请!'.format(instance.customer.name))
  124. if instance.amount > self.order.total_amount:
  125. raise CustomError(u'转账总金额大于收款总金额!')
  126. if instance.amount == 0:
  127. raise CustomError(u'转账总金额为0!')
  128. if instance.rebate.status != CashRebate.DEFAULT:
  129. raise CustomError(u'该返利已转账,禁止重复申请!')
  130. instance.rebate.status = CashRebate.TRANSFERING
  131. instance.rebate.save()
  132. tc = TransferCashRebate.objects.create(order=instance.main.order, transfer_num=1, transfer_amount=instance.amount)
  133. tc_no = 'TC{}{:0>4}'.format(timezone.now().strftime('%Y%m%d%H%M%S'), tc.id)
  134. tc.no = tc_no
  135. tc.save()
  136. tcd = TransferCashRebateDetail.objects.create(main=tc, rebate=instance.rebate, amount=instance.amount, customer=instance.customer)
  137. tcd_no = 'TCD{}{:0>4}'.format(timezone.now().strftime('%Y%m%d%H%M%S'), tcd.id)
  138. tcd.no = tcd_no
  139. tcd.save()
  140. return tc
  141. def create_transfer(self):
  142. '''
  143. 转账的时候创建转账批次单 和转账批次单明细
  144. 如果是没有转账批次的 添加新的转账批次和转账批次明细 如果有转账批次的 只创建失败的
  145. 要判断收款人的余额
  146. 如果订单已经申请过转账批次 就不能再申请了 只能在付款失败的批次明细里边重新申请(没有考虑订单关闭的请框)
  147. :return:
  148. '''
  149. count = TransferCashRebate.objects.filter(order=self.order).count()
  150. if count:
  151. raise CustomError(u'该返利已申请转账,禁止重复申请!')
  152. tc = TransferCashRebate.objects.create(order=self.order)
  153. tc_no = 'TC{}{:0>4}'.format(timezone.now().strftime('%Y%m%d%H%M%S'), tc.id)
  154. rows = CashRebate.objects.filter(order=self.order, status=CashRebate.DEFAULT)
  155. transfer_amount = 0
  156. transfer_num = 0
  157. for row in rows:
  158. if row.customer.balance < row.amount:
  159. raise CustomError(u'客户[{}]余额不足!'.format(row.customer.name))
  160. row.status = CashRebate.TRANSFERING
  161. row.save()
  162. transfer_num += 1
  163. transfer_amount += row.amount
  164. tcd = TransferCashRebateDetail.objects.create(main=tc, rebate=row, amount=row.amount, customer=row.customer)
  165. tcd_no = 'TCD{}{:0>4}'.format(timezone.now().strftime('%Y%m%d%H%M%S'), tcd.id)
  166. tcd.no = tcd_no
  167. tcd.save()
  168. if transfer_amount > self.order.total_amount:
  169. raise CustomError(u'转账总金额大于收款总金额!')
  170. if transfer_num == 0:
  171. raise CustomError(u'转账总笔数为0!')
  172. if transfer_amount == 0:
  173. raise CustomError(u'转账总金额为0!')
  174. tc.no = tc_no
  175. tc.transfer_num = transfer_num
  176. tc.transfer_amount = transfer_amount
  177. tc.save()
  178. return tc
  179. def transfer_rebate(self, detail=None):
  180. '''
  181. 现金返利转账申请
  182. :return:
  183. '''
  184. if detail:
  185. tc = self.apply_transfer_again(detail)
  186. else:
  187. tc = self.create_transfer()
  188. transfer_detail_list = []
  189. rows = TransferCashRebateDetail.objects.filter(main=tc)
  190. for row in rows:
  191. wx_c = CustomerWechat.objects.filter(wechat_app=self.wx, customer=row.customer).first()
  192. if not wx_c:
  193. continue
  194. item = {
  195. 'out_detail_no': row.no,
  196. 'transfer_amount': row.amount,
  197. 'transfer_remark': '订单{}返利'.format(self.order.no),
  198. 'openid': wx_c.openid
  199. }
  200. # 注意 明细转账金额 大于等于 2000 收款用户姓名必须
  201. transfer_detail_list.append(item)
  202. appid = self.wx.authorizer_appid
  203. out_batch_no = self.order.no
  204. batch_name = '订单{}返利'.format(self.order.no)
  205. batch_remark = '订单{}返利'.format(self.order.no)
  206. total_amount = tc.transfer_amount
  207. total_num = tc.transfer_num
  208. t = Transfer(self.wx)
  209. code, message = t.transfer_batch(appid, out_batch_no, batch_name, batch_remark, total_amount, total_num, transfer_detail_list)
  210. result = json.loads(message)
  211. if code != 200:
  212. raise Exception(u'[{}]转账申请失败!原因:{}'.format(self.order.no, result))
  213. batch_id = result.get('batch_id')
  214. create_time = result.get('create_time')
  215. tc.transfer_status = TransferCashRebate.ACCEPTED
  216. if batch_id:
  217. tc.batch_id = batch_id
  218. if create_time:
  219. tc.transfer_time = datetime.datetime.strptime(create_time, "%Y-%m-%dT%H:%M:%S+08:00")
  220. tc.save()
  221. TransferCashRebateDetail.objects.filter(main=tc).update(status=TransferCashRebateDetail.PENDING)
  222. def transfer_refresh(transfer):
  223. # 如果转账批次单 已经受理30天了 中间没有刷新 状态是已受理 然后受理时间到当前时间超过30
  224. wx = WechatApplet.objects.filter().first()
  225. if not wx:
  226. raise Exception(u'小程序appid认证失败!')
  227. t = Transfer(wx)
  228. code, message = t.transfer_query_out_batch_no(transfer.no, need_query_detail=False, offset=0, limit=20, detail_status='ALL')
  229. result = json.loads(message)
  230. if code != 200:
  231. # 如果查询结果 NOT_FOUND 记录不存在 且转账批次单的状态是已申请 把状态改成 未处理 重新申请
  232. raise Exception(u'转账[{}]查询失败!原因:{}'.format(transfer.no, result))
  233. transfer_batch = result.get('transfer_batch')
  234. if not transfer_batch:
  235. return
  236. batch_id = transfer_batch.get('batch_id')
  237. batch_status = transfer_batch.get('batch_status')
  238. close_reason = transfer_batch.get('close_reason')
  239. create_time = transfer_batch.get('create_time')
  240. update_time = transfer_batch.get('update_time')
  241. success_amount = transfer_batch.get('success_amount')
  242. success_num = transfer_batch.get('success_num')
  243. fail_amount = transfer_batch.get('fail_amount')
  244. fail_num = transfer_batch.get('fail_num')
  245. if batch_id and not transfer.batch_id:
  246. transfer.batch_id = batch_id
  247. if batch_status:
  248. if batch_status == 'WAIT_PAY':
  249. transfer.transfer_status = TransferCashRebate.WAIT_PAY
  250. elif batch_status == 'ACCEPTED':
  251. transfer.transfer_status = TransferCashRebate.ACCEPTED
  252. elif batch_status == 'PROCESSING':
  253. transfer.transfer_status = TransferCashRebate.PROCESSING
  254. elif batch_status == 'FINISHED':
  255. transfer.transfer_status = TransferCashRebate.FINISHED
  256. elif batch_status == 'CLOSED':
  257. transfer.transfer_status = TransferCashRebate.CLOSED
  258. if close_reason:
  259. transfer.close_reason = close_reason
  260. if create_time and not transfer.transfer_time:
  261. transfer.transfer_time = datetime.datetime.strptime(create_time, "%Y-%m-%dT%H:%M:%S+08:00")
  262. if update_time:
  263. transfer.update_time = datetime.datetime.strptime(update_time, "%Y-%m-%dT%H:%M:%S+08:00")
  264. if success_amount:
  265. transfer.success_amount = success_amount
  266. if success_num:
  267. transfer.success_num = success_num
  268. if fail_amount:
  269. transfer.fail_amount = fail_amount
  270. if fail_num:
  271. transfer.fail_num = fail_num
  272. transfer.save()
  273. # 转账结束的时候 自动更新明细信息
  274. if batch_status and batch_status == 'FINISHED':
  275. detail = TransferCashRebateDetail.objects.filter(main=transfer, status__in=[TransferCashRebateDetail.DEFAULT, TransferCashRebateDetail.PENDING])
  276. for item in detail:
  277. transfer_detail_refresh(item)
  278. def transfer_detail_refresh(detail):
  279. wx = WechatApplet.objects.filter().first()
  280. if not wx:
  281. raise Exception(u'小程序appid认证失败!')
  282. out_detail_no = detail.no or None
  283. out_batch_no = detail.main and detail.main.no or None
  284. t = Transfer(wx)
  285. code, message = t.transfer_query_out_detail_no(out_detail_no, out_batch_no)
  286. result = json.loads(message)
  287. if code != 200:
  288. raise Exception(u'转账明细[{}]查询失败!原因:{}'.format(out_detail_no, result))
  289. # 更新明细状态
  290. detail_id = result.get('detail_id') # 微信明细单号
  291. detail_status = result.get('detail_status') # 明细装填 PROCESSING:转账中。正在处理中,转账结果尚未明确 SUCCESS:转账成功 FAIL:转账失败。需要确认失败原因后,再决定是否重新发起对该笔明细单的转账(并非整个转账批次单)
  292. fail_reason = result.get('fail_reason') # 失败原因
  293. initiate_time = result.get('initiate_time') # 转账发起时间
  294. update_time = result.get('update_time') # 明细更新时间
  295. if detail_id:
  296. detail.detail_no = detail_id
  297. if detail_status:
  298. if detail_status == 'PROCESSING':
  299. detail.status = TransferCashRebateDetail.PENDING
  300. elif detail_status == 'SUCCESS':
  301. detail.status = TransferCashRebateDetail.SUCCESS
  302. # 在这将收款人的余额减掉相应的金额
  303. # 转账已经成功了 就算是客户的余额是负数 也要统计成负数 转出去的钱不能返回了 所以只有在创建转账明细的时候要控制 转账金额不能大于客户余额
  304. CashTransferLog.addnew(detail)
  305. elif detail_status == 'FAIL':
  306. detail.status = TransferCashRebateDetail.FAIL
  307. # 转账失败 将返利表状态改回 未处理 后边手动处理或重新申请转账都可以
  308. detail.rebate.status = CashRebate.DEFAULT
  309. detail.rebate.save()
  310. if fail_reason:
  311. detail.fail_reason = fail_reason
  312. if initiate_time:
  313. detail.initiate_time = datetime.datetime.strptime(initiate_time, "%Y-%m-%dT%H:%M:%S+08:00")
  314. if update_time:
  315. detail.update_time = datetime.datetime.strptime(update_time, "%Y-%m-%dT%H:%M:%S+08:00")
  316. detail.save()