Update with QB Desktop connection
This commit is contained in:
@@ -19,6 +19,7 @@ from werkzeug.utils import secure_filename
|
||||
|
||||
from src.config.settings import Settings, QBOCredentials, MappingTemplate, FieldMapping
|
||||
from src.api.qbo_client import QBOClient
|
||||
from src.api.qbd_client import QBDesktopClient, QBDCredentials, QBDesktopError, check_qbd_availability
|
||||
from src.core.excel_parser import ExcelParser, ParseResult
|
||||
from src.core.import_processor import ImportProcessor, ImportResult, ImportStatus
|
||||
from dataclasses import asdict
|
||||
@@ -60,8 +61,17 @@ def allowed_file(filename):
|
||||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
|
||||
def get_connection_type():
|
||||
"""Get the current connection type (qbo or qbd)."""
|
||||
return session.get('connection_type', 'qbo')
|
||||
|
||||
|
||||
def get_qbo_client():
|
||||
"""Get or create QBO client from session or saved credentials."""
|
||||
# Check if using QBD instead
|
||||
if get_connection_type() == 'qbd':
|
||||
return None
|
||||
|
||||
# First try session
|
||||
creds_data = session.get('qbo_credentials')
|
||||
if creds_data:
|
||||
@@ -88,24 +98,89 @@ def get_qbo_client():
|
||||
return None
|
||||
|
||||
|
||||
def get_qbd_client():
|
||||
"""Get QBD client from session."""
|
||||
if get_connection_type() != 'qbd':
|
||||
return None
|
||||
|
||||
qbd_data = session.get('qbd_connection')
|
||||
if qbd_data and qbd_data.get('connected'):
|
||||
# Create new client with saved credentials
|
||||
creds = QBDCredentials(
|
||||
application_name=qbd_data.get('application_name', 'QBO Excel Sync'),
|
||||
company_file=qbd_data.get('company_file', '')
|
||||
)
|
||||
client = QBDesktopClient(creds)
|
||||
# We need to reconnect each request since COM objects don't persist
|
||||
try:
|
||||
client.connect()
|
||||
return client
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to reconnect to QBD: {e}")
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def get_active_client():
|
||||
"""Get the active QB client (either QBO or QBD)."""
|
||||
conn_type = get_connection_type()
|
||||
if conn_type == 'qbd':
|
||||
return get_qbd_client()
|
||||
else:
|
||||
return get_qbo_client()
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def inject_common_variables():
|
||||
"""Inject common variables into all templates."""
|
||||
client = get_qbo_client()
|
||||
is_connected = client.is_authenticated if client else False
|
||||
# Get connection type directly from session
|
||||
connection_type = session.get('connection_type', 'qbo')
|
||||
is_connected = False
|
||||
company_name = ""
|
||||
|
||||
if is_connected:
|
||||
if connection_type == 'qbd':
|
||||
# Get QBD connection data directly
|
||||
qbd_data = session.get('qbd_connection')
|
||||
if qbd_data:
|
||||
is_connected = qbd_data.get('connected', False)
|
||||
company_name = qbd_data.get('company_name', '')
|
||||
else:
|
||||
client = get_qbo_client()
|
||||
is_connected = client.is_authenticated if client else False
|
||||
|
||||
if is_connected:
|
||||
try:
|
||||
company_info = client.get_company_info()
|
||||
company_name = company_info.get('CompanyName', '')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# For QBD availability - just check if we're on Windows and have pywin32
|
||||
qbd_available = False
|
||||
qbd_running = False
|
||||
|
||||
import sys
|
||||
if sys.platform == 'win32':
|
||||
try:
|
||||
company_info = client.get_company_info()
|
||||
company_name = company_info.get('CompanyName', '')
|
||||
except Exception:
|
||||
import pythoncom
|
||||
from win32com.client import Dispatch
|
||||
qbd_available = True
|
||||
# If we're connected, QB is obviously running
|
||||
if connection_type == 'qbd' and is_connected:
|
||||
qbd_running = True
|
||||
else:
|
||||
# Assume running if available - actual check happens via API
|
||||
qbd_running = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
return {
|
||||
'is_connected': is_connected,
|
||||
'company_name': company_name,
|
||||
'environment': settings.qbo_environment
|
||||
'environment': settings.qbo_environment,
|
||||
'connection_type': connection_type,
|
||||
'qbd_available': qbd_available,
|
||||
'qbd_running': qbd_running
|
||||
}
|
||||
|
||||
|
||||
@@ -137,20 +212,11 @@ def log_action(action: str, details: str = "", level: str = "info"):
|
||||
def index():
|
||||
"""Home page / Dashboard."""
|
||||
log_action("PAGE_VIEW", "Dashboard")
|
||||
client = get_qbo_client()
|
||||
is_connected = client.is_authenticated if client else False
|
||||
company_name = ""
|
||||
|
||||
if is_connected:
|
||||
try:
|
||||
company_info = client.get_company_info()
|
||||
company_name = company_info.get('CompanyName', '')
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to get company info: {e}")
|
||||
# Don't pass is_connected and company_name - let the context processor handle it
|
||||
# This allows both QBO and QBD connection status to be shown correctly
|
||||
|
||||
return render_template('index.html',
|
||||
is_connected=is_connected,
|
||||
company_name=company_name,
|
||||
environment=settings.qbo_environment)
|
||||
|
||||
|
||||
@@ -159,21 +225,12 @@ def connection():
|
||||
"""Connection management page."""
|
||||
log_action("PAGE_VIEW", "Connection")
|
||||
creds = settings.get_credentials()
|
||||
client = get_qbo_client()
|
||||
is_connected = client.is_authenticated if client else False
|
||||
|
||||
company_name = ""
|
||||
if is_connected:
|
||||
try:
|
||||
company_info = client.get_company_info()
|
||||
company_name = company_info.get('CompanyName', '')
|
||||
except Exception:
|
||||
pass
|
||||
# Don't pass is_connected and company_name - let the context processor handle it
|
||||
# This allows QBD connection status to be shown correctly
|
||||
|
||||
return render_template('connection.html',
|
||||
credentials=creds,
|
||||
is_connected=is_connected,
|
||||
company_name=company_name,
|
||||
environment=settings.qbo_environment)
|
||||
|
||||
|
||||
@@ -181,14 +238,12 @@ def connection():
|
||||
def import_page():
|
||||
"""Import page."""
|
||||
log_action("PAGE_VIEW", "Import")
|
||||
client = get_qbo_client()
|
||||
is_connected = client.is_authenticated if client else False
|
||||
|
||||
# Get available templates
|
||||
templates = settings.get_templates()
|
||||
|
||||
# Don't pass is_connected - let the context processor handle it
|
||||
return render_template('import.html',
|
||||
is_connected=is_connected,
|
||||
templates=templates,
|
||||
environment=settings.qbo_environment)
|
||||
|
||||
@@ -391,30 +446,512 @@ def disconnect():
|
||||
return jsonify({'success': False, 'error': str(e)}), 400
|
||||
|
||||
|
||||
@app.route('/api/connection/test', methods=['GET'])
|
||||
def test_connection():
|
||||
"""Test QBO connection."""
|
||||
@app.route('/api/session/debug', methods=['GET'])
|
||||
def session_debug():
|
||||
"""Debug endpoint to check session contents."""
|
||||
# Get session ID if using filesystem sessions
|
||||
session_id = None
|
||||
try:
|
||||
client = get_qbo_client()
|
||||
from flask import request
|
||||
session_id = request.cookies.get('session')
|
||||
except:
|
||||
pass
|
||||
|
||||
return jsonify({
|
||||
'session_id': session_id,
|
||||
'connection_type': session.get('connection_type', 'not set'),
|
||||
'qbd_connection': session.get('qbd_connection', 'not set'),
|
||||
'qbo_credentials': 'present' if session.get('qbo_credentials') else 'not set',
|
||||
'session_keys': list(session.keys())
|
||||
})
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Routes - QuickBooks Desktop Connection
|
||||
# =============================================================================
|
||||
|
||||
@app.route('/api/qbd/status', methods=['GET'])
|
||||
def qbd_status():
|
||||
"""Check QuickBooks Desktop availability and status."""
|
||||
try:
|
||||
status = check_qbd_availability()
|
||||
|
||||
if not client or not client.is_authenticated:
|
||||
return jsonify({'success': False, 'connected': False, 'error': 'Not connected'})
|
||||
# Also check current connection
|
||||
qbd_data = session.get('qbd_connection', {})
|
||||
status['connected'] = qbd_data.get('connected', False)
|
||||
status['company_name'] = qbd_data.get('company_name', '')
|
||||
status['connection_type'] = get_connection_type()
|
||||
|
||||
company_info = client.get_company_info()
|
||||
customers = client.get_customers()
|
||||
vendors = client.get_vendors()
|
||||
accounts = client.get_accounts()
|
||||
return jsonify({'success': True, **status})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 400
|
||||
|
||||
|
||||
@app.route('/api/qbd/connect', methods=['POST'])
|
||||
def qbd_connect():
|
||||
"""Connect to QuickBooks Desktop using subprocess to avoid COM threading issues."""
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
try:
|
||||
log_action("CREATE", "Attempting to connect to QuickBooks Desktop (via subprocess)")
|
||||
|
||||
log_action("CREATE", f"Connection test successful: {len(customers)} customers, {len(vendors)} vendors, {len(accounts)} accounts")
|
||||
# Create a small Python script to run the connection in a separate process
|
||||
# This avoids all Flask threading/COM issues
|
||||
connect_script = '''
|
||||
import sys
|
||||
import json
|
||||
|
||||
try:
|
||||
import pythoncom
|
||||
from win32com.client import Dispatch
|
||||
|
||||
pythoncom.CoInitialize()
|
||||
|
||||
rp = Dispatch("QBXMLRP2.RequestProcessor")
|
||||
rp.OpenConnection2("", "QBO Excel Sync", 1)
|
||||
ticket = rp.BeginSession("", 0)
|
||||
|
||||
# Query company name
|
||||
request = """<?xml version="1.0" encoding="utf-8"?>
|
||||
<?qbxml version="13.0"?>
|
||||
<QBXML>
|
||||
<QBXMLMsgsRq onError="stopOnError">
|
||||
<CompanyQueryRq></CompanyQueryRq>
|
||||
</QBXMLMsgsRq>
|
||||
</QBXML>"""
|
||||
|
||||
response = rp.ProcessRequest(ticket, request)
|
||||
|
||||
# Parse company name
|
||||
import xml.etree.ElementTree as ET
|
||||
root = ET.fromstring(response)
|
||||
company_elem = root.find(".//CompanyName")
|
||||
company_name = company_elem.text if company_elem is not None else "QuickBooks Desktop"
|
||||
|
||||
rp.EndSession(ticket)
|
||||
rp.CloseConnection()
|
||||
pythoncom.CoUninitialize()
|
||||
|
||||
print(json.dumps({"success": True, "company_name": company_name}))
|
||||
|
||||
except Exception as e:
|
||||
print(json.dumps({"success": False, "error": str(e)}))
|
||||
sys.exit(1)
|
||||
'''
|
||||
|
||||
# Run the script in a separate Python process
|
||||
result = subprocess.run(
|
||||
[sys.executable, '-c', connect_script],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60 # 60 second timeout
|
||||
)
|
||||
|
||||
logger.info(f"Subprocess stdout: {result.stdout}")
|
||||
logger.info(f"Subprocess stderr: {result.stderr}")
|
||||
|
||||
if result.returncode != 0:
|
||||
error_msg = result.stderr or result.stdout or "Unknown error"
|
||||
log_action("CREATE", f"QBD subprocess failed: {error_msg}", "error")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f"Connection failed: {error_msg}"
|
||||
}), 400
|
||||
|
||||
# Parse the JSON output
|
||||
try:
|
||||
output = json.loads(result.stdout.strip())
|
||||
except json.JSONDecodeError:
|
||||
log_action("CREATE", f"QBD subprocess invalid output: {result.stdout}", "error")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f"Invalid response from connection script: {result.stdout}"
|
||||
}), 400
|
||||
|
||||
if not output.get('success'):
|
||||
error_msg = output.get('error', 'Unknown error')
|
||||
log_action("CREATE", f"QBD connection failed: {error_msg}", "error")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': error_msg
|
||||
}), 400
|
||||
|
||||
company_name = output.get('company_name', 'QuickBooks Desktop')
|
||||
|
||||
# Store connection info in session
|
||||
session['connection_type'] = 'qbd'
|
||||
session['qbd_connection'] = {
|
||||
'connected': True,
|
||||
'company_name': company_name,
|
||||
'company_file': '',
|
||||
'application_name': 'QBO Excel Sync',
|
||||
'connected_at': datetime.now().isoformat()
|
||||
}
|
||||
session.modified = True # Force session to be saved
|
||||
|
||||
log_action("CREATE", f"Connected to QuickBooks Desktop: {company_name}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'company_name': company_name,
|
||||
'message': 'Connected to QuickBooks Desktop'
|
||||
})
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
log_action("CREATE", "QBD connection timed out after 60 seconds", "error")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': "Connection timed out. QuickBooks may be waiting for user input or not responding."
|
||||
}), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_trace = traceback.format_exc()
|
||||
logger.error(f"QBD connection failed: {str(e)}\nTraceback: {error_trace}")
|
||||
log_action("CREATE", f"QBD connection failed: {str(e)}", "error")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}), 400
|
||||
|
||||
|
||||
@app.route('/api/qbd/debug', methods=['GET'])
|
||||
def qbd_debug():
|
||||
"""Debug endpoint to check QBD availability with detailed info."""
|
||||
import sys
|
||||
|
||||
result = {
|
||||
'platform': sys.platform,
|
||||
'is_windows': sys.platform == 'win32',
|
||||
'python_version': sys.version,
|
||||
}
|
||||
|
||||
# Check pywin32
|
||||
try:
|
||||
import pythoncom
|
||||
from win32com.client import Dispatch
|
||||
result['pywin32_installed'] = True
|
||||
result['pythoncom_available'] = True
|
||||
except ImportError as e:
|
||||
result['pywin32_installed'] = False
|
||||
result['pywin32_error'] = str(e)
|
||||
return jsonify(result)
|
||||
|
||||
# Try to create RequestProcessor
|
||||
try:
|
||||
pythoncom.CoInitialize()
|
||||
result['com_initialized'] = True
|
||||
except Exception as e:
|
||||
result['com_initialized'] = False
|
||||
result['com_error'] = str(e)
|
||||
return jsonify(result)
|
||||
|
||||
try:
|
||||
rp = Dispatch("QBXMLRP2.RequestProcessor")
|
||||
result['request_processor_created'] = True
|
||||
except Exception as e:
|
||||
result['request_processor_created'] = False
|
||||
result['request_processor_error'] = str(e)
|
||||
try:
|
||||
pythoncom.CoUninitialize()
|
||||
except:
|
||||
pass
|
||||
return jsonify(result)
|
||||
|
||||
# Try OpenConnection2
|
||||
try:
|
||||
rp.OpenConnection2("", "QBD Debug Test", 1) # 1 = localQBD
|
||||
result['connection_opened'] = True
|
||||
except Exception as e:
|
||||
result['connection_opened'] = False
|
||||
result['connection_error'] = str(e)
|
||||
try:
|
||||
pythoncom.CoUninitialize()
|
||||
except:
|
||||
pass
|
||||
return jsonify(result)
|
||||
|
||||
# Try BeginSession
|
||||
try:
|
||||
ticket = rp.BeginSession("", 0) # 0 = qbFileOpenDoNotCare
|
||||
result['session_started'] = True
|
||||
result['session_ticket'] = ticket[:20] + "..." if ticket else None
|
||||
|
||||
# End session
|
||||
rp.EndSession(ticket)
|
||||
result['session_ended'] = True
|
||||
except Exception as e:
|
||||
result['session_started'] = False
|
||||
result['session_error'] = str(e)
|
||||
|
||||
# Close connection
|
||||
try:
|
||||
rp.CloseConnection()
|
||||
result['connection_closed'] = True
|
||||
except Exception as e:
|
||||
result['connection_closed'] = False
|
||||
result['close_error'] = str(e)
|
||||
|
||||
try:
|
||||
pythoncom.CoUninitialize()
|
||||
except:
|
||||
pass
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@app.route('/api/qbd/disconnect', methods=['POST'])
|
||||
def qbd_disconnect():
|
||||
"""Disconnect from QuickBooks Desktop."""
|
||||
try:
|
||||
log_action("DELETE", "Disconnecting from QuickBooks Desktop")
|
||||
|
||||
# Clear QBD session data
|
||||
session.pop('qbd_connection', None)
|
||||
session['connection_type'] = 'qbo' # Reset to QBO mode
|
||||
|
||||
logger.info("Disconnected from QuickBooks Desktop")
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
log_action("DELETE", f"QBD disconnect failed: {str(e)}", "error")
|
||||
return jsonify({'success': False, 'error': str(e)}), 400
|
||||
|
||||
|
||||
@app.route('/api/qbd/test', methods=['GET'])
|
||||
def qbd_test():
|
||||
"""Test QuickBooks Desktop connection using subprocess."""
|
||||
import subprocess
|
||||
import json as json_module
|
||||
|
||||
try:
|
||||
qbd_data = session.get('qbd_connection', {})
|
||||
if not qbd_data.get('connected'):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'connected': False,
|
||||
'error': 'Not connected to QuickBooks Desktop'
|
||||
})
|
||||
|
||||
# Run test in subprocess
|
||||
test_script = '''
|
||||
import sys
|
||||
import json
|
||||
|
||||
try:
|
||||
import pythoncom
|
||||
from win32com.client import Dispatch
|
||||
|
||||
pythoncom.CoInitialize()
|
||||
|
||||
rp = Dispatch("QBXMLRP2.RequestProcessor")
|
||||
rp.OpenConnection2("", "QBO Excel Sync", 1)
|
||||
ticket = rp.BeginSession("", 0)
|
||||
|
||||
# Query company
|
||||
company_request = """<?xml version="1.0" encoding="utf-8"?>
|
||||
<?qbxml version="13.0"?>
|
||||
<QBXML>
|
||||
<QBXMLMsgsRq onError="stopOnError">
|
||||
<CompanyQueryRq></CompanyQueryRq>
|
||||
</QBXMLMsgsRq>
|
||||
</QBXML>"""
|
||||
|
||||
response = rp.ProcessRequest(ticket, company_request)
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
root = ET.fromstring(response)
|
||||
company_elem = root.find(".//CompanyName")
|
||||
company_name = company_elem.text if company_elem is not None else "QuickBooks Desktop"
|
||||
|
||||
# Query customers count
|
||||
customer_request = """<?xml version="1.0" encoding="utf-8"?>
|
||||
<?qbxml version="13.0"?>
|
||||
<QBXML>
|
||||
<QBXMLMsgsRq onError="stopOnError">
|
||||
<CustomerQueryRq><MaxReturned>1000</MaxReturned></CustomerQueryRq>
|
||||
</QBXMLMsgsRq>
|
||||
</QBXML>"""
|
||||
response = rp.ProcessRequest(ticket, customer_request)
|
||||
root = ET.fromstring(response)
|
||||
customers = root.findall(".//CustomerRet")
|
||||
|
||||
# Query vendors count
|
||||
vendor_request = """<?xml version="1.0" encoding="utf-8"?>
|
||||
<?qbxml version="13.0"?>
|
||||
<QBXML>
|
||||
<QBXMLMsgsRq onError="stopOnError">
|
||||
<VendorQueryRq><MaxReturned>1000</MaxReturned></VendorQueryRq>
|
||||
</QBXMLMsgsRq>
|
||||
</QBXML>"""
|
||||
response = rp.ProcessRequest(ticket, vendor_request)
|
||||
root = ET.fromstring(response)
|
||||
vendors = root.findall(".//VendorRet")
|
||||
|
||||
# Query accounts count
|
||||
account_request = """<?xml version="1.0" encoding="utf-8"?>
|
||||
<?qbxml version="13.0"?>
|
||||
<QBXML>
|
||||
<QBXMLMsgsRq onError="stopOnError">
|
||||
<AccountQueryRq><MaxReturned>1000</MaxReturned></AccountQueryRq>
|
||||
</QBXMLMsgsRq>
|
||||
</QBXML>"""
|
||||
response = rp.ProcessRequest(ticket, account_request)
|
||||
root = ET.fromstring(response)
|
||||
accounts = root.findall(".//AccountRet")
|
||||
|
||||
rp.EndSession(ticket)
|
||||
rp.CloseConnection()
|
||||
pythoncom.CoUninitialize()
|
||||
|
||||
print(json.dumps({
|
||||
"success": True,
|
||||
"company_name": company_name,
|
||||
"customers_count": len(customers),
|
||||
"vendors_count": len(vendors),
|
||||
"accounts_count": len(accounts)
|
||||
}))
|
||||
|
||||
except Exception as e:
|
||||
print(json.dumps({"success": False, "error": str(e)}))
|
||||
sys.exit(1)
|
||||
'''
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, '-c', test_script],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
session.pop('qbd_connection', None)
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'connected': False,
|
||||
'error': result.stderr or result.stdout or "Test failed"
|
||||
})
|
||||
|
||||
output = json_module.loads(result.stdout.strip())
|
||||
|
||||
if not output.get('success'):
|
||||
session.pop('qbd_connection', None)
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'connected': False,
|
||||
'error': output.get('error', 'Unknown error')
|
||||
})
|
||||
|
||||
# Update session
|
||||
session['qbd_connection']['company_name'] = output.get('company_name')
|
||||
|
||||
log_action("CREATE", f"QBD test successful: {output.get('customers_count')} customers")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'connected': True,
|
||||
'company_name': company_info.get('CompanyName', 'Unknown'),
|
||||
'customers_count': len(customers),
|
||||
'vendors_count': len(vendors),
|
||||
'accounts_count': len(accounts)
|
||||
'company_name': output.get('company_name'),
|
||||
'customers_count': output.get('customers_count', 0),
|
||||
'vendors_count': output.get('vendors_count', 0),
|
||||
'accounts_count': output.get('accounts_count', 0)
|
||||
})
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
session.pop('qbd_connection', None)
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'connected': False,
|
||||
'error': 'Test timed out'
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'connected': False,
|
||||
'error': str(e)
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/connection/switch', methods=['POST'])
|
||||
def switch_connection():
|
||||
"""Switch between QBO and QBD connection modes."""
|
||||
try:
|
||||
data = request.json or {}
|
||||
new_type = data.get('type', 'qbo')
|
||||
|
||||
if new_type not in ['qbo', 'qbd']:
|
||||
return jsonify({'success': False, 'error': 'Invalid connection type'}), 400
|
||||
|
||||
session['connection_type'] = new_type
|
||||
log_action("EDIT", f"Switched connection type to {new_type.upper()}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'connection_type': new_type
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 400
|
||||
|
||||
|
||||
@app.route('/api/connection/test', methods=['GET'])
|
||||
def test_connection():
|
||||
"""Test QB connection (QBO or QBD based on current mode)."""
|
||||
try:
|
||||
connection_type = get_connection_type()
|
||||
|
||||
if connection_type == 'qbd':
|
||||
# Test QBD connection
|
||||
qbd_data = session.get('qbd_connection', {})
|
||||
if not qbd_data.get('connected'):
|
||||
return jsonify({'success': False, 'connected': False, 'error': 'Not connected'})
|
||||
|
||||
creds = QBDCredentials(
|
||||
application_name=qbd_data.get('application_name', 'QBO Excel Sync'),
|
||||
company_file=qbd_data.get('company_file', '')
|
||||
)
|
||||
|
||||
client = QBDesktopClient(creds)
|
||||
client.connect()
|
||||
|
||||
customers = client.get_customers()
|
||||
vendors = client.get_vendors()
|
||||
accounts = client.get_accounts()
|
||||
company_name = client.company_name
|
||||
|
||||
client.disconnect()
|
||||
|
||||
log_action("CREATE", f"QBD connection test successful: {len(customers)} customers")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'connected': True,
|
||||
'connection_type': 'qbd',
|
||||
'company_name': company_name,
|
||||
'customers_count': len(customers),
|
||||
'vendors_count': len(vendors),
|
||||
'accounts_count': len(accounts)
|
||||
})
|
||||
else:
|
||||
# Test QBO connection
|
||||
client = get_qbo_client()
|
||||
|
||||
if not client or not client.is_authenticated:
|
||||
return jsonify({'success': False, 'connected': False, 'error': 'Not connected'})
|
||||
|
||||
company_info = client.get_company_info()
|
||||
customers = client.get_customers()
|
||||
vendors = client.get_vendors()
|
||||
accounts = client.get_accounts()
|
||||
|
||||
log_action("CREATE", f"QBO connection test successful: {len(customers)} customers")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'connected': True,
|
||||
'connection_type': 'qbo',
|
||||
'company_name': company_info.get('CompanyName', 'Unknown'),
|
||||
'customers_count': len(customers),
|
||||
'vendors_count': len(vendors),
|
||||
'accounts_count': len(accounts)
|
||||
})
|
||||
except Exception as e:
|
||||
log_action("CREATE", f"Connection test failed: {str(e)}", "error")
|
||||
return jsonify({'success': False, 'connected': False, 'error': str(e)})
|
||||
@@ -1456,8 +1993,10 @@ if __name__ == '__main__':
|
||||
UPLOAD_FOLDER.mkdir(exist_ok=True)
|
||||
|
||||
# Run the app
|
||||
# NOTE: threaded=False is important for COM/QuickBooks Desktop compatibility
|
||||
# COM objects don't work well with Flask's default multi-threaded mode
|
||||
debug_mode = os.environ.get('FLASK_DEBUG', 'true').lower() == 'true'
|
||||
port = int(os.environ.get('PORT', 9000))
|
||||
|
||||
logger.info(f"Starting QBO Excel Sync Web App on port {port} (debug={debug_mode})")
|
||||
app.run(host='0.0.0.0', port=port, debug=debug_mode)
|
||||
app.run(host='0.0.0.0', port=port, debug=debug_mode, threaded=False)
|
||||
Reference in New Issue
Block a user