# TechTitans POS — IPTV Management Phase I1 Implementation Report

**Branch:** `techtitans/iptv-i1` (cut from `techtitans/repair-r1`, which carries all completed Repair R1
work). **Scope:** foundation + subscription management for a modern IPTV module built on the existing
Customer CRM, auth, RBAC and money/tenancy architecture — reusing the Repairs R1 patterns, not rebuilding
them. **Not pushed or deployed.**

---

## 1. Verified legacy features mapped (from the read-only legacy audit)

Source: `legacy-portal/04-IPTV-AND-SUBSCRIPTIONS-ANALYSIS.md`, `06-LEGACY-DATABASE-MAPPING.md`,
`07/08/09`. The legacy `iptv_details` (~1,623 rows) schema was inspected read-only (no legacy PHP executed,
no upload dirs touched).

| Legacy concept | Legacy shape | New I1 model |
|---|---|---|
| Connection (`iptv_details`, PK `ipid`) | loose `varchar` customer FK; no constraints | `iptv_subscriptions` — composite FK `(tenant_id, customer_id)` into the single Customer CRM |
| Provider ("portal") | **free-text hardcoded dropdown** (startv, eagletv…) | `iptv_providers` — first-class tenant catalogue (name/reference/status/notes) |
| Plan / package | **no entity** — `portal` + ad-hoc `monthly_charge` | `iptv_plans` — configurable duration (days/months) + price/cost (minor units) + POS tax class |
| Account ids | `user_login`, `macid`, `act_user` (echoed unescaped → stored XSS) | `account_identifier` (safe, parameterized; React auto-escapes) |
| Credentials | legacy stored carrier creds **plaintext**, echoed to browser (R6) | `credential_secret` **encrypted at rest**, masked, reveal gated + audited |
| Activation / expiry | `start_date` / `payment_finished_date` as **varchar**, `strtotime()`, 1970 errors | real `DATE` columns + calendar-safe arithmetic |
| Renewals / adjustments | `ip_day_update_tbl` ad-hoc expiry nudges (6,345 rows) | `iptv_subscription_renewals` — append-only, immutable history (never overwrites expiry) |
| Reminders | **page-load only, no cron** (R13) | `iptv_reminder_rules` as data + eligibility API (I1); scheduled send deferred to I2 |
| Billing | dead `iptv_payments` ledger; money via shared `sales_details` | **no new ledger** — I1 documents the Order→Invoice→Payment boundary for I2 |
| Subcontractor cascade-delete (R12) | hard-deletes all connections | **no hard deletes**; `restrictOnDelete` FKs; lifecycle via status |

**Not done in I1 (per task):** no historical IPTV records migrated; no provider API integrations invented;
no real SMS/email sent; no plaintext credential copied from the legacy portal.

## 2. Database migrations (`database/migrations/2026_11_23_*`)

All UUID PKs (`UuidColumn::key`), `tenant_id` + composite tenant FKs (DB-enforced isolation), `(tenant_id,
…)` indexes, CHECK constraints — mirroring Repairs. Applied additively to the dev DB `techpos` via plain
`php artisan migrate` (the `TechTitansDatabaseGuard` dev carve-out permits additive migrate; destructive
commands stay blocked). The test DB `techtitans_pos_test` builds them via RefreshDatabase.

- `100001_create_iptv_providers_table` — tenant-owned; `UNIQUE(tenant_id,name)`; status CHECK.
- `100002_create_iptv_plans_table` — provider FK; duration unit/value CHECKs; price/cost minor units;
  nullable `tax_class_id` FK into `tax_classes`.
- `100003_create_iptv_subscriptions_table` — **location-owned** (location FK, `UNIQUE(tenant_id,
  location_id,id)`); customer/provider/plan/responsible-employee FKs; `credential_secret` text (encrypted
  by the model); `start_date`/`expiry_date` DATE; status CHECK; per-location human `subscription_number`.
- `100004_create_iptv_subscription_renewals_table` — append-only history (monotonic `sequence_no`,
  `created_at` only); previous/new expiry; actor snapshot; cascade on subscription delete.
- `100005_create_iptv_reminder_rules_table` — tenant-owned reminder windows (`days_before`, channel).
- `100006_backfill_iptv_permissions` — reseeds the catalogue + reprovisions existing tenants' system roles.
- `100007_create_iptv_number_sequences_table` — per-(tenant,location,year) subscription-number allocator.

## 3. Backend routes & services

Routes (`routes/api.php`, 20 endpoints under the authenticated tenant group):

- `GET iptv/dashboard`, `GET iptv/reminders/due`, `GET iptv/subscriptions`, `GET …/{id}`,
  `GET …/{id}/renewals`, `GET customers/{c}/iptv-subscriptions`, `GET iptv/providers`, `GET iptv/plans` —
  **iptv.view**.
- `POST iptv/subscriptions` — **iptv.manage** + `resolve.location`; `PATCH …/{id}` — iptv.manage.
- `POST iptv/subscriptions/{id}/renew` — **iptv.renew**.
- `GET iptv/subscriptions/{id}/credential` — **iptv.credentials_view** (reveal; audited).
- `GET/POST/PATCH/DELETE iptv/config/reminder-rules`, `POST/PATCH iptv/providers|plans` — **iptv.configure**.

All writes carry `reject.tenant_id`. Services (`app/Iptv`):

- `IptvSubscriptionService` — activation/update/renew/reveal/search/dashboard/reminders; transactional,
  row-locked, audited (fail-closed), idempotent via `RequestIdempotency` (X-Request-Id).
