The first NetSuite application took two months. The second took fifteen days. Same architecture, same quality standards, same comprehensive testing. This is the story of how domain knowledge compounds, and why the hardest part of building enterprise software isn't the code.
In November 2025, we started building a Time Entry application for NetSuite. Employees needed to log time from Outlook and their phones instead of navigating NetSuite's complex interface. Two months later, we shipped 77,000 lines of production code, 2,912 automated tests, and a dual-platform application running as both an Office Add-in and a Progressive Web App.
Then in January 2026, we started the Expense Report application. Same problem domain: NetSuite data, OAuth authentication, cascading dropdowns, budget tracking, mobile-first design. But this time, something was different. The patterns were familiar. The architecture was proven. The NetSuite quirks were documented.
Fifteen days later, we shipped 42,000 lines of code and 1,985 automated tests. A complete, production-ready expense management system with receipt uploads, image compression, billable/non-billable workflows, and real-time budget tracking.
The second application was built 4x faster than the first, not because we cut corners, but because we'd already paid the learning tax. Domain knowledge compounds. Architecture decisions compound. Test patterns compound. The hard problems were solved once and reused many times.
The Numbers Tell the Story
| Metric | Time Entry | Expense Report |
|---|---|---|
| Development Time | ~60 days | 15 days |
| Commits | 343 | 97 |
| Lines of Code | 77,000 | 42,000 |
| Automated Tests | 2,912 | 1,985 |
| TypeScript Files | 135 | 89 |
| Architecture Layers | 13 | 15 |
| Test Coverage | 53% | 51% |
Look at those numbers carefully. The Expense Report application isn't a stripped-down version. It has more architecture layers, comparable test coverage, and domain-specific features like receipt upload with image compression, HEIC detection, and dual budget tracking for Engineering Expenses and Billable Consumables.
What We Learned Building Time Entry
The Time Entry application was our education in NetSuite development. Every lesson was hard-won:
NetSuite API Quirks
- Field name inconsistency: Some records use
id, others useinternalid. Some use camelCase, others use lowercase. We built field resolution utilities that handle both. - Pagination patterns: REST API pagination isn't consistent across endpoints. We built generic paginated fetch utilities.
- SuiteQL vs REST: Some data is only accessible via SuiteQL queries. Others require REST calls. We learned when to use which.
- Token refresh timing: OAuth tokens expire, but NetSuite's error messages aren't always clear. We built proactive token refresh.
OAuth 2.0 Complexity
NetSuite's OAuth implementation has specific requirements around redirect URIs, CORS handling, and the authorization code flow. We built a proxy server architecture that handles all of this, keeping client secrets server-side and managing the token exchange securely.
Cascading Dropdown Performance
Loading customers, then projects, then tasks, with each dependent on the previous selection, creates complex state management. We built caching layers, debounced fetches, and optimistic UI patterns that make the experience feel instant.
The Architecture That Emerged
After two months of iteration, we arrived at a Clean Architecture with clear layer separation:
- Domain: Pure business logic with no I/O dependencies
- Gateway: All NetSuite API communication isolated here
- Controllers: Orchestration layer connecting views to services
- Views: DOM manipulation only, no business logic
- State: Observable state container with indexed lookups
- Persistence: AES-GCM encrypted token storage
- Infrastructure: Shared utilities, error handling, telemetry
This architecture wasn't designed upfront. It evolved through solving real problems, then was codified for reuse.
Building Expense Report: The Compound Effect
When we started the Expense Report application, we didn't start from scratch. We started from understanding.
Day 1-2: Foundation Sprint
The entire project scaffolding, build configuration, testing setup, and authentication flow was copied and adapted. Not blindly copied, but intelligently reused. The OAuth proxy server needed zero changes. The encryption utilities worked identically. The error handling patterns transferred directly.
Day 3-5: Core API Integration
NetSuite's Expense Report API has different endpoints than Time Entry, but the patterns are identical. Pagination works the same way. Field resolution uses the same utilities. The gateway layer needed new functions, but the architecture was proven.
Day 6-10: Domain-Specific Features
This is where Expense Report diverged: receipt uploads with image compression, billable vs non-billable workflows, Engineering Expenses budget tracking. These were genuinely new features, but they plugged into the existing architecture cleanly.
Day 11-15: Polish and Testing
The testing patterns from Time Entry transferred directly. We knew what to test, how to mock NetSuite responses, and how to structure test files for maintainability. Writing 1,985 tests in five days is only possible when you know exactly what patterns work.
What Transferred Directly
- Authentication system: OAuth flow, token storage, refresh logic
- Encryption utilities: AES-GCM with PBKDF2 key derivation
- Cascading dropdown architecture: Customer → Project → Task selection
- Budget tracking patterns: Real-time remaining budget display
- Build configuration: Webpack for dual-platform (Add-in + PWA)
- Testing infrastructure: Jest setup, mock factories, test utilities
- CSS architecture: Design tokens, component styles, themes
The Expense Report Feature Set
Despite the compressed timeline, the Expense Report application is feature-complete for production use:
Receipt Upload with Intelligence
The receipt upload system demonstrates domain knowledge in action:
- Unified two-button UI: Camera and Gallery buttons work consistently across desktop and mobile
- Image compression: Canvas-based JPEG compression with EXIF orientation correction
- Compression feedback: Shows size reduction percentage after optimization
- HEIC detection: User-friendly error for Apple's unsupported format
- Platform detection: Camera button disabled on desktop browsers where it doesn't make sense
Dual Budget Tracking
For billable expenses, the system tracks two separate budgets from Sales Orders:
- Engineering Expenses: Primary budget for billable project work
- Billable Consumables: Secondary budget for materials and supplies
- Real-time remaining: SuiteQL queries calculate unbilled amounts instantly
- PM notifications: Email alerts when budgets are exceeded
The Timeline Comparison
Why Domain Knowledge Matters More Than Code
The real value created during the Time Entry project wasn't the 77,000 lines of code. It was the understanding:
- Understanding NetSuite's data model: How customers relate to projects, how projects relate to tasks, how budgets are tracked across Sales Orders
- Understanding OAuth complexity: The specific flow required for NetSuite, the proxy architecture that keeps secrets safe
- Understanding mobile constraints: Touch targets, camera APIs, offline considerations, PWA requirements
- Understanding testing patterns: What to mock, how to structure tests, what coverage actually matters
When we talk about "reusable code," we're usually thinking too small. The reusable asset isn't the functions or classes. It's the mental model of the domain, the architectural decisions that survived real-world testing, and the patterns that proved they work under pressure.
Combined Platform Statistics
Together, the two applications represent a significant NetSuite integration platform:
What This Means for Future Development
The pattern is now clear. Each new NetSuite application we build will benefit from everything we've learned:
- Authentication: The OAuth proxy server handles any NetSuite integration. Zero additional work.
- Architecture: The Clean Architecture layers are proven. Copy, adapt, extend.
- Testing: Mock factories, test utilities, and patterns all transfer directly.
- UI Components: Design tokens, component styles, and interaction patterns are established.
- Domain Knowledge: Every NetSuite quirk is documented. Every API pattern is understood.
The third application won't take fifteen days. It might take ten. The fourth might take a week. This is what compounding looks like in software development.
The two months spent on Time Entry wasn't just building one application. It was building the foundation for a family of NetSuite integrations. The expense application proved that investment pays dividends. Future applications will prove it again.
Lessons for Leaders
If you're planning enterprise software development, consider:
- The first project is an investment: It will take longer than you expect because you're learning the domain, not just writing code.
- Architecture matters for reuse: Quick hacks don't compound. Clean architecture does.
- Testing enables speed: The 2,912 tests on Time Entry gave us confidence to move fast on Expense Report.
- Document the quirks: Every API inconsistency, every workaround, every hard-won insight should be captured.
- Plan for the family: If you're building one NetSuite integration, you'll probably build more. Design accordingly.
Building on What You've Learned
The hardest part of enterprise software isn't writing code. It's understanding the domain deeply enough that code becomes obvious. That understanding compounds. Each project makes the next one faster, better, and more reliable.