106 lines
3.2 KiB
Python
106 lines
3.2 KiB
Python
|
|
"""Производственный календарь — порт themes/merakomis/day/model.php."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from datetime import date, timedelta
|
||
|
|
|
||
|
|
from app.labor import _column_lookup, _prefixed_col, _resolve_table
|
||
|
|
from app.main import _quote_ident, _table_columns
|
||
|
|
from app.merakomis_schema import DAY_TABLE, DAY_TYPE_NO_WORK
|
||
|
|
|
||
|
|
PER_HOUR_DEFAULT = 8
|
||
|
|
|
||
|
|
|
||
|
|
def _load_day_row(cur, db: str, day: date) -> dict | None:
|
||
|
|
table = _resolve_table(cur, db, DAY_TABLE)
|
||
|
|
cols = _table_columns(cur, db, table)
|
||
|
|
lut = _column_lookup(cols)
|
||
|
|
date_col = _prefixed_col(lut, DAY_TABLE, "date")
|
||
|
|
if not date_col:
|
||
|
|
return None
|
||
|
|
type_col = _prefixed_col(lut, DAY_TABLE, "type")
|
||
|
|
text_col = _prefixed_col(lut, DAY_TABLE, "text")
|
||
|
|
hours_col = _prefixed_col(lut, DAY_TABLE, "hours")
|
||
|
|
tq = _quote_ident(table)
|
||
|
|
sel = [f"{_quote_ident(date_col)} AS d"]
|
||
|
|
if type_col:
|
||
|
|
sel.append(f"{_quote_ident(type_col)} AS type")
|
||
|
|
if text_col:
|
||
|
|
sel.append(f"{_quote_ident(text_col)} AS text")
|
||
|
|
if hours_col:
|
||
|
|
sel.append(f"{_quote_ident(hours_col)} AS hours")
|
||
|
|
cur.execute(
|
||
|
|
f"SELECT {', '.join(sel)} FROM {tq} WHERE {_quote_ident(date_col)} = %s LIMIT 1",
|
||
|
|
(day.isoformat(),),
|
||
|
|
)
|
||
|
|
return cur.fetchone()
|
||
|
|
|
||
|
|
|
||
|
|
def get_work_hours_in_date(day: date) -> float:
|
||
|
|
if day.weekday() < 5:
|
||
|
|
return float(PER_HOUR_DEFAULT)
|
||
|
|
return 0.0
|
||
|
|
|
||
|
|
|
||
|
|
def get_work_hours_by_date(cur, db: str, day: date) -> float:
|
||
|
|
row = _load_day_row(cur, db, day)
|
||
|
|
if row and row.get("hours") is not None:
|
||
|
|
return float(row["hours"])
|
||
|
|
return get_work_hours_in_date(day)
|
||
|
|
|
||
|
|
|
||
|
|
def is_no_work_day(cur, db: str, day: date) -> bool:
|
||
|
|
row = _load_day_row(cur, db, day)
|
||
|
|
if row and int(row.get("type") or 0) == DAY_TYPE_NO_WORK:
|
||
|
|
return True
|
||
|
|
return day.weekday() >= 5
|
||
|
|
|
||
|
|
|
||
|
|
def format_day_row(row: dict) -> dict:
|
||
|
|
d = row.get("d") or row.get("date")
|
||
|
|
if isinstance(d, date):
|
||
|
|
ds = d.isoformat()
|
||
|
|
else:
|
||
|
|
ds = str(d)[:10]
|
||
|
|
return {
|
||
|
|
"date": ds,
|
||
|
|
"type": int(row.get("type") or 0),
|
||
|
|
"text": (row.get("text") or "").strip(),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def get_by_range_formatted(cur, db: str, begin: date, end: date) -> dict[str, dict]:
|
||
|
|
table = _resolve_table(cur, db, DAY_TABLE)
|
||
|
|
cols = _table_columns(cur, db, table)
|
||
|
|
lut = _column_lookup(cols)
|
||
|
|
date_col = _prefixed_col(lut, DAY_TABLE, "date")
|
||
|
|
type_col = _prefixed_col(lut, DAY_TABLE, "type")
|
||
|
|
text_col = _prefixed_col(lut, DAY_TABLE, "text")
|
||
|
|
if not date_col:
|
||
|
|
return {}
|
||
|
|
tq = _quote_ident(table)
|
||
|
|
parts = [f"{_quote_ident(date_col)} AS d"]
|
||
|
|
if type_col:
|
||
|
|
parts.append(f"{_quote_ident(type_col)} AS type")
|
||
|
|
if text_col:
|
||
|
|
parts.append(f"{_quote_ident(text_col)} AS text")
|
||
|
|
cur.execute(
|
||
|
|
f"""
|
||
|
|
SELECT {', '.join(parts)} FROM {tq}
|
||
|
|
WHERE {_quote_ident(date_col)} BETWEEN %s AND %s
|
||
|
|
""",
|
||
|
|
(begin.isoformat(), end.isoformat()),
|
||
|
|
)
|
||
|
|
out: dict[str, dict] = {}
|
||
|
|
for row in cur.fetchall():
|
||
|
|
fmt = format_day_row(row)
|
||
|
|
out[fmt["date"]] = fmt
|
||
|
|
return out
|
||
|
|
|
||
|
|
|
||
|
|
def iter_dates(begin: date, end: date):
|
||
|
|
d = begin
|
||
|
|
while d <= end:
|
||
|
|
yield d
|
||
|
|
d += timedelta(days=1)
|