From e712c300c67c5ff3846ac79fd734d5a9a02ef62e Mon Sep 17 00:00:00 2001 From: "Andrey Cunh@" Date: Fri, 13 Mar 2026 16:54:09 -0300 Subject: [PATCH] att --- Lançamento de documentos/.project | 11 + ...rtalfornecedor_proxy_example.cpython-313.pyc | Bin 0 -> 8941 bytes .../datasets/dsPortalFornecedorNF.js | 42 --- .../datasets/dsPortalFornecedorStartProcess.js | 174 +++++++++++++ .../.metadata | Bin 623 -> 730 bytes .../totvsflow_lancamento_documento.html | 92 +++---- .../portalfornecedor_endpoint_contract.md | 117 +++++++++ .../portalfornecedor_proxy.env.example | 9 + .../portalfornecedor_proxy_example.py | 214 ++++++++++++++++ .../portalfornecedor_proxy_requirements.txt | 5 + .../src/main/resources/view.ftl | 51 ++-- .../webapp/resources/js/portalfornecedor.js | 242 +++++++----------- .../target/portalfornecedor.war | Bin 31780 -> 31285 bytes 13 files changed, 693 insertions(+), 264 deletions(-) create mode 100644 Lançamento de documentos/.project create mode 100644 Lançamento de documentos/lançamentos/__pycache__/portalfornecedor_proxy_example.cpython-313.pyc delete mode 100644 Lançamento de documentos/lançamentos/datasets/dsPortalFornecedorNF.js create mode 100644 Lançamento de documentos/lançamentos/datasets/dsPortalFornecedorStartProcess.js create mode 100644 Lançamento de documentos/lançamentos/portalfornecedor_endpoint_contract.md create mode 100644 Lançamento de documentos/lançamentos/portalfornecedor_proxy.env.example create mode 100644 Lançamento de documentos/lançamentos/portalfornecedor_proxy_example.py create mode 100644 Lançamento de documentos/lançamentos/portalfornecedor_proxy_requirements.txt diff --git a/Lançamento de documentos/.project b/Lançamento de documentos/.project new file mode 100644 index 0000000..95f01ce --- /dev/null +++ b/Lançamento de documentos/.project @@ -0,0 +1,11 @@ + + + Lançamento de documentos + + + + + + + + diff --git a/Lançamento de documentos/lançamentos/__pycache__/portalfornecedor_proxy_example.cpython-313.pyc b/Lançamento de documentos/lançamentos/__pycache__/portalfornecedor_proxy_example.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09d9e40a076979255e233df6c74c44e27b1c2a72 GIT binary patch literal 8941 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 literal 0 HcmV?d00001 diff --git a/Lançamento de documentos/lançamentos/datasets/dsPortalFornecedorNF.js b/Lançamento de documentos/lançamentos/datasets/dsPortalFornecedorNF.js deleted file mode 100644 index 407dd52..0000000 --- a/Lançamento de documentos/lançamentos/datasets/dsPortalFornecedorNF.js +++ /dev/null @@ -1,42 +0,0 @@ -function createDataset(fields, constraints, sortFields) { - var dataset = DatasetBuilder.newDataset(); - dataset.addColumn("STATUS"); - dataset.addColumn("CAMPO"); - dataset.addColumn("VALOR"); - - try { - dataset.addRow(["DEBUG", "__version__", "dsPortalFornecedorNF_debug_2026_03_12"]); - - if (!constraints || constraints.length === 0) { - dataset.addRow(["DEBUG", "__constraints__", "vazio"]); - return dataset; - } - - for (var i = 0; i < constraints.length; i++) { - var c = constraints[i]; - var fieldName = getFieldNameSafe(c); - var initialValue = getInitialValueSafe(c); - dataset.addRow(["DEBUG", fieldName, initialValue]); - } - - return dataset; - } catch (e) { - var erro = (e && e.message) ? e.message : ("" + e); - dataset.addRow(["ERRO", "__exception__", erro]); - return dataset; - } -} - -function getFieldNameSafe(c) { - if (!c) return ""; - if (typeof c.getFieldName === "function") return (c.getFieldName() || "") + ""; - if (c.fieldName !== undefined && c.fieldName !== null) return (c.fieldName || "") + ""; - return ""; -} - -function getInitialValueSafe(c) { - if (!c) return ""; - if (typeof c.getInitialValue === "function") return (c.getInitialValue() || "") + ""; - if (c.initialValue !== undefined && c.initialValue !== null) return (c.initialValue || "") + ""; - return ""; -} diff --git a/Lançamento de documentos/lançamentos/datasets/dsPortalFornecedorStartProcess.js b/Lançamento de documentos/lançamentos/datasets/dsPortalFornecedorStartProcess.js new file mode 100644 index 0000000..30fa6e9 --- /dev/null +++ b/Lançamento de documentos/lançamentos/datasets/dsPortalFornecedorStartProcess.js @@ -0,0 +1,174 @@ +function createDataset(fields, constraints, sortFields) { + var dataset = DatasetBuilder.newDataset(); + dataset.addColumn("STATUS"); + dataset.addColumn("MESSAGE"); + dataset.addColumn("PROCESS_INSTANCE_ID"); + dataset.addColumn("RAW_RESPONSE"); + + try { + var params = constraintsToMap(constraints); + validateRequired(params); + + var payload = { + targetState: parseInt(params.targetState || "5", 10), + comment: params.comment || "Envio via portal fornecedor", + formFields: { + data_abertura: valueOrEmpty(params.data_abertura), + emitido_por: valueOrEmpty(params.emitido_por), + entidade_responsavel: valueOrEmpty(params.entidade_responsavel), + tipo_cadastro: valueOrEmpty(params.tipo_cadastro), + emailSolicitante: valueOrEmpty(params.emailSolicitante), + cpf: valueOrEmpty(params.cpf), + tipo_documento: valueOrEmpty(params.tipo_documento), + numero_documento: valueOrEmpty(params.numero_documento), + valor: valueOrEmpty(params.valor), + justificativa: valueOrEmpty(params.justificativa) + } + }; + + var clientService = fluigAPI.getAuthorizeClientService(); + var requestData = { + companyId: getCompanyId(), + serviceCode: "fluig_rest", + endpoint: "/process-management/api/v2/processes/FlowEssentials_LancamentodeDocumento/start", + method: "post", + timeoutService: "100", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + }, + options: { + encoding: "UTF-8", + mediaType: "application/json", + useSSL: true + }, + params: payload + }; + + var vo = clientService.invoke(JSON.stringify(requestData)); + var raw = vo ? String(vo.getResult() || "") : ""; + if (!raw) { + throw "fluig_rest retornou vazio."; + } + + var response = parseJsonSafe(raw); + var processInstanceId = extractProcessInstanceId(response); + var responseMessage = extractResponseMessage(response); + + if (isErrorResponse(response, raw)) { + dataset.addRow(["ERROR", responseMessage || "Falha ao iniciar processo.", processInstanceId, raw]); + return dataset; + } + + dataset.addRow(["OK", responseMessage || "Solicitação enviada com sucesso.", processInstanceId, raw]); + return dataset; + } catch (e) { + dataset.addRow(["ERROR", errorMessage(e), "", ""]); + return dataset; + } +} + +function constraintsToMap(constraints) { + var map = {}; + if (!constraints) { + return map; + } + + for (var i = 0; i < constraints.length; i++) { + var c = constraints[i]; + var fieldName = getFieldNameSafe(c); + if (!fieldName) { + continue; + } + map[fieldName] = getInitialValueSafe(c); + } + + return map; +} + +function validateRequired(params) { + var requiredFields = [ + "data_abertura", + "emitido_por", + "entidade_responsavel", + "tipo_cadastro", + "cpf", + "numero_documento", + "justificativa" + ]; + + for (var i = 0; i < requiredFields.length; i++) { + var fieldName = requiredFields[i]; + if (!valueOrEmpty(params[fieldName])) { + throw "Campo obrigatório não informado: " + fieldName; + } + } +} + +function extractProcessInstanceId(response) { + if (!response) return ""; + if (response.processInstanceId) return String(response.processInstanceId); + if (response.content && response.content.processInstanceId) return String(response.content.processInstanceId); + if (response.content && response.content.processInstanceid) return String(response.content.processInstanceid); + if (response.content && response.content.requestNumber) return String(response.content.requestNumber); + return ""; +} + +function extractResponseMessage(response) { + if (!response) return ""; + if (response.message) return String(response.message); + if (response.detailedMessage) return String(response.detailedMessage); + if (response.content && response.content.message) return String(response.content.message); + return ""; +} + +function isErrorResponse(response, raw) { + if (!response) return false; + if (response.code && !extractProcessInstanceId(response)) return true; + if (response.message && String(response.message).toLowerCase().indexOf("erro") >= 0 && !extractProcessInstanceId(response)) return true; + if (raw && raw.indexOf("\"code\"") >= 0 && !extractProcessInstanceId(response)) return true; + return false; +} + +function parseJsonSafe(value) { + try { + return JSON.parse(value); + } catch (e) { + return { raw: value }; + } +} + +function getCompanyId() { + try { + if (typeof getValue === "function") { + return String(getValue("WKCompany") || "1"); + } + } catch (e) { + // ignore + } + + return "1"; +} + +function valueOrEmpty(value) { + return value == null ? "" : String(value); +} + +function errorMessage(e) { + if (e && e.message) return String(e.message); + return String(e); +} + +function getFieldNameSafe(c) { + if (!c) return ""; + if (typeof c.getFieldName === "function") return String(c.getFieldName() || ""); + if (c.fieldName !== undefined && c.fieldName !== null) return String(c.fieldName || ""); + return ""; +} + +function getInitialValueSafe(c) { + if (!c) return ""; + if (typeof c.getInitialValue === "function") return String(c.getInitialValue() || ""); + if (c.initialValue !== undefined && c.initialValue !== null) return String(c.initialValue || ""); + return ""; +} diff --git a/Lançamento de documentos/lançamentos/forms/41254 - totvsflow_lancamento_documento/.metadata b/Lançamento de documentos/lançamentos/forms/41254 - totvsflow_lancamento_documento/.metadata index 9eca6c27a0e91ca776e2fc046a7e6345efa45717..ab065a3796ca7b1c09e8a57a09c94f9c167f3531 100644 GIT binary patch delta 86 zcmaFQa*K7s6?Ud_76t|erj55V7e-6EDG=d diff --git a/Lançamento de documentos/lançamentos/forms/41254 - totvsflow_lancamento_documento/totvsflow_lancamento_documento.html b/Lançamento de documentos/lançamentos/forms/41254 - totvsflow_lancamento_documento/totvsflow_lancamento_documento.html index 701035a..62ad23e 100644 --- a/Lançamento de documentos/lançamentos/forms/41254 - totvsflow_lancamento_documento/totvsflow_lancamento_documento.html +++ b/Lançamento de documentos/lançamentos/forms/41254 - totvsflow_lancamento_documento/totvsflow_lancamento_documento.html @@ -1,4 +1,4 @@ - + @@ -79,7 +79,7 @@ -

Lançamento de documento

+

Lançamento de documento

@@ -90,7 +90,7 @@

 Dados do documento 

-
Dados referentes ao documento que será lançado.
+
Dados referentes ao documento que será lançado.

@@ -117,7 +117,7 @@
- + *
- + Tipo de cadastro * + obrigatório

* @@ -176,7 +176,7 @@ mask="00.000.000/0000-00" class="form-control" readonly /> + obrigatório

@@ -199,7 +199,7 @@ />

@@ -208,7 +208,7 @@ @@ -219,13 +219,13 @@
- * + * + placeholder="Inserir número do documento" class="form-control" /> + obrigatório

@@ -245,8 +245,8 @@ * -

Utilize a aba anexos para anexar o documento ou o botão - abaixo. Anexo obrigatório.

+

Utilize a aba anexos para anexar o documento ou o botão + abaixo. Anexo obrigatório.

@@ -258,21 +258,21 @@

 Descrição dos serviços +  Descrição dos serviços  

-
Descrição detalhada dos serviços prestados e as possíveis informações complementares.
+
Descrição detalhada dos serviços prestados e as possíveis informações complementares.

- Descrição dos serviços * + placeholder="Descreva os serviços prestados."> + obrigatório

@@ -288,9 +288,9 @@ @@ -416,17 +416,17 @@
@@ -436,12 +436,12 @@ -
-
-
-
-
-
- - + obrigatório

@@ -479,7 +469,7 @@

-
Todos os campos com * são de preenchimento obrigatório.
+
Todos os campos com * são de preenchimento obrigatório.
@@ -1131,8 +1121,8 @@ function msgModal(size) { FLUIGC.modal({ - title: "Atenção", - content: "Existem campos que estão preenchidos incorretamente e/ou não foram preenchidos. Confira e realize as correções nos campos indicados.", + title: "Atenção", + content: "Existem campos que estão preenchidos incorretamente e/ou não foram preenchidos. Confira e realize as correções nos campos indicados.", id: 'fluig-modal', size: size, actions: [{ @@ -1156,7 +1146,7 @@ document.getElementById("mensagemErroDiasExpiracao").textContent = ""; } else { // O valor não é um número positivo, exibe mensagem de erro - document.getElementById("mensagemErroDiasExpiracao").textContent = "Digite um número positivo válido."; + document.getElementById("mensagemErroDiasExpiracao").textContent = "Digite um número positivo válido."; } } diff --git a/Lançamento de documentos/lançamentos/portalfornecedor_endpoint_contract.md b/Lançamento de documentos/lançamentos/portalfornecedor_endpoint_contract.md new file mode 100644 index 0000000..f6bd8d9 --- /dev/null +++ b/Lançamento de documentos/lançamentos/portalfornecedor_endpoint_contract.md @@ -0,0 +1,117 @@ +# Portal Fornecedor - contrato do endpoint intermediario + +## Objetivo + +A widget publica nao deve chamar diretamente: + +- `/process-management/api/v2/processes/FlowEssentials_LancamentodeDocumento/start` +- `/api/public/ecm/dataset/datasets` + +Ela deve chamar um endpoint intermediario no servidor, por exemplo: + +- `POST /api/public/portalfornecedor/enviar` + +Esse endpoint e quem usa `fluig_rest` no backend. + +## Request esperado da widget + +```json +{ + "targetState": 5, + "comment": "Envio via portal fornecedor", + "formFields": { + "data_abertura": "2026-03-13", + "emitido_por": "fornecedor", + "entidade_responsavel": "Empresa X", + "tipo_cadastro": "cpf", + "emailSolicitante": "email@empresa.com.br", + "cpf": "12345678900", + "tipo_documento": "danfe", + "numero_documento": "123456", + "valor": "10,00", + "justificativa": "Descricao do servico" + } +} +``` + +## Response de sucesso + +```json +{ + "success": true, + "message": "Solicitacao enviada com sucesso.", + "processInstanceId": "12345", + "content": { + "processInstanceId": "12345" + } +} +``` + +## Response de erro + +```json +{ + "success": false, + "message": "Descricao do erro" +} +``` + +## Logica esperada no backend + +1. Receber o JSON da widget. +2. Validar os campos obrigatorios. +3. Usar `fluigAPI.getAuthorizeClientService()`. +4. Invocar o servico `fluig_rest`. +5. Chamar o endpoint final: + `/process-management/api/v2/processes/FlowEssentials_LancamentodeDocumento/start` +6. Retornar para a widget somente o resultado final. + +## Exemplo de chamada server-side + +```javascript +var clientService = fluigAPI.getAuthorizeClientService(); + +var requestData = { + companyId: String(getValue("WKCompany") || "1"), + serviceCode: "fluig_rest", + endpoint: "/process-management/api/v2/processes/FlowEssentials_LancamentodeDocumento/start", + method: "post", + timeoutService: "100", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + }, + options: { + encoding: "UTF-8", + mediaType: "application/json", + useSSL: true + }, + params: payloadRecebidoDaWidget +}; + +var vo = clientService.invoke(JSON.stringify(requestData)); +var raw = String(vo.getResult() || ""); +var response = JSON.parse(raw); +``` + +## Configuracao da widget + +Por padrao a widget usa: + +- `https://api.grupoginseng.com.br/v2/portalfornecedor/enviar_api_public_portalfornecedor_enviar_post` + +Se necessario, sobrescreva antes de carregar a widget: + +```html + +``` + +## Observacao importante + +`apiKey` no front nao substitui OAuth 1.0 do Fluig. Se existir um header como `apiKey`, +ele deve ser validado apenas no endpoint intermediario. O endpoint intermediario continua +sendo o responsavel por usar `fluig_rest` no servidor. diff --git a/Lançamento de documentos/lançamentos/portalfornecedor_proxy.env.example b/Lançamento de documentos/lançamentos/portalfornecedor_proxy.env.example new file mode 100644 index 0000000..1929f22 --- /dev/null +++ b/Lançamento de documentos/lançamentos/portalfornecedor_proxy.env.example @@ -0,0 +1,9 @@ +PORTAL_FORNECEDOR_BASE_URL=https://comerciode188006.fluig.cloudtotvs.com.br +PORTAL_FORNECEDOR_PROCESS_ID=FlowEssentials_LancamentodeDocumento +PORTAL_FORNECEDOR_CLIENT_KEY=your_consumer_key +PORTAL_FORNECEDOR_CLIENT_SECRET=your_consumer_secret +PORTAL_FORNECEDOR_RESOURCE_OWNER_KEY=your_access_token +PORTAL_FORNECEDOR_RESOURCE_OWNER_SECRET=your_token_secret +PORTAL_FORNECEDOR_COMPANY_ID=1 +PORTAL_FORNECEDOR_PARENT_FOLDER_ID=10 +PORTAL_FORNECEDOR_CORS_ORIGINS=https://comerciode188006.fluig.cloudtotvs.com.br diff --git a/Lançamento de documentos/lançamentos/portalfornecedor_proxy_example.py b/Lançamento de documentos/lançamentos/portalfornecedor_proxy_example.py new file mode 100644 index 0000000..025c31f --- /dev/null +++ b/Lançamento de documentos/lançamentos/portalfornecedor_proxy_example.py @@ -0,0 +1,214 @@ +from __future__ import annotations + +import os +from typing import Any + +import requests +from fastapi import FastAPI, File, Form, HTTPException, UploadFile +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +from requests_oauthlib import OAuth1 + + +class PortalFornecedorSuccessResponse(BaseModel): + success: bool = True + message: str + processInstanceId: str = "" + content: dict[str, Any] + + +class PortalFornecedorErrorDetail(BaseModel): + message: str | None = None + + +def env(name: str, default: str = "") -> str: + value = os.getenv(name, default).strip() + if not value: + raise RuntimeError(f"Missing environment variable: {name}") + return value + + +app = FastAPI(title="Portal Fornecedor Proxy") + +app.add_middleware( + CORSMiddleware, + allow_origins=os.getenv("PORTAL_FORNECEDOR_CORS_ORIGINS", "*").split(","), + allow_credentials=False, + allow_methods=["POST", "OPTIONS", "GET"], + allow_headers=["*"], +) + + +@app.get("/health") +def health() -> dict[str, str]: + return {"status": "ok"} + + +@app.post( + "/api/public/portalfornecedor/enviar", + response_model=PortalFornecedorSuccessResponse, + responses={ + 400: {"model": PortalFornecedorErrorDetail}, + 401: {"model": PortalFornecedorErrorDetail}, + 500: {"model": PortalFornecedorErrorDetail}, + }, +) +async def enviar( + arquivo: UploadFile = File(...), + targetState: int = Form(5), + comment: str = Form("Envio via portal fornecedor"), + data_abertura: str = Form(...), + emitido_por: str = Form(...), + entidade_responsavel: str = Form(...), + tipo_cadastro: str = Form(...), + emailSolicitante: str = Form(""), + cpf: str = Form(...), + tipo_documento: str = Form(""), + numero_documento: str = Form(...), + valor: str = Form(""), + justificativa: str = Form(...), +) -> PortalFornecedorSuccessResponse: + file_name = arquivo.filename or "anexo" + file_mime = arquivo.content_type or "application/octet-stream" + file_bytes = await arquivo.read() + + if not file_bytes: + raise HTTPException(status_code=400, detail={"message": "Arquivo obrigatorio."}) + + auth = build_auth() + upload_binary(file_name, file_bytes, auth) + document_id = create_document(file_name, file_mime, auth) + + process_payload = { + "targetState": targetState, + "comment": comment, + "formFields": { + "data_abertura": data_abertura, + "emitido_por": emitido_por, + "entidade_responsavel": entidade_responsavel, + "tipo_cadastro": tipo_cadastro, + "emailSolicitante": emailSolicitante, + "cpf": cpf, + "tipo_documento": tipo_documento, + "numero_documento": numero_documento, + "valor": valor, + "justificativa": justificativa, + "anexo_documento_id": str(document_id), + "anexo_documento_nome": file_name, + "anexo_documento_mime": file_mime, + }, + } + + response = requests.post( + process_start_endpoint(), + json=process_payload, + auth=auth, + headers={"Accept": "application/json"}, + timeout=30, + ) + + if not response.ok: + raise HTTPException(status_code=response.status_code, detail=safe_json(response)) + + data = safe_json(response) + + return PortalFornecedorSuccessResponse( + success=True, + message="Solicitacao enviada com sucesso.", + processInstanceId=extract_process_instance_id(data), + content=data, + ) + + +def build_auth() -> OAuth1: + return OAuth1( + client_key=env("PORTAL_FORNECEDOR_CLIENT_KEY"), + client_secret=env("PORTAL_FORNECEDOR_CLIENT_SECRET"), + resource_owner_key=env("PORTAL_FORNECEDOR_RESOURCE_OWNER_KEY"), + resource_owner_secret=env("PORTAL_FORNECEDOR_RESOURCE_OWNER_SECRET"), + signature_method="HMAC-SHA1", + ) + + +def base_url() -> str: + return env("PORTAL_FORNECEDOR_BASE_URL").rstrip("/") + + +def process_start_endpoint() -> str: + process_id = env("PORTAL_FORNECEDOR_PROCESS_ID", "FlowEssentials_LancamentodeDocumento") + return f"{base_url()}/process-management/api/v2/processes/{process_id}/start" + + +def upload_binary(file_name: str, file_bytes: bytes, auth: OAuth1) -> None: + response = requests.post( + f"{base_url()}/api/public/2.0/contentfiles/upload/", + params={"fileName": file_name}, + data=file_bytes, + auth=auth, + headers={ + "Content-Type": "application/octet-stream", + "Accept": "application/json", + }, + timeout=30, + ) + + if not response.ok: + raise HTTPException(status_code=response.status_code, detail=safe_json(response)) + + +def create_document(file_name: str, mime_type: str, auth: OAuth1) -> str: + payload = { + "companyId": env("PORTAL_FORNECEDOR_COMPANY_ID", "1"), + "description": file_name, + "parentId": int(env("PORTAL_FORNECEDOR_PARENT_FOLDER_ID", "10")), + "immutable": True, + "isPrivate": False, + "downloadEnabled": True, + "attachments": [{"fileName": file_name}], + "additionalComments": mime_type, + } + + response = requests.post( + f"{base_url()}/api/public/ecm/document/createDocument", + json=payload, + auth=auth, + headers={"Accept": "application/json"}, + timeout=30, + ) + + if not response.ok: + raise HTTPException(status_code=response.status_code, detail=safe_json(response)) + + data = safe_json(response) + content = data.get("content", {}) if isinstance(data, dict) else {} + document_id = content.get("id") or content.get("documentId") + + if not document_id: + raise HTTPException(status_code=500, detail={"message": "Fluig nao retornou documentId do anexo."}) + + return str(document_id) + + +def safe_json(response: requests.Response) -> Any: + try: + return response.json() + except Exception: + return {"message": response.text} + + +def extract_process_instance_id(data: Any) -> str: + if not isinstance(data, dict): + return "" + if data.get("processInstanceId"): + return str(data["processInstanceId"]) + + content = data.get("content") + if isinstance(content, dict): + if content.get("processInstanceId"): + return str(content["processInstanceId"]) + if content.get("processInstanceid"): + return str(content["processInstanceid"]) + if content.get("requestNumber"): + return str(content["requestNumber"]) + + return "" diff --git a/Lançamento de documentos/lançamentos/portalfornecedor_proxy_requirements.txt b/Lançamento de documentos/lançamentos/portalfornecedor_proxy_requirements.txt new file mode 100644 index 0000000..30f7c4e --- /dev/null +++ b/Lançamento de documentos/lançamentos/portalfornecedor_proxy_requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.115.12 +uvicorn==0.34.0 +requests==2.32.3 +requests-oauthlib==2.0.0 +python-multipart==0.0.20 diff --git a/Lançamento de documentos/lançamentos/wcm/widget/portalfornecedor/src/main/resources/view.ftl b/Lançamento de documentos/lançamentos/wcm/widget/portalfornecedor/src/main/resources/view.ftl index 2aaf5fe..2288fc2 100644 --- a/Lançamento de documentos/lançamentos/wcm/widget/portalfornecedor/src/main/resources/view.ftl +++ b/Lançamento de documentos/lançamentos/wcm/widget/portalfornecedor/src/main/resources/view.ftl @@ -1,4 +1,4 @@ -
@@ -102,6 +102,32 @@ data-params="MyWidget.instance()">
+
+ +
+ + + +

+ Selecione o arquivo ou use a camera do celular para capturar o documento. +

+ + + + + +
+ +
+ +
+
@@ -131,29 +157,6 @@ data-params="MyWidget.instance()">
-
- -
- - - -

- Utilize o botão abaixo para anexar o documento. -

- - - - - -
- -
- -
-
diff --git a/Lançamento de documentos/lançamentos/wcm/widget/portalfornecedor/src/main/webapp/resources/js/portalfornecedor.js b/Lançamento de documentos/lançamentos/wcm/widget/portalfornecedor/src/main/webapp/resources/js/portalfornecedor.js index 79fddb5..44b7a37 100644 --- a/Lançamento de documentos/lançamentos/wcm/widget/portalfornecedor/src/main/webapp/resources/js/portalfornecedor.js +++ b/Lançamento de documentos/lançamentos/wcm/widget/portalfornecedor/src/main/webapp/resources/js/portalfornecedor.js @@ -1,9 +1,10 @@ -if (typeof window !== "undefined") { +if (typeof window !== "undefined") { window.WCMAPI = window.WCMAPI || {}; if (typeof window.WCMAPI.isMobileAppMode !== "function") { window.WCMAPI.isMobileAppMode = function () { return false; }; } } + function showCamera(param) { if (typeof JSInterface !== "undefined" && JSInterface && typeof JSInterface.showCamera === "function") { JSInterface.showCamera(param); @@ -16,7 +17,6 @@ var MyWidget = SuperWidget.extend({ init: function () { this.root = $("#MyWidget_" + this.instanceId); this.anexoInfo = null; - this.gedParentFolderId = 10; this.isSending = false; this.bindEvents(); this.setupInitialValues(); @@ -29,11 +29,11 @@ var MyWidget = SuperWidget.extend({ self.enviarDocumento(); }); - this.root.find("#anexaDocumento").off("click").on("click", function () { + this.root.find("#anexaDocumento_" + this.instanceId + ", #anexaDocumento").off("click").on("click", function () { self.handleAnexo(); }); - this.root.find("#arquivoLocal").off("change").on("change", function () { + this.root.find("#arquivoLocal_" + this.instanceId + ", #arquivoLocal").off("change").on("change", function () { self.onArquivoSelecionado(this); }); @@ -57,6 +57,7 @@ var MyWidget = SuperWidget.extend({ this.root.find("#tipo_cadastro").val("cpf").trigger("change"); this.root.find("#emitido_por").val("fornecedor"); this.root.find("#tipo_documento").val("danfe"); + this.root.find("#arquivoLocalNome").text(""); this.clearStatus(); }, @@ -78,48 +79,10 @@ var MyWidget = SuperWidget.extend({ this.root.find("#arquivoLocalNome").text("Arquivo selecionado: " + file.name + " (" + this.formatFileSize(file.size || 0) + ")"); this.clearStatus(); - - console.log("[portalfornecedor] anexo preparado para envio:", { - fileName: this.anexoInfo.fileName, - mimeType: this.anexoInfo.mimeType, - fileSize: this.anexoInfo.fileSize - }); }, handleAnexo: function () { - if (showCamera("anexo_documento")) { - return; - } - - if (this.openAttachmentTab()) { - return; - } - - this.root.find("#arquivoLocal").trigger("click"); - }, - - openAttachmentTab: function () { - try { - var parentDoc = window.parent && window.parent.document ? window.parent.document : document; - var selectors = [ - "#tab-attachments", - "a[href*='attachments']", - "a[aria-controls*='attachments']", - "[data-tab*='attachments']" - ]; - - for (var i = 0; i < selectors.length; i++) { - var el = parentDoc.querySelector(selectors[i]); - if (el) { - el.click(); - return true; - } - } - } catch (e) { - // ignore - } - - return false; + this.root.find("#arquivoLocal_" + this.instanceId + ", #arquivoLocal").trigger("click"); }, toggleTipoCadastro: function (tipo) { @@ -188,61 +151,44 @@ var MyWidget = SuperWidget.extend({ this.setLoading(true, "Enviando documento, aguarde..."); - this.uploadAnexoToECM(this.anexoInfo.file) - .done(function (docData) { - var payloadProcesso = { - targetState: 0, - subProcessTargetState: 0, - comment: "Solicitacao criada via widget", - formFields: { - data_abertura: self.value("#data_abertura"), - emitido_por: self.value("#emitido_por"), - entidade_responsavel: self.value("#entidade_responsavel"), - tipo_cadastro: tipoCadastro, - emailSolicitante: self.value("#emailSolicitante"), - cpf: cpfField || documentoPessoa, - tipo_documento: self.value("#tipo_documento"), - numero_documento: numeroDocumento, - valor: valor, - justificativa: self.value("#justificativa"), - anexo_documento_id: String(docData.documentId || ""), - anexo_documento_nome: self.anexoInfo.fileName, - anexo_documento_mime: self.anexoInfo.mimeType - } - }; + var payloadProcesso = { + targetState: 5, + comment: "Envio via portal fornecedor", + formFields: { + data_abertura: self.value("#data_abertura"), + emitido_por: self.value("#emitido_por"), + entidade_responsavel: self.value("#entidade_responsavel"), + tipo_cadastro: tipoCadastro, + emailSolicitante: self.value("#emailSolicitante"), + cpf: cpfField || documentoPessoa, + tipo_documento: self.value("#tipo_documento"), + numero_documento: numeroDocumento, + valor: valor, + justificativa: self.value("#justificativa") + } + }; - window.__portalfornecedor_lastProcessPayload = payloadProcesso; - console.log("[portalfornecedor] payload direto processo:", payloadProcesso); + window.__portalfornecedor_lastProcessPayload = payloadProcesso; + console.log("[portalfornecedor] payload endpoint:", payloadProcesso); - self.enviarDiretoProcesso(payloadProcesso) - .done(function (response) { - console.log("[portalfornecedor] response processo direto:", response); - self.setLoading(false); - self.renderSuccessState(response); - }) - .fail(function (xhrDireto) { - self.setLoading(false); - console.warn("[portalfornecedor] falha no envio direto:", xhrDireto); - console.error("[portalfornecedor] detalhe erro processo direto:", { - status: xhrDireto.status, - statusText: xhrDireto.statusText, - responseText: xhrDireto.responseText - }); - self.setStatus("error", "Falha ao iniciar processo. Veja o console para detalhes."); - FLUIGC.toast({ - title: "Erro", - message: "Falha ao iniciar processo. Veja o console para detalhes.", - type: "danger" - }); - }); - }) - .fail(function (uploadErr) { + this.enviarViaEndpoint(payloadProcesso, this.anexoInfo.file) + .done(function (response) { + console.log("[portalfornecedor] response endpoint:", response); self.setLoading(false); - console.error("[portalfornecedor] falha upload documento ECM:", uploadErr); - self.setStatus("error", "Falha ao enviar anexo para o GED."); + self.renderSuccessState(response); + }) + .fail(function (xhr) { + self.setLoading(false); + console.warn("[portalfornecedor] falha no envio via endpoint:", xhr); + console.error("[portalfornecedor] detalhe erro endpoint:", { + status: xhr.status, + statusText: xhr.statusText, + responseText: xhr.responseText + }); + self.setStatus("error", self.extractEndpointErrorMessage(xhr)); FLUIGC.toast({ title: "Erro", - message: "Falha ao enviar anexo para o GED.", + message: "Falha ao enviar pelo endpoint do portal.", type: "danger" }); }); @@ -270,67 +216,68 @@ var MyWidget = SuperWidget.extend({ return ""; }, - enviarDiretoProcesso: function (payloadProcesso) { + enviarViaEndpoint: function (payloadProcesso, file) { return $.ajax({ - url: "/process-management/api/v2/processes/FlowEssentials_LancamentodeDocumento/start", + url: this.getPublicEndpointUrl(), type: "POST", - contentType: "application/json", - data: JSON.stringify(payloadProcesso) + data: this.buildMultipartData(payloadProcesso, file), + processData: false, + contentType: false }); }, - uploadAnexoToECM: function (file) { - var self = this; - var dfd = $.Deferred(); - var fileName = file.name; - var uploadUrl = "/api/public/2.0/contentfiles/upload/?fileName=" + encodeURIComponent(fileName); + getPublicEndpointUrl: function () { + if (window.portalfornecedorConfig && window.portalfornecedorConfig.publicEndpointUrl) { + return window.portalfornecedorConfig.publicEndpointUrl; + } - fetch(uploadUrl, { - method: "POST", - headers: { - "Content-Type": "application/octet-stream" - }, - body: file - }) - .then(function (resp) { - if (!resp.ok) throw new Error("Falha no upload binario"); - return resp.text(); - }) - .then(function () { - var companyId = (window.WCMAPI && WCMAPI.organizationId) ? String(WCMAPI.organizationId) : "1"; - var createPayload = { - companyId: companyId, - description: fileName, - parentId: self.gedParentFolderId, - immutable: true, - isPrivate: false, - downloadEnabled: true, - attachments: [{ fileName: fileName }] - }; + return "https://api.grupoginseng.com.br/v2/api/public/portalfornecedor/enviar"; + }, - return fetch("/api/public/ecm/document/createDocument", { - method: "POST", - headers: { - "Content-Type": "application/json;charset=utf-8" - }, - body: JSON.stringify(createPayload) - }); - }) - .then(function (resp) { - if (!resp.ok) throw new Error("Falha ao criar documento no GED"); - return resp.json(); - }) - .then(function (data) { - var content = data && data.content ? data.content : {}; - var documentId = content.id || content.documentId; - if (!documentId) throw new Error("GED nao retornou documentId"); - dfd.resolve({ documentId: documentId, raw: data }); - }) - .catch(function (err) { - dfd.reject(err); - }); + buildMultipartData: function (payloadProcesso, file) { + var formData = new FormData(); + var formFields = payloadProcesso && payloadProcesso.formFields ? payloadProcesso.formFields : {}; + var keys = Object.keys(formFields); - return dfd.promise(); + formData.append("targetState", String(payloadProcesso.targetState || 5)); + formData.append("comment", payloadProcesso.comment || ""); + + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + formData.append(key, formFields[key] == null ? "" : String(formFields[key])); + } + + if (file) { + formData.append("arquivo", file, file.name || "anexo"); + } + + return formData; + }, + + extractEndpointErrorMessage: function (xhr) { + if (!xhr || !xhr.responseText) { + return "Falha ao enviar pelo endpoint do portal. Veja o console para detalhes."; + } + + try { + var payload = JSON.parse(xhr.responseText); + if (payload && payload.message) { + return String(payload.message); + } + if (payload && payload.detail && typeof payload.detail === "string") { + return String(payload.detail); + } + if (payload && payload.detail && payload.detail.message) { + return String(payload.detail.message); + } + if (payload && payload.detailedMessage) { + return String(payload.detailedMessage); + } + } catch (e) { + // ignore + } + + return xhr.responseText; }, setLoading: function (isLoading, message) { @@ -379,6 +326,8 @@ var MyWidget = SuperWidget.extend({ this.clearFieldError("#cpf"); } + if (!this.anexoInfo || !this.anexoInfo.file) ok = this.markRequired("#arquivoLocal") && ok; + return ok; }, @@ -457,4 +406,3 @@ var MyWidget = SuperWidget.extend({ return (b / 1048576).toFixed(2) + " MB"; } }); - diff --git a/Lançamento de documentos/lançamentos/wcm/widget/portalfornecedor/target/portalfornecedor.war b/Lançamento de documentos/lançamentos/wcm/widget/portalfornecedor/target/portalfornecedor.war index 09757e9fad84af3f8a757c7791a64d671e87a28f..8d016e370c225a51d70a601885a1dab1bd095b72 100644 GIT binary patch delta 8211 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