This commit is contained in:
Alex55 2026-08-07 17:53:48 +03:00
parent a3d95669cb
commit 57257326ca
2 changed files with 78 additions and 89 deletions

165
files2.py
View File

@ -2,6 +2,7 @@ from fast_bitrix24 import Bitrix
from woocommerce import API from woocommerce import API
from tg import send_telegram from tg import send_telegram
from dotenv import load_dotenv from dotenv import load_dotenv
import html
import os import os
load_dotenv() load_dotenv()
@ -20,10 +21,34 @@ wcapi = API(
) )
# замените на ваш вебхук для доступа к Bitrix24 # замените на ваш вебхук для доступа к Bitrix24
webhook = os.getenv('WEBHOOKS') webhook = os.getenv('WEBHOOKS')
bx = Bitrix(webhook) bx = Bitrix(webhook, verbose=False)
def clean(value):
"""Woo отдаёт поля с HTML-сущностями: " & и т.п."""
return html.unescape(str(value or '')).strip()
def find_contact_by_email(email):
"""Ищет контакт по email штатным методом Битрикса.
crm.contact.list НЕ умеет фильтровать по мультиполю EMAIL:
фильтр молча игнорируется и возвращается вся база контактов.
После такой выкачки fast_bitrix24 упирается в лимит времени
отработки метода (480 сек / 10 мин) и засыпает на ~10 минут
именно из-за этого скрипт «висел» после создания сделки.
"""
response = bx.call('crm.duplicate.findbycomm', {
'entity_type': 'CONTACT',
'type': 'EMAIL',
'values': [email],
}, raw=True)
result = (response or {}).get('result') or {}
ids = result.get('CONTACT') if isinstance(result, dict) else None
return str(ids[0]) if ids else None
@ -32,7 +57,13 @@ bx = Bitrix(webhook)
def woo(order_id): def woo(order_id):
# order_id = 112882 # order_id = 112882
order = wcapi.get(f"orders/{order_id}").json() response = wcapi.get(f"orders/{order_id}")
if response.status_code != 200:
print(f"❌ WooCommerce вернул {response.status_code} по заказу {order_id}: {response.text[:200]}")
return None
order = response.json()
prud = order['line_items'] prud = order['line_items']
rows = [] rows = []
@ -56,10 +87,10 @@ def woo(order_id):
name = f"{name} {display_key}: {display_value}" name = f"{name} {display_key}: {display_value}"
rows.append({ rows.append({
"PRODUCT_NAME": name, "PRODUCT_NAME": clean(name),
"PRICE": subtotal, "PRICE": subtotal,
"QUANTITY": quantity, "QUANTITY": quantity,
"CURRENCY_ID": "UAH", "CURRENCY_ID": order.get('currency') or "UAH",
}) })
print(rows) print(rows)
@ -76,82 +107,33 @@ def woo(order_id):
print(f"City : {order['shipping']['city']}") print(f"City : {order['shipping']['city']}")
print(f"state : {order['shipping']['state']}") print(f"state : {order['shipping']['state']}")
print(f"address_1 : {order['shipping']['address_1']}") print(f"address_1 : {order['shipping']['address_1']}")
email = {order['billing']['email']} email = order['billing'].get('email')
""" # 🔹 1. Нормализация email
Поиск контакта по email
email должен быть строкой, а не set/list!
"""
# 🔹 Защита: если вдруг передали set/list — берём первый элемент
# 🔹 1. Защита и нормализация email
# 🔹 1. Нормализация email
if isinstance(email, (set, list)):
email = list(email)[0] if email else None
if not email or not isinstance(email, str): if not email or not isinstance(email, str):
print(f"❌ Неверный тип email: {type(email)}") print(f"❌ Неверный тип email: {type(email)}")
return None return None
search_email = email.lower().strip() search_email = email.lower().strip()
print(f"🔍 Ищем контакт с email: {search_email}") print(f"🔍 Ищем контакт с email: {search_email}")
contact_id = None # 🔹 2. Поиск через crm.duplicate.findbycomm — единственный метод,
# который реально умеет искать по мультиполю EMAIL
# 🔹 2. Поиск через ПРАВИЛЬНЫЙ фильтр для мультиполя contact_id = find_contact_by_email(search_email)
# Оператор '=EMAIL' ищет точное совпадение значения в мультиполе
found_contacts = bx.call('crm.contact.list', { if contact_id:
'filter': {'=EMAIL': search_email}, print(f"✅ Контакт найден: #{contact_id}")
'select': ['ID', 'NAME', 'LAST_NAME', 'EMAIL'],
'limit': 5 # Берем с запасом для отладки # 🔹 3. Если не найден — создаем новый
})
# Получаем список результатов
contacts_list = []
if isinstance(found_contacts, dict):
contacts_list = found_contacts.get('result', [])
elif isinstance(found_contacts, list):
contacts_list = found_contacts
print(f"📋 Найдено контактов через фильтр: {len(contacts_list)}")
# 🔹 3. Если фильтр не сработал — ищем вручную (Fallback)
# Это нужно, если в Битриксе много мусора или нестандартные данные
if not contacts_list:
print("⚠️ Фильтр не дал результатов. Пробуем ручной перебор...")
all_contacts = bx.get_all('crm.contact.list', {
'select': ['ID', 'NAME', 'LAST_NAME', 'EMAIL'],
'filter': {'!@EMAIL': ''} # Только где есть email
})
for contact in all_contacts:
for email_item in contact.get('EMAIL', []):
stored_email = email_item.get('VALUE', '').lower().strip()
if stored_email == search_email:
contact_id = contact['ID']
print(f"✅ Контакт найден вручную: #{contact_id}")
break
if contact_id:
break
if not contact_id:
print(f"❌ Контакт действительно не найден в базе")
else:
# Берем первый из найденных фильтром
contact_id = contacts_list[0]['ID']
print(f"✅ Контакт найден через фильтр: #{contact_id}")
# 🔹 4. Если не найден — создаем новый
if not contact_id: if not contact_id:
print(f"⚡ Создаем новый контакт...") print(f"⚡ Создаем новый контакт...")
add_result = bx.call('crm.contact.add', { add_result = bx.call('crm.contact.add', {
'fields': { 'fields': {
'EMAIL': [{'VALUE': search_email, 'TYPE_ID': 'WORK'}], 'EMAIL': [{'VALUE': search_email, 'TYPE_ID': 'WORK'}],
'NAME': order['billing'].get('first_name', ''), 'NAME': clean(order['billing'].get('first_name')),
'LAST_NAME': order['billing'].get('last_name', ''), 'LAST_NAME': clean(order['billing'].get('last_name')),
'PHONE': [{'VALUE': order['billing'].get('phone', ''), 'TYPE_ID': 'WORK'}] 'PHONE': [{'VALUE': clean(order['billing'].get('phone')), 'TYPE_ID': 'WORK'}]
} }
}) })
@ -171,21 +153,21 @@ def woo(order_id):
stage_id = 'PREPARATION' stage_id = 'PREPARATION'
else: else:
stage_id = 'NEW' # Или любой другой статус по умолчанию stage_id = 'NEW' # Или любой другой статус по умолчанию
# 🔹 5. Создаем сделку # 🔹 4. Создаем сделку
if contact_id: if contact_id:
deal_result = bx.call('crm.deal.add', { deal_result = bx.call('crm.deal.add', {
"fields": { "fields": {
"TITLE": f"Order {order['id']}", "TITLE": f"Order {order['id']}",
'STAGE_ID': stage_id, 'STAGE_ID': stage_id,
'CONTACT_IDS': [contact_id], 'CONTACT_IDS': [contact_id],
'COMMENTS': f"{order['customer_note']}", 'COMMENTS': clean(order.get('customer_note')),
'UF_CRM_1684256942409': f"{order['billing'].get('first_name', '')}", 'UF_CRM_1684256942409': clean(order['billing'].get('first_name')),
'UF_CRM_1684256770733': f"{order['billing'].get('last_name', '')}", 'UF_CRM_1684256770733': clean(order['billing'].get('last_name')),
'UF_CRM_1684256782976': f"{order['billing'].get('email', '')}", 'UF_CRM_1684256782976': clean(order['billing'].get('email')),
'UF_CRM_5ECB65AB5E752': f"{order['shipping'].get('city', '')}", 'UF_CRM_5ECB65AB5E752': clean(order['shipping'].get('city')),
'UF_CRM_5ECB65AB672CE': f"{order['shipping'].get('state', '')}", 'UF_CRM_5ECB65AB672CE': clean(order['shipping'].get('state')),
'UF_CRM_5ECB65AB708F4': f"{order['shipping'].get('address_1', '')}", 'UF_CRM_5ECB65AB708F4': clean(order['shipping'].get('address_1')),
'UF_CRM_5ECB65AB7AD15': f"{order['billing'].get('phone', '')}", 'UF_CRM_5ECB65AB7AD15': clean(order['billing'].get('phone')),
}, },
}) })
@ -196,19 +178,22 @@ def woo(order_id):
if deal_id: if deal_id:
print(f"✅ Сделка создана: #{deal_id}") print(f"✅ Сделка создана: #{deal_id}")
if rows: if rows:
bx.call("crm.deal.productrows.set", { bx.call("crm.deal.productrows.set", {
"ID": deal_id, "ID": deal_id,
'rows': rows 'rows': rows
}) })
print(f"📦 Товары добавлены") print(f"📦 Товары добавлены")
return deal_id
else: else:
print(f"❌ Ошибка сделки: {deal_result}") print(f"❌ Ошибка сделки: {deal_result}")
# woo(112928) #920 ЗАГРУЗИЛ return None
# woo(118961) #920 ЗАГРУЗИЛ
def update_deal_stage(order_id): def update_deal_stage(order_id):
"""Находит сделку и меняет её стадию""" """Находит сделку и меняет её стадию"""
@ -229,7 +214,7 @@ def lists_deal(orders):
'crm.deal.list', 'crm.deal.list',
params={ params={
'select': ['ID', 'STAGE_ID'], 'select': ['ID', 'STAGE_ID'],
'filter': {"TITLE": f"Order {orders}",} 'filter': {"=TITLE": f"Order {orders}",}
}) })
if deals and len(deals) > 0: if deals and len(deals) > 0:
deal = deals[0] # Берём первую найденную сделку deal = deals[0] # Берём первую найденную сделку
@ -260,8 +245,12 @@ def toom():
if idsss is None: if idsss is None:
print(f"✅ #{order_id} | Добавить") print(f"✅ #{order_id} | Добавить")
woo(order_id) deal_id = woo(order_id)
send_telegram(f" ✅ Добавил в Б24 {order_id}")
if deal_id:
send_telegram(f" ✅ Добавил в Б24 {order_id}")
else:
send_telegram(f"Не удалось добавить в Б24 {order_id}")
elif status == 'processing' and stage_id == "NEW": elif status == 'processing' and stage_id == "NEW":
print(f"✅ #{order_id} | Изменить статус") print(f"✅ #{order_id} | Изменить статус")
update_deal_stage(idsss) update_deal_stage(idsss)

View File

@ -1 +1 @@
{"id": 113590, "hash": "4598a9c5561c50655b36b21c6ffed12a", "status": "pending", "total": "1615", "modified": "2026-04-06T09:51:21"} {"id": 118969, "hash": "5d5d8a2f0f8ceb2170bc3863cb4b470d", "status": "processing", "total": "2435", "modified": "2026-08-07T11:10:34"}