Subiendo api v2

This commit is contained in:
2025-04-16 10:03:13 -03:00
commit 226933fda7
7537 changed files with 576844 additions and 0 deletions

View File

@ -0,0 +1,70 @@
defmodule Api.Downloadpdf do
import Ecto.Query
alias Api.Repo
require Logger
def get_report_query(idstudyreport) do
Logger.info("hash antes de query: #{idstudyreport}")
# uso hex en la desencriptación porque el proceso de encriptación involucraba convertir los datos binarios a una cadena de texto (substring)
q = """
SELECT convert_from(decrypt(decode('#{idstudyreport}', 'hex')::bytea, '1nf0rm3'::bytea, 'aes'), 'UTF8');
"""
idstudyreport = Repo.query!(q).rows |> hd() |> hd()
Logger.info("hash despues de query: #{idstudyreport}")
idstudyreport = String.to_integer(idstudyreport)
template_name = Envar.get("TEMPLATE_NAME")
# template_name = System.get_env("TEMPLATE_NAME")
Logger.info("template: #{template_name}")
# pr2_statusname = Envar.get("pr2_statusname")
query =
from sr in "studyreport",
join: s in "study",
on: s.idstudy == sr.idstudy,
join: p in "patient",
on: p.idpatient == s.idpatient,
left_join: pr in "professional",
on: pr.idprofessional == sr.idprofessional_finalizedby,
left_join: u in "users",
on: u.iduser == pr.iduser,
join: pt in "printtemplates",
on: pt.printtemplate_type == "STUDYREPORT",
where: sr.idstudyreport == ^idstudyreport,
where: pt.printtemplate == ^template_name,
where: pt.enabled == true,
select: %{
idstudyreport: sr.idstudyreport,
body: sr.body,
idstudy: s.idstudy,
printtemplate: pt.template,
accessionnumber: s.accessionnumber,
patientname: fragment("replace(?, '^', ' ')", p.patientname),
studydate: fragment("TO_CHAR(?, 'YYYY-MM-DD')", s.studydate),
studytime: s.studytime,
studydescription: s.studydescription,
proceduredescription: s.proceduredescription,
procedurecode: s.procedurecode,
modality: s.modality,
patientid: p.patientid,
patientbirthdate: fragment("TO_CHAR(?, 'YYYY-MM-DD')", p.patientbirthdate),
fin_signature: pr.signature,
matricula_finalized: fragment("COALESCE(?, '')",pr.matricula),
informantphysician: u.username,
insurer: fragment("COALESCE(?, '')",s.insurer),
institutionname: s.institutionname,
referringphysiciansname: fragment("COALESCE(?, '')", s.referringphysiciansname)
}
result = Repo.one(
from q in subquery(query),
select: fragment("json_build_object('idstudyreport', ?, 'body', ?, 'idstudy', ?, 'accessionnumber', ?, 'patientname', ?, 'studydate', ?, 'studytime', ?, 'studydescription', ?, 'proceduredescription', ?, 'procedurecode', ?, 'modality', ?, 'patientid', ?, 'patientbirthdate', ?, 'fin_signature', ?, 'matricula_finalized', ?, 'informantphysician', ?, 'insurer', ?, 'institutionname', ?, 'referringphysiciansname', ?, 'printtemplate', ?)",
q.idstudyreport, q.body, q.idstudy, q.accessionnumber, q.patientname, q.studydate, q.studytime, q.studydescription, q.proceduredescription, q.procedurecode, q.modality, q.patientid, q.patientbirthdate, q.fin_signature, q.matricula_finalized, q.informantphysician, q.insurer, q.institutionname, q.referringphysiciansname, q.printtemplate
)
)
Logger.info("#{inspect(result)}")
result
end
end

View File

