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