422 lines
14 KiB
Python
422 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Migration script: gb.rob -> crm_deals + crm_activities
|
|
Execute: python3 migrate_gb_rob.py
|
|
"""
|
|
import os
|
|
import re
|
|
import json
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
|
|
import psycopg2
|
|
from psycopg2 import extras
|
|
|
|
# Database connection
|
|
conn = psycopg2.connect(
|
|
host="localhost",
|
|
database="botserver",
|
|
user="gbuser"
|
|
)
|
|
conn.autocommit = False
|
|
cur = conn.cursor()
|
|
|
|
def parse_history_to_activities(history_text, deal_id, org_id, bot_id, owner_id):
|
|
"""Parse history field into crm_activities"""
|
|
if not history_text:
|
|
return []
|
|
|
|
activities = []
|
|
# Pattern: "DD/MM/YYYY: description" or "DD/MM/YYYY - description"
|
|
pattern = r'(\d{2}/\d{2}/\d{4})[;:]?\s*(.+?)(?=\d{2}/\d{2}/\d{4}|$)'
|
|
|
|
for match in re.finditer(pattern, str(history_text), re.DOTALL):
|
|
date_str = match.group(1)
|
|
description = match.group(2).strip()
|
|
|
|
try:
|
|
due_date = datetime.strptime(date_str, '%d/%m/%Y')
|
|
except:
|
|
due_date = None
|
|
|
|
# Determine activity type from description
|
|
activity_type = 'call'
|
|
if 'email' in description.lower() or 'mail' in description.lower():
|
|
activity_type = 'email'
|
|
elif 'wpp' in description.lower() or 'whatsapp' in description.lower():
|
|
activity_type = 'whatsapp'
|
|
elif 'reunião' in description.lower() or 'reuniao' in description.lower() or 'meeting' in description.lower():
|
|
activity_type = 'meeting'
|
|
|
|
# Determine outcome
|
|
outcome = None
|
|
desc_lower = description.lower()
|
|
if 'enviad' in desc_lower or 'enviado' in desc_lower:
|
|
outcome = 'sent'
|
|
elif 'recebid' in desc_lower:
|
|
outcome = 'received'
|
|
elif 'interesse' in desc_lower and 'não' in desc_lower:
|
|
outcome = 'not_interested'
|
|
elif 'agend' in desc_lower:
|
|
outcome = 'scheduled'
|
|
|
|
activities.append({
|
|
'org_id': org_id,
|
|
'bot_id': bot_id,
|
|
'deal_id': deal_id,
|
|
'activity_type': activity_type,
|
|
'subject': f"Follow-up: {description[:50]}",
|
|
'description': description,
|
|
'due_date': due_date,
|
|
'outcome': outcome,
|
|
'owner_id': owner_id,
|
|
'created_at': datetime.now()
|
|
})
|
|
|
|
return activities
|
|
|
|
|
|
def get_stage_id(stage_name):
|
|
"""Get stage_id from crm_pipeline_stages"""
|
|
cur.execute(
|
|
"SELECT id FROM crm_pipeline_stages WHERE LOWER(name) = %s",
|
|
(stage_name.lower(),)
|
|
)
|
|
result = cur.fetchone()
|
|
return result[0] if result else None
|
|
|
|
|
|
def get_user_id(username):
|
|
"""Get user_id from username"""
|
|
if not username:
|
|
return None
|
|
cur.execute(
|
|
"SELECT id FROM users WHERE username = %s",
|
|
(str(username).strip(),)
|
|
)
|
|
result = cur.fetchone()
|
|
return result[0] if result else None
|
|
|
|
|
|
def get_segment_id(segment_name, org_id, bot_id):
|
|
"""Get or create segment_id"""
|
|
if not segment_name:
|
|
return None
|
|
|
|
# Try to find existing
|
|
cur.execute(
|
|
"SELECT id FROM crm_deal_segments WHERE LOWER(name) = %s",
|
|
(str(segment_name).lower(),)
|
|
)
|
|
result = cur.fetchone()
|
|
if result:
|
|
return result[0]
|
|
|
|
# Create new
|
|
cur.execute("""
|
|
INSERT INTO crm_deal_segments (org_id, bot_id, name)
|
|
VALUES (%s, %s, %s)
|
|
RETURNING id
|
|
""", (org_id, bot_id, str(segment_name).strip()))
|
|
return cur.fetchone()[0]
|
|
|
|
|
|
def migrate_rob_to_deals():
|
|
"""Main migration function"""
|
|
print("🔄 Starting migration...")
|
|
|
|
# Get default org_id and bot_id
|
|
cur.execute("SELECT org_id, id FROM bots LIMIT 1")
|
|
bot_row = cur.fetchone()
|
|
bot_id = bot_row[1]
|
|
|
|
cur.execute("SELECT org_id FROM organizations LIMIT 1")
|
|
org_id = cur.fetchone()[0]
|
|
|
|
# Check if gb.rob exists
|
|
cur.execute("""
|
|
SELECT table_name FROM information_schema.tables
|
|
WHERE table_schema = 'gb' AND table_name = 'rob'
|
|
""")
|
|
if not cur.fetchone():
|
|
print("⚠️ Table gb.rob not found!")
|
|
print(" Checking if it's in public schema...")
|
|
|
|
cur.execute("""
|
|
SELECT table_name FROM information_schema.tables
|
|
WHERE table_name = 'rob'
|
|
""")
|
|
result = cur.fetchone()
|
|
if not result:
|
|
print("❌ Table rob not found in any schema")
|
|
return
|
|
|
|
schema = 'public'
|
|
print(f"✅ Found table in schema: {schema}")
|
|
else:
|
|
schema = 'gb'
|
|
|
|
table_name = f"{schema}.rob" if schema != 'public' else 'rob'
|
|
|
|
print(f"📋 Reading from table: {table_name}")
|
|
|
|
# Get column names
|
|
cur.execute(f"""
|
|
SELECT column_name FROM information_schema.columns
|
|
WHERE table_name = 'rob' AND table_schema = '{schema}'
|
|
ORDER BY ordinal_position
|
|
""")
|
|
columns = [row[0] for row in cur.fetchall()]
|
|
print(f" Columns: {len(columns)}")
|
|
|
|
# Fetch all rows
|
|
cur.execute(f"SELECT * FROM {table_name}")
|
|
rows = cur.fetchall()
|
|
print(f" Rows to migrate: {len(rows)}")
|
|
|
|
# Create column index map
|
|
col_map = {col: i for i, col in enumerate(columns)}
|
|
|
|
migrated = 0
|
|
activities_created = 0
|
|
|
|
for row in rows:
|
|
try:
|
|
# Extract fields using column map
|
|
company = row[col_map.get('company')]
|
|
contact = row[col_map.get('contact')]
|
|
email = row[col_map.get('email')]
|
|
phone = row[col_map.get('phone')]
|
|
mobile = row[col_map.get('mobile')]
|
|
website = row[col_map.get('website')]
|
|
address = row[col_map.get('address')]
|
|
city = row[col_map.get('city')]
|
|
state = row[col_map.get('state')]
|
|
country = row[col_map.get('country')]
|
|
notes = row[col_map.get('notes')]
|
|
history = row[col_map.get('history')]
|
|
action = row[col_map.get('action')]
|
|
segment = row[col_map.get('segment')]
|
|
segment1 = row[col_map.get('segment1')]
|
|
territory = row[col_map.get('territory')]
|
|
linkedin = row[col_map.get('linkedin')]
|
|
facebook = row[col_map.get('facebook')]
|
|
twitter = row[col_map.get('twitter')]
|
|
hard_to_find = row[col_map.get('hard_to_find')]
|
|
value = row[col_map.get('value')]
|
|
chance = row[col_map.get('chance')]
|
|
period = row[col_map.get('period')]
|
|
assignedto = row[col_map.get('assignedto')]
|
|
am = row[col_map.get('am')]
|
|
robid = row[col_map.get('robid')]
|
|
created = row[col_map.get('created'))
|
|
updated_at = row[col_map.get('updated_at'))
|
|
|
|
# Parse name from contact
|
|
first_name = None
|
|
last_name = None
|
|
if contact:
|
|
contact_str = str(contact).strip()
|
|
if ' ' in contact_str:
|
|
parts = contact_str.split(None, 1)
|
|
first_name = parts[0]
|
|
last_name = parts[1] if len(parts) > 1 else None
|
|
else:
|
|
first_name = contact_str
|
|
|
|
# Map action to stage
|
|
action_map = {
|
|
'lead': 'new',
|
|
'desqualified': 'lost',
|
|
'qualified': 'qualified',
|
|
'email': 'new',
|
|
'call': 'new',
|
|
'schedule': 'qualified',
|
|
'poc': 'proposal',
|
|
'win': 'won',
|
|
'recover': 'qualified',
|
|
'wait': 'negotiation',
|
|
'find': 'new',
|
|
'emul_asked': 'new',
|
|
'emul': 'new',
|
|
'close': 'won',
|
|
'recovered': 'qualified'
|
|
}
|
|
stage_name = action_map.get(str(action).lower().strip(), 'new')
|
|
stage_id = get_stage_id(stage_name)
|
|
|
|
# Get owner_id from assignedto
|
|
owner_id = get_user_id(assignedto)
|
|
|
|
# Get am_id
|
|
am_id = get_user_id(am)
|
|
|
|
# Get segment_id
|
|
segment_id = get_segment_id(segment1, org_id, bot_id)
|
|
|
|
# Parse period (hour or morning/afternoon)
|
|
period_val = None
|
|
if period:
|
|
period_str = str(period).lower().strip()
|
|
if period_str.isdigit():
|
|
period_val = int(period_str)
|
|
elif 'manhã' in period_str or 'manha' in period_str:
|
|
period_val = 1
|
|
elif 'tarde' in period_str:
|
|
period_val = 2
|
|
elif 'noite' in period_str:
|
|
period_val = 3
|
|
|
|
# Parse value
|
|
deal_value = None
|
|
if value:
|
|
try:
|
|
deal_value = Decimal(str(value))
|
|
except:
|
|
pass
|
|
|
|
# Parse chance to probability
|
|
probability = 0
|
|
if chance:
|
|
chance_str = str(chance).lower()
|
|
if 'alta' in chance_str:
|
|
probability = 80
|
|
elif 'média' in chance_str or 'media' in chance_str:
|
|
probability = 50
|
|
elif 'baixa' in chance_str:
|
|
probability = 20
|
|
|
|
# Parse created timestamp
|
|
created_at = datetime.now()
|
|
if created:
|
|
try:
|
|
created_at = datetime.fromtimestamp(int(created))
|
|
except:
|
|
try:
|
|
created_at = datetime.strptime(str(created), '%Y-%m-%d')
|
|
except:
|
|
pass
|
|
|
|
# Updated at
|
|
updated_at_val = updated_at if updated_at else datetime.now()
|
|
|
|
# Generate deal_id
|
|
deal_id = extras.uuid.uuid4()
|
|
|
|
# Build custom_fields with remaining columns
|
|
custom_fields = {}
|
|
remaining_cols = [
|
|
'segment', 'segment1', 'segment2', 'networking',
|
|
'tech', 'bot', 'template', 'legalname',
|
|
'questions', 'features',
|
|
'partner', 'partnercontact',
|
|
'revenue', 'once', 'recurrence',
|
|
'employees', 'poc',
|
|
'talk', 'mail',
|
|
'mkt7', 'mkt8', 'mkt9',
|
|
'video_sent', 'prp_sent', 'emulator',
|
|
'CTOFound', 'priority', 'llm_notes',
|
|
'detailedsegment', 'assessment',
|
|
'address1', 'address2', 'address3', 'state_1', 'cep',
|
|
'fax', 'video_1', 'specialistazureappsinfra',
|
|
'specialistazuredataai', 'azurecloudacqspecialist',
|
|
'cloudsolutionmw', 'cloudacqspecialistmw',
|
|
'cloudsolutionbizapps', 'cloudacqspecialistbizapps',
|
|
'cloudsolutionarchitectsappsinfra', 'cloudsolutionarchitectsdataai'
|
|
]
|
|
|
|
for col in remaining_cols:
|
|
if col in col_map:
|
|
val = row[col_map[col]]
|
|
if val is not None:
|
|
custom_fields[col] = val
|
|
|
|
# Insert deal
|
|
cur.execute("""
|
|
INSERT INTO crm_deals (
|
|
id, org_id, bot_id,
|
|
contact_id, account_id,
|
|
owner_id, assignedto_id, am_id,
|
|
description, value,
|
|
stage_id, probability,
|
|
source, segment_id, territory,
|
|
linkedin, facebook, twitter,
|
|
notes,
|
|
hard_to_find, period,
|
|
custom_fields,
|
|
created_at, updated_at
|
|
) VALUES (
|
|
%s, %s, %s,
|
|
NULL, NULL,
|
|
%s, %s, %s,
|
|
%s, %s,
|
|
%s, %s,
|
|
%s, %s, %s,
|
|
%s, %s, %s,
|
|
%s,
|
|
%s, %s,
|
|
%s,
|
|
%s, %s
|
|
)
|
|
""", (
|
|
deal_id, org_id, bot_id,
|
|
None, None,
|
|
owner_id, owner_id, am_id,
|
|
notes, deal_value,
|
|
stage_id, probability,
|
|
action, segment_id, territory,
|
|
linkedin, facebook, twitter,
|
|
notes,
|
|
hard_to_find if hard_to_find else False,
|
|
period_val,
|
|
json.dumps(custom_fields) if custom_fields else '{}',
|
|
created_at, updated_at_val
|
|
))
|
|
|
|
# Migrate history to activities
|
|
activities = parse_history_to_activities(history, deal_id, org_id, bot_id, owner_id)
|
|
|
|
for activity in activities:
|
|
cur.execute("""
|
|
INSERT INTO crm_activities (
|
|
id, org_id, bot_id, deal_id,
|
|
activity_type, subject, description,
|
|
due_date, outcome, owner_id, created_at
|
|
) VALUES (
|
|
%s, %s, %s, %s,
|
|
%s, %s, %s,
|
|
%s, %s, %s, %s
|
|
)
|
|
""", (
|
|
extras.uuid.uuid4(),
|
|
activity['org_id'], activity['bot_id'], deal_id,
|
|
activity['activity_type'], activity['subject'], activity['description'],
|
|
activity['due_date'], activity['outcome'], activity['owner_id'],
|
|
activity['created_at']
|
|
))
|
|
activities_created += 1
|
|
|
|
migrated += 1
|
|
|
|
if migrated % 10 == 0:
|
|
print(f" Migrated {migrated} deals...")
|
|
|
|
except Exception as e:
|
|
print(f" ⚠️ Error on row {migrated}: {e}")
|
|
continue
|
|
|
|
conn.commit()
|
|
print(f"✅ Migration complete!")
|
|
print(f" Deals migrated: {migrated}")
|
|
print(f" Activities created: {activities_created}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
migrate_rob_to_deals()
|
|
except Exception as e:
|
|
print(f"❌ Migration failed: {e}")
|
|
conn.rollback()
|
|
finally:
|
|
cur.close()
|
|
conn.close()
|