@ -0,0 +1,136 @@
defmodule Api.Studies do
import Ecto.Query
alias Api.Repo
def studies_sql_query(filters) do
page = filters["page"] || 1
size = filters["size"] || 24
filter = filters["filter"] || []
sort = filters["sort"] || [%{"dir" => "desc", "field" => "studydate"}]
# Construcción de condiciones de filtro dinámicas
filter_conditions =
Enum.reduce(filter, dynamic(true), fn f, acc ->
field = f["field"]
value = f["value"]
condition =
case field do
"modality" ->
dynamic([s], ilike(s.modality, ^"%#{value}%"))
"idstudy" ->
dynamic([s], s.idstudy == ^String.to_integer(value))
"studydate" ->
dynamic([s], fragment("CAST(? AS DATE)::text LIKE ?", s.studydate, ^"%#{value}%"))
"studydate_start" ->
if value == "" do
dynamic(true)
else
dynamic([s], fragment("CAST(? AS DATE)::text", s.studydate) >= ^value)
end
"studydate_end" ->
if value == "" do
dynamic(true)
else
dynamic([s], fragment("CAST(? AS DATE)::text", s.studydate) <= ^value)
end
"accessionnumber" ->
dynamic([s], ilike(s.accessionnumber, ^"%#{value}%"))
"proceduredescription" ->
dynamic([s], ilike(s.studydescription, ^"%#{value}%"))
"insurer" ->
dynamic([s], ilike(s.insurer, ^"%#{value}%"))
"sitename" ->
dynamic([s], ilike(s.institutionname, ^"%#{value}%"))
"nrodocumento" ->
dynamic([s, p], ilike(p.patientid, ^"%#{value}%"))
"patientname" ->
dynamic([s, p], ilike(p.patientname, ^"%#{value}%"))
"region" ->
dynamic([s], s.region == ^value)
"studytime" ->
dynamic([s], fragment("CAST(? AS TIME)::text LIKE ?", s.studytime, ^"#{value}%"))
_ ->
dynamic(true)
end
# Combina las condiciones dinámicas
dynamic([s, p], ^condition and ^acc)
end)
sort_conditions =
Enum.map(sort, fn v ->
direction = String.to_atom(v["dir"])
field = String.to_atom(v["field"])
{direction, field}
end)
query =
from s in "study",
join: p in "patient",
on: p.idpatient == s.idpatient,
left_join: sr in "studyreport",
on: sr.idstudy == s.idstudy,
left_join: st in "statuses",
on: st.idstatus == sr.idstudyreport,
where: ^filter_conditions,
select: %{
recordstotal: fragment("count(*) over()"),
idstudy: fragment("substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", s.idstudy),
#idstudy: s.idstudy,
accessionnumber: s.accessionnumber,
studydate: s.studydate,
studytime: s.studytime,
patientname: fragment("select replace(?, '^', ' ')", p.patientname),
proceduredescription: s.studydescription,
modality: s.modality,
sitename: s.institutionname,
insurer: s.insurer,
nrodocumento: fragment("substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", p.patientid),
#nrodocumento: p.patientid,
hasaudio: fragment("CASE WHEN ? IS NOT NULL THEN true ELSE false END", s.audiofile)
},
order_by: ^sort_conditions,
limit: ^size,
offset: ^((page - 1) * size)
result = Repo.one(
from q in subquery(query),
group_by: q.recordstotal,
select: %{
data: fragment("json_agg(?)::text", q),
recordstotal: q.recordstotal
}
)
case result do
nil ->
%{data: [], last_page: 0}
result ->
# Calcula last_page
last_page = div(result.recordstotal + size - 1, size)
%{data: Jason.decode!(result.data), last_page: last_page}
end
end
end

View File

