Includes: API (Express/TypeScript), Astro site, Python workers, document generators, FCC compliance tools, Canada CRTC formation, Ansible infrastructure, and deployment scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
14 KiB
Performance West — ERPNext CRM Integration
Last updated: 2026-04-06
Architecture Overview
Browser Astro Site Express API ERPNext
| | | |
+- Place order --------->+- POST /api/orders ----->+- Create DocType ----->|
| | | |
| | | ERPNext Webhook |
| | | | |
| | | +----+----+ |
| | | | Webhook |------->|
| | | | triggers| |
| | | | workers | |
| | | +----+----+ |
| | | | |
| | | +----+----+ |
| | | | Generate | |
| | | | Docs | |
| | | +----+----+ |
| | | | |
| | | +----+----+ |
| | | | Upload | |
| | | | to MinIO| |
| | | +----+----+ |
| | | | |
| | | Update status ---->|
| | | |
<-- email with docs -----| | |
Data flow:
- Customer submits order on Astro site
- Express API creates ERPNext Sales Invoice + Payment Request
- Customer pays via Adyen or SHKeeper (payment routed through ERPNext gateways)
- ERPNext marks invoice as Paid, webhook fires to Express API
- Express API dispatches job to Workers HTTP server (:8090)
- Workers generate documents (DOCX templates or LLM-written), convert to PDF
- Documents are uploaded to MinIO
- Worker updates ERPNext order status and attaches the MinIO URL
- Admin reviews in ERPNext; approves or requests revision
- Delivery worker emails documents to customer
Key principle: ERPNext is the single source of truth for all customer data, orders, and tickets. The Express API and Python workers read/write to ERPNext via its REST API. The API's own PostgreSQL database stores only state filing fees, API keys, session data, discount codes, payment surcharges, and commission ledger backup.
ERPNext Setup
- URL: https://crm.performancewest.net
- Admin:
Administrator(password in Ansible vault) - API keys: Set in Express API
.envasERPNEXT_API_KEY/ERPNEXT_API_SECRET - Image:
performancewest-erpnext:latest(custom, extendsfrappe/erpnext:version-15) - 6 apps installed: frappe, erpnext, payments, frappe_crypto, frappe_adyen, performancewest_erpnext
- Timezone: America/Chicago
Custom DocTypes
Formation Order
Tracks LLC/Corp formation orders through the pipeline.
| Field | Type | Description |
|---|---|---|
| customer | Link (Customer) | ERPNext Customer record |
| entity_name | Data | Desired company name |
| entity_type | Select | LLC, Corporation, Nonprofit |
| state | Data | Two-letter state code |
| members | Table (Formation Member) | Member names, ownership %, capital |
| management_type | Select | Member-managed, Manager-managed |
| registered_agent | Data | RA provider name |
| add_ons | Table (Formation Add-On) | Operating agreement, EIN, RA service |
| status | Select | See Order Flow below |
| minio_path | Data | Path to generated documents in MinIO |
| filing_number | Data | State filing confirmation number |
| order_total | Currency | Total amount charged |
Compliance Calendar
Tracks recurring compliance deadlines per entity.
| Field | Type | Description |
|---|---|---|
| customer | Link (Customer) | ERPNext Customer |
| entity_name | Data | Company name |
| state | Data | State code |
| event_type | Select | Annual Report, Franchise Tax, RA Renewal, etc. |
| due_date | Date | Next due date |
| recurrence | Select | Annual, Quarterly, Monthly |
| status | Select | Upcoming, Due Soon, Overdue, Completed |
| reminder_sent | Check | Whether reminder email was sent |
Sensitive ID
Stores SSN/EIN with encryption. Uses ERPNext's Password field type, which encrypts values at rest.
| Field | Type | Description |
|---|---|---|
| customer | Link (Customer) | ERPNext Customer |
| id_type | Select | SSN, EIN, ITIN |
| id_value | Password | Encrypted value (never exposed in API responses) |
| entity_name | Data | Associated entity (for EIN) |
Referral Partner
Tracks affiliate/referral partners and their commissions.
| Field | Type | Description |
|---|---|---|
| partner_name | Data | Partner name |
| partner_email | Data | |
| referral_code | Data | Unique referral code |
| commission_rate | Percent | Commission percentage |
| total_referrals | Int | Count of referred customers |
| total_paid | Currency | Total commissions paid |
Compliance Service
Tracks compliance consulting service orders (FLSA audit, CCPA audit, etc.).
| Field | Type | Description |
|---|---|---|
| customer | Link (Customer) | ERPNext Customer |
| service_type | Select | FLSA Audit, CCPA Audit, TCPA Audit, Contractor Assessment, Handbook Review, Breach Response Plan, Privacy Policy |
| status | Select | See Order Flow below |
| intake_data | JSON | Service-specific intake form data |
| minio_path | Data | Path to generated documents in MinIO |
| order_total | Currency | Total amount charged |
Sales Agent
Tracks sales agents and their referral codes.
| Field | Type | Description |
|---|---|---|
| agent_name | Data | Agent full name |
| agent_email | Data | |
| referral_code | Data | REF-XXXXX format code |
| commission_tier | Select | Standard, Premium |
| payout_method | Select | Relay ACH |
Commission Ledger
Per-order commission records.
| Field | Type | Description |
|---|---|---|
| agent | Link (Sales Agent) | Referring agent |
| order | Link (Sales Order) | The order |
| service_type | Select | Formation, CRTC, Bundle |
| commission_amount | Currency | Flat commission ($50/$100/$300) |
| status | Select | Pending, Eligible, Paid |
| paid_date | Date | When commission was paid |
Custom Fields on Standard DocTypes
| DocType | Field | Type | Purpose |
|---|---|---|---|
| Sales Order | custom_identity_status | Select | Identity verification status for CRTC orders |
| Sales Order | custom_order_type | Select | formation, crtc, bundle, compliance |
| Sales Order | custom_external_order_id | Data | PostgreSQL order ID |
| Sales Invoice | custom_surcharge_pct | Percent | Payment method surcharge applied |
| Sales Invoice | custom_payment_gateway | Data | Which gateway processed payment |
| Payment Request | custom_adyen_session_id | Data | Adyen session reference |
Order Flow
All orders (Formation Order and Compliance Service) follow this status pipeline:
Ordered --> Queued --> Processing --> Review --> Approved --> Ready --> Delivered
| Status | Description |
|---|---|
| Ordered | Customer has paid; order created in ERPNext |
| Queued | Payment confirmed; order queued for processing |
| Processing | Worker is actively generating documents or filing |
| Review | Documents generated; awaiting admin review |
| Approved | Admin approved; documents ready for delivery |
| Ready | All reviews passed; documents ready for delivery |
| Delivered | Documents emailed to customer; order complete |
Error states:
| Status | Description |
|---|---|
| Failed | Worker encountered an error; needs manual intervention |
| Revision | Reviewer requested changes; returns to Processing |
| Refunded | Order cancelled and refunded |
Document Generation Pipeline
Two paths depending on the service:
Template-Based (Formation Orders)
Used for: Operating agreements, invoices, privacy policies.
- Worker fetches the DOCX template from MinIO (
templates/operating-agreement.docx) DocxBuilderfills Jinja2 placeholders with order data from ERPNext- DocServer converts DOCX to PDF (or LibreOffice headless as fallback)
- Both files uploaded to MinIO at
orders/{order_id}/ - ERPNext order updated with
minio_path
LLM-Based (Compliance Services)
Used for: FLSA audits, CCPA audits, TCPA audits, contractor assessments, handbook reviews, breach response plans.
- Worker fetches the DOCX template from MinIO (provides structure/formatting)
- Worker calls Ollama (qwen2.5:7b) with a service-specific prompt + intake data
- LLM generates analysis text for each section
DocxBuilderfills template placeholders and inserts LLM-generated sections- DocServer converts to PDF (LibreOffice fallback)
- Both files uploaded to MinIO
- Order status set to Review (always requires human review for LLM output)
Hybrid
Some documents use both approaches: template for structure/boilerplate, LLM for analysis sections. Example: FLSA audit uses a template for the report structure, table formatting, and disclaimer, but LLM writes the executive summary, classification analysis, and remediation plan.
MinIO Storage Structure
performancewest/ # Bucket
+-- templates/ # DOCX templates (9 uploaded)
| +-- operating-agreement.docx
| +-- privacy-policy.docx
| +-- breach-response-plan.docx
| +-- flsa-audit-report.docx
| +-- contractor-assessment.docx
| +-- handbook-review.docx
| +-- ccpa-audit-report.docx
| +-- tcpa-audit-report.docx
| +-- invoice.docx
+-- orders/ # Generated documents per order
| +-- FO-2026-0001/ # Formation Order
| | +-- operating-agreement.docx
| | +-- operating-agreement.pdf
| | +-- articles-of-organization.pdf
| | +-- ein-confirmation.pdf
| +-- CS-2026-0042/ # Compliance Service
| | +-- flsa-audit-report.docx
| | +-- flsa-audit-report.pdf
| | +-- remediation-checklist.pdf
| +-- ...
+-- invoices/ # Generated invoices
| +-- INV-2026-0001.pdf
| +-- ...
+-- backups/ # Encrypted database backups
+-- erpnext-2026-03-19.sql.gpg
+-- ...
Access control: MinIO is internal only (not exposed to the internet). Documents are served to customers via pre-signed URLs generated by the Express API, valid for 1 hour.
Listmonk Integration
Listmonk (lists.performancewest.net) handles email marketing campaigns. It does not
store customer orders or tickets — ERPNext is the CRM source of truth.
Admin: admin / F6IHwDFeMjaDGDPR1OQcKtUA86BGfs2
Sync flow:
- When a new Customer is created in ERPNext, the Express API pushes the subscriber to Listmonk via its REST API
- Listmonk manages email campaigns, drip sequences, and newsletter sends
- Listmonk tracks email opens/clicks and manages subscriber lists
- Bounce processing via POP3 from Carbonio (
co.carrierone.com)
Mass email: SMTP2GO is used for Listmonk campaign sends (not Carbonio — Carbonio is for transactional email only).
Campaigns:
| Campaign | Trigger | Content |
|---|---|---|
| Welcome drip | New customer created | 3-email series: welcome, services overview, compliance tips |
| Formation follow-up | Formation delivered | Annual report reminder, RA renewal, compliance calendar |
| CRTC follow-up | CRTC carrier delivered | Banking setup, BITS filing, ATS survey prep |
| Compliance upsell | 30 days post-formation | FLSA audit, handbook review, privacy policy offers |
| Renewal reminder | 30 days before RA renewal | Registered agent renewal notice |
Important: Listmonk subscribers are synced one-way from ERPNext. ERPNext remains the source of truth. If a subscriber unsubscribes in Listmonk, the email_opted_out flag is synced back to ERPNext via webhook.
ERPNext Helpdesk
ERPNext's built-in Helpdesk module handles all customer support:
| Feature | ERPNext Helpdesk |
|---|---|
| Ticket creation | Via API (contact form), email, or ERPNext UI |
| Assignment | Auto-assign rules based on ticket type |
| SLA tracking | Built-in SLA configuration per priority |
| Knowledge base | ERPNext Knowledge Base module |
| Customer portal | ERPNext portal (customers can view their tickets) |
| Email integration | ERPNext Email Account (incoming/outgoing) |
This eliminates the need for a separate ticketing system. All customer data stays in one place with native linking between tickets and orders.
Security
Encrypted Backups
- ERPNext database is backed up daily via
bench backup - Backups are GPG-encrypted before upload to MinIO
backups/prefix - Retention: 30 daily, 12 monthly, indefinite yearly
- Restore tested monthly
RBAC (Role-Based Access Control)
ERPNext provides granular role permissions:
| Role | Access |
|---|---|
| Admin | Full access to all DocTypes |
| Operations | Formation Orders, Compliance Services (read/write), Customers (read) |
| Support | Helpdesk tickets (read/write), Customers (read) |
| API User | REST API access with specific DocType permissions |
| Sales Agent | Own referrals and commission ledger (read) |
Audit Logging
- ERPNext Version (audit log) tracks all changes to all DocTypes
- Every create/update/delete is logged with user, timestamp, and field-level diff
- Audit logs are included in encrypted backups
- Access logs retained for 1 year
Sensitive Data (SSN/EIN)
- Stored in ERPNext Password fields (AES-256 encrypted at rest)
- Never returned in API list responses; only accessible via explicit single-record GET with appropriate role
- Masked in the ERPNext UI (shown as
***-**-1234) - Workers decrypt only when needed for IRS EIN application, then discard from memory