domain.ex (13023B)
1 # Zenflows is designed to implement the Valueflows vocabulary, 2 # written and maintained by srfsh <info@dyne.org>. 3 # Copyright (C) 2021-2023 Dyne.org foundation <foundation@dyne.org>. 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Affero General Public License as published by 7 # the Free Software Foundation, either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU Affero General Public License for more details. 14 # 15 # You should have received a copy of the GNU Affero General Public License 16 # along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 defmodule Zenflows.VF.EconomicResource.Domain do 19 @moduledoc "Domain logic of EconomicResources." 20 21 alias Ecto.{Changeset, Multi} 22 alias Zenflows.DB.{Page, Repo, Schema} 23 alias Zenflows.VF.{ 24 Action, 25 EconomicEvent, 26 EconomicResource, 27 EconomicResource.Query, 28 Measure, 29 Process, 30 } 31 32 @spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) 33 :: {:ok, EconomicResource.t()} | {:error, String.t()} 34 def one(repo \\ Repo, _) 35 def one(repo, id) when is_binary(id), do: one(repo, id: id) 36 def one(repo, clauses) do 37 case repo.get_by(EconomicResource, clauses) do 38 nil -> {:error, "not found"} 39 found -> {:ok, found} 40 end 41 end 42 43 @spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) 44 :: EconomicResource.t() 45 def one!(repo \\ Repo, id_or_clauses) do 46 {:ok, value} = one(repo, id_or_clauses) 47 value 48 end 49 50 @spec all(Page.t()) :: {:ok, [EconomicResource.t()]} | {:error, Changeset.t()} 51 def all(page \\ Page.new()) do 52 with {:ok, q} <- Query.all(page) do 53 {:ok, Page.all(q, page)} 54 end 55 end 56 57 @spec all!(Page.t()) :: [EconomicResource.t()] 58 def all!(page \\ Page.new()) do 59 {:ok, value} = all(page) 60 value 61 end 62 63 @spec previous(EconomicResource.t() | Schema.id()) :: [EconomicEvent.t()] 64 def previous(_, _ \\ Page.new()) 65 def previous(%EconomicResource{id: id}, page), do: previous(id, page) 66 def previous(id, page) do 67 Query.previous(id) 68 |> Page.all(page) 69 |> Enum.sort(&( 70 &1.previous_event_id == nil 71 or &1.id == &2.previous_event_id 72 or &1.id <= &2.id)) 73 |> Enum.reverse() 74 end 75 76 @spec trace(EconomicResource.t() | EconomicEvent.t() | Process.t(), Page.t()) 77 :: [EconomicResource.t() | EconomicEvent.t() | Process.t()] 78 def trace(item, _page \\ Page.new()) do 79 flows = [item] 80 visited = MapSet.new([{item.__struct__, item.id}]) 81 {contained, modified, delivered} = 82 case item do 83 %EconomicEvent{action_id: "unpack"} -> 84 {MapSet.new([item.resource_inventoried_as_id]), %MapSet{}, %MapSet{}} 85 %EconomicEvent{action_id: "modify"} -> 86 {%MapSet{}, MapSet.new([item.resource_inventoried_as_id]), %MapSet{}} 87 %EconomicEvent{action_id: "dropoff"} -> 88 {%MapSet{}, %MapSet{}, MapSet.new([item.resource_inventoried_as_id])} 89 _ -> 90 {%MapSet{}, %MapSet{}, %MapSet{}} 91 end 92 {flows, _, _, _, _, _} = trace_depth_first_search(flows, visited, contained, modified, delivered, nil) 93 Enum.reverse(flows) 94 end 95 96 @spec trace_depth_first_search([EconomicResource.t() | EconomicEvent.t() | Process.t()], 97 MapSet.t(), MapSet.t(), MapSet.t(), MapSet.t(), nil | EconomicResource.t()) 98 :: {[EconomicResource.t() | EconomicEvent.t() | Process.t()], 99 MapSet.t(), MapSet.t(), MapSet.t(), MapSet.t(), nil | EconomicResource.t()} 100 defp trace_depth_first_search(flows, visited, contained, modified, delivered, saved_event) do 101 [last | _] = flows 102 previous = 103 case last do 104 %EconomicResource{} -> EconomicResource.Domain.previous(last) 105 %EconomicEvent{} -> [EconomicEvent.Domain.previous(last)] 106 %Process{} -> Process.Domain.previous(last) 107 end 108 saved_event = if match?(%EconomicEvent{}, last), 109 do: EconomicEvent.Domain.preload(last, :previous_event).previous_event, 110 else: saved_event 111 previous = 112 case {previous, saved_event} do 113 # ensure that: 114 # * `previous` has at least one item that is not `nil` (events' previous can return `nil`) 115 # * `saved_event` is not `nil` (events like raise have nullable previous_event) 116 {[%EconomicResource{}], %EconomicEvent{}} -> 117 [saved_event | previous] 118 119 {[%{id: _} | _], %{id: id}} -> 120 case Enum.split_while(previous, &(&1.id != id)) do 121 {left, [found | right]} -> [found | left] ++ right 122 {left, []} -> left 123 end 124 _ -> 125 previous 126 end 127 Enum.reduce(previous, {flows, visited, contained, modified, delivered, saved_event}, 128 fn item, {flows, visited, contained, modified, delivered, saved_event} -> 129 if item != nil and not MapSet.member?(visited, {item.__struct__, item.id}) do 130 {flows, visited, contained, modified, delivered, saved_event} = 131 handle_set(:delivered, item, flows, visited, contained, modified, delivered, saved_event) 132 {flows, visited, contained, modified, delivered, saved_event} = 133 handle_set(:modified, item, flows, visited, contained, modified, delivered, saved_event) 134 {flows, visited, contained, modified, delivered, saved_event} = 135 handle_set(:contained, item, flows, visited, contained, modified, delivered, saved_event) 136 case item do 137 %EconomicEvent{action_id: id} 138 when id in ~w[pickup dropoff accept modify pack unpack] -> 139 {flows, visited, contained, modified, delivered, saved_event} 140 _ -> 141 visited = MapSet.put(visited, {item.__struct__, item.id}) 142 flows = [item | flows] 143 trace_depth_first_search(flows, visited, contained, modified, delivered, saved_event) 144 end 145 else 146 {flows, visited, contained, modified, delivered, saved_event} 147 end 148 end) 149 end 150 151 @spec handle_set(:delivered | :modified | :contained, EconomicEvent.t(), 152 [EconomicResource.t() | EconomicEvent.t() | Process.t()], 153 MapSet.t(), MapSet.t(), MapSet.t(), MapSet.t(), nil | EconomicEvent.t()) 154 :: {[EconomicResource.t() | EconomicEvent.t() | Process.t()], 155 MapSet.t(), MapSet.t(), MapSet.t(), MapSet.t(), nil | EconomicEvent.t()} 156 for name <- [:delivered, :modified, :contained] do 157 set = Macro.var(name, nil) 158 {action0, action1} = case name do 159 :delivered -> {"pickup", "dropoff"} 160 :modified -> {"accept", "modify"} 161 :contained -> {"pack", "unpack"} 162 end 163 defp handle_set(unquote(name), %EconomicEvent{action_id: unquote(action0)} = evt, 164 flows, visited, contained, modified, delivered, saved_event) do 165 if MapSet.member?(unquote(set), {EconomicResource, evt.resource_inventoried_as_id}) do 166 unquote(set) = MapSet.delete(unquote(set), {EconomicResource, evt.resource_inventoried_as_id}) 167 visited = MapSet.put(visited, {EconomicEvent, evt.id}) 168 flows = [evt | flows] 169 trace_depth_first_search(flows, visited, contained, modified, delivered, saved_event) 170 else 171 {flows, visited, contained, modified, delivered, saved_event} 172 end 173 end 174 defp handle_set(unquote(name), %EconomicEvent{action_id: unquote(action1)} = evt, 175 flows, visited, contained, modified, delivered, saved_event) do 176 unquote(set) = MapSet.put(unquote(set), {EconomicResource, evt.resource_inventoried_as_id}) 177 visited = MapSet.put(visited, {EconomicEvent, evt.id}) 178 flows = [evt | flows] 179 trace_depth_first_search(flows, visited, contained, modified, delivered, saved_event) 180 end 181 end 182 defp handle_set(_, _, flows, visited, contained, modified, delivered, saved_event), 183 do: {flows, visited, contained, modified, delivered, saved_event} 184 185 @spec classifications() :: [String.t()] 186 def classifications() do 187 import Ecto.Query 188 189 from(r in EconomicResource, 190 select: fragment("distinct unnest(?)", r.classified_as)) 191 |> Repo.all() 192 end 193 194 @spec update(Schema.id(), Schema.params()) 195 :: {:ok, EconomicResource.t()} | {:error, String.t() | Changeset.t()} 196 def update(id, params) do 197 key = multi_key() 198 Multi.new() 199 |> multi_update(id, params) 200 |> Repo.transaction() 201 |> case do 202 {:ok, %{^key => value}} -> {:ok, value} 203 {:error, _, reason, _} -> {:error, reason} 204 end 205 end 206 207 @spec update!(Schema.id(), Schema.params()) :: EconomicResource.t() 208 def update!(id, params) do 209 {:ok, value} = update(id, params) 210 value 211 end 212 213 @spec delete(Schema.id()) :: 214 {:ok, EconomicResource.t()} | {:error, String.t() | Changeset.t()} 215 def delete(id) do 216 key = multi_key() 217 Multi.new() 218 |> multi_delete(id) 219 |> Repo.transaction() 220 |> case do 221 {:ok, %{^key => value}} -> {:ok, value} 222 {:error, _, reason, _} -> {:error, reason} 223 end 224 end 225 226 @spec delete!(Schema.id) :: EconomicResource.t() 227 def delete!(id) do 228 {:ok, value} = delete(id) 229 value 230 end 231 232 @spec preload(EconomicResource.t(), :images 233 | :conforms_to | :accounting_quantity 234 | :onhand_quantity | :primary_accountable | :custodian 235 | :stage | :state | :current_location | :lot | :contained_in 236 | :unit_of_effort | :previous_event) 237 :: EconomicResource.t() 238 def preload(eco_res, x) when x in ~w[ 239 images conforms_to primary_accountable custodian lot 240 stage current_location contained_in unit_of_effort previous_event 241 ]a, 242 do: Repo.preload(eco_res, x) 243 def preload(eco_res, x) when x in ~w[accounting_quantity onhand_quantity]a, 244 do: Measure.preload(eco_res, x) 245 def preload(eco_res, :state), 246 do: Action.preload(eco_res, :state) 247 248 @spec multi_key() :: atom() 249 def multi_key(), do: :economic_resource 250 251 @spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() 252 def multi_one(m, key \\ multi_key(), id) do 253 Multi.run(m, key, fn repo, _ -> one(repo, id) end) 254 end 255 256 @spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() 257 def multi_insert(m, key \\ multi_key(), params) do 258 Multi.insert(m, key, EconomicResource.changeset(params)) 259 end 260 261 @spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() 262 def multi_update(m, key \\ multi_key(), id, params) do 263 m 264 |> multi_one("#{key}.one", id) 265 |> Multi.update(key, 266 &EconomicResource.changeset(Map.fetch!(&1, "#{key}.one"), params)) 267 end 268 269 @spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() 270 def multi_delete(m, key \\ multi_key(), id) do 271 m 272 |> multi_one("#{key}.one", id) 273 |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) 274 end 275 276 @max_depth 100_000_000 277 278 @spec trace_dpp_er_before(EconomicResource.t(), MapSet.t(), integer()) :: {MapSet.t(), map()} 279 def trace_dpp_er_before(_item, visited, depth) when depth >= @max_depth do 280 {visited, %{}} 281 end 282 def trace_dpp_er_before(%EconomicResource{} = item, visited, depth) do 283 a_dpp_item = %{type: "EconomicResource", node: item} 284 {visited2, children} = EconomicResource.Domain.previous(item) |> Enum.reduce({visited, []}, 285 fn ee, {visited, children} -> 286 if MapSet.member?(visited, {ee.__struct__, ee.id}) do 287 {visited, children} 288 else 289 {visited2, child} = trace_dpp_ee_before(ee, MapSet.put(visited, {ee.__struct__, ee.id}), depth + 1) 290 {visited2, [child | children]} 291 end 292 end 293 ) 294 {visited2, Map.put(a_dpp_item, :children, children)} 295 end 296 297 @spec trace_dpp_ee_before_recurse( 298 EconomicEvent.t() | EconomicResource.t() | Process.t(), 299 MapSet.t(), pos_integer() ) :: {MapSet.t(), map()} 300 def trace_dpp_ee_before_recurse(%EconomicResource{} = item, visited, depth) do 301 trace_dpp_er_before(item, visited, depth) 302 end 303 def trace_dpp_ee_before_recurse(%EconomicEvent{} = item, visited, depth) do 304 trace_dpp_ee_before(item, MapSet.put(visited, {item.__struct__, item.id}), depth) 305 end 306 def trace_dpp_ee_before_recurse(%Process{} = item, visited, depth) do 307 trace_dpp_pr_before(item, MapSet.put(visited, {item.__struct__, item.id}), depth) 308 end 309 310 @spec trace_dpp_ee_before(EconomicEvent.t(), MapSet.t(), pos_integer()) :: {MapSet.t(), map()} 311 def trace_dpp_ee_before(_item, visited, depth) when depth >= @max_depth do 312 {visited, %{}} 313 end 314 def trace_dpp_ee_before(%EconomicEvent{} = item, visited, depth) do 315 a_dpp_item = %{type: "EconomicEvent", node: item} 316 pr_item = EconomicEvent.Domain.previous(item) 317 if pr_item == nil do 318 {visited, Map.put(a_dpp_item, :children, [])} 319 else 320 {visited2, children} = [pr_item] |> Enum.reduce({visited, []}, 321 fn pf, {visited, children} -> 322 if MapSet.member?(visited, {pf.__struct__, pf.id}) do 323 {visited, children} 324 else 325 {visited2, child} = trace_dpp_ee_before_recurse(pf, visited, depth + 1) 326 {visited2, [child | children]} 327 end 328 end 329 ) 330 {visited2, Map.put(a_dpp_item, :children, children)} 331 end 332 end 333 334 @spec trace_dpp_pr_before(Process.t(), MapSet.t(), integer()) :: {MapSet.t(), map()} 335 def trace_dpp_pr_before(_item, visited, depth) when depth >= @max_depth do 336 {visited, %{}} 337 end 338 def trace_dpp_pr_before(item, visited, depth) do 339 a_dpp_item = %{type: "Process", node: item} 340 {visited2, children} = Process.Domain.previous(item) |> Enum.reduce({visited, []}, 341 fn ee, {visited, children} -> 342 if MapSet.member?(visited, {ee.__struct__, ee.id}) do 343 {visited, children} 344 else 345 {visited2, child} = trace_dpp_ee_before(ee, MapSet.put(visited, {ee.__struct__, ee.id}), depth + 1) 346 {visited2, [child | children]} 347 end 348 end 349 ) 350 {visited2, Map.put(a_dpp_item, :children, children)} 351 end 352 353 @spec trace_dpp(EconomicResource.t(), Page.t()) :: map() 354 def trace_dpp(item, _page \\ Page.new()) do 355 {_, dpp} = trace_dpp_er_before(item, MapSet.new(), 0) 356 dpp 357 end 358 end