@ -0,0 +1,153 @@
defmodule Api.Study do
import Ecto.Query
alias Api.Repo
require Logger
def open_study(accession) do
Envar.load(".env")
Envar.require_env_file(".env")
idsite = Envar.get("IDSITE") |> String.to_integer
domain = Envar.get("CHECK_ORIGIN")
escaneados = Envar.get("ESCANEADOS")
id_study = if Envar.get("IDENTIFIERFIELD") == "ACCESSIONNUMBER" do
query =
from s in "study",
where: s.accessionnumber == ^accession,
select: s.idstudy
idstudy = Repo.one(query)
Logger.info("accession ----> #{inspect(accession)} - idstudy -------> #{inspect(idstudy)}")
idstudy
else
accession
end
# id_study = if is_integer(id_study), do: id_study, else: String.to_integer(id_study)
# accession_fragment =
# if Envar.get("IDENTIFIERFIELD") == "ACCESSIONNUMBER" do
# fragment("?::text", ^accession)
# else
# fragment("?::integer", ^id_study)
# end
Logger.info("id_study -------> #{inspect(id_study)}")
# Las siguientes consultas obtienen la información completa
# de un estudio.
# informes
subquery1 = subquery(
from s in "study",
join: sr in "studyreport", on: sr.idstudy == s.idstudy,
join: p in "patient", on: s.idpatient == p.idpatient,
where: not is_nil(sr.body) and s.idstudy == ^id_study and
sr.idstatus in subquery(
from st in "statuses",
where: st.statusname in ["Revisado", "Final"],
select: st.idstatus
),
select: %{
idsite: type(^idsite, :integer),
iddocument: fragment("substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", sr.idstudyreport),
document_name: "INFORME PRINCIPAL",
document_type: "pdf",
url: fragment(
"concat(?::text, ?::text, substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3))",
^domain,
"/api/downloadpdf/",
sr.idstudyreport
),
patientname: p.patientname,
proceduredescription: s.studydescription,
studydate: fragment("TO_CHAR(?, 'YYYY-MM-DD')", s.studydate),
studytime: fragment("TO_CHAR(?, 'HH24:MI:SS')", s.studytime),
accessionnumber: s.accessionnumber,
patientid: p.patientid
}
)
# escaneados
subquery2 = subquery(
from s in "study",
join: ss in "studyscans", on: ss.idstudy == s.idstudy,
join: sc in "scanclasses", on: sc.idscanclass == ss.idscanclass,
join: p in "patient", on: s.idpatient == p.idpatient,
where: s.idstudy == ^id_study,
select: %{
idsite: type(^idsite, :integer),
iddocument: fragment("substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", ss.idstudyscan),
document_name: sc.scanclass,
document_type: "url",
url: fragment(
"concat(?::text, substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3))",
^escaneados,
ss.idstudyscan
),
patientname: p.patientname,
proceduredescription: s.studydescription,
studydate: fragment("TO_CHAR(?, 'YYYY-MM-DD')", s.studydate),
studytime: fragment("TO_CHAR(?, 'HH24:MI:SS')", s.studytime),
accessionnumber: s.accessionnumber,
patientid: p.patientid
}
)
# adjuntos
subquery3 =
from s in "study",
join: sa in "studyattachments", on: sa.idstudy == s.idstudy,
join: p in "patient", on: p.idpatient == s.idpatient,
where: s.idstudy == ^id_study,
select: %{
idsite: type(^idsite, :integer),
iddocument: fragment("substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", sa.idstudyattachment),
document_name: sa.name,
document_type: fragment("CASE WHEN ? NOT IN ('jpg', 'jpeg', 'png') THEN 'attachment' ELSE 'url' END", sa.format),
url: fragment(
"concat(?::text, ?::text, substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3))",
^domain,
"/api/attachment/",
sa.idstudyattachment
),
patientname: p.patientname,
proceduredescription: s.studydescription,
studydate: fragment("TO_CHAR(?, 'YYYY-MM-DD')", s.studydate),
studytime: fragment("TO_CHAR(?, 'HH24:MI:SS')", s.studytime),
accessionnumber: s.accessionnumber,
patientid: ""
}
# Si no encontró informes, ni adjuntos, ni nada
# se traen sólo los datos básicos del estudio
basicquery =
from s in "study",
join: p in "patient", on: p.idpatient == s.idpatient,
where: s.idstudy == ^id_study,
select: %{
idsite: type(^idsite, :integer),
patientname: p.patientname,
proceduredescription: s.studydescription,
studydate: fragment("TO_CHAR(?, 'YYYY-MM-DD')", s.studydate),
studytime: fragment("TO_CHAR(?, 'HH24:MI:SS')", s.studytime),
accessionnumber: s.accessionnumber,
patientid: ""
}
combined_query =
from q in subquery1,
union_all: ^subquery2,
union_all: ^subquery3,
select: q
results = Repo.all(combined_query)
case results do
[] -> Repo.all(from q in basicquery)
_ -> results
end
end
end

