feat: enhance duty information handling with contact details and current duty view

- Added `bot_username` to settings for dynamic retrieval of the bot's username.
- Implemented `_resolve_bot_username` function to fetch the bot's username if not set, improving user experience in group chats.
- Updated `DutyWithUser` schema to include optional `phone` and `username` fields for enhanced duty information.
- Enhanced API responses to include contact details for users, ensuring better communication.
- Introduced a new current duty view in the web app, displaying active duty information along with contact options.
- Updated CSS styles for better presentation of contact information in duty cards.
- Added unit tests to verify the inclusion of contact details in API responses and the functionality of the current duty view.
This commit is contained in:
2026-03-02 16:09:08 +03:00
parent f8aceabab5
commit e3240d0981
25 changed files with 1126 additions and 44 deletions

View File

@@ -317,8 +317,8 @@ def get_duties(
session: Session,
from_date: str,
to_date: str,
) -> list[tuple[Duty, str]]:
"""Return duties overlapping the given date range with user full_name.
) -> list[tuple[Duty, str, str | None, str | None]]:
"""Return duties overlapping the given date range with user full_name, phone, username.
Args:
session: DB session.
@@ -326,11 +326,11 @@ def get_duties(
to_date: End date YYYY-MM-DD.
Returns:
List of (Duty, full_name) tuples.
List of (Duty, full_name, phone, username) tuples.
"""
to_date_next = to_date_exclusive_iso(to_date)
q = (
session.query(Duty, User.full_name)
session.query(Duty, User.full_name, User.phone, User.username)
.join(User, Duty.user_id == User.id)
.filter(Duty.start_at < to_date_next, Duty.end_at >= from_date)
)
@@ -343,7 +343,7 @@ def get_duties_for_user(
from_date: str,
to_date: str,
event_types: list[str] | None = None,
) -> list[tuple[Duty, str]]:
) -> list[tuple[Duty, str, str | None, str | None]]:
"""Return duties for one user overlapping the date range.
Optionally filter by event_type (e.g. "duty", "unavailable", "vacation").
@@ -357,7 +357,7 @@ def get_duties_for_user(
event_types: If not None, only return duties whose event_type is in this list.
Returns:
List of (Duty, full_name) tuples.
List of (Duty, full_name, phone, username) tuples.
"""
to_date_next = to_date_exclusive_iso(to_date)
filters = [
@@ -368,7 +368,7 @@ def get_duties_for_user(
if event_types is not None:
filters.append(Duty.event_type.in_(event_types))
q = (
session.query(Duty, User.full_name)
session.query(Duty, User.full_name, User.phone, User.username)
.join(User, Duty.user_id == User.id)
.filter(*filters)
)

View File

@@ -55,13 +55,16 @@ class DutyInDb(DutyBase):
class DutyWithUser(DutyInDb):
"""Duty with full_name and event_type for calendar display.
"""Duty with full_name, event_type, and optional contact fields for calendar display.
event_type: only these values are returned; unknown DB values are mapped to "duty" in the API.
phone and username are exposed only to authenticated Mini App users (role-gated).
"""
full_name: str
event_type: Literal["duty", "unavailable", "vacation"] = "duty"
phone: str | None = None
username: str | None = None
model_config = ConfigDict(from_attributes=True)