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:
import pyautoguiimport time
# Safety feature: move mouse to corner to abortpyautogui.FAILSAFE = True
# Add small pause between actionspyautogui.PAUSE = 0.5The 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:
import pyautoguiimport 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 orderprocess_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:
import pyautoguiimport timefrom PIL import Image
pyautogui.FAILSAFE = Truepyautogui.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 TrueThis 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:
import pyautoguiimport timeimport pandas as pdfrom datetime import datetime
pyautogui.FAILSAFE = Truepyautogui.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:
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)
# Usagebot = ERPBot()orders = load_orders_from_csv('daily_orders.csv')results = process_orders_batch(bot, orders)
# Save resultsresults.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:
| Metric | Before | After |
|---|---|---|
| Time per order | 22 seconds | 4.5 seconds |
| Daily processing time | 3+ hours | 45 minutes |
| Cost | $23,000 vendor quote | My 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