cai-exos-systems/daveadmin-exos-demo:ops/seed-mr-glenn.php
ops/seed-mr-glenn.php
```text
<?php
/**
* seed-mr-glenn.php — FOSSBilling seed for Mr Glenn demo accounts
*
* Creates 5 clients (FOSSBilling IDs 17–21 assuming clean state):
* 17 — Mr Glenn Greenfield Apartments (Order Sherpa scenario)
* 18 — Mr Glenn Acme Holdings (Enterprise Billing — parent)
* 19 — Mr Glenn Acme Nordics
* 20 — Mr Glenn Acme UK
* 21 — Mr Glenn Acme EMEA
*
* Usage:
* php seed-mr-glenn.php [--dry-run] [--foss-url=https://foss.exos.bluenotelogic.com] [--token=...]
*
* Idempotent: skips creation if email already exists.
*/
declare(strict_types=1);
$fossUrl = 'https://foss.exos.bluenotelogic.com';
$token = 'ExosFoss26ApiTkn7xR9mK4q';
$user = 'admin';
$dryRun = false;
foreach ($argv as $arg) {
if (str_starts_with($arg, '--foss-url=')) $fossUrl = trim(substr($arg, 11));
if (str_starts_with($arg, '--token=')) $token = trim(substr($arg, 8));
if ($arg === '--dry-run') $dryRun = true;
}
echo "FOSSBilling Seed — Mr Glenn Demo Accounts\n";
echo "URL: $fossUrl\n";
echo $dryRun ? "[DRY RUN — no changes will be made]\n\n" : "\n";
// ── HTTP helper ────────────────────────────────────────────────────────────────
function fossApi(string $url, string $user, string $token, string $module, string $method, array $payload): array
{
$endpoint = rtrim($url, '/') . '/api/admin/' . $module . '/' . $method;
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_USERPWD => $user . ':' . $token,
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
CURLOPT_USERAGENT => 'EXOS-Seed/1.0',
]);
$body = curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($err || $code >= 400) {
return ['error' => "HTTP $code: $err", 'body' => (string)$body];
}
$json = json_decode((string)$body, true);
return is_array($json) ? $json : ['error' => 'Invalid JSON', 'body' => (string)$body];
}
function findClientByEmail(string $fossUrl, string $user, string $token, string $email): ?int
{
$result = fossApi($fossUrl, $user, $token, 'client', 'get_list', ['per_page' => 100]);
$total = (int)($result['result']['total'] ?? 0);
$pages = (int)ceil($total / 100) ?: 1;
for ($p = 1; $p <= $pages; $p++) {
$res = fossApi($fossUrl, $user, $token, 'client', 'get_list', ['per_page' => 100, 'page' => $p]);
foreach ($res['result']['list'] ?? [] as $c) {
if (strtolower((string)$c['email']) === strtolower($email)) {
return (int)$c['id'];
}
}
}
return null;
}
function getHelpdeskId(string $fossUrl, string $user, string $token): int
{
$result = fossApi($fossUrl, $user, $token, 'support', 'helpdesk_get_list', ['per_page' => 10]);
foreach ($result['result']['list'] ?? [] as $h) {
if (stripos((string)($h['name'] ?? ''), 'Exos') !== false || stripos((string)($h['name'] ?? ''), 'General') !== false) {
return (int)$h['id'];
}
}
return (int)(($result['result']['list'][0]['id'] ?? 1));
}
function getProductGroupId(string $fossUrl, string $user, string $token, string $name): ?int
{
$result = fossApi($fossUrl, $user, $token, 'product', 'get_categories', []);
foreach ($result['result'] ?? [] as $cat) {
if (stripos((string)($cat['title'] ?? ''), $name) !== false) {
return (int)$cat['id'];
}
}
return null;
}
function seedClient(string $fossUrl, string $user, string $token, array $data, bool $dryRun): int
{
$existing = findClientByEmail($fossUrl, $user, $token, $data['email']);
if ($existing) {
echo " [SKIP] Client already exists (ID $existing): {$data['company']}\n";
return $existing;
}
if ($dryRun) {
echo " [DRY] Would create client: {$data['company']} <{$data['email']}>\n";
return 0;
}
$result = fossApi($fossUrl, $user, $token, 'client', 'create', $data);
$id = (int)($result['result'] ?? 0);
if ($id <= 0) {
echo " [FAIL] Could not create {$data['company']}: " . json_encode($result) . "\n";
return 0;
}
echo " [OK] Created {$data['company']} → ID $id\n";
return $id;
}
function seedProduct(string $fossUrl, string $user, string $token, array $data, bool $dryRun): int
{
if ($dryRun) {
echo " [DRY] Would create product: {$data['title']}\n";
return 0;
}
$result = fossApi($fossUrl, $user, $token, 'product', 'prepare', $data);
$id = (int)($result['result'] ?? 0);
if ($id <= 0) {
echo " [FAIL] Could not create product {$data['title']}: " . json_encode($result) . "\n";
return 0;
}
echo " [OK] Created product: {$data['title']} → ID $id\n";
return $id;
}
ops/seed-mr-glenn.php return $id; } function seedProduct(string $fossUrl, string $user, string $token, array $data, bool $dryRun): int { if ($dryRun) { echo " [DRY] Would create product: {$data['title']}\n"; return 0; } $result = fossApi($fossUrl, $user, $token, 'product', 'prepare', $data); $id = (int)($result['result'] ?? 0); if ($id <= 0) { echo " [FAIL] Could not create product {$data['title']}: " . json_encode($result) . "\n"; return 0; } echo " [OK] Created product: {$data['title']} → ID $id\n"; return $id; } function seedOrder(string $fossUrl, string $user, string $token, int $clientId, int $productId, array $extra, bool $dryRun): int { if ($dryRun) { echo " [DRY] Would create order for client $clientId / product $productId\n"; return 0; } $payload = array_merge(['client_id' => $clientId, 'product_id' => $productId], $extra); $result = fossApi($fossUrl, $user, $token, 'order', 'create', $payload); $id = (int)($result['result'] ?? 0); if ($id <= 0) { echo " [FAIL] Could not create order: " . json_encode($result) . "\n"; return 0; } echo " [OK] Created order ID $id for client $clientId\n"; return $id; } function seedInvoice(string $fossUrl, string $user, string $token, int $clientId, array $lines, string $note, bool $dryRun): int { if ($dryRun) { echo " [DRY] Would create invoice for client $clientId with " . count($lines) . " lines\n"; return 0; } $result = fossApi($fossUrl, $user, $token, 'invoice', 'create', [ 'client_id' => $clientId, 'note' => $note, 'lines' => $lines, ]); $id = (int)($result['result'] ?? 0); if ($id <= 0) { echo " [FAIL] Could not create invoice: " . json_encode($result) . "\n"; return 0; } echo " [OK] Created invoice ID $id for client $clientId\n"; return $id; } function seedTicket(string $fossUrl, string $user, string $token, int $clientId, int $helpdeskId, array $data, bool $dryRun): int { if ($dryRun) { echo " [DRY] Would create ticket: {$data['subject']}\n"; return 0; } $result = fossApi($fossUrl, $user, $token, 'support', 'ticket_create', array_merge( ['client_id' => $clientId, 'support_helpdesk_id' => $helpdeskId, 'status' => 'open'], $data )); $id = (int)($result['result'] ?? 0); if ($id <= 0) { echo " [FAIL] Could not create ticket: " . json_encode($result) . "\n"; return 0; } // Set priority after creation (FOSSBilling ticket_create may not accept priority) if (!empty($data['priority'])) { fossApi($fossUrl, $user, $token, 'support', 'ticket_update', ['id' => $id, 'priority' => $data['priority'], 'status' => 'open']); } echo " [OK] Created ticket ID $id: {$data['subject']}\n"; return $id; } // ── Get helpdesk ID ──────────────────────────────────────────────────────────── $helpdeskId = $dryRun ? 1 : getHelpdeskId($fossUrl, $user, $token); echo "Using helpdesk ID: $helpdeskId\n\n"; // ══════════════════════════════════════════════════════════════════════════════ // CLIENT 17 — Mr Glenn Greenfield Apartments (Order Sherpa) // ══════════════════════════════════════════════════════════════════════════════ echo "── Client 17: Mr Glenn Greenfield Apartments Management Ltd ──\n"; $greenfieldId = seedClient($fossUrl, $user, $token, [ 'email' => 'mr.glenn.greenfield@exos-demo.bluenotelogic.com', 'password' => 'ExosDemo26!', 'first_name' => 'Mr Glenn', 'last_name' => 'Greenfield', 'company' => 'Mr Glenn Greenfield Apartments Management Ltd', 'type' => 'company', 'address_1' => '45 Greenfield Way, Building B', 'city' => 'Oslo', 'country' => 'NO', 'phone' => '+47 22 55 00 45', 'currency' => 'EUR', 'notes' => "Customer ID: CUST-10045 | MDU – 48 units | Order Sherpa demo account\nOrder ORD-70021 FALLOUT: DOCSIS 3.1 modems unavailable Oslo depot. Bergen Depot has 12. Technician TECH-204 pending assignment. SLA risk: Medium.", ], $dryRun); if ($greenfieldId > 0) { // Seed order with fallout status echo " Seeding MSO order ORD-70021 (in Fallout)...\n"; $gfProductResult = fossApi($fossUrl, $user, $token, 'product', 'prepare', [ 'title' => 'Triple-Play Installation (DOCSIS + Cable TV + Voice)', 'type' => 'custom', 'price' => 0, 'period' => 'monthly', 'is_addon' => 0, 'category_id' => 1, 'description' => 'CUST-10045 MDU 48-unit triple-play: DOCSIS Gigabit Internet, Cable TV Premium, Digital Voice. Ref: ORD-70021.', ]); $gfProductId = (int)($gfProductResult['result'] ?? 0); if ($gfProductId > 0 && !$dryRun) { $ordResult = fossApi($fossUrl, $user, $token, 'order', 'create', [ 'client_id' => $greenfieldId, 'product_id' => $gfProductId, 'price' => 0, 'period' => 'monthly', 'notes' => "ORDER ORD-70021 | New Triple-Play Installation | Ordered: 2026-03-12 | Requested Install: 2026-03-18\nSTATUS: FALLOUT\nFALLOUT CODE: INV-001\nDESCRIPTION: Requested DOCSIS 3.1 modems unavailable in Oslo Central Depot (0 units available). Bergen Depot has 12 units available. Reroute required before field assignment.\nTASKS: 1-Address Validation COMPLETED | 2-Serviceability COMPLETED | 3-Equipment Allocation FAILED | 4-Tech Assignment PENDING | 5-Provisioning NOT STARTED\nJEOPARDY: OPEN — SLA risk MEDIUM — install date 2026-03-18 approaching.", ]); $gfOrderId = (int)($ordResult['result'] ?? 0); echo " [OK] Created order $gfOrderId for Greenfield\n";
ops/seed-mr-glenn.php | Ordered: 2026-03-12 | Requested Install: 2026-03-18\nSTATUS: FALLOUT\nFALLOUT CODE: INV-001\nDESCRIPTION: Requested DOCSIS 3.1 modems unavailable in Oslo Central Depot (0 units available). Bergen Depot has 12 units available. Reroute required before field assignment.\nTASKS: 1-Address Validation COMPLETED | 2-Serviceability COMPLETED | 3-Equipment Allocation FAILED | 4-Tech Assignment PENDING | 5-Provisioning NOT STARTED\nJEOPARDY: OPEN — SLA risk MEDIUM — install date 2026-03-18 approaching.", ]); $gfOrderId = (int)($ordResult['result'] ?? 0); echo " [OK] Created order $gfOrderId for Greenfield\n"; // Set order to suspended/fallout status if ($gfOrderId > 0) { fossApi($fossUrl, $user, $token, 'order', 'update_status', ['id' => $gfOrderId, 'status' => 'suspended']); } } // Seed support ticket echo " Seeding equipment fallout ticket...\n"; seedTicket($fossUrl, $user, $token, $greenfieldId, $helpdeskId, [ 'subject' => '[P2] ORD-70021 Equipment Fallout — DOCSIS 3.1 Modem Shortage Oslo Depot', 'content' => "Order ORD-70021 has entered FALLOUT status.\n\nFALLOUT CODE: INV-001\nAFFECTED SERVICES: Internet, TV\nROOT CAUSE: Requested DOCSIS 3.1 modems unavailable in Oslo Central Depot (0 in stock). Bergen Depot has 12 units available.\n\nRESOLUTION PATH:\n1. Approve reallocation from Bergen Depot to Oslo East\n2. Assign TECH-204 once equipment confirmed en route\n3. Resume network provisioning\n4. Notify customer of revised install date\n\nSLA RISK: Medium — committed install date 2026-03-18 approaching.", 'priority' => 200, ], $dryRun); // Seed a pending invoice echo " Seeding pending invoice...\n"; seedInvoice($fossUrl, $user, $token, $greenfieldId, [ ['title' => 'DOCSIS Gigabit Internet (1 Gbps) — pending activation', 'quantity' => 1, 'price' => 0], ['title' => 'Cable TV Premium Bundle — pending activation', 'quantity' => 1, 'price' => 0], ['title' => 'Digital Voice Line — pending activation', 'quantity' => 1, 'price' => 0], ], 'Order ORD-70021 — held pending equipment allocation resolution', $dryRun); } echo "\n"; // ══════════════════════════════════════════════════════════════════════════════ // CLIENTS 18–21 — Mr Glenn Acme Holdings hierarchy // ══════════════════════════════════════════════════════════════════════════════ echo "── Client 18: Mr Glenn Acme Holdings (parent) ──\n"; $acmeHoldingsId = seedClient($fossUrl, $user, $token, [ 'email' => 'mr.glenn.acme-holdings@exos-demo.bluenotelogic.com', 'password' => 'ExosDemo26!', 'first_name' => 'Mr Glenn', 'last_name' => 'Acme', 'company' => 'Mr Glenn Acme Holdings', 'type' => 'company', 'country' => 'IE', 'currency' => 'EUR', 'notes' => "Enterprise Billing Expert demo — PARENT account\nChild accounts: Acme Nordics (ID 19), Acme UK (ID 20), Acme EMEA (ID 21)\nConsolidated Mar 2026 spend: €22,547 across 3 subsidiaries.", ], $dryRun); echo "\n── Client 19: Mr Glenn Acme Nordics ──\n"; $acmeNordicsId = seedClient($fossUrl, $user, $token, [ 'email' => 'mr.glenn.acme-nordics@exos-demo.bluenotelogic.com', 'password' => 'ExosDemo26!', 'first_name' => 'Mr Glenn', 'last_name' => 'Nordics', 'company' => 'Mr Glenn Acme Nordics', 'type' => 'company', 'country' => 'NO', 'currency' => 'EUR', 'notes' => "Enterprise Billing Expert — Nordics subsidiary of Acme Holdings\nCost Centres: Engineering (100 SIMs), Sales (73 SIMs + roaming)\nContract: 150 min SIMs Jan 2025–Dec 2026 (173 active — above commit)", ], $dryRun); if ($acmeNordicsId > 0) { echo " Seeding Nordics invoices (Jan–Mar 2026)...\n"; $nordicsMonths = [ ['2026-01', 5980, "Jan 2026 — 150 SIMs (Engineering 100 + Sales 50)"], ['2026-02', 6110, "Feb 2026 — 165 SIMs (growing mobile fleet)"], ['2026-03', 6842, "Mar 2026 — 173 SIMs + US roaming spike (Sales 12 SIMs × 12 days)"], ]; foreach ($nordicsMonths as [$month, $total, $note]) { seedInvoice($fossUrl, $user, $token, $acmeNordicsId, [ ['title' => "Mobile 5G Standard Plan — Engineering SIMs ($month)", 'quantity' => 1, 'price' => round($total * 0.41, 2)], ['title' => "Mobile 5G Pro Plan — Sales SIMs ($month)", 'quantity' => 1, 'price' => round($total * 0.48, 2)], ['title' => "Roaming / Overages ($month)", 'quantity' => 1, 'price' => round($total * 0.11, 2)], ], $note, $dryRun); } } echo "\n── Client 20: Mr Glenn Acme UK ──\n"; $acmeUkId = seedClient($fossUrl, $user, $token, [ 'email' => 'mr.glenn.acme-uk@exos-demo.bluenotelogic.com', 'password' => 'ExosDemo26!', 'first_name' => 'Mr Glenn', 'last_name' => 'UK', 'company' => 'Mr Glenn Acme UK', 'type' => 'company', 'country' => 'GB', 'currency' => 'EUR', 'notes' => "Enterprise Billing Expert — UK subsidiary of Acme Holdings\nCost Centre: IT Operations\nANOMALY: 31 unused PBX extensions (€372/mo waste). 1 legacy SIP trunk low-usage.\nContract: 200 PBX seats Mar 2025–Feb 2026 (214 active — 31 UNUSED)", ], $dryRun);
ops/seed-mr-glenn.php Acme UK ──\n"; $acmeUkId = seedClient($fossUrl, $user, $token, [ 'email' => 'mr.glenn.acme-uk@exos-demo.bluenotelogic.com', 'password' => 'ExosDemo26!', 'first_name' => 'Mr Glenn', 'last_name' => 'UK', 'company' => 'Mr Glenn Acme UK', 'type' => 'company', 'country' => 'GB', 'currency' => 'EUR', 'notes' => "Enterprise Billing Expert — UK subsidiary of Acme Holdings\nCost Centre: IT Operations\nANOMALY: 31 unused PBX extensions (€372/mo waste). 1 legacy SIP trunk low-usage.\nContract: 200 PBX seats Mar 2025–Feb 2026 (214 active — 31 UNUSED)", ], $dryRun); if ($acmeUkId > 0) { echo " Seeding UK invoices (Jan–Mar 2026)...\n"; $ukMonths = [ ['2026-01', 5420, "Jan 2026"], ['2026-02', 5488, "Feb 2026"], ['2026-03', 5931, "Mar 2026 — 214 PBX ext (31 unused) + 8 SIP trunks + 85 Mobile Pro SIMs"], ]; foreach ($ukMonths as [$month, $total, $note]) { seedInvoice($fossUrl, $user, $token, $acmeUkId, [ ['title' => "PBX Extension Licences — 214 seats incl 31 unused ($month)", 'quantity' => 1, 'price' => round($total * 0.433, 2)], ['title' => "SIP Trunk — 8 trunks (1 legacy/low-usage) ($month)", 'quantity' => 1, 'price' => round($total * 0.054, 2)], ['title' => "Mobile 5G Pro — 85 SIMs ($month)", 'quantity' => 1, 'price' => round($total * 0.645, 2)], ], $note, $dryRun); } } echo "\n── Client 21: Mr Glenn Acme EMEA ──\n"; $acmeEmeaId = seedClient($fossUrl, $user, $token, [ 'email' => 'mr.glenn.acme-emea@exos-demo.bluenotelogic.com', 'password' => 'ExosDemo26!', 'first_name' => 'Mr Glenn', 'last_name' => 'EMEA', 'company' => 'Mr Glenn Acme EMEA', 'type' => 'company', 'country' => 'DE', 'currency' => 'EUR', 'notes' => "Enterprise Billing Expert — EMEA subsidiary of Acme Holdings\nCost Centre: Human Resources\nBILL SHOCK: Cloud Compute +32% in Mar 2026 (analytics workload). Commitment: €5,000/mo cloud; actual: €6,248.\nContract: €5,000/mo Cloud Compute Jun 2025–May 2026", ], $dryRun); if ($acmeEmeaId > 0) { echo " Seeding EMEA invoices (Jan–Mar 2026)...\n"; $emeaMonths = [ ['2026-01', 6100, "Jan 2026"], ['2026-02', 7230, "Feb 2026 — cloud spend increasing"], ['2026-03', 9774, "Mar 2026 — BILL SHOCK: Cloud Compute +32% (analytics workload) — overage above €5,000 commitment"], ]; foreach ($emeaMonths as [$month, $total, $note]) { $cloudRatio = ($month === '2026-03') ? 0.639 : 0.55; seedInvoice($fossUrl, $user, $token, $acmeEmeaId, [ ['title' => "Cloud Compute — 28,400 hrs (analytics workload) ($month)", 'quantity' => 1, 'price' => round($total * $cloudRatio, 2)], ['title' => "Cloud Storage — 12,800 GB ($month)", 'quantity' => 1, 'price' => round($total * 0.105, 2)], ['title' => "Mobile 5G Standard — 115 SIMs ($month)", 'quantity' => 1, 'price' => round($total * 0.33, 2)], ], $note, $dryRun); } } echo "\n── Seed complete ──────────────────────────────────────────────────\n"; echo "Verify client IDs in FOSSBilling admin: https://foss.exos.bluenotelogic.com/admin/client\n"; echo "Update fossClientIdForAccount() in agent.php if IDs differ from 17–21.\n\n"; // Print summary $ids = [ 'Greenfield (Order Sherpa)' => $greenfieldId, 'Acme Holdings (parent)' => $acmeHoldingsId, 'Acme Nordics' => $acmeNordicsId, 'Acme UK' => $acmeUkId, 'Acme EMEA' => $acmeEmeaId, ]; foreach ($ids as $label => $id) { echo sprintf(" %-35s → FOSSBilling ID %d\n", $label, $id); } ```
ops/seed-mr-glenn.php return $id; } function seedProduct(string $fossUrl, string $user, string $token, array $data, bool $dryRun): int { if ($dryRun) { echo " [DRY] Would create product: {$data['title']}\n"; return 0; } $result = fossApi($fossUrl, $user, $token, 'product', 'prepare', $data); $id = (int)($result['result'] ?? 0); if ($id <= 0) { echo " [FAIL] Could not create product {$data['title']}: " . json_encode($result) . "\n"; return 0; } echo " [OK] Created product: {$data['title']} → ID $id\n"; return $id; } function seedOrder(string $fossUrl, string $user, string $token, int $clientId, int $productId, array $extra, bool $dryRun): int { if ($dryRun) { echo " [DRY] Would create order for client $clientId / product $productId\n"; return 0; } $payload = array_merge(['client_id' => $clientId, 'product_id' => $productId], $extra); $result = fossApi($fossUrl, $user, $token, 'order', 'create', $payload); $id = (int)($result['result'] ?? 0); if ($id <= 0) { echo " [FAIL] Could not create order: " . json_encode($result) . "\n"; return 0; } echo " [OK] Created order ID $id for client $clientId\n"; return $id; } function seedInvoice(string $fossUrl, string $user, string $token, int $clientId, array $lines, string $note, bool $dryRun): int { if ($dryRun) { echo " [DRY] Would create invoice for client $clientId with " . count($lines) . " lines\n"; return 0; } $result = fossApi($fossUrl, $user, $token, 'invoice', 'create', [ 'client_id' => $clientId, 'note' => $note, 'lines' => $lines, ]); $id = (int)($result['result'] ?? 0); if ($id <= 0) { echo " [FAIL] Could not create invoice: " . json_encode($result) . "\n"; return 0; } echo " [OK] Created invoice ID $id for client $clientId\n"; return $id; } function seedTicket(string $fossUrl, string $user, string $token, int $clientId, int $helpdeskId, array $data, bool $dryRun): int { if ($dryRun) { echo " [DRY] Would create ticket: {$data['subject']}\n"; return 0; } $result = fossApi($fossUrl, $user, $token, 'support', 'ticket_create', array_merge( ['client_id' => $clientId, 'support_helpdesk_id' => $helpdeskId, 'status' => 'open'], $data )); $id = (int)($result['result'] ?? 0); if ($id <= 0) { echo " [FAIL] Could not create ticket: " . json_encode($result) . "\n"; return 0; } // Set priority after creation (FOSSBilling ticket_create may not accept priority) if (!empty($data['priority'])) { fossApi($fossUrl, $user, $token, 'support', 'ticket_update', ['id' => $id, 'priority' => $data['priority'], 'status' => 'open']); } echo " [OK] Created ticket ID $id: {$data['subject']}\n"; return $id; } // ── Get helpdesk ID ──────────────────────────────────────────────────────────── $helpdeskId = $dryRun ? 1 : getHelpdeskId($fossUrl, $user, $token); echo "Using helpdesk ID: $helpdeskId\n\n"; // ══════════════════════════════════════════════════════════════════════════════ // CLIENT 17 — Mr Glenn Greenfield Apartments (Order Sherpa) // ══════════════════════════════════════════════════════════════════════════════ echo "── Client 17: Mr Glenn Greenfield Apartments Management Ltd ──\n"; $greenfieldId = seedClient($fossUrl, $user, $token, [ 'email' => 'mr.glenn.greenfield@exos-demo.bluenotelogic.com', 'password' => 'ExosDemo26!', 'first_name' => 'Mr Glenn', 'last_name' => 'Greenfield', 'company' => 'Mr Glenn Greenfield Apartments Management Ltd', 'type' => 'company', 'address_1' => '45 Greenfield Way, Building B', 'city' => 'Oslo', 'country' => 'NO', 'phone' => '+47 22 55 00 45', 'currency' => 'EUR', 'notes' => "Customer ID: CUST-10045 | MDU – 48 units | Order Sherpa demo account\nOrder ORD-70021 FALLOUT: DOCSIS 3.1 modems unavailable Oslo depot. Bergen Depot has 12. Technician TECH-204 pending assignment. SLA risk: Medium.", ], $dryRun); if ($greenfieldId > 0) { // Seed order with fallout status echo " Seeding MSO order ORD-70021 (in Fallout)...\n"; $gfProductResult = fossApi($fossUrl, $user, $token, 'product', 'prepare', [ 'title' => 'Triple-Play Installation (DOCSIS + Cable TV + Voice)', 'type' => 'custom', 'price' => 0, 'period' => 'monthly', 'is_addon' => 0, 'category_id' => 1, 'description' => 'CUST-10045 MDU 48-unit triple-play: DOCSIS Gigabit Internet, Cable TV Premium, Digital Voice. Ref: ORD-70021.', ]); $gfProductId = (int)($gfProductResult['result'] ?? 0); if ($gfProductId > 0 && !$dryRun) { $ordResult = fossApi($fossUrl, $user, $token, 'order', 'create', [ 'client_id' => $greenfieldId, 'product_id' => $gfProductId, 'price' => 0, 'period' => 'monthly', 'notes' => "ORDER ORD-70021 | New Triple-Play Installation | Ordered: 2026-03-12 | Requested Install: 2026-03-18\nSTATUS: FALLOUT\nFALLOUT CODE: INV-001\nDESCRIPTION: Requested DOCSIS 3.1 modems unavailable in Oslo Central Depot (0 units available). Bergen Depot has 12 units available. Reroute required before field assignment.\nTASKS: 1-Address Validation COMPLETED | 2-Serviceability COMPLETED | 3-Equipment Allocation FAILED | 4-Tech Assignment PENDING | 5-Provisioning NOT STARTED\nJEOPARDY: OPEN — SLA risk MEDIUM — install date 2026-03-18 approaching.", ]); $gfOrderId = (int)($ordResult['result'] ?? 0); echo " [OK] Created order $gfOrderId for Greenfield\n";
ops/seed-mr-glenn.php | Ordered: 2026-03-12 | Requested Install: 2026-03-18\nSTATUS: FALLOUT\nFALLOUT CODE: INV-001\nDESCRIPTION: Requested DOCSIS 3.1 modems unavailable in Oslo Central Depot (0 units available). Bergen Depot has 12 units available. Reroute required before field assignment.\nTASKS: 1-Address Validation COMPLETED | 2-Serviceability COMPLETED | 3-Equipment Allocation FAILED | 4-Tech Assignment PENDING | 5-Provisioning NOT STARTED\nJEOPARDY: OPEN — SLA risk MEDIUM — install date 2026-03-18 approaching.", ]); $gfOrderId = (int)($ordResult['result'] ?? 0); echo " [OK] Created order $gfOrderId for Greenfield\n"; // Set order to suspended/fallout status if ($gfOrderId > 0) { fossApi($fossUrl, $user, $token, 'order', 'update_status', ['id' => $gfOrderId, 'status' => 'suspended']); } } // Seed support ticket echo " Seeding equipment fallout ticket...\n"; seedTicket($fossUrl, $user, $token, $greenfieldId, $helpdeskId, [ 'subject' => '[P2] ORD-70021 Equipment Fallout — DOCSIS 3.1 Modem Shortage Oslo Depot', 'content' => "Order ORD-70021 has entered FALLOUT status.\n\nFALLOUT CODE: INV-001\nAFFECTED SERVICES: Internet, TV\nROOT CAUSE: Requested DOCSIS 3.1 modems unavailable in Oslo Central Depot (0 in stock). Bergen Depot has 12 units available.\n\nRESOLUTION PATH:\n1. Approve reallocation from Bergen Depot to Oslo East\n2. Assign TECH-204 once equipment confirmed en route\n3. Resume network provisioning\n4. Notify customer of revised install date\n\nSLA RISK: Medium — committed install date 2026-03-18 approaching.", 'priority' => 200, ], $dryRun); // Seed a pending invoice echo " Seeding pending invoice...\n"; seedInvoice($fossUrl, $user, $token, $greenfieldId, [ ['title' => 'DOCSIS Gigabit Internet (1 Gbps) — pending activation', 'quantity' => 1, 'price' => 0], ['title' => 'Cable TV Premium Bundle — pending activation', 'quantity' => 1, 'price' => 0], ['title' => 'Digital Voice Line — pending activation', 'quantity' => 1, 'price' => 0], ], 'Order ORD-70021 — held pending equipment allocation resolution', $dryRun); } echo "\n"; // ══════════════════════════════════════════════════════════════════════════════ // CLIENTS 18–21 — Mr Glenn Acme Holdings hierarchy // ══════════════════════════════════════════════════════════════════════════════ echo "── Client 18: Mr Glenn Acme Holdings (parent) ──\n"; $acmeHoldingsId = seedClient($fossUrl, $user, $token, [ 'email' => 'mr.glenn.acme-holdings@exos-demo.bluenotelogic.com', 'password' => 'ExosDemo26!', 'first_name' => 'Mr Glenn', 'last_name' => 'Acme', 'company' => 'Mr Glenn Acme Holdings', 'type' => 'company', 'country' => 'IE', 'currency' => 'EUR', 'notes' => "Enterprise Billing Expert demo — PARENT account\nChild accounts: Acme Nordics (ID 19), Acme UK (ID 20), Acme EMEA (ID 21)\nConsolidated Mar 2026 spend: €22,547 across 3 subsidiaries.", ], $dryRun); echo "\n── Client 19: Mr Glenn Acme Nordics ──\n"; $acmeNordicsId = seedClient($fossUrl, $user, $token, [ 'email' => 'mr.glenn.acme-nordics@exos-demo.bluenotelogic.com', 'password' => 'ExosDemo26!', 'first_name' => 'Mr Glenn', 'last_name' => 'Nordics', 'company' => 'Mr Glenn Acme Nordics', 'type' => 'company', 'country' => 'NO', 'currency' => 'EUR', 'notes' => "Enterprise Billing Expert — Nordics subsidiary of Acme Holdings\nCost Centres: Engineering (100 SIMs), Sales (73 SIMs + roaming)\nContract: 150 min SIMs Jan 2025–Dec 2026 (173 active — above commit)", ], $dryRun); if ($acmeNordicsId > 0) { echo " Seeding Nordics invoices (Jan–Mar 2026)...\n"; $nordicsMonths = [ ['2026-01', 5980, "Jan 2026 — 150 SIMs (Engineering 100 + Sales 50)"], ['2026-02', 6110, "Feb 2026 — 165 SIMs (growing mobile fleet)"], ['2026-03', 6842, "Mar 2026 — 173 SIMs + US roaming spike (Sales 12 SIMs × 12 days)"], ]; foreach ($nordicsMonths as [$month, $total, $note]) { seedInvoice($fossUrl, $user, $token, $acmeNordicsId, [ ['title' => "Mobile 5G Standard Plan — Engineering SIMs ($month)", 'quantity' => 1, 'price' => round($total * 0.41, 2)], ['title' => "Mobile 5G Pro Plan — Sales SIMs ($month)", 'quantity' => 1, 'price' => round($total * 0.48, 2)], ['title' => "Roaming / Overages ($month)", 'quantity' => 1, 'price' => round($total * 0.11, 2)], ], $note, $dryRun); } } echo "\n── Client 20: Mr Glenn Acme UK ──\n"; $acmeUkId = seedClient($fossUrl, $user, $token, [ 'email' => 'mr.glenn.acme-uk@exos-demo.bluenotelogic.com', 'password' => 'ExosDemo26!', 'first_name' => 'Mr Glenn', 'last_name' => 'UK', 'company' => 'Mr Glenn Acme UK', 'type' => 'company', 'country' => 'GB', 'currency' => 'EUR', 'notes' => "Enterprise Billing Expert — UK subsidiary of Acme Holdings\nCost Centre: IT Operations\nANOMALY: 31 unused PBX extensions (€372/mo waste). 1 legacy SIP trunk low-usage.\nContract: 200 PBX seats Mar 2025–Feb 2026 (214 active — 31 UNUSED)", ], $dryRun);
ops/seed-mr-glenn.php Acme UK ──\n"; $acmeUkId = seedClient($fossUrl, $user, $token, [ 'email' => 'mr.glenn.acme-uk@exos-demo.bluenotelogic.com', 'password' => 'ExosDemo26!', 'first_name' => 'Mr Glenn', 'last_name' => 'UK', 'company' => 'Mr Glenn Acme UK', 'type' => 'company', 'country' => 'GB', 'currency' => 'EUR', 'notes' => "Enterprise Billing Expert — UK subsidiary of Acme Holdings\nCost Centre: IT Operations\nANOMALY: 31 unused PBX extensions (€372/mo waste). 1 legacy SIP trunk low-usage.\nContract: 200 PBX seats Mar 2025–Feb 2026 (214 active — 31 UNUSED)", ], $dryRun); if ($acmeUkId > 0) { echo " Seeding UK invoices (Jan–Mar 2026)...\n"; $ukMonths = [ ['2026-01', 5420, "Jan 2026"], ['2026-02', 5488, "Feb 2026"], ['2026-03', 5931, "Mar 2026 — 214 PBX ext (31 unused) + 8 SIP trunks + 85 Mobile Pro SIMs"], ]; foreach ($ukMonths as [$month, $total, $note]) { seedInvoice($fossUrl, $user, $token, $acmeUkId, [ ['title' => "PBX Extension Licences — 214 seats incl 31 unused ($month)", 'quantity' => 1, 'price' => round($total * 0.433, 2)], ['title' => "SIP Trunk — 8 trunks (1 legacy/low-usage) ($month)", 'quantity' => 1, 'price' => round($total * 0.054, 2)], ['title' => "Mobile 5G Pro — 85 SIMs ($month)", 'quantity' => 1, 'price' => round($total * 0.645, 2)], ], $note, $dryRun); } } echo "\n── Client 21: Mr Glenn Acme EMEA ──\n"; $acmeEmeaId = seedClient($fossUrl, $user, $token, [ 'email' => 'mr.glenn.acme-emea@exos-demo.bluenotelogic.com', 'password' => 'ExosDemo26!', 'first_name' => 'Mr Glenn', 'last_name' => 'EMEA', 'company' => 'Mr Glenn Acme EMEA', 'type' => 'company', 'country' => 'DE', 'currency' => 'EUR', 'notes' => "Enterprise Billing Expert — EMEA subsidiary of Acme Holdings\nCost Centre: Human Resources\nBILL SHOCK: Cloud Compute +32% in Mar 2026 (analytics workload). Commitment: €5,000/mo cloud; actual: €6,248.\nContract: €5,000/mo Cloud Compute Jun 2025–May 2026", ], $dryRun); if ($acmeEmeaId > 0) { echo " Seeding EMEA invoices (Jan–Mar 2026)...\n"; $emeaMonths = [ ['2026-01', 6100, "Jan 2026"], ['2026-02', 7230, "Feb 2026 — cloud spend increasing"], ['2026-03', 9774, "Mar 2026 — BILL SHOCK: Cloud Compute +32% (analytics workload) — overage above €5,000 commitment"], ]; foreach ($emeaMonths as [$month, $total, $note]) { $cloudRatio = ($month === '2026-03') ? 0.639 : 0.55; seedInvoice($fossUrl, $user, $token, $acmeEmeaId, [ ['title' => "Cloud Compute — 28,400 hrs (analytics workload) ($month)", 'quantity' => 1, 'price' => round($total * $cloudRatio, 2)], ['title' => "Cloud Storage — 12,800 GB ($month)", 'quantity' => 1, 'price' => round($total * 0.105, 2)], ['title' => "Mobile 5G Standard — 115 SIMs ($month)", 'quantity' => 1, 'price' => round($total * 0.33, 2)], ], $note, $dryRun); } } echo "\n── Seed complete ──────────────────────────────────────────────────\n"; echo "Verify client IDs in FOSSBilling admin: https://foss.exos.bluenotelogic.com/admin/client\n"; echo "Update fossClientIdForAccount() in agent.php if IDs differ from 17–21.\n\n"; // Print summary $ids = [ 'Greenfield (Order Sherpa)' => $greenfieldId, 'Acme Holdings (parent)' => $acmeHoldingsId, 'Acme Nordics' => $acmeNordicsId, 'Acme UK' => $acmeUkId, 'Acme EMEA' => $acmeEmeaId, ]; foreach ($ids as $label => $id) { echo sprintf(" %-35s → FOSSBilling ID %d\n", $label, $id); } ```