Seed DemoWorld business and customer continuity for Woo Manager demos
Deploy Ladill Woo Manager / deploy (push) Successful in 47s
Deploy Ladill Woo Manager / deploy (push) Successful in 47s
Store site names now reference the DemoWorld business identity for the active plan, and order customers use DemoWorld people (name, email, phone) instead of generic "Demo Customer N" placeholders. Staff logins skip the login reseed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,6 +9,7 @@ use App\Models\WooOrder;
|
||||
use App\Models\WooProduct;
|
||||
use App\Models\WooStore;
|
||||
use App\Services\Woo\SubscriptionService;
|
||||
use App\Support\DemoWorld;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -115,25 +116,26 @@ class DemoSeedCommand extends Command
|
||||
private function storeSpecs(string $plan): array
|
||||
{
|
||||
$baseCategories = ['Apparel', 'Accessories', 'Home'];
|
||||
$businessName = DemoWorld::business($plan)['name'];
|
||||
|
||||
return match ($plan) {
|
||||
'enterprise' => [
|
||||
[
|
||||
'site_name' => 'Demo Accra Shop',
|
||||
'site_name' => $businessName.' Shop',
|
||||
'site_url' => 'https://demo-accra.example.com',
|
||||
'products' => 30,
|
||||
'orders' => 12,
|
||||
'categories' => $baseCategories,
|
||||
],
|
||||
[
|
||||
'site_name' => 'Demo Kumasi Shop',
|
||||
'site_name' => $businessName.' — Kumasi Branch Shop',
|
||||
'site_url' => 'https://demo-kumasi.example.com',
|
||||
'products' => 24,
|
||||
'orders' => 8,
|
||||
'categories' => ['Electronics', 'Gadgets'],
|
||||
],
|
||||
[
|
||||
'site_name' => 'Demo Wholesale',
|
||||
'site_name' => $businessName.' Wholesale',
|
||||
'site_url' => 'https://demo-wholesale.example.com',
|
||||
'products' => 20,
|
||||
'orders' => 6,
|
||||
@@ -142,14 +144,14 @@ class DemoSeedCommand extends Command
|
||||
],
|
||||
'pro' => [
|
||||
[
|
||||
'site_name' => 'Demo Pro Shop',
|
||||
'site_name' => $businessName.' Shop',
|
||||
'site_url' => 'https://demo-pro-shop.example.com',
|
||||
'products' => 35,
|
||||
'orders' => 10,
|
||||
'categories' => $baseCategories,
|
||||
],
|
||||
[
|
||||
'site_name' => 'Demo Outlet',
|
||||
'site_name' => $businessName.' Outlet',
|
||||
'site_url' => 'https://demo-outlet.example.com',
|
||||
'products' => 15,
|
||||
'orders' => 5,
|
||||
@@ -158,7 +160,7 @@ class DemoSeedCommand extends Command
|
||||
],
|
||||
default => [
|
||||
[
|
||||
'site_name' => 'Demo Free Shop',
|
||||
'site_name' => $businessName.' Shop',
|
||||
'site_url' => 'https://demo-free-shop.example.com',
|
||||
'products' => 15,
|
||||
'orders' => 4,
|
||||
@@ -225,10 +227,13 @@ class DemoSeedCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
$people = DemoWorld::people();
|
||||
|
||||
for ($i = 1; $i <= $spec['orders']; $i++) {
|
||||
$product = $products[($i - 1) % count($products)];
|
||||
$qty = 1 + ($i % 3);
|
||||
$lineTotal = (int) $product->regular_price_minor * $qty;
|
||||
$customer = $people[($i - 1) % count($people)];
|
||||
|
||||
WooOrder::query()->create([
|
||||
'woo_store_id' => $store->id,
|
||||
@@ -236,9 +241,9 @@ class DemoSeedCommand extends Command
|
||||
'order_number' => (string) (1000 + $i),
|
||||
'wc_status' => $i % 4 === 0 ? 'completed' : 'processing',
|
||||
'payment_status' => 'paid',
|
||||
'customer_name' => 'Demo Customer '.$i,
|
||||
'customer_email' => 'customer'.$i.'@example.com',
|
||||
'customer_phone' => '+233200000'.str_pad((string) $i, 3, '0', STR_PAD_LEFT),
|
||||
'customer_name' => DemoWorld::fullName($customer),
|
||||
'customer_email' => $customer['email'],
|
||||
'customer_phone' => $customer['phone'],
|
||||
'line_items' => [[
|
||||
'id' => $product->external_id,
|
||||
'name' => $product->name,
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
namespace App\Services\Woo;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Support\DemoWorld;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Reseed Woo Manager demo tenants after a fresh SSO login.
|
||||
*
|
||||
* Only owner demo accounts trigger a full reseed. Staff training logins
|
||||
* share the owner tenant but never wipe mid-demo.
|
||||
*/
|
||||
class DemoLoginReseeder
|
||||
{
|
||||
@@ -19,6 +23,10 @@ class DemoLoginReseeder
|
||||
return;
|
||||
}
|
||||
|
||||
if (DemoWorld::isStaffDemoEmail($user->email)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plan = $this->planForEmail($user->email);
|
||||
if ($plan === null) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,553 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
/**
|
||||
* Canonical shared demo world for the Ladill suite.
|
||||
*
|
||||
* Single source of truth for business identity, people, companies, branches,
|
||||
* and staff rosters used across Care, Queue, Frontdesk, CRM, Invoice,
|
||||
* Accounting, POS, Meet, Events, and Woo. Suite apps copy this file via
|
||||
* `php scripts/sync-demo-world.php` — keep copies byte-identical.
|
||||
*
|
||||
* Continuity is by matching identifiers (phone, email, national_id, company
|
||||
* name), not cross-database foreign keys.
|
||||
*
|
||||
* Reseed policy: only owner demo emails (`demo-{tier}@ladill.com`) trigger
|
||||
* full product reseed on login. Staff logins never wipe mid-demo.
|
||||
*/
|
||||
final class DemoWorld
|
||||
{
|
||||
public const PASSWORD_ENV = 'DEMO_ACCOUNT_PASSWORD';
|
||||
|
||||
public const DEFAULT_PASSWORD = 'DemoLadill2026!';
|
||||
|
||||
public const TIMEZONE = 'Africa/Accra';
|
||||
|
||||
public const CURRENCY = 'GHS';
|
||||
|
||||
/** Anchor “today” for same-day appointments / tickets / visits (relative). */
|
||||
public const STORY_DAY_OFFSET = 0;
|
||||
|
||||
/**
|
||||
* @var array<string, array{
|
||||
* name: string,
|
||||
* legal_name: string,
|
||||
* phone: string,
|
||||
* email: string,
|
||||
* address: string,
|
||||
* city: string,
|
||||
* region: string,
|
||||
* timezone: string
|
||||
* }>
|
||||
*/
|
||||
public const BUSINESS = [
|
||||
'free' => [
|
||||
'name' => 'Ridge Family Clinic',
|
||||
'legal_name' => 'Ridge Family Clinic Ltd',
|
||||
'phone' => '+233302400100',
|
||||
'email' => 'hello@ridgeclinic.demo.ladill.com',
|
||||
'address' => '12 Castle Road, Ridge, Accra',
|
||||
'city' => 'Accra',
|
||||
'region' => 'Greater Accra',
|
||||
'timezone' => self::TIMEZONE,
|
||||
],
|
||||
'pro' => [
|
||||
'name' => 'Ridge Medical Centre',
|
||||
'legal_name' => 'Ridge Medical Centre Ltd',
|
||||
'phone' => '+233302400200',
|
||||
'email' => 'hello@ridgemedical.demo.ladill.com',
|
||||
'address' => '45 Independence Ave, Ridge, Accra',
|
||||
'city' => 'Accra',
|
||||
'region' => 'Greater Accra',
|
||||
'timezone' => self::TIMEZONE,
|
||||
],
|
||||
'enterprise' => [
|
||||
'name' => 'Accra Healthcare Group',
|
||||
'legal_name' => 'Accra Healthcare Group Ltd',
|
||||
'phone' => '+233302400300',
|
||||
'email' => 'hello@accrahealth.demo.ladill.com',
|
||||
'address' => '1 Liberation Road, Airport Residential, Accra',
|
||||
'city' => 'Accra',
|
||||
'region' => 'Greater Accra',
|
||||
'timezone' => self::TIMEZONE,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Shared branch catalog. Plans take the first N (free=1, pro=3, enterprise=6).
|
||||
*
|
||||
* @var list<array{code: string, name: string, phone: string, address: string}>
|
||||
*/
|
||||
public const BRANCHES = [
|
||||
['code' => 'HQ', 'name' => 'Ridge Clinic', 'phone' => '+233302400101', 'address' => '12 Castle Road, Ridge, Accra'],
|
||||
['code' => 'EL', 'name' => 'East Legon Care', 'phone' => '+233302400102', 'address' => '18 Boundary Rd, East Legon, Accra'],
|
||||
['code' => 'TM', 'name' => 'Tema Harbour Clinic', 'phone' => '+233302400103', 'address' => 'Harbour Road, Community 1, Tema'],
|
||||
['code' => 'OS', 'name' => 'Osu Medical Centre', 'phone' => '+233302400104', 'address' => 'Oxford Street, Osu, Accra'],
|
||||
['code' => 'SX', 'name' => 'Spintex Wellness', 'phone' => '+233302400105', 'address' => 'Spintex Road, Accra'],
|
||||
['code' => 'AP', 'name' => 'Airport Residential Care', 'phone' => '+233302400106', 'address' => 'Liberation Rd, Airport Residential, Accra'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Canonical people reused as patients / visitors / ticket guests / CRM
|
||||
* contacts / invoice clients / POS customers / event attendees.
|
||||
*
|
||||
* @var list<array{
|
||||
* key: string,
|
||||
* first_name: string,
|
||||
* last_name: string,
|
||||
* phone: string,
|
||||
* email: string,
|
||||
* national_id: string,
|
||||
* gender: string,
|
||||
* dob: string,
|
||||
* company_key: string,
|
||||
* city: string,
|
||||
* address: string
|
||||
* }>
|
||||
*/
|
||||
public const PEOPLE = [
|
||||
['key' => 'ama-mensah', 'first_name' => 'Ama', 'last_name' => 'Mensah', 'phone' => '+233241000001', 'email' => 'ama.mensah@demo.ladill.com', 'national_id' => 'GHA-729001001', 'gender' => 'female', 'dob' => '1990-03-12', 'company_key' => 'accra-retail', 'city' => 'Accra', 'address' => '14 Ring Road Central, Accra'],
|
||||
['key' => 'kofi-asante', 'first_name' => 'Kofi', 'last_name' => 'Asante', 'phone' => '+233241000002', 'email' => 'kofi.asante@demo.ladill.com', 'national_id' => 'GHA-729001002', 'gender' => 'male', 'dob' => '1985-07-21', 'company_key' => 'kumasi-foods', 'city' => 'Accra', 'address' => '3 Labone Crescent, Accra'],
|
||||
['key' => 'abena-osei', 'first_name' => 'Abena', 'last_name' => 'Osei', 'phone' => '+233241000003', 'email' => 'abena.osei@demo.ladill.com', 'national_id' => 'GHA-729001003', 'gender' => 'female', 'dob' => '1992-11-05', 'company_key' => 'tema-logistics', 'city' => 'Tema', 'address' => 'Community 4, Tema'],
|
||||
['key' => 'kwame-boateng', 'first_name' => 'Kwame', 'last_name' => 'Boateng', 'phone' => '+233241000004', 'email' => 'kwame.boateng@demo.ladill.com', 'national_id' => 'GHA-729001004', 'gender' => 'male', 'dob' => '1978-01-30', 'company_key' => 'cape-coast-tours', 'city' => 'Accra', 'address' => '8 Cantonments Road, Accra'],
|
||||
['key' => 'akosua-owusu', 'first_name' => 'Akosua', 'last_name' => 'Owusu', 'phone' => '+233241000005', 'email' => 'akosua.owusu@demo.ladill.com', 'national_id' => 'GHA-729001005', 'gender' => 'female', 'dob' => '1995-06-18', 'company_key' => 'tamale-agro', 'city' => 'Accra', 'address' => '22 Dansoman Estates, Accra'],
|
||||
['key' => 'yaw-adjei', 'first_name' => 'Yaw', 'last_name' => 'Adjei', 'phone' => '+233241000006', 'email' => 'yaw.adjei@demo.ladill.com', 'national_id' => 'GHA-729001006', 'gender' => 'male', 'dob' => '1988-09-09', 'company_key' => 'spintex-clinics', 'city' => 'Accra', 'address' => 'Spintex Junction, Accra'],
|
||||
['key' => 'efua-appiah', 'first_name' => 'Efua', 'last_name' => 'Appiah', 'phone' => '+233241000007', 'email' => 'efua.appiah@demo.ladill.com', 'national_id' => 'GHA-729001007', 'gender' => 'female', 'dob' => '1993-02-14', 'company_key' => 'osu-cafe', 'city' => 'Accra', 'address' => 'Oxford Street, Osu, Accra'],
|
||||
['key' => 'kojo-darko', 'first_name' => 'Kojo', 'last_name' => 'Darko', 'phone' => '+233241000008', 'email' => 'kojo.darko@demo.ladill.com', 'national_id' => 'GHA-729001008', 'gender' => 'male', 'dob' => '1982-12-01', 'company_key' => 'legon-tech', 'city' => 'Accra', 'address' => 'University Area, Legon'],
|
||||
['key' => 'adwoa-agyeman', 'first_name' => 'Adwoa', 'last_name' => 'Agyeman', 'phone' => '+233241000009', 'email' => 'adwoa.agyeman@demo.ladill.com', 'national_id' => 'GHA-729001009', 'gender' => 'female', 'dob' => '1991-04-22', 'company_key' => 'airport-hotels', 'city' => 'Accra', 'address' => 'Airport Residential, Accra'],
|
||||
['key' => 'kwesi-sarpong', 'first_name' => 'Kwesi', 'last_name' => 'Sarpong', 'phone' => '+233241000010', 'email' => 'kwesi.sarpong@demo.ladill.com', 'national_id' => 'GHA-729001010', 'gender' => 'male', 'dob' => '1987-08-16', 'company_key' => 'kaneshie-traders', 'city' => 'Accra', 'address' => 'Kaneshie Market, Accra'],
|
||||
['key' => 'afia-nyarko', 'first_name' => 'Afia', 'last_name' => 'Nyarko', 'phone' => '+233241000011', 'email' => 'afia.nyarko@demo.ladill.com', 'national_id' => 'GHA-729001011', 'gender' => 'female', 'dob' => '1994-10-03', 'company_key' => 'accra-retail', 'city' => 'Accra', 'address' => 'Adabraka, Accra'],
|
||||
['key' => 'kwadwo-ankrah', 'first_name' => 'Kwadwo', 'last_name' => 'Ankrah', 'phone' => '+233241000012', 'email' => 'kwadwo.ankrah@demo.ladill.com', 'national_id' => 'GHA-729001012', 'gender' => 'male', 'dob' => '1980-05-27', 'company_key' => 'kumasi-foods', 'city' => 'Accra', 'address' => 'Mamprobi, Accra'],
|
||||
['key' => 'esi-tetteh', 'first_name' => 'Esi', 'last_name' => 'Tetteh', 'phone' => '+233241000013', 'email' => 'esi.tetteh@demo.ladill.com', 'national_id' => 'GHA-729001013', 'gender' => 'female', 'dob' => '1996-01-19', 'company_key' => 'tema-logistics', 'city' => 'Tema', 'address' => 'Community 8, Tema'],
|
||||
['key' => 'fiifi-quaye', 'first_name' => 'Fiifi', 'last_name' => 'Quaye', 'phone' => '+233241000014', 'email' => 'fiifi.quaye@demo.ladill.com', 'national_id' => 'GHA-729001014', 'gender' => 'male', 'dob' => '1989-07-07', 'company_key' => 'cape-coast-tours', 'city' => 'Accra', 'address' => 'Tesano, Accra'],
|
||||
['key' => 'mansa-addo', 'first_name' => 'Mansa', 'last_name' => 'Addo', 'phone' => '+233241000015', 'email' => 'mansa.addo@demo.ladill.com', 'national_id' => 'GHA-729001015', 'gender' => 'female', 'dob' => '1993-09-25', 'company_key' => 'tamale-agro', 'city' => 'Accra', 'address' => 'Abelemkpe, Accra'],
|
||||
['key' => 'nana-frimpong', 'first_name' => 'Nana', 'last_name' => 'Frimpong', 'phone' => '+233241000016', 'email' => 'nana.frimpong@demo.ladill.com', 'national_id' => 'GHA-729001016', 'gender' => 'male', 'dob' => '1984-03-11', 'company_key' => 'spintex-clinics', 'city' => 'Accra', 'address' => 'Teshie, Accra'],
|
||||
['key' => 'ama-boateng', 'first_name' => 'Ama', 'last_name' => 'Boateng', 'phone' => '+233241000017', 'email' => 'ama.boateng@demo.ladill.com', 'national_id' => 'GHA-729001017', 'gender' => 'female', 'dob' => '1997-12-08', 'company_key' => 'osu-cafe', 'city' => 'Accra', 'address' => 'Kokomlemle, Accra'],
|
||||
['key' => 'kofi-owusu', 'first_name' => 'Kofi', 'last_name' => 'Owusu', 'phone' => '+233241000018', 'email' => 'kofi.owusu@demo.ladill.com', 'national_id' => 'GHA-729001018', 'gender' => 'male', 'dob' => '1986-06-02', 'company_key' => 'legon-tech', 'city' => 'Accra', 'address' => 'Haatso, Accra'],
|
||||
['key' => 'abena-darko', 'first_name' => 'Abena', 'last_name' => 'Darko', 'phone' => '+233241000019', 'email' => 'abena.darko@demo.ladill.com', 'national_id' => 'GHA-729001019', 'gender' => 'female', 'dob' => '1990-08-29', 'company_key' => 'airport-hotels', 'city' => 'Accra', 'address' => 'Dzorwulu, Accra'],
|
||||
['key' => 'kwame-mensah', 'first_name' => 'Kwame', 'last_name' => 'Mensah', 'phone' => '+233241000020', 'email' => 'kwame.mensah@demo.ladill.com', 'national_id' => 'GHA-729001020', 'gender' => 'male', 'dob' => '1979-11-13', 'company_key' => 'kaneshie-traders', 'city' => 'Accra', 'address' => 'Ablekuma, Accra'],
|
||||
['key' => 'akosua-asante', 'first_name' => 'Akosua', 'last_name' => 'Asante', 'phone' => '+233241000021', 'email' => 'akosua.asante@demo.ladill.com', 'national_id' => 'GHA-729001021', 'gender' => 'female', 'dob' => '1992-02-20', 'company_key' => 'accra-retail', 'city' => 'Accra', 'address' => 'Roman Ridge, Accra'],
|
||||
['key' => 'yaw-osei', 'first_name' => 'Yaw', 'last_name' => 'Osei', 'phone' => '+233241000022', 'email' => 'yaw.osei@demo.ladill.com', 'national_id' => 'GHA-729001022', 'gender' => 'male', 'dob' => '1983-04-15', 'company_key' => 'kumasi-foods', 'city' => 'Accra', 'address' => 'Nungua, Accra'],
|
||||
['key' => 'efua-boateng', 'first_name' => 'Efua', 'last_name' => 'Boateng', 'phone' => '+233241000023', 'email' => 'efua.boateng@demo.ladill.com', 'national_id' => 'GHA-729001023', 'gender' => 'female', 'dob' => '1995-07-01', 'company_key' => 'tema-logistics', 'city' => 'Tema', 'address' => 'Community 2, Tema'],
|
||||
['key' => 'kojo-owusu', 'first_name' => 'Kojo', 'last_name' => 'Owusu', 'phone' => '+233241000024', 'email' => 'kojo.owusu@demo.ladill.com', 'national_id' => 'GHA-729001024', 'gender' => 'male', 'dob' => '1981-10-28', 'company_key' => 'cape-coast-tours', 'city' => 'Accra', 'address' => 'Madina, Accra'],
|
||||
['key' => 'adwoa-adjei', 'first_name' => 'Adwoa', 'last_name' => 'Adjei', 'phone' => '+233241000025', 'email' => 'adwoa.adjei@demo.ladill.com', 'national_id' => 'GHA-729001025', 'gender' => 'female', 'dob' => '1994-05-06', 'company_key' => 'tamale-agro', 'city' => 'Accra', 'address' => 'Ashaiman, Accra'],
|
||||
['key' => 'kwesi-appiah', 'first_name' => 'Kwesi', 'last_name' => 'Appiah', 'phone' => '+233241000026', 'email' => 'kwesi.appiah@demo.ladill.com', 'national_id' => 'GHA-729001026', 'gender' => 'male', 'dob' => '1988-12-17', 'company_key' => 'spintex-clinics', 'city' => 'Accra', 'address' => 'Baatsona, Accra'],
|
||||
['key' => 'afia-darko', 'first_name' => 'Afia', 'last_name' => 'Darko', 'phone' => '+233241000027', 'email' => 'afia.darko@demo.ladill.com', 'national_id' => 'GHA-729001027', 'gender' => 'female', 'dob' => '1991-09-23', 'company_key' => 'osu-cafe', 'city' => 'Accra', 'address' => 'Labadi, Accra'],
|
||||
['key' => 'kwadwo-agyeman', 'first_name' => 'Kwadwo', 'last_name' => 'Agyeman', 'phone' => '+233241000028', 'email' => 'kwadwo.agyeman@demo.ladill.com', 'national_id' => 'GHA-729001028', 'gender' => 'male', 'dob' => '1985-01-04', 'company_key' => 'legon-tech', 'city' => 'Accra', 'address' => 'Kwabenya, Accra'],
|
||||
['key' => 'esi-sarpong', 'first_name' => 'Esi', 'last_name' => 'Sarpong', 'phone' => '+233241000029', 'email' => 'esi.sarpong@demo.ladill.com', 'national_id' => 'GHA-729001029', 'gender' => 'female', 'dob' => '1996-03-30', 'company_key' => 'airport-hotels', 'city' => 'Accra', 'address' => 'East Legon Hills, Accra'],
|
||||
['key' => 'fiifi-nyarko', 'first_name' => 'Fiifi', 'last_name' => 'Nyarko', 'phone' => '+233241000030', 'email' => 'fiifi.nyarko@demo.ladill.com', 'national_id' => 'GHA-729001030', 'gender' => 'male', 'dob' => '1987-06-14', 'company_key' => 'kaneshie-traders', 'city' => 'Accra', 'address' => 'Odorkor, Accra'],
|
||||
['key' => 'mansa-ankrah', 'first_name' => 'Mansa', 'last_name' => 'Ankrah', 'phone' => '+233241000031', 'email' => 'mansa.ankrah@demo.ladill.com', 'national_id' => 'GHA-729001031', 'gender' => 'female', 'dob' => '1993-11-11', 'company_key' => 'accra-retail', 'city' => 'Accra', 'address' => 'Korle Bu, Accra'],
|
||||
['key' => 'nana-tetteh', 'first_name' => 'Nana', 'last_name' => 'Tetteh', 'phone' => '+233241000032', 'email' => 'nana.tetteh@demo.ladill.com', 'national_id' => 'GHA-729001032', 'gender' => 'male', 'dob' => '1982-08-08', 'company_key' => 'kumasi-foods', 'city' => 'Accra', 'address' => 'Weija, Accra'],
|
||||
['key' => 'ama-quaye', 'first_name' => 'Ama', 'last_name' => 'Quaye', 'phone' => '+233241000033', 'email' => 'ama.quaye@demo.ladill.com', 'national_id' => 'GHA-729001033', 'gender' => 'female', 'dob' => '1990-04-04', 'company_key' => 'tema-logistics', 'city' => 'Tema', 'address' => 'Community 11, Tema'],
|
||||
['key' => 'kofi-addo', 'first_name' => 'Kofi', 'last_name' => 'Addo', 'phone' => '+233241000034', 'email' => 'kofi.addo@demo.ladill.com', 'national_id' => 'GHA-729001034', 'gender' => 'male', 'dob' => '1989-02-26', 'company_key' => 'cape-coast-tours', 'city' => 'Accra', 'address' => 'Kasoa Road, Accra'],
|
||||
['key' => 'abena-frimpong', 'first_name' => 'Abena', 'last_name' => 'Frimpong', 'phone' => '+233241000035', 'email' => 'abena.frimpong@demo.ladill.com', 'national_id' => 'GHA-729001035', 'gender' => 'female', 'dob' => '1994-07-19', 'company_key' => 'tamale-agro', 'city' => 'Accra', 'address' => 'Achimota, Accra'],
|
||||
['key' => 'kwame-agyeman', 'first_name' => 'Kwame', 'last_name' => 'Agyeman', 'phone' => '+233241000036', 'email' => 'kwame.agyeman@demo.ladill.com', 'national_id' => 'GHA-729001036', 'gender' => 'male', 'dob' => '1980-09-01', 'company_key' => 'spintex-clinics', 'city' => 'Accra', 'address' => 'Sakumono, Accra'],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var list<array{key: string, name: string, email: string, phone: string, address: string}>
|
||||
*/
|
||||
public const COMPANIES = [
|
||||
['key' => 'accra-retail', 'name' => 'Accra Retail Ltd', 'email' => 'accounts@accra-retail.demo.ladill.com', 'phone' => '+233302500001', 'address' => 'Oxford Street, Accra'],
|
||||
['key' => 'kumasi-foods', 'name' => 'Kumasi Foods', 'email' => 'billing@kumasi-foods.demo.ladill.com', 'phone' => '+233302500002', 'address' => 'Kejetia Road, Kumasi'],
|
||||
['key' => 'tema-logistics', 'name' => 'Tema Logistics', 'email' => 'finance@tema-logistics.demo.ladill.com', 'phone' => '+233302500003', 'address' => 'Harbour Road, Tema'],
|
||||
['key' => 'cape-coast-tours', 'name' => 'Cape Coast Tours', 'email' => 'pay@cape-coast-tours.demo.ladill.com', 'phone' => '+233302500004', 'address' => 'Castle Road, Cape Coast'],
|
||||
['key' => 'tamale-agro', 'name' => 'Tamale Agro', 'email' => 'accounts@tamale-agro.demo.ladill.com', 'phone' => '+233302500005', 'address' => 'Salaga Road, Tamale'],
|
||||
['key' => 'spintex-clinics', 'name' => 'Spintex Clinics', 'email' => 'billing@spintex-clinics.demo.ladill.com', 'phone' => '+233302500006', 'address' => 'Spintex Road, Accra'],
|
||||
['key' => 'osu-cafe', 'name' => 'Osu Café Co', 'email' => 'hello@osu-cafe.demo.ladill.com', 'phone' => '+233302500007', 'address' => 'Oxford Street, Osu'],
|
||||
['key' => 'legon-tech', 'name' => 'Legon Tech', 'email' => 'ap@legon-tech.demo.ladill.com', 'phone' => '+233302500008', 'address' => 'University Area, Legon'],
|
||||
['key' => 'airport-hotels', 'name' => 'Airport City Hotels', 'email' => 'finance@airport-hotels.demo.ladill.com', 'phone' => '+233302500009', 'address' => 'Airport City, Accra'],
|
||||
['key' => 'kaneshie-traders', 'name' => 'Kaneshie Market Traders', 'email' => 'pay@kaneshie-traders.demo.ladill.com', 'phone' => '+233302500010', 'address' => 'Kaneshie Market, Accra'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Plausible shared story amounts (GHS major units) for Accounting / Invoice / POS labels.
|
||||
*
|
||||
* @var array{care_consult: int, care_lab: int, invoice_typical: int, pos_typical: int, crm_deal: int}
|
||||
*/
|
||||
public const STORY_AMOUNTS_GHS = [
|
||||
'care_consult' => 250,
|
||||
'care_lab' => 180,
|
||||
'invoice_typical' => 1500,
|
||||
'pos_typical' => 85,
|
||||
'crm_deal' => 12000,
|
||||
];
|
||||
|
||||
/**
|
||||
* Staff roster keyed by tier. Free: owner + receptionist. Pro/Enterprise: full roster.
|
||||
*
|
||||
* @var array<string, list<array{
|
||||
* key: string,
|
||||
* email: string,
|
||||
* name: string,
|
||||
* apps: list<string>,
|
||||
* roles: array<string, string>,
|
||||
* account_role?: string
|
||||
* }>>
|
||||
*/
|
||||
public const STAFF = [
|
||||
'free' => [
|
||||
[
|
||||
'key' => 'receptionist',
|
||||
'email' => 'demo-free-receptionist@ladill.com',
|
||||
'name' => 'Abena Reception (Free)',
|
||||
'apps' => ['care', 'frontdesk', 'queue'],
|
||||
'roles' => [
|
||||
'care' => 'receptionist',
|
||||
'frontdesk' => 'receptionist',
|
||||
'queue' => 'receptionist',
|
||||
],
|
||||
],
|
||||
],
|
||||
'pro' => [
|
||||
[
|
||||
'key' => 'doctor',
|
||||
'email' => 'demo-pro-doctor@ladill.com',
|
||||
'name' => 'Dr. Kwame Mensah (Pro)',
|
||||
'apps' => ['care'],
|
||||
'roles' => ['care' => 'doctor'],
|
||||
],
|
||||
[
|
||||
'key' => 'nurse',
|
||||
'email' => 'demo-pro-nurse@ladill.com',
|
||||
'name' => 'Akosua Nurse (Pro)',
|
||||
'apps' => ['care'],
|
||||
'roles' => ['care' => 'nurse'],
|
||||
],
|
||||
[
|
||||
'key' => 'receptionist',
|
||||
'email' => 'demo-pro-receptionist@ladill.com',
|
||||
'name' => 'Abena Reception (Pro)',
|
||||
'apps' => ['care', 'frontdesk', 'queue'],
|
||||
'roles' => [
|
||||
'care' => 'receptionist',
|
||||
'frontdesk' => 'receptionist',
|
||||
'queue' => 'receptionist',
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'pharmacist',
|
||||
'email' => 'demo-pro-pharmacist@ladill.com',
|
||||
'name' => 'Yaw Pharmacist (Pro)',
|
||||
'apps' => ['care'],
|
||||
'roles' => ['care' => 'pharmacist'],
|
||||
],
|
||||
[
|
||||
'key' => 'lab',
|
||||
'email' => 'demo-pro-lab@ladill.com',
|
||||
'name' => 'Efua Lab Tech (Pro)',
|
||||
'apps' => ['care'],
|
||||
'roles' => ['care' => 'lab_technician'],
|
||||
],
|
||||
[
|
||||
'key' => 'cashier',
|
||||
'email' => 'demo-pro-cashier@ladill.com',
|
||||
'name' => 'Kojo Cashier (Pro)',
|
||||
'apps' => ['care', 'pos'],
|
||||
'roles' => [
|
||||
'care' => 'cashier',
|
||||
'pos' => 'cashier',
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'accountant',
|
||||
'email' => 'demo-pro-accountant@ladill.com',
|
||||
'name' => 'Adwoa Accountant (Pro)',
|
||||
'apps' => ['accounting', 'invoice'],
|
||||
'roles' => [
|
||||
'accounting' => 'member',
|
||||
'invoice' => 'member',
|
||||
'care' => 'accountant',
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'sales',
|
||||
'email' => 'demo-pro-sales@ladill.com',
|
||||
'name' => 'Kofi Sales (Pro)',
|
||||
'apps' => ['crm', 'invoice', 'meet'],
|
||||
'roles' => [
|
||||
'crm' => 'member',
|
||||
'invoice' => 'member',
|
||||
'meet' => 'member',
|
||||
],
|
||||
],
|
||||
],
|
||||
'enterprise' => [
|
||||
[
|
||||
'key' => 'doctor',
|
||||
'email' => 'demo-enterprise-doctor@ladill.com',
|
||||
'name' => 'Dr. Kwame Mensah',
|
||||
'apps' => ['care'],
|
||||
'roles' => ['care' => 'doctor'],
|
||||
],
|
||||
[
|
||||
'key' => 'nurse',
|
||||
'email' => 'demo-enterprise-nurse@ladill.com',
|
||||
'name' => 'Akosua Nurse',
|
||||
'apps' => ['care'],
|
||||
'roles' => ['care' => 'nurse'],
|
||||
],
|
||||
[
|
||||
'key' => 'receptionist',
|
||||
'email' => 'demo-enterprise-receptionist@ladill.com',
|
||||
'name' => 'Abena Reception',
|
||||
'apps' => ['care', 'frontdesk', 'queue'],
|
||||
'roles' => [
|
||||
'care' => 'receptionist',
|
||||
'frontdesk' => 'receptionist',
|
||||
'queue' => 'receptionist',
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'pharmacist',
|
||||
'email' => 'demo-enterprise-pharmacist@ladill.com',
|
||||
'name' => 'Yaw Pharmacist',
|
||||
'apps' => ['care'],
|
||||
'roles' => ['care' => 'pharmacist'],
|
||||
],
|
||||
[
|
||||
'key' => 'lab',
|
||||
'email' => 'demo-enterprise-lab@ladill.com',
|
||||
'name' => 'Efua Lab Tech',
|
||||
'apps' => ['care'],
|
||||
'roles' => ['care' => 'lab_technician'],
|
||||
],
|
||||
[
|
||||
'key' => 'cashier',
|
||||
'email' => 'demo-enterprise-cashier@ladill.com',
|
||||
'name' => 'Kojo Cashier',
|
||||
'apps' => ['care', 'pos'],
|
||||
'roles' => [
|
||||
'care' => 'cashier',
|
||||
'pos' => 'cashier',
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'accountant',
|
||||
'email' => 'demo-enterprise-accountant@ladill.com',
|
||||
'name' => 'Adwoa Accountant',
|
||||
'apps' => ['accounting', 'invoice', 'care'],
|
||||
'roles' => [
|
||||
'accounting' => 'member',
|
||||
'invoice' => 'member',
|
||||
'care' => 'accountant',
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'sales',
|
||||
'email' => 'demo-enterprise-sales@ladill.com',
|
||||
'name' => 'Kofi Sales',
|
||||
'apps' => ['crm', 'invoice', 'meet', 'events'],
|
||||
'roles' => [
|
||||
'crm' => 'member',
|
||||
'invoice' => 'member',
|
||||
'meet' => 'member',
|
||||
'events' => 'member',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/** @var array<string, string> owner email → tier */
|
||||
public const OWNER_EMAILS = [
|
||||
'demo-free@ladill.com' => 'free',
|
||||
'demo-pro@ladill.com' => 'pro',
|
||||
'demo-enterprise@ladill.com' => 'enterprise',
|
||||
];
|
||||
|
||||
public static function normalizeTier(string $tier): string
|
||||
{
|
||||
$tier = strtolower(trim($tier));
|
||||
|
||||
return in_array($tier, ['free', 'pro', 'enterprise'], true) ? $tier : 'free';
|
||||
}
|
||||
|
||||
public static function ownerEmailForTier(string $tier): string
|
||||
{
|
||||
$tier = self::normalizeTier($tier);
|
||||
|
||||
return match ($tier) {
|
||||
'pro' => 'demo-pro@ladill.com',
|
||||
'enterprise' => 'demo-enterprise@ladill.com',
|
||||
default => 'demo-free@ladill.com',
|
||||
};
|
||||
}
|
||||
|
||||
public static function tierForOwnerEmail(?string $email): ?string
|
||||
{
|
||||
$email = strtolower(trim((string) $email));
|
||||
|
||||
return self::OWNER_EMAILS[$email] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{name: string, legal_name: string, phone: string, email: string, address: string, city: string, region: string, timezone: string}
|
||||
*/
|
||||
public static function business(string $tier): array
|
||||
{
|
||||
return self::BUSINESS[self::normalizeTier($tier)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{code: string, name: string, phone: string, address: string}>
|
||||
*/
|
||||
public static function branches(string $tier, ?int $limit = null): array
|
||||
{
|
||||
$tier = self::normalizeTier($tier);
|
||||
$limit ??= match ($tier) {
|
||||
'enterprise' => 6,
|
||||
'pro' => 3,
|
||||
default => 1,
|
||||
};
|
||||
|
||||
return array_slice(self::BRANCHES, 0, max(1, $limit));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{key: string, first_name: string, last_name: string, phone: string, email: string, national_id: string, gender: string, dob: string, company_key: string, city: string, address: string}>
|
||||
*/
|
||||
public static function people(?int $limit = null): array
|
||||
{
|
||||
if ($limit === null) {
|
||||
return self::PEOPLE;
|
||||
}
|
||||
|
||||
return array_slice(self::PEOPLE, 0, max(0, $limit));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{key: string, first_name: string, last_name: string, phone: string, email: string, national_id: string, gender: string, dob: string, company_key: string, city: string, address: string}|null
|
||||
*/
|
||||
public static function personByKey(string $key): ?array
|
||||
{
|
||||
foreach (self::PEOPLE as $person) {
|
||||
if ($person['key'] === $key) {
|
||||
return $person;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function fullName(array $person): string
|
||||
{
|
||||
return trim($person['first_name'].' '.$person['last_name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{key: string, name: string, email: string, phone: string, address: string}>
|
||||
*/
|
||||
public static function companies(?int $limit = null): array
|
||||
{
|
||||
if ($limit === null) {
|
||||
return self::COMPANIES;
|
||||
}
|
||||
|
||||
return array_slice(self::COMPANIES, 0, max(0, $limit));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{key: string, name: string, email: string, phone: string, address: string}|null
|
||||
*/
|
||||
public static function companyByKey(string $key): ?array
|
||||
{
|
||||
foreach (self::COMPANIES as $company) {
|
||||
if ($company['key'] === $key) {
|
||||
return $company;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function companyNameForPerson(array $person): string
|
||||
{
|
||||
return self::companyByKey($person['company_key'])['name'] ?? 'Demo Co';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{key: string, email: string, name: string, apps: list<string>, roles: array<string, string>, account_role?: string}>
|
||||
*/
|
||||
public static function staff(string $tier): array
|
||||
{
|
||||
return self::STAFF[self::normalizeTier($tier)] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{key: string, email: string, name: string, apps: list<string>, roles: array<string, string>, account_role?: string}|null
|
||||
*/
|
||||
public static function staffByEmail(?string $email): ?array
|
||||
{
|
||||
$email = strtolower(trim((string) $email));
|
||||
if ($email === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (self::STAFF as $roster) {
|
||||
foreach ($roster as $member) {
|
||||
if (strtolower($member['email']) === $email) {
|
||||
return $member;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function isOwnerDemoEmail(?string $email): bool
|
||||
{
|
||||
return self::tierForOwnerEmail($email) !== null;
|
||||
}
|
||||
|
||||
public static function isStaffDemoEmail(?string $email): bool
|
||||
{
|
||||
return self::staffByEmail($email) !== null;
|
||||
}
|
||||
|
||||
/** Owner email for a staff member, or null if not staff. */
|
||||
public static function ownerEmailForStaff(?string $email): ?string
|
||||
{
|
||||
$email = strtolower(trim((string) $email));
|
||||
foreach (self::STAFF as $tier => $roster) {
|
||||
foreach ($roster as $member) {
|
||||
if (strtolower($member['email']) === $email) {
|
||||
return self::ownerEmailForTier($tier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Identity AccountTeamMember metadata from a staff roles map.
|
||||
*
|
||||
* @param array<string, string> $roles
|
||||
* @return array<string, array{role: string}>
|
||||
*/
|
||||
public static function metadataFromRoles(array $roles): array
|
||||
{
|
||||
$metadata = [];
|
||||
foreach ($roles as $app => $role) {
|
||||
$metadata[$app] = ['role' => $role];
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
public static function storyDay(): \Carbon\CarbonInterface
|
||||
{
|
||||
return now(self::TIMEZONE)->startOfDay()->addDays(self::STORY_DAY_OFFSET);
|
||||
}
|
||||
|
||||
/** Local phone form used by some apps (024####### without +233). */
|
||||
public static function phoneLocal(string $e164): string
|
||||
{
|
||||
if (str_starts_with($e164, '+233')) {
|
||||
return '0'.substr($e164, 4);
|
||||
}
|
||||
|
||||
return $e164;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use App\Models\Woo\ProSubscription;
|
||||
use App\Models\WooOrder;
|
||||
use App\Models\WooProduct;
|
||||
use App\Models\WooStore;
|
||||
use App\Support\DemoWorld;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
@@ -74,4 +75,22 @@ class DemoSeedCommandTest extends TestCase
|
||||
$this->assertCount(1, WooStore::query()->where('user_id', $user->id)->get());
|
||||
$this->assertDatabaseMissing('woo_pro_subscriptions', ['user_id' => $user->id]);
|
||||
}
|
||||
|
||||
public function test_pro_plan_uses_demo_world_business_and_customer_continuity(): void
|
||||
{
|
||||
$user = User::factory()->create(['email' => 'demo-pro@ladill.com']);
|
||||
|
||||
$this->artisan('demo:seed', ['identity' => $user->email, '--plan' => 'pro'])
|
||||
->assertSuccessful();
|
||||
|
||||
$store = WooStore::query()->where('user_id', $user->id)->first();
|
||||
$this->assertStringContainsString(DemoWorld::business('pro')['name'], $store->site_name);
|
||||
|
||||
$firstPerson = DemoWorld::people(1)[0];
|
||||
$this->assertDatabaseHas('woo_orders', [
|
||||
'woo_store_id' => $store->id,
|
||||
'customer_name' => DemoWorld::fullName($firstPerson),
|
||||
'customer_email' => $firstPerson['email'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user