#!/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()