37
lib/api/application.ex Normal file
View File

@ -0,0 +1,37 @@
defmodule Api.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = [
ApiWeb.Telemetry,
Api.Repo,
#{Api.Autosender, 1000 * 62 * 4},
# {DNSCluster, query: Application.get_env(:api, :dns_cluster_query) || :ignore},
{Phoenix.PubSub, name: Api.PubSub},
# Start the Finch HTTP client for sending emails
{Finch, name: Api.Finch},
# Start a worker by calling: Api.Worker.start_link(arg)
# {Api.Worker, arg},
# Start to serve requests, typically the last entry
ApiWeb.Endpoint
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Api.Supervisor]
Supervisor.start_link(children, opts)
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
@impl true
def config_change(changed, _new, removed) do
ApiWeb.Endpoint.config_change(changed, removed)
:ok
end
end

View File

@ -0,0 +1,26 @@
defmodule Api.Autosender do
alias Api.StudyMailer
use GenServer
def start_link(period_in_millis) do
GenServer.start_link(__MODULE__, %{period: period_in_millis})
end
@impl true
def init(state) do
next_send(state.period)
{:ok, state}
end
def next_send(period_in_millis) do
Process.send_after(self(), :send, period_in_millis)
end
@impl true
def handle_info(:send, state) do
StudyMailer.deliver_mail()
next_send(state.period)
{:noreply, state}
end
end

View File

