148 lines
7.5 KiB
Python
148 lines
7.5 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Callable
|
|
|
|
from fastapi import Body, FastAPI, File, HTTPException, UploadFile
|
|
|
|
|
|
def register_member_routes(
|
|
app: FastAPI,
|
|
*,
|
|
get_conn,
|
|
member_payload_cls,
|
|
member_bulk_payload_cls,
|
|
parse_as_of: Callable[[str | None], datetime | None],
|
|
fetch_members: Callable[[], list[dict[str, object]]],
|
|
fetch_members_as_of: Callable[[object, datetime], list[dict[str, object]]],
|
|
build_member_compare_items: Callable[[list[dict[str, object]], list[dict[str, object]]], list[dict[str, object]]],
|
|
serialize_member_payload: Callable[[object, int], tuple[object, ...]],
|
|
sync_auth_users_from_members: Callable[[object], None],
|
|
create_history_revision: Callable[[object, str, str], int],
|
|
fetch_history_revision_created_at: Callable[[object, int], datetime],
|
|
sync_member_versions: Callable[[object, list[int], str, int], None],
|
|
sync_seat_assignment_versions: Callable[[object, list[int], str, int], None],
|
|
replace_members: Callable[[list[object]], list[dict[str, object]]],
|
|
parse_import_rows: Callable[[UploadFile, bytes], list[object]],
|
|
) -> None:
|
|
@app.get("/api/members")
|
|
def list_members(as_of: str | None = None) -> dict[str, list[dict[str, object]]]:
|
|
parsed_as_of = parse_as_of(as_of)
|
|
if parsed_as_of is None:
|
|
return {"items": fetch_members()}
|
|
with get_conn() as conn:
|
|
with conn.cursor() as cur:
|
|
return {"items": fetch_members_as_of(cur, parsed_as_of)}
|
|
|
|
@app.get("/api/history/members/compare")
|
|
def compare_members_history(from_date: str, to_date: str) -> dict[str, list[dict[str, object]]]:
|
|
parsed_from = parse_as_of(from_date)
|
|
parsed_to = parse_as_of(to_date)
|
|
if parsed_from is None or parsed_to is None:
|
|
raise HTTPException(status_code=400, detail="from_date and to_date are required.")
|
|
with get_conn() as conn:
|
|
with conn.cursor() as cur:
|
|
from_items = fetch_members_as_of(cur, parsed_from)
|
|
to_items = fetch_members_as_of(cur, parsed_to)
|
|
return {"items": build_member_compare_items(from_items, to_items)}
|
|
|
|
@app.post("/api/members")
|
|
def create_member(payload: dict = Body(...)) -> dict[str, object]:
|
|
payload = member_payload_cls.model_validate(payload)
|
|
with get_conn() as conn:
|
|
with conn.cursor() as cur:
|
|
cur.execute("SELECT COALESCE(MAX(sort_order), -1) + 1 AS next_order FROM members")
|
|
next_order = int(cur.fetchone()["next_order"])
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO members (
|
|
name, employee_id, company, rank, role, department, grp, division, team, cell,
|
|
work_status, work_time, phone, email, seat_label, photo_url, sort_order
|
|
)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
RETURNING id, name, employee_id, company, rank, role, department, grp, division, team, cell,
|
|
work_status, work_time, phone, email, seat_label, photo_url,
|
|
sort_order, created_at, updated_at
|
|
""",
|
|
serialize_member_payload(payload, payload.sort_order if payload.sort_order is not None else next_order),
|
|
)
|
|
member = cur.fetchone()
|
|
sync_auth_users_from_members(cur)
|
|
revision_no = create_history_revision(cur, "member-create", f"Member created id={int(member['id'])}")
|
|
revision_created_at = fetch_history_revision_created_at(cur, revision_no)
|
|
sync_member_versions(cur, [int(member["id"])], "member-create", revision_no, revision_created_at)
|
|
conn.commit()
|
|
return {"item": member}
|
|
|
|
@app.put("/api/members/bulk-sync")
|
|
def bulk_sync_members(payload: dict = Body(...)) -> dict[str, list[dict[str, object]]]:
|
|
payload = member_bulk_payload_cls.model_validate(payload)
|
|
return {"items": replace_members(payload.items)}
|
|
|
|
@app.put("/api/members/{member_id}")
|
|
def update_member(member_id: int, payload: dict = Body(...)) -> dict[str, object]:
|
|
payload = member_payload_cls.model_validate(payload)
|
|
with get_conn() as conn:
|
|
with conn.cursor() as cur:
|
|
cur.execute(
|
|
"""
|
|
UPDATE members
|
|
SET name = %s,
|
|
employee_id = %s,
|
|
company = %s,
|
|
rank = %s,
|
|
role = %s,
|
|
department = %s,
|
|
grp = %s,
|
|
division = %s,
|
|
team = %s,
|
|
cell = %s,
|
|
work_status = %s,
|
|
work_time = %s,
|
|
phone = %s,
|
|
email = %s,
|
|
seat_label = %s,
|
|
photo_url = %s,
|
|
sort_order = COALESCE(%s, sort_order),
|
|
updated_at = NOW()
|
|
WHERE id = %s
|
|
RETURNING id, name, employee_id, company, rank, role, department, grp, division, team, cell,
|
|
work_status, work_time, phone, email, seat_label, photo_url,
|
|
sort_order, created_at, updated_at
|
|
""",
|
|
(*serialize_member_payload(payload, payload.sort_order or 0)[:-1], payload.sort_order, member_id),
|
|
)
|
|
member = cur.fetchone()
|
|
if member is None:
|
|
raise HTTPException(status_code=404, detail="Member not found.")
|
|
sync_auth_users_from_members(cur)
|
|
revision_no = create_history_revision(cur, "member-update", f"Member updated id={member_id}")
|
|
revision_created_at = fetch_history_revision_created_at(cur, revision_no)
|
|
sync_member_versions(cur, [member_id], "member-update", revision_no, revision_created_at)
|
|
sync_seat_assignment_versions(cur, [member_id], "member-update", revision_no, revision_created_at)
|
|
conn.commit()
|
|
return {"item": member}
|
|
|
|
@app.delete("/api/members/{member_id}")
|
|
def delete_member(member_id: int) -> dict[str, bool]:
|
|
with get_conn() as conn:
|
|
with conn.cursor() as cur:
|
|
cur.execute("DELETE FROM members WHERE id = %s", (member_id,))
|
|
deleted = cur.rowcount > 0
|
|
if deleted:
|
|
sync_auth_users_from_members(cur)
|
|
revision_no = create_history_revision(cur, "member-delete", f"Member deleted id={member_id}")
|
|
revision_created_at = fetch_history_revision_created_at(cur, revision_no)
|
|
sync_member_versions(cur, [member_id], "member-delete", revision_no, revision_created_at)
|
|
sync_seat_assignment_versions(cur, [member_id], "member-delete", revision_no, revision_created_at)
|
|
conn.commit()
|
|
if not deleted:
|
|
raise HTTPException(status_code=404, detail="Member not found.")
|
|
return {"ok": True}
|
|
|
|
@app.post("/api/members/import")
|
|
async def import_members(file: UploadFile = File(...)) -> dict[str, list[dict[str, object]]]:
|
|
content = await file.read()
|
|
items = parse_import_rows(file, content)
|
|
return {"items": replace_members(items)}
|