Commit Graph

27 Commits

Author SHA1 Message Date
louispaulb
9b06e2df30 fix(docs/campaigns): support@targointernet.com is the validated sender, not support@targo.ca
Previous commit (380f3bc) incorrectly claimed Mailjet verified targo.ca
at the domain level. It doesn't — Mailjet validates senders ONE BY ONE,
even when SPF/DKIM/DMARC are correctly published at the domain. The
mistake: SMTP returned `250 OK` on a send-test from support@targo.ca,
but the message was silently dropped on Mailjet's side because that
specific mailbox hadn't been approved.

Validated senders on this Mailjet account:
  ✓ noreply@targo.ca           — hub transactional (invoices etc.)
  ✓ support@targointernet.com  — gift campaigns

`support@targo.ca` is NOT validated, despite being on a domain whose
sibling (`noreply@targo.ca`) is.

Updated:
  - README default --from value
  - The "sender" section now explains per-sender validation (not
    domain-level) and the SMTP-250-but-not-delivered gotcha
  - Listed both validated senders explicitly with usage intent

The script itself (send_gift_campaign.js) was already
sender-agnostic — only README guidance changed. New senders are added
in Mailjet console → Sender Domain Verification → Add sender, with
the verification link mailed to the new address.
2026-05-21 16:42:18 -04:00
louispaulb
380f3bc0e7 docs(campaigns): document support@targo.ca as the default gift-campaign sender
Validated live with Mailjet: targo.ca is verified at the DOMAIN level
(SPF + DKIM + DMARC published in Cloudflare), so any *@targo.ca sender
works without per-mailbox approval. Tested 1 send from
support@targo.ca → accepted, delivered.

