Skip to content

How to Automate Legacy ERP Systems with Python: A Real-World RPA Solution

The Problem

My company uses a legacy ERP system from the early 2000s. It has no API, no web services, and no automation interface. Every day, someone has to manually enter orders by clicking through screens and typing data. We process over 16,000 orders per year. Each order takes about 22 seconds manually.

I asked vendors for automation solutions. They quoted us $23,000.

That’s when I decided to build it myself with Python.

The Solution: GUI Automation with PyAutoGUI

Since the ERP has no API, I had to automate what a human does: clicking and typing. PyAutoGUI is a Python library that controls the mouse and keyboard programmatically.

Here’s the basic setup:

erp_bot.py
import pyautogui
import time
# Safety feature: move mouse to corner to abort
pyautogui.FAILSAFE = True
# Add small pause between actions
pyautogui.PAUSE = 0.5

The FAILSAFE setting is crucial. If something goes wrong, just slam your mouse to the corner of the screen. The script will stop immediately.

My First Attempt: Hard-coded Coordinates

I started by recording the exact screen positions where I needed to click:

erp_bot_v1.py
import pyautogui
import time
pyautogui.FAILSAFE = True
def process_order(customer_id, sku, quantity):
# Click on Orders menu
pyautogui.click(x=150, y=50)
time.sleep(0.5)
# Click New Order
pyautogui.click(x=150, y=100)
time.sleep(1)
# Tab to customer field and type
pyautogui.press('tab')
pyautogui.write(customer_id)
pyautogui.press('tab')
# Enter SKU
pyautogui.write(sku)
pyautogui.press('tab')
pyautogui.write(str(quantity))
# Submit
pyautogui.click(x=700, y=500)
time.sleep(2)
# Process one order
process_order("CUST001", "SKU-12345", 10)

This worked… until someone moved the ERP window. The coordinates were wrong, and the bot started clicking random places on the screen.

The Fix: Image Recognition Instead of Coordinates

I switched to image-based element detection. Instead of hard-coding coordinates, I take screenshots of buttons and let PyAutoGUI find them on screen:

erp_bot_v2.py
import pyautogui
import time
from PIL import Image
pyautogui.FAILSAFE = True
pyautogui.PAUSE = 0.3
def find_and_click(image_path, timeout=10):
"""Find an image on screen and click it."""
start = time.time()
while time.time() - start < timeout:
try:
location = pyautogui.locateOnScreen(image_path, confidence=0.9)
if location:
center = pyautogui.center(location)
pyautogui.click(center)
return True
except pyautogui.ImageNotFoundException:
pass
time.sleep(0.5)
raise TimeoutError(f"Could not find {image_path}")
def process_order_robust(customer_id, sku, quantity):
"""Process order using image recognition."""
# Navigate using button images
find_and_click('images/orders_menu.png')
find_and_click('images/new_order_button.png')
# Type customer ID
pyautogui.press('tab')
pyautogui.write(customer_id)
# Enter line item
find_and_click('images/line_item_field.png')
pyautogui.write(sku)
pyautogui.press('tab')
pyautogui.write(str(quantity))
# Submit
find_and_click('images/submit_button.png')
time.sleep(1)
return True

This approach is much more reliable. The bot finds buttons visually, just like a human would.

Adding Error Handling

A bot that crashes on every error is useless. I wrapped everything in try-catch blocks and logged failures:

erp_bot_v3.py
import pyautogui
import time
import pandas as pd
from datetime import datetime
pyautogui.FAILSAFE = True
pyautogui.PAUSE = 0.3
class ERPBot:
def __init__(self):
self.order_count = 0
self.errors = []
def process_order(self, order_data):
"""Process a single order with error handling."""
try:
self.navigate_to_order_entry()
self.fill_customer_info(order_data['customer'])
self.add_order_line(order_data['line'])
self.submit_order()
self.order_count += 1
return True
except Exception as e:
self.errors.append({
'order_id': order_data.get('id', 'unknown'),
'error': str(e),
'timestamp': datetime.now()
})
# Try to recover - go back to main screen
self.recover()
return False
def navigate_to_order_entry(self):
find_and_click('images/orders_menu.png')
find_and_click('images/new_order_button.png')
def fill_customer_info(self, customer):
pyautogui.press('tab')
pyautogui.write(customer['id'])
pyautogui.press('tab')
pyautogui.write(customer['name'])
def add_order_line(self, line):
find_and_click('images/line_item_field.png')
pyautogui.write(line['sku'])
pyautogui.press('tab')
pyautogui.write(str(line['qty']))
def submit_order(self):
find_and_click('images/submit_button.png')
time.sleep(1)
def recover(self):
"""Try to return to a known state."""
pyautogui.press('escape')
pyautogui.press('escape')
time.sleep(0.5)
def generate_report(self):
"""Create daily summary."""
return {
'total_orders': self.order_count,
'errors': len(self.errors),
'error_details': self.errors
}
def find_and_click(image_path, timeout=10):
start = time.time()
while time.time() - start < timeout:
try:
location = pyautogui.locateOnScreen(image_path, confidence=0.9)
if location:
center = pyautogui.center(location)
pyautogui.click(center)
return True
except pyautogui.ImageNotFoundException:
pass
time.sleep(0.5)
raise TimeoutError(f"Could not find {image_path}")

Batch Processing with Progress Tracking

Processing orders one by one is slow. I added batch processing with progress reports:

batch_processor.py
def process_orders_batch(bot, orders, report_interval=100):
"""Process orders with progress reporting."""
results = []
for i, order in enumerate(orders, 1):
success = bot.process_order(order)
results.append({
'order_id': order['id'],
'success': success,
'timestamp': datetime.now()
})
# Progress report every N orders
if i % report_interval == 0:
print(f"Processed {i}/{len(orders)} orders")
# Small delay to avoid overwhelming the ERP
time.sleep(0.5)
return pd.DataFrame(results)
# Usage
bot = ERPBot()
orders = load_orders_from_csv('daily_orders.csv')
results = process_orders_batch(bot, orders)
# Save results
results.to_excel('processing_results.xlsx', index=False)
report = bot.generate_report()
print(f"Total: {report['total_orders']}, Errors: {report['errors']}")

The Results

After implementing this bot (which I named “Artie”), here’s what happened:

MetricBeforeAfter
Time per order22 seconds4.5 seconds
Daily processing time3+ hours45 minutes
Cost$23,000 vendor quoteMy time + $0

The bot handles 16,000+ orders per year. It can process any order type and variable. My team was amazed.

Common Mistakes to Avoid

Mistake 1: Hard-coding screen coordinates

The ERP window moves, and your bot breaks. Use image recognition instead.

Mistake 2: No error handling

When the bot fails, it can corrupt data. Always validate and catch exceptions.

Mistake 3: No recovery mechanism

When something goes wrong, the bot needs to return to a known state. Add escape key presses and screen resets.

Mistake 4: Ignoring speed optimization

A slow bot provides limited value. Tune your wait times and use batch processing.

Mistake 5: Skipping documentation

When you leave, someone else needs to maintain the bot. Document everything.

Trade-offs to Consider

This approach is not perfect:

  • Maintenance: When ERP screens change, you need new screenshots
  • Fragility: Less robust than native API integration
  • Monitoring: Needs human oversight for edge cases
  • Resolution dependency: Works best with consistent screen settings

But for $23,000 less than vendor solutions, these trade-offs were worth it for us.

Summary

You can automate legacy ERP systems with Python using GUI automation. PyAutoGUI simulates mouse clicks and keyboard input. Use image recognition instead of hard-coded coordinates for reliability. Add error handling and recovery mechanisms. The result: dramatic time savings at a fraction of vendor costs.

Start by mapping your manual workflow step by step. Take screenshots of every button. Build incrementally with proper error handling. You will have a working automation bot in weeks, not months.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments