Files
2025-07-31 15:44:11 +08:00

730 lines
30 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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']