Why support@ rather than noreply@ for campaigns:
  - Campaigns INVITE a reply (questions about the gift, "I didn't get
    mine", "the link doesn't work", etc.)
  - noreply@ is for transactional system mail where there's nothing
    useful for a human to reply to
  - Different intent → different sender

The hub's transactional emails (invoices, magic links) continue to
use noreply@targo.ca; campaigns specifically use support@targo.ca.
README updated accordingly with the rationale.

Note for future: if we ever want a @gigafibre.ca sender, that's
~30 min of Mailjet setup (add domain, publish SPF/DKIM CNAMEs in
Cloudflare). Not done today because all customer-facing email
flows through targo.ca and support@ is the right mailbox for this
campaign intent.
2026-05-21 16:36:06 -04:00
louispaulb
e1283f30e8 feat(campaigns): add Giftbit API client + validate end-to-end with sandbox
Adds create_giftbit_campaign.js — Node CLI that POSTs to the Giftbit
API (testbed or production), creates a campaign with
delivery_type=SHORTLINK so Giftbit does NOT send their own English
template emails, polls /gifts?campaign_uuid=... until the redemption
shortlinks are generated, then writes a gifts CSV ready to feed into
send_gift_campaign.js.

Two non-obvious things learned while wiring it up:

1. The right endpoint to get the shortlinks is /gifts (not /links).
   /links/{uuid} returned 0 rows on our sandbox account; /gifts has
   a `shortlink` field on each gift once delivery_status transitions
   from QUEUED → LINKCREATED. Polled with 2s interval, up to 20 tries.

2. delivery_type=SHORTLINK is mandatory. Default is GIFTBIT_EMAIL,
   which fires their English template immediately — defeating the
   whole point of bridging through our French Mailjet template.
   Confirmed in the campaign GET response that delivery_type echoes
   back correctly when we send "SHORTLINK".

Validated end-to-end (entirely synthetic data — Alice/Bob/Charlie at
@example.com, no real customer info in the sandbox):
  ✓ Auth probe via /ping returns 200
  ✓ POST /campaign returns campaign UUID
  ✓ After ~12s, /gifts returns 3 gifts each with a working shortlink
  ✓ send_gift_campaign.js consumes the gifts CSV + the contacts CSV
  ✓ FR template renders: "Bonjour Alice", http://gtbt.co/7TKGFDBNVZq
    embedded in the CTA button href, address in the footer line

The --sandbox flag does double duty: routes the API to
api-testbed.giftbit.com AND replaces every recipient email with
louis@targo.ca so we can't accidentally hit real customer inboxes
with the non-redeemable test gifts.

README updated with the two-stage pipeline (create → send), explicit
warnings about the customer-matching gap (only 25% of source rows
resolve via legacy_delivery_id — the rest use a different ID space
from the source Map tool), and the sandbox-quirk where Giftbit
collapses recipient_name when emails are duplicated.

Token NOT committed — pulled from GIFTBIT_TOKEN env var per the
script's contract. In production we'll store it in the hub's
.env alongside SMTP_USER / SMTP_PASS.
2026-05-21 16:20:28 -04:00
louispaulb
37896421c3 feat(campaigns): MVP gift campaign sender (Node CLI + FR email template)
User context: needs to send Giftbit gift cards to 203 customers with a
branded French email instead of Giftbit's English-only default delivery.
Giftbit's own UI/API can issue the gifts but its email is English; this
MVP bridges the gap by taking the gift URLs back from Giftbit, pairing
them with our contact CSV, and sending personalized FR emails through
the Mailjet SMTP that's already wired up for ERPNext invoice mail.

Three files in scripts/campaigns/:

1. send_gift_campaign.js — Node CLI. Two CSV inputs (gifts + contacts),
   matches by row order (default) or email key, renders the HTML
   template with mustache-style {{firstname}} / {{gift_url}} / etc.,
   sends via nodemailer with configurable SMTP + throttle.
   --dry-run writes per-recipient previews to disk for visual review
   before flipping to live mode. Results CSV with per-row status
   (sent / failed / dry-run) + error message + timestamp is written
   next to the script for follow-up on failures.

2. templates/gift-email-fr.html — branded French email. Table-based
   layout (the only thing that renders consistently in Gmail / Outlook /
   iOS Mail / Apple Mail / Bell Sympatico). Indigo gradient header,
   centered CTA button, contextual {{description}} line citing the
   service address, support contact in the footer, no inline images
   (defers to text + colour blocks to dodge image-blocking).

3. contacts_from_legacy.py — replaces the ad-hoc /tmp Python I ran
   earlier with a proper repo'd version. Same multi-email handling
   options (first / split / skip) as I offered the user; defaults to
   "first" = 1 gift per household, which is what they chose. Title-
   cases the address with French article rules (de / du / la / aux
   stay lowercase, 1re / 2e ordinals stay lowercase too).

4. README.md — end-to-end usage with the actual SMTP env vars from
   /opt/targo-hub/.env and the matching strategy decision matrix.

Validated end-to-end with a 5-row dry run: matching works, accents
preserved (Amélie, Geneviève, Marc-André), {{firstname}} interpolates,
gift URLs land in the rendered button href, address shows in the
contextual footer line. Previews written to disk for visual QA.

NOT in this MVP (out of scope, can come next if we end up running
gift campaigns regularly):
  - No persistence to ERPNext doctype (no Gift Campaign / Recipient
    records — pure CLI, results CSV is the audit trail)
  - No click-tracking redirect (the gift_url goes verbatim to the
    recipient; Giftbit's own API/dashboard reports redemption status,
    which is the more relevant signal than "clicked the link")
  - No ops UI page (CLI is fine for one-shot; if this becomes regular
    we wrap it in services/targo-hub/lib/gift-campaign.js + a Vue page)
2026-05-21 15:51:01 -04:00
louispaulb
10afd696ae fix(migration): clean address_line + postal_code + connection_type at import
Three legacy data-quality issues that were leaking into ERPNext on every
import run. Caught while auditing C-LPB4's mis-pinned dispatch job.

1. **Postal code embedded in address_line.** Legacy `gestionclient` had
   rows like `2200-3 chemin de la riviere de la guerre  J0S1B0` with
   the postal code concatenated at the end (and the same code repeated
   in the dedicated zip column). Caused 48-char address_line on what
   should have been a 39-char address. Now stripped at import: a regex
   matches `\\s+<FSA><LDU>\\s*$` (with or without space) and removes
   it; the dedicated postal_code field carries the canonical form.

2. **Abbreviations + Cobol-style capitalization.** Legacy stored
   `2066 Ch De La 1Re-Concession` instead of the canonical
   `2066 Chemin de la 1re-Concession`. ABBREV_MAP expands `Ch` →
   `Chemin`, `Av` → `Avenue`, `Bd`/`Boul` → `Boulevard`, `Rte` →
   `Route`, `St-` → `Saint-`, `Ste-` → `Sainte-`, `Mtl` → `Montréal`.
   Title-casing rule preserves French articles lowercase (`de`, `du`,
   `des`, `la`, `le`, `les`, `au`, `aux`, `à`, `et`, `sur`, `en`)
   and ordinal markers (`1re`, `2e`, `3e`). 96 SLs in production had
   the `1Re-Concession` style; they'll be re-normalized on next
   migration run.

3. **`connection_type` left empty even when ONT/CPE devices existed.**
   Pre-loads device→delivery mapping at import start; if the legacy
   delivery has any device whose category/name/model contains "ont",
   "onu", "cpe", "fibre", "gpon", or "ftth", we set
   connection_type='Fibre FTTH'. Without devices on file, the field
   stays empty (rep fills it later) — we don't guess.

4. **`postal_code` normalized too** — `j0s1b0` → `J0S 1B0` (uppercase
   + canonical space). Was being inserted in lowercase no-space form.

Self-tested on 8 representative cases including the actual broken
records found in production (LOC-15903, LOC-6227, LOC-4 / C-LPB4).

These changes affect only re-imports of locations. Existing data
needs a separate backfill script — a follow-up will cover that
either as a one-shot migration or by running the existing
`reimport_subscriptions.py` after this script.
2026-05-08 15:38:19 -04:00
louispaulb
41d9b5f316 feat: flow editor, Gemini QR scanner with offline queue, dispatch planning v2
Major additions accumulated over 9 days — single commit per request.

Flow editor (new):
- Generic visual editor for step trees, usable by project wizard + agent flows
- PROJECT_KINDS / AGENT_KINDS catalogs decouple UI from domain
- Drag-and-drop reorder via vuedraggable with scope isolation per peer group
- Chain-aware depends_on rewrite on reorder (sequential only — DAGs preserved)
- Variable picker with per-applies_to catalog (Customer / Quotation /
  Service Contract / Issue / Subscription), insert + copy-clipboard modes
- trigger_condition helper with domain-specific JSONLogic examples
- Global FlowEditorDialog mounted once in MainLayout, Odoo inline pattern
- Server: targo-hub flow-runtime.js, flow-api.js, flow-templates.js
- ERPNext: Flow Template/Run doctypes, scheduler, 5 seeded system templates
- depends_on chips resolve to step labels instead of opaque "s4" ids

QR/OCR scanner (field app):
- Camera capture → Gemini Vision via targo-hub with 8s timeout
- IndexedDB offline queue retries photos when signal returns
- Watcher merges late-arriving scan results into the live UI

Dispatch:
- Planning mode (draft → publish) with offer pool for unassigned jobs
- Shared presets, recurrence selector, suggested-slots dialog
- PublishScheduleModal, unassign confirmation

Ops app:
- ClientDetailPage composables extraction (useClientData, useDeviceStatus,
  useWifiDiagnostic, useModemDiagnostic)
- Project wizard: shared detail sections, wizard catalog/publish composables
- Address pricing composable + pricing-mock data
- Settings redesign hosting flow templates

Targo-hub:
- Contract acceptance (JWT residential + DocuSeal commercial tracks)
- Referral system
- Modem-bridge diagnostic normalizer
- Device extractors consolidated

Migration scripts:
- Invoice/quote print format setup, Jinja rendering
- Additional import + fix scripts (reversals, dates, customers, payments)

Docs:
- Consolidated: old scattered MDs → HANDOFF, ARCHITECTURE, DATA_AND_FLOWS,
  FLOW_EDITOR_ARCHITECTURE, BILLING_AND_PAYMENTS, CPE_MANAGEMENT,
  APP_DESIGN_GUIDELINES
- Archived legacy wizard PHP for reference
- STATUS snapshots for 2026-04-18/19

Cleanup:
- Removed ~40 generated PDFs/HTMLs (invoice_preview*, rendered_jinja*)
- .gitignore now covers invoice preview output + nested .DS_Store

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 10:44:17 -04:00
louispaulb
607ea54b5c refactor: reduce token count, DRY code, consolidate docs
Backend services:
- targo-hub: extract deepGetValue to helpers.js, DRY disconnect reasons
  lookup map, compact CAPABILITIES, consolidate vision.js prompts/schemas,
  extract dispatch scoring weights, trim section dividers across 9 files
- modem-bridge: extract getSession() helper (6 occurrences), resetIdleTimer(),
  consolidate DM query factory, fix duplicate username fill bug, trim headers
  (server.js -36%, tplink-session.js -47%, docker-compose.yml -57%)

Frontend:
- useWifiDiagnostic: extract THRESHOLDS const, split processDiagnostic into
  6 focused helpers (processOnlineStatus, processWanIPs, processRadios,
  processMeshNodes, processClients, checkRadioIssues)
- EquipmentDetail: merge duplicate ROLE_LABELS, remove verbose comments

Documentation (17 → 13 files, -1,400 lines):
- New consolidated README.md (architecture, services, dependencies, auth)
- Merge ECOSYSTEM-OVERVIEW into ARCHITECTURE.md
- Merge MIGRATION-PLAN + ARCHITECTURE-COMPARE + FIELD-GAP + CHANGELOG → MIGRATION.md
- Merge COMPETITIVE-ANALYSIS into PLATFORM-STRATEGY.md
- Update ROADMAP.md with current phase status
- Delete CONTEXT.md (absorbed into README)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 08:39:58 -04:00
louispaulb
320655b0a0 refactor: major cleanup — remove dead dispatch app, commit all backend code, extract client composables
- Remove apps/dispatch/ (100% replaced by ops dispatch module, unmaintained)
- Commit services/targo-hub/lib/ (24 modules, 6290 lines — was never tracked)
- Commit services/docuseal + services/legacy-db docker-compose configs
- Extract client app composables: useOTP, useAddressSearch, catalog data, format utils
- Refactor CartPage.vue 630→175 lines, CatalogPage.vue 375→95 lines
- Clean hardcoded credentials from config.js fallback values
- Add client portal: catalog, cart, checkout, OTP verification, address search
- Add ops: NetworkPage, AgentFlowsPage, ConversationPanel, UnifiedCreateModal
- Add ops composables: useBestTech, useConversations, usePermissions, useScanner
- Add field app: scanner composable, docker/nginx configs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 17:38:38 -04:00
louispaulb
bfffed2b41 feat: ONT diagnostics — grouped mesh topology, signal RSSI, management link
- EquipmentDetail: collapsible node groups (clients grouped by mesh node)
- Signal strength as RSSI % (0-255 per 802.11-2020) with 10-tone color scale
- Management IP clickable link to device web GUI (/superadmin/)
- Fibre status compact top bar (status + Rx/Tx power when available)
- targo-hub: WAN IP detection across all VLAN interfaces
- targo-hub: full WiFi client count (direct + EasyMesh mesh repeaters)
- targo-hub: /devices/:id/hosts endpoint with client-to-node mapping
- ClientsPage: start empty, load only on search (no auto-load all)
- nginx: dynamic ollama resolver (won't crash if ollama is down)
- Cleanup: remove unused BillingKPIs.vue and TagInput.vue
- New docs and migration scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 21:26:14 -04:00
louispaulb
0536e04c86 feat: extract GenieACS WiFi/VoIP provisioning data from MariaDB
Found GenieACS MariaDB at 10.100.80.100 (NOT 10.5.14.21 as
configured in ext scripts — that IP was stale/blocked).

Provisioning data:
- 1,713 WiFi entries (858 unique Deco MACs → SSID/password)
- 797 VoIP entries (469 unique RCMG ONT serials → SIP creds)
- WiFi keyed by Deco MAC (403F8C OUI), VoIP by ONT serial

Complete chain verified:
  ONT serial (RCMG) → fibre table (OLT/slot/port)
                     → device table (delivery_id)
                     → delivery (account_id → ERPNext customer)
                     → VoIP provisioning (SIP credentials)
                     → WiFi provisioning (via linked Deco MAC)

Reconciliation: 2,499 RCMG serials addressable, 2,003 have
full fibre+device chain, 282 have VoIP provisioning attached.
3,185 TPLG serials, 2,935 in both fibre and device tables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 08:49:26 -04:00
louispaulb
231bb6fbcc feat: complete device matching analysis (legacy ↔ GenieACS ↔ ERPNext)
Full data export and cross-reference analysis:
- 7,550 GenieACS devices with IPs, deviceId, tags
- 6,720 legacy devices (raisecom, tplink, onu categories)
- 16,056 fibre table entries (OLT frame/slot/port/ontid, VLANs)
- 8,434 legacy services linked to devices

Key finding: CWMP serial ≠ physical serial. Only 22/7,550 devices
are tagged with their physical serial (RCMG/TPLG). Raisecom MAC
is extractable from CWMP serial suffix. TP-Link CWMP serial = sticker
serial for ONT models.

Matching strategy documented: tag-based, MAC-based, OLT port-based.
Recommends bulk tagging via OLT query as first step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 08:08:57 -04:00
louispaulb
8ba73251f3 feat: full GenieACS config export (provisions, ext scripts, fleet data)
Complete backup of all GenieACS ACS configuration:
- 24 provision scripts (default, inform, bootstrap, firmware upgrades,
  per-model configs for HT803G, HT502, HT812, Deco, XX230v, XX430v, XX530v)
- 25 presets (trigger rules mapping events to provisions)
- 6 ext scripts (provisioning.js, wifi.js, voip.js — query MariaDB
  for per-device WiFi SSID/password and VoIP credentials)
- 12 firmware images catalogued (HT502, HG8245, HT803G-W/WS2, HT812, Deco)
- 7,550 device fleet snapshot (4,035 online, 53.4% online rate)
- GenieACS env config (MongoDB at 10.5.2.116, ext dir, JWT secret)

Fleet breakdown:
- Device2 (TP-Link Deco): 4,051 units (74% online) — bulk of fleet
- HT803G (Raisecom): 2,833 units (33% online) — legacy ONTs
- DISCOVERYSERVICE: 156 ghost entries (0% online)
- Grandstream phones: GXP2130/2160/1630, HT502/812

Key finding: ext scripts use MariaDB (10.5.14.21) for WiFi/VoIP
provisioning data (SSID, passwords, SIP credentials per serial).
This data must be migrated to ERPNext or a new provisioning DB
for Oktopus.

Custom fork: @genieacs/genieacs-targo v1.2.8-targo.3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 21:08:51 -04:00
louispaulb
56ad97bc71 feat: GenieACS config export + TR-069 to TR-369 migration plan
- Add /acs/export endpoint: dumps all provisions, presets, virtual
  params, files metadata in one call (insurance policy for migration)
- Add /acs/provisions, /acs/presets, /acs/virtual-parameters, /acs/files
- Shell script export_genieacs.sh for offline full backup
- TR069-TO-TR369-MIGRATION.md: phased migration plan from GenieACS
  to Oktopus with parallel run, provision mapping, CPE batching

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 21:03:41 -04:00
louispaulb
4693bcf60c feat: telephony UI, performance indexes, Twilio softphone, lazy-load invoices
- Add PostgreSQL performance indexes migration script (1000x faster queries)
  Sales Invoice: 1,248ms → 28ms, Payment Entry: 443ms → 31ms
  Indexes on customer/party columns for all major tables
- Disable 3CX poller (PBX_ENABLED flag, using Twilio instead)
- Add TelephonyPage: full CRUD UI for Routr/Fonoster resources
  (trunks, agents, credentials, numbers, domains, peers)
- Add PhoneModal + usePhone composable (Twilio WebRTC softphone)
- Lazy-load invoices/payments (initial 5, expand on demand)
- Parallelize all API calls in ClientDetailPage (no waterfall)
- Add targo-hub service (SSE relay, SMS, voice, telephony API)
- Customer portal: invoice detail, ticket detail, messages pages
- Remove dead Ollama nginx upstream

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 13:59:59 -04:00
louispaulb
4a8718f67c feat: subscription reimport, customer/doctype ID rename, zero-padded format
- Switch Ops data source from Subscription to Service Subscription (source of truth)
- Reimport 39,630 native Subscriptions from Service Subscription data
- Rename 15,302 customers to CUST-{legacy_customer_id} (eliminates hex UUIDs)
- Rename all doctypes to zero-padded 10-digit numeric format:
  SINV-0000001234, PE-0000001234, ISS-0000001234, LOC-0000001234,
  EQP-0000001234, SUB-0000001234, ASUB-0000001234
- Fix subscription pricing: LPB4 now correctly shows 0$/month
- Update ASUB- prefix detection in useSubscriptionActions.js
- Add reconciliation, reimport, and rename migration scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 17:17:23 -04:00
louispaulb
101faa21f1 feat: inline editing, search, notifications + full repo cleanup
- InlineField component + useInlineEdit composable for Odoo-style dblclick editing
- Client search by name, account ID, and legacy_customer_id (or_filters)
- SMS/Email notification panel on ContactCard via n8n webhooks
- Ticket reply thread via Communication docs
- All migration scripts (51 files) now tracked
- Client portal and field tech app added to monorepo
- README rewritten with full feature list, migration summary, architecture
- CHANGELOG updated with all recent work
- ROADMAP updated with current completion status
- Removed hardcoded tokens from docs (use $ERP_SERVICE_TOKEN)
- .gitignore updated (docker/, .claude/, exports/, .quasar/)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 07:34:41 -04:00
louispaulb
08cf1c94e3 feat: 29K customer memos imported as Comments with real dates
- 29,178 account_memo → Comment on Customer
- Timestamps converted from unix to datetime
- Author mapped from staff_id → User email
- Visible in Customer page comment section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:43:57 -04:00
louispaulb
c6b5aa8f61 feat: 99K payments imported with invoice references
- 99,839 Payment Entries (24 months, excl credits)
- 120,130 Payment-Invoice references (payment_item → Sales Invoice)
- Modes: Bank Draft (PPA/direct), Credit Card, Cheque, Cash
- legacy_payment_id custom field for traceability
- Creation dates set from legacy timestamps
- 0 errors

Full financial chain: Customer → Subscription → Invoice → Payment → Bank ref (PPA)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:33:35 -04:00
louispaulb
5640063bd0 fix: correct creation/modified dates from unix timestamps
- 129,078 Issues: creation = ticket.date_create, modified = ticket.last_update
- 115,721 Invoices: creation = invoice.date_orig
- 15,059 Customers: creation = account.date_orig, modified = account.date_last

All timestamps now show real legacy dates instead of import date.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:20:47 -04:00
louispaulb
4f74376412 feat: complete data mirror — all customers + 115K invoices
- 8,636 terminated customers imported (disabled=1, terminate reason/company/notes preserved)
- Total customers: 15,303 (100% of legacy)
- 33,131 Subscription.party links fixed (CUST-xxx)
- 115,721 Sales Invoices (24 months) + 658K line items
- Custom field: Sales Invoice.legacy_invoice_id
- All invoices as Draft (not submitted, not sent)

Customer lifecycle preserved:
  Active → services, subscriptions, invoices
  Terminated → disabled=1, customer_details has departure reason/competitor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:09:16 -04:00
louispaulb
22377bb381 feat: fix all data relationships + PPA reference numbers
- fix_issue_owners.py: 53K Issues linked to creator (owner) + 55K to assignee (_assign)
- fix_issue_cust2.py: 47K Issues linked to Customer via legacy_account_id
- fix_sub_address.py: 21K Subscriptions linked to service Address
- customer_pos_id set to legacy PPA reference (15-digit bank number) on all 6,667 Customers
- Subscription custom fields: service_address (Link→Address), service_location (Link→Service Location)
- Fiscal Year 2025-2026 created (Jul 1 2025 → Jun 30 2026)

Relationships now complete:
  Customer → Address (N) → Subscription (N) → Item (plan + speeds)
  Customer → Contact (N) → email/phone
  Customer → Issue (N) → parent_incident → child Issues
  Issue → owner (User who created) + _assign (User responsible)
  Subscription → service_address → specific installation address
  Customer.customer_pos_id = PPA bank reference number

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 15:45:51 -04:00
louispaulb
ac9b367334 feat: Phase 7 — 45 ERPNext Users from legacy staff
- 45 users created with Authentik SSO (no password)
- Roles assigned: System Manager, Support Team, Sales/Accounts
- Service accounts skipped (admin, tech, dev, inventaire, agent)
- Email = Authentik identity link

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 15:13:31 -04:00
louispaulb
7a15bfd600 feat: Phase 6 — 242K tickets migrated as Issues with parent/child
- 38 Issue Types from ticket_dept
- 242,605 Issues created (open + closed)
- 25,805 parent/child links (incident pattern)
- Custom fields: parent_incident, is_incident, affected_clients, impact_zone, service_location, legacy_ticket_id
- Communications deferred (778K closed ticket messages — import separately)
- 0 staff→user mapped (ERPNext users need to be created/linked)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 15:06:58 -04:00
louispaulb
571f89976d feat: Phase 5 opening balance + AR analysis
- Journal Entry draft created with 1,918 customer balance lines
- AR analysis: $423K monthly billing, $77.96 avg/client, $62K aging 90j+
- Temporary Opening equity account created
- Scheduler remains PAUSED

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:47:18 -04:00
louispaulb
93dd7a525f feat: migration legacy → ERPNext phases 1-4 complete
Phase 1: 833 Items + 34 Item Groups + custom fields (ISP speeds, RADIUS, legacy IDs)
Phase 2: 6,667 Customers + Contacts + Addresses via direct PG (~30s)
Phase 3: Tax template QC TPS+TVQ + 92 Subscription Plans
Phase 4: 21,876 Subscriptions with RADIUS data

CRITICAL: ERPNext scheduler is PAUSED — do not reactivate without explicit go.

Includes:
- ARCHITECTURE-COMPARE.md: full schema mapping legacy vs ERPNext
- CHANGELOG.md: detailed migration log
- MIGRATION-PLAN.md: strategy and next steps
- scripts/migration/: idempotent Python scripts (direct PG method)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:35:02 -04:00
louispaulb
04dc0ceb14 refactor: monorepo structure — apps/dispatch, apps/website, erpnext/
- Merged dispatch-app (17 commits) into apps/dispatch/
- Merged site-web-targo (4 commits) into apps/website/
- Renamed scripts/ → erpnext/
- Removed empty doctypes/
- Updated README with monorepo layout and Gigafibre branding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 08:10:15 -04:00
louispaulb
49494cf1a7 Initial commit: FSM data model, architecture docs, setup scripts
Data model inspired by Odoo OCA Field Service + Salesforce FS patterns.
Adapted for small ISP/telecom (Gigafibre) running ERPNext.

Doctypes: Service Location, Service Equipment, Service Subscription
+ child tables for equipment history, checklists, photos, materials
+ extended Dispatch Job with customer/location/equipment links

Docs: architecture overview, tech stack, auth flow, industry comparison, roadmap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:02:25 -04:00