Context module for managing locations and location types.
Locations and types have a many-to-many relationship via a join table, so a location can be both a "Showroom" and "Storage" at the same time.
Both locations and types use hard-delete only (simple reference data).
Activity logging
Every mutating function accepts opts \ []. When actor_uuid: is
present in opts, the mutation is logged via PhoenixKit.Activity.log/1
under the "locations" module key. Logging failures never crash the
primary operation — the helper rescues and falls back to
Logger.warning.
Usage from IEx
alias PhoenixKitLocations.Locations
# Types
{:ok, showroom} = Locations.create_location_type(%{name: "Showroom"})
{:ok, storage} = Locations.create_location_type(%{name: "Storage"})
# Locations
{:ok, loc} = Locations.create_location(%{name: "HQ", address_line_1: "123 Main St"})
# Assign types
{:ok, _} = Locations.sync_location_types(loc.uuid, [showroom.uuid, storage.uuid])
# Or add/remove individually
{:ok, _} = Locations.add_location_type(loc.uuid, showroom.uuid)
{:ok, _} = Locations.remove_location_type(loc.uuid, storage.uuid)
# Query
Locations.list_locations(type_uuid: showroom.uuid)
Locations.count_locations()
Locations.get_location_by(:name, "HQ")
Summary
Functions
Adds a single type to a location. No-op if already assigned.
Returns an Ecto.Changeset for tracking location changes.
Returns an Ecto.Changeset for tracking location type changes.
Returns the total count of location types.
Returns the total count of locations.
Creates a location.
Creates a location type. Required: :name. Optional: :description, :status, :data.
Hard-deletes a location. Cascades to type assignments.
Hard-deletes a location type. Cascades to type assignments (locations keep existing, just lose the link).
Finds locations with the same address_line_1, city, and postal_code.
Fetches a location by UUID with types preloaded. Returns nil if not found.
Fetches a location by a field value. Returns nil if not found.
Fetches a location type by UUID. Returns nil if not found.
Fetches a location type by name (case-sensitive). Returns nil if not found.
Returns true if the location has the given type assigned.
Returns a list of type UUIDs linked to a location.
Returns a list of LocationType structs linked to a location.
Lists all location types, ordered by name.
Lists all locations, ordered by name, with their types preloaded.
Logs a module enable/disable toggle. Called by PhoenixKitLocations.enable_system/0
and disable_system/0.
Removes a single type from a location. No-op if not assigned.
Syncs the type assignments for a location (full replace).
Updates a location with the given attributes.
Updates a location type with the given attributes.
Types
Functions
@spec add_location_type(String.t(), String.t(), opts()) :: {:ok, PhoenixKitLocations.Schemas.LocationTypeAssignment.t()} | {:error, Ecto.Changeset.t()}
Adds a single type to a location. No-op if already assigned.
Returns {:ok, assignment} or {:error, changeset}.
@spec change_location(PhoenixKitLocations.Schemas.Location.t(), map()) :: Ecto.Changeset.t()
Returns an Ecto.Changeset for tracking location changes.
@spec change_location_type(PhoenixKitLocations.Schemas.LocationType.t(), map()) :: Ecto.Changeset.t()
Returns an Ecto.Changeset for tracking location type changes.
@spec count_location_types(status_filter()) :: non_neg_integer()
Returns the total count of location types.
@spec count_locations(status_filter()) :: non_neg_integer()
Returns the total count of locations.
@spec create_location(map(), opts()) :: {:ok, PhoenixKitLocations.Schemas.Location.t()} | {:error, Ecto.Changeset.t()}
Creates a location.
Required: :name. Optional: :description, :public_notes, :address_line_1,
:address_line_2, :city, :state, :postal_code, :country, :phone,
:email, :website, :notes, :status, :features, :data.
@spec create_location_type(map(), opts()) :: {:ok, PhoenixKitLocations.Schemas.LocationType.t()} | {:error, Ecto.Changeset.t()}
Creates a location type. Required: :name. Optional: :description, :status, :data.
@spec delete_location(PhoenixKitLocations.Schemas.Location.t(), opts()) :: {:ok, PhoenixKitLocations.Schemas.Location.t()} | {:error, Ecto.Changeset.t()}
Hard-deletes a location. Cascades to type assignments.
@spec delete_location_type(PhoenixKitLocations.Schemas.LocationType.t(), opts()) :: {:ok, PhoenixKitLocations.Schemas.LocationType.t()} | {:error, Ecto.Changeset.t()}
Hard-deletes a location type. Cascades to type assignments (locations keep existing, just lose the link).
@spec find_similar_addresses( String.t() | nil, String.t() | nil, String.t() | nil, String.t() | nil ) :: [map()]
Finds locations with the same address_line_1, city, and postal_code.
Returns a list of matching locations, excluding the given exclude_uuid.
Only checks if address_line_1 is non-empty. Returns [] on any error
(with the error logged — treated as a soft-fail so the form still saves).
@spec get_location(String.t()) :: PhoenixKitLocations.Schemas.Location.t() | nil
Fetches a location by UUID with types preloaded. Returns nil if not found.
@spec get_location_by(:name | :email | :phone, String.t()) :: PhoenixKitLocations.Schemas.Location.t() | nil
Fetches a location by a field value. Returns nil if not found.
Only safe field names are accepted — unknown fields raise ArgumentError.
Examples
Locations.get_location_by(:name, "Main Office")
Locations.get_location_by(:email, "hq@example.com")
@spec get_location_type(String.t()) :: PhoenixKitLocations.Schemas.LocationType.t() | nil
Fetches a location type by UUID. Returns nil if not found.
@spec get_location_type_by_name(String.t()) :: PhoenixKitLocations.Schemas.LocationType.t() | nil
Fetches a location type by name (case-sensitive). Returns nil if not found.
Returns true if the location has the given type assigned.
Returns a list of type UUIDs linked to a location.
@spec linked_types(String.t()) :: [PhoenixKitLocations.Schemas.LocationType.t()]
Returns a list of LocationType structs linked to a location.
@spec list_location_types(status_filter()) :: [ PhoenixKitLocations.Schemas.LocationType.t() ]
Lists all location types, ordered by name.
Options
:status— filter by status (e.g."active","inactive").
@spec list_locations(list_locations_opts()) :: [ PhoenixKitLocations.Schemas.Location.t() ]
Lists all locations, ordered by name, with their types preloaded.
Options
:status— filter by status (e.g."active","inactive").:type_uuid— filter to only locations that have this type assigned.
@spec log_module_toggle(:enabled | :disabled, opts()) :: :ok
Logs a module enable/disable toggle. Called by PhoenixKitLocations.enable_system/0
and disable_system/0.
Removes a single type from a location. No-op if not assigned.
Returns {:ok, count} where count is 0 or 1.
@spec sync_location_types(String.t(), [String.t()], opts()) :: {:ok, :synced | :unchanged} | {:error, :type_assignment_failed}
Syncs the type assignments for a location (full replace).
Replaces all existing assignments with the given list of type UUIDs. Wrapped in a transaction for atomicity — if any insert fails, all changes are rolled back (existing assignments preserved).
Logs location.types_synced only when the assignment set actually
changed; a no-op sync is silent.
@spec update_location(PhoenixKitLocations.Schemas.Location.t(), map(), opts()) :: {:ok, PhoenixKitLocations.Schemas.Location.t()} | {:error, Ecto.Changeset.t()}
Updates a location with the given attributes.
@spec update_location_type( PhoenixKitLocations.Schemas.LocationType.t(), map(), opts() ) :: {:ok, PhoenixKitLocations.Schemas.LocationType.t()} | {:error, Ecto.Changeset.t()}
Updates a location type with the given attributes.