From ae9f26af03330bb86dd435f9c2d9d405e70c9b70 Mon Sep 17 00:00:00 2001 From: rangelbruno Date: Mon, 9 Feb 2026 08:18:34 -0300 Subject: [PATCH] =?UTF-8?q?Fix(OnlyOffice):=20Salvar=20e=20Voltar=20na=20p?= =?UTF-8?q?roposi=C3=A7=C3=A3o=20usando=20forcesave=20via=20Command=20Serv?= =?UTF-8?q?ice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrige o problema onde o botão Salvar e Voltar no editor OnlyOffice da proposição redirecionava antes do documento ser salvo, mostrando "Documento não criado". A solução usa o Command Service do OnlyOffice para forçar o salvamento (forcesave) e polling server-side para confirmar que o documento foi persistido antes de redirecionar. Adiciona endpoints check-doc e forcesave para proposição. --- sapl/materia/onlyoffice_views.py | 56 +++++++++++- sapl/materia/urls.py | 7 +- sapl/templates/materia/onlyoffice_editor.html | 87 ++++++++++++++++--- 3 files changed, 133 insertions(+), 17 deletions(-) diff --git a/sapl/materia/onlyoffice_views.py b/sapl/materia/onlyoffice_views.py index fc0d6a3d3..c3c8f9e60 100644 --- a/sapl/materia/onlyoffice_views.py +++ b/sapl/materia/onlyoffice_views.py @@ -228,7 +228,6 @@ def onlyoffice_callback(request, pk): ) logger.info(f"URL substituída para: {download_url}") - logger.info(f"Iniciando download do documento de: {download_url}") proposicao = get_object_or_404(Proposicao, pk=pk) # Baixa o documento do OnlyOffice @@ -245,7 +244,6 @@ def onlyoffice_callback(request, pk): from django.core.files.base import ContentFile filename = f"proposicao_{pk}_{int(time.time())}.docx" - logger.info(f"Salvando arquivo: {filename}") # Remove arquivo antigo se existir if proposicao.texto_original: @@ -265,7 +263,6 @@ def onlyoffice_callback(request, pk): from sapl.utils import gerar_hash_arquivo proposicao.hash_code = gerar_hash_arquivo( proposicao.texto_original.path, str(proposicao.pk)) - logger.info(f"Hash code atualizado: {proposicao.hash_code}") proposicao.save() logger.info(f"Documento salvo com sucesso: {filename}") @@ -285,6 +282,59 @@ def onlyoffice_callback(request, pk): return JsonResponse({"error": 1}) +@login_required +@require_http_methods(["GET"]) +def proposicao_check_doc(request, pk): + """ + Verifica se a proposição já possui documento salvo. + Usado pelo frontend para polling após forcesave do OnlyOffice. + """ + proposicao = get_object_or_404(Proposicao, pk=pk) + has_document = bool(proposicao.texto_original) + return JsonResponse({"has_document": has_document}) + + +@login_required +@require_http_methods(["POST"]) +def proposicao_forcesave(request, pk): + """ + Força o salvamento do documento via OnlyOffice Command Service. + Chama o endpoint /coauthoring/CommandService.ashx com o comando forcesave. + """ + proposicao = get_object_or_404(Proposicao, pk=pk) + + body = json.loads(request.body.decode('utf-8')) + doc_key = body.get('key') + + if not doc_key: + return JsonResponse({"error": "key é obrigatório"}, status=400) + + # Monta a URL do Command Service do OnlyOffice + command_url = f"{settings.ONLYOFFICE_URL}/coauthoring/CommandService.ashx" + + payload = { + "c": "forcesave", + "key": doc_key + } + + # Adiciona JWT se habilitado + if settings.ONLYOFFICE_JWT_ENABLED and settings.ONLYOFFICE_JWT_SECRET: + import jwt + token = jwt.encode(payload, settings.ONLYOFFICE_JWT_SECRET, algorithm='HS256') + payload['token'] = token + + try: + import requests as http_requests + logger.info(f"Forcesave para proposição {pk}: key={doc_key}") + resp = http_requests.post(command_url, json=payload, timeout=10) + result = resp.json() + logger.info(f"Forcesave response para proposição {pk}: {result}") + return JsonResponse(result) + except Exception as e: + logger.error(f"Erro ao chamar forcesave para proposição {pk}: {e}") + return JsonResponse({"error": str(e)}, status=500) + + @login_required def onlyoffice_editor(request, pk): """ diff --git a/sapl/materia/urls.py b/sapl/materia/urls.py index 9390349de..12586d8e0 100644 --- a/sapl/materia/urls.py +++ b/sapl/materia/urls.py @@ -34,7 +34,8 @@ from sapl.materia.views import (AcompanhamentoConfirmarView, PesquisarStatusTramitacaoView, HistoricoProposicaoView) from sapl.materia.onlyoffice_views import (onlyoffice_config, onlyoffice_download, onlyoffice_callback, onlyoffice_editor, - onlyoffice_confirmar_config, onlyoffice_confirmar_editor) + onlyoffice_confirmar_config, onlyoffice_confirmar_editor, + proposicao_check_doc, proposicao_forcesave) from sapl.materia.onlyoffice_materia_views import ( materia_onlyoffice_editor, materia_onlyoffice_config, materia_onlyoffice_download, materia_onlyoffice_callback, @@ -226,6 +227,10 @@ urlpatterns_proposicao = [ name='onlyoffice_download'), url(r'^proposicao/(?P\d+)/onlyoffice/callback$', onlyoffice_callback, name='onlyoffice_callback'), + url(r'^proposicao/(?P\d+)/check-doc$', proposicao_check_doc, + name='proposicao_check_doc'), + url(r'^proposicao/(?P\d+)/forcesave$', proposicao_forcesave, + name='proposicao_forcesave'), url(r'^proposicao/(?P\d+)/onlyoffice/confirmar/config$', onlyoffice_confirmar_config, diff --git a/sapl/templates/materia/onlyoffice_editor.html b/sapl/templates/materia/onlyoffice_editor.html index 72206a41e..376b9e941 100644 --- a/sapl/templates/materia/onlyoffice_editor.html +++ b/sapl/templates/materia/onlyoffice_editor.html @@ -52,22 +52,82 @@