"""Resolução de competência (mês/ano) com fallback único e compartilhado. Requisito: quando a competência do documento está ilegível/ausente, atribuir o **mês/ano corrente** (o mês da importação/lançamento) — mesma semântica que o antigo "primeiro dia do mês corrente", um passo mais simples (não precisa mais inventar um dia fictício). Tanto a extração por IA/OCR quanto o cadastro manual passam por aqui, para nunca divergirem. """ from __future__ import annotations import re from datetime import date _ISO_RE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})$") _BR_RE = re.compile(r"^([0-3]?\d)[/.\-]([01]?\d)[/.\-]((?:19|20)?\d{2})$") MESES = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"] # Intervalo de anos oferecido nos seletores de competência da UI. ANOS_COMPETENCIA = list(range(2026, 2031)) def current_competencia(reference: date | None = None) -> tuple[int, int]: ref = reference or date.today() return ref.month, ref.year def _valid_date(year: int, month: int, day: int) -> tuple[int, int] | None: try: parsed = date(year, month, day) except ValueError: return None return parsed.month, parsed.year def parse_competencia(value: str | None) -> tuple[int, int] | None: """Tenta interpretar uma data em ISO (YYYY-MM-DD) ou BR (dd/mm/aaaa) e devolve apenas o (mês, ano) correspondente, descartando o dia. Retorna None (sem fallback) se o valor não for uma data válida — usado quando a ausência de competência deve ficar visível (ex. staging). """ if not value: return None text = value.strip() m = _ISO_RE.match(text) if m: return _valid_date(int(m.group(1)), int(m.group(2)), int(m.group(3))) m = _BR_RE.match(text) if m: day, month, year = m.groups() year_i = int(year) if year_i < 100: year_i += 2000 return _valid_date(year_i, int(month), int(day)) return None def resolve_competencia(value: str | None, *, reference: date | None = None) -> tuple[int, int]: """(mês, ano) válidos a partir de `value`, ou o mês/ano corrente quando ilegível/ausente.""" parsed = parse_competencia(value) if parsed: return parsed return current_competencia(reference) def format_competencia(mes: int | None, ano: int | None) -> str: """Formata mês/ano para exibição, ex. `(6, 2026)` -> "Jun/26".""" if not mes or not ano or not (1 <= mes <= 12): return "" return f"{MESES[mes - 1]}/{ano % 100:02d}"