Linha de OS: tabela linhas-os.yaml como fallback quando o SD.md não declara
Decisão de 2026-09-04, revendo a de 2026-09-03 (sem de-para): o .md continua mandando, e a tabela contrato/linhas-os.yaml (OS Mãe × linha × item) só entra quando o entregável não declara `linha_os`. Linha declarada vai como está e não é conferida contra a tabela; existência/item/status seguem sendo do banco. - caminhos.LINHAS_OS; regras_sd: Canonico(itens, linhas_os), carregar_linhas_os (arquivo ausente = fallback desligado), preparar_linhas_os (valida: formato, linha repetida, dois `padrao`, `padrao` não booleano) e linha_da_tabela. - exporta_sd: ausente → padrão do item; 2+ candidatas sem padrão → pendência nomeando-as; item fora da tabela → pendência. CLI informa quantas linhas vieram da tabela. - app: sessao.adotar_linhas_da_tabela grava a linha no frontmatter da sessão com comentário de origem, para o Salvar SD.md deixar a SD autodeclarada; a tela marca `tabela` e mostra a data da tabela. - testes: SD18, SD27, SD31–36 passam a exportar de saída; SD10 só PO+objetivo; casos sintéticos para tabela ausente, ambígua, sem o item e malformada. - deploy: linhas-os.yaml vive em clientes/ (gitignored) — vai por scp ao volume. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -362,12 +362,18 @@ def revisar():
|
||||
sess = ATUAL["sessao"]
|
||||
if sess is None:
|
||||
return redirect(url_for("inicio"))
|
||||
# A prévia lista os entregáveis pelo `n`, e a adoção da tabela é por índice
|
||||
# no frontmatter: traduz para a tela marcar a origem na linha certa.
|
||||
ents = sess.dados.get("entregaveis") or []
|
||||
da_tabela = {ents[i].get("n"): linha for i, linha in sess.linhas_da_tabela.items()
|
||||
if i < len(ents) and isinstance(ents[i], dict)}
|
||||
return render_template(
|
||||
"revisar.html",
|
||||
s=sess,
|
||||
rotulo=sess.rotulo_estado(),
|
||||
formulario=montar_formulario(sess),
|
||||
previa=sess.previa or {},
|
||||
linhas_da_tabela=da_tabela,
|
||||
aviso=ATUAL.pop("aviso", None),
|
||||
)
|
||||
|
||||
|
||||
+42
-1
@@ -26,7 +26,38 @@ from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
||||
|
||||
from exporta_sd import Pendencia, analisar # noqa: E402
|
||||
from regras_sd import Canonico # noqa: E402
|
||||
from regras_sd import Canonico, linha_da_tabela # noqa: E402
|
||||
|
||||
|
||||
def adotar_linhas_da_tabela(dados, canon: Canonico) -> dict[int, str]:
|
||||
"""Escreve NO OBJETO DA SESSÃO a linha padrão da tabela onde o SD.md não
|
||||
declara `linha_os`. Devolve {índice do entregável: linha} do que adotou.
|
||||
|
||||
Por que mutar aqui, e não deixar só o exportador consultar a tabela: o
|
||||
Salvar SD.md regrava este objeto. Com a linha adotada nele, a SD sai do
|
||||
disco autodeclarada e a tabela deixa de importar para ela — o risco de
|
||||
tabela envelhecida morde uma vez só. O comentário de linha diz de onde
|
||||
veio, para quem abrir o .md depois. O exportador faz a mesma consulta
|
||||
(mesmo helper, mesma tabela), então a CLI sobre o .md original e a app dão
|
||||
os mesmos bytes. Idempotente: só toca em quem não declara.
|
||||
"""
|
||||
tabela = canon.linhas_os
|
||||
if not tabela:
|
||||
return {}
|
||||
adotadas: dict[int, str] = {}
|
||||
for i, e in enumerate(dados.get("entregaveis") or []):
|
||||
if not isinstance(e, dict) or e.get("linha_os") not in (None, ""):
|
||||
continue
|
||||
padrao, _ = linha_da_tabela(tabela, e.get("item"))
|
||||
if not padrao:
|
||||
continue
|
||||
e["linha_os"] = padrao
|
||||
if hasattr(e, "yaml_add_eol_comment"): # CommentedMap do ruamel
|
||||
data = tabela.get("atualizado_em") or "sem data"
|
||||
e.yaml_add_eol_comment(f"da tabela linhas-os.yaml ({data}) — o SD.md não declarava",
|
||||
"linha_os")
|
||||
adotadas[i] = padrao
|
||||
return adotadas
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Estados — os que a tela precisa distinguir, e nenhum a mais
|
||||
@@ -56,6 +87,10 @@ class Sessao:
|
||||
caminho_origem: Path | None = None
|
||||
ultimo_erro: str | None = None
|
||||
exportada_em: str | None = None
|
||||
# {índice do entregável: linha} adotado de linhas-os.yaml porque o SD.md
|
||||
# não declarava — a tela marca a origem, e o Salvar SD.md grava.
|
||||
linhas_da_tabela: dict = field(default_factory=dict)
|
||||
tabela_atualizada_em: str | None = None
|
||||
|
||||
def reavaliar(self, canon: Canonico) -> None:
|
||||
"""Roda o exportador em modo de análise sobre os dados COMO ESTÃO.
|
||||
@@ -63,7 +98,13 @@ class Sessao:
|
||||
É grátis e instantâneo; roda a cada edição. Uma edição depois da
|
||||
exportação volta a sessão para pronta/pendente: o que foi exportado era
|
||||
outra versão dos dados, e a tela não pode dizer "exportada" sobre esta.
|
||||
Antes de analisar, adota da tabela a linha de OS que o .md não declara
|
||||
(ver adotar_linhas_da_tabela) — e só então o exportador vê os dados.
|
||||
"""
|
||||
adotadas = adotar_linhas_da_tabela(self.dados, canon)
|
||||
if adotadas:
|
||||
self.linhas_da_tabela.update(adotadas)
|
||||
self.tabela_atualizada_em = (canon.linhas_os or {}).get("atualizado_em")
|
||||
self.previa, self.pendencias = analisar(self.dados, self.corpo, canon)
|
||||
self.estado = PRONTA if not self.pendencias else COM_PENDENCIAS
|
||||
self.exportada_em = None
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
<td>{{ e.titulo }}</td>
|
||||
<td>{{ e.tipo_entrega }}</td>
|
||||
<td>{{ e.item }}</td>
|
||||
<td class="{{ 'vermelho' if not e.ordem_servico.linha }}">{{ e.ordem_servico.linha or '—' }}</td>
|
||||
<td class="{{ 'vermelho' if not e.ordem_servico.linha }}">{{ e.ordem_servico.linha or '—' }}{% if e.n in linhas_da_tabela %} <span class="selo" title="o SD.md não declarava linha_os; veio de contrato/linhas-os.yaml">tabela</span>{% endif %}</td>
|
||||
<td class="num">{{ e.data_inicio or '—' }}</td>
|
||||
<td class="num">{{ e.data_prevista_termino or '—' }}</td>
|
||||
<td class="num">{% if e.data_inicio and e.data_prevista_termino %}{{ dias(e.data_inicio, e.data_prevista_termino) }}{% else %}—{% endif %}</td>
|
||||
@@ -126,6 +126,14 @@
|
||||
valor <strong>não saem daqui</strong>: o sistema os calcula na carga a partir de datas, item
|
||||
e tipo, com as tabelas vigentes. Linha de OS é o <code>linha_os</code> declarado no SD.md;
|
||||
quem confere se a OS e a linha existem é o sistema de gestão, na carga.</p>
|
||||
{% if linhas_da_tabela %}
|
||||
<p class="nota"><span class="selo">tabela</span> {{ linhas_da_tabela|length }} entregável(is) não
|
||||
declaravam <code>linha_os</code> no SD.md: a linha veio de <code>contrato/linhas-os.yaml</code>
|
||||
(atualizada em {{ s.tabela_atualizada_em or 'data não informada' }}), pelo padrão do item.
|
||||
<strong>Salvar SD.md…</strong> grava essa linha no arquivo, com a origem em comentário —
|
||||
a SD passa a declará-la e a tabela deixa de importar para ela. Se a OS certa for outra,
|
||||
corrija no SD.md.</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user