Skip to main content

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​

  1. Suitelet (JS): Fetches data via SuiteQL, passes JSON to HTML
  2. HTML Template: Contains Pyodide Python code for Excel generation
  3. 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​

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​

PhaseToolPurpose
Data ExtractionNetSuite Debug FilesGet real data structure
DevelopmentJupyter NotebookInteractive debugging
TestingLocal PythonVerify Excel output
IntegrationBrowser ConsoleDebug Pyodide issues
ProductionNetSuite SuiteletFinal deployment

Key Principle: Always develop and debug Python code locally in Jupyter first, then port to Pyodide once working. This saves significant debugging time.