diff --git a/app/routers/clientes.py b/app/routers/clientes.py index d445706..d859038 100644 --- a/app/routers/clientes.py +++ b/app/routers/clientes.py @@ -34,6 +34,10 @@ templates = Jinja2Templates(directory="app/templates") TAMANHO_PAGINA = 100 +OPCOES_POR_PAGINA = [5, 10, 15, 20, 30, 40, 50, 100] +POR_PAGINA_PADRAO = 15 +POR_PAGINA_COOKIE = "clientes_por_pagina" + # Únicos campos que o formulário exige preenchidos — os demais (contrato, # valores, datas) podem ficar em branco e recebem um default sensato # (ver _aplicar_defaults_opcionais) em vez de bloquear o cadastro. @@ -86,6 +90,51 @@ def _campos_obrigatorios_faltando(dados: dict) -> list[str]: return [campo for campo in CAMPOS_OBRIGATORIOS if not dados.get(campo)] +def _resolver_por_pagina(por_pagina_query: int | None, cookie_valor: str | None) -> int: + """ + Decide o tamanho de página efetivo: query string (se for um dos + valores de OPCOES_POR_PAGINA) -> cookie clientes_por_pagina (se for um + valor válido) -> padrão (15). Nunca deixa passar um valor fora de + OPCOES_POR_PAGINA, mesmo que venha adulterado na URL ou no cookie. + """ + if por_pagina_query in OPCOES_POR_PAGINA: + return por_pagina_query + try: + cookie_int = int(cookie_valor) if cookie_valor is not None else None + except ValueError: + cookie_int = None + if cookie_int in OPCOES_POR_PAGINA: + return cookie_int + return POR_PAGINA_PADRAO + + +def _janela_paginas(pagina: int, total_paginas: int) -> list[int | None]: + """ + Lista de números de página a mostrar na paginação, com None + representando uma reticência ("…"). Até 7 páginas, mostra todas. Acima + disso, mostra a primeira, a última, a página atual e uma vizinha de + cada lado, preenchendo os buracos com None. Ex.: página 5 de 20 -> + [1, None, 4, 5, 6, None, 20]. + """ + if total_paginas <= 7: + return list(range(1, total_paginas + 1)) + + nucleo = {1, total_paginas, pagina} + for vizinha in (pagina - 1, pagina + 1): + if 1 <= vizinha <= total_paginas: + nucleo.add(vizinha) + + ordenado = sorted(nucleo) + resultado: list[int | None] = [] + anterior = None + for pag in ordenado: + if anterior is not None and pag - anterior > 1: + resultado.append(None) + resultado.append(pag) + anterior = pag + return resultado + + def _validar_documento(dados: dict, confirmar_dv: bool) -> dict: """ Confere o dígito verificador do documento em CLIENTE.CNPJ (o mesmo diff --git a/tests/test_router_clientes.py b/tests/test_router_clientes.py index 9b008d2..b0e5ac0 100644 --- a/tests/test_router_clientes.py +++ b/tests/test_router_clientes.py @@ -53,6 +53,40 @@ def _form_valido(codigo: str = "000001") -> dict: return dados +def test_resolver_por_pagina_usa_query_quando_valido(): + assert router_clientes._resolver_por_pagina(50, None) == 50 + + +def test_resolver_por_pagina_ignora_query_invalida_usa_cookie(): + assert router_clientes._resolver_por_pagina(999, "30") == 30 + + +def test_resolver_por_pagina_ignora_cookie_invalido_usa_padrao(): + assert router_clientes._resolver_por_pagina(None, "abc") == 15 + assert router_clientes._resolver_por_pagina(None, "999") == 15 + + +def test_resolver_por_pagina_sem_query_nem_cookie_usa_padrao(): + assert router_clientes._resolver_por_pagina(None, None) == 15 + + +def test_janela_paginas_poucas_paginas_mostra_todas(): + assert router_clientes._janela_paginas(1, 5) == [1, 2, 3, 4, 5] + assert router_clientes._janela_paginas(3, 7) == [1, 2, 3, 4, 5, 6, 7] + + +def test_janela_paginas_no_meio_com_reticencias_dos_dois_lados(): + assert router_clientes._janela_paginas(5, 20) == [1, None, 4, 5, 6, None, 20] + + +def test_janela_paginas_primeira_pagina(): + assert router_clientes._janela_paginas(1, 20) == [1, 2, None, 20] + + +def test_janela_paginas_ultima_pagina(): + assert router_clientes._janela_paginas(20, 20) == [1, None, 19, 20] + + def test_listar_clientes(monkeypatch): monkeypatch.setattr( router_clientes.db_clientes,