From 314aaa9d855fe9ba7f0de7fae4a0bcb0601f6a5f Mon Sep 17 00:00:00 2001 From: ping <1017253325@qq.com> Date: Tue, 19 May 2026 15:37:41 +0800 Subject: [PATCH] update --- b/cntoai/get_deerer_header.dspy | 2 +- b/cntoai/get_user_balance.dspy | 33 +++ b/cntoai/process_user_billing.dspy | 354 +++++++++++++++++++++++++++++ b/customer/registerUser.dspy | 1 + 4 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 b/cntoai/get_user_balance.dspy create mode 100644 b/cntoai/process_user_billing.dspy diff --git a/b/cntoai/get_deerer_header.dspy b/b/cntoai/get_deerer_header.dspy index 7bda013..ab7fb6a 100644 --- a/b/cntoai/get_deerer_header.dspy +++ b/b/cntoai/get_deerer_header.dspy @@ -11,7 +11,7 @@ async def get_deerer_header(ns={}): } db = DBPools() async with db.sqlorContext('kboss') as sor: - records = await sor.R('user_api_keys', {'userid': userid}) + records = await sor.R('user_api_keys', {'userid': userid, 'action': 'sync'}) if not records: return { 'status': False, diff --git a/b/cntoai/get_user_balance.dspy b/b/cntoai/get_user_balance.dspy new file mode 100644 index 0000000..bbec0cb --- /dev/null +++ b/b/cntoai/get_user_balance.dspy @@ -0,0 +1,33 @@ +async def get_user_balance(ns={}): + """ + 根据 userid 查询对应机构的客户余额。 + + :param userid: 用户 ID + :return: 账户余额(与 getCustomerBalance 返回值一致) + """ + apikey = ns.get('apikey') + db = DBPools() + async with db.sqlorContext('kboss') as sor: + if not apikey: + return { + 'status': False, + 'msg': 'apikey is required' + } + userid_li = await sor.R('user_api_keys', {'opc_apikey': apikey}) + if not userid_li: + return { + 'status': False, + 'msg': '用户未同步到系统' + } + userid = userid_li[0]['userid'] + user = await sor.R('users', {'id': userid}) + orgid = await sor.R('organization', {'id': user[0]['orgid']}) + balance = await getCustomerBalance(sor, orgid[0]['id']) + return { + 'status': True, + 'balance': balance + } + + +ret = await get_user_balance(params_kw) +return ret \ No newline at end of file diff --git a/b/cntoai/process_user_billing.dspy b/b/cntoai/process_user_billing.dspy new file mode 100644 index 0000000..f443765 --- /dev/null +++ b/b/cntoai/process_user_billing.dspy @@ -0,0 +1,354 @@ +async def _lookup_product(sor, providername, productname): + """ + 按厂商名 + 产品名解析 product 记录。 + 依次尝试:provider.name + product.name → providerpid → 仅 product.name + """ + provider_list = await sor.R('provider', {'name': providername, 'del_flg': '0'}) + if provider_list: + product_list = await sor.R( + 'product', + {'name': productname, 'providerid': provider_list[0]['orgid'], 'del_flg': '0'}, + ) + if product_list: + return product_list[0] + + product_list = await sor.R('product', {'name': productname, 'providerid': provider_list[0]['orgid'], 'del_flg': '0'}) + if product_list: + return product_list[0] + + return None + + +async def _charge_order(sor, orderid, order_type='NEW'): + """ + 确认支付:校验余额 → order2bill → BillAccounting → 更新订单/账单 → customer_goods。 + 逻辑来自 get_baidu_orderlist.dspy 的 affirmbz_order。 + """ + order_rows = await sor.R('bz_order', {'id': orderid}) + if not order_rows: + return {'status': False, 'msg': '订单不存在'} + + order_row = order_rows[0] + product_url = None + + await get_business_date(sor=None) + + count = await getCustomerBalance(sor, order_row['customerid']) + if count is None: + count = 0 + if count - float(order_row['amount']) < 0: + pricedifference = count - round(order_row['amount'], 2) + return { + 'status': False, + 'msg': '账户余额不足', + 'pricedifference': round(pricedifference, 2), + } + + await order2bill(orderid, sor) + bills = await sor.R('bill', {'orderid': orderid, 'del_flg': '0'}) + try: + for bill in bills: + ba = BillAccounting(bill) + await ba.accounting(sor) + dates = datetime.datetime.now() + await sor.U('bz_order', {'id': orderid, 'order_status': '1', 'create_at': dates}) + await sor.U('bill', {'id': orderid, 'bill_state': '1'}) + + # 暂时不处理customer_goods + # order_goods = await sor.R('order_goods', {'orderid': orderid}) + # for item in order_goods: + # if order_type == 'REFUND': + # resource_find_sql = ( + # "select id from customer_goods where resourceid = '%s';" + # % item['resourceids'] + # ) + # resource_find_li = await sor.sqlExe(resource_find_sql, {}) + # resource_find_id = resource_find_li[0]['id'] + # await sor.U('customer_goods', {'id': resource_find_id, 'del_flg': '1'}) + # elif order_type == 'RENEW': + # resource_find_sql = ( + # "select id from customer_goods where FIND_IN_SET('%s', resourceid) and del_flg = '0';" + # % item['resourceids'] + # ) + # resource_find_li = await sor.sqlExe(resource_find_sql, {}) + # resource_find_id = resource_find_li[0]['id'] + # await sor.U( + # 'customer_goods', + # { + # 'id': resource_find_id, + # 'start_date': item['resourcestarttime'], + # 'expire_date': item['resourceendtime'], + # }, + # ) + # else: + # if item.get('chargemode') == 'postpay' and item.get('orderkey') == 'snapshot': + # continue + + # product = await sor.R('product', {'id': item['productid']}) + # now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + # nss = { + # 'id': uuid(), + # 'providerrid': product[0]['providerid'], + # 'productname': product[0]['name'], + # 'productdesc': product[0]['description'], + # 'customerid': order_row['customerid'], + # 'productid': product[0]['id'], + # 'specdataid': item.get('spec_id'), + # 'orderid': order_row['id'], + # 'start_date': item.get('resourcestarttime') or now_str, + # 'expire_date': item.get('resourceendtime'), + # 'resourceid': item.get('resourceids') or '', + # 'orderkey': item.get('orderkey'), + # } + + # if product_url: + # nss['product_url'] = product_url + # else: + # spec = ( + # json.loads(product[0]['spec_note']) + # if isinstance(product[0]['spec_note'], str) + # else product[0]['spec_note'] + # ) + # spec_list_url = [ + # spec_item['value'] + # for spec_item in (spec or []) + # if spec_item.get('configName') == 'listUrl' + # ] + # nss['product_url'] = ( + # spec_list_url[0] + # if spec_list_url + # else 'https://console.vcp.baidu.com/bcc/#/bcc/instance/list' + # ) + + # await sor.C('customer_goods', nss) + return {'status': True, 'msg': '支付成功'} + except Exception as error: + return {'status': False, 'msg': str(error)} + +async def calc_price_by_saleprotocol(sor, org, product_id, supply_price, quantity=1): + """ + 查 saleprotocol、product_salemode,按折扣计算应付金额。 + + :param sor: sqlor 上下文(kboss) + :param org: organization 记录,须含 id、parentid + :param product_id: product 表主键 + :param supply_price: 供应价/目录价(折扣前单价,与百度脚本 catalogPrice / itemFee.price 同义) + :param quantity: 数量,默认 1 + :return: dict + 成功: status=True, amount(行总金额), price(折后单价), list_price, discount + 失败: status=False, msg + """ + try: + supply_price = abs(float(supply_price)) + quantity = int(quantity) + except (TypeError, ValueError): + return {'status': False, 'msg': 'supply_price / quantity 必须为有效数字'} + + if supply_price <= 0: + return {'status': False, 'msg': 'supply_price 必须大于 0'} + if quantity <= 0: + return {'status': False, 'msg': 'quantity 必须大于 0'} + + saleprotocol_to_person = await sor.R( + 'saleprotocol', + {'bid_orgid': org['id'], 'offer_orgid': org['parentid'], 'del_flg': '0'}, + ) + saleprotocol_to_all = await sor.R( + 'saleprotocol', + { + 'bid_orgid': '*', + 'offer_orgid': org['parentid'], + 'del_flg': '0', + 'salemode': '0', + }, + ) + + product_salemode = None + if saleprotocol_to_person: + product_salemode = await sor.R( + 'product_salemode', + { + 'protocolid': saleprotocol_to_person[0]['id'], + 'productid': product_id, + 'del_flg': '0', + }, + ) + if not product_salemode and saleprotocol_to_all: + product_salemode = await sor.R( + 'product_salemode', + { + 'protocolid': saleprotocol_to_all[0]['id'], + 'productid': product_id, + 'del_flg': '0', + }, + ) + elif saleprotocol_to_all: + product_salemode = await sor.R( + 'product_salemode', + { + 'protocolid': saleprotocol_to_all[0]['id'], + 'productid': product_id, + 'del_flg': '0', + }, + ) + + if not product_salemode: + return {'status': False, 'msg': '还未上线这个产品的协议配置'} + + discount = product_salemode[0]['discount'] + list_price = supply_price + price = abs(round(list_price * discount, 2)) + amount = abs(round(price * quantity, 2)) + + return { + 'status': True, + 'amount': amount, + 'price': price, + 'list_price': list_price, + 'discount': discount, + 'protocolid': product_salemode[0]['protocolid'], + 'product_salemode_id': product_salemode[0].get('id'), + } + +async def process_user_billing(ns={}): + """ + 通用记账扣费:创建本地订单 → 校验余额 → 出账记账。 + + :param userid: 用户 ID + :param providername: 厂商名称(写入 bz_order.source,并用于查 product) + :param productname: 产品名称(写入 servicename,并用于查 product) + :param amount: 扣费金额;use_saleprotocol=False 时为最终扣费额; + use_saleprotocol=True 时为供应价/目录价(折扣前单价),走协议算价 + :param use_saleprotocol: 是否启用 saleprotocol_pricing 协议折扣算价,默认 False 直接按 amount 扣费 + :param quantity: 仅 use_saleprotocol=True 时生效,数量默认 1 + :return: dict,含 status、msg;成功时含 orderid、amount + """ + apikey = ns.get('apikey') + providername = ns.get('providername') + productname = ns.get('productname') + amount = ns.get('amount') + use_saleprotocol = ns.get('use_saleprotocol', False) + quantity = int(ns.get('quantity', 1)) + + userid_li = await sor.R('user_api_keys', {'opc_apikey': apikey}) + if not userid_li: + return { + 'status': False, + 'msg': '用户未同步到系统' + } + userid = userid_li[0]['userid'] + + try: + amount = round(float(amount), 2) + except (TypeError, ValueError): + return {'status': False, 'msg': 'amount 必须为有效数字'} + + if amount <= 0: + return {'status': False, 'msg': 'amount 必须大于 0'} + + db = DBPools() + async with db.sqlorContext('kboss') as sor: + provider_list = await sor.R('provider', {'name': providername}) + if not provider_list: + return {'status': False, 'msg': '厂商不存在 %s' % providername} + + user_list = await sor.R('users', {'id': userid}) + if not user_list: + return {'status': False, 'msg': '用户不存在 %s' % userid} + + org_list = await sor.R('organization', {'id': user_list[0]['orgid']}) + if not org_list: + return {'status': False, 'msg': '用户所属机构不存在'} + + customerid = org_list[0]['id'] + product = await _lookup_product(sor, providername, productname) + if not product: + return { + 'status': False, + 'msg': '未找到对应产品,请确认 providername/productname 与库中 provider、product 配置一致', + } + + list_price = amount + unit_price = amount + discount = 1 + originalprice = amount + + if use_saleprotocol: + price_res = await calc_price_by_saleprotocol( + sor, org_list[0], product['id'], amount, quantity=quantity, + ) + if not price_res['status']: + return price_res + debug(price_res) + debug('list_price %s' % list_price) + amount = price_res['amount'] + list_price = price_res['list_price'] + unit_price = price_res['price'] + discount = price_res['discount'] + originalprice = list_price * quantity + + balance = await getCustomerBalance(sor, customerid) + if balance is None: + balance = 0 + if amount > balance: + return { + 'status': False, + 'msg': '账户余额不足', + 'pricedifference': round(balance - amount, 2), + } + + order_id = uuid() + now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + bz_ns = { + 'id': order_id, + 'order_status': '0', + 'business_op': 'BUY', + 'userid': userid, + 'customerid': customerid, + 'order_date': now_str, + 'source': providername, + 'amount': amount, + 'originalprice': round(originalprice, 2), + 'ordertype': 'prepay', + 'servicename': productname, + } + await sor.C('bz_order', bz_ns) + + goods_ns = { + 'id': uuid(), + 'orderid': order_id, + 'productid': product['id'], + 'providerid': product['providerid'], + 'list_price': list_price, + 'discount': discount, + 'quantity': quantity if use_saleprotocol else 1, + 'price': unit_price, + 'amount': amount, + 'chargemode': 'prepay', + 'servicename': productname, + 'resourceids': '', + 'resourcestarttime': now_str, + 'resourceendtime': None, + } + await sor.C('order_goods', goods_ns) + + charge_res = await _charge_order(sor, order_id, order_type='NEW') + if not charge_res['status']: + await sor.rollback() + return charge_res + + result = { + 'status': True, + 'msg': '扣费成功', + 'orderid': order_id, + 'amount': amount, + 'productid': product['id'], + } + if use_saleprotocol: + result['discount'] = discount + result['list_price'] = list_price + result['price'] = unit_price + return result + +ret = await process_user_billing(params_kw) +return ret \ No newline at end of file diff --git a/b/customer/registerUser.dspy b/b/customer/registerUser.dspy index 10efd48..e7a4a25 100644 --- a/b/customer/registerUser.dspy +++ b/b/customer/registerUser.dspy @@ -65,6 +65,7 @@ async def sync_cn_ai_user(userid=None, orgid=None, username=None, name=None, ema 'opc_apikey': apikey, 'appid': appid, 'secretkey': secretkey, + 'action': 'sync', 'expire_time': None, })