import xmlrpc.client import psycopg2 from psycopg2.extras import RealDictCursor from flask import Flask, render_template, request, session, redirect, url_for, jsonify from datetime import datetime, timedelta app = Flask(__name__) # Secret key for sessions app.secret_key = 'super_secret_tada_migration_key_change_me_in_prod' app.permanent_session_lifetime = timedelta(hours=8) # --- Configurations --- # PostgreSQL (Hardcoded as requested) DB_HOST = "192.169.0.10" DB_PORT = "5432" DB_NAME = "postgres" DB_USER = "postgres" DB_PASS = "Mulut!23Berkomunikasi" # Odoo 19 ODOO_URL = "http://localhost:1900" ODOO_DB = "odoo19_OT_v3" ODOO_USER = "admin" ODOO_PASS = "admin" def get_db_connection(): conn = psycopg2.connect( host=DB_HOST, port=DB_PORT, dbname=DB_NAME, user=DB_USER, password=DB_PASS ) return conn @app.route('/') def index(): if 'employee_id' not in session: return redirect(url_for('login')) return render_template('index.html', employee_id=session['employee_id']) @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': employee_id = request.form.get('employee_id') if employee_id: session.permanent = True session['employee_id'] = employee_id return redirect(url_for('index')) return render_template('login.html') @app.route('/logout') def logout(): session.pop('employee_id', None) return redirect(url_for('login')) @app.route('/api/customers') def get_customers(): if 'employee_id' not in session: return jsonify({"error": "Unauthorized"}), 401 # DataTables parameters draw = request.args.get('draw', type=int, default=1) start = request.args.get('start', type=int, default=0) length = request.args.get('length', type=int, default=10) search_value = request.args.get('search[value]', default="") order_column_index = request.args.get('order[0][column]', type=int, default=0) order_dir = request.args.get('order[0][dir]', default='asc') columns_map = ['id', 'name', 'phone_number', 'email', 'level', 'point_amount', 'responsible', 'total_spending'] order_column = columns_map[order_column_index] if order_column_index < len(columns_map) else 'id' if order_dir not in ['asc', 'desc']: order_dir = 'asc' conn = None try: conn = get_db_connection() cursor = conn.cursor(cursor_factory=RealDictCursor) # Base query query = "SELECT * FROM tada_member" count_query = "SELECT COUNT(*) FROM tada_member" params = [] if search_value: search_pattern = f"%{search_value}%" where_clause = " WHERE name ILIKE %s OR phone_number ILIKE %s OR email ILIKE %s" query += where_clause count_query += where_clause params.extend([search_pattern, search_pattern, search_pattern]) # Get total filtered count cursor.execute(count_query, params) total_filtered = cursor.fetchone()['count'] # Total records without filter cursor.execute("SELECT COUNT(*) FROM tada_member") total_records = cursor.fetchone()['count'] # Add pagination and sorting query += f" ORDER BY {order_column} {order_dir.upper()} LIMIT %s OFFSET %s" params.extend([length, start]) cursor.execute(query, params) customers = cursor.fetchall() # Convert date fields to string if any to allow JSON serialization for row in customers: if row.get('birthday'): row['birthday'] = str(row['birthday']) if row.get('updated_at'): row['updated_at'] = str(row['updated_at']) return jsonify({ "draw": draw, "recordsTotal": total_records, "recordsFiltered": total_filtered, "data": customers }) except Exception as e: print(f"Error fetching customers: {e}") return jsonify({"error": str(e)}), 500 finally: if conn: conn.close() @app.route('/api/migrate/', methods=['POST']) def migrate_customer(customer_id): if 'employee_id' not in session: return jsonify({"success": False, "message": "Unauthorized"}), 401 employee_id = session['employee_id'] conn = None try: # Fetch customer data conn = get_db_connection() cursor = conn.cursor(cursor_factory=RealDictCursor) cursor.execute("SELECT * FROM tada_member WHERE id = %s", (customer_id,)) customer = cursor.fetchone() if not customer: return jsonify({"success": False, "message": "Customer not found."}), 404 # Odoo connection common = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/common') uid = common.authenticate(ODOO_DB, ODOO_USER, ODOO_PASS, {}) if not uid: return jsonify({"success": False, "message": "Failed to authenticate with Odoo."}), 500 models = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/object') # 1. Find loyalty.program based on level level = customer.get('level', '') program_ids = models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'loyalty.program', 'search', [[('name', 'ilike', level)]]) if not program_ids: return jsonify({"success": False, "message": f"Loyalty program containing '{level}' not found in Odoo."}), 404 program_id = program_ids[0] # Map gender safely (e.g. 'female' -> 'Female') gender_val = customer.get('gender') if isinstance(gender_val, str) and gender_val: gender_val = gender_val.title() # 2. Create res.partner partner_data = { 'name': customer.get('name', ''), 'phone': customer.get('phone_number', ''), 'email': customer.get('email', ''), 'gender': gender_val or '', 'birth_date': str(customer.get('birthday', '')) if customer.get('birthday') else False, 'city': customer.get('city', ''), 'membership_level_id': program_id, 'total_spend': float(customer.get('total_spending') or 0.0) } # Replace None with False for Odoo XML-RPC compatibility partner_data = {k: (v if v is not None else False) for k, v in partner_data.items()} partner_id = models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'res.partner', 'create', [partner_data]) # 3. Create loyalty.card point_amount = customer.get('point_amount') card_data = { 'partner_id': partner_id, 'program_id': program_id, 'points': float(point_amount) if point_amount is not None else 0.0 } card_id = models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'loyalty.card', 'create', [card_data]) # 4. Update tada_member now = datetime.now() cursor.execute("UPDATE tada_member SET responsible = %s, updated_at = %s WHERE id = %s", (employee_id, now, customer_id)) conn.commit() return jsonify({"success": True, "message": "Migrasi data berhasil!"}) except Exception as e: if conn: conn.rollback() print(f"Migration Error: {e}") return jsonify({"success": False, "message": str(e)}), 500 finally: if conn: conn.close() if __name__ == '__main__': app.run(debug=True, port=5000, host='0.0.0.0')