rev
This commit is contained in:
parent
a3d95669cb
commit
57257326ca
137
files2.py
137
files2.py
|
|
@ -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()
|
||||||
|
|
||||||
|
|
@ -21,9 +22,33 @@ 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,18 +107,9 @@ 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')
|
||||||
|
|
||||||
"""
|
|
||||||
Поиск контакта по email
|
|
||||||
⚠️ email должен быть строкой, а не set/list!
|
|
||||||
"""
|
|
||||||
# 🔹 Защита: если вдруг передали set/list — берём первый элемент
|
|
||||||
# 🔹 1. Защита и нормализация email
|
|
||||||
# 🔹 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
|
||||||
|
|
@ -95,63 +117,23 @@ def woo(order_id):
|
||||||
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
|
||||||
|
contact_id = find_contact_by_email(search_email)
|
||||||
|
|
||||||
# 🔹 2. Поиск через ПРАВИЛЬНЫЙ фильтр для мультиполя
|
|
||||||
# Оператор '=EMAIL' ищет точное совпадение значения в мультиполе
|
|
||||||
found_contacts = bx.call('crm.contact.list', {
|
|
||||||
'filter': {'=EMAIL': search_email},
|
|
||||||
'select': ['ID', 'NAME', 'LAST_NAME', 'EMAIL'],
|
|
||||||
'limit': 5 # Берем с запасом для отладки
|
|
||||||
})
|
|
||||||
|
|
||||||
# Получаем список результатов
|
|
||||||
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:
|
if contact_id:
|
||||||
break
|
print(f"✅ Контакт найден: #{contact_id}")
|
||||||
|
|
||||||
if not contact_id:
|
# 🔹 3. Если не найден — создаем новый
|
||||||
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')),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -203,12 +185,15 @@ def woo(order_id):
|
||||||
'rows': rows
|
'rows': rows
|
||||||
})
|
})
|
||||||
print(f"📦 Товары добавлены")
|
print(f"📦 Товары добавлены")
|
||||||
|
|
||||||
|
return deal_id
|
||||||
else:
|
else:
|
||||||
print(f"❌ Ошибка сделки: {deal_result}")
|
print(f"❌ Ошибка сделки: {deal_result}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# woo(112928) #920 ЗАГРУЗИЛ
|
# 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)
|
||||||
|
|
||||||
|
if deal_id:
|
||||||
send_telegram(f" ✅ Добавил в Б24 {order_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)
|
||||||
|
|
|
||||||
|
|
@ -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"}
|
||||||
Loading…
Reference in New Issue