57 lines
1.7 KiB
Python
57 lines
1.7 KiB
Python
"""Resolução de datas de compra com fallback único e compartilhado.
|
|
|
|
Requisito: quando a data do documento está ilegível/ausente, atribuir o
|
|
**primeiro dia do mês corrente** (o mês da importação/lançamento). Tanto a
|
|
extração por IA 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})$")
|
|
|
|
|
|
def first_of_current_month(reference: date | None = None) -> str:
|
|
ref = reference or date.today()
|
|
return f"{ref.year:04d}-{ref.month:02d}-01"
|
|
|
|
|
|
def _valid_iso(year: int, month: int, day: int) -> str | None:
|
|
try:
|
|
return date(year, month, day).isoformat()
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
def parse_date(value: str | None) -> str | None:
|
|
"""Tenta interpretar uma data em ISO (YYYY-MM-DD) ou BR (dd/mm/aaaa).
|
|
|
|
Retorna a data normalizada em ISO, ou None se não for uma data válida.
|
|
"""
|
|
if not value:
|
|
return None
|
|
text = value.strip()
|
|
m = _ISO_RE.match(text)
|
|
if m:
|
|
return _valid_iso(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_iso(year_i, int(month), int(day))
|
|
return None
|
|
|
|
|
|
def resolve_purchase_date(value: str | None, *, reference: date | None = None) -> str:
|
|
"""Data válida em ISO, ou o 1º dia do mês corrente quando ilegível/ausente."""
|
|
parsed = parse_date(value)
|
|
if parsed:
|
|
return parsed
|
|
return first_of_current_month(reference)
|