75 lines
2.5 KiB
Python
75 lines
2.5 KiB
Python
"""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}"
|