@ -0,0 +1,179 @@
defmodule Api.EmailToSendContext do
require Logger
import Ecto.Query, warn: false
alias Api.Repo
alias Api.Emailtosend
alias Api.Pagination
def mark_sent(idemailtosend, attrs)do
from(e in Emailtosend, where: e.idemailtosend == ^idemailtosend)
|> Repo.one
|> Emailtosend.changeset(attrs)
|> Repo.update()
end
defp list_emails({filter, sort}) do
filter_conditions =
Enum.reduce(filter, true, fn v, filter_conditions ->
case v["field"] do
"idstudy" ->
dynamic([e], e.idstudy == ^v["value"] and ^filter_conditions)
"patientemail" ->
dynamic([e], ilike(e.patientemail, ^"#{v["value"]}%") and ^filter_conditions)
"sent" ->
dynamic([e], e.sent == ^v["value"] and ^filter_conditions)
"hasreport" ->
dynamic([e], e.hasreport == ^v["value"] and ^filter_conditions)
"retries" ->
dynamic([e], e.retries == ^v["value"] and ^filter_conditions)
"errormsg" ->
dynamic([e], ilike(e.errormsg, ^"#{v["value"]}%") and ^filter_conditions)
"forcereprocess" ->
dynamic([e], e.send == ^v["forcereprocess"] and ^filter_conditions)
"sentdatetime" ->
dynamic([e], e.send == ^v["sentdatetime"] and ^filter_conditions)
"accessionnumber" ->
dynamic([s], ilike(s.accessionnumber, ^"#{v["value"]}%") and ^filter_conditions)
"errormsg" ->
dynamic([p], ilike(p.patientname, ^"#{v["value"]}%") and ^filter_conditions)
"errormsg" ->
dynamic([p], ilike(p.patientid, ^"#{v["value"]}%") and ^filter_conditions)
"errormsg" ->
dynamic([s], ilike(s.studydate, ^"#{v["value"]}%") and ^filter_conditions)
_ ->
filter_conditions
end
end)
sort_values =
case sort do
[] -> [desc: :idstudy]
_ -> Enum.reduce(sort, [], fn v, acc ->
IO.inspect(v)
case v["field"] do
"patientname" ->
if v["dir"] == "asc" do
[asc: dynamic([_,_,p], p.patientname)]
else
[desc: dynamic([_,_,p], p.patientname)]
end
"patientid" ->
if v["dir"] == "asc" do
[asc: dynamic([_,_,p], p.patientid)]
else
[desc: dynamic([_,_,p], p.patientid)]
end
"accessionnumber" ->
if v["dir"] == "asc" do
[asc: dynamic([_,s], s.accessionnumber)]
else
[desc: dynamic([_,s], s.accessionnumber)]
end
"studydate" ->
if v["dir"] == "asc" do
[asc: dynamic([_,s], s.studydate)]
else
[desc: dynamic([_,s], s.studydate)]
end
_ ->
if v["dir"] == "asc" do
Enum.concat( acc, asc: String.to_existing_atom(v["field"]))
else
Enum.concat( acc, desc: String.to_existing_atom(v["field"]))
end
end
end)
end
queryable = from(
e in Emailtosend,
where: ^filter_conditions,
join: s in "study", on: s.idstudy == e.idstudy,
join: p in "patient", on: p.idpatient == s.idpatient,
order_by: ^sort_values,
select: %{
idemailtosend: e.idemailtosend,
idstudy: e.idstudy,
patientemail: e.patientemail,
sent: e.sent,
hasreport: e.hasreport,
retries: e.retries,
errormsg: e.errormsg,
forcereprocess: e.forcereprocess,
sentdatetime: e.sentdatetime,
accessionnumber: s.accessionnumber,
patientname: p.patientname,
patientid: p.patientid,
studydate: s.studydate
}
)
queryable
end
def list_emails(:paged, params, page, per_page) do
list_emails(params)
|> Pagination.page(page, per_page: per_page)
end
def get_email_by_id(id) do
from(e in Emailtosend, where: e.idemailtosend == ^id)
|> Repo.one()
end
def update_emailtosend(attrs) do
get_email_by_id(attrs["idemailtosend"])
|> Emailtosend.changeset(attrs)
|> Repo.update()
end
def get_next_email() do
from(
e in Emailtosend,
join: s in "study", on: s.idstudy == e.idstudy,
join: p in "patient", on: p.idpatient == s.idpatient,
where:
(
e.patientemail != "notiene@notiene.com" and
e.patientemail != "" and
not is_nil(e.patientemail) and
e.forcereprocess == true
) or
(
e.patientemail != "notiene@notiene.com" and
e.patientemail != "" and
not is_nil(e.patientemail) and
e.hasreport == true and
e.sent != true and
e.retries < 10
),
limit: 1,
select: %{
idemailtosend: e.idemailtosend,
retries: e.retries,
idstudy: s.idstudy,
accessionnumber: s.accessionnumber,
patientemail: e.patientemail,
patientname: p.patientname,
patientid: p.patientid,
studydate: s.studydate
}
)
|> Repo.one()
|> IO.inspect(label: "CONTEXTO")
end
end

View File

@ -0,0 +1,30 @@
defmodule Api.Emailtosend do
use Ecto.Schema
import Ecto.Changeset
@derive {Jason.Encoder, only: [:idemailtosend, :idstudy, :patientemail, :sent, :hasreport, :retries, :errormsg, :forcereprocess, :sentdatetime]}
@primary_key {:idemailtosend, :id, autogenerate: true}
schema "emailstosend" do
field :idstudy, :integer
field :patientemail, :string
field :sent, :boolean
field :hasreport, :boolean
field :retries, :integer
field :errormsg, :string
field :forcereprocess, :boolean
field :sentdatetime, :naive_datetime
end
def changeset(emailtosend, attrs) do
emailtosend
|> cast(attrs, [:idstudy,
:patientemail,
:sent,
:hasreport,
:retries,
:errormsg,
:forcereprocess,
:sentdatetime])
|> validate_required([])
end
end

View File

@ -0,0 +1,56 @@
defmodule Api.StudyMailer do
import Swoosh.Email
alias Api.EmailToSendContext
alias Api.Mailer
def create_mail(mail) do
Envar.load(".env")
Envar.require_env_file(".env")
htmlbody = File.read(Envar.get("EMAIL_BODY_PATH"))
|> elem(1)
|> Solid.parse()
|> elem(1)
|> Solid.render!(
%{"mail" =>
%{"patientname" => mail.patientname |> String.replace("^", " "),
"studydate" => mail.studydate,
"patientid" => mail.patientid,
"accessionnumber" => mail.accessionnumber,
"idstudy" => mail.idstudy}
})
|> to_string()
new()
|> to({mail.patientname |> String.replace("^", " "), mail.patientemail |> String.trim |> String.replace(" ", "")})
|> from({Envar.get("FROM"), Envar.get("SMTP_USER")})
|> subject("Resultado del estudio")
|> html_body(htmlbody)
end
def deliver_mail() do
email = EmailToSendContext.get_next_email()
if email do
email_regex = ~r/[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+/
if String.match?(email.patientemail, email_regex) do
emailtosendtest = create_mail(email)
{res, term} = Mailer.deliver(emailtosendtest)
case res do
:ok ->
EmailToSendContext.mark_sent(email.idemailtosend, %{sent: true, forcereprocess: false})
:error ->
EmailToSendContext.mark_sent(email.idemailtosend, %{errormsg: "Error", retries: email.retries + 1, forcereprocess: false})
IO.inspect(res, label: "===> ERROR ======>")
IO.inspect(term, label: "===> ERROR ======>")
_ ->
IO.inspect(res)
end
else
EmailToSendContext.mark_sent(email.idemailtosend, %{errormsg: "Mail Incorrecto", retries: 10, forcereprocess: false})
end
else
IO.puts("No hay estudios para enviar. Revisar que los mismos tengan un informe y mail correcto")
end
end
end

4
lib/api/mailer.ex Normal file
View File

@ -0,0 +1,4 @@
defmodule Api.Mailer do
use Swoosh.Mailer, otp_app: :api,
adapter: Swoosh.Adapters.SMTP
end

39
lib/api/pagination.ex Normal file
View File

@ -0,0 +1,39 @@
defmodule Api.Pagination do
import Ecto.Query
alias Api.Repo
def query(query, page, per_page: per_page) when is_binary(page) do
query(query, String.to_integer(page), per_page: per_page)
end
def query(query, page, per_page: per_page) do
query
|> limit(^(per_page + 1))
|> offset(^(per_page * (page - 1)))
|> Repo.all()
end
def page(query, page, per_page: per_page) when is_binary(page) do
page(query, String.to_integer(page), per_page: per_page)
end
def page(query, page, per_page: per_page) do
results = query(query, page, per_page: per_page)
has_next = length(results) > per_page
has_previous = page > 1
count = Repo.one(from(t in subquery(query), select: count("*")))
%{
has_next: has_next,
has_previous: has_previous,
prev_page: page - 1,
page: page,
next_page: page + 1,
first: (page - 1) * per_page + 1,
last: Enum.min([page * per_page, count]),
# total de registros
total_pages: round(count / per_page),
list: Enum.slice(results, 0, per_page)
}
end
end

28
lib/api/release.ex Normal file
View File

@ -0,0 +1,28 @@
defmodule Api.Release do
@moduledoc """
Used for executing DB release tasks when run in production without Mix
installed.
"""
@app :api
def migrate do
load_app()
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
def rollback(repo, version) do
load_app()
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
end
defp repos do
Application.fetch_env!(@app, :ecto_repos)
end
defp load_app do
Application.load(@app)
end
end

5
lib/api/repo.ex Normal file
View File

@ -0,0 +1,5 @@
defmodule Api.Repo do
use Ecto.Repo,
otp_app: :api,
adapter: Ecto.Adapters.Postgres
end