{
  "$schemaVersion": 1,
  "_readme": [
    "The module list. A module is the SMALLEST UNIT AN AGENCY CAN BUY.",
    "",
    "If two things can be sold separately they are separate modules, even when they",
    "sit under one menu heading. Bundling separate modules is just granting several;",
    "splitting one later needs a data migration, so err towards separate.",
    "",
    "A menu heading ABOVE modules (Activities, Campaigns, Templates, Automations)",
    "has no module and no permission of its own - it appears when any child does.",
    "The code already works this way: ActivitiesModule is computed as",
    "(TaskModule || MeetingModule || TourModule) in crm-menu.service.ts.",
    "",
    "entitlement: true  -> agency buys it; appears in agency.modules",
    "entitlement: false -> every agency has it; still permission-gated",
    "",
    "scoped   true when the module's records belong to a user, so 'own / team / all'",
    "         is a meaningful question. Drives whether the role editor offers a",
    "         per-module data_scope override for it."
  ],
  "containers": [
    {
      "label": "Contacts",
      "children": [
        "accounts",
        "owners",
        "companies"
      ]
    },
    {
      "label": "Transactions",
      "children": [
        "sales_offers",
        "reservations",
        "sales"
      ]
    },
    {
      "label": "Finance",
      "children": [
        "invoices",
        "commissions"
      ]
    },
    {
      "label": "Add-ons",
      "children": [
        "dialer",
        "mailbox",
        "mls"
      ]
    },
    {
      "label": "Activities",
      "children": [
        "tasks",
        "meetings",
        "tours"
      ]
    },
    {
      "label": "Campaigns",
      "children": [
        "campaigns_email",
        "campaigns_whatsapp",
        "campaigns_meta"
      ]
    },
    {
      "label": "Templates",
      "children": [
        "templates_email",
        "templates_message",
        "templates_pdf",
        "templates_contract",
        "notification_manager"
      ]
    },
    {
      "label": "Automations",
      "children": [
        "actionable_rules",
        "sequences",
        "lead_distribution"
      ]
    }
  ],
  "modules": [
    {
      "key": "properties",
      "label": "Properties",
      "scoped": true,
      "entitlement": true,
      "status": "active",
      "note": "Renamed from 'commercials' (Sept 2026). The code, routes and collections still say commercial - auth.check('commercial_properties'), the commercialModule flag, the /commercials routes and the commercials/ folder. Only the product name changed. NOT the same as the legacy 'properties' module (the old /properties tree, ~163 files, deprecated and unused) - that one is dead; this is the live property module. Do not rename the underlying collections without a separate migration."
    },
    {
      "key": "projects",
      "label": "Projects",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "accounts",
      "label": "Accounts",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "owners",
      "label": "Owners",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "companies",
      "label": "Companies",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "property_groups",
      "label": "Property groups",
      "scoped": true,
      "entitlement": true,
      "status": "active",
      "note": "Renamed from 'urbanisations' (Sept 2026). The code, routes and collections still say urbanisation - crm_menu urls, auth.check('urbanisations'), the urbanisationModule flag. Only the product name changed. Do not rename the underlying collections without a separate migration."
    },
    {
      "key": "tasks",
      "label": "Tasks",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "meetings",
      "label": "Meetings",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "tours",
      "label": "Tours",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "sales_offers",
      "label": "Sales offers",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "reservations",
      "label": "Reservations",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "sales",
      "label": "Sales",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "bookings",
      "label": "Bookings",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "invoices",
      "label": "Invoices",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "commissions",
      "label": "Commissions",
      "scoped": true,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "lead_distribution",
      "label": "Lead distribution",
      "scoped": false,
      "entitlement": true,
      "status": "active",
      "note": "Backed by account_distribution."
    },
    {
      "key": "campaigns_email",
      "label": "Email campaigns",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "campaigns_whatsapp",
      "label": "WhatsApp campaigns",
      "scoped": false,
      "entitlement": true,
      "status": "active",
      "note": "Has its own agency flag, whatsapp_module."
    },
    {
      "key": "campaigns_meta",
      "label": "Meta campaigns",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "templates_email",
      "label": "Email templates",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "templates_message",
      "label": "Message templates",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "templates_pdf",
      "label": "PDF templates",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "templates_contract",
      "label": "Contract templates",
      "scoped": false,
      "entitlement": true,
      "status": "active",
      "note": "Backed by contract_genrator (spelling as stored)."
    },
    {
      "key": "sequences",
      "label": "Sequences",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "notification_manager",
      "label": "Notification manager",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "dialer",
      "label": "Dialer",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "mailbox",
      "label": "Mailbox",
      "scoped": false,
      "entitlement": true,
      "status": "active",
      "note": "Added Sept 2026. The legacy agencies.mailbox_module boolean still gates the ~16 existing call sites and keeps its own toggle on the Add-on modules tab; this key exists so mailbox can be sold and resolved like every other module. The two are NOT cross-wired - backfill-mailbox-module seeds `mailbox` for agencies that already have mailbox_module set, and after that they move independently until the boolean is retired."
    },
    {
      "key": "mls",
      "label": "MLS",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "exporter",
      "label": "Exporter",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "importer",
      "label": "Importer",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "support_tickets",
      "label": "Support tickets",
      "scoped": true,
      "entitlement": false,
      "status": "active",
      "note": "Core, not sellable. Every agency has it and every user can raise a ticket; still permission-gated."
    },
    {
      "key": "cms",
      "label": "CMS",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "actionable_rules",
      "label": "Actionable rules",
      "scoped": false,
      "entitlement": true,
      "status": "active"
    },
    {
      "key": "reporting",
      "label": "Reporting",
      "scoped": false,
      "entitlement": true,
      "status": "active",
      "note": "No module-level key: each report carries its own."
    },
    {
      "key": "dashboard",
      "label": "Dashboard",
      "scoped": false,
      "entitlement": false,
      "status": "active"
    },
    {
      "key": "user_management",
      "label": "User management",
      "scoped": false,
      "entitlement": false,
      "status": "active"
    },
    {
      "key": "system_settings",
      "label": "System settings",
      "scoped": false,
      "entitlement": false,
      "status": "active"
    },
    {
      "key": "notifications",
      "label": "Notifications",
      "scoped": false,
      "entitlement": false,
      "status": "active",
      "note": "The user's own notification list. Core and per-user - distinct from notification_manager, which is the template/configuration module."
    },
    {
      "key": "help",
      "label": "Help centre",
      "scoped": false,
      "entitlement": false,
      "status": "active",
      "note": "Help and FAQ content. Core and per-user; carries no permission today."
    }
  ]
}
