zf

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

domain.ex (46044B)


      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.EconomicEvent.Domain do
     19 @moduledoc "Domain logic of EconomicEvents."
     20 
     21 import Ecto.Query
     22 
     23 alias Ecto.{Changeset, Multi}
     24 alias Zenflows.DB.{Page, Repo, Schema}
     25 alias Zenflows.VF.{
     26 	Action,
     27 	EconomicEvent,
     28 	EconomicEvent.Query,
     29 	EconomicResource,
     30 	Measure,
     31 	Process,
     32 }
     33 
     34 @spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t())
     35 	:: {:ok, EconomicEvent.t()} | {:error, String.t()}
     36 def one(repo \\ Repo, _)
     37 def one(repo, id) when is_binary(id), do: one(repo, id: id)
     38 def one(repo, clauses) do
     39 	case repo.get_by(EconomicEvent, clauses) do
     40 		nil -> {:error, "not found"}
     41 		found -> {:ok, found}
     42 	end
     43 end
     44 
     45 @spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t())
     46 	:: EconomicEvent.t()
     47 def one!(repo \\ Repo, id_or_clauses) do
     48 	{:ok, value} = one(repo, id_or_clauses)
     49 	value
     50 end
     51 
     52 @spec all(Page.t()) :: {:ok, [EconomicEvent.t()]} | {:error, Changeset.t()}
     53 def all(page \\ Page.new()) do
     54 	{:ok, Page.all(EconomicEvent, page)}
     55 end
     56 
     57 @spec all!(Page.t()) :: [EconomicEvent.t()]
     58 def all!(page \\ Page.new()) do
     59 	{:ok, value} = all(page)
     60 	value
     61 end
     62 
     63 @spec previous(EconomicEvent.t() | Schema.id())
     64 	:: nil | Process.t() | EconomicEvent.t() | EconomicResource.t()
     65 def previous(%EconomicEvent{id: id}), do: previous(id)
     66 def previous(id) do
     67 	case Query.previous(id) do
     68 		nil -> nil
     69 		q -> Repo.one!(q)
     70 	end
     71 end
     72 
     73 @spec create(Schema.params(), nil | Schema.params())
     74 	:: {:ok, EconomicEvent.t()} | {:error, String.t() | Changeset.t()}
     75 def create(evt_params, res_params \\ nil) do
     76 	key = multi_key()
     77 	Multi.new()
     78 	|> multi_insert(evt_params, res_params)
     79 	|> Repo.transaction()
     80 	|> case do
     81 		{:ok, %{^key => value}} -> {:ok, value}
     82 		{:error, _, reason, _} -> {:error, reason}
     83 	end
     84 end
     85 
     86 @spec create!(Schema.params(), nil | Schema.params()) :: EconomicEvent.t()
     87 def create!(evt_params, res_params \\ nil) do
     88 	{:ok, value} = create(evt_params, res_params)
     89 	value
     90 end
     91 
     92 @spec update(Schema.id(), Schema.params())
     93 	:: {:ok, EconomicEvent.t()} | {:error, String.t() | Changeset.t()}
     94 def update(id, params) do
     95 	key = multi_key()
     96 	Multi.new()
     97 	|> multi_update(id, params)
     98 	|> Repo.transaction()
     99 	|> case do
    100 		{:ok, %{^key => value}} -> {:ok, value}
    101 		{:error, _, reason, _} -> {:error, reason}
    102 	end
    103 end
    104 
    105 @spec update!(Schema.id(), Schema.params()) :: EconomicEvent.t()
    106 def update!(id, params) do
    107 	# `__MODULE__` because it confilicts with `import Ecto.Query`
    108 	{:ok, value} = __MODULE__.update(id, params)
    109 	value
    110 end
    111 
    112 @spec preload(EconomicEvent.t(), :action | :input_of | :output_of
    113 		| :provider | :receiver
    114 		| :resource_inventoried_as | :to_resource_inventoried_as
    115 		| :resource_conforms_to | :resource_quantity | :effort_quantity
    116 		| :to_location | :at_location | :realization_of | :triggered_by
    117 		| :previous_event)
    118 	:: EconomicEvent.t()
    119 def preload(eco_evt, x) when x in ~w[
    120 	input_of output_of provider receiver
    121 	resource_inventoried_as to_resource_inventoried_as
    122 	resource_conforms_to to_location at_location realization_of
    123 	triggered_by previous_event
    124 ]a do
    125 	Repo.preload(eco_evt, x)
    126 end
    127 def preload(eco_evt, :action),
    128 	do: Action.preload(eco_evt, :action)
    129 def preload(eco_evt, x) when x in ~w[resource_quantity effort_quantity]a,
    130 	do: Measure.preload(eco_evt, x)
    131 
    132 @spec multi_key() :: atom()
    133 def multi_key(), do: :economic_event
    134 
    135 @spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t()
    136 def multi_one(m, key \\ multi_key(), id) do
    137 	Multi.run(m, key, fn repo, _ -> one(repo, id) end)
    138 end
    139 
    140 @spec multi_insert(Multi.t(), term(), Schema.params(), nil | Schema.params())
    141 	:: Multi.t()
    142 def multi_insert(m, key \\ multi_key(), evt_params, res_params) do
    143 	m
    144 	|> Multi.insert("#{key}.created", EconomicEvent.changeset(evt_params))
    145 	|> Multi.merge(&handle_insert(key, Map.fetch!(&1, "#{key}.created"), res_params))
    146 end
    147 
    148 # Handle the part after the event is created.  These clauses deal with
    149 # validations, creation of resources, and any other side-effects.
    150 #
    151 # It either returns the given `evt` as it is under the name `key`,
    152 # or updates `evt` and returns it under the name `key`.
    153 @spec handle_insert(term(), EconomicEvent.t(), nil | Schema.params()) :: Multi.t()
    154 defp handle_insert(key, %{action_id: action_id} = evt, res_params)
    155 		when action_id in ["raise", "produce"] do
    156 	cond do
    157 		evt.resource_conforms_to_id != nil ->
    158 			res_params =
    159 				(res_params || %{})
    160 				|> Map.put(:previous_event_id, evt.id)
    161 				|> Map.put(:primary_accountable_id, evt.receiver_id)
    162 				|> Map.put(:custodian_id, evt.receiver_id)
    163 				|> Map.put(:conforms_to_id, evt.resource_conforms_to_id)
    164 				|> Map.put(:accounting_quantity_has_unit_id, evt.resource_quantity_has_unit_id)
    165 				|> Map.put(:accounting_quantity_has_numerical_value, evt.resource_quantity_has_numerical_value)
    166 				|> Map.put(:onhand_quantity_has_unit_id, evt.resource_quantity_has_unit_id)
    167 				|> Map.put(:onhand_quantity_has_numerical_value, evt.resource_quantity_has_numerical_value)
    168 				|> Map.put(:current_location_id, evt.to_location_id)
    169 				|> Map.put(:classified_as, evt.resource_classified_as)
    170 
    171 			Multi.new()
    172 			|> EconomicResource.Domain.multi_insert("#{key}.eco_res", res_params)
    173 			|> Multi.update(key, &Changeset.change(evt,
    174 				resource_inventoried_as_id: &1 |> Map.fetch!("#{key}.eco_res") |> Map.fetch!(:id)))
    175 		evt.resource_inventoried_as_id != nil ->
    176 			Multi.new()
    177 			|> Multi.run("#{key}.checks", fn repo, _ ->
    178 				fields = ~w[
    179 					primary_accountable_id custodian_id
    180 					accounting_quantity_has_unit_id
    181 					previous_event_id
    182 				]a
    183 				res = from(
    184 					r in EconomicResource,
    185 					where: [id: ^evt.resource_inventoried_as_id],
    186 					select: merge(map(r, ^fields), %{
    187 						contained?: not is_nil(r.contained_in_id), # is it contained in something?
    188 					})
    189 				)
    190 				|> repo.one!()
    191 				res = Map.put(res, :container?,
    192 					where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id)
    193 					|> repo.exists?())
    194 
    195 				cond do
    196 					evt.provider_id != res.primary_accountable_id or evt.provider_id != res.custodian_id ->
    197 						{:error, "you don't have ownership over this resource"}
    198 					evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id ->
    199 						{:error, "the unit of resource quantity must match with the unit of this resource"}
    200 					res.contained? ->
    201 						{:error, "you can't #{action_id} into a contained resource"}
    202 					res.container? ->
    203 						{:error, "you can't #{action_id} into a container resource"}
    204 					true ->
    205 						# some fields of the resource is required for the following multis
    206 						{:ok, res}
    207 				end
    208 			end)
    209 			|> Multi.update(key, &Changeset.change(evt,
    210 				previous_event_id: &1 |> Map.fetch!("#{key}.checks") |> Map.fetch!(:previous_event_id)))
    211 			|> Multi.update_all("#{key}.set_and_inc",
    212 				where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    213 				set: [previous_event_id: evt.id],
    214 				inc: [
    215 					accounting_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value,
    216 					onhand_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value,
    217 				])
    218 	end
    219 end
    220 defp handle_insert(key, %{action_id: action_id} = evt, _)
    221 		when action_id in ["lower", "consume"] do
    222 	Multi.new()
    223 	|> Multi.run("#{key}.checks", fn repo, _ ->
    224 		fields = ~w[
    225 			primary_accountable_id custodian_id
    226 			accounting_quantity_has_unit_id
    227 			previous_event_id
    228 		]a
    229 		res = from(
    230 			r in EconomicResource,
    231 			where: [id: ^evt.resource_inventoried_as_id],
    232 			select: merge(map(r, ^fields), %{
    233 				contained?: not is_nil(r.contained_in_id), # is it contained in something?
    234 			})
    235 		)
    236 		|> repo.one!()
    237 		res = Map.put(res, :container?,
    238 			where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id)
    239 			|> repo.exists?())
    240 
    241 		cond do
    242 			evt.provider_id != res.primary_accountable_id or evt.provider_id != res.custodian_id ->
    243 				{:error, "you don't have ownership over this resource"}
    244 			evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id ->
    245 				{:error, "the unit of resource quantity must match with the unit of this resource"}
    246 			res.contained? -> # TODO: study combine-separate
    247 				{:error, "you can't #{action_id} a contained resource"}
    248 			res.container? ->
    249 				{:error, "you can't #{action_id} a container resource"}
    250 			true ->
    251 				# some fields of the resource is required for the following multis
    252 				{:ok, res}
    253 		end
    254 	end)
    255 	|> Multi.update(key, &Changeset.change(evt,
    256 		previous_event_id: &1 |> Map.fetch!("#{key}.checks") |> Map.fetch!(:previous_event_id)))
    257 	|> Multi.update_all("#{key}.set_and_dec",
    258 		where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    259 		set: [previous_event_id: evt.id],
    260 		inc: [
    261 			accounting_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
    262 			onhand_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
    263 		])
    264 end
    265 defp handle_insert(key, %{action_id: action_id} = evt, _)
    266 		when action_id in ~w[work deliverService] do
    267 	Multi.put(Multi.new(), key, evt)
    268 end
    269 defp handle_insert(key, %{action_id: "use"} = evt, _) do
    270 	cond do
    271 		evt.resource_conforms_to_id != nil ->
    272 			Multi.put(Multi.new(), key, evt)
    273 		evt.resource_inventoried_as_id != nil ->
    274 			Multi.new()
    275 			|> Multi.run("#{key}.checks", fn repo, _ ->
    276 				fields = if evt.resource_quantity_has_unit_id != nil,
    277 					do: [:accounting_quantity_has_unit_id, :previous_event_id],
    278 					else: [:previous_event_id]
    279 
    280 				res = from(
    281 					r in EconomicResource,
    282 					where: [id: ^evt.resource_inventoried_as_id],
    283 					select: merge(map(r, ^fields), %{
    284 						contained?: not is_nil(r.contained_in_id), # is it contained in something?
    285 					})
    286 				)
    287 				|> repo.one!()
    288 				res = Map.put(res, :container?,
    289 					where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id)
    290 					|> repo.exists?())
    291 
    292 				cond do
    293 					evt.resource_quantity && (evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id) ->
    294 						{:error, "the unit of resource quantity must match with the unit of this resource"}
    295 					res.contained? -> # TODO: study combine-separate
    296 						{:error, "you can't use a contained resource"}
    297 					res.container? ->
    298 						{:error, "you can't use a container resource"}
    299 					true ->
    300 						# some fields of the resource is required for the following multis
    301 						{:ok, res}
    302 				end
    303 			end)
    304 			|> Multi.update(key, &Changeset.change(evt,
    305 				previous_event_id: &1 |> Map.fetch!("#{key}.checks") |> Map.fetch!(:previous_event_id)))
    306 			|> Multi.update_all("#{key}.set",
    307 				where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    308 				set: [previous_event_id: evt.id])
    309 	end
    310 end
    311 defp handle_insert(key, %{action_id: "cite"} = evt, _) do
    312 	cond do
    313 		evt.resource_conforms_to_id != nil ->
    314 			Multi.put(Multi.new(), key, evt)
    315 		evt.resource_inventoried_as_id != nil ->
    316 			Multi.new()
    317 			|> Multi.run("#{key}.checks", fn repo, _ ->
    318 				fields = [:accounting_quantity_has_unit_id, :previous_event_id]
    319 				res = from(
    320 					r in EconomicResource,
    321 					where: [id: ^evt.resource_inventoried_as_id],
    322 					select: merge(map(r, ^fields), %{
    323 						contained?: not is_nil(r.contained_in_id), # is it contained in something?
    324 					})
    325 				)
    326 				|> repo.one!()
    327 				res = Map.put(res, :container?,
    328 					where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id)
    329 					|> repo.exists?())
    330 
    331 				cond do
    332 					evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id ->
    333 						{:error, "the unit of resource quantity must match with the unit of this resource"}
    334 					res.contained? -> # TODO: study combine-separate
    335 						{:error, "you can't cite a contained resource"}
    336 					res.container? ->
    337 						{:error, "you can't cite a container resource"}
    338 					true ->
    339 						# some fields of the resource is required for the following multis
    340 						{:ok, res}
    341 				end
    342 			end)
    343 			|> Multi.update(key, &Changeset.change(evt,
    344 				previous_event_id: &1 |> Map.fetch!("#{key}.checks") |> Map.fetch!(:previous_event_id)))
    345 			|> Multi.update_all("#{key}.set",
    346 				where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    347 				set: [previous_event_id: evt.id])
    348 	end
    349 end
    350 defp handle_insert(key, %{action_id: "pickup"} = evt, _) do
    351 	Multi.new()
    352 	|> Multi.run("#{key}.checks", fn repo, _ ->
    353 		fields = ~w[
    354 			custodian_id
    355 			onhand_quantity_has_numerical_value onhand_quantity_has_unit_id
    356 			previous_event_id
    357 		]a
    358 		res = from(
    359 			r in EconomicResource,
    360 			where: [id: ^evt.resource_inventoried_as_id],
    361 			select: merge(map(r, ^fields), %{
    362 				contained?: not is_nil(r.contained_in_id), # is it contained in something?
    363 			})
    364 		)
    365 		|> repo.one!()
    366 
    367 		not_single_ref? = fn ->
    368 			where(EconomicEvent, [e],
    369 				e.resource_inventoried_as_id == ^evt.resource_inventoried_as_id
    370 					and e.id != ^evt.id
    371 					and e.action_id == "pickup"
    372 					and e.input_of_id == ^evt.input_of_id)
    373 			|> repo.exists?()
    374 		end
    375 
    376 		cond do
    377 			evt.provider_id != res.custodian_id ->
    378 				{:error, "you don't have custody over this resource"}
    379 			res.contained? -> # TODO: study combine-separate
    380 				{:error, "you can't pickup a contained resource"}
    381 			evt.resource_quantity_has_unit_id != res.onhand_quantity_has_unit_id ->
    382 				{:error, "the unit of resource quantity must match with the unit of this resource"}
    383 			# This also handles the requirement that the
    384 			# resource's onhand quantity must be positive.
    385 			# This is guranteed because of the check below
    386 			# and the requriment that the resource quantity
    387 			# of events must be positive.
    388 			evt.resource_quantity_has_numerical_value != res.onhand_quantity_has_numerical_value ->
    389 				{:error, "the pickup events need to fully pickup the resource"}
    390 			not_single_ref?.() ->
    391 				{:error,
    392 					"no more than one pickup event in the same process, referring to the same resource is allowed"}
    393 			true ->
    394 				# some fields of the resource is required for the following multis
    395 				{:ok, res}
    396 		end
    397 	end)
    398 	|> Multi.update(key, &Changeset.change(evt,
    399 		previous_event_id: &1 |> Map.fetch!("#{key}.checks") |> Map.fetch!(:previous_event_id)))
    400 	|> Multi.update_all("#{key}.set",
    401 		where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    402 		set: [previous_event_id: evt.id])
    403 end
    404 defp handle_insert(key, %{action_id: "dropoff"} = evt, _) do
    405 	Multi.new()
    406 	|> Multi.run("#{key}.checks", fn repo, _ ->
    407 		pair_evt =
    408 			from(e in EconomicEvent,
    409 				where: e.resource_inventoried_as_id == ^evt.resource_inventoried_as_id
    410 					and e.action_id == "pickup"
    411 					and e.input_of_id == ^evt.output_of_id,
    412 				select: map(e, ~w[
    413 					provider_id
    414 					resource_quantity_has_numerical_value resource_quantity_has_unit_id
    415 				]a))
    416 			|> repo.one!()
    417 
    418 		not_single_ref? = fn ->
    419 			where(EconomicEvent, [e],
    420 				e.resource_inventoried_as_id == ^evt.resource_inventoried_as_id
    421 					and e.id != ^evt.id
    422 					and e.action_id == "dropoff"
    423 					and e.output_of_id == ^evt.output_of_id)
    424 			|> repo.exists?()
    425 		end
    426 
    427 		container? = fn ->
    428 			where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id)
    429 			|> repo.exists?()
    430 		end
    431 
    432 		cond do
    433 			evt.provider_id != pair_evt.provider_id ->
    434 				{:error, "you don't have custody over this resource"}
    435 			evt.resource_quantity_has_unit_id != pair_evt.resource_quantity_has_unit_id ->
    436 				{:error, "the unit of resource quantity must match with the unit of the paired event"}
    437 			container?.() && evt.resource_quantity_has_numerical_value != pair_evt.resource_quantity_has_numerical_value ->
    438 				{:error, "the dropoff events need to fully dropoff the resource"}
    439 			not_single_ref?.() ->
    440 				{:error,
    441 					"no more than one dropoff event in the same process, referring to the same resource is allowed"}
    442 			true ->
    443 				{:ok, nil}
    444 		end
    445 	end)
    446 	|> Multi.run("#{key}.set_loc", fn repo, _ ->
    447 		if evt.to_location_id do
    448 			q = where(EconomicResource, [r],
    449 					r.id == ^evt.resource_inventoried_as_id
    450 						or r.contained_in_id == ^evt.resource_inventoried_as_id)
    451 			{:ok, repo.update_all(q, set: [current_location_id: evt.to_location_id])}
    452 		else
    453 			{:ok, nil}
    454 		end
    455 	end)
    456 	|> Multi.run("#{key}.prev_evt_id", fn repo, _ ->
    457 		{:ok,
    458 			from(r in EconomicResource, where: r.id == ^evt.resource_inventoried_as_id,
    459 				select: r.previous_event_id)
    460 			|> repo.one()}
    461 	end)
    462 	|> Multi.update(key, &Changeset.change(evt,
    463 		previous_event_id: Map.fetch!(&1, "#{key}.prev_evt_id")))
    464 	|> Multi.update_all("#{key}.set_prev_evt",
    465 		where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    466 		set: [previous_event_id: evt.id])
    467 end
    468 defp handle_insert(key, %{action_id: "accept"} = evt, _) do
    469 	Multi.new()
    470 	|> Multi.run("#{key}.checks", fn repo, _ ->
    471 		fields = ~w[
    472 			custodian_id
    473 			onhand_quantity_has_numerical_value onhand_quantity_has_unit_id
    474 			previous_event_id
    475 		]a
    476 		res = from(
    477 			r in EconomicResource,
    478 			where: [id: ^evt.resource_inventoried_as_id],
    479 			select: merge(map(r, ^fields), %{
    480 				contained?: not is_nil(r.contained_in_id), # is it contained in something?
    481 			})
    482 		)
    483 		|> repo.one!()
    484 		res = Map.put(res, :container?,
    485 			where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id)
    486 			|> repo.exists?())
    487 
    488 		not_single_ref? = fn ->
    489 			where(EconomicEvent, [e],
    490 				e.resource_inventoried_as_id == ^evt.resource_inventoried_as_id
    491 					and e.id != ^evt.id
    492 					and e.action_id == "accept"
    493 					and e.input_of_id == ^evt.input_of_id)
    494 			|> repo.exists?()
    495 		end
    496 
    497 		any_combine_separate? = fn ->
    498 			where(EconomicEvent, [e],
    499 				(e.action_id == "combine" and e.input_of_id == ^evt.input_of_id)
    500 				or
    501 				(e.action_id == "separate" and e.output_of_id == ^evt.input_of_id))
    502 			|> repo.exists?()
    503 		end
    504 
    505 		cond do
    506 			evt.provider_id != res.custodian_id ->
    507 				{:error, "you don't have custody over this resource"}
    508 
    509 			res.contained? ->
    510 				{:error, "you can't accept a contained resource"}
    511 
    512 			evt.resource_quantity_has_unit_id != res.onhand_quantity_has_unit_id ->
    513 				{:error, "the unit of resource quantity must match with the unit of this resource"}
    514 
    515 			evt.resource_quantity_has_numerical_value != res.onhand_quantity_has_numerical_value ->
    516 				{:error, "the accept events need to fully accept the resource"}
    517 
    518 			res.container? and not Decimal.gt?(res.onhand_quantity_has_numerical_value, 0) ->
    519 				{:error, "the accept events need container resources to have positive onhand quantity"}
    520 
    521 			any_combine_separate?.() ->
    522 				{:error, "you can't add another accept event to the same process where there are at least one combine or separate events"}
    523 
    524 			not_single_ref?.() ->
    525 				{:error, "no more than one accept event in the same process, referring to the same resource is allowed"}
    526 
    527 			true ->
    528 				# some fields of the resource is required for the following multis
    529 				{:ok, res}
    530 		end
    531 	end)
    532 	|> Multi.update(key, &Changeset.change(evt,
    533 		previous_event_id: &1 |> Map.fetch!("#{key}.checks") |> Map.fetch!(:previous_event_id)))
    534 	|> Multi.update_all("#{key}.set_and_dec",
    535 		where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    536 		set: [previous_event_id: evt.id],
    537 		inc: [
    538 			onhand_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
    539 		])
    540 end
    541 defp handle_insert(key, %{action_id: "modify"} = evt, _) do
    542 	Multi.new()
    543 	|> Multi.run("#{key}.checks", fn repo, _ ->
    544 		pair_evt =
    545 			from(e in EconomicEvent,
    546 				where: e.resource_inventoried_as_id == ^evt.resource_inventoried_as_id
    547 					and e.action_id == "accept"
    548 					and e.input_of_id == ^evt.output_of_id,
    549 				select: map(e, ~w[
    550 					provider_id
    551 					resource_quantity_has_numerical_value resource_quantity_has_unit_id
    552 				]a))
    553 			|> repo.one()
    554 
    555 		not_single_ref? = fn ->
    556 			where(EconomicEvent, [e],
    557 				e.resource_inventoried_as_id == ^evt.resource_inventoried_as_id
    558 					and e.id != ^evt.id
    559 					and e.action_id == "modify"
    560 					and e.output_of_id == ^evt.output_of_id)
    561 			|> repo.exists?()
    562 		end
    563 
    564 		cond do
    565 			pair_evt == nil ->
    566 				{:error, "there must be a paired accept event"}
    567 			evt.provider_id != pair_evt.provider_id ->
    568 				{:error, "you don't have custody over this resource"}
    569 			evt.resource_quantity_has_unit_id != pair_evt.resource_quantity_has_unit_id ->
    570 				{:error, "the unit of resource quantity must match with the unit of the paired event"}
    571 			evt.resource_quantity_has_numerical_value != pair_evt.resource_quantity_has_numerical_value ->
    572 				{:error, "the modify events need to fully modify the resource"}
    573 			not_single_ref?.() ->
    574 				{:error, "no more than one modify event in the same process, referring to the same resource is allowed"}
    575 			true ->
    576 				{:ok, nil}
    577 		end
    578 	end)
    579 	|> Multi.run("#{key}.prev_evt_id", fn repo, _ ->
    580 		{:ok,
    581 			from(r in EconomicResource, where: r.id == ^evt.resource_inventoried_as_id,
    582 				select: r.previous_event_id)
    583 			|> repo.one()}
    584 	end)
    585 	|> Multi.update(key, &Changeset.change(evt,
    586 		previous_event_id: Map.fetch!(&1, "#{key}.prev_evt_id")))
    587 	|> Multi.update_all("#{key}.set_and_inc",
    588 		where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    589 		set: [previous_event_id: evt.id],
    590 		inc: [
    591 			onhand_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value,
    592 		])
    593 	|> Multi.update_all("#{key}.stage", fn _ ->
    594 		from(r in EconomicResource,
    595 			join: e in EconomicEvent, on: e.id == ^evt.id,
    596 			join: p in assoc(e, :output_of),
    597 			where: r.id == e.resource_inventoried_as_id,
    598 			update: [set: [stage_id: p.based_on_id]])
    599 	end, [])
    600 end
    601 defp handle_insert(key, %{action_id: "transferCustody"} = evt, res_params) do
    602 	Multi.new()
    603 	|> Multi.run("#{key}.checks", fn repo, _ ->
    604 		%{resource_inventoried_as_id: res_id, to_resource_inventoried_as_id: to_res_id} = evt
    605 		res_ids =
    606 			if to_res_id do
    607 				[res_id, to_res_id]
    608 			else
    609 				[res_id]
    610 			end
    611 		fields = ~w[
    612 			id custodian_id conforms_to_id
    613 			onhand_quantity_has_numerical_value onhand_quantity_has_unit_id
    614 
    615 			name note tracking_identifier okhv repo version licensor license metadata
    616 			classified_as
    617 
    618 			previous_event_id
    619 		]a
    620 		{res, to_res} =
    621 			from(r in EconomicResource,
    622 				where: r.id in ^res_ids,
    623 				select: merge(map(r, ^fields), %{
    624 					contained?: not is_nil(r.contained_in_id), # is it contained in something?
    625 				}))
    626 			|> repo.all()
    627 			|> case do
    628 				[%{id: ^res_id} = res] -> {res, nil}
    629 				[%{id: ^res_id} = res, %{id: ^to_res_id} = to_res] -> {res, to_res}
    630 				[%{id: ^to_res_id} = to_res, %{id: ^res_id} = res] -> {res, to_res}
    631 			end
    632 		res = Map.put(res, :container?,
    633 			where(EconomicResource, contained_in_id: ^res_id) |> repo.exists?())
    634 		to_res = to_res
    635 			&& Map.put(to_res, :container?,
    636 				where(EconomicResource, contained_in_id: ^to_res_id) |> repo.exists?())
    637 
    638 		cond do
    639 			evt.provider_id != res.custodian_id ->
    640 				{:error, "you don't have custody over this resource"}
    641 			res.contained? ->
    642 				{:error, "you can't transfer-custody a contained resource"}
    643 			evt.resource_quantity_has_unit_id != res.onhand_quantity_has_unit_id ->
    644 				{:error, "the unit of resource-quantity must match with the unit of resource-inventoried-as"}
    645 			res.container? and not Decimal.gt?(res.onhand_quantity_has_numerical_value, 0) ->
    646 				{:error, "the transfer-custody events need container resources to have positive onhand-quantity"}
    647 			res.container? && evt.resource_quantity_has_numerical_value != res.onhand_quantity_has_numerical_value ->
    648 				{:error, "the transfer-custody events need to fully transfer the resource"}
    649 			res.container? && to_res ->
    650 				{:error, "you can't transfer-custody a container resource into another resource"}
    651 			to_res && to_res.contained? ->
    652 				{:error, "you can't transfer-custody into a contained resource"}
    653 			to_res && to_res.container? ->
    654 				{:error, "you can't transfer-custody into a container resource"}
    655 			to_res && evt.resource_quantity_has_unit_id != to_res.onhand_quantity_has_unit_id ->
    656 				{:error, "the unit of resource-quantity must match with the unit of to-resource-inventoried-as"}
    657 			to_res && res.conforms_to_id != to_res.conforms_to_id ->
    658 				{:error, "the resources must conform to the same specification"}
    659 			true ->
    660 				# some fields of the resource is required for the following multis
    661 				{:ok, res}
    662 		end
    663 	end)
    664 	|> Multi.merge(fn changes ->
    665 		res = Map.fetch!(changes, "#{key}.checks")
    666 		if evt.to_resource_inventoried_as_id do
    667 			Multi.new()
    668 			|> Multi.update(key, Changeset.change(evt, previous_event_id: res.previous_event_id))
    669 			|> Multi.update_all("#{key}.set_and_dec",
    670 				where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    671 				set: [previous_event_id: evt.id, custodian_id: evt.receiver_id],
    672 				inc: [
    673 					onhand_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
    674 				])
    675 			|> Multi.update_all("#{key}.inc", where(EconomicResource, id: ^evt.to_resource_inventoried_as_id), inc: [
    676 				onhand_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value,
    677 			])
    678 		else
    679 			res_params =
    680 				(res_params || %{})
    681 				|> Map.put(:previous_event_id, evt.id)
    682 				|> Map.put(:primary_accountable_id, evt.receiver_id)
    683 				|> Map.put(:custodian_id, evt.receiver_id)
    684 				|> Map.put(:conforms_to_id, res.conforms_to_id)
    685 				|> Map.put(:accounting_quantity_has_unit_id, evt.resource_quantity_has_unit_id)
    686 				|> Map.put(:accounting_quantity_has_numerical_value, 0)
    687 				|> Map.put(:onhand_quantity_has_unit_id, evt.resource_quantity_has_unit_id)
    688 				|> Map.put(:onhand_quantity_has_numerical_value, evt.resource_quantity_has_numerical_value)
    689 				|> Map.put(:current_location_id, evt.to_location_id)
    690 				|> Map.put(:classified_as, evt.resource_classified_as || res.classified_as)
    691 				|> Map.put_new(:name, res.name)
    692 				|> Map.put_new(:note, res.note)
    693 				|> Map.put_new(:tracking_identifier, res.tracking_identifier)
    694 				|> Map.put_new(:okhv, res.okhv)
    695 				|> Map.put_new(:repo, res.repo)
    696 				|> Map.put_new(:version, res.version)
    697 				|> Map.put_new(:licensor, res.licensor)
    698 				|> Map.put_new(:license, res.license)
    699 				|> Map.put_new(:metadata, res.metadata)
    700 
    701 			Multi.new()
    702 			|> EconomicResource.Domain.multi_insert("#{key}.to_eco_res", res_params)
    703 			|> Multi.update(key, &Changeset.change(evt,
    704 				to_resource_inventoried_as_id: &1 |> Map.fetch!("#{key}.to_eco_res") |> Map.fetch!(:id),
    705 				previous_event_id: res.previous_event_id))
    706 			|> Multi.update_all("#{key}.set_and_dec",
    707 				where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    708 				set: [previous_event_id: evt.id],
    709 				inc: [
    710 					onhand_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
    711 				])
    712 			|> Multi.merge(fn %{^key => evt} ->
    713 				if res.container? do
    714 					Multi.update_all(Multi.new(), "#{key}.set",
    715 						where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id), set: [
    716 							contained_in_id: evt.to_resource_inventoried_as_id,
    717 							custodian_id: evt.receiver_id,
    718 						])
    719 				else
    720 					Multi.new()
    721 				end
    722 			end)
    723 		end
    724 	end)
    725 end
    726 defp handle_insert(key, %{action_id: "transferAllRights"} = evt, res_params) do
    727 	Multi.new()
    728 	|> Multi.run("#{key}.checks", fn repo, _ ->
    729 		%{resource_inventoried_as_id: res_id, to_resource_inventoried_as_id: to_res_id} = evt
    730 		res_ids =
    731 			if to_res_id do
    732 				[res_id, to_res_id]
    733 			else
    734 				[res_id]
    735 			end
    736 		fields = ~w[
    737 			id primary_accountable_id conforms_to_id
    738 			accounting_quantity_has_numerical_value accounting_quantity_has_unit_id
    739 
    740 			name note tracking_identifier okhv repo version licensor license metadata
    741 			classified_as
    742 
    743 			previous_event_id
    744 		]a
    745 		{res, to_res} =
    746 			from(r in EconomicResource,
    747 				where: r.id in ^res_ids,
    748 				select: merge(map(r, ^fields), %{
    749 					contained?: not is_nil(r.contained_in_id), # is it contained in something?
    750 				}))
    751 			|> repo.all()
    752 			|> case do
    753 				[%{id: ^res_id} = res] -> {res, nil}
    754 				[%{id: ^res_id} = res, %{id: ^to_res_id} = to_res] -> {res, to_res}
    755 				[%{id: ^to_res_id} = to_res, %{id: ^res_id} = res] -> {res, to_res}
    756 			end
    757 		res = Map.put(res, :container?,
    758 			where(EconomicResource, contained_in_id: ^res_id) |> repo.exists?())
    759 		to_res = to_res
    760 			&& Map.put(to_res, :container?,
    761 				where(EconomicResource, contained_in_id: ^to_res_id) |> repo.exists?())
    762 
    763 		cond do
    764 			evt.provider_id != res.primary_accountable_id ->
    765 				{:error, "you don't have accountability over this resource"}
    766 			res.contained? ->
    767 				{:error, "you can't transfer-all-rights a contained resource"}
    768 			evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id ->
    769 				{:error, "the unit of resource-quantity must match with the unit of resource-inventoried-as"}
    770 			res.container? and not Decimal.gt?(res.accounting_quantity_has_numerical_value, 0) ->
    771 				{:error, "the transfer-all-rights events need container resources to have positive accounting-quantity"}
    772 			res.container? && evt.resource_quantity_has_numerical_value != res.accounting_quantity_has_numerical_value ->
    773 				{:error, "the transfer-all-rights events need to fully transfer the resource"}
    774 			res.container? && to_res ->
    775 				{:error, "you can't transfer-all-rights a container resource into another resource"}
    776 			to_res && to_res.contained? ->
    777 				{:error, "you can't transfer-all-rights into a contained resource"}
    778 			to_res && to_res.container? ->
    779 				{:error, "you can't transfer-all-rights into a container resource"}
    780 			to_res && evt.resource_quantity_has_unit_id != to_res.accounting_quantity_has_unit_id ->
    781 				{:error, "the unit of resource-quantity must match with the unit of to-resource-inventoried-as"}
    782 			to_res && res.conforms_to_id != to_res.conforms_to_id ->
    783 				{:error, "the resources must conform to the same specification"}
    784 			true ->
    785 				# some fields of the resource is required for the following multis
    786 				{:ok, res}
    787 		end
    788 	end)
    789 	|> Multi.merge(fn changes ->
    790 		res = Map.fetch!(changes, "#{key}.checks")
    791 		if evt.to_resource_inventoried_as_id do
    792 			Multi.new()
    793 			|> Multi.update(key, Changeset.change(evt, previous_event_id: res.previous_event_id))
    794 			|> Multi.update_all(:set_and_dec,
    795 				where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    796 				set: [previous_event_id: evt.id, primary_accountable_id: evt.receiver_id],
    797 				inc: [
    798 					accounting_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
    799 				])
    800 			|> Multi.update_all(:inc, where(EconomicResource, id: ^evt.to_resource_inventoried_as_id), inc: [
    801 				accounting_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value,
    802 			])
    803 		else
    804 			res_params =
    805 				(res_params || %{})
    806 				|> Map.put(:previous_event_id, evt.id)
    807 				|> Map.put(:primary_accountable_id, evt.receiver_id)
    808 				|> Map.put(:custodian_id, evt.receiver_id)
    809 				|> Map.put(:conforms_to_id, res.conforms_to_id)
    810 				|> Map.put(:accounting_quantity_has_unit_id, evt.resource_quantity_has_unit_id)
    811 				|> Map.put(:accounting_quantity_has_numerical_value, evt.resource_quantity_has_numerical_value)
    812 				|> Map.put(:onhand_quantity_has_unit_id, evt.resource_quantity_has_unit_id)
    813 				|> Map.put(:onhand_quantity_has_numerical_value, 0)
    814 				|> Map.put(:classified_as, evt.resource_classified_as || res.classified_as)
    815 				|> Map.put_new(:name, res.name)
    816 				|> Map.put_new(:note, res.note)
    817 				|> Map.put_new(:tracking_identifier, res.tracking_identifier)
    818 				|> Map.put_new(:okhv, res.okhv)
    819 				|> Map.put_new(:repo, res.repo)
    820 				|> Map.put_new(:version, res.version)
    821 				|> Map.put_new(:licensor, res.licensor)
    822 				|> Map.put_new(:license, res.license)
    823 				|> Map.put_new(:metadata, res.metadata)
    824 
    825 			Multi.new()
    826 			|> EconomicResource.Domain.multi_insert("#{key}.to_eco_res", res_params)
    827 			|> Multi.update(key, &Changeset.change(evt,
    828 				to_resource_inventoried_as_id: &1 |> Map.fetch!("#{key}.to_eco_res") |> Map.fetch!(:id),
    829 				previous_event_id: res.previous_event_id))
    830 			|> Multi.update_all("#{key}.set_and_dec",
    831 				where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    832 				set: [previous_event_id: evt.id],
    833 				inc: [
    834 					accounting_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
    835 				])
    836 			|> Multi.merge(fn %{^key => evt} ->
    837 				if res.container? do
    838 					Multi.update_all(Multi.new(), "#{key}.set", where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id), set: [
    839 						contained_in_id: evt.to_resource_inventoried_as_id,
    840 						primary_accountable_id: evt.receiver_id,
    841 					])
    842 				else
    843 					Multi.new()
    844 				end
    845 			end)
    846 		end
    847 	end)
    848 end
    849 defp handle_insert(key, %{action_id: "transfer"} = evt, res_params) do
    850 	Multi.new()
    851 	|> Multi.run("#{key}.checks", fn repo, _ ->
    852 		%{resource_inventoried_as_id: res_id, to_resource_inventoried_as_id: to_res_id} = evt
    853 		res_ids =
    854 			if to_res_id do
    855 				[res_id, to_res_id]
    856 			else
    857 				[res_id]
    858 			end
    859 		fields = ~w[
    860 			id primary_accountable_id custodian_id conforms_to_id
    861 			accounting_quantity_has_numerical_value accounting_quantity_has_unit_id
    862 			onhand_quantity_has_numerical_value
    863 
    864 			name note tracking_identifier okhv repo version licensor license metadata
    865 			classified_as
    866 
    867 			previous_event_id
    868 		]a
    869 		{res, to_res} =
    870 			from(r in EconomicResource,
    871 				where: r.id in ^res_ids,
    872 				select: merge(map(r, ^fields), %{
    873 					contained?: not is_nil(r.contained_in_id), # is it contained in something?
    874 				}))
    875 			|> repo.all()
    876 			|> case do
    877 				[%{id: ^res_id} = res] -> {res, nil}
    878 				[%{id: ^res_id} = res, %{id: ^to_res_id} = to_res] -> {res, to_res}
    879 				[%{id: ^to_res_id} = to_res, %{id: ^res_id} = res] -> {res, to_res}
    880 			end
    881 		res = Map.put(res, :container?,
    882 			where(EconomicResource, contained_in_id: ^res_id) |> repo.exists?())
    883 		to_res = to_res
    884 			&& Map.put(to_res, :container?,
    885 				where(EconomicResource, contained_in_id: ^to_res_id) |> repo.exists?())
    886 
    887 		cond do
    888 			evt.provider_id != res.primary_accountable_id ->
    889 				{:error, "you don't have accountability over this resource"}
    890 			evt.provider_id != res.custodian_id ->
    891 				{:error, "you don't have custody over this resource"}
    892 			res.contained? ->
    893 				{:error, "you can't transfer a contained resource"}
    894 			evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id ->
    895 				{:error, "the unit of resource-quantity must match with the unit of resource-inventoried-as"}
    896 			res.container? and not Decimal.gt?(res.accounting_quantity_has_numerical_value, 0) ->
    897 				{:error, "the transfer events need container resources to have positive accounting-quantity"}
    898 			res.container? and not Decimal.gt?(res.onhand_quantity_has_numerical_value, 0) ->
    899 				{:error, "the transfer events need container resources to have positive onhand-quantity"}
    900 			res.container? && evt.resource_quantity_has_numerical_value != res.accounting_quantity_has_numerical_value ->
    901 				{:error, "the transfer events need to fully transfer the resource"}
    902 			res.container? && evt.resource_quantity_has_numerical_value != res.onhand_quantity_has_numerical_value ->
    903 				{:error, "the transfer events need to fully transfer the resource"}
    904 			res.container? && to_res ->
    905 				{:error, "you can't transfer a container resource into another resource"}
    906 			to_res && to_res.contained? ->
    907 				{:error, "you can't transfer into a contained resource"}
    908 			to_res && to_res.container? ->
    909 				{:error, "you can't transfer into a container resource"}
    910 			to_res && evt.resource_quantity_has_unit_id != to_res.accounting_quantity_has_unit_id ->
    911 				{:error, "the unit of resource-quantity must match with the unit of to-resource-inventoried-as"}
    912 			to_res && res.conforms_to_id != to_res.conforms_to_id ->
    913 				{:error, "the resources must conform to the same specification"}
    914 			true ->
    915 				# some fields of the resource is required for the following multis
    916 				{:ok, res}
    917 		end
    918 	end)
    919 	|> Multi.merge(fn changes ->
    920 		res = Map.fetch!(changes, "#{key}.checks")
    921 		if evt.to_resource_inventoried_as_id do
    922 			Multi.new()
    923 			|> Multi.update(key, Changeset.change(evt, previous_event_id: res.previous_event_id))
    924 			|> Multi.update_all("#{key}.set_and_dec",
    925 				where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    926 				set: [
    927 					previous_event_id: evt.id,
    928 					primary_accountable_id: evt.receiver_id,
    929 					custodian_id: evt.receiver_id,
    930 				],
    931 				inc: [
    932 					accounting_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
    933 					onhand_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
    934 				])
    935 			|> Multi.update_all("#{key}.inc", where(EconomicResource, id: ^evt.to_resource_inventoried_as_id), inc: [
    936 				accounting_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value,
    937 				onhand_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value,
    938 			])
    939 		else
    940 			res_params =
    941 				(res_params || %{})
    942 				|> Map.put(:previous_event_id, evt.id)
    943 				|> Map.put(:primary_accountable_id, evt.receiver_id)
    944 				|> Map.put(:custodian_id, evt.receiver_id)
    945 				|> Map.put(:conforms_to_id, res.conforms_to_id)
    946 				|> Map.put(:accounting_quantity_has_unit_id, evt.resource_quantity_has_unit_id)
    947 				|> Map.put(:accounting_quantity_has_numerical_value, evt.resource_quantity_has_numerical_value)
    948 				|> Map.put(:onhand_quantity_has_unit_id, evt.resource_quantity_has_unit_id)
    949 				|> Map.put(:onhand_quantity_has_numerical_value, evt.resource_quantity_has_numerical_value)
    950 				|> Map.put(:current_location_id, evt.to_location_id)
    951 				|> Map.put(:classified_as, evt.resource_classified_as || res.classified_as)
    952 				|> Map.put_new(:name, res.name)
    953 				|> Map.put_new(:note, res.note)
    954 				|> Map.put_new(:tracking_identifier, res.tracking_identifier)
    955 				|> Map.put_new(:okhv, res.okhv)
    956 				|> Map.put_new(:repo, res.repo)
    957 				|> Map.put_new(:version, res.version)
    958 				|> Map.put_new(:licensor, res.licensor)
    959 				|> Map.put_new(:license, res.license)
    960 				|> Map.put_new(:metadata, res.metadata)
    961 
    962 			Multi.new()
    963 			|> EconomicResource.Domain.multi_insert("#{key}.to_eco_res", res_params)
    964 			|> Multi.update(key, &Changeset.change(evt,
    965 				to_resource_inventoried_as_id: &1 |> Map.fetch!("#{key}.to_eco_res") |> Map.fetch!(:id),
    966 				previous_event_id: res.previous_event_id))
    967 			|> Multi.update_all("#{key}.set_and_dec",
    968 				where(EconomicResource, id: ^evt.resource_inventoried_as_id),
    969 				set: [previous_event_id: evt.id],
    970 				inc: [
    971 					accounting_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
    972 					onhand_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
    973 				])
    974 			|> Multi.merge(fn %{^key => evt} ->
    975 				if res.container? do
    976 					Multi.update_all(Multi.new(), "#{key}.set", where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id), set: [
    977 						contained_in_id: evt.to_resource_inventoried_as_id,
    978 						primary_accountable_id: evt.receiver_id,
    979 						custodian_id: evt.receiver_id,
    980 					])
    981 				else
    982 					Multi.new()
    983 				end
    984 			end)
    985 		end
    986 	end)
    987 end
    988 defp handle_insert(key, %{action_id: "move"} = evt, res_params) do
    989 	Multi.new()
    990 	|> Multi.run("#{key}.checks", fn repo, _ ->
    991 		%{resource_inventoried_as_id: res_id, to_resource_inventoried_as_id: to_res_id} = evt
    992 		res_ids =
    993 			if to_res_id do
    994 				[res_id, to_res_id]
    995 			else
    996 				[res_id]
    997 			end
    998 		fields = ~w[
    999 			id primary_accountable_id custodian_id conforms_to_id
   1000 			accounting_quantity_has_numerical_value accounting_quantity_has_unit_id
   1001 			onhand_quantity_has_numerical_value
   1002 
   1003 			name note tracking_identifier okhv repo version licensor license metadata
   1004 			classified_as
   1005 
   1006 			previous_event_id
   1007 		]a
   1008 		{res, to_res} =
   1009 			from(r in EconomicResource,
   1010 				where: r.id in ^res_ids,
   1011 				select: merge(map(r, ^fields), %{
   1012 					contained?: not is_nil(r.contained_in_id), # is it contained in something?
   1013 				}))
   1014 			|> repo.all()
   1015 			|> case do
   1016 				[%{id: ^res_id} = res] -> {res, nil}
   1017 				[%{id: ^res_id} = res, %{id: ^to_res_id} = to_res] -> {res, to_res}
   1018 				[%{id: ^to_res_id} = to_res, %{id: ^res_id} = res] -> {res, to_res}
   1019 			end
   1020 		res = Map.put(res, :container?,
   1021 			where(EconomicResource, contained_in_id: ^res_id) |> repo.exists?())
   1022 		to_res = to_res
   1023 			&& Map.put(to_res, :container?,
   1024 				where(EconomicResource, contained_in_id: ^to_res_id) |> repo.exists?())
   1025 
   1026 		cond do
   1027 			evt.provider_id != res.primary_accountable_id ->
   1028 				{:error, "you don't have accountability over resource-inventoried-as"}
   1029 			evt.provider_id != res.custodian_id ->
   1030 				{:error, "you don't have custody over resource-inventoried-as"}
   1031 			res.contained? ->
   1032 				{:error, "you can't move a contained resource"}
   1033 			evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id ->
   1034 				{:error, "the unit of resource-quantity must match with the unit of resource-inventoried-as"}
   1035 			res.container? and not Decimal.gt?(res.accounting_quantity_has_numerical_value, 0) ->
   1036 				{:error, "the move events need container resources to have positive accounting-quantity"}
   1037 			res.container? and not Decimal.gt?(res.onhand_quantity_has_numerical_value, 0) ->
   1038 				{:error, "the move events need container resources to have positive onhand-quantity"}
   1039 			res.container? && evt.resource_quantity_has_numerical_value != res.accounting_quantity_has_numerical_value ->
   1040 				{:error, "the move events need to fully move the resource"}
   1041 			res.container? && evt.resource_quantity_has_numerical_value != res.onhand_quantity_has_numerical_value ->
   1042 				{:error, "the move events need to fully move the resource"}
   1043 			res.container? && to_res ->
   1044 				{:error, "you can't move a container resource into another resource"}
   1045 			to_res && evt.provider_id != to_res.primary_accountable_id ->
   1046 				{:error, "you don't have accountability over to-resource-inventoried-as"}
   1047 			to_res && evt.provider_id != to_res.custodian_id ->
   1048 				{:error, "you don't have custody over to-resource-inventoried-as"}
   1049 			to_res && to_res.contained? ->
   1050 				{:error, "you can't move into a contained resource"}
   1051 			to_res && to_res.container? ->
   1052 				{:error, "you can't move into a container resource"}
   1053 			to_res && evt.resource_quantity_has_unit_id != to_res.accounting_quantity_has_unit_id ->
   1054 				{:error, "the unit of resource-quantity must match with the unit of to-resource-inventoried-as"}
   1055 			to_res && res.conforms_to_id != to_res.conforms_to_id ->
   1056 				{:error, "the resources must conform to the same specification"}
   1057 			true ->
   1058 				# some fields of the resource is required for the following multis
   1059 				{:ok, res}
   1060 		end
   1061 	end)
   1062 	|> Multi.merge(fn changes ->
   1063 		res = Map.fetch!(changes, "#{key}.checks")
   1064 		if evt.to_resource_inventoried_as_id do
   1065 			Multi.new()
   1066 			|> Multi.update(key, Changeset.change(evt, previous_event_id: res.previous_event_id))
   1067 			|> Multi.update_all("#{key}.set_and_dec",
   1068 				where(EconomicResource, id: ^evt.resource_inventoried_as_id),
   1069 				set: [previous_event_id: evt.id],
   1070 				inc: [
   1071 					accounting_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
   1072 					onhand_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
   1073 				])
   1074 			|> Multi.update_all("#{key}.inc", where(EconomicResource, id: ^evt.to_resource_inventoried_as_id), inc: [
   1075 				accounting_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value,
   1076 				onhand_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value,
   1077 			])
   1078 		else
   1079 			res_params =
   1080 				(res_params || %{})
   1081 				|> Map.put(:previous_event_id, evt.id)
   1082 				|> Map.put(:primary_accountable_id, evt.receiver_id)
   1083 				|> Map.put(:custodian_id, evt.receiver_id)
   1084 				|> Map.put(:conforms_to_id, res.conforms_to_id)
   1085 				|> Map.put(:accounting_quantity_has_unit_id, evt.resource_quantity_has_unit_id)
   1086 				|> Map.put(:accounting_quantity_has_numerical_value, evt.resource_quantity_has_numerical_value)
   1087 				|> Map.put(:onhand_quantity_has_unit_id, evt.resource_quantity_has_unit_id)
   1088 				|> Map.put(:onhand_quantity_has_numerical_value, evt.resource_quantity_has_numerical_value)
   1089 				|> Map.put(:current_location_id, evt.to_location_id)
   1090 				|> Map.put(:classified_as, evt.resource_classified_as || res.classified_as)
   1091 				|> Map.put_new(:name, res.name)
   1092 				|> Map.put_new(:note, res.note)
   1093 				|> Map.put_new(:tracking_identifier, res.tracking_identifier)
   1094 				|> Map.put_new(:okhv, res.okhv)
   1095 				|> Map.put_new(:repo, res.repo)
   1096 				|> Map.put_new(:version, res.version)
   1097 				|> Map.put_new(:licensor, res.licensor)
   1098 				|> Map.put_new(:license, res.license)
   1099 				|> Map.put_new(:metadata, res.metadata)
   1100 
   1101 			Multi.new()
   1102 			|> EconomicResource.Domain.multi_insert("#{key}.to_eco_res", res_params)
   1103 			|> Multi.update(key, &Changeset.change(evt,
   1104 				to_resource_inventoried_as_id: &1 |> Map.fetch!("#{key}.to_eco_res") |> Map.fetch!(:id),
   1105 				previous_event_id: res.previous_event_id))
   1106 			|> Multi.update_all(:set_and_dec,
   1107 				where(EconomicResource, id: ^evt.resource_inventoried_as_id),
   1108 				set: [previous_event_id: evt.id],
   1109 				inc: [
   1110 					accounting_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
   1111 					onhand_quantity_has_numerical_value: Decimal.negate(evt.resource_quantity_has_numerical_value),
   1112 				])
   1113 			|> Multi.merge(fn %{^key => evt} ->
   1114 				if res.container? do
   1115 					Multi.update_all(Multi.new(), "#{key}.set", where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id), set: [
   1116 						contained_in_id: evt.to_resource_inventoried_as_id,
   1117 						primary_accountable_id: evt.receiver_id,
   1118 						custodian_id: evt.receiver_id,
   1119 					])
   1120 				else
   1121 					Multi.new()
   1122 				end
   1123 			end)
   1124 		end
   1125 	end)
   1126 end
   1127 
   1128 @spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t()
   1129 def multi_update(m, key \\ multi_key(), id, params) do
   1130 	m
   1131 	|> multi_one("#{key}.one", id)
   1132 	|> Multi.update(key,
   1133 		&EconomicEvent.changeset(Map.fetch!(&1, "#{key}.one"), params))
   1134 end
   1135 
   1136 @spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t()
   1137 def multi_delete(m, key \\ multi_key(), id) do
   1138 	m
   1139 	|> multi_one("#{key}.one", id)
   1140 	|> Multi.delete(key, &Map.fetch!(&1, "#{key}.one"))
   1141 end
   1142 end