Частина 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, як працювати з нею читайте в минулих випусках журналу:

2019-04-13 Нова Версія KVS
synrc/kvs

Інкапсуляція структури підприємства

Увесь код, який потрібен для створення фідів, зазвичай виносять в додаток з назвою ERP. Для кожного конкретного підприємства ми використовуємо свою Github організацію, можна навіть інше ім'я репозиторію, але ім'я Erlang/OTP додатку — завжди залишається ERP:

enterprizing/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, []} ]