zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

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