Fix flagged items: CRTC email submission, BITS todo, selector docs, stale plans
- CRTC letter now auto-emailed to secretary.general@crtc.gc.ca after eSign - BITS admin todo updated to reference electronic + physical submission - COLIN selectors.py: documented verification status per step - BC config: added CRTC Secretary General email address - plan.md: marked completed items (eSign, portal auth, CRTC email) - go-live-todo.md: marked Compliance Calendar DocType as imported Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
05eec47528
commit
97dd08c821
8 changed files with 413 additions and 13 deletions
|
|
@ -231,7 +231,7 @@
|
||||||
- On detecting a new quarterly factor, emails all FCC-carrier customers (bcc justin@) with the new % and the delta vs. prior quarter so they can update their USF surcharges before the quarter starts
|
- On detecting a new quarterly factor, emails all FCC-carrier customers (bcc justin@) with the new % and the delta vs. prior quarter so they can update their USF surcharges before the quarter starts
|
||||||
- Requires migration 049 (`usf_contribution_factors` table) to be applied
|
- Requires migration 049 (`usf_contribution_factors` table) to be applied
|
||||||
- [x] Create ERPNext Items for renewal invoicing: CRTC-MAINT-ANNUAL, MAILBOX-RENEWAL, BC-ANNUAL-REPORT, DOMAIN-RENEWAL-CA, COMPLIANCE-OTHER (fixture: `performancewest_erpnext/performancewest_erpnext/fixtures/item.json`; imports on `bench migrate`)
|
- [x] Create ERPNext Items for renewal invoicing: CRTC-MAINT-ANNUAL, MAILBOX-RENEWAL, BC-ANNUAL-REPORT, DOMAIN-RENEWAL-CA, COMPLIANCE-OTHER (fixture: `performancewest_erpnext/performancewest_erpnext/fixtures/item.json`; imports on `bench migrate`)
|
||||||
- [ ] Import updated Compliance Calendar DocType to production ERPNext
|
- [x] Import updated Compliance Calendar + Compliance Deadline DocTypes to ERPNext (2026-05-04)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
17
docs/plan.md
17
docs/plan.md
|
|
@ -48,20 +48,17 @@ The BC incorporation adapter (`frappe_ca_registry/provinces/bc/adapter.py`) has
|
||||||
### 1.3 CRTC Pipeline Remaining Stubs `[CRTC]`
|
### 1.3 CRTC Pipeline Remaining Stubs `[CRTC]`
|
||||||
|
|
||||||
- [ ] **Anytime Mailbox automation hardening** — provider has no API, but Playwright flow now exists. Validate selectors against live UI and stabilize OTP retrieval via Carbonio IMAP, then keep admin handoff as fallback.
|
- [ ] **Anytime Mailbox automation hardening** — provider has no API, but Playwright flow now exists. Validate selectors against live UI and stabilize OTP retrieval via Carbonio IMAP, then keep admin handoff as fallback.
|
||||||
- [ ] **CCTS registration** — Step 11 is a stub. Research CCTS online registration form, implement Playwright or keep as admin ToDo with instructions.
|
- [ ] **CCTS registration** — Step 12 is admin ToDo with detailed instructions and CCTS membership URL. Playwright automation planned for future release.
|
||||||
- [ ] **eSign workflow for CRTC letter** — Step 6 generates the DOCX letter but customer signature is not collected. Use ERPNext built-in eSign (drawing pad). Wire: generate letter → send for eSign → on signed → continue pipeline.
|
- [x] **eSign workflow for CRTC letter** — DONE. CRTC-specific eSign at `/portal/sign` (portal-esign.ts). Generic eSign at `/portal/esign/` (portal-esign-generic.ts) works for all doc types.
|
||||||
- [ ] **CRTC letter email submission** — After eSign, email the signed letter to CRTC from the customer's provisioned `.ca` address (`regulatory@{domain}.ca`). Requires IMAP send via HestiaCP provisioned mailbox.
|
- [x] **CRTC letter email submission** — DONE (2026-05-04). After client eSign, letter auto-emailed to secretary.general@crtc.gc.ca from regulatory@{domain}.ca. Also included in physical binder.
|
||||||
- [ ] **BITS affidavit** — BITS requires a notarized affidavit confirming the company is a US carrier (or Canadian equivalent). Provider: NotaryLive ($59/mo platform + $23/session). Implement: generate affidavit DOCX → send NotaryLive session invite → on completion → attach to binder.
|
- [ ] **BITS affidavit** — BITS requires a notarized affidavit confirming the company is a US carrier (or Canadian equivalent). Provider: NotaryLive ($59/mo platform + $23/session). Implement: generate affidavit DOCX → send NotaryLive session invite → on completion → attach to binder.
|
||||||
- [ ] **Order confirmation email** — After payment, send customer a confirmation email with order summary, expected timeline, and next steps checklist. Currently nothing is sent at payment time.
|
- [x] **Order confirmation email** — DONE. Telegram notification + email on payment via checkout.ts handlePaymentComplete().
|
||||||
- [ ] **Branded HTML email templates** — 15 ERPNext Email Notifications are plain text. Design and import HTML templates (header logo, PW brand colors, footer with unsubscribe).
|
- [ ] **Branded HTML email templates** — 15 ERPNext Email Notifications are plain text. Design and import HTML templates (header logo, PW brand colors, footer with unsubscribe).
|
||||||
|
|
||||||
### 1.4 Customer Portal Auth `[CRTC]`
|
### 1.4 Customer Portal Auth `[CRTC]`
|
||||||
Portal pages (`/portal/domain-search`, `/portal/manage-services`) exist but have no authentication. Any URL visitor can access any order.
|
- [x] **JWT portal authentication** — DONE. All portal pages use signed JWT tokens (72h expiry) passed via email links. Middleware at portalAuth.ts validates token → scopes to customer's order.
|
||||||
|
- [x] Auth middleware on all `/portal/*` API routes — requirePortalAuth middleware validates JWT, scopes by order_id + email.
|
||||||
- [ ] Implement portal authentication via ERPNext portal login (ERPNext has a built-in portal user system)
|
- [x] Session via query param (email link), Bearer header (XHR), or cookie (pw_portal_token).
|
||||||
- [ ] Generate a signed JWT or ERPNext portal token and embed in the email links sent to customers
|
|
||||||
- [ ] Add auth middleware to all `/portal/*` API routes — validate token, scope to customer's own orders only
|
|
||||||
- [ ] Add session expiry (24h) and re-send link flow
|
|
||||||
|
|
||||||
### 1.5 End-to-End CRTC Test `[CRTC]`
|
### 1.5 End-to-End CRTC Test `[CRTC]`
|
||||||
- [ ] Place a real CRTC order (numbered company, test customer)
|
- [ ] Place a real CRTC order (numbered company, test customer)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,16 @@ COLIN uses old-school HTML forms with image buttons (type="image")
|
||||||
and standard form field names. No JavaScript frameworks, no iframes,
|
and standard form field names. No JavaScript frameworks, no iframes,
|
||||||
no CAPTCHAs. Authentication is not required for one-time incorporation
|
no CAPTCHAs. Authentication is not required for one-time incorporation
|
||||||
filings — the form is publicly accessible.
|
filings — the form is publicly accessible.
|
||||||
|
|
||||||
|
Steps 1-4 and 8-9: CONFIRMED from live HTML inspection.
|
||||||
|
Steps 5 (translated name): skipped — no fields needed.
|
||||||
|
Steps 6-7 (director, offices): CONFIRMED from Struts DTO naming.
|
||||||
|
Steps 10-11 (summary, confirm): read-only + checkbox.
|
||||||
|
Step 12 (payment): uses standard Moneris card fields.
|
||||||
|
Step 13 (receipt): CSS selector for confirmation number — verify on first live run.
|
||||||
|
|
||||||
|
If any selector fails at runtime, the adapter takes a screenshot and the
|
||||||
|
CRTC handler falls back to an admin ToDo with the screenshot attached.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ── Entry point ──────────────────────────────────────────────
|
# ── Entry point ──────────────────────────────────────────────
|
||||||
|
|
|
||||||
39
infra/ansible/roles/backup/defaults/main.yml
Normal file
39
infra/ansible/roles/backup/defaults/main.yml
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
# ── Backup Role Configuration ────────────────────────────────────────────────
|
||||||
|
backup_dir: /opt/backups
|
||||||
|
backup_user: deploy
|
||||||
|
|
||||||
|
# Retention (days)
|
||||||
|
pg_backup_retention: 30
|
||||||
|
mariadb_backup_retention: 30
|
||||||
|
umami_backup_retention: 14
|
||||||
|
minio_backup_retention: 30
|
||||||
|
forgejo_backup_retention: 30
|
||||||
|
worker_backup_retention: 7
|
||||||
|
|
||||||
|
# On-site backup server
|
||||||
|
backup_remote_host: 207.174.124.50
|
||||||
|
backup_remote_port: 22022
|
||||||
|
backup_remote_user: deploy
|
||||||
|
backup_remote_dir: /opt/backups
|
||||||
|
|
||||||
|
# Container names
|
||||||
|
pg_container: performancewest-api-postgres-1
|
||||||
|
umami_pg_container: performancewest-umami-postgres-1
|
||||||
|
mariadb_container: performancewest-erpnext-mariadb-1
|
||||||
|
forgejo_container: performancewest-forgejo
|
||||||
|
minio_container: performancewest-minio-1
|
||||||
|
|
||||||
|
# Database credentials
|
||||||
|
pg_user: pw
|
||||||
|
pg_database: performancewest
|
||||||
|
umami_pg_user: umami
|
||||||
|
umami_pg_database: umami
|
||||||
|
mariadb_root_password: "{{ erpnext_db_password }}"
|
||||||
|
|
||||||
|
# MinIO
|
||||||
|
minio_alias: local
|
||||||
|
minio_endpoint: "http://127.0.0.1:9000"
|
||||||
|
minio_access_key: "{{ vault_minio_access_key }}"
|
||||||
|
minio_secret_key: "{{ vault_minio_secret_key }}"
|
||||||
|
minio_bucket: performancewest
|
||||||
4
infra/ansible/roles/backup/handlers/main.yml
Normal file
4
infra/ansible/roles/backup/handlers/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
- name: Reload systemd
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
daemon_reload: true
|
||||||
|
|
@ -156,6 +156,7 @@ CONFIG = {
|
||||||
"postal_code": "J8X 4B1",
|
"postal_code": "J8X 4B1",
|
||||||
"country": "Canada",
|
"country": "Canada",
|
||||||
"website": "https://crtc.gc.ca",
|
"website": "https://crtc.gc.ca",
|
||||||
|
"email": "secretary.general@crtc.gc.ca",
|
||||||
"notification_required": True,
|
"notification_required": True,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
273
scripts/generate_all_templates.py
Normal file
273
scripts/generate_all_templates.py
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate sample documents from every DOCX template and email them."""
|
||||||
|
import os
|
||||||
|
import smtplib
|
||||||
|
import tempfile
|
||||||
|
from datetime import datetime
|
||||||
|
from email import encoders
|
||||||
|
from email.mime.base import MIMEBase
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
|
||||||
|
WORK = tempfile.mkdtemp(prefix="pw_template_test_")
|
||||||
|
DATE_STR = datetime.now().strftime("%Y%m%d")
|
||||||
|
TODAY = datetime.now().strftime("%B %d, %Y")
|
||||||
|
generated = []
|
||||||
|
|
||||||
|
|
||||||
|
def try_gen(num, name, func):
|
||||||
|
try:
|
||||||
|
result = func()
|
||||||
|
if result:
|
||||||
|
generated.append((name, result))
|
||||||
|
print(f"{num}. {name}: {'OK' if result else 'FAIL'}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{num}. {name}: ERROR - {str(e)[:150]}")
|
||||||
|
|
||||||
|
|
||||||
|
# ── Common kwargs for CPNI variants ────────────────────────────────
|
||||||
|
CPNI_COMMON = dict(
|
||||||
|
entity_name="Acme Telecom LLC", frn="0015341902",
|
||||||
|
filer_id_499="829999",
|
||||||
|
address_street="1712 Pioneer Ave, Suite 200",
|
||||||
|
address_city="Cheyenne", address_state="WY", address_zip="82001",
|
||||||
|
officer_name="John Smith", officer_title="CEO",
|
||||||
|
contact_email="john@acmetelecom.example",
|
||||||
|
contact_phone="(307) 555-0142",
|
||||||
|
reporting_year=2025, complaints_count=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Common kwargs for CALEA variants ───────────────────────────────
|
||||||
|
CALEA_COMMON = dict(
|
||||||
|
entity_name="Acme Telecom LLC", frn="0015341902",
|
||||||
|
law_enforcement_contact={
|
||||||
|
"name": "Jane Doe",
|
||||||
|
"phone": "(307) 555-0199",
|
||||||
|
"email_24h": "compliance@acmetelecom.example",
|
||||||
|
},
|
||||||
|
signatory_name="John Smith", signatory_title="CEO",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 1. RMD Letter
|
||||||
|
def gen1():
|
||||||
|
from scripts.document_gen.templates.rmd_letter_generator import generate_rmd_letter
|
||||||
|
return generate_rmd_letter(
|
||||||
|
entity_name="Acme Telecom LLC", frn="0015341902",
|
||||||
|
provider_classification="voice_service_provider",
|
||||||
|
carrier_category="interconnected_voip",
|
||||||
|
stir_shaken_status="partial_implementation",
|
||||||
|
upstream_provider_name="Bandwidth.com",
|
||||||
|
address_street="1712 Pioneer Ave, Suite 200",
|
||||||
|
address_city="Cheyenne", address_state="WY", address_zip="82001",
|
||||||
|
contact_name="John Smith", contact_title="CEO",
|
||||||
|
contact_email="john@acmetelecom.example", contact_phone="(307) 555-0142",
|
||||||
|
ceo_name="John Smith", ceo_title="CEO",
|
||||||
|
output_path=os.path.join(WORK, f"01_rmd_letter.docx"),
|
||||||
|
)
|
||||||
|
try_gen(1, "RMD Certification Letter", gen1)
|
||||||
|
|
||||||
|
|
||||||
|
# 2. RMD Exhibit A
|
||||||
|
def gen2():
|
||||||
|
from scripts.document_gen.templates.rmd_exhibit_a_generator import generate_exhibit_a
|
||||||
|
return generate_exhibit_a(
|
||||||
|
entity_name="Acme Telecom LLC", frn="0015341902",
|
||||||
|
carrier_role="ucaas", rmd_option="option2",
|
||||||
|
upstream_provider_name="Bandwidth.com",
|
||||||
|
contact_name="John Smith", contact_title="CEO",
|
||||||
|
contact_email="john@acmetelecom.example", contact_phone="(307) 555-0142",
|
||||||
|
address="1712 Pioneer Ave, Suite 200, Cheyenne, WY 82001",
|
||||||
|
principals=["John Smith (CEO)"],
|
||||||
|
output_path=os.path.join(WORK, f"02_rmd_exhibit_a.docx"),
|
||||||
|
)
|
||||||
|
try_gen(2, "RMD Exhibit A (Robocall Mitigation Plan)", gen2)
|
||||||
|
|
||||||
|
|
||||||
|
# 3. CPNI Certification Letter (generic)
|
||||||
|
def gen3():
|
||||||
|
from scripts.document_gen.templates.cpni_cert_letter_generator import generate_cpni_cert_letter
|
||||||
|
return generate_cpni_cert_letter(
|
||||||
|
entity_name="Acme Telecom LLC", frn="0015341902",
|
||||||
|
filer_id_499="829999",
|
||||||
|
address_street="1712 Pioneer Ave, Suite 200",
|
||||||
|
address_city="Cheyenne", address_state="WY", address_zip="82001",
|
||||||
|
officer_name="John Smith", officer_title="CEO",
|
||||||
|
contact_email="john@acmetelecom.example", contact_phone="(307) 555-0142",
|
||||||
|
reporting_year=2025, complaints_count=0,
|
||||||
|
output_path=os.path.join(WORK, f"03_cpni_cert_generic.docx"),
|
||||||
|
)
|
||||||
|
try_gen(3, "CPNI Certification Letter (Generic)", gen3)
|
||||||
|
|
||||||
|
|
||||||
|
# 4. CPNI Procedure Statement
|
||||||
|
def gen4():
|
||||||
|
from scripts.document_gen.templates.cpni_procedure_statement_generator import generate_cpni_procedure_statement
|
||||||
|
return generate_cpni_procedure_statement(
|
||||||
|
entity_name="Acme Telecom LLC",
|
||||||
|
signatory_name="John Smith", signatory_title="CEO",
|
||||||
|
support_email="support@acmetelecom.example",
|
||||||
|
output_path=os.path.join(WORK, f"04_cpni_procedures.docx"),
|
||||||
|
)
|
||||||
|
try_gen(4, "CPNI Procedure Statement", gen4)
|
||||||
|
|
||||||
|
|
||||||
|
# 5. CALEA SSI Plan (generic VoIP)
|
||||||
|
def gen5():
|
||||||
|
from scripts.document_gen.templates.calea_ssi_generator import generate_calea_ssi_plan
|
||||||
|
return generate_calea_ssi_plan(
|
||||||
|
**CALEA_COMMON,
|
||||||
|
output_path=os.path.join(WORK, f"05_calea_ssi_voip.docx"),
|
||||||
|
)
|
||||||
|
try_gen(5, "CALEA SSI Plan (VoIP)", gen5)
|
||||||
|
|
||||||
|
|
||||||
|
# 6. Discontinuance Letter
|
||||||
|
def gen6():
|
||||||
|
from scripts.document_gen.templates.form_499a_discontinuance_letter_generator import generate_discontinuance_letter
|
||||||
|
return generate_discontinuance_letter(
|
||||||
|
entity_name="Acme Telecom LLC", filer_id="829999", frn="0015341902",
|
||||||
|
ein="87-1234567", address="1712 Pioneer Ave, Suite 200, Cheyenne, WY 82001",
|
||||||
|
officer_name="John Smith", officer_title="CEO",
|
||||||
|
officer_email="john@acmetelecom.example", officer_phone="(307) 555-0142",
|
||||||
|
termination_date="April 30, 2026",
|
||||||
|
discontinuance_reason="Company no longer providing telecommunications services",
|
||||||
|
output_path=os.path.join(WORK, f"06_discontinuance.docx"),
|
||||||
|
)
|
||||||
|
try_gen(6, "USAC Discontinuance Letter", gen6)
|
||||||
|
|
||||||
|
|
||||||
|
# 7. OCN Request Form
|
||||||
|
def gen7():
|
||||||
|
from scripts.document_gen.templates.ocn_request_form_generator import generate_ocn_request_packet
|
||||||
|
return generate_ocn_request_packet(
|
||||||
|
entity_name="Acme Telecom LLC",
|
||||||
|
company_contact_name="John Smith",
|
||||||
|
company_contact_voice="(307) 555-0142",
|
||||||
|
company_contact_email="john@acmetelecom.example",
|
||||||
|
company_contact_address="1712 Pioneer Ave, Suite 200, Cheyenne, WY 82001",
|
||||||
|
service_category="IPES",
|
||||||
|
operating_states=["WY", "CO", "MT"],
|
||||||
|
output_path=os.path.join(WORK, f"07_ocn_request.docx"),
|
||||||
|
)
|
||||||
|
try_gen(7, "NECA OCN Request Form", gen7)
|
||||||
|
|
||||||
|
|
||||||
|
# 8. Reseller Certification
|
||||||
|
def gen8():
|
||||||
|
from scripts.document_gen.templates.reseller_cert_attestation_generator import generate_reseller_cert_attestation
|
||||||
|
return generate_reseller_cert_attestation(
|
||||||
|
output_path=os.path.join(WORK, f"08_reseller_cert.docx"),
|
||||||
|
filer_legal_name="Acme Telecom LLC",
|
||||||
|
filer_filer_id_499="829999",
|
||||||
|
reseller_legal_name="Upstream Carrier Inc",
|
||||||
|
reseller_filer_id_499="123456",
|
||||||
|
reporting_year=2025,
|
||||||
|
filer_contact_name="John Smith",
|
||||||
|
filer_contact_email="john@acmetelecom.example",
|
||||||
|
)
|
||||||
|
try_gen(8, "Reseller Certification Attestation", gen8)
|
||||||
|
|
||||||
|
|
||||||
|
# 9. CRTC Letter
|
||||||
|
def gen9():
|
||||||
|
from scripts.document_gen.templates.crtc_letter_generator import generate_crtc_letter
|
||||||
|
return generate_crtc_letter(
|
||||||
|
entity_name="1234567 B.C. Ltd.",
|
||||||
|
incorporation_number="BC1234567",
|
||||||
|
registered_office="123 W Georgia St, Vancouver, BC V6B 1J5",
|
||||||
|
services_description="Interconnected VoIP and UCaaS services",
|
||||||
|
director_name="John Smith",
|
||||||
|
ca_domain="acmetelecom.ca",
|
||||||
|
output_path=os.path.join(WORK, f"09_crtc_letter.docx"),
|
||||||
|
)
|
||||||
|
try_gen(9, "CRTC Registration Letter", gen9)
|
||||||
|
|
||||||
|
|
||||||
|
# 10-18. CPNI variants
|
||||||
|
CPNI_VARIANTS = [
|
||||||
|
("cpni_clec_generator", "generate_cpni_clec", "CPNI CLEC (Facilities)"),
|
||||||
|
("cpni_ixc_generator", "generate_cpni_ixc", "CPNI IXC (Facilities)"),
|
||||||
|
("cpni_wireless_generator", "generate_cpni_wireless", "CPNI Wireless (CMRS)"),
|
||||||
|
("cpni_clec_reseller_generator", "generate_cpni_clec_reseller", "CPNI CLEC Reseller"),
|
||||||
|
("cpni_ixc_reseller_generator", "generate_cpni_ixc_reseller", "CPNI IXC Reseller"),
|
||||||
|
("cpni_wireless_mvno_generator", "generate_cpni_wireless_mvno", "CPNI Wireless MVNO"),
|
||||||
|
("cpni_private_line_generator", "generate_cpni_private_line", "CPNI Private Line"),
|
||||||
|
("cpni_satellite_generator", "generate_cpni_satellite", "CPNI Satellite"),
|
||||||
|
("cpni_audio_bridge_generator", "generate_cpni_audio_bridge", "CPNI Audio Bridge"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, (module, func_name, label) in enumerate(CPNI_VARIANTS, 10):
|
||||||
|
def make_gen(m=module, fn=func_name, n=i):
|
||||||
|
def gen():
|
||||||
|
mod = __import__(f"scripts.document_gen.templates.{m}", fromlist=[fn])
|
||||||
|
func = getattr(mod, fn)
|
||||||
|
return func(**CPNI_COMMON, output_path=os.path.join(WORK, f"{n:02d}_{m}.docx"))
|
||||||
|
return gen
|
||||||
|
try_gen(i, label, make_gen())
|
||||||
|
|
||||||
|
|
||||||
|
# 19-24. CALEA variants
|
||||||
|
CALEA_VARIANTS = [
|
||||||
|
("calea_wireless_generator", "generate_calea_wireless", "CALEA Wireless (CMRS)"),
|
||||||
|
("calea_ixc_ss7_generator", "generate_calea_ixc_ss7", "CALEA IXC SS7"),
|
||||||
|
("calea_clec_ss7_generator", "generate_calea_clec_ss7", "CALEA CLEC SS7"),
|
||||||
|
("calea_satellite_generator", "generate_calea_satellite", "CALEA Satellite"),
|
||||||
|
("calea_wireless_mvno_generator", "generate_calea_wireless_mvno", "CALEA Wireless MVNO"),
|
||||||
|
("calea_audio_bridge_generator", "generate_calea_audio_bridge", "CALEA Audio Bridge"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, (module, func_name, label) in enumerate(CALEA_VARIANTS, 19):
|
||||||
|
def make_gen(m=module, fn=func_name, n=i):
|
||||||
|
def gen():
|
||||||
|
mod = __import__(f"scripts.document_gen.templates.{m}", fromlist=[fn])
|
||||||
|
func = getattr(mod, fn)
|
||||||
|
return func(**CALEA_COMMON, output_path=os.path.join(WORK, f"{n:02d}_{m}.docx"))
|
||||||
|
return gen
|
||||||
|
try_gen(i, label, make_gen())
|
||||||
|
|
||||||
|
|
||||||
|
# 25. Engagement Letter (499-A)
|
||||||
|
def gen25():
|
||||||
|
from scripts.document_gen.templates.engagement_letter_499a import generate_engagement_letter
|
||||||
|
return generate_engagement_letter(
|
||||||
|
entity_name="Acme Telecom LLC",
|
||||||
|
contact_name="John Smith",
|
||||||
|
contact_email="john@acmetelecom.example",
|
||||||
|
order_number="CO-TEST-ENG",
|
||||||
|
filing_years=[2023, 2024, 2025],
|
||||||
|
output_path=os.path.join(WORK, f"25_engagement_letter.docx"),
|
||||||
|
)
|
||||||
|
try_gen(25, "Engagement Letter (499-A Past-Due)", gen25)
|
||||||
|
|
||||||
|
|
||||||
|
print(f"\n=== Generated {len(generated)} documents ===")
|
||||||
|
|
||||||
|
# Email all
|
||||||
|
if generated:
|
||||||
|
msg = MIMEMultipart()
|
||||||
|
msg["From"] = "Performance West <noreply@performancewest.net>"
|
||||||
|
msg["To"] = "justin@performancewest.net"
|
||||||
|
msg["Subject"] = f"All Templates: {len(generated)} documents ({TODAY})"
|
||||||
|
msg["Reply-To"] = "info@performancewest.net"
|
||||||
|
|
||||||
|
body = f"{len(generated)} document templates generated with sample data.\n\n"
|
||||||
|
body += "Entity: Acme Telecom LLC (FRN: 0015341902)\n\n"
|
||||||
|
for name, path in generated:
|
||||||
|
size = os.path.getsize(path)
|
||||||
|
body += f" {name}: {os.path.basename(path)} ({size:,} bytes)\n"
|
||||||
|
msg.attach(MIMEText(body, "plain"))
|
||||||
|
|
||||||
|
for name, path in generated:
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
part = MIMEBase("application", "octet-stream")
|
||||||
|
part.set_payload(f.read())
|
||||||
|
encoders.encode_base64(part)
|
||||||
|
part.add_header("Content-Disposition", f'attachment; filename="{os.path.basename(path)}"')
|
||||||
|
msg.attach(part)
|
||||||
|
|
||||||
|
with smtplib.SMTP("email-smtp.us-east-2.amazonaws.com", 587, timeout=30) as s:
|
||||||
|
s.starttls()
|
||||||
|
s.login("AKIAYEWLMNWPHSHQWCRD", "BKrUBud+KjyaRA1RiA26FFu1R+hqR4cpFShwbZf7RUzG")
|
||||||
|
s.send_message(msg)
|
||||||
|
print(f"\nEmailed {len(generated)} documents to justin@performancewest.net")
|
||||||
|
|
@ -1164,7 +1164,30 @@ class CanadaCRTCHandler(BaseServiceHandler):
|
||||||
from_password=regulatory_pw,
|
from_password=regulatory_pw,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 9b: Email print/ship instructions to admin
|
# 9b: Email CRTC notification letter to Secretary General
|
||||||
|
# Per CRTC Telecom Information Bulletin 2015-134, new entrants
|
||||||
|
# must notify the CRTC. Electronic submission is accepted.
|
||||||
|
crtc_letter_path = None
|
||||||
|
for fpath in generated_files:
|
||||||
|
if "crtc" in Path(fpath).name.lower() and fpath.endswith(".pdf"):
|
||||||
|
crtc_letter_path = fpath
|
||||||
|
break
|
||||||
|
if crtc_letter_path and Path(crtc_letter_path).exists():
|
||||||
|
try:
|
||||||
|
self._send_crtc_notification_email(
|
||||||
|
entity_name=formation_order.entity_name,
|
||||||
|
order_number=order_number,
|
||||||
|
letter_path=crtc_letter_path,
|
||||||
|
from_addr=regulatory_from,
|
||||||
|
from_password=regulatory_pw,
|
||||||
|
)
|
||||||
|
LOG.info("[Step 9b] CRTC notification letter emailed to Secretary General")
|
||||||
|
except Exception as crtc_err:
|
||||||
|
LOG.warning("[Step 9b] Could not email CRTC notification: %s — admin will mail physically", crtc_err)
|
||||||
|
else:
|
||||||
|
LOG.warning("[Step 9b] No CRTC letter PDF found in generated files — admin will mail physically")
|
||||||
|
|
||||||
|
# 9c: Email print/ship instructions to admin
|
||||||
binder_company = order_data.get("custom_own_ca_company") or ""
|
binder_company = order_data.get("custom_own_ca_company") or ""
|
||||||
binder_attn = order_data.get("custom_own_ca_attn") or ""
|
binder_attn = order_data.get("custom_own_ca_attn") or ""
|
||||||
if binder_path:
|
if binder_path:
|
||||||
|
|
@ -1556,6 +1579,9 @@ class CanadaCRTCHandler(BaseServiceHandler):
|
||||||
f"**BITS Registration — {entity_name}** (Order: {order_number})\n\n"
|
f"**BITS Registration — {entity_name}** (Order: {order_number})\n\n"
|
||||||
f"The CRTC notification letter has been sent to the Secretary General "
|
f"The CRTC notification letter has been sent to the Secretary General "
|
||||||
f"via the signed letter included in the corporate binder.\n\n"
|
f"via the signed letter included in the corporate binder.\n\n"
|
||||||
|
f"**CRTC notification submitted:**\n"
|
||||||
|
f"- Electronic: notification letter emailed to secretary.general@crtc.gc.ca\n"
|
||||||
|
f"- Physical: included in corporate binder (ship to address below)\n\n"
|
||||||
f"**Action required:**\n"
|
f"**Action required:**\n"
|
||||||
f"1. Confirm the binder was shipped to the CRTC address:\n"
|
f"1. Confirm the binder was shipped to the CRTC address:\n"
|
||||||
f" {crtc_config.get('secretary_general', 'Secretary General, CRTC')}\n"
|
f" {crtc_config.get('secretary_general', 'Secretary General, CRTC')}\n"
|
||||||
|
|
@ -1563,7 +1589,8 @@ class CanadaCRTCHandler(BaseServiceHandler):
|
||||||
f"{crtc_config.get('city', '')}, {crtc_config.get('province', '')} "
|
f"{crtc_config.get('city', '')}, {crtc_config.get('province', '')} "
|
||||||
f"{crtc_config.get('postal_code', '')}\n"
|
f"{crtc_config.get('postal_code', '')}\n"
|
||||||
f"2. Monitor for CRTC acknowledgement letter (30-60 days)\n"
|
f"2. Monitor for CRTC acknowledgement letter (30-60 days)\n"
|
||||||
f"3. File acknowledgement in ERPNext Sensitive ID when received"
|
f"3. File acknowledgement in ERPNext Sensitive ID when received\n"
|
||||||
|
f"4. After acknowledgement: request ATS activation code ({ats_config.get('activation_code_phone', '1-877-249-2782')})"
|
||||||
f"{reg_line}"
|
f"{reg_line}"
|
||||||
f"{gckey_status}"
|
f"{gckey_status}"
|
||||||
)
|
)
|
||||||
|
|
@ -2292,6 +2319,55 @@ class CanadaCRTCHandler(BaseServiceHandler):
|
||||||
|
|
||||||
self._send_email(to_email=to_email, subject=subject, body=body)
|
self._send_email(to_email=to_email, subject=subject, body=body)
|
||||||
|
|
||||||
|
def _send_crtc_notification_email(
|
||||||
|
self,
|
||||||
|
entity_name: str,
|
||||||
|
order_number: str,
|
||||||
|
letter_path: str,
|
||||||
|
from_addr: str | None = None,
|
||||||
|
from_password: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Email the signed CRTC notification letter to the Secretary General.
|
||||||
|
|
||||||
|
Sends FROM the customer's regulatory@domain.ca if available, otherwise
|
||||||
|
from Performance West. The CRTC accepts electronic notifications.
|
||||||
|
On dev, redirects to admin email.
|
||||||
|
"""
|
||||||
|
crtc_email = "secretary.general@crtc.gc.ca"
|
||||||
|
admin_email = os.environ.get("ADMIN_EMAIL", "ops@performancewest.net")
|
||||||
|
|
||||||
|
# Dev mode: redirect to admin
|
||||||
|
if os.environ.get("NODE_ENV") == "development":
|
||||||
|
crtc_email = admin_email
|
||||||
|
LOG.info("Dev mode: redirecting CRTC notification to %s", crtc_email)
|
||||||
|
|
||||||
|
subject = f"Notification of New Telecommunications Service Provider — {entity_name}"
|
||||||
|
body = (
|
||||||
|
f"Dear Secretary General,\n\n"
|
||||||
|
f"Please find attached the formal notification letter for {entity_name}, "
|
||||||
|
f"a new telecommunications service provider, pursuant to the Telecommunications Act "
|
||||||
|
f"and CRTC Telecom Information Bulletin 2015-134.\n\n"
|
||||||
|
f"This notification is being filed on behalf of {entity_name} by "
|
||||||
|
f"Performance West Inc., their authorized compliance representative.\n\n"
|
||||||
|
f"Please do not hesitate to contact us if any additional information is required.\n\n"
|
||||||
|
f"Respectfully,\n"
|
||||||
|
f"Performance West Inc.\n"
|
||||||
|
f"525 Randall Ave Ste 100-1195\n"
|
||||||
|
f"Cheyenne, WY 82001\n"
|
||||||
|
f"(888) 411-0383\n"
|
||||||
|
f"info@performancewest.net\n\n"
|
||||||
|
f"Order reference: {order_number}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._send_email(
|
||||||
|
to_email=crtc_email,
|
||||||
|
subject=subject,
|
||||||
|
body=body,
|
||||||
|
attachment_path=letter_path,
|
||||||
|
from_addr=from_addr,
|
||||||
|
from_password=from_password,
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _send_email(
|
def _send_email(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue