222 lines
8.3 KiB
Python
222 lines
8.3 KiB
Python
"""Общие запросы для составов проектов (read + write)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from app.emp_schema import EMP_TABLE_CANONICAL
|
|
from app.labor import _column_lookup, _prefixed_col, _resolve_table
|
|
from app.main import _quote_ident, _table_columns, resolve_emp_table
|
|
from app.project_status import enrich_project_status_fields
|
|
from app.merakomis_schema import (
|
|
PROJECT_SECTION_TABLE,
|
|
PROJECT_TABLE,
|
|
SECTION_TABLE,
|
|
STEP_TABLE,
|
|
TEAM_MEMBER_TABLE,
|
|
)
|
|
|
|
|
|
def project_section_ids(cur, db: str, project_id: int) -> list[int]:
|
|
"""Порт ProjectSection::get — id разделов, допустимых на проекте."""
|
|
ps_t = _resolve_table(cur, db, PROJECT_SECTION_TABLE)
|
|
cols = _table_columns(cur, db, ps_t)
|
|
lut = _column_lookup(cols)
|
|
ps_project = _prefixed_col(lut, PROJECT_SECTION_TABLE, "project")
|
|
ps_section = _prefixed_col(lut, PROJECT_SECTION_TABLE, "section")
|
|
if not ps_project or not ps_section:
|
|
return []
|
|
cur.execute(
|
|
f"""
|
|
SELECT {_quote_ident(ps_section)} AS section_id
|
|
FROM {_quote_ident(ps_t)}
|
|
WHERE {_quote_ident(ps_project)} = %s
|
|
ORDER BY {_quote_ident(ps_section)}
|
|
""",
|
|
(project_id,),
|
|
)
|
|
return [int(r["section_id"]) for r in cur.fetchall() if r.get("section_id")]
|
|
|
|
|
|
def fetch_project_row(cur, db: str, project_id: int) -> dict[str, Any] | None:
|
|
proj_t = _resolve_table(cur, db, PROJECT_TABLE)
|
|
cols = _table_columns(cur, db, proj_t)
|
|
lut = _column_lookup(cols)
|
|
p_id = _prefixed_col(lut, PROJECT_TABLE, "id")
|
|
p_team = _prefixed_col(lut, PROJECT_TABLE, "team")
|
|
p_director = _prefixed_col(lut, PROJECT_TABLE, "director")
|
|
p_removed = _prefixed_col(lut, PROJECT_TABLE, "removed")
|
|
if not p_id:
|
|
return None
|
|
removed_sql = f" AND {_quote_ident(p_removed)} = 0" if p_removed else ""
|
|
fields = [f"{_quote_ident(p_id)} AS id"]
|
|
if p_team:
|
|
fields.append(f"{_quote_ident(p_team)} AS team")
|
|
if p_director:
|
|
fields.append(f"{_quote_ident(p_director)} AS director")
|
|
cur.execute(
|
|
f"SELECT {', '.join(fields)} FROM {_quote_ident(proj_t)}"
|
|
f" WHERE {_quote_ident(p_id)} = %s{removed_sql} LIMIT 1",
|
|
(project_id,),
|
|
)
|
|
return cur.fetchone()
|
|
|
|
|
|
def fetch_team_member_row(
|
|
cur, db: str, team_id: int, emp_id: int
|
|
) -> dict[str, Any] | None:
|
|
mem_t = _resolve_table(cur, db, TEAM_MEMBER_TABLE)
|
|
cols = _table_columns(cur, db, mem_t)
|
|
lut = _column_lookup(cols)
|
|
m_id = _prefixed_col(lut, TEAM_MEMBER_TABLE, "id")
|
|
m_emp = _prefixed_col(lut, TEAM_MEMBER_TABLE, "emp")
|
|
m_team = _prefixed_col(lut, TEAM_MEMBER_TABLE, "team")
|
|
if not all([m_id, m_emp, m_team]):
|
|
return None
|
|
cur.execute(
|
|
f"""
|
|
SELECT {_quote_ident(m_id)} AS id, {_quote_ident(m_emp)} AS emp,
|
|
{_quote_ident(m_team)} AS team
|
|
FROM {_quote_ident(mem_t)}
|
|
WHERE {_quote_ident(m_team)} = %s AND {_quote_ident(m_emp)} = %s
|
|
LIMIT 1
|
|
""",
|
|
(team_id, emp_id),
|
|
)
|
|
return cur.fetchone()
|
|
|
|
|
|
def fetch_member_by_id(cur, db: str, member_id: int) -> dict[str, Any] | None:
|
|
mem_t = _resolve_table(cur, db, TEAM_MEMBER_TABLE)
|
|
cols = _table_columns(cur, db, mem_t)
|
|
lut = _column_lookup(cols)
|
|
m_id = _prefixed_col(lut, TEAM_MEMBER_TABLE, "id")
|
|
m_emp = _prefixed_col(lut, TEAM_MEMBER_TABLE, "emp")
|
|
if not m_id or not m_emp:
|
|
return None
|
|
cur.execute(
|
|
f"""
|
|
SELECT {_quote_ident(m_id)} AS id, {_quote_ident(m_emp)} AS emp
|
|
FROM {_quote_ident(mem_t)}
|
|
WHERE {_quote_ident(m_id)} = %s LIMIT 1
|
|
""",
|
|
(member_id,),
|
|
)
|
|
return cur.fetchone()
|
|
|
|
|
|
def fetch_member_item(
|
|
cur, db: str, project_id: int, emp_id: int
|
|
) -> dict[str, Any] | None:
|
|
"""Один элемент в формате GET /api/project-members."""
|
|
member_t = _resolve_table(cur, db, TEAM_MEMBER_TABLE)
|
|
project_t = _resolve_table(cur, db, PROJECT_TABLE)
|
|
section_t = _resolve_table(cur, db, SECTION_TABLE)
|
|
try:
|
|
step_t = _resolve_table(cur, db, STEP_TABLE)
|
|
except RuntimeError:
|
|
step_t = None
|
|
emp_t = resolve_emp_table(cur, db)
|
|
|
|
m_cols = _table_columns(cur, db, member_t)
|
|
p_cols = _table_columns(cur, db, project_t)
|
|
s_cols = _table_columns(cur, db, section_t)
|
|
st_cols = _table_columns(cur, db, step_t) if step_t else []
|
|
e_cols = _table_columns(cur, db, emp_t)
|
|
ml, pl, sl, stl, el = (
|
|
_column_lookup(m_cols),
|
|
_column_lookup(p_cols),
|
|
_column_lookup(s_cols),
|
|
_column_lookup(st_cols) if step_t else {},
|
|
_column_lookup(e_cols),
|
|
)
|
|
|
|
m_id = _prefixed_col(ml, TEAM_MEMBER_TABLE, "id")
|
|
m_emp = _prefixed_col(ml, TEAM_MEMBER_TABLE, "emp")
|
|
m_team = _prefixed_col(ml, TEAM_MEMBER_TABLE, "team")
|
|
m_section = _prefixed_col(ml, TEAM_MEMBER_TABLE, "section")
|
|
m_role = _prefixed_col(ml, TEAM_MEMBER_TABLE, "role")
|
|
m_active = _prefixed_col(ml, TEAM_MEMBER_TABLE, "active")
|
|
p_id = _prefixed_col(pl, PROJECT_TABLE, "id")
|
|
p_code = _prefixed_col(pl, PROJECT_TABLE, "code")
|
|
p_name = _prefixed_col(pl, PROJECT_TABLE, "name")
|
|
p_step = _prefixed_col(pl, PROJECT_TABLE, "step")
|
|
p_status = _prefixed_col(pl, PROJECT_TABLE, "status")
|
|
p_archive = _prefixed_col(pl, PROJECT_TABLE, "archive")
|
|
p_archive_date = _prefixed_col(pl, PROJECT_TABLE, "archive_date")
|
|
p_team = _prefixed_col(pl, PROJECT_TABLE, "team")
|
|
st_id = _prefixed_col(stl, STEP_TABLE, "id") if step_t else None
|
|
st_name = _prefixed_col(stl, STEP_TABLE, "name") if step_t else None
|
|
step_name_sql = (
|
|
f"COALESCE(st.{_quote_ident(st_name)}, '') AS step_name"
|
|
if st_name and step_t and st_id and p_step
|
|
else "'' AS step_name"
|
|
)
|
|
step_join_sql = (
|
|
f"""
|
|
LEFT JOIN {_quote_ident(step_t)} st
|
|
ON {_quote_ident(st_id)} = {_quote_ident(p_step)}"""
|
|
if step_t and st_id and p_step
|
|
else ""
|
|
)
|
|
s_id = _prefixed_col(sl, SECTION_TABLE, "id")
|
|
s_name = _prefixed_col(sl, SECTION_TABLE, "name")
|
|
e_id = _prefixed_col(el, EMP_TABLE_CANONICAL, "id")
|
|
e_name = _prefixed_col(el, EMP_TABLE_CANONICAL, "name")
|
|
|
|
sql = f"""
|
|
SELECT
|
|
{_quote_ident(m_id)} AS member_id,
|
|
{_quote_ident(m_emp)} AS emp_id,
|
|
{_quote_ident(e_name)} AS emp_name,
|
|
{_quote_ident(p_id)} AS project_id,
|
|
{_quote_ident(p_code)} AS project_code,
|
|
{_quote_ident(p_name)} AS project_name,
|
|
{step_name_sql},
|
|
{_quote_ident(p_status) if p_status else 'NULL'} AS status,
|
|
{_quote_ident(p_archive) if p_archive else '0'} AS archive,
|
|
{_quote_ident(p_archive_date) if p_archive_date else 'NULL'} AS archive_date,
|
|
{_quote_ident(m_section)} AS section_id,
|
|
{_quote_ident(s_name)} AS section_name,
|
|
{_quote_ident(m_role)} AS role,
|
|
{_quote_ident(m_active)} AS active
|
|
FROM {_quote_ident(member_t)} m
|
|
INNER JOIN {_quote_ident(project_t)} p
|
|
ON {_quote_ident(p_team)} = {_quote_ident(m_team)}{step_join_sql}
|
|
LEFT JOIN {_quote_ident(emp_t)} e
|
|
ON {_quote_ident(e_id)} = {_quote_ident(m_emp)}
|
|
LEFT JOIN {_quote_ident(section_t)} s
|
|
ON {_quote_ident(s_id)} = {_quote_ident(m_section)}
|
|
WHERE {_quote_ident(p_id)} = %s AND {_quote_ident(m_emp)} = %s
|
|
LIMIT 1
|
|
"""
|
|
cur.execute(sql, (project_id, emp_id))
|
|
row = cur.fetchone()
|
|
if not row:
|
|
return None
|
|
return enrich_project_status_fields({k: row[k] for k in row})
|
|
|
|
|
|
def emp_is_active_for_team(cur, db: str, emp_id: int) -> bool:
|
|
"""Сотрудник не в архиве и не удалён."""
|
|
table = resolve_emp_table(cur, db)
|
|
cols = _table_columns(cur, db, table)
|
|
lut = _column_lookup(cols)
|
|
prefix = EMP_TABLE_CANONICAL.lower()
|
|
id_col = lut.get(f"{prefix}_id") or lut.get("id")
|
|
archive_col = lut.get(f"{prefix}_archive") or lut.get("archive")
|
|
removed_col = lut.get(f"{prefix}_removed") or lut.get("removed")
|
|
if not id_col:
|
|
return False
|
|
conds = [f"{_quote_ident(id_col)} = %s"]
|
|
params: list = [emp_id]
|
|
if archive_col:
|
|
conds.append(f"{_quote_ident(archive_col)} = 0")
|
|
if removed_col:
|
|
conds.append(f"{_quote_ident(removed_col)} = 0")
|
|
cur.execute(
|
|
f"SELECT 1 AS ok FROM {_quote_ident(table)} WHERE {' AND '.join(conds)} LIMIT 1",
|
|
params,
|
|
)
|
|
return bool(cur.fetchone())
|