- `IptvConfigService` — provider/plan/reminder CRUD with uniqueness + tax-class validation + audit.
- `RenewalCalculator` — calendar-safe (`addMonthsNoOverflow`, leap-year); baseline = max(expiry, today).
- `IptvNumberSequencer` — deadlock-free human numbers `IPTV-{YYYY}-{seq}`.
- `IptvResource` — safe projection; **never serializes the credential** (only `has_credential`); derives
  `expiry_state` (active/expiring_soon/expired/suspended/cancelled) from expiry + the reminder horizon.
- Models `IptvProvider/IptvPlan/IptvSubscription/IptvSubscriptionRenewal/IptvReminderRule`
  (`BelongsToTenant` + `HasUuidV7`; `credential_secret` → `encrypted` cast + `$hidden`).

## 4. Frontend pages (`frontend/src/pages/iptv`)

- **IptvDashboardPage** — derived health tiles (total/active/expiring/expired/suspended/cancelled) +
  active-by-provider; expiring/expired tiles deep-link into the filtered list.
- **IptvListPage** — searchable, filterable (state/provider), paginated subscription table + mobile cards.
- **NewIptvPage** — the 4-step flow **Select Customer → Provider/Plan → Account Details → Review & Save**
  (reuses the CRM customer search; idempotent create; optional write-only credential).
- **IptvDetailPage** — account summary, derived state, renewal history, inline **Renew**, **Reveal
  credential** (masked by default; fetched on demand from the gated endpoint), lifecycle status change.
- **IptvProvidersPage** / **IptvPlansPage** — tenant configuration (create/edit; prices in major units →
  minor units).
- **CustomerIptvPanel** — an **IPTV tab on the existing customer profile**, linking each account back to
  the customer (no duplicated PII). Nav: an **IPTV** group (Dashboard/Subscriptions/New/Providers/Plans),
  permission-gated per item. API client `lib/api/iptv.ts`.

## 5. Permissions

Added to `PermissionCatalog` (+ `RoleTemplates`, backfilled to existing tenants):
`iptv.view`, `iptv.manage`, `iptv.configure`, `iptv.renew`, `iptv.credentials_view`. view/manage/renew +
credential reveal are **location-sensitive**; configure is tenant-wide; credential reveal is **sensitive**
(audited). Granted to Owner (all), General Manager + Location Manager (all five), Shift Supervisor
(view/manage/renew only — no config/credentials).

## 6. Tests

- **Backend** (dedicated test DB `techtitans_pos_test`): `RenewalCalculatorTest` (9) — month-end, leap-year
  (Jan 31→Feb 28/29; Feb 29 +12mo→Feb 28), active-vs-lapsed baseline, invalid durations.
  `IptvSubscriptionTest` (11) — activation+expiry derivation+history, **credential encrypted at rest &
  never listed**, **reveal requires iptv.credentials_view + audits**, multiple accounts per customer,
  plan↔provider mismatch rejected, renewal appends immutable history + advances expiry, **create+renew
  idempotent per X-Request-Id**, cross-tenant 404, permission gating, dashboard active/expiring/expired
  counts, provider/plan config gating. **20 passed (73 assertions).** Existing authorization suite
  (incl. the catalogue golden-list, updated) still green (**240+ passed**).
- **Frontend**: `IptvPages.test.tsx` — list render + **/403 without iptv.view**, full new-subscription
  wizard (asserts the create body incl. credential), detail **credential masked → revealed via the gated
  endpoint**, renew. **5 passed.** `tsc` ✓, `eslint` ✓. Customer suite still green (31).

## 7. Browser verification (Playwright, real servers, techtitans)

`frontend/e2e/iptv.spec.ts` passed; screenshots in `frontend/test-results/iptv/`:
`dashboard.png` (3 total / 1 active / 1 expiring / 1 expired, window 7, StarTV ×3), `navigation.png` (the
IPTV nav group), `list.png`, `new-wizard.png`, `providers.png`, and
`detail-credential-revealed.png` (credential `star-portal-pw-9931` revealed through the audited endpoint,
expiry 2/14/2027 "97 days left", renewal history). Data seeded via the API (provider, two plans, a 7-day
reminder rule, three subscriptions with varied expiry).

## 8. Known limitations

- **No billing in I1** (by design): plan price/cost are reference figures; no Order/Invoice/Payment is
  created. See §9.
- **No reminders sent** (by design): rules drive eligibility + the dashboard horizon only; no SMS/email.
- **No legacy data migrated** (by design): `iptv_details`/`ip_day_update_tbl` import is a later increment.
- Renewal uses the server's current date for "today" (tenant/location-local date refinement is a minor
  future improvement; the calculator itself is timezone-neutral and takes an explicit `today`).
- "Expiring soon" horizon = the largest active reminder rule (or `config('iptv.default_reminder_days', 7)`).

## 9. Readiness for I2 (billing & notifications)

- **Billing boundary (documented, not built):** a renewal/activation should create an `Order` (a service
  line referencing the plan) → `Invoice` → `Payment` through the existing money pipeline (`app/Orders`,
  `app/Payments`), reusing tax via the plan's `tax_class_id`. The subscription already records a
  price/currency snapshot per renewal row to seed the charge. No parallel IPTV ledger.
- **Notifications:** `iptv_reminder_rules` + `IptvSubscriptionService::remindersDue()` already compute
  eligible subscriptions per window; I2 wires a scheduled job (+ an SMS channel) to deliver them.
- Permissions `iptv.*` and the location-owned model are already in place for per-location billing/reporting.

## 10. Local commit hashes (branch `techtitans/iptv-i1`, not pushed)

- Backend: **9f4ffc0** (schema/services/API/tests) + the commit carrying this report.
- Frontend: see the frontend commit on `techtitans/iptv-i1` (pages, API client, nav, CRM tab, tests, e2e).
