"""报销模块日期范围工具(从 bx-backend 迁移)。""" from __future__ import annotations from datetime import datetime, timedelta def current_month_bounds(now: datetime | None = None) -> tuple[datetime, datetime]: """当前自然月 [month_start, next_month_start)。""" if now is None: now = datetime.now() month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) if now.month == 12: month_end = datetime(now.year + 1, 1, 1) else: month_end = datetime(now.year, now.month + 1, 1) return month_start, month_end def parse_create_time_day_bounds(apply_date: str | None) -> tuple[datetime, datetime] | None: if not apply_date or not str(apply_date).strip(): return None try: day_start = datetime.strptime(str(apply_date).strip(), "%Y-%m-%d") return day_start, day_start + timedelta(days=1) except ValueError: return None def _parse_day_start(date_str: str | None) -> datetime | None: if not date_str or not str(date_str).strip(): return None try: return datetime.strptime(str(date_str).strip(), "%Y-%m-%d") except ValueError: return None def resolve_create_time_bounds( apply_date: str | None = None, apply_date_from: str | None = None, apply_date_to: str | None = None, ) -> tuple[datetime | None, datetime | None]: """解析申请时间筛选边界,返回 (含起, 不含止)。""" if apply_date_from or apply_date_to: start = _parse_day_start(apply_date_from) end = None day_end = _parse_day_start(apply_date_to) if day_end is not None: end = day_end + timedelta(days=1) return start, end bounds = parse_create_time_day_bounds(apply_date) if bounds: return bounds[0], bounds[1] return None, None def append_create_time_filters( filters: list, model, *, apply_date: str | None = None, apply_date_from: str | None = None, apply_date_to: str | None = None, ) -> None: start, end = resolve_create_time_bounds( apply_date=apply_date, apply_date_from=apply_date_from, apply_date_to=apply_date_to, ) if start is not None: filters.append(model.create_time >= start) if end is not None: filters.append(model.create_time < end)