feat: port basic functionality to elixir
This commit is contained in:
254
lib/components_elixir/inventory.ex
Normal file
254
lib/components_elixir/inventory.ex
Normal file
@@ -0,0 +1,254 @@
|
||||
defmodule ComponentsElixir.Inventory do
|
||||
@moduledoc """
|
||||
The Inventory context for managing components and categories.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias ComponentsElixir.Repo
|
||||
|
||||
alias ComponentsElixir.Inventory.{Category, Component}
|
||||
|
||||
## Categories
|
||||
|
||||
@doc """
|
||||
Returns the list of categories.
|
||||
"""
|
||||
def list_categories do
|
||||
Category
|
||||
|> order_by([c], [asc: c.name])
|
||||
|> preload(:parent)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of root categories (no parent).
|
||||
"""
|
||||
def list_root_categories do
|
||||
Category
|
||||
|> where([c], is_nil(c.parent_id))
|
||||
|> order_by([c], [asc: c.name])
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of child categories for a given parent.
|
||||
"""
|
||||
def list_child_categories(parent_id) do
|
||||
Category
|
||||
|> where([c], c.parent_id == ^parent_id)
|
||||
|> order_by([c], [asc: c.name])
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single category.
|
||||
"""
|
||||
def get_category!(id) do
|
||||
Category
|
||||
|> preload(:parent)
|
||||
|> Repo.get!(id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a category.
|
||||
"""
|
||||
def create_category(attrs \\ %{}) do
|
||||
%Category{}
|
||||
|> Category.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a category.
|
||||
"""
|
||||
def update_category(%Category{} = category, attrs) do
|
||||
category
|
||||
|> Category.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a category.
|
||||
"""
|
||||
def delete_category(%Category{} = category) do
|
||||
Repo.delete(category)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking category changes.
|
||||
"""
|
||||
def change_category(%Category{} = category, attrs \\ %{}) do
|
||||
Category.changeset(category, attrs)
|
||||
end
|
||||
|
||||
## Components
|
||||
|
||||
@doc """
|
||||
Returns the list of components with optional filtering and pagination.
|
||||
"""
|
||||
def list_components(opts \\ []) do
|
||||
Component
|
||||
|> apply_component_filters(opts)
|
||||
|> preload(:category)
|
||||
|> order_by([c], [asc: c.category_id, asc: c.name])
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns paginated components with search and filtering.
|
||||
"""
|
||||
def paginate_components(opts \\ []) do
|
||||
limit = Keyword.get(opts, :limit, 20)
|
||||
offset = Keyword.get(opts, :offset, 0)
|
||||
|
||||
query =
|
||||
Component
|
||||
|> apply_component_filters(opts)
|
||||
|> preload(:category)
|
||||
|> order_by([c], [asc: c.category_id, asc: c.name])
|
||||
|
||||
components =
|
||||
query
|
||||
|> limit(^limit)
|
||||
|> offset(^offset)
|
||||
|> Repo.all()
|
||||
|
||||
total_count = Repo.aggregate(query, :count, :id)
|
||||
|
||||
%{
|
||||
components: components,
|
||||
total_count: total_count,
|
||||
has_more: total_count > offset + length(components)
|
||||
}
|
||||
end
|
||||
|
||||
defp apply_component_filters(query, opts) do
|
||||
Enum.reduce(opts, query, fn
|
||||
{:search, search}, query when is_binary(search) and search != "" ->
|
||||
if String.length(search) > 3 do
|
||||
# Use full-text search for longer queries
|
||||
where(query, [c],
|
||||
fragment("to_tsvector('english', ? || ' ' || coalesce(?, '') || ' ' || coalesce(?, '')) @@ plainto_tsquery(?)",
|
||||
c.name, c.description, c.keywords, ^search))
|
||||
else
|
||||
# Use ILIKE for shorter queries
|
||||
search_term = "%#{search}%"
|
||||
where(query, [c],
|
||||
ilike(c.name, ^search_term) or
|
||||
ilike(c.description, ^search_term) or
|
||||
ilike(c.keywords, ^search_term))
|
||||
end
|
||||
|
||||
{:category_id, category_id}, query when is_integer(category_id) ->
|
||||
where(query, [c], c.category_id == ^category_id)
|
||||
|
||||
{:sort_criteria, "name"}, query ->
|
||||
order_by(query, [c], [asc: c.name])
|
||||
|
||||
{:sort_criteria, "description"}, query ->
|
||||
order_by(query, [c], [asc: c.description])
|
||||
|
||||
{:sort_criteria, "id"}, query ->
|
||||
order_by(query, [c], [asc: c.id])
|
||||
|
||||
{:sort_criteria, "category_id"}, query ->
|
||||
order_by(query, [c], [asc: c.category_id, asc: c.name])
|
||||
|
||||
_, query ->
|
||||
query
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single component.
|
||||
"""
|
||||
def get_component!(id) do
|
||||
Component
|
||||
|> preload(:category)
|
||||
|> Repo.get!(id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a component.
|
||||
"""
|
||||
def create_component(attrs \\ %{}) do
|
||||
%Component{}
|
||||
|> Component.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a component.
|
||||
"""
|
||||
def update_component(%Component{} = component, attrs) do
|
||||
component
|
||||
|> Component.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a component's count.
|
||||
"""
|
||||
def update_component_count(%Component{} = component, count) when is_integer(count) do
|
||||
component
|
||||
|> Component.count_changeset(%{count: count})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Increments a component's count.
|
||||
"""
|
||||
def increment_component_count(%Component{} = component) do
|
||||
update_component_count(component, component.count + 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decrements a component's count (minimum 0).
|
||||
"""
|
||||
def decrement_component_count(%Component{} = component) do
|
||||
new_count = max(0, component.count - 1)
|
||||
update_component_count(component, new_count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a component's image filename.
|
||||
"""
|
||||
def update_component_image(%Component{} = component, image_filename) do
|
||||
component
|
||||
|> Component.image_changeset(%{image_filename: image_filename})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a component.
|
||||
"""
|
||||
def delete_component(%Component{} = component) do
|
||||
Repo.delete(component)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking component changes.
|
||||
"""
|
||||
def change_component(%Component{} = component, attrs \\ %{}) do
|
||||
Component.changeset(component, attrs)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns component statistics.
|
||||
"""
|
||||
def component_stats do
|
||||
total_components = Repo.aggregate(Component, :count, :id)
|
||||
total_stock = Repo.aggregate(Component, :sum, :count) || 0
|
||||
categories_with_components =
|
||||
Component
|
||||
|> select([c], c.category_id)
|
||||
|> distinct(true)
|
||||
|> Repo.aggregate(:count, :category_id)
|
||||
|
||||
%{
|
||||
total_components: total_components,
|
||||
total_stock: total_stock,
|
||||
categories_with_components: categories_with_components
|
||||
}
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user