467 lines
19 KiB
Python
467 lines
19 KiB
Python
|
|
"""Read API табеля: calendar, summary, справочники, permissions."""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
from datetime import date
|
|||
|
|
from typing import Annotated, Any
|
|||
|
|
|
|||
|
|
import pymysql
|
|||
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|||
|
|
|
|||
|
|
from app.emp_schema import EMP_TABLE_CANONICAL
|
|||
|
|
from app.labor import _column_lookup, _prefixed_col, _resolve_table, _parse_date_param
|
|||
|
|
from app.labor_day import get_by_range_formatted
|
|||
|
|
from app.labor_identity import (
|
|||
|
|
ensure_emp_exists,
|
|||
|
|
parse_acting_emp_id,
|
|||
|
|
resolve_target_emp_id,
|
|||
|
|
)
|
|||
|
|
from app.labor_permissions import (
|
|||
|
|
can_read_time_calendar,
|
|||
|
|
can_write_project_member,
|
|||
|
|
can_write_time_entry,
|
|||
|
|
is_admin,
|
|||
|
|
is_write_other_table_write,
|
|||
|
|
)
|
|||
|
|
from app.main import (
|
|||
|
|
_db_name,
|
|||
|
|
_quote_ident,
|
|||
|
|
_table_columns,
|
|||
|
|
get_conn,
|
|||
|
|
require_api_key,
|
|||
|
|
resolve_emp_table,
|
|||
|
|
)
|
|||
|
|
from app.merakomis_schema import (
|
|||
|
|
ABSENCE_DICT_TABLE,
|
|||
|
|
PROJECT_TABLE,
|
|||
|
|
TIME_ABSENCE_TABLE,
|
|||
|
|
TIME_TABLE,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
router = APIRouter()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _emp_name(cur, db: str, emp_id: int) -> str:
|
|||
|
|
table = resolve_emp_table(cur, db)
|
|||
|
|
cols = _table_columns(cur, db, table)
|
|||
|
|
lut = _column_lookup(cols)
|
|||
|
|
id_col = lut.get(f"{EMP_TABLE_CANONICAL.lower()}_id") or lut.get("id")
|
|||
|
|
name_col = lut.get(f"{EMP_TABLE_CANONICAL.lower()}_name") or lut.get("name")
|
|||
|
|
if not id_col or not name_col:
|
|||
|
|
return str(emp_id)
|
|||
|
|
cur.execute(
|
|||
|
|
f"""
|
|||
|
|
SELECT {_quote_ident(name_col)} AS n FROM {_quote_ident(table)}
|
|||
|
|
WHERE {_quote_ident(id_col)} = %s LIMIT 1
|
|||
|
|
""",
|
|||
|
|
(emp_id,),
|
|||
|
|
)
|
|||
|
|
row = cur.fetchone()
|
|||
|
|
return str(row["n"]) if row and row.get("n") else str(emp_id)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _project_row(cur, db: str, project_id: int) -> dict | None:
|
|||
|
|
if not project_id:
|
|||
|
|
return None
|
|||
|
|
table = _resolve_table(cur, db, PROJECT_TABLE)
|
|||
|
|
cols = _table_columns(cur, db, table)
|
|||
|
|
lut = _column_lookup(cols)
|
|||
|
|
id_col = _prefixed_col(lut, PROJECT_TABLE, "id")
|
|||
|
|
if not id_col:
|
|||
|
|
return None
|
|||
|
|
sel = [f"{_quote_ident(id_col)} AS id"]
|
|||
|
|
for f in ("code", "name", "archive", "date", "team"):
|
|||
|
|
c = _prefixed_col(lut, PROJECT_TABLE, f)
|
|||
|
|
if c:
|
|||
|
|
sel.append(f"{_quote_ident(c)} AS {f}")
|
|||
|
|
cur.execute(
|
|||
|
|
f"SELECT {', '.join(sel)} FROM {_quote_ident(table)} WHERE {_quote_ident(id_col)} = %s LIMIT 1",
|
|||
|
|
(project_id,),
|
|||
|
|
)
|
|||
|
|
return cur.fetchone()
|
|||
|
|
|
|||
|
|
|
|||
|
|
@router.get("/api/calendar-days")
|
|||
|
|
def calendar_days(
|
|||
|
|
_auth: Annotated[None, Depends(require_api_key)],
|
|||
|
|
date_from: str = Query(..., description="YYYY-MM-DD"),
|
|||
|
|
date_to: str = Query(..., description="YYYY-MM-DD"),
|
|||
|
|
) -> dict[str, Any]:
|
|||
|
|
d_from = _parse_date_param(date_from, "date_from")
|
|||
|
|
d_to = _parse_date_param(date_to, "date_to")
|
|||
|
|
if not d_from or not d_to:
|
|||
|
|
raise HTTPException(status_code=400, detail="date_from и date_to обязательны")
|
|||
|
|
if d_from > d_to:
|
|||
|
|
raise HTTPException(status_code=400, detail="date_from > date_to")
|
|||
|
|
with get_conn() as c:
|
|||
|
|
with c.cursor() as cur:
|
|||
|
|
items = get_by_range_formatted(cur, _db_name(cur), d_from, d_to)
|
|||
|
|
return {"date_from": d_from.isoformat(), "date_to": d_to.isoformat(), "items": items}
|
|||
|
|
|
|||
|
|
|
|||
|
|
@router.get("/api/absence-types")
|
|||
|
|
def absence_types(
|
|||
|
|
_auth: Annotated[None, Depends(require_api_key)],
|
|||
|
|
) -> dict[str, Any]:
|
|||
|
|
with get_conn() as c:
|
|||
|
|
with c.cursor() as cur:
|
|||
|
|
db = _db_name(cur)
|
|||
|
|
table = _resolve_table(cur, db, ABSENCE_DICT_TABLE)
|
|||
|
|
cols = _table_columns(cur, db, table)
|
|||
|
|
lut = _column_lookup(cols)
|
|||
|
|
id_col = _prefixed_col(lut, ABSENCE_DICT_TABLE, "id")
|
|||
|
|
name_col = _prefixed_col(lut, ABSENCE_DICT_TABLE, "name")
|
|||
|
|
code_col = _prefixed_col(lut, ABSENCE_DICT_TABLE, "code")
|
|||
|
|
vis_col = _prefixed_col(lut, ABSENCE_DICT_TABLE, "vis")
|
|||
|
|
items: list[dict] = [{"id": 0, "title": "—", "code": ""}]
|
|||
|
|
if id_col and name_col:
|
|||
|
|
vis_sql = f" AND {_quote_ident(vis_col)} = 1" if vis_col else ""
|
|||
|
|
code_sql = f", {_quote_ident(code_col)} AS code" if code_col else ", '' AS code"
|
|||
|
|
cur.execute(
|
|||
|
|
f"""
|
|||
|
|
SELECT {_quote_ident(id_col)} AS id,
|
|||
|
|
{_quote_ident(name_col)} AS title
|
|||
|
|
{code_sql}
|
|||
|
|
FROM {_quote_ident(table)}
|
|||
|
|
WHERE 1=1 {vis_sql}
|
|||
|
|
ORDER BY {_quote_ident(id_col)}
|
|||
|
|
"""
|
|||
|
|
)
|
|||
|
|
for row in cur.fetchall():
|
|||
|
|
items.append(
|
|||
|
|
{
|
|||
|
|
"id": int(row["id"]),
|
|||
|
|
"title": row["title"],
|
|||
|
|
"code": row.get("code") or "",
|
|||
|
|
"vis": 1,
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
return {"items": items}
|
|||
|
|
|
|||
|
|
|
|||
|
|
@router.get("/api/labor/permissions")
|
|||
|
|
def labor_permissions(
|
|||
|
|
_auth: Annotated[None, Depends(require_api_key)],
|
|||
|
|
acting_emp_id: Annotated[int, Depends(parse_acting_emp_id)],
|
|||
|
|
target_emp_id: int = Query(..., ge=1),
|
|||
|
|
project_id: int | None = Query(None, ge=0),
|
|||
|
|
member_id: int | None = Query(None, ge=1),
|
|||
|
|
) -> dict[str, Any]:
|
|||
|
|
with get_conn() as c:
|
|||
|
|
with c.cursor() as cur:
|
|||
|
|
db = _db_name(cur)
|
|||
|
|
ensure_emp_exists(cur, db, acting_emp_id, kind="acting")
|
|||
|
|
ensure_emp_exists(cur, db, target_emp_id, kind="target")
|
|||
|
|
pid = int(project_id or 0)
|
|||
|
|
can_member = False
|
|||
|
|
if pid:
|
|||
|
|
can_member = can_write_project_member(
|
|||
|
|
cur,
|
|||
|
|
db,
|
|||
|
|
acting_emp_id,
|
|||
|
|
pid,
|
|||
|
|
member_id=member_id,
|
|||
|
|
target_emp_id=target_emp_id,
|
|||
|
|
)
|
|||
|
|
return {
|
|||
|
|
"acting_emp_id": acting_emp_id,
|
|||
|
|
"target_emp_id": target_emp_id,
|
|||
|
|
"project_id": pid or None,
|
|||
|
|
"member_id": member_id,
|
|||
|
|
"can_read": can_read_time_calendar(
|
|||
|
|
cur, db, acting_emp_id, target_emp_id, pid or None
|
|||
|
|
),
|
|||
|
|
"can_write_time": can_write_time_entry(
|
|||
|
|
cur, db, acting_emp_id, target_emp_id, pid
|
|||
|
|
)
|
|||
|
|
if pid
|
|||
|
|
else False,
|
|||
|
|
"can_write_absence": can_read_time_calendar(
|
|||
|
|
cur, db, acting_emp_id, target_emp_id, pid or None
|
|||
|
|
),
|
|||
|
|
"can_write_member": can_member,
|
|||
|
|
"is_admin": is_admin(cur, db, acting_emp_id),
|
|||
|
|
"is_delegate_writer": is_write_other_table_write(
|
|||
|
|
cur, db, acting_emp_id, target_emp_id
|
|||
|
|
),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
@router.get("/api/time-summary")
|
|||
|
|
def time_summary(
|
|||
|
|
_auth: Annotated[None, Depends(require_api_key)],
|
|||
|
|
acting_emp_id: Annotated[int, Depends(parse_acting_emp_id)],
|
|||
|
|
emp_id: int | None = Query(None, ge=1),
|
|||
|
|
) -> dict[str, Any]:
|
|||
|
|
target = resolve_target_emp_id(acting_emp_id, emp_id)
|
|||
|
|
today = date.today()
|
|||
|
|
periods = {
|
|||
|
|
"month": (date(today.year, today.month, 1), today),
|
|||
|
|
"year": (date(today.year, 1, 1), today),
|
|||
|
|
}
|
|||
|
|
res: dict[str, Any] = {}
|
|||
|
|
|
|||
|
|
with get_conn() as c:
|
|||
|
|
with c.cursor() as cur:
|
|||
|
|
db = _db_name(cur)
|
|||
|
|
ensure_emp_exists(cur, db, acting_emp_id, kind="acting")
|
|||
|
|
ensure_emp_exists(cur, db, target, kind="target")
|
|||
|
|
if not can_read_time_calendar(cur, db, acting_emp_id, target):
|
|||
|
|
raise HTTPException(status_code=403, detail={"code": "forbidden", "message": "Нет прав"})
|
|||
|
|
|
|||
|
|
tt = _resolve_table(cur, db, TIME_TABLE)
|
|||
|
|
pt = _resolve_table(cur, db, PROJECT_TABLE)
|
|||
|
|
tcols = _column_lookup(_table_columns(cur, db, tt))
|
|||
|
|
pcols = _column_lookup(_table_columns(cur, db, pt))
|
|||
|
|
t_emp = _prefixed_col(tcols, TIME_TABLE, "emp")
|
|||
|
|
t_date = _prefixed_col(tcols, TIME_TABLE, "date")
|
|||
|
|
t_dur = _prefixed_col(tcols, TIME_TABLE, "duration")
|
|||
|
|
t_over = _prefixed_col(tcols, TIME_TABLE, "is_over")
|
|||
|
|
t_proj = _prefixed_col(tcols, TIME_TABLE, "project")
|
|||
|
|
p_id = _prefixed_col(pcols, PROJECT_TABLE, "id")
|
|||
|
|
p_code = _prefixed_col(pcols, PROJECT_TABLE, "code")
|
|||
|
|
p_name = _prefixed_col(pcols, PROJECT_TABLE, "name")
|
|||
|
|
|
|||
|
|
for key, (begin, end) in periods.items():
|
|||
|
|
block: dict[str, Any] = {
|
|||
|
|
"project": [],
|
|||
|
|
"total": [],
|
|||
|
|
"absence": [],
|
|||
|
|
"title": f"{begin.year}" if key == "year" else f"{begin.month:02d}.{begin.year}",
|
|||
|
|
}
|
|||
|
|
hours = 0.0
|
|||
|
|
over = 0.0
|
|||
|
|
if all([t_emp, t_date, t_dur, t_over, t_proj, p_id]):
|
|||
|
|
cur.execute(
|
|||
|
|
f"""
|
|||
|
|
SELECT p.{_quote_ident(p_code)} AS code,
|
|||
|
|
p.{_quote_ident(p_name)} AS name,
|
|||
|
|
SUM(t.{_quote_ident(t_dur)}) AS dur,
|
|||
|
|
t.{_quote_ident(t_over)} AS is_over
|
|||
|
|
FROM {_quote_ident(tt)} t
|
|||
|
|
LEFT JOIN {_quote_ident(pt)} p ON p.{_quote_ident(p_id)} = t.{_quote_ident(t_proj)}
|
|||
|
|
WHERE t.{_quote_ident(t_emp)} = %s
|
|||
|
|
AND t.{_quote_ident(t_date)} BETWEEN %s AND %s
|
|||
|
|
GROUP BY t.{_quote_ident(t_proj)}, t.{_quote_ident(t_over)}
|
|||
|
|
""",
|
|||
|
|
(target, begin.isoformat(), end.isoformat()),
|
|||
|
|
)
|
|||
|
|
proj_acc: dict[str, float] = {}
|
|||
|
|
for row in cur.fetchall():
|
|||
|
|
title = row.get("code") or row.get("name") or "?"
|
|||
|
|
dur = float(row["dur"] or 0)
|
|||
|
|
if int(row["is_over"] or 0):
|
|||
|
|
over += dur
|
|||
|
|
else:
|
|||
|
|
hours += dur
|
|||
|
|
proj_acc[title] = proj_acc.get(title, 0) + dur
|
|||
|
|
ci = 1
|
|||
|
|
for title, val in proj_acc.items():
|
|||
|
|
block["project"].append(
|
|||
|
|
{"title": title, "value": val, "class": f"chart-color{ci}"}
|
|||
|
|
)
|
|||
|
|
ci += 1
|
|||
|
|
block["total"] = [
|
|||
|
|
{"title": "Раб.", "value": hours, "class": "color-green"},
|
|||
|
|
{"title": "Сверх.", "value": over, "class": "color-brown"},
|
|||
|
|
]
|
|||
|
|
res[key] = block
|
|||
|
|
|
|||
|
|
res["text"] = ""
|
|||
|
|
return res
|
|||
|
|
|
|||
|
|
|
|||
|
|
@router.get("/api/time-calendar")
|
|||
|
|
def time_calendar(
|
|||
|
|
_auth: Annotated[None, Depends(require_api_key)],
|
|||
|
|
acting_emp_id: Annotated[int, Depends(parse_acting_emp_id)],
|
|||
|
|
emp_id: int | None = Query(None, ge=1),
|
|||
|
|
project_id: int = Query(0, ge=0),
|
|||
|
|
) -> dict[str, Any]:
|
|||
|
|
target = resolve_target_emp_id(acting_emp_id, emp_id)
|
|||
|
|
today = date.today()
|
|||
|
|
|
|||
|
|
with get_conn() as c:
|
|||
|
|
with c.cursor() as cur:
|
|||
|
|
db = _db_name(cur)
|
|||
|
|
ensure_emp_exists(cur, db, acting_emp_id, kind="acting")
|
|||
|
|
ensure_emp_exists(cur, db, target, kind="target")
|
|||
|
|
if not can_read_time_calendar(cur, db, acting_emp_id, target, project_id or None):
|
|||
|
|
raise HTTPException(status_code=403, detail={"code": "forbidden", "message": "Нет прав"})
|
|||
|
|
|
|||
|
|
can_edit = False
|
|||
|
|
archive = False
|
|||
|
|
project_label = "Сводный табель"
|
|||
|
|
project_date = None
|
|||
|
|
day_begin = date(2000, 1, 1)
|
|||
|
|
if project_id:
|
|||
|
|
prow = _project_row(cur, db, project_id)
|
|||
|
|
if not prow:
|
|||
|
|
raise HTTPException(status_code=404, detail={"code": "project_not_found", "message": "Проект не найден"})
|
|||
|
|
project_label = prow.get("code") or prow.get("name") or str(project_id)
|
|||
|
|
project_date = prow.get("date")
|
|||
|
|
if project_date:
|
|||
|
|
if isinstance(project_date, date):
|
|||
|
|
day_begin = project_date
|
|||
|
|
else:
|
|||
|
|
day_begin = date.fromisoformat(str(project_date)[:10])
|
|||
|
|
can_edit = can_write_time_entry(
|
|||
|
|
cur, db, acting_emp_id, target, project_id
|
|||
|
|
)
|
|||
|
|
archive = bool(int(prow.get("archive") or 0))
|
|||
|
|
|
|||
|
|
tt = _resolve_table(cur, db, TIME_TABLE)
|
|||
|
|
tcols = _column_lookup(_table_columns(cur, db, tt))
|
|||
|
|
t_emp = _prefixed_col(tcols, TIME_TABLE, "emp")
|
|||
|
|
t_date = _prefixed_col(tcols, TIME_TABLE, "date")
|
|||
|
|
t_dur = _prefixed_col(tcols, TIME_TABLE, "duration")
|
|||
|
|
t_over = _prefixed_col(tcols, TIME_TABLE, "is_over")
|
|||
|
|
t_proj = _prefixed_col(tcols, TIME_TABLE, "project")
|
|||
|
|
|
|||
|
|
dates: dict[str, dict[str, float]] = {}
|
|||
|
|
base: dict[str, dict[str, float]] = {}
|
|||
|
|
total_hours = 0.0
|
|||
|
|
total_over = 0.0
|
|||
|
|
totals: dict[str, dict[str, str]] = {}
|
|||
|
|
|
|||
|
|
if all([t_emp, t_date, t_dur, t_over]):
|
|||
|
|
conds = [f"{_quote_ident(t_emp)} = %s", f"{_quote_ident(t_dur)} > 0"]
|
|||
|
|
params: list[Any] = [target]
|
|||
|
|
if project_id and t_proj:
|
|||
|
|
conds.append(f"{_quote_ident(t_proj)} = %s")
|
|||
|
|
params.append(project_id)
|
|||
|
|
cur.execute(
|
|||
|
|
f"""
|
|||
|
|
SELECT {_quote_ident(t_date)} AS d,
|
|||
|
|
{_quote_ident(t_dur)} AS duration,
|
|||
|
|
{_quote_ident(t_over)} AS is_over,
|
|||
|
|
{_quote_ident(t_proj)} AS project
|
|||
|
|
FROM {_quote_ident(tt)}
|
|||
|
|
WHERE {' AND '.join(conds)}
|
|||
|
|
""",
|
|||
|
|
tuple(params),
|
|||
|
|
)
|
|||
|
|
for row in cur.fetchall():
|
|||
|
|
ds = row["d"].isoformat() if isinstance(row["d"], date) else str(row["d"])[:10]
|
|||
|
|
dur = float(row["duration"] or 0)
|
|||
|
|
key = "over" if int(row["is_over"] or 0) else "hours"
|
|||
|
|
dates.setdefault(ds, {"hours": 0.0, "over": 0.0})
|
|||
|
|
dates[ds][key] += dur
|
|||
|
|
if not project_id:
|
|||
|
|
base.setdefault(ds, {"hours": 0.0, "over": 0.0})
|
|||
|
|
base[ds][key] += dur
|
|||
|
|
if key == "hours":
|
|||
|
|
total_hours += dur
|
|||
|
|
else:
|
|||
|
|
total_over += dur
|
|||
|
|
|
|||
|
|
cur.execute(
|
|||
|
|
f"""
|
|||
|
|
SELECT {_quote_ident(t_date)} AS d,
|
|||
|
|
{_quote_ident(t_over)} AS is_over,
|
|||
|
|
SUM({_quote_ident(t_dur)}) AS cc
|
|||
|
|
FROM {_quote_ident(tt)}
|
|||
|
|
WHERE {_quote_ident(t_emp)} = %s
|
|||
|
|
GROUP BY {_quote_ident(t_date)}, {_quote_ident(t_over)}
|
|||
|
|
""",
|
|||
|
|
(target,),
|
|||
|
|
)
|
|||
|
|
for row in cur.fetchall():
|
|||
|
|
ds = row["d"].isoformat() if isinstance(row["d"], date) else str(row["d"])[:10]
|
|||
|
|
key = "over" if int(row["is_over"] or 0) else "hours"
|
|||
|
|
base.setdefault(ds, {"hours": 0.0, "over": 0.0})
|
|||
|
|
base[ds][key] += float(row["cc"] or 0)
|
|||
|
|
|
|||
|
|
days = get_by_range_formatted(cur, db, day_begin, today)
|
|||
|
|
events: dict[str, list[str]] = {}
|
|||
|
|
for ds, info in days.items():
|
|||
|
|
if info.get("text"):
|
|||
|
|
events.setdefault(ds, []).append(info["text"])
|
|||
|
|
|
|||
|
|
absence: dict[str, dict] = {}
|
|||
|
|
at = _resolve_table(cur, db, TIME_ABSENCE_TABLE)
|
|||
|
|
acols = _column_lookup(_table_columns(cur, db, at))
|
|||
|
|
a_emp = _prefixed_col(acols, TIME_ABSENCE_TABLE, "emp")
|
|||
|
|
a_date = _prefixed_col(acols, TIME_ABSENCE_TABLE, "date")
|
|||
|
|
a_type = _prefixed_col(acols, TIME_ABSENCE_TABLE, "absence")
|
|||
|
|
if a_emp and a_date and a_type:
|
|||
|
|
dt = _resolve_table(cur, db, ABSENCE_DICT_TABLE)
|
|||
|
|
dcols = _column_lookup(_table_columns(cur, db, dt))
|
|||
|
|
d_id = _prefixed_col(dcols, ABSENCE_DICT_TABLE, "id")
|
|||
|
|
d_name = _prefixed_col(dcols, ABSENCE_DICT_TABLE, "name")
|
|||
|
|
d_code = _prefixed_col(dcols, ABSENCE_DICT_TABLE, "code")
|
|||
|
|
cur.execute(
|
|||
|
|
f"""
|
|||
|
|
SELECT a.{_quote_ident(a_date)} AS d,
|
|||
|
|
a.{_quote_ident(a_type)} AS absence_id,
|
|||
|
|
d.{_quote_ident(d_name)} AS title,
|
|||
|
|
d.{_quote_ident(d_code)} AS code
|
|||
|
|
FROM {_quote_ident(at)} a
|
|||
|
|
LEFT JOIN {_quote_ident(dt)} d ON d.{_quote_ident(d_id)} = a.{_quote_ident(a_type)}
|
|||
|
|
WHERE a.{_quote_ident(a_emp)} = %s AND a.{_quote_ident(a_type)} <> 0
|
|||
|
|
""",
|
|||
|
|
(target,),
|
|||
|
|
)
|
|||
|
|
for row in cur.fetchall():
|
|||
|
|
ds = row["d"].isoformat() if isinstance(row["d"], date) else str(row["d"])[:10]
|
|||
|
|
absence[ds] = {
|
|||
|
|
"id": int(row["absence_id"] or 0),
|
|||
|
|
"title": row.get("title") or "",
|
|||
|
|
"code": row.get("code") or "",
|
|||
|
|
"absence_id": int(row["absence_id"] or 0),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
absence_options: list[dict] = [{"id": 0, "title": "—"}]
|
|||
|
|
dt = _resolve_table(cur, db, ABSENCE_DICT_TABLE)
|
|||
|
|
dcols = _column_lookup(_table_columns(cur, db, dt))
|
|||
|
|
d_id = _prefixed_col(dcols, ABSENCE_DICT_TABLE, "id")
|
|||
|
|
d_name = _prefixed_col(dcols, ABSENCE_DICT_TABLE, "name")
|
|||
|
|
d_vis = _prefixed_col(dcols, ABSENCE_DICT_TABLE, "vis")
|
|||
|
|
if d_id and d_name:
|
|||
|
|
vis_sql = f" AND {_quote_ident(d_vis)} = 1" if d_vis else ""
|
|||
|
|
cur.execute(
|
|||
|
|
f"""
|
|||
|
|
SELECT {_quote_ident(d_id)} AS id, {_quote_ident(d_name)} AS title
|
|||
|
|
FROM {_quote_ident(dt)} WHERE 1=1 {vis_sql}
|
|||
|
|
ORDER BY {_quote_ident(d_id)}
|
|||
|
|
"""
|
|||
|
|
)
|
|||
|
|
for row in cur.fetchall():
|
|||
|
|
absence_options.append(
|
|||
|
|
{"id": int(row["id"]), "title": row["title"]}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"can_edit": can_edit,
|
|||
|
|
"cant_edit": not can_edit,
|
|||
|
|
"dates": dates,
|
|||
|
|
"days": days,
|
|||
|
|
"day_begin": day_begin.isoformat(),
|
|||
|
|
"day_end": today.isoformat(),
|
|||
|
|
"name": _emp_name(cur, db, target),
|
|||
|
|
"project": project_label,
|
|||
|
|
"text": "",
|
|||
|
|
"archive": archive,
|
|||
|
|
"month": {},
|
|||
|
|
"absence": absence,
|
|||
|
|
"absence_stat": {},
|
|||
|
|
"absence_options": absence_options,
|
|||
|
|
"project_date": (
|
|||
|
|
project_date.isoformat()
|
|||
|
|
if isinstance(project_date, date)
|
|||
|
|
else (str(project_date)[:10] if project_date else None)
|
|||
|
|
),
|
|||
|
|
"events": events,
|
|||
|
|
"base": base,
|
|||
|
|
"graph1": {},
|
|||
|
|
"graph2": {},
|
|||
|
|
"totals": totals,
|
|||
|
|
"total": {
|
|||
|
|
"hours": f"{total_hours:g} час.",
|
|||
|
|
"over": f"{total_over:g} час.",
|
|||
|
|
"total": f"{total_hours + total_over:g} час.",
|
|||
|
|
},
|
|||
|
|
"acting_emp_id": acting_emp_id,
|
|||
|
|
"target_emp_id": target,
|
|||
|
|
}
|