lib/body.ex

defmodule SEV.Body do
  @behaviour Sax.Handler
  @prefix "priv/sev/download/"
  require ERP
  import SEV.Listener

  def handle_event(:start_document, _, state), do: {:ok, {[], state}}
  def handle_event(:end_document, _, {_, state}), do: {:ok, state}

  def handle_event(:start_element, {"Header", attributes}, {t, _}), do:
    {:ok, {t, case  msg_type(:proplists.get_value("msg_type", attributes, "1")) do
                ERP.dict(name: "Сповіщення") = x ->
                  acknowledgement(id: :proplists.get_value("msg_id", attributes, ""),
                                  msg_type: x,
                                  time: time_to_erl(:proplists.get_value("time", attributes, "")),
                                  corr: org(:proplists.get_value("from_org_id", attributes, []), :proplists.get_value("from_organization", attributes, "")),
                                  org: org(:proplists.get_value("to_org_id", attributes, []), :proplists.get_value("to_organization", attributes, "")))
                x ->
                  document(id: :proplists.get_value("msg_id", attributes, ""),
                           time: time_to_erl(:proplists.get_value("time", attributes, "")),
                           msg_type: x,
                           corr: org(:proplists.get_value("from_org_id", attributes, []), :proplists.get_value("from_organization", attributes, "")),
                           org: org(:proplists.get_value("to_org_id", attributes, []), :proplists.get_value("to_organization", attributes, ""))) end}}
  def handle_event(:start_element, {"Acknowledgement", attributes}, {t, acknowledgement(org: c) = state}), do:
    {:ok, {[{:Acknowledgment, []} | t], acknowledgement(state, ack_type: :proplists.get_value("ack_type", attributes, "0"),
                                                               ack_to: ack_to(c, :proplists.get_value("msg_id", attributes, "")))}}
  def handle_event(:start_element, {"AckResult", attributes}, {[{:Acknowledgment, _} | _] = t, acknowledgement(ack_type: "4") = state}), do:
    {:ok, {t, acknowledgement(state, error: :proplists.get_value("errortext", attributes, ""),
                                     error_code: :proplists.get_value("errorcode", attributes, "0"))}}
  def handle_event(:start_element, {"Document", attributes}, {t, state}), do:
    {:ok, {[{:Document, []} | t], document(state, document_type: document_type(:proplists.get_value("type", attributes, "0")),
                                                  kind: :proplists.get_value("kind", attributes, ""),
                                                  title: :proplists.get_value("title", attributes, ""),
                                                  annotation: :proplists.get_value("annotation", attributes, ""),
                                                  pages: :proplists.get_value("pages", attributes, "0"),
                                                  purpose: purpose(:proplists.get_value("purpose_type", attributes, "0")),
                                                  urgent: bool(:proplists.get_value("urgent", attributes, "0")))}}
  def handle_event(:start_element, {"Approver", attributes}, {t, state}), do:
    {:ok, {[{:Approver, approver(deadline: date_to_erl(:proplists.get_value("deadline", attributes, [])))} | t], state}}
  def handle_event(:start_element, {"ApprovalResponse", attributes}, {t, state}), do:
    {:ok, {t, document(state, attestation: :proplists.get_value("attestation", attributes, []),
                              comment: :proplists.get_value("comment", attributes, []))}}
  def handle_event(:start_element, {"Referred", attributes}, {t, state}), do:
    {:ok, {[{:Referred, reference(id: :proplists.get_value("idnumber", attributes, []),
                                  type: ref_type(:proplists.get_value("retype", attributes, [])))} | t], state}}
  def handle_event(:start_element, {"RegNumber", attributes}, {[{:Document, _} | _] = t, state}), do:
    {:ok, {[{:RegNumber, []} | t], document(state, regdate: date_to_erl(:proplists.get_value("regdate", attributes, [])))}}
  def handle_event(:start_element, {"RegNumber", attributes}, {[{:Acknowledgment, _} | _] = t, state}), do:
    {:ok, {[{:RegNumber, []} | t], acknowledgement(state, regdate: date_to_erl(:proplists.get_value("regdate", attributes, [])))}}
  def handle_event(:start_element, {"AddDocuments", _}, {t, state}), do: {:ok, {[{:AddDocuments, []} | t], state}}
  def handle_event(:start_element, {"DocTransfer", attributes}, {[{x, _} | _] = t, state}), do:
    {:ok, {[{:DocTransfer, file(type: :proplists.get_value("type", attributes, ""),
                                main: x == :Document,
                                description: :proplists.get_value("description", attributes, ""),
                                id: :proplists.get_value("idnumber", attributes, ""))} | t], state}}
  def handle_event(:start_element, {"SignInfo", attributes}, {t, state}), do:
    {:ok, {[{:SignInfo, sign(id: :proplists.get_value("docTransfer_idnumber", attributes, ""))} | t], state}}
  def handle_event(:start_element, {"SigningTime", _}, {[{:SignInfo, _} | _] = t, state}), do: {:ok, {[{:SigningTime, []} | t], state}}
  def handle_event(:start_element, {"SignData", _}, {[{:SignInfo, _} | _] = t, state}), do: {:ok, {[{:SignData, []} | t], state}}
  def handle_event(:start_element, {"TaskList", _}, {t, state}), do: {:ok, {[{:TaskList, []} | t], state}}
  def handle_event(:start_element, {"Task", attributes}, {[{:TaskList, _} | _] = t, state}), do:
    {:ok, {[{:Task, task(guid: :proplists.get_value("idnumber", attributes, []),
                         text: :proplists.get_value("task_text", attributes, ""),
                         deadline: date_to_erl(:proplists.get_value("deadline", attributes, [])),
                         control: bool(:proplists.get_value("referred-control", attributes, "0")))} | t], state}}
  def handle_event(:start_element, {"TaskNumber", attributes}, {[{:Task, x} | t], state}), do:
    {:ok, {[{:TaskNumber, []}, {:Task, task(x, date: date_to_erl(:proplists.get_value("taskDate", attributes, [])))} | t], state}}
  def handle_event(:start_element, {"Executor", attributes}, {[{:Task, _} = x | t], state}), do:
    {:ok, {[{:Executor, executor(type: executor_type(:proplists.get_value("responsible", attributes, "0")))}, x | t], state}}
  def handle_event(:start_element, {"Organization", attributes}, {[{:Approver, app} | t], state}), do:
    {:ok, {[{:Approver, approver(app, org: org(:proplists.get_value("ogrn", attributes, ""), :proplists.get_value("fullname", attributes, "")))} | t], state}}
  def handle_event(:start_element, {"Organization", attributes}, {[{:Executor, exec} | t], state}), do:
    {:ok, {[{:Executor, executor(exec, org: org(:proplists.get_value("ogrn", attributes, ""), :proplists.get_value("fullname", attributes, "")))} | t], state}}
  def handle_event(:start_element, _, state), do: {:ok, state}

  def handle_event(:characters, c, {[{:SignData, _}, {:SignInfo, _} = s | t], state}), do: {:ok, {[{:SignData, c}, s | t], state}}
  def handle_event(:characters, c, {[{:SigningTime, _}, {:SignInfo, _} = s | t], state}), do: {:ok, {[{:SigningTime, time_to_erl(c)}, s | t], state}}
  def handle_event(:characters, c, {[{:DocTransfer, x} | t], state}), do: {:ok, {[{:DocTransfer, file(x, body: c)} | t], state}}
  def handle_event(:characters, c, {[{:RegNumber, _}, {:Document, _} = d | t], state}), do: {:ok, {[{:RegNumber, c}, d | t], state}}
  def handle_event(:characters, c, {[{:RegNumber, _}, {:Acknowledgment, _} = d | t], state}), do: {:ok, {[{:RegNumber, c}, d | t], state}}
  def handle_event(:characters, c, {[{:TaskNumber, _}, {:Task, _} = x | t], state}), do: {:ok, {[{:TaskNumber, c}, x | t], state}}
  def handle_event(:characters, _, state), do: {:ok, state}

  def handle_event(:end_element, "SigningTime", {[{:SigningTime, x}, {:SignInfo, s} | t], state}), do: {:ok, {[{:SignInfo, sign(s, time: x)} | t], state}}
  def handle_event(:end_element, "SignData", {[{:SignData, x}, {:SignInfo, s} | t], state}), do: {:ok, {[{:SignInfo, sign(s, body: x)} | t], state}}
  def handle_event(:end_element, "RegNumber", {[{:RegNumber, x} | t], acknowledgement() = state}), do: {:ok, {t, acknowledgement(state, regnumber: x)}}
  def handle_event(:end_element, "RegNumber", {[{:RegNumber, x} | t], state}), do: {:ok, {t, document(state, regnumber: x)}}
  def handle_event(:end_element, "SignInfo", {[{:SignInfo, x} | t], document(files: f) = state}), do: (save(x, state); {:ok, {t, document(state, files: file_sign(x, f))}})
  def handle_event(:end_element, "DocTransfer", {[{:DocTransfer, x} | t], document(files: f) = state}), do: (save(x, state); {:ok, {t, document(state, files: [file(x, body: []) | f])}})
  def handle_event(:end_element, "AddDocuments", {[{:AddDocuments, _} | t], state}), do: {:ok, {t, state}}
  def handle_event(:end_element, "Referred", {[{:Referred, reference(type: ERP.dict(name: "Документ")) = x} | t], state}), do: {:ok, {t, document(state, referred: x)}}
  def handle_event(:end_element, "Referred", {[{:Referred, reference(type: ERP.dict(name: "Задача")) = x} | t], document(referred_tasks: tasks) = state}), do:
    {:ok, {t, document(state, referred_tasks: [x | tasks])}}
  def handle_event(:end_element, "Referred", {[{:Referred, _} | t], state}), do: {:ok, {t, state}}
  def handle_event(:end_element, "TaskList", {[{:TaskList, _} | t], state}), do: {:ok, {t, state}}
  def handle_event(:end_element, "TaskNumber", {[{:TaskNumber, n}, {:Task, x} | t], state}), do: {:ok, {[{:Task, task(x, number: n)} | t], state}}
  def handle_event(:end_element, "Executor", {[{:Executor, executor(type: ERP.dict(name: "Співвиконавець")) = e}, {:Task, task(subexecutors: s) = x} | t], state}), do:
    {:ok, {[{:Task, task(x, subexecutors: [e | s])} | t], state}}
  def handle_event(:end_element, "Executor", {[{:Executor, executor(type: ERP.dict(name: "Виконавець")) = e}, {:Task, x} | t], state}), do:
    {:ok, {[{:Task, task(x, executor: e)} | t], state}}
  def handle_event(:end_element, "Executor", {[{:Executor, executor(type: ERP.dict(name: "До відома")) = e}, {:Task, task(notify: n) = x} | t], state}), do:
    {:ok, {[{:Task, task(x, notify: [e | n])} | t], state}}
  def handle_event(:end_element, "Executor", {[{:Executor, _} | t], state}), do: {:ok, {t, state}}
  def handle_event(:end_element, "Approver", {[{:Approver, x} | t], document(approvers: a) = state}), do: {:ok, {t, document(state, approvers: [x | a])}}
  def handle_event(:end_element, "Task", {[{:Task, x} | t], document(tasks: tasks) = state}), do: {:ok, {t, document(state, tasks: [x | tasks])}}
  def handle_event(:end_element, _, state), do: {:ok, state}

  def parse(xml), do: ({:ok, x} = Sax.parse_string(xml, SEV.Body, []); x)

  def gen(login, document(id: id, org: ERP."Organization"(code: from), corr: ERP."Organization"(code: to), files: f) = doc), do:
    {from, to, time(), [:xml_decl, {:Header, header(login, doc),
                                     [{:Document, documentHeader(doc), documentBody(doc)}, tasks(doc) ++ files(id, :add, f) ++ expansion(doc)]}] |> XmlGen.generate(format: :none)}
  def gen(login, acknowledgement(ack_to: document(id: id), ack_type: ackT, org: ERP."Organization"(code: from), corr: ERP."Organization"(code: to)) = ack), do:
    {from, to, time(), [:xml_decl, {:Header, header(login, ack),
                                     [{:Acknowledgement, [msg_id: id, ack_type: ackT], ackBody(ack)}]}] |> XmlGen.generate(format: :none)}

  defp header(document(msg_type: ERP.dict(id: t), org: ERP."Organization"(code: fId, name: fName), corr: ERP."Organization"(code: tId, name: tName))), do:
    [msg_type: :nitro.to_binary(t), from_org_id: fId, from_organization: fName, to_org_id: tId, to_organization: tName, msg_acknow: "2"]
  defp header(acknowledgement(org: ERP."Organization"(code: fId, name: fName), corr: ERP."Organization"(code: tId, name: tName))), do:
    [msg_type: "0", from_org_id: fId, from_organization: fName, to_org_id: tId, to_organization: tName]
  defp header(_), do: []
  defp header(login, doc), do:
    [standart: "1207",
     version: "1.5",
     charset: "UTF-8",
     msg_id: :kvs.field(doc, :id),
     time: time(),
     from_system_details: "",
     from_system: "СЕВ",
     from_sys_id: login,
     to_system: "СЕВ",
     to_system_details: "",
     to_sys_id: "99999999"] ++ header(doc)

  defp documentHeader(document(id: id, annotation: annotation, urgent: urgent, pages: pages, kind: kind, purpose: ERP.dict(id: p), document_type: ERP.dict(id: t))), do:
    [idnumber: id, annotation: annotation, urgent: bool(urgent), collection: 0, pages: pages, type: :nitro.to_binary(t), kind: kind, purpose_type: :nitro.to_binary(p)]

  defp documentBody(document(id: id, org: ERP."Organization"() = from, corr: ERP."Organization"() = to, regnumber: rN, regdate: rD, files: f) = doc), do:
    regNumber(rN, rD) ++ [{:Confident, [flag: 0], ""}] ++ files(id, :main, f) ++
      [{:Author, nil, [organization(from), {:OutNumber, nil, regNumber(rN, rD)}]},
       {:Addressee, [type: 0], [organization(to)] ++ referred(doc)},
       {:Writer, nil, [organization(to)]}] ++ approval(doc)

  defp approval(document(purpose: ERP.dict(id: '1'), approvers: a)), do:
    [{:Approval, nil, [{:ApprovalRequest, nil, :lists.map(fn approver(deadline: deadline, org: ERP."Organization"(code: id) = c) ->
                         {:Approver, [idnumber: :nitro.to_binary(id), deadline: date(deadline)], [organization(c)]} end, a)}]}]
  defp approval(document(purpose: ERP.dict(id: '4'), attestation: ERP.dict(name: a), comment: c)), do:
    [{:Approval, nil, [{:ApprovalResponse, [attestation: a, comment: c], []}]}]
  defp approval(_), do: []

  defp referred(document(purpose: ERP.dict(id: '4'), referred: ERP.sevRef(id: id, regnumber: rN, regdate: rD), referred_tasks: [ERP.sevRef() | _] = x)), do:
    [{:Referred, [idnumber: id, retype: "д"], [{:RegNumber, [regdate: date(rD)], rN}]} |
      :lists.map(fn ERP.sevRef(id: guid, regdate: date, regnumber: num) ->
        {:Referred, [idnumber: guid, retype: "з"], [{:TaskNumber, [taskDate: date(date)], num}]}
      end, x)]
  defp referred(_), do: []

  defp tasks(document(id: guid, purpose: ERP.dict(id: '3'), regnumber: rN, regdate: rD, org: ERP."Organization"() = org, tasks: [task() | _] = x)), do:
    [{:TaskList, nil, :lists.map(fn task(guid: i, number: n, date: date, text: t, deadline: d, control: c, executor: e, subexecutors: s, notify: notify) ->
      {:Task, [idnumber: i, task_reg: 0, task_copy: 0, kind: 3, task_text: t, deadline: date(d)] ++ [{:"referred-control", bool(c)}],
        [{:TaskNumber, [taskDate: date(date)], n}, {:Confident, [flag: 0], nil}, {:Referred, [idnumber: guid, retype: "д"], regNumber(rN, rD)}, {:Author, nil, [organization(org)]}] ++
          :lists.flatten(:lists.map(&exec(date, &1), [e | s ++ notify]))}
    end, x)}]
  defp tasks(_), do: []

  defp exec(date, executor(org: ERP."Organization"() = x, type: ERP.dict(id: t))), do: {:Executor, [responsible: t, deadline: date(date)], [organization(x)]}
  defp exec(_, _), do: []

  defp organization(ERP."Organization"(name: name, code: c, position: post, director: person)), do:
    {:Organization, [fullname: name, shortname: name, ogrn: :nitro.to_binary(c), inn: 0],
                      [{:Address, [country: "Україна"], ""}, {:OfficialPerson, nil,
                        [{:Name, nil, person}, {:Official, [post: post, separator: ""], ""}]}]}

  defp regNumber(regnum, date), do: [{:RegNumber, [regdate: date(date)], regnum}]

  defp ackBody(acknowledgement(ack_type: 3, regnumber: regnum, regdate: regdate)), do: [regNumber(regnum, regdate), {:AckResult, [errorcode: 0, errortext: "OK"], nil}]
  defp ackBody(acknowledgement(ack_type: 4, error: error)), do: [{:AckResult, [errorcode: 0, errortext: error], nil}]
  defp ackBody(_), do: [{:AckResult, [errorcode: 0, errortext: "OK"], nil}]

  defp expansion(document(id: id, org: ERP."Organization"(phones: phone, url: url), files: f)), do:
    [{:Expansion,
       [organization: "INFOTECH SE", exp_ver: "1.0-[2.7.20]"],
       [{:Econtact, [type: "р"], :nitro.to_binary(phone)}, {:Econtact, [type: "і"], :nitro.to_binary(url)}, {:StaticExpansion, nil, signatures(id, f)}]
    }]

  defp files(guid, [file() | _] = x), do:
    :lists.map(fn file(id: id, description: d, type: t) ->
      {:DocTransfer, [type: t, char_set: "UTF-8", description: d, idnumber: id],
        case :filelib.is_regular("priv/sev/upload/#{guid}/#{id}") do
          true -> :base64.encode(:erlang.element(2, :file.read_file("priv/sev/upload/#{guid}/#{id}")))
          false -> ""
        end}
    end, x)
  defp files(_, _), do: []
  defp files(id, :main, [file() | _] = x), do: files(id, :lists.filter(&file(&1, :main), x))
  defp files(id, :add, [file() | _] = x), do: [{:AddDocuments, nil, [{:Folder, [add_type: "0"], files(id, :lists.filter(&(file(&1, :main) != true), x))}]}]

  defp signatures(guid, [file() | _] = x), do:
    :lists.map(fn file(id: id, sign: sign(time: t)) ->
      {:SignInfo, [docTransfer_idnumber: id], [{:SigningTime, nil, time(t)}, {:SignData, nil,
        case :filelib.is_regular("priv/sev/upload/#{guid}/#{id}.p7s") do
          true -> :base64.encode(:erlang.element(2, :file.read_file("priv/sev/upload/#{guid}/#{id}.p7s")))
          false -> ""
        end}]}
                _ -> [] end, x) |> :lists.flatten

  defp save(file(id: i, body: b), document(id: g)), do: :ok = :file.write_file("#{@prefix}#{g}/#{i}", :base64.decode(b), [:raw, :binary])
  defp save(sign(id: i, body: b), document(id: g)), do: :ok = :file.write_file("#{@prefix}#{g}/#{i}.p7s", :base64.decode(b), [:raw, :binary])

  defp file_sign(sign(id: i) = s, x), do:
    (case :lists.keytake(i, 2, x) do {:value, file() = f, t} -> [file(f, sign: sign(s, body: [])) | t]; _ -> x end)

  defp time(), do:
    ({{y, m, d}, {h, min, s}} = :calendar.local_time();
     :erlang.list_to_binary(:io_lib.format('~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0wZ', [y, m, d, h, min, s])))
  defp time({{y, m, d}, {h, min, s}}), do:
    :erlang.list_to_binary(:io_lib.format('~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0wZ', [y, m, d, h, min, s]))

  defp time_to_erl(x) when is_binary(x), do: ({_, t} = NaiveDateTime.from_iso8601(x); NaiveDateTime.to_erl(t))
  defp time_to_erl(_), do: []

  defp date({y, m, d}), do: :erlang.list_to_binary(:io_lib.format('~4..0w-~2..0w-~2..0w', [y, m, d]))
  defp date(_), do: []

  defp date_to_erl(x) when is_binary(x), do:
    {String.to_integer(String.slice(x, 0, 4)), String.to_integer(String.slice(x, 5, 2)),
     String.to_integer(String.slice(x, 8, 2))}
  defp date_to_erl(_), do: []

  defp org(edrpou, name), do:
    (case :kvs.get("/crm/sev/validstakeholders", :nitro.to_list(edrpou)) do
      {:ok, ERP."Organization"() = x} -> x;
      _ ->
        x = ERP."Organization"(id: :nitro.to_list(edrpou), code: edrpou, name: name, orgname: name, type: :juridical);
        :kvs.append(x, "/crm/sev/stakeholders");
        :kvs.append(x, "/crm/sev/stakeholders/orgs");
        :kvs.append(x, "/crm/sev/validstakeholders"); x
    end)

  defp msg_type(id), do: (case :kvs.get("/crm/sevdicts/msg_type", :nitro.to_list(id)) do {:ok, ERP.dict() = x} -> x; _ -> [] end)

  defp purpose(id), do: (case :kvs.get("/crm/sevdicts/purpose/in", :nitro.to_list(id)) do {:ok, ERP.dict() = x} -> x; _ -> [] end)

  defp document_type(id), do: (case :kvs.get("/crm/sevdicts/document_type", :nitro.to_list(id)) do {:ok, ERP.dict() = x} -> x; _ -> [] end)

  defp executor_type(id), do: (case :kvs.get("/crm/sevdicts/executor_type", :nitro.to_list(id)) do {:ok, ERP.dict() = x} -> x; _ -> [] end)

  defp ref_type(id), do: (case :kvs.get("/crm/sevdicts/referred_type", :nitro.to_list(id)) do {:ok, ERP.dict() = x} -> x; _ -> [] end)

  defp bool(true), do: "1"
  defp bool(false), do: "0"
  defp bool("1"), do: true
  defp bool("0"), do: false

end