NetSuite Python Report Development Guide
Overview
This guide covers how to develop, debug, and test NetSuite Suitelet reports that use Pyodide (Python in browser) for generating Excel files.
Architecture Understanding
Your reports follow this data flow:
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
│ NetSuite SQL │ ──► │ JavaScript │ ──► │ Pyodide/Python │ ──► │ Excel File │
│ (SuiteQL) │ │ (JSON data) │ │ (pandas) │ │ (download) │
└─────────────────┘ └──────────────┘ └─────────────────┘ └──────────────┘
Key Components
- Suitelet (JS): Fetches data via SuiteQL, passes JSON to HTML
- HTML Template: Contains Pyodide Python code for Excel generation
- Python (Pyodide): Processes data with pandas, generates Excel with xlsxwriter
Development Workflow
Step 1: Extract and Create Dummy Data
Why? You can't easily debug Pyodide in browser. Extract real data once, then develop locally.
Method A: Save Query Results to File (Already in Your Code!)
In your Suitelet, uncomment the file-saving debug code:
// Already exists in your code - just uncomment!
var fileContents = JSON.stringify(mainResult, null, 2);
var fileObj2 = file.create({
name: 'mainResult.txt',
fileType: file.Type.PLAINTEXT,
contents: fileContents,
description: 'query_debug',
encoding: file.Encoding.UTF8,
folder: 4742, // Your debug folder ID
isOnline: false
});
fileObj2.save();
Method B: Browser Console Export
Add this to your HTML after data loads:
// Add to HTML template for data extraction
console.log(JSON.stringify(agingData, null, 2));
// Or download directly:
function downloadJSON(data, filename) {
const blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
}
downloadJSON(agingData, 'aging_data_sample.json');
Step 2: Set Up Local Development Environment
Option A: Jupyter Notebook (Recommended for Development)
Advantages:
- Cell-by-cell execution for debugging
- Immediate visual feedback
- Easy data inspection
- Same pandas/xlsxwriter libraries
Setup:
# Create virtual environment
python -m venv netsuite_reports
source netsuite_reports/bin/activate # Windows: netsuite_reports\Scripts\activate
# Install dependencies
pip install jupyter pandas xlsxwriter numpy openpyxl
# Start Jupyter
jupyter notebook
Sample Jupyter Notebook Structure:
# Cell 1: Load dummy data
import json
import pandas as pd
with open('aging_data_sample.json', 'r') as f:
aging_data = json.load(f)
asofdate = '15/12/2024' # Match your test date
# Cell 2: Verify data structure
print(f"Total records: {len(aging_data)}")
print(f"Columns: {aging_data[0].keys() if aging_data else 'Empty'}")
pd.DataFrame(aging_data).head()
# Cell 3: Your processing logic (copy from HTML)
data_df = pd.DataFrame(aging_data)
data_df = data_df.drop(columns=['rownumber']).fillna(0)
# ... rest of your code
# Cell 4: Debug intermediate results
print("AR Data shape:", ar_df.shape)
print("AP Data shape:", ap_df.shape)
ar_df.head()
Option B: Plain Python Script
For simpler debugging:
# test_aging_report.py
import json
import pandas as pd
import io
# Load test data
with open('test_data/aging_data_sample.json', 'r') as f:
aging_data = json.load(f)
asofdate = '15/12/2024'
# Your processing code here...
# (Copy from HTML template)
# Save for inspection
output_path = 'test_output/test_report.xlsx'
# ... save Excel
print(f"Report saved to {output_path}")
Step 3: Create Test Data Files
Create a test_data/ folder with sample JSON:
aging_data_sample.json
[
{
"rownumber": 1,
"accttype": "AcctRec",
"companyname": "Test Customer A",
"currency": "IDR",
"foreignamount_current": 1000000,
"amount_current": 1000000,
"foreignamount_h30": 500000,
"amount_h30": 500000,
"foreignamount_h60": 250000,
"amount_h60": 250000,
"foreignamount_h60_above": 100000,
"amount_h60_above": 100000
},
{
"rownumber": 2,
"accttype": "AcctRec",
"companyname": "Test Customer B",
"currency": "USD",
"foreignamount_current": 5000,
"amount_current": 77500000,
"foreignamount_h30": 2000,
"amount_h30": 31000000,
"foreignamount_h60": 0,
"amount_h60": 0,
"foreignamount_h60_above": 1000,
"amount_h60_above": 15500000
},
{
"rownumber": 3,
"accttype": "AcctPay",
"companyname": "Test Vendor A",
"currency": "IDR",
"foreignamount_current": 2000000,
"amount_current": 2000000,
"foreignamount_h30": 0,
"amount_h30": 0,
"foreignamount_h60": 750000,
"amount_h60": 750000,
"foreignamount_h60_above": 0,
"amount_h60_above": 0
}
]
Step 4: Debugging Strategies
A. Python/Pandas Debugging (Local)
# Add debug prints at key points
print("="*50)
print("DEBUG: After initial processing")
print(f"DataFrame shape: {data_df.shape}")
print(f"Columns: {list(data_df.columns)}")
print(f"Sample data:\n{data_df.head()}")
print("="*50)
# Check for nulls/issues
print("Null counts:", data_df.isnull().sum())
# Verify calculations
print("Calculated Overdue:", data_df['Rp Overdue'].head())
B. Pyodide Browser Debugging
Add console outputs to your Python code in HTML:
# Inside your Pyodide code block
from js import console
console.log("Starting Python processing...")
console.log(f"Data length: {len(aging_data)}")
# After key operations
console.log(f"AR records: {len(ar_df)}")
console.log(f"AP records: {len(ap_df)}")
C. Excel Output Verification
# Add verification before saving
print("Final AR DataFrame:")
print(final_ar_df.to_string())
print(f"\nAR row range: {ar_row_start} to {ar_row_end}")
print(f"AP row range: {ap_row_start} to {ap_row_end}")
Step 5: Development Cycle
┌─────────────────────────────────────────────────────────────────┐
│ DEVELOPMENT CYCLE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. EXTRACT DATA │
│ └── Run Suitelet once → Save JSON to file │
│ │
│ 2. DEVELOP LOCALLY (Jupyter) │
│ └── Load JSON → Write/Test pandas code → Debug │
│ │
│ 3. TEST EXCEL OUTPUT │
│ └── Generate local Excel → Verify formatting │
│ │
│ 4. PORT TO PYODIDE │
│ └── Copy working code to HTML template │
│ └── Add JS↔Python variable bridges │
│ │
│ 5. TEST IN BROWSER │
│ └── Test with real NetSuite data │
│ └── Fix any Pyodide-specific issues │
│ │
│ 6. DEPLOY │
│ └── Upload to NetSuite File Cabinet │
│ │
└─────────────────────────────────────────────────────────────────┘
Common Issues & Solutions
Issue 1: Pyodide Package Loading
# Required loading sequence
await pyodide.loadPackage("micropip")
await pyodide.loadPackage("requests")
await pyodide.loadPackage('tzdata')
micropip = pyodide.pyimport("micropip")
await micropip.install('pandas')
await micropip.install('xlsxwriter')
Issue 2: Date Formatting
# Local Python
from datetime import datetime
asof_date = datetime.strptime(asofdate, '%d/%m/%Y').strftime('%d %B %Y')
# If timezone needed
from zoneinfo import ZoneInfo
# Pyodide requires tzdata package
Issue 3: Large Data Performance
# For large datasets, consider:
# 1. Chunked processing
# 2. Reducing data before sending to Pyodide
# 3. Using more efficient data types
data_df = data_df.astype({
'amount_current': 'float32',
'amount_h30': 'float32',
# ... etc
})
Issue 4: Memory in Browser
// Add cleanup after download
pyodide.runPython(`
import gc
del data_df, ar_df, ap_df, final_ar_df, final_ap_df
gc.collect()
`)
Quick Reference: Debug Checklist
- Export real data to JSON file
- Test pandas code in Jupyter first
- Verify Excel output locally before Pyodide
- Check console for Pyodide errors
- Verify JS→Python variable passing
- Test with edge cases (empty data, nulls, special characters)
- Check Excel cell ranges match data length
- Verify formatting applies correctly
Summary
| Phase | Tool | Purpose |
|---|---|---|
| Data Extraction | NetSuite Debug Files | Get real data structure |
| Development | Jupyter Notebook | Interactive debugging |
| Testing | Local Python | Verify Excel output |
| Integration | Browser Console | Debug Pyodide issues |
| Production | NetSuite Suitelet | Final deployment |
Key Principle: Always develop and debug Python code locally in Jupyter first, then port to Pyodide once working. This saves significant debugging time.