import requests, time, json, uuid, datetime from config import * from dateutil.relativedelta import relativedelta import threading import urllib3 from Crypto.Cipher import AES from base64 import b64encode, b64decode import logging logger = logging.getLogger(__name__) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) lock = threading.Lock() def format_uct(origin_date_str): origin_date_str = origin_date_str.split('.')[0] utc_date = datetime.datetime.strptime(origin_date_str, "%Y-%m-%dT%H:%M:%S") local_date = utc_date + datetime.timedelta(hours=8) local_date_str = datetime.datetime.strftime(local_date, '%Y-%m-%d %H:%M:%S') return local_date_str def query_reasons_for_payment(): url = f'{base_path}/api/v5/app/entry/data/list' filter_data = { "app_id": pay_app, "entry_id": reasons_id, "limit": 100, "filter": { "rel": "and", "cond": [ { "field": "business", "method": "eq", "value": ["联合运营"] } ] } } reasons_data = turn_page(url, filter_data) return reasons_data def get_reasons(): """获取收付款事由明细""" reasons_data = query_reasons_for_payment() reasons_data = sorted(reasons_data, key=lambda x: x['sort']) # 使用字典推导式创建reason为键,dktype为值的字典 # 初始化字典,包含两个空列表作为值 dktype_to_reasons_dict = { '非专项扣除': [], '专项扣除': [] } # 遍历数据,根据dktype的值将reason添加到对应的列表中 for item in reasons_data: dktype = item['dktype'] reason = item['reason'] if dktype in dktype_to_reasons_dict: dktype_to_reasons_dict[dktype].append(reason) return dktype_to_reasons_dict def get_sort(): """获取收付款事由明细""" res = query_reasons_for_payment() # 初始化两个空字典来存储结果 reason_to_sort = {} # 遍历data列表 for item in res: reason = item['reason'] sort = item['sort'] # 更新reason_to_sort字典 if reason not in reason_to_sort: reason_to_sort[reason] = sort # 注意:这里我们没有处理相同reason但不同sort的情况,因为只保留了第一个sort值 # 打印结果以验证 return reason_to_sort def queryUser(company_jc,jg): # 个人账户余额查询 url = f'{base_path}/api/v5/app/entry/data/list' filter_data = { "app_id": cw_app, "entry_id": pay_details_id, "limit": 100, "filter": { "rel": "and", "cond": [ { "field": "name", "method": "eq", "value": [company_jc] }, { "field": "mode_type", "method": "eq", "value": ["联合运营"] }, { "field": "jg", "method": "eq", "value": [jg] }, { "field": "type", "method": "eq", "value": "转入" }, { "field": "zhtype", "method": "eq", "value": "预收款" } ] } } # 查询充值明细 pay_detail = turn_page(url, filter_data) # for i in pay_detail: # i['time'] = format_uct(i['time']) # 查询个人余额 info = { "app_id": pay_app, "entry_id": accout_id, "filter": {"rel": "and", "cond": [ { "field": "name", "method": "eq", "value": [company_jc] }, { "field": "mode_type", "method": "eq", "value": ["联合运营"] }, { "field": "jg", "method": "eq", "value": [jg] }, { "field": "account_type", "method": "eq", "value": "预收款" } ]} } # 查询个人余额账户表 res = req_tool_jdy(url, info) balance = 0 if res['data']: balance = res['data'][0]['balance'] balance = float("%.2f" % balance) return {"balance": balance, "pay_detail": pay_detail} def get_last_8_hour(): _time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time() - 8 * 60 * 60)) return _time def turn_page(url, data): """翻页查询""" info = [] while 1: try: res = req_tool_jdy(url, data) info += res['data'] if len(res['data']) < 100: return info data["data_id"] = res["data"][-1]["_id"] except Exception as e: logger.error(f"turn_page_error,e {e}, res{res}") def query_pay_details(company_jc,jg): # 查询某人未收付明细 url = f'{base_path}/api/v5/app/entry/data/list' filter_data = { "app_id": pay_app, "entry_id": uncollected_id, "limit": 100, "filter": { "rel": "and", "cond": [ { "field": "company_jc", "method": "eq", "value": [company_jc] }, { "field": "mode_type", "method": "eq", "value": ["联合运营"] }, { "field": "sijisuozaigongsi", "method": "eq", "value": [jg] }, ] } } uncollected_data = turn_page(url, filter_data) _data = [] reasons = get_reasons() for i in uncollected_data: flag = False for keys in reasons.keys(): for j in reasons[keys]: # if j == i['fukuanshiyou'] and i['mode_type']=='联合运营' and i['lydunjiao'] != "趸交": if j == i['fukuanshiyou'] and i['mode_type']=='联合运营': flag = True break if not flag: continue i['money'] = float("%.2f" % i['money']) if i['money'] else 0 i['pay'] = float("%.2f" % i['pay']) if i['pay'] != None and i['pay'] != '' else 0 i['no_money'] = float("%.2f" % i['no_money']) if i['no_money'] != None and i['no_money'] != '' else 0 i['sort'] = 9999 rule_sort = get_sort() for j in rule_sort: if j == i['fukuanshiyou']: i['sort'] = rule_sort[j] _data.append(i) uncollected_data = _data uncollected_data = sorted(uncollected_data, key=lambda x: x['createTime'], reverse=True) # 过滤已结清数据 uncollected_data = [i for i in uncollected_data if i['money'] != i['pay']] undata = {} # # 按月份归类 uncollected_data # for un in uncollected_data: # if un['yuefen'] in undata.keys(): # undata[un['yuefen']]['data'].append(un) # else: # undata[un['yuefen']] = {"data": []} # undata[un['yuefen']]['data'] = [un] # 按收付款事由归类 for un in uncollected_data: fukuanshiyou = un['fukuanshiyou'] if fukuanshiyou in undata.keys(): undata[fukuanshiyou]['data'].append(un) else: undata[fukuanshiyou] = {"data": []} undata[fukuanshiyou]['data'] = [un] # 按月查询增减项 flowState结束 amount = 0 for un in undata: _th_amount = 0 del_list = [] for index, j in enumerate(undata[un]['data']): j['no_money'] = j['no_money'] if j['no_money'] else j['money'] if '费税' in j['fukuanshiyou'] and j['yewubiaodanmingcheng'] == '费税计划': # 定额 de = 表单中的金额-增减 # 应付金额 money = 表单中的金额 # 未付金额 no_money = 应付金额-增减-已付金额 j['pay'] = j['pay'] if j['pay'] else 0 # j['money'] = undata[un]['de'] + zj_sum j['no_money'] = j['money'] - j['pay'] amount += j['no_money'] if j['no_money'] else j['money'] _th_amount += j['no_money'] if j['no_money'] else j['money'] undata[un]['_th_amount'] = _th_amount for d in del_list: undata[un]['data'].pop(d) undata['amount'] = float("%.2f" % amount) for i in list(undata.keys()): if i != 'amount' and not undata[i]['data']: del undata[i] return undata # def req_tool_jdy(url, data): # headers = {"Authorization": f"Bearer {apikey}"} # while 1: # try: # res = requests.post(url, headers=headers, json=data, timeout=10, verify=False).json() # if "code" in res.keys(): # for count in range(0,5): # logger.info(f"req_tool_jdy", res, url, data) # time.sleep(2) # continue # return res # except Exception as e: # print(e) def req_tool_jdy(url, data): headers = {"Authorization": f"Bearer {apikey}"} max_retries = 5 # 最大重试次数 retries = 0 while retries < max_retries: try: res = requests.post(url, headers=headers, json=data, timeout=10, verify=False).json() if "code" in res.keys(): logger.error(f"req_tool_jdy response indicates an error:res{res}, url{url}, data{data}") retries += 1 time.sleep(2) # 等待时间,避免迅速重试 else: return res except Exception as e: logger.error(f"Request error:", e) logger.info(f"Max retries reached for url: {url} with data: {data}") return None # 在多次尝试后返回 None def req_tool_vx(url, data=None, flag=None): while 1: try: if not flag: res = requests.post(url, json=data, timeout=10, verify=False).json() else: res = requests.get(url, timeout=10, verify=False).json() return res except Exception as e: logger.error(f"error{e}") def update_unpay(data,now_time,iszx): """更新未收付,添加已收付明细""" url = f'{base_path}/api/v5/app/entry/data/update' add_url = f'{base_path}/api/v5/app/entry/data/create' del_url = f'{base_path}/api/v1/app/{pay_app}/entry/{uncollected_id}/data_delete' for i in data: if 'amount' == i: continue for j in data[i]['data']: info = { "app_id": pay_app, "entry_id": uncollected_id, "data_id": j['_id'], "is_start_trigger": True, "is_start_workflow": True, "data": { "pay": {"value": j['pay']}, "no_money": {"value": j['no_money']}, } } req_tool_jdy(url, info) # 新增已收付明细 if 'flag' in j.keys() and float(j['flag_m']): if not j['flag_m'] or not float(j['flag_m']): continue fs_p = j['fs_p']['dept_no'] if j['fs_p'] else "" if iszx == 1: add_info = { "app_id": pay_app, "entry_id": received_id, "is_start_trigger": True, "is_start_workflow": True, "data": { "code": {"value": j['code']}, "type": {"value": "收款"}, "fukuanshiyou": {"value": j['fukuanshiyou']}, "month1": {"value": j['month1']}, "month": {"value": j['month']},"yewubiaodanmingcheng": {"value": j['yewubiaodanmingcheng']}, "yewubiaodanbianma": {"value": j['yewubiaodanbianma']}, "sijixingming": {"value": j['sijixingming']}, "shfid": {"value": j['shfid']}, "id_card": {"value": j['id_card']},"pay_time": {"value": now_time},"start_time": {"value": j['start_time']},"end_time": {"value": j['end_time']}, "company_jc": {"value": j['company_jc']}, "xd": {"value": j['xd']},"cz_time": {"value": now_time}, "sijisuozaigongsi": {"value": j['sijisuozaigongsi']}, "fs": {"value": j['fs']}, "license_plate": {"value": j['license_plate']}, "money": {"value": j['money']}, "pay": {"value": j['flag_m']}, "hz_month": {"value": j['hz_month']}, "fs_p": {"value": fs_p}, "shgid": {"value": j['shgid']}, "mode_type": {"value": j['mode_type']}, "sy": {"value": "联营账户抵扣-专项"}, "ys": {"value": j['ys']}, "lydunjiao": {"value": j['lydunjiao']},"bill_type": {"value": j['bill_type']}, "car_dept": {"value": j['car_dept']},"contract_num": {"value": j['contract_num']} } } else: add_info = { "app_id": pay_app, "entry_id": received_id, "is_start_trigger": True, "is_start_workflow": True, "data": { "code": {"value": j['code']}, "type": {"value": "收款"}, "fukuanshiyou": {"value": j['fukuanshiyou']}, "month1": {"value": j['month1']}, "month": {"value": j['month']},"yewubiaodanmingcheng": {"value": j['yewubiaodanmingcheng']}, "yewubiaodanbianma": {"value": j['yewubiaodanbianma']}, "sijixingming": {"value": j['sijixingming']},"shfid": {"value": j['shfid']}, "id_card": {"value": j['id_card']},"pay_time": {"value": now_time},"start_time": {"value": j['start_time']},"end_time": {"value": j['end_time']}, "company_jc": {"value": j['company_jc']}, "xd": {"value": j['xd']},"cz_time": {"value": now_time}, "sijisuozaigongsi": {"value": j['sijisuozaigongsi']}, "fs": {"value": j['fs']}, "license_plate": {"value": j['license_plate']}, "money": {"value": j['money']}, "pay": {"value": j['flag_m']}, "hz_month": {"value": j['hz_month']}, "fs_p": {"value": fs_p}, "shgid": {"value": j['shgid']}, "mode_type": {"value": j['mode_type']}, "sy": {"value": "联营账户抵扣-非专项"}, "ys": {"value": j['ys']},"lydunjiao": {"value": j['lydunjiao']},"bill_type": {"value": j['bill_type']}, "car_dept": {"value": j['car_dept']},"contract_num": {"value": j['contract_num']} } } req_tool_jdy(add_url, add_info) logger.info(f"删除========={j['pay']}, {j['money']}") print(f"删除========={j['pay']}, {j['money']}") if j['sijixingming'] == '姓名': print(j) if j['pay'] == j['money']: # 未收付已清缴,删除未收付明细 j['sy'] = "联营账户抵扣" del_info = { "data_id": j["_id"], "is_start_trigger": True } req_tool_jdy(del_url, del_info) def update_info(cz, _balcace, balance, user_info): # 余额 = 累计充值金额-累计抵扣金额 # 本次抵扣=本次充值+本次余额-剩余金额 di = cz + balance - _balcace """更新个人账户余额""" # if pay_amount: # # 支付完有剩余余额可直接更新 # amount = pay_amount # else: # # 查询未收付历史数据 # details = query_pay_details(user_info['user']['username']) # amount = details['amount'] # amount *= -1 url = f'{base_path}/api/v5/app/entry/data/list' info = { "app_id": pay_app, "entry_id": accout_id, "filter": {"rel": "and", "cond": [ { "field": "name", "method": "eq", "value": [user_info['name']] }, { "field": "mode_type", "method": "eq", "value": ["联合运营"] }, { "field": "jg", "method": "eq", "value": [user_info['jg']] }, { "field": "account_type", "method": "eq", "value": "预收款" } ]} } # 查询个人余额账户表 没有新增,有更新 res = req_tool_jdy(url, info) logger.info(f"查询信息{info}") if res['data']: # 更新 logger.info(f"有账户") url = f'{base_path}/api/v5/app/entry/data/update' refund_amount = res['data'][-1]['refund_amount'] pay = res['data'][-1]['pay'] + cz di = di + (res['data'][-1]['deduction'] if res['data'][-1]['deduction'] else 0) amount = pay - di - refund_amount# 累计充值-累计抵扣-退款金额 info = { "app_id": pay_app, "entry_id": accout_id, "data_id": res['data'][-1]["_id"], "is_start_workflow": True, "is_start_trigger": True, "data": { "pay": {"value": pay}, "balance": {"value": amount}, "deduction": {"value": di} } } req_tool_jdy(url, info) else: logger.info(f"====================未找到联营客户账户===========================,{user_info['name']},{user_info['jg']}") def add_payment(data, type=None): lock.acquire() logger.info(f"开始========{data}") print(data, type) # user = get_user_info(data['id']) # 充值成功后 回写充值明细 now_month = datetime.datetime.now().strftime("%Y-%m") now_time = get_last_8_hour() url = f'{base_path}/api/v5/app/entry/data/create' if not type: info = { "app_id": cw_app, "entry_id": pay_details_id, "is_start_workflow": True, "is_start_trigger": True, "data": { "id_card": {"value": data['id']}, "name": {"value": data['name']}, "charge_amount": {"value": data['pay_amount']}, "pay_type": {"value": "企微支付"}, "type": {"value": "转入"}, "rq": {"value": now_time},"mode_type": {"value": "联合运营"}, "jg": {"value": data['jg']}, "laiyuan": {"value": "待定"}, "zhtype": {"value": "待定"} } } req_tool_jdy(url, info) details = query_pay_details(data['name'],data['jg']) dates = set() # 使用集合来存储唯一的 yuefen for item in details: if item == "amount": continue data_list = details[item]['data'] for reason in data_list: dates.add(reason['month1']) # 使用 add 方法,集合会自动处理重复元素 # 将集合转换回列表 dates = list(dates) # 将日期字符串转换为日期对象,并存储在一个新列表中 date_objects = [datetime.datetime.strptime(date_str, '%Y-%m') for date_str in dates] # 对日期对象进行排序 date_objects.sort() # 如果需要,将排序后的日期对象转换回字符串 sorted_dates = [date_obj.strftime('%Y-%m') for date_obj in date_objects] pay_amount = data['pay_amount'] # 查询余额 balance = queryUser(data['name'],data['jg']) pay_amount += balance['balance'] if pay_amount <= 0: logger.info(f"余额为0结束========{data}") lock.release() return logger.info(f"执行add_payment, {data},pay_amount,{pay_amount}") # next_month = (datetime.datetime.now().date() - relativedelta(months=-1)).strftime("%Y-%m") # 先抵扣业务表单编码一致的 reasons = get_reasons() for keys in reasons.keys(): for i in reasons[keys]: if i not in details.keys(): continue _tmp = details[i]['data'] _tmp = sorted(_tmp, key=lambda x: x['sort']) for month in sorted_dates: logger.info(f"nowmonth, {month}") for j in _tmp: if j['month1'] == month: if pay_amount: j['flag'] = 1 if '保证金尾款' in j['fukuanshiyou'] or pay_amount == 0: j['flag_m'] = 0 continue if data['is_zx'] == 1: for code in data['form_code']: if code == j['code']: if j['money'] != j['pay']: # 根据未支付的金额,扣除总金额 df = j['money'] - j['pay'] if pay_amount < df: # 修改 i['pay'] 充值金额,推送修改 j['pay'] += pay_amount j['no_money'] = j['money'] - j['pay'] j['flag_m'] = pay_amount pay_amount = 0 else: pay_amount -= df j['pay'] = j['money'] j['no_money'] = 0 j['flag_m'] = df else: continue else: if data['form_code'] == j['yewubiaodanbianma']: # if data['form_code'] == j['yewubiaodanbianma'] and j['fukuanshiyou'] in ["费税收取-首月", "费税收取-次月"]: if j['money'] != j['pay']: # 根据未支付的金额,扣除总金额 df = j['money'] - j['pay'] if pay_amount < df: # 修改 i['pay'] 充值金额,推送修改 j['pay'] += pay_amount j['no_money'] = j['money'] - j['pay'] j['flag_m'] = pay_amount pay_amount = 0 else: pay_amount -= df j['pay'] = j['money'] j['no_money'] = 0 j['flag_m'] = df else: continue # 再抵扣其他 for keys in reasons.keys(): for i in reasons[keys]: if i not in details.keys(): continue _tmp = details[i]['data'] _tmp = sorted(_tmp, key=lambda x: x['sort']) for month in sorted_dates: logger.info(f"nowmonth, {month}") for j in _tmp: if j['month1'] == month: if pay_amount: j['flag'] = 1 if '保证金尾款' in j['fukuanshiyou'] or pay_amount == 0: # 检查 j['flag_m'] 是否已有值 if 'flag_m' in j and j['flag_m'] is not None: # 如果已有值,继续下一个循环 continue else: # 如果没有值,给 j['flag_m'] 赋值为 0 j['flag_m'] = 0 if data['is_zx'] == 1: if 'flag_m' in j and j['flag_m'] is not None: # 如果已有值,继续下一个循环 continue else: # 如果没有值,给 j['flag_m'] 赋值为 0 if j['money'] != j['pay']: # 根据未支付的金额,扣除总金额 df = j['money'] - j['pay'] if pay_amount < df: # 修改 i['pay'] 充值金额,推送修改 j['pay'] += pay_amount j['no_money'] = j['money'] - j['pay'] j['flag_m'] = pay_amount pay_amount = 0 else: pay_amount -= df j['pay'] = j['money'] j['no_money'] = 0 j['flag_m'] = df # if data['form_code'] == j['yewubiaodanbianma'] and j['fukuanshiyou'] in ["费税收取-首月","费税收取-次月"]: if data['form_code'] == j['yewubiaodanbianma']: # 承包金实际需要缴纳的 if 'flag_m' in j and j['flag_m'] is not None: # 如果已有值,继续下一个循环 continue else: # 如果没有值,给 j['flag_m'] 赋值为 0 if j['money'] != j['pay']: # 根据未支付的金额,扣除总金额 df = j['money'] - j['pay'] if pay_amount < df: # 修改 i['pay'] 充值金额,推送修改 j['pay'] += pay_amount j['no_money'] = j['money'] - j['pay'] j['flag_m'] = pay_amount pay_amount = 0 else: pay_amount -= df j['pay'] = j['money'] j['no_money'] = 0 j['flag_m'] = df else: if 'flag_m' in j and j['flag_m'] is not None: # 如果已有值,继续下一个循环 continue else: if j['money'] != j['pay']: # 根据未支付的金额,扣除总金额 df = j['money'] - j['pay'] if pay_amount < df: # 修改 i['pay'] 充值金额,推送修改 j['pay'] += pay_amount j['no_money'] = j['money'] - j['pay'] j['flag_m'] = pay_amount pay_amount = 0 else: pay_amount -= df j['pay'] = j['money'] j['no_money'] = 0 j['flag_m'] = df # 根据抵扣规则,更新未支付的数据,将清缴数据新增至已收付明细 logger.info(f"更新未收付{details}") update_unpay(details,now_time,data['is_zx']) # 未收付更新完后,更新个人账户余额update_info logger.info(f"更新个人账户余额update_info") logger.info(f"data['pay_amount'], {data['pay_amount']}, pay_amount, {pay_amount}, balance['balance'],{balance['balance']}, data, {data}") update_info(data['pay_amount'], pay_amount, balance['balance'], data) lock.release() logger.info(f"结束========") def decode_notify_data(res_json): try: ciphertext = res_json['resource']['ciphertext'] nonce = res_json['resource']['nonce'] associated_data = res_json['resource']['associated_data'] cipher = AES.new(v3_SECRET.encode(), AES.MODE_GCM, nonce=nonce.encode()) cipher.update(associated_data.encode()) en_data = b64decode(ciphertext.encode('utf-8')) auth_tag = en_data[-16:] _en_data = en_data[:-16] plaintext = cipher.decrypt_and_verify(_en_data, auth_tag) decodejson = json.loads(plaintext.decode()) except Exception as e: logger.error(f"解密回调失败:{e}") return None return decodejson def get_wx_token(): url = f'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpid}&corpsecret={SECRET}' res = req_tool_vx(url, flag=1) return res['access_token'] def query_wx_fj_info(id): """查询企微中的附加信息""" token = get_wx_token() url = f'https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token={token}&userid={id}' res = req_tool_vx(url, flag=1) val = "" logger.info(f"query_wx_fj_info,res,{res}, id, {id}") for i in res['extattr']['attrs']: if i['name'] == '附加信息': val = i['value'] return val def query_wx_userid(code): """查询企微中的userid""" token = get_wx_token() url = f'https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token={token}&code={code}' res = req_tool_vx(url, flag=1) return res['userid']