Частина 3. Створення ланцюжків
У цій статті ми покажемо, як визначати схеми бізнес-об'єктів підприємства та ієрархічну структуру ідентифікаторів ланцюжків цих об'єктів. Це визначає консольну взаємодію користувача та інформаційної системи.
Типові специфікації
Схема включає у себе всю типову інформацію про бізнес-об'єкти. Схема ERP модуля визначає базові об'єкти організаційної структури, об'єкти платіжної системи PAY та систему обліку співробітників ACC.
ERP SPEC — Таблиці организаційної структури
-type locationType() :: normal | extra.
-record('Loc',
{ id = kvs:seq([],[]) :: [] | term(),
code = [] :: [] | term(),
country = [] :: [] | binary(),
city = [] :: [] | binary(),
address = [] :: [] | binary(),
type = [] :: locationType() }).
-record('Branch',
{ id = kvs:seq([],[]) :: [] | term(),
loc = [] :: [] | #'Loc'{} }).
-record('Inventory',
{ id = [] :: [] | binary(),
name = [] :: [] | binary(),
branch = [] :: [] | #'Branch'{},
type = [] :: term() }).
-record('Organization',
{ name = [] :: [] | binary(),
url = [] :: [] | string(),
location = [] :: [] | #'Loc'{},
type = [] :: term() }).
-record('Person',
{ id = kvs:seq([],[]) :: [] | term(),
cn = [] :: [] | binary(),
name = [] :: [] | binary(),
displayName = [] :: [] | binary(),
location = [] :: #'Loc'{},
type = [] :: term() }).
-record('Employee',
{ id = kvs:seq([],[]) :: [] | binary(),
person = [] :: [] | #'Person'{},
org = [] :: [] | #'Organization'{},
branch = [] :: [] | #'Branch'{},
type = [] :: term() }).
PAY SPEC — Таблиці системи управління платежами
Гроші зберігаються у форматі {N,M}, де N — кількість знаків після коми, а M — усі значущі цифри. Таким чином числа кодуються багатьма способами, наприклад одиниця: 1 = {0,1} = {1,10} = {2,100}. Операція множення у такій системі виглядає просто mul({A,B},{C,D}) -> {A+C,B*D}.
-type fraction_length() :: integer().
-type digits() :: integer().
-type money() :: {fraction_length(),digits()}.
-record('Payment',
{ invoice = [] :: [] | term(),
volume = [] :: [] | money(),
price = {0,1} :: money(),
instrument = [] :: term(),
type = [] :: paymentType(),
from = [] :: term(),
to = [] :: term() }).
PLM SPEC — Таблиці системи управління життєвим циклом
-record('Acc',
{ id = [] :: [] | binary() | list(),
rate = {0,0} :: money() }).
-record('Product',
{ code = [] :: [] | term(),
id = kvs:seq([],[]) :: [] | binary(),
url = [] :: [] | binary() | list(),
engineer = [] :: [] | #'Person'{},
director = [] :: [] | #'Person'{},
owner = [] :: [] | #'Person'{},
organization = [] :: [] | #'Organization'{},
type = [] :: productType() }).
-record('Investment',
{ id = [] :: [] | term(),
volume = [] :: [] | money(),
price = {0,1} :: money(),
instrument = [] :: term(),
type = [] :: investmentType(),
from = [] :: term(),
to = [] :: term() }).
Створення кореневих ланцюжків
ERP BOOT або пусконаладка підприємства — це процес заповнення первинних словників та таблиць фундаментальною інформацією. Головним чином це відображення ієрархічної, організаційної структури підприємства. Від працівника підприємства, його рабочого місця, його локального офісу, його локальної компанії, і далі, до группи міжнародних компанії з офісами в різних країнах світу, та можливо, навіть до синдикатів транснаціональних корпорацій.
ERP BOOT — Організаційна структура підприємства
Розглянемо приклад: компанія Quanterall, головний підрядник Aeternity, має офіси в Софії, Варні (головний офіс компанії) та Пловдіві. Сама компанія здійснює операції тільки в Болгарії, тому группа складається из однієї компанії.
Добавляємо, зараз та надалі, дані за допомогою list комбінаторів:
-module(erp).
-compile(export_all).
boot() ->
GroupOrgs =
[ #'Organization'{
name="Quanterall",
url="quanterall.com"} ],
HeadBranches =
[ #'Branch'{ loc = #'Loc'{ city =
"Varna", country = "BG" } },
#'Branch'{ loc = #'Loc'{ city =
"Sophia", country = "BG" } },
#'Branch'{ loc = #'Loc'{ city =
"Plovdiv", country = "BG" } } ],
PartnersOrgs =
[ #'Organization'{ name="NYNJA"},
#'Organization'{ name="Catalx"},
#'Organization'{ name="FiaTech"},
#'Organization'{ name="3Stars"},
#'Organization'{ name="SwissEMX"},
#'Organization'{ name="HistoricalPark"},
#'Organization'{ name="Intralinks"} ],
Structure =
[ {"/erp/group", GroupOrgs},
{"/erp/partners", PartnersOrgs},
{"/erp/quanterall", HeadBranches} ],
lists:foreach(fun({Feed, Data}) ->
case kvs:get(writer, Feed) of
{ok,_} -> skip;
{error,_} -> lists:map(fun(X) ->
kvs:append(X,Feed)
end, Data) end end, Structure).
PAY BOOT — Звітність CashFlow
Управління звітністю аутсорс підприємства доволі просте: 1) ми приймаємо оплати по інвойсам, виставленим клієнтам, з періодом раз на місяць; 2) ми виплачуємо зарплати раз на місяць. Тому згортки групуються календарно та зіпуються помісячно.
sal_boot() ->
lists:map(fun(#'Product'{code=C} = P) ->
lists:map(fun(#'Payment'{}=Pay) ->
kvs:append(Pay,
"/plm/"++C++"/outcome") end,
salaries(C))
end, products()).
pay_boot() ->
lists:map(fun(#'Product'{code=C} = P) ->
lists:map(fun(#'Payment'{}=Pay) ->
kvs:append(Pay,
"/plm/"++C++"/income") end,
payments(C))
end, products()).
PLM BOOT — Бюджетування проектів
Іниціалізація погодинна для кожного співробітника по проекту. Цей список буде використовуватись в майбутньому для розподілення виплат по опціонах.
assignees() ->
lists:map(fun(#'Product'{code=C} = P) ->
case kvs:get(writer,"/plm/"++C++"/staff") of
{error,_} ->
lists:map(fun(#'Person'{}=Person) ->
kvs:append(Person,
"/plm/"++C++"/staff") end,staff(C));
{ok,_} -> skip end
end, products()).
Виплати за процентами на субконто попроектно:
accounts() ->
lists:map(fun(#'Product'{code=C}) ->
lists:map(fun(#'Acc'{id=Id, rate=R}=SubAcc) ->
Address = lists:concat(["/fin/acc/",C]),
kvs:append(SubAcc,Address),
Feed = lists:concat(["/fin/tx/",Id]),
case kvs:get(writer, Feed) of
{error,_} ->
lists:map(fun(#'Payment'{
invoice=I,price=P, volume=V}=Pay) ->
kvs:append(rate(Pay,SubAcc,C), Feed)
end, payments(C));
{ok,_} -> skip
end
end, acc(C))
end, plm_boot:products()).
За работу з даними відповідає біблиотека KVS, як працювати з нею читайте в минулих випусках журналу:
Інкапсуляція структури підприємства
Увесь код, який потрібен для створення фідів, зазвичай виносять в додаток з назвою ERP. Для кожного конкретного підприємства ми використовуємо свою Github організацію, можна навіть інше ім'я репозиторію, але ім'я Erlang/OTP додатку — завжди залишається ERP:
У цьому репозиторії завжди будуть якісь реальні дані якоїсь компанії, яку ми автоматизуємо, якщо вони дозволять нам опублікувати свою організаційну структуру, первинні дані та словники. В інших випадках цей репозиторій робимо приватним. Самі додатки здатні працювати з довільними структурами ERP.
Приклади запитів до сховища
Elixir прелюдія:
defmodule PLM.Mixfile do
use Mix.Project
def project() do
[
app: :plm,
version: "0.7.1",
elixir: "~> 1.8.1",
description: "PLM Product Lifecycle Management",
deps: [{:bpe, "~> 4.7.3"}, {:erp, "~> 0.7.6"}]
]
end
def application(),
do: [mod: {PLM.Application, []},
applications: [:rocksdb, :kvs, :bpe, :erp]]
end
Зміст кореневої директорії БД підприємства:
> :writer |> :kvs.all |> :lists.sort
[
{:writer, '/acc/quanterall/Plovdiv', 3, [], [], []},
{:writer, '/acc/quanterall/Sophia', 9, [], [], []},
{:writer, '/acc/quanterall/Varna', 23, [], [], []},
{:writer, '/bpe/hist/1562855060639704000', 1, [], [], []},
{:writer, '/bpe/proc', 1, [], [], []},
{:writer, '/erp/group', 1, [], [], []},
{:writer, '/erp/partners', 7, [], [], []},
{:writer, '/erp/quanterall', 3, [], [], []},
{:writer, '/fin/acc/CATALX', 4, [], [], []},
{:writer, '/fin/acc/NYNJA', 4, [], [], []},
{:writer, '/fin/tx/CATALX/R&D', 12, [], [], []},
{:writer, '/fin/tx/CATALX/insurance', 12, [], [], []},
{:writer, '/fin/tx/CATALX/options', 12, [], [], []},
{:writer, '/fin/tx/CATALX/reserved', 12, [], [], []},
{:writer, '/fin/tx/NYNJA/R&D', 5, [], [], []},
{:writer, '/fin/tx/NYNJA/insurance', 5, [], [], []},
{:writer, '/fin/tx/NYNJA/options', 5, [], [], []},
{:writer, '/fin/tx/NYNJA/reserved', 5, [], [], []},
{:writer, '/plm/CATALX/income', 12, [], [], []},
{:writer, '/plm/CATALX/investments', 4, [], [], []},
{:writer, '/plm/CATALX/outcome', 12, [], [], []},
{:writer, '/plm/CATALX/staff', 2, [], [], []},
{:writer, '/plm/NYNJA/income', 5, [], [], []},
{:writer, '/plm/NYNJA/investments', 2, [], [], []},
{:writer, '/plm/NYNJA/outcome', 5, [], [], []},
{:writer, '/plm/NYNJA/staff', 4, [], [], []},
{:writer, '/plm/products', 2, [], [], []}
]
Список компаній, які входять в групу підприємства:
> :kvs.feed '/erp/group'
[{:Organization, 'Quanterall', 'quanterall.com', [], []}]
Список бренч-офісів головної (та єдиної) компанії групи:
> :kvs.feed '/erp/quanterall'
[
{:Branch, '1562329445378242000',
{:Loc, '1562329445378243000', [], 'BG', 'Plovdiv', [], []}},
{:Branch, '1562329445378241000',
{:Loc, '1562329445378242000', [], 'BG', 'Sophia', [], []}},
{:Branch, '1562329445378234000',
{:Loc, '1562329445378240000', [], 'BG', 'Varna', [], []}}
]
Список контрагентнів:
> :kvs.feed '/erp/partners'
[
{:Organization, 'Catalx Exchange Inc.', 'catalx.io', [], []},
{:Organization, 'HistoricalPark', [], [], []},
{:Organization, 'NYNJA, Inc.', 'nynja.io', [], []},
{:Organization, 'Intralinks', [], [], []},
{:Organization, 'SwissEMX', [], [], []},
{:Organization, 'FiaTech', [], [], []},
{:Organization, '3Stars', [], [], []}
]
Бюджетування проекту по статтям субконто:
> :kvs.feed '/fin/acc/NYNJA'
[
{:Acc, 'NYNJA/insurance', {2, 70}},
{:Acc, 'NYNJA/reserved', {2, 10}},
{:Acc, 'NYNJA/options', {2, 10}},
{:Acc, 'NYNJA/R&D', {2, 10}}
]
Виплати по опціонам для програмістів:
> :kvs.feed '/fin/tx/CATALX/options'
[
{:Payment, '1562868880497278000', {0, 1}, {2, 150000}, 'USD', :crypto, [], []},
{:Payment, '1562868880496849000', {0, 1}, {2, 100000}, 'USD', :crypto, [], []},
{:Payment, '1562868880496409000', {0, 1}, {2, 120000}, 'USD', :crypto, [], []},
{:Payment, '1562868880495897000', {0, 1}, {2, 150000}, 'USD', :crypto, [], []},
{:Payment, '1562868880495412000', {0, 1}, {2, 100000}, 'USD', :crypto, [], []},
{:Payment, '1562868880494920000', {0, 1}, {2, 100000}, 'USD', :crypto, [], []},
{:Payment, '1562868880494538000', {0, 1}, {2, 100000}, 'USD', :crypto, [], []},
{:Payment, '1562868880494072000', {0, 1}, {2, 50000}, 'USD', :crypto, [], []},
{:Payment, '1562868880493672000', {0, 1}, {2, 70000}, 'USD', :crypto, [], []},
{:Payment, '1562868880493387000', {0, 1}, {2, 150000}, 'USD', :crypto, [], []},
{:Payment, '1562868880492939000', {0, 1}, {2, 120000}, 'USD', :crypto, [], []},
{:Payment, '1562868880492234000', {0, 1}, {2, 120000}, 'USD', :crypto, [], []}
]
Люди, які працюють на проекті:
> :kvs.feed '/plm/NYNJA/staff'
[
{:Person, '1562868880467887000', 'Maxim Sokhatsky', [], [], [], 1, []},
{:Person, '1562868880467886000', 'Yuri Maslovsky', [], [], [], 8, []},
{:Person, '1562868880467885000', 'Nikolay Dimitrov', [], [], [], 4, []},
{:Person, '1562868880467884000', 'Radostin Dimitrov', [], [], [], 4, []},
{:Person, '1562868880467883000', 'Georgi Spasov', [], [], [], 8, []}
]