Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/remove backup management #22

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 35 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

## Features
* Use three basic transactions to increase/decrease accounts amount:
* Deposit an amount to backup account.
* Extract an amount from backup account.
* Deposit an amount to account.
* Extract an amount from account.
* Transfer an amount between accounts (This include foreign exchange when necessary).
* Includes a simple DSL to create custom transactions at runtime.
* Manage currencies, exchange rates, accounts, users, transactions and entities.
Expand Down Expand Up @@ -52,7 +52,7 @@

This guide introduces you how can interact with points platform through easy examples. Adicionally, I'll show you how to configure the server api.

### User Roles
### User Roles (under cosstruction)

Each user has a role that allow perform different actions on the platform. Next I'll show each role and its associated actions.

Expand All @@ -65,32 +65,17 @@ Is a platform user. They can:
Cant be created by a _system_admin_ user only and their actions modify the context of the entity.
An entity_admin:
* Could administrate many entities.
* Can deposit/extract amount to/from backup accounts under an entity.
* Can manage custom transations that belong to entity.
* Can manage partner associations.
* Is an account issuer, this means that can manage default accounts under an entity. Keep in mind that when an entity_admin user creates an account, **that account belong to same issuer entity**.
* Is an account issuer, this means that can manage normal user accounts under an entity. Keep in mind that when an entity_admin user creates an account, **that account belong to same issuer entity**.

#### system_admin
Can perfom all functions like a root user on Linux OS.

### Account types

There are two account types.

#### Default accounts

These accounts belongs to a _normal_user_ could be used to trasnfer amounts between accounts only.

#### Backup accounts

These accounts contain money in real currency only. They are used to support amounts in virtual currency in other accounts (default accounts). An emiter entity has one backup account by real currency and many default accounts by user registered in itself. These default accounts has money y real or virtual currency but all supported by entity backup accounts.
Can perfom all functions like a root user on Linux OS.

#### Accounts and allow movements
### Account

You can:
* Deposit an amount in real currency to backup account.
* Extract an amount in real currency from backup amount.
* And transfer an amount between backup/default accounts.
Belongs to an user and contains amount in a given currency.

### Mix Tasks

Expand All @@ -101,7 +86,7 @@ Let's begin by run next command under _points_ path:
```bash
$ mix help | grep cli
mix cli.accounts # Show accounts. Params: token
mix cli.accounts.create # Create an account. Params: token owner_email currency_code type(Optional: default/backup)
mix cli.accounts.create # Create an account. Params: token owner_email currency_code
mix cli.accounts.delete # Delete an account. Params: token owner_email currency_code
mix cli.accounts.show # Show an account. Params: token owner_email currency_code
mix cli.currencies # Show currencies. Params: token
Expand Down Expand Up @@ -278,15 +263,14 @@ $ mix cli.currencies.delete $TOKEN PTS
*Step 1:* Create an account for [email protected] under PTS currency.
```bash
$ mix help | grep cli.accounts.create
mix cli.accounts.create # Create an account. Params: token owner_email currency_code type(Optional: default/backup)
mix cli.accounts.create # Create an account. Params: token owner_email currency_code
$ mix cli.accounts.create $TOKEN [email protected] PTS
13:41:26.641 [info] Response - Status: 201, Body: {
"amount": "0.00",
"currency": "PTS",
"id": 4,
"issuer_email": "[email protected]",
"owner_email": "[email protected]",
"type": "default"
"owner_email": "[email protected]"
}
```

Expand All @@ -300,8 +284,7 @@ $ mix cli.accounts.show $TOKEN [email protected] PTS
"currency": "PTS",
"id": 4,
"issuer_email": "[email protected]",
"owner_email": "[email protected]",
"type": "default"
"owner_email": "[email protected]"
}
```

Expand Down Expand Up @@ -385,7 +368,7 @@ $ mix cli.entities.delete $TOKEN XYZ

##### Primitive transactions

*Deposit:* Deposit an amount to backup account.
*Deposit:* Deposit an amount to account.
```bash
$ mix help | grep cli.transactions.exec.deposit
mix cli.transactions.exec.deposit # Deposit. Params: token params(as json: '{...}')
Expand All @@ -395,14 +378,13 @@ $ mix cli.transactions.exec.deposit $TOKEN '{"to":{"email":"[email protected]
"source": "non",
"target": {
"amount": "19990.00",
"currency": "ARS",
"type": "backup"
"currency": "ARS"
},
"type": "deposit"
}
```

