new-site/performancewest_erpnext/performancewest_erpnext/hooks.py
justin 557b45f65d fix(erpnext): self-heal outgoing Email Account password from SMTP_* env
Root cause of recurring 'Password not found for Email Account Performance West
Outgoing': the account was shipped as a fixture with awaiting_password=1 and no
password. Email Account SMTP passwords are encrypted per-site and cannot live in
a fixture, so every `bench migrate` reimported the fixture and re-broke
outgoing mail (login notifications, password resets, welcome emails).

- Remove the Email Account fixture (it cannot carry the encrypted secret).
- Add email_account_sync.sync_outgoing_password: idempotent, exception-safe
  upsert that reconciles the account + password from SMTP_* env and clears
  awaiting_password.
- Wire it to after_migrate (repairs at end of every deploy/migrate, right after
  fixtures import) and the daily scheduler (heals out-of-band restore/restart
  drift).
- Pass SMTP_* into the erpnext + erpnext-scheduler containers so the sync has
  the secret (they previously had no SMTP env).
2026-06-17 09:48:28 -05:00

68 lines
3 KiB
Python

app_name = "performancewest_erpnext"
app_title = "Performance West ERPNext"
app_publisher = "Performance West Inc."
app_description = "Custom payment gateways, surcharge hooks, and identity verification for Performance West"
app_email = "support@performancewest.net"
app_license = "MIT"
# Fixtures to import on bench migrate
fixtures = [
{"dt": "Custom Field", "filters": [["dt", "in", ["Sales Order", "Sales Invoice", "Payment Request"]]]},
{"dt": "Notification", "filters": [["name", "like", "CRTC%"]]},
{"dt": "Notification", "filters": [["name", "like", "Admin %"]]},
# NB: the "Performance West Outgoing" Email Account is intentionally NOT a
# fixture. Its SMTP password is stored encrypted (per-site encryption_key)
# and cannot be carried in a fixture, so reimporting it on `bench migrate`
# would set awaiting_password=1 and an empty password — breaking all
# outgoing mail ("Password not found for Email Account ..."). The account is
# created/repaired idempotently from the SMTP_* env via
# email_account_sync.sync_outgoing_password (after_migrate + scheduler).
# Subscription plans for recurring renewals (RA, annual report, CRTC maintenance,
# formation maintenance bundles). Pricing updated per go-live-todo.md:37, 261.
{"dt": "Subscription Plan"},
# Service Items referenced by renewal_worker._compliance_type_to_item —
# CRTC-MAINT-ANNUAL, MAILBOX-RENEWAL, BC-ANNUAL-REPORT, DOMAIN-RENEWAL-CA,
# and the COMPLIANCE-OTHER catch-all.
{"dt": "Item", "filters": [["item_code", "in", [
"CRTC-MAINT-ANNUAL", "MAILBOX-RENEWAL", "BC-ANNUAL-REPORT",
"DOMAIN-RENEWAL-CA", "COMPLIANCE-OTHER",
]]]},
]
# Portal menu items — adds "My Orders" to the ERPNext portal sidebar
portal_menu_items = [
{"title": "My Orders", "route": "/orders", "reference_doctype": "Sales Order", "role": "Customer"},
]
# Document event hooks
doc_events = {
"Payment Request": {
"before_insert": "performancewest_erpnext.payments.surcharge.inject_surcharge",
},
"Sales Order": {
"before_submit": "performancewest_erpnext.payments.identity_gate.check_identity",
},
}
# pw_stripe_checkout is served from www/pw_stripe_checkout.py automatically
# Exempt Stripe webhook from CSRF — Stripe uses signature verification instead
csrf_ignore_methods = [
"performancewest_erpnext.api.stripe_webhook",
]
# Reconcile the outgoing Email Account password from the SMTP_* environment at
# the end of every migrate (fixtures import just before this, and cannot carry
# the encrypted password). Keeps "Password not found for Email Account ..." from
# recurring after a deploy.
after_migrate = [
"performancewest_erpnext.email_account_sync.sync_outgoing_password",
]
# Daily self-heal: catches drift from out-of-band restarts / DB restores that
# wipe the encrypted password without going through migrate.
scheduler_events = {
"daily": [
"performancewest_erpnext.email_account_sync.sync_outgoing_password",
],
}