Pennylane
Pennylane integration for Frappe ecosystem.
- Author: future-onprint
- Repository: https://github.com/future-onprint/pennylane
- GitHub stars: 0
- Forks: 0
- License: AGPL-3.0
- Category: Integrations
- Maintenance: Actively Maintained
- Frappe versions: develop
Install Pennylane
bench get-app https://github.com/future-onprint/pennylane
Tags
- frappe
- pennylane
Add the Frappe Gems badge to your README
Maintain Pennylane? Paste this into your README:
[](https://frappegems.com/gems/apps/future-onprint/pennylane)
About Pennylane
# 🪙 Pennylane
**Pennylane integration for the Frappe ecosystem.**
[](https://python.org)
[](https://frappeframework.com)
[](license.txt)
*A project by [Underscore Blank OÜ](mailto:contact@underscore-blank.io)*
---
## About
**Pennylane** is a standalone Frappe app that bridges [Pennylane](https://www.pennylane.com) — a modern French accounting platform — with the Frappe ecosystem. It has **no ERPNext dependency** and works with any Frappe v16 installation.
It provides native Frappe DocTypes for all Pennylane resources, bidirectional sync via the Pennylane API v2, automatic webhook registration, and built-in observability tools.
---
## Features
### Resources
| DocType | Push (Frappe → Pennylane) | Pull (Pennylane → Frappe) |
|---|---|---|
| **Pennylane Customer** | ✓ on save | ✓ hourly changelog |
| **Pennylane Product** | ✓ on save | ✓ hourly changelog |
| **Pennylane Customer Invoice** | ✓ on save / submit | ✓ hourly changelog + webhook |
| **Pennylane Customer Quote** | ✓ on save | ✓ hourly changelog + webhook |
| **Pennylane Customer Contact** | — (API read-only) | ✓ on customer pull |
### Sync mechanics
- **Incremental sync** — changelog-based cursor per resource type; only changed records are fetched each hour.
- **Independent pull jobs** — each resource type is enqueued as its own background job on the `long` queue with a 1 500 s timeout, so a slow changelog never blocks the others.
- **Force full sync** — Settings UI lets you reset cursors and re-import any resource from scratch; concurrent runs are rejected via a Redis lock.
- **Retry queue** — failed push jobs land in `Pennylane Sync Queue` and are retried up to 5 times with exponential back-off and jitter.
- **Real-time webhooks** — `customer_invoice.created` and `quote.created` events trigger an immediate pull without waiting for the hourly job.
- **Dependency resolution** — pushing a quote or invoice automatically syncs its customer first if not yet known to Pennylane.
### Customer types
Both `company` and `individual` customers are supported. Push and pull are routed to the correct Pennylane endpoint (`/company_customers` or `/individual_customers`) based on the `customer_type` field.
### Invoice lifecycle
Invoices use Frappe's **submittable** DocType pattern:
| Frappe state | Meaning |
|---|---|
| `docstatus = 0` — Draft | Editable; pushed to Pennylane with `draft: true` |
| `docstatus = 1` — Submitted | Read-only; finalized in Pennylane on submit |
| `docstatus = 2` — Cancelled | Cancelled in Frappe; Pennylane action is manual |
### Quote lifecycle
Quotes are **not submittable**. They remain in `docstatus = 0` at all times and are always editable in Frappe — unless Pennylane has locked them. Fields become read-only automatically when the Pennylane status is `accepted`, `denied`, `invoiced`, or `expired`. A quote can originate one or many invoices; each invoice carries a `source_quote` link back.
### PDF attachments
On every pull, if Pennylane returns a `public_file_url`:
- **Invoices** — the PDF is downloaded once and never re-downloaded if already attached (invoices are immutable in Pennylane).
- **Quotes** — the PDF is always replaced because it can be regenerated after each edit.
### Webhook security
- The HMAC-SHA256 signature is verified **before** any application logic executes — an invalid or missing signature returns `401` without leaking whether webhooks are enabled.
- Duplicate events are deduplicated via a Redis key (60 s TTL) to handle Pennylane's at-least-once delivery guarantee.
- When webhooks are disabled, the endpoint returns `200 OK` so Pennylane does not retry.
### Notifications
When a sync failure persists after retries, all active System Manager users receive a Frappe Notification Log entry and a real-time desk notification.
---
## DocTypes
```
Pennylane Settings — API credentials, sync toggles, webhook config, force full sync
Pennylane Customer — Company or individual customer
Pennylane Customer Contact — Read-only contacts child table on Customer
Pennylane Product — Sellable product / service
Pennylane Invoice Line — Shared child table for Invoice and Quote lines
Pennylane Customer Invoice — Customer invoice (submittable)
Pennylane Customer Quote — Customer quote / estimate (non-submittable, conditionally read-only)
Pennylane Sync Queue — Retry queue for failed push jobs
Pennylane Sync Log — Audit log for every sync operation
```
---
## Architecture
```
pennylane/
├── api/
│ └── webhook.py # Guest-whitelisted Pennylane webhook receiver
├── client/
│ ├── base.py # PennylaneClient — HTTP, rate-limiting, pagination
│ ├── customers.py
│ ├── invoices.py
│ ├── products.py
│ ├── quotes.py
│ └── webhooks.py # Webhook subscription management
├── mappers/ # Pure functions: Frappe doc ↔ API payload
│ ├── customer.py
│ ├── invoice.py
│ ├── invoice_line.py
│ ├── product.py
│ └── quote.py
├── sync/ # Orchestration: push, pull, full sync
│ ├── customer.py
│ ├── invoice.py
│ ├── product.py
│ ├── quote.py
│ └── utils.py
├── utils/
│ └── pdf.py # PDF download and attachment helper
├── hooks.py
└── tasks.py # Scheduled jobs (hourly pull, retry queue, daily cleanup)
```
---
## Configuration
### 1. Install the app
```bash
cd $PATH_TO_YOUR_BENCH
bench get-app $URL_OF_THIS_REPO --branch main
bench --site your.site install-app pennylane
bench --site your.site migrate
```
### 2. Enter your API credentials
Open **Pennylane Settings** in the Frappe desk:
| Field | Description |
|---|---|
| **API Token** | Your Pennylane developer token (required) |
| **Enable Pennylane Integration** | Master switch — disables all sync when off |
| **Sync Customers / Products / Invoices / Quotes** | Per-resource sync toggles |
| **Notify on Sync Failure** | Send desk notifications when sync fails persistently |
### 3. Test the connection
Click **Test Connection** in Settings. On success the connection status turns green and the **Company ID** field is populated automatically — this ID is required for the "Open in Pennylane" deep links on each DocType form.
### 4. Enable webhooks (optional but recommended)
Toggle **Enable Webhook Reception** in Settings. The app automatically registers a subscription with Pennylane and stores the signing secret. The site must be reachable via HTTPS from the public internet.
Supported events: `customer_invoice.created`, `quote.created`.
To unregister, disable the toggle — the subscription is removed from Pennylane automatically.
### 5. Start the long worker
Pull jobs and full syncs run on the `long` queue. Make sure the worker is started:
```bash
# Development
bench worker --queue long
# Production (Procfile / Supervisor)
# Ensure the long worker is listed alongside default and short
```
---
## Requirements
- [Frappe Bench](https://github.com/frappe/bench) installed and configured
- Frappe Framework **v16**
- Python **≥ 3.14**
- Redis (required by Frappe for the job queue)
---
## Development
This project uses `pre-commit` to enforce code quality:
```bash
cd apps/pennylane
pre-commit install
```
| Tool | Role |
|---|---|
| `ruff` | Python linting and formatting |
| `eslint` | JavaScript linting |
| `prettier` | JavaScript / CSS formatting |
| `pyupgrade` | Python syntax modernization |
Run the test suite against a local bench site:
```bash
bench --site your.site run-tests --app pennylane
```
---
## CI / CD
| Workflow | Trigger |
|---|---|
| **CI** | Push to `develop` — installs app and runs unit tests |
| **Linters** | Pull request — runs Frappe Semgrep Rules and `pip-audit` |
---
## Changelog
See [CHANGELOG.md](CHANGELOG.md).
---
## License
Distributed under the **AGPL-3.0** License. See [`license.txt`](license.txt) for details.
---
Maintained by Underscore Blank OÜ
Related Integrations apps for Frappe & ERPNext
- Insights — Open Source Business Intelligence Tool
- Raven — Simple, open source team messaging platform
- Frappe Whatsapp — WhatsApp cloud integration for frappe
- Frappe Assistant Core — Infrastructure that connects LLMs to ERPNext. Frappe Assistant Core works with the Model Context Protocol (MCP) to expose ERPNext functionality to any compatible Language Model
- Biometric Attendance Sync Tool — A simple tool for syncing Biometric Attendance data with your ERPNext server
- Frappe React Sdk — React hooks for Frappe
- Frappe Js Sdk — TypeScript/JavaScript library for Frappe REST API
- Mcp — Frappe MCP allows Frappe apps to function as MCP servers