mirror of
https://github.com/ash-project/ash_double_entry.git
synced 2024-09-19 13:03:19 +12:00
improvement: wrap up initial implementaiton, add guides
This commit is contained in:
parent
8c7cf55008
commit
54608d5c76
11 changed files with 210 additions and 121 deletions
13
README.md
13
README.md
|
@ -1,11 +1,13 @@
|
|||
# AshDoubleEntry
|
||||
|
||||
**TODO: Add description**
|
||||
An extensible double entry system built using [Ash](ash-hq.org) resources.
|
||||
|
||||
See the [getting-started-guide](github.com/ash-project/ash_double_entry.git) to
|
||||
setup the project!
|
||||
|
||||
## Installation
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||
by adding `ash_double_entry` to your list of dependencies in `mix.exs`:
|
||||
The package can be installed by adding `ash_double_entry` to your list of dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
|
@ -14,8 +16,3 @@ def deps do
|
|||
]
|
||||
end
|
||||
```
|
||||
|
||||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||
be found at <https://hexdocs.pm/ash_double_entry>.
|
||||
|
||||
|
|
155
documentation/tutorials/get-started-with-double-entry.md
Normal file
155
documentation/tutorials/get-started-with-double-entry.md
Normal file
|
@ -0,0 +1,155 @@
|
|||
# Getting Started with Ash Double Entry
|
||||
|
||||
Ash Double Entry is implemented as a set of Ash resource extensions. You build the resources yourself, and the extensions add the attributes, relationships, actions and validations required for them to constitute a double entry system.
|
||||
|
||||
## What makes it special?
|
||||
|
||||
1. Account balances are updated automatically as transfers are introduced.
|
||||
2. Arbitrary custom validations and behavior by virtue of modifying your own resources.
|
||||
3. Transactions can be entered in the past, and all future balances are updated (and therefore validated).
|
||||
|
||||
## Setup
|
||||
|
||||
### Define your account resource
|
||||
|
||||
#### Example
|
||||
|
||||
```elixir
|
||||
defmodule YourApp.Account do
|
||||
use Ash.Resource,
|
||||
data_layer: AshPostgres.DataLayer,
|
||||
extensions: [AshDoubleEntry.Account]
|
||||
|
||||
postgres do
|
||||
table "accounts"
|
||||
repo YourApp.Repo
|
||||
end
|
||||
|
||||
account do
|
||||
# configure the other resources it will interact with
|
||||
transfer_resource YourApp.Transfer
|
||||
balance_resource YourApp.Balance
|
||||
# accept custom attributes in the autogenerated `open` create action
|
||||
open_action_accept [:account_number]
|
||||
end
|
||||
|
||||
attributes do
|
||||
# Add custom attributes
|
||||
attribute :account_number, :string do
|
||||
allow_nil? false
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### What does this extension do?
|
||||
|
||||
- Adds the following attributes:
|
||||
- `:id`, a `:uuid` primary key
|
||||
- `:currency`, a `:string` representing the currency of the transfer
|
||||
- `:inserted_at`, a `:utc_datetime_usec` timestamp
|
||||
- `:identifier`, a `:string` and a unique identifier for the account
|
||||
- Adds the following actions:
|
||||
- A primary read called `:read`, unless a primary read action already exists.
|
||||
- A create action called `open`, that accepts `identifier`, `currency`, and the attributes in `open_action_accept`
|
||||
- A read action called `:lock_accounts` that can be used to lock a list of accounts while in a transaction(for data layers that support it)
|
||||
- Adds a `has_many` relationship called `balances`, referring to all related balances of an account
|
||||
- Adds an aggregate called `balance`, referring to the latest balance as a `decimal` for that account
|
||||
- Adds the following calculations:
|
||||
- A `balance_as_of_ulid` calculation that takes an argument called `ulid`, which corresponds to a transfer id and returns the balance as a
|
||||
decimal.
|
||||
- A `balance_as_of` calculation that takes a `utc_datetime_usec` and returns the balance as of that datetime.
|
||||
- Adds an identity called `unique_identifier` that ensures `identifier` is unique.
|
||||
|
||||
### Define your transfer resource
|
||||
|
||||
#### Example
|
||||
|
||||
```elixir
|
||||
defmodule YourApp.Transfer do
|
||||
use Ash.Resource,
|
||||
data_layer: AshPostgres.DataLayer,
|
||||
extensions: [AshDoubleEntry.Transfer]
|
||||
|
||||
postgres do
|
||||
table "transfers"
|
||||
repo YourApp.Repo
|
||||
end
|
||||
|
||||
transfer do
|
||||
# configure the other resources it will interact with
|
||||
account_resource YourApp.Account
|
||||
balance_resource YourApp.Balance
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### What does this extension do?
|
||||
|
||||
- Adds the following attributes
|
||||
- `:id`, a `AshDoubleEntry.ULID` primary key which is sortable based on the `timestamp` of the transfer.
|
||||
- `:amount`, a `:decimal` representing the amount of the transfer
|
||||
- `:timestamp`, a `:utc_datetime_usec` representing when the transfer occurred
|
||||
- `:inserted_at`, a `:utc_datetime_usec` timestamp
|
||||
- Adds the following relationships
|
||||
- `:from_account`, a `belongs_to` relationship of the account the transfer is from
|
||||
- `:to_account`, a `belongs_to` relationship of the account the transfer is to
|
||||
- Adds a `:read` action called `:read_transfers` with keyset pagination enabled. Required for streaming transfers, used for validating balances.
|
||||
- Adds a change that runs on all create and update actions that reifies the balances table. It inserts a balance for the transfer, and updates any affected future balances.
|
||||
|
||||
### Define your balance resource
|
||||
|
||||
#### Example
|
||||
|
||||
```elixir
|
||||
defmodule YourApp.Balance do
|
||||
use Ash.Resource,
|
||||
data_layer: AshPostgres.DataLayer,
|
||||
extensions: [AshDoubleEntry.Balance]
|
||||
|
||||
postgres do
|
||||
table "balances"
|
||||
repo YourApp.Repo
|
||||
end
|
||||
|
||||
balance do
|
||||
# configure the other resources it will interact with
|
||||
transfer_resource Transfer
|
||||
account_resource Account
|
||||
end
|
||||
|
||||
changes do
|
||||
# add custom behavior. In this case, we're preventing certain balances from being less than zero
|
||||
change after_action(&validate_balance/2)
|
||||
end
|
||||
|
||||
defp validate_balance(changeset, result) do
|
||||
account = result |> changeset.api.load!(:account) |> Map.get(:account)
|
||||
|
||||
if account.allow_zero_balance == false && Decimal.negative?(result.balance) do
|
||||
{:error,
|
||||
Ash.Error.Changes.InvalidAttribute.exception(
|
||||
value: result.balance,
|
||||
field: :balance,
|
||||
message: "balance cannot be negative"
|
||||
)}
|
||||
else
|
||||
{:ok, result}
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### What does this extension do?
|
||||
|
||||
- Adds the following attributes:
|
||||
- `:id`, a `:uuid` primary key
|
||||
- `:balance`, the balance as a decimal of the account at the time of the related transfer
|
||||
- Adds the following relationships:
|
||||
- `:transfer` a `:belongs_to` relationship, pointing to the transfer that this balance is as of.
|
||||
- `:account` a `:belongs_to` relationship, pointing to the account the balance is for
|
||||
- Adds the following actions:
|
||||
- a primary read action called `:read`, if a priamry read action doesn't
|
||||
exist
|
||||
- a create action caleld `:upsert_balance`, which will create or update the relevant balance, by `transfer_id` and `account_id`
|
||||
- Adds an identity that ensures that `account_id` and `transfer_id` are unique
|
|
@ -8,29 +8,19 @@ defmodule AshDoubleEntry.Account.Transformers.AddStructure do
|
|||
def transform(dsl) do
|
||||
dsl
|
||||
|> add_primary_read_action()
|
||||
|> add_balance_as_of_ulid_calculation()
|
||||
|> add_balance_as_of_calculation()
|
||||
|> Ash.Resource.Builder.add_attribute(:identifier, :string, allow_nil?: false)
|
||||
|> Ash.Resource.Builder.add_aggregate(:balance, :first, [:balances],
|
||||
field: :balance,
|
||||
default: Decimal.new(0),
|
||||
sort: [transfer_id: :desc]
|
||||
|> Ash.Resource.Builder.add_attribute(:id, :uuid,
|
||||
primary_key?: true,
|
||||
writable?: false,
|
||||
generated?: true,
|
||||
allow_nil?: false,
|
||||
default: &Ash.UUID.generate/0
|
||||
)
|
||||
|> Ash.Resource.Builder.add_attribute(:identifier, :string, allow_nil?: false)
|
||||
|> Ash.Resource.Builder.add_attribute(
|
||||
:currency,
|
||||
:string,
|
||||
allow_nil?: false
|
||||
)
|
||||
|> Ash.Resource.Builder.add_attribute(
|
||||
:must_be_positive,
|
||||
:boolean,
|
||||
allow_nil?: false,
|
||||
default: true
|
||||
)
|
||||
|> Ash.Resource.Builder.add_attribute(:inserted_at, :utc_datetime_usec,
|
||||
allow_nil?: false,
|
||||
default: &DateTime.utc_now/0
|
||||
)
|
||||
|> Ash.Resource.Builder.add_action(:create, :open,
|
||||
accept:
|
||||
Enum.uniq(
|
||||
|
@ -44,15 +34,25 @@ defmodule AshDoubleEntry.Account.Transformers.AddStructure do
|
|||
)
|
||||
]
|
||||
)
|
||||
|> Ash.Resource.Builder.add_identity(:unique_identifier, [:identifier],
|
||||
pre_check_with: pre_check_with(dsl)
|
||||
|> Ash.Resource.Builder.add_attribute(:inserted_at, :utc_datetime_usec,
|
||||
allow_nil?: false,
|
||||
default: &DateTime.utc_now/0
|
||||
)
|
||||
|> Ash.Resource.Builder.add_aggregate(:balance, :first, [:balances],
|
||||
field: :balance,
|
||||
default: Decimal.new(0),
|
||||
sort: [transfer_id: :desc]
|
||||
)
|
||||
|> Ash.Resource.Builder.add_relationship(
|
||||
:has_many,
|
||||
:balances,
|
||||
AshDoubleEntry.Account.Info.account_balance_resource!(dsl),
|
||||
destination_attribute: :account_id,
|
||||
source_attribute: :id
|
||||
destination_attribute: :account_id
|
||||
)
|
||||
|> add_balance_as_of_ulid_calculation()
|
||||
|> add_balance_as_of_calculation()
|
||||
|> Ash.Resource.Builder.add_identity(:unique_identifier, [:identifier],
|
||||
pre_check_with: pre_check_with(dsl)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -92,19 +92,6 @@ defmodule AshDoubleEntry.Account.Transformers.AddStructure do
|
|||
)
|
||||
end
|
||||
|
||||
defbuilder add_balance_aggregate(dsl) do
|
||||
Ash.Resource.Builder.add_aggregate(
|
||||
dsl,
|
||||
:balance,
|
||||
:first,
|
||||
[:balances],
|
||||
field: :balance,
|
||||
query: [
|
||||
sort: [ulid: :desc]
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
defbuilder add_primary_read_action(dsl) do
|
||||
if Ash.Resource.Info.primary_action(dsl, :read) do
|
||||
{:ok, dsl}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
defmodule AshDoubleEntry do
|
||||
@moduledoc false
|
||||
end
|
||||
|
|
|
@ -7,7 +7,6 @@ defmodule AshDoubleEntry.Balance.Transformers.AddStructure do
|
|||
|
||||
def transform(dsl) do
|
||||
dsl
|
||||
|> add_primary_read_action()
|
||||
|> Ash.Resource.Builder.add_attribute(:id, :uuid,
|
||||
primary_key?: true,
|
||||
writable?: false,
|
||||
|
@ -15,6 +14,7 @@ defmodule AshDoubleEntry.Balance.Transformers.AddStructure do
|
|||
allow_nil?: false,
|
||||
default: &Ash.UUID.generate/0
|
||||
)
|
||||
|> Ash.Resource.Builder.add_attribute(:balance, :decimal, allow_nil?: false)
|
||||
|> Ash.Resource.Builder.add_relationship(
|
||||
:belongs_to,
|
||||
:transfer,
|
||||
|
@ -30,15 +30,15 @@ defmodule AshDoubleEntry.Balance.Transformers.AddStructure do
|
|||
allow_nil?: false,
|
||||
attribute_writable?: true
|
||||
)
|
||||
|> Ash.Resource.Builder.add_attribute(:balance, :decimal, allow_nil?: false)
|
||||
|> Ash.Resource.Builder.add_identity(:unique_references, [:account_id, :transfer_id],
|
||||
pre_check_with: pre_check_with(dsl)
|
||||
)
|
||||
|> add_primary_read_action()
|
||||
|> Ash.Resource.Builder.add_action(:create, :upsert_balance,
|
||||
accept: [:balance, :account_id, :transfer_id],
|
||||
upsert?: true,
|
||||
upsert_identity: :unique_references
|
||||
)
|
||||
|> Ash.Resource.Builder.add_identity(:unique_references, [:account_id, :transfer_id],
|
||||
pre_check_with: pre_check_with(dsl)
|
||||
)
|
||||
end
|
||||
|
||||
defbuilder add_primary_read_action(dsl) do
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
defmodule AshDoubleEntry.Transfer.Changes.SetAmounts do
|
||||
use Ash.Resource.Change
|
||||
|
||||
def change(changeset, _, _) do
|
||||
case Ash.Changeset.fetch_argument(changeset, :amount) do
|
||||
{:ok, amount} when not is_nil(amount) ->
|
||||
changeset
|
||||
|> Ash.Changeset.force_change_new_attribute(:to_amount, amount)
|
||||
|> Ash.Changeset.force_change_new_attribute(:from_amount, amount)
|
||||
|
||||
_ ->
|
||||
cond do
|
||||
is_nil(Ash.Changeset.get_attribute(changeset, :to_amount)) ->
|
||||
{:error,
|
||||
Ash.Error.Changes.Required.exception(
|
||||
field: :to_amount,
|
||||
type: :attribute,
|
||||
resource: changeset.resource
|
||||
)}
|
||||
|
||||
is_nil(Ash.Changeset.get_attribute(changeset, :from_amount)) ->
|
||||
{:error,
|
||||
Ash.Error.Changes.Required.exception(
|
||||
field: :from_amount,
|
||||
type: :attribute,
|
||||
resource: changeset.resource
|
||||
)}
|
||||
|
||||
true ->
|
||||
changeset
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -13,8 +13,7 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do
|
|||
|> Ash.Changeset.before_action(fn changeset ->
|
||||
from_account_id = Ash.Changeset.get_attribute(changeset, :from_account_id)
|
||||
to_account_id = Ash.Changeset.get_attribute(changeset, :to_account_id)
|
||||
from_amount = Ash.Changeset.get_attribute(changeset, :from_amount)
|
||||
to_amount = Ash.Changeset.get_attribute(changeset, :to_amount)
|
||||
amount = Ash.Changeset.get_attribute(changeset, :amount)
|
||||
timestamp = Ash.Changeset.get_attribute(changeset, :timestamp)
|
||||
|
||||
timestamp =
|
||||
|
@ -26,27 +25,21 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do
|
|||
ulid = AshDoubleEntry.ULID.generate(timestamp)
|
||||
|
||||
accounts =
|
||||
try do
|
||||
changeset.resource
|
||||
|> AshDoubleEntry.Transfer.Info.transfer_account_resource!()
|
||||
|> Ash.Query.filter(id in ^[from_account_id, to_account_id])
|
||||
|> Ash.Query.for_read(:lock_accounts)
|
||||
|> Ash.Query.load(balance_as_of_ulid: %{ulid: ulid})
|
||||
|> changeset.api.read!(authorize?: false, tracer: context[:tracer])
|
||||
rescue
|
||||
e ->
|
||||
IO.puts(Exception.format(:error, e, __STACKTRACE__))
|
||||
[]
|
||||
end
|
||||
changeset.resource
|
||||
|> AshDoubleEntry.Transfer.Info.transfer_account_resource!()
|
||||
|> Ash.Query.filter(id in ^[from_account_id, to_account_id])
|
||||
|> Ash.Query.for_read(:lock_accounts)
|
||||
|> Ash.Query.load(balance_as_of_ulid: %{ulid: ulid})
|
||||
|> changeset.api.read!(authorize?: false, tracer: context[:tracer])
|
||||
|
||||
from_account = Enum.find(accounts, &(&1.id == from_account_id))
|
||||
to_account = Enum.find(accounts, &(&1.id == to_account_id))
|
||||
|
||||
new_from_account_balance =
|
||||
Decimal.sub(from_account.balance_as_of_ulid, from_amount)
|
||||
Decimal.sub(from_account.balance_as_of_ulid, amount)
|
||||
|
||||
new_to_account_balance =
|
||||
Decimal.add(to_account.balance_as_of_ulid, to_amount)
|
||||
Decimal.add(to_account.balance_as_of_ulid, amount)
|
||||
|
||||
changeset.resource
|
||||
|> AshDoubleEntry.Transfer.Info.transfer_balance_resource!()
|
||||
|
@ -80,8 +73,7 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do
|
|||
|> Ash.Changeset.set_context(%{
|
||||
from_account: from_account,
|
||||
to_account: to_account,
|
||||
from_amount: from_amount,
|
||||
to_amount: to_amount
|
||||
amount: amount
|
||||
})
|
||||
end)
|
||||
|> Ash.Changeset.after_action(fn changeset, result ->
|
||||
|
@ -98,17 +90,17 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do
|
|||
%{
|
||||
account_id: balance.account_id,
|
||||
transfer_id: balance.transfer_id,
|
||||
balance: Decimal.sub(balance.balance, changeset.context.from_amount)
|
||||
balance: Decimal.sub(balance.balance, changeset.context.amount)
|
||||
}
|
||||
else
|
||||
%{
|
||||
account_id: balance.account_id,
|
||||
transfer_id: balance.transfer_id,
|
||||
balance: Decimal.add(balance.balance, changeset.context.to_amount)
|
||||
balance: Decimal.add(balance.balance, changeset.context.amount)
|
||||
}
|
||||
end
|
||||
end)
|
||||
|> changeset.api.bulk_create(
|
||||
|> changeset.api.bulk_create!(
|
||||
AshDoubleEntry.Transfer.Info.transfer_balance_resource!(changeset.resource),
|
||||
:upsert_balance,
|
||||
context_to_opts(context,
|
||||
|
@ -119,7 +111,6 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do
|
|||
)
|
||||
|
||||
{:ok, result}
|
||||
# do verification logic
|
||||
end)
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ defmodule AshDoubleEntry.Transfer.Transformers.AddStructure do
|
|||
use Spark.Dsl.Transformer
|
||||
|
||||
def before?(Ash.Resource.Transformers.CachePrimaryKey), do: true
|
||||
def before?(Ash.Resource.Transformers.BelongsToSourceField), do: true
|
||||
def before?(Ash.Resource.Transformers.BelongsToAttribute), do: true
|
||||
def before?(_), do: false
|
||||
|
||||
def transform(dsl) do
|
||||
|
@ -12,8 +14,7 @@ defmodule AshDoubleEntry.Transfer.Transformers.AddStructure do
|
|||
default: &AshDoubleEntry.ULID.generate/0,
|
||||
generated?: false
|
||||
)
|
||||
|> Ash.Resource.Builder.add_attribute(:from_amount, :decimal, allow_nil?: false)
|
||||
|> Ash.Resource.Builder.add_attribute(:to_amount, :decimal, allow_nil?: false)
|
||||
|> Ash.Resource.Builder.add_attribute(:amount, :decimal, allow_nil?: false)
|
||||
|> Ash.Resource.Builder.add_attribute(:timestamp, :utc_datetime_usec,
|
||||
allow_nil?: false,
|
||||
default: &DateTime.utc_now/0
|
||||
|
@ -34,19 +35,12 @@ defmodule AshDoubleEntry.Transfer.Transformers.AddStructure do
|
|||
AshDoubleEntry.Transfer.Info.transfer_account_resource!(dsl),
|
||||
attribute_writable?: true
|
||||
)
|
||||
|> Ash.Resource.Builder.add_change({AshDoubleEntry.Transfer.Changes.VerifyTransfer, []})
|
||||
|> Ash.Resource.Builder.add_action(:create, :transfer,
|
||||
accept: [:to_amount, :from_amount, :timestamp, :from_account_id, :to_account_id],
|
||||
allow_nil_input: [:to_amount, :from_amount],
|
||||
arguments: [
|
||||
Ash.Resource.Builder.build_action_argument(:amount, :decimal)
|
||||
],
|
||||
changes: [
|
||||
Ash.Resource.Builder.build_action_change({AshDoubleEntry.Transfer.Changes.SetAmounts, []})
|
||||
]
|
||||
accept: [:amount, :timestamp, :from_account_id, :to_account_id]
|
||||
)
|
||||
|> Ash.Resource.Builder.add_action(:read, :read_transfers,
|
||||
pagination: Ash.Resource.Builder.build_pagination(keyset?: true)
|
||||
)
|
||||
|> Ash.Resource.Builder.add_change({AshDoubleEntry.Transfer.Changes.VerifyTransfer, []})
|
||||
end
|
||||
end
|
||||
|
|
8
mix.exs
8
mix.exs
|
@ -1,10 +1,12 @@
|
|||
defmodule AshDoubleEntry.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
@version "0.1.0"
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :ash_double_entry,
|
||||
version: "0.1.0",
|
||||
version: @version,
|
||||
elixir: "~> 1.15",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
consolidate_protocols: Mix.env() != :test,
|
||||
|
@ -33,9 +35,7 @@ defmodule AshDoubleEntry.MixProject do
|
|||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:ash, github: "ash-project/ash"}
|
||||
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||
{:ash, "~> 2.14"}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -1,5 +1,5 @@
|
|||
%{
|
||||
"ash": {:git, "https://github.com/ash-project/ash.git", "6daae630f446441a314af162dae23154502e9fb8", []},
|
||||
"ash": {:hex, :ash, "2.14.2", "111829b1db52e43c28c0660b2da8f1bfc76199dd8e15289f86f5998c07fb8d36", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e6e5924ccb3f8b0ab5de15b22006a4a18c0f002aadadd7aa9dfa39d53cff0b9a"},
|
||||
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
|
||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
|
||||
|
|
|
@ -15,8 +15,6 @@ defmodule AshDoubleEntryTest do
|
|||
end
|
||||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
|
||||
attribute :allow_zero_balance, :boolean do
|
||||
default true
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue