"""Общие запросы для составов проектов (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())