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).
The custom_incorporation_province field had default='BC', which stamped 'BC'
on EVERY Sales Order (US trucking, formation, compliance) — not just Canadian
CRTC orders. This leaked a meaningless 'BC' onto e.g. an SC scrap-metal carrier's
order. Removed the default and added a blank option so it's empty unless it's an
actual Canadian incorporation. Existing non-canada_crtc orders cleared in prod
via db_set (13 fixed; the 2 real canada_crtc orders keep BC).
Root cause of the 'Link invalid' onboarding link: Frappe's TemplatePage
resolves a www page's Python controller by converting hyphens to underscores
(see frappe/website/page_renderers/template_page.py set_pymodule: it looks for
'set_password.py' next to 'set-password.html'). Our controller was named
'set-password.py' (hyphen), so os.path.exists() missed it, pymodule_name stayed
None, get_context never ran over HTTP, and the template rendered with no
context -> raw {{ email }}, title 'Link invalid', token never verified. (It
worked under bench/in-process only because we called get_context directly.)
Fix: rename www/set-password.py -> www/set_password.py (route stays
/set-password, driven by the .html filename) and update the whitelisted submit
endpoint path in set-password.html to ...www.set_password.submit.
NOTE: the sibling legacy CRTC/CDR admin pages (admin-filings.py,
admin-resellers.py, cdr-*.py) have the same latent hyphen bug; left as-is since
they're outside the compliance portal, but they are silently controller-less.
- Added COMPLIANCE_PIPELINE with 7 stages (Received → Filing → Complete)
- Show service items as type label for compliance orders
- Fixed is_cancelled: only "Cancelled" counts, not "Closed" (ERPNext
auto-sets Closed when fully billed, which is wrong for active orders)
- Changed delivered badge text from "binder delivered" to "documents delivered"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
/Sales Invoice/NAME goes to desk (permission error for portal users).
Changed to PDF download API which respects portal user permissions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The custom_contact_email field doesn't exist on Sales Order DocType,
causing the email-based fallback query to crash. Simplified to use
Customer record lookup only (email_id match works).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Disable sidebar (was auto-linking order names as broken relative URLs)
- Remove broken portal_user_name lookup (field doesn't exist)
- Match orders by Customer record OR by custom_contact_email on Sales Order
- Merges both result sets without duplicates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Compliance Deadline: created by job_server after filing completion,
tracks annual filing deadlines (RMD, CPNI, 499-A, BDC).
Compliance Calendar: used by renewal_worker for billing cycle,
tracks due dates, invoices, and recurring renewal entries.
Both were referenced in code but never created — caused 417 errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>