*Extract:* Extract an amount from backup account.
*Extract:* Extract an amount from account.
```bash
$ mix help | grep cli.transactions.exec.extract
mix cli.transactions.exec.extract # Extract. Params: token params(as json: '{...}')
Expand All @@ -411,8 +393,7 @@ $ mix cli.transactions.exec.extract $TOKEN '{"from":{"email":"adrianmarino@gmail
"amount": "100.00",
"source": {
"amount": "9890.00",
"currency": "ARS",
"type": "backup"
"currency": "ARS"
},
"target": "non",
"type": "extract"
Expand Down Expand Up @@ -518,30 +499,40 @@ To complete

##### Exercise 1: X company offer points to its clients

Suppose that X company sell flights through a web site and would like to grant points for each time that a client buy a flight giving their clients the opportunity to use these points in the following purchase.
Suppose that _X company_ sell flights through a web site and would like to grant points for each time that a client buy a flight giving their clients the opportunity to use these points in the following purchase.

_Guidelines:_
1. The entity grant points to client from a backup amount in real money.
1. The entity grant points to clients from an issuer account under a real currency.
2. Points can be tranfered between clients.
3. Then a client spent N points there are transfered to an entity acount to control the amount of spent points.
3. When a client spent N points there are transfered to an entity acount to control the amount of spent points.

How we implement this with _points_?
You can see the exercise resolution in an executable scenario in [scripts/exercise_1](scripts/exercise_1) script. You must execute next command to run the exercise:
You can see the exercise resolution in an executable scenario in [exercise_1](scripts/exercises/exercise_1) script. You must execute next command to run the exercise:
```bash
$ bash scripts/exercise_1
$ bash scripts/exercises/exercise_1
```

##### Exercise 2: Share points between X and Y companies
##### Exercise 2: Share points between A and B companies

Suppose that _A company_ sell products of any type through a web site and
offer to _B company_ share points between their giving their clients
the opportunity to use these points(A+B) in the following purchase
in either company.

Suppose that Y company sell products of any type also through a web site and offer to X company
share points between their giving their clients the opportunity to use these points(X+Y) in the following purchase in either company.
_Guidelines:
1. The entity grant points to clients from an issuer account under a real currency.
2. Points can be tranfered between clients of both compaines.
3. Then a client spent N points there are transfered to an entity acount to control the amount of spent points.

How we implement this with _points_?
To complete
You can see the exercise resolution in an executable scenario under [exercise_2](scripts/exercises/exercise_2) script. You must execute next command to run the exercise:
```bash
$ bash scripts/exercises/exercise_2
```

##### Exercise 3: X company offer buy with points that belong to Y company clients.

This example differs from _Exercise 1_ in that X company give their clients the opportunity to use points of Y company.
This example differs from _Exercise 1_ in that _X company_ give their clients the opportunity to use points of _Y company_.

How we implement this with _points_?
To complete
Expand Down
2 changes: 1 addition & 1 deletion lib/mix/tasks/client/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule Mix.Tasks.Cli.Accounts do
end
defmodule Create do
use Mix.Task.Point.Client
@shortdoc "Create an account. Params: token owner_email currency_code type(Optional: default/backup)"
@shortdoc "Create an account. Params: token owner_email currency_code"
defrun fn([token | account]) -> points(base_url(), token) |> accounts(create: Account.create(account)) end
end
defmodule Delete do
Expand Down
7 changes: 2 additions & 5 deletions lib/point/client/dtos.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,9 @@ defmodule Point.Client.Dto do
end

defmodule Account do
defstruct [:type, :owner_email, :currency_code]
def create([owner_email, currency_code, type]) do
%Account{type: type, owner_email: owner_email, currency_code: currency_code}
end
defstruct [:owner_email, :currency_code]
def create([owner_email, currency_code]) do
%Account{type: "default", owner_email: owner_email, currency_code: currency_code}
%Account{owner_email: owner_email, currency_code: currency_code}
end
end

Expand Down
3 changes: 2 additions & 1 deletion lib/point/extensions/to_string.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ defimpl String.Chars, for: Map do
def to_string(value), do: JSON.to_pretty_json(value)
end

# Workaround to remoce a end zero in decimal number
defimpl String.Chars, for: Decimal do
def to_string(value), do: Decimal.to_string(Decimal.round(value, 2))
def to_string(value), do: Decimal.to_string(Decimal.reduce(value), :normal)
end

# Domain types
Expand Down
18 changes: 2 additions & 16 deletions lib/point/services/account_service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,7 @@ defmodule Point.AccountService do
import Ecto.Query
import Decimal
import Point.DecimalUtil, only: [is: 2, zero: 0]
import Enum, only: [map: 2, reduce: 3]
alias Point.{Repo, Account, Currency, User, ExchangeRateService}

def system_amount(backup_account) do
Repo.all(from a in Account, where: a.issuer_id == ^backup_account.owner_id and a.type == "default" )
|> map(fn(account)->
{:ok, rate } = ExchangeRateService.rate_between(account, backup_account)
Decimal.mult(Decimal.new(account.amount), rate)
end)
|> reduce(Decimal.new(0), &Decimal.add/2)
end
alias Point.{Repo, Account, Currency, User}

# Crud
def all, do: Repo.all(Account)
Expand Down Expand Up @@ -53,10 +43,6 @@ defmodule Point.AccountService do

def count_by(entity: entity), do: Repo.one(from a in by_entity_query(entity), select: count(a.id))

def backup_account_of(account) do
Repo.one(from a in Account, where: a.type == "backup" and a.owner_id == ^account.issuer_id)
end

def increase_changeset(account, amount), do: Account.changeset(account, %{amount: add(account.amount, new(amount))})

def decrease_changeset(account, amount), do: Account.changeset(account, %{amount: sub(account.amount, new(amount))})
Expand All @@ -68,5 +54,5 @@ defmodule Point.AccountService do
where: a.issuer_id == u.id and u.id == ue.user_id and ue.entity_id == ^entity.id
end

defp insert_changeset(params), do: Account.insert_changeset(%Account{}, Map.merge(%{"type" => "default"}, params))
defp insert_changeset(params), do: Account.insert_changeset(%Account{}, params)
end
9 changes: 3 additions & 6 deletions lib/point/services/movement/deposit_service.ex
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
defmodule Point.DepositService do
alias Point.Account
alias Ecto.Multi

import PointLogger
import Point.Repo
import Point.MovementFactory
import Point.AccountService
alias Point.Account
alias Ecto.Multi

def deposit(amount: _, to: %Account{type: "default"} = _),
do: raise "Deposite only is supported in default accounts!"
def deposit(amount: amount, to: %Account{type: "backup"} = account) do
def deposit(amount: amount, to: %Account{} = account) do
{:ok, %{deposit: movement}} = Multi.new
|> Multi.update(:increase_amount, increase_changeset(account, amount))
|> Multi.insert(:deposit, deposit(account, amount))
Expand Down
17 changes: 7 additions & 10 deletions lib/point/services/movement/extract_service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@ defmodule Point.ExtractService do
alias Point.Account
alias Ecto.Multi

def extract(amount: _, from: %Account{type: "default"} = account) do
raise "Must not extract amount from '#{assoc(account, :owner).email}' default account!. Only allowed with backup accounts."
end
def extract(amount: amount, from: %Account{type: "backup"} = account) do
{:ok, %{extract: movement}} = Multi.new
|> Multi.update(:decrease_amount, decrease_changeset(account, amount))
|> Multi.insert(:extract, extract(account, amount))
|> transaction
def extract(amount: amount, from: %Account{} = account) do
{:ok, %{extract: movement}} = Multi.new
|> Multi.update(:decrease_amount, decrease_changeset(account, amount))
|> Multi.insert(:extract, extract(account, amount))
|> transaction

debug(movement)
movement
debug(movement)
movement
end
end
7 changes: 0 additions & 7 deletions lib/point/services/movement/transfer_service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,10 @@ defmodule Point.TransferService do
amount: amount) do
assert_transfer_allowed(between: source, and: target)

backup_target = backup_account_of(target)
backup_source = backup_account_of(source)
backup_amount = Decimal.mult(amount, rate_between(source, backup_source))

{:ok, result} = Multi.new
|> append(backup_source, backup_target, backup_amount)
|> append(source, target, amount)
|> transaction

backup_data = result[move_name(backup_source, backup_target)]
if backup_data, do: debug(backup_data)
result[move_name(source, target)]
end

Expand Down
2 changes: 1 addition & 1 deletion lib/point/util/http_potion.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ defmodule Point.HTTPotion.Logger do

case Config.get(:http_potion_log_request_as_info_level) do
:yes -> info(message)
_ -> debug(message)
_ -> info(message)
end
end

Expand Down
10 changes: 1 addition & 9 deletions lib/point/util/model.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,13 @@ end
defimpl Point.ModelMap, for: Point.Account do
alias Point.{Repo, ModelMap}

def to_map(%{type: "default"} = account) do
def to_map(account) do
%{
owner: ModelMap.to_map(Repo.assoc account, :owner),
amount: to_string(account.amount),
currency: ModelMap.to_map(Repo.assoc account, :currency)
}
end

def to_map(%{type: "backup"} = account) do
%{
type: account.type,
currency: ModelMap.to_map(Repo.assoc account, :currency),
amount: to_string(account.amount)
}
end
end
defimpl Point.ModelMap, for: Point.ExchangeRate do
alias Point.{ModelMap, Repo}
Expand Down
15 changes: 0 additions & 15 deletions priv/repo/migrations/20161127203254_add_type_to_account.exs

This file was deleted.

6 changes: 3 additions & 3 deletions priv/repo/seeds.exs
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ insert! %Partner{entity: empire, partner: rebelion}
initial_amount = Decimal.new(10000)

insert! %Account{
amount: initial_amount, type: "backup", currency: ars,
amount: initial_amount, currency: ars,
owner: chewbacca, issuer: chewbacca, entity: platform
}
insert! %Account{
amount: initial_amount, type: "default", currency: rebel_point,
amount: initial_amount, currency: rebel_point,
owner: obiwan_kenoby, issuer: chewbacca, entity: empire
}
insert! %Account{
amount: initial_amount, type: "default", currency: empire_point,
amount: initial_amount, currency: empire_point,
owner: anakin_skywalker, issuer: chewbacca, entity: empire
}
Loading