← Back to blog

Unlocking ERPNext Data with Pandas & Python

Unlocking ERPNext Data with Pandas & Python

Introduction: Bridging the Gap Between ERPNext and Data Analysis

ERPNext, a powerful open-source ERP system, is a treasure trove of business data. From sales orders and customer records to inventory levels and financial transactions, it holds the keys to understanding your organization's performance. However, extracting meaningful insights directly from ERPNext's database can sometimes feel daunting, especially for those accustomed to the flexibility of modern data analysis tools. This is where Python, and specifically the versatile Pandas library, comes in. Pandas provides an elegant and efficient way to interact with, manipulate, and analyze tabular data, making it the perfect companion for unlocking the full potential of your ERPNext data.

In this post, we'll explore how to use Python and Pandas to connect to your ERPNext database, extract relevant data, perform common data cleaning tasks, and conduct initial analyses. This approach empowers business analysts, developers, and decision-makers to move beyond standard ERPNext reports and gain a more granular and customized understanding of their business operations.

Setting Up Your Environment: Python, Pandas, and Database Connectivity

Before we dive into the data, let's ensure you have the necessary tools installed. You'll need Python installed on your system. If you don't have it, you can download it from python.org.

Next, we need to install the Pandas library. Open your terminal or command prompt and run:

pip install pandas

To connect to your ERPNext database, you'll need a database driver. ERPNext typically uses MariaDB or PostgreSQL. For MariaDB, you can install mysql-connector-python:

pip install mysql-connector-python

For PostgreSQL, install psycopg2:

pip install psycopg2-binary

Finally, you'll need a way to execute SQL queries against your ERPNext database. While you can use command-line tools, for this tutorial, we'll assume you have a method to run SQL queries and retrieve the results, or we'll demonstrate a direct connection using Python. The exact connection string and credentials will depend on your ERPNext setup. You'll typically need the database host, port, username, password, and database name.

Connecting to Your ERPNext Database with Python

Let's start by establishing a connection to your ERPNext database. We'll use the mysql.connector library for this example, assuming a MariaDB backend. The process for PostgreSQL is very similar, just substituting the appropriate library and connection parameters.

import mysql.connector
import pandas as pd
 
# --- Replace with your ERPNext database credentials ---
db_config = {
    'host': 'your_db_host',
    'user': 'your_db_user',
    'password': 'your_db_password',
    'database': 'your_erpnext_database_name'
}
 
try:
    conn = mysql.connector.connect(**db_config)
    print("Successfully connected to ERPNext database")
 
    # Example: Fetching data from the 'Customer' doctype
    query = "SELECT name, customer_name, email_id, phone, creation, modified FROM tabCustomer LIMIT 100"
    
    # Use pandas to read the SQL query directly into a DataFrame
    df_customers = pd.read_sql(query, conn)
    print("\nFirst 5 rows of Customer data:")
    print(df_customers.head())
 
except mysql.connector.Error as err:
    print(f"Error connecting to database: {err}")
finally:
    if conn and conn.is_connected():
        conn.close()
        print("\nDatabase connection closed")

In this code snippet:

  • We import the necessary libraries: mysql.connector for database connection and pandas for data manipulation.
  • We define a db_config dictionary with your specific database credentials. Remember to replace the placeholder values with your actual credentials.
  • We establish a connection using mysql.connector.connect().
  • We define a sample SQL query to fetch data from the tabCustomer table (which corresponds to the Customer doctype in ERPNext). ERPNext tables are typically prefixed with tab.
  • pd.read_sql(query, conn) is a powerful Pandas function that executes the SQL query and directly loads the results into a Pandas DataFrame. This is a significant shortcut compared to manually fetching rows and converting them.
  • We print the first few rows of the DataFrame using .head() to verify that the data has been loaded correctly.
  • A try...except...finally block is used for robust error handling and to ensure the database connection is closed properly.

Understanding how ERPNext stores data is crucial. Each 'DocType' in ERPNext typically corresponds to a table in the database, with the table name usually prefixed by tab (e.g., tabSales Order becomes tabSales Order table). Field names in the DocType often become column names. However, there are nuances:

  • Custom Fields: Custom fields you add to DocTypes will also appear as columns in their respective tables.
  • Child Tables: Fields that are part of a child table (like item rows in a sales order) are stored in separate tables (e.g., tabSales Order Item). You'll need to join these tables if you need to analyze item-level details alongside the main document.
  • Linking Fields: Fields that link to other DocTypes (e.g., customer in tabSales Order) store the name (primary key) of the linked document. You might need to join with the linked table (e.g., tabCustomer) to get more details about the linked entity.

Common tables you might want to query include:

  • tabSales Order
  • tabPurchase Order
  • tabItem
  • tabStock Entry
  • tabJournal Entry
  • tabUser

Remember that the exact table names might vary slightly depending on your ERPNext version and customizations.

Data Cleaning and Preparation with Pandas

Raw data from any system, including ERPNext, often requires cleaning. Pandas excels at this. Let's assume we've fetched data for tabSales Order and tabSales Order Item and need to prepare it for analysis.

# Assuming df_so (Sales Orders) and df_so_items (Sales Order Items) are already loaded
# and conn is the active database connection.
 
try:
    # Fetch Sales Orders
    query_so = "SELECT name, customer, order_date, status, total_amount_after_tax FROM tabSales Order WHERE docstatus=1 LIMIT 500"
    df_so = pd.read_sql(query_so, conn)
 
    # Fetch Sales Order Items
    query_so_items = "SELECT parent, item_code, item_name, qty, rate, amount FROM tabSales Order Item WHERE parent IN ('SO00001', 'SO00002', ...)" # You'd dynamically get parent SO names
    # A more robust way is to join or fetch based on fetched SO names
    so_names = df_so['name'].tolist()
    query_so_items = f"SELECT parent, item_code, item_name, qty, rate, amount FROM tabSales Order Item WHERE parent IN ({', '.join(['%s'] * len(so_names))})"
    df_so_items = pd.read_sql(query_so_items, conn, params=so_names)
 
    # --- Data Cleaning Steps ---
 
    # 1. Handle Missing Values (Example: Fill missing customer email with 'N/A')
    # df_customers['email_id'].fillna('N/A', inplace=True)
 
    # 2. Convert Data Types (Example: Convert order_date to datetime objects)
    df_so['order_date'] = pd.to_datetime(df_so['order_date'])
    df_so_items['amount'] = pd.to_numeric(df_so_items['amount'])
 
    # 3. Rename Columns for Clarity (Optional)
    df_so.rename(columns={'total_amount_after_tax': 'total_value'}, inplace=True)
 
    # 4. Merge DataFrames (Example: Joining Sales Orders with their Items)
    # We can't directly merge based on 'name' and 'parent' as they are different concepts.
    # Instead, let's aggregate item data per sales order.
    item_summary = df_so_items.groupby('parent')['amount'].sum().reset_index()
    item_summary.rename(columns={'parent': 'name', 'amount': 'items_total_value'}, inplace=True)
    
    df_so_detailed = pd.merge(df_so, item_summary, on='name', how='left')
 
    print("\nCleaned and Merged Sales Order Data (first 5 rows):")
    print(df_so_detailed.head())
 
except Exception as e:
    print(f"An error occurred during data processing: {e}")
finally:
    if conn and conn.is_connected():
        conn.close()
 

Key cleaning operations demonstrated:

  • Data Type Conversion: Dates (order_date) and numerical values (amount, rate) are converted to appropriate Python data types using pd.to_datetime() and pd.to_numeric(). This is crucial for calculations and date-based filtering.
  • Handling Missing Values: .fillna() can be used to replace NaN (Not a Number) values with a specific placeholder or value, preventing errors in subsequent analyses.
  • Column Renaming: .rename() makes column names more intuitive if needed.
  • Merging DataFrames: We demonstrate how to aggregate item-level data (df_so_items) and then merge this summary back with the main sales order data (df_so) using pd.merge(). This allows for a consolidated view.

Performing Basic Data Analysis

Once your data is clean and structured, Pandas makes analysis straightforward.

# Continuing from the previous example, assuming df_so_detailed is available
 
# Calculate total revenue including item sums
df_so_detailed['total_revenue'] = df_so_detailed['items_total_value'].fillna(0) # Fill potential NaNs from left merge
 
# Calculate average order value
average_order_value = df_so_detailed['total_revenue'].mean()
print(f"\nAverage Sales Order Value: {average_order_value:.2f}")
 
# Find top customers by total order value (assuming we have customer names)
# For this, we'd need to join with the Customer table earlier or fetch customer names in the SO query
# Let's assume df_so_detailed has a 'customer' column with customer names/IDs
 
# Example: If 'customer' column has IDs, we'd need another query to get names
# For simplicity, let's assume we have aggregated by customer name directly.
# If not, the merge step would need to be extended.
 
# Example aggregation (conceptual, requires customer names in df_so_detailed)
# top_customers = df_so_detailed.groupby('customer_name')['total_revenue'].sum().nlargest(5)
# print("\nTop 5 Customers by Revenue:")
# print(top_customers)
 
# Analyze sales by status
sales_by_status = df_so_detailed['status'].value_counts()
print("\nSales Order Status Distribution:")
print(sales_by_status)
 
# Analyze sales over time (using the order_date)
# First, set order_date as index for easier time series analysis
df_so_time = df_so_detailed.set_index('order_date')
 
# Resample by month and sum the total revenue
monthly_sales = df_so_time['total_revenue'].resample('M').sum()
print("\nMonthly Sales Revenue:")
print(monthly_sales)

Pandas allows for quick calculations like:

  • Aggregations: mean(), sum(), count() for calculating average, total, or counts.
  • Filtering and Sorting: Selecting specific rows based on conditions (e.g., df_so_detailed[df_so_detailed['status'] == 'Completed']) or sorting data.
  • Grouping: groupby() is essential for segmenting data (e.g., by customer, item, or status) and performing calculations on each group.
  • Time Series Analysis: Pandas has excellent built-in support for time-based data, allowing you to resample (e.g., resample('M') for monthly) and aggregate data over different time periods.

Beyond Basic Analysis: Visualization and Machine Learning

This is just the beginning. With your data in Pandas DataFrames, you can easily integrate with other Python libraries for more advanced tasks:

  • Visualization: Libraries like Matplotlib and Seaborn can be used to create insightful charts and graphs from your Pandas DataFrames, making trends and patterns immediately visible.
  • Machine Learning: You can feed your cleaned Pandas DataFrames into machine learning models using libraries like Scikit-learn for tasks such as sales forecasting, customer churn prediction, or anomaly detection.
  • Business Intelligence Tools: Export your processed data to CSV, Excel, or connect directly to BI tools like Tableau or Power BI for sophisticated dashboards.

Conclusion: Empowering Your Business with Data

By combining the operational power of ERPNext with the analytical flexibility of Python and Pandas, you gain a significant advantage. You can move beyond predefined reports to ask specific questions of your data, uncover hidden trends, identify areas for improvement, and ultimately make more informed, data-driven decisions. This approach democratizes data analysis within your organization, empowering more people to leverage the valuable information stored within your ERP system. Start exploring your ERPNext data with Python today – the insights are waiting to be discovered.

Get new articles in your inbox

Occasional writing on AI, ERP and data analytics — no spam, unsubscribe any time.