A custom ERPNext app for 508.dev that automates referral fee Purchase Invoices.
When a Sales Invoice is submitted, the system automatically creates one Draft Purchase Invoice per referrer defined on the linked Project — no manual calculation or PI creation needed.
Before this app: Referral fees were created manually. Someone had to calculate the amount, find the right supplier, and create a Purchase Invoice by hand every time a Sales Invoice was submitted.
After this app: Set up referrers once on each Project. Every Sales Invoice submitted against that Project automatically generates the correct Draft Purchase Invoices.
Project: Cooper Dark Mater
└── Referrers:
├── Michael Wu — 10%
└── Tony Sun — 5%
Sales Invoice submitted (grand total $1,000)
→ Auto-creates Draft PI for Michael Wu: $100
→ Auto-creates Draft PI for Tony Sun: $50
The Draft PIs can then be reviewed and submitted through the normal Purchase Invoice workflow.
| Rule | Behaviour |
|---|---|
| Amount formula | grand_total × percentage% |
| Item used on PI | Internal Commission |
| PI status on creation | Draft — requires manual review before submitting |
| First-year limit | Not enforced automatically. Admins remove referrers from the Project after year 1 to stop PI creation |
| % total validation | Total referrer % on a Project cannot exceed 100% — blocked on save |
| Duplicate guard | Resubmitting the same Sales Invoice will not create duplicate PIs |
| Cancel behaviour | Cancelling a Sales Invoice auto-deletes its Draft referral PIs. Submitted PIs must be cancelled manually |
bench get-app https://github.com/508-dev/referral_fee
bench --site <sitename> install-app referral_fee
bench --site <sitename> migratemigrate creates the Project Referrer child table and applies custom fields to Project and Purchase Invoice.
The repo is mounted as a volume — no image rebuild needed.
- Place the repo under
app/custom_apps/referral_fee/ - Add
referral_feetoapp/dev-entrypoint.sh(already there if cloning alongsideengineer_onboarding) - Start containers:
cd app
docker compose -f docker-compose.yml -f docker-compose.override.yml up -d- Install into the site (first time only):
# Add to apps.txt so ERPNext recognises the app
docker compose -f docker-compose.yml -f docker-compose.override.yml exec backend \
bash -c "echo 'referral_fee' >> /home/frappe/frappe-bench/sites/apps.txt"
# Install app and apply schema
docker compose -f docker-compose.yml -f docker-compose.override.yml exec backend \
bench --site frontend install-app referral_fee
docker compose -f docker-compose.yml -f docker-compose.override.yml exec backend \
bench --site frontend migrate- After any Python changes, restart backend to reload:
docker compose -f docker-compose.yml -f docker-compose.override.yml restart backendNote:
docker-compose.override.ymlanddev-entrypoint.share Docker-level orchestration files — they are not part of this repo and should not be committed here.
referral_fee/
├── pyproject.toml # package metadata (flit)
├── README.md
└── referral_fee/ # Frappe app package
├── hooks.py # event hooks & fixture config
├── install.py # after_install: creates "Referral Fee" service item
├── modules.txt
├── fixtures/
│ └── custom_field.json # custom fields added to Project & Purchase Invoice
└── referral_fee/ # module
├── referral_utils.py # all business logic (on_submit, on_cancel, validate)
└── doctype/
└── project_referrer/ # child table: Supplier + Percentage per Project
├── project_referrer.json
└── project_referrer.py
| Function | Triggered by | Purpose |
|---|---|---|
on_sales_invoice_submit |
Sales Invoice submit | Creates Draft PIs for each referrer |
on_sales_invoice_cancel |
Sales Invoice cancel | Deletes Draft PIs linked to that SI |
validate_project_referrers |
Project save | Blocks save if total % > 100 |
_make_purchase_invoice |
internal | Builds and inserts one Purchase Invoice |
_get_expense_account |
internal | Finds expense account (tries "Referral Fee Expense", falls back to company default) |
_is_within_first_year |
unused (commented out) | Year-1 enforcement logic — ready to enable |
On Project:
referrers— Table (child table:Project Referrer) — the list of referrers and their %
On Purchase Invoice:
referral_source_si— Link to Sales Invoice — which SI triggered this PIis_referral_fee— Check — marks auto-generated PIs (used by cancel logic to avoid touching manual PIs)
- Open the Project
- Click the Referrers tab
- Add one row per referrer:
- Supplier — must already exist as a Supplier in ERPNext
- Percentage (%) — e.g.
10for 10%
- Save
Total percentage across all referrers cannot exceed 100%.
- Create a Sales Invoice as usual
- Make sure the Project field is set to the project with referrers
- Submit
The system will immediately create one Draft Purchase Invoice per referrer. A green notification with links to the new PIs will appear.
Go to Accounting > Purchase Invoice and find the new Draft PIs. Review amounts, then submit them through the normal workflow.
Run through these after any install or code change:
| Test | Steps | Expected result |
|---|---|---|
| Normal flow | Set 10% referrer on Project → Submit $1,000 SI | Draft PI created for $100 |
| Multiple referrers | Set 5% + 10% → Submit $1,000 SI | Two Draft PIs: $50 and $100 |
| % validation | Set referrers totalling > 100% → Save Project | Error: cannot exceed 100% |
| Duplicate guard | Submit same SI twice (or cancel + resubmit without amending) | Only one PI per referrer |
| Cancel cleanup | Submit SI → Cancel SI | Draft PI deleted automatically |
| Amend flow | Submit → Cancel → Amend → Resubmit | New PI created, no duplicates |
| No project | Submit SI with no Project set | No PI created, no error |
| No referrers | Submit SI against Project with empty Referrers tab | No PI created, no error |
The logic is written but commented out in referral_utils.py inside on_sales_invoice_submit. To enable automatic enforcement:
- Uncomment the
if not _is_within_first_year(...)block - Confirm that each Project has
Expected Start Dateset (the function uses this as the 1-year start point; falls back to the Project's creation date if not set) - Restart the backend
| Question | Decision | Confirmed by |
|---|---|---|
| Item on Purchase Invoice | Internal Commission |
Caleb, 2026-05-04 |
| Rounding rule | Round to nearest penny using Python's standard round(x, 2) |
Caleb, 2026-05-04 |
| First-year automatic enforcement | Deferred — handle manually for now. Code is written but commented out, ready to enable | Caleb, 2026-05-04 |