# 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:**

1. Customer submits order on Astro site
2. Express API creates ERPNext Sales Invoice + Payment Request
3. Customer pays via Adyen or SHKeeper (payment routed through ERPNext gateways)
4. ERPNext marks invoice as Paid, webhook fires to Express API
5. Express API dispatches job to Workers HTTP server (:8090)
6. Workers generate documents (DOCX templates or LLM-written), convert to PDF
7. Documents are uploaded to MinIO
8. Worker updates ERPNext order status and attaches the MinIO URL
9. Admin reviews in ERPNext; approves or requests revision
10. 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 `.env` as `ERPNEXT_API_KEY` / `ERPNEXT_API_SECRET`
- **Image:** `performancewest-erpnext:latest` (custom, extends `frappe/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 | Email |
| 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 | Email |
| 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.

1. Worker fetches the DOCX template from MinIO (`templates/operating-agreement.docx`)
2. `DocxBuilder` fills Jinja2 placeholders with order data from ERPNext
3. DocServer converts DOCX to PDF (or LibreOffice headless as fallback)
4. Both files uploaded to MinIO at `orders/{order_id}/`
5. 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.

1. Worker fetches the DOCX template from MinIO (provides structure/formatting)
2. Worker calls Ollama (qwen2.5:7b) with a service-specific prompt + intake data
3. LLM generates analysis text for each section
4. `DocxBuilder` fills template placeholders and inserts LLM-generated sections
5. DocServer converts to PDF (LibreOffice fallback)
6. Both files uploaded to MinIO
7. 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:**

1. When a new Customer is created in ERPNext, the Express API pushes the subscriber to Listmonk via its REST API
2. Listmonk manages email campaigns, drip sequences, and newsletter sends
3. Listmonk tracks email opens/clicks and manages subscriber lists
4. Bounce processing via POP3 from Carbonio (`co.carrierone.com`)

**Mass email:** Listmonk campaign sends relay through the local Postfix MTA (172.18.0.1:25 from the Docker network), which DKIM-signs and delivers direct-to-recipient-MX. Carbonio (co.carrierone.com) is for transactional email only. (SMTP2GO was decommissioned — no external relay is used.)

**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
