EMS Problema: API Rest Reporte Ponto de Controle SFC

Discussão em 'EMS , HCM e Totvs 11' iniciado por Marcelo Goncalves, Fevereiro 28, 2025.

  1. Marcelo Goncalves

    Marcelo Goncalves Membro Participativo

    Olá, boa tarde.

    Estou desenvolvendo uma API REST para reporte de ordens. Nela, tenho uma procedure para reportar ponto de controle e estou utilizando a API 'sfc/sfapi009.p'.

    Contudo, estou enfrentando a seguinte dificuldade:

    1. Ao enviar uma requisição para a API REST, ela chama a api sfapi009, os pontos de controle são reportados e a ordem finalizada;
    1.1 A API retorna no response body os números de reporte gerados na tabela EMS2EMP.REP_PROD:
    {
    "numerosDeReporteGerados": [
    1219092
    ]
    }

    2. Como esperado, ao enviar uma segunda requisição para a API REST um erro é retornado pela sfapi009 - Code: 27584 | Message: "Estado da Ordem não permite reporte.";

    3. Alterando o status da ordem de "Finalizada" para "Iniciada", via CP0301, a API sfapi009 continua retornando o mesmo erro, dizendo que o estado da ordem não permite reporte. Porém, o erro esperado é o - Code: 17786 | Message: "Nenhuma Operação Split pode estar finalizada.", pois a ordem agora está com o status "Iniciada" e nenhum split para ser finalizado.

    Observei que:

    1. Ao comentar a linha que atualiza a variavel jsonOutput com o retorno REST customizado, o problema de aparente "caching" no registro da tabela EMS2EMP.ORD_PROD é resolvido. Neste caso, por padrão um body vazio é retornado com status 200;

    2. Mesmo sem descomentar a linha de retorno REST customizado, depois de um certo tempo da primeira requisição enviada, ao tentar enviar novamente a requisição, o erro esperado é retornado (Code: 17786 | Message: "Nenhuma Operação Split pode estar finalizada.").


    Realizo a chamada para api sf009 de maneira direta, como diz a documentação: https://tdn.totvs.com/pages/releaseview.action?pageId=506137656, e crio a resposta REST customizada utilizando a classe estática com.totvs.framework.api.JsonAPIResponseBuilder.

    Código da PROCEDURE:

    ...
    PROCEDURE pi-erro:

    DEFINE INPUT PARAMETER p-cod-erro AS CHARACTER NO-UNDO.
    DEFINE INPUT PARAMETER p-des-erro AS CHARACTER NO-UNDO.
    DEFINE INPUT PARAMETER p-ajd-erro AS CHARACTER NO-UNDO.
    DEFINE OUTPUT PARAMETER jsonOutput AS JsonObject NO-UNDO.
    DEFINE INPUT PARAMETER statusCode AS INTEGER NO-UNDO INIT 500.

    DEFINE VARIABLE oJsonObjectErr AS JsonObject NO-UNDO.
    DEFINE VARIABLE oResponse AS JsonAPIResponse NO-UNDO.

    ASSIGN statusCode = (IF statusCode <> ? THEN statusCode ELSE 500) NO-ERROR.

    IF p-des-erro = "" THEN DO:

    FIND cadast_msg NO-LOCK
    WHERE cadast_msg.cdn_msg = INT(p-cod-erro) NO-ERROR.
    IF AVAIL cadast_msg THEN
    ASSIGN p-des-erro = cadast_msg.des_text_msg
    p-ajd-erro = cadast_msg.dsl_help_msg.
    ELSE
    ASSIGN p-des-erro = "ERRO - Mensagem não carregada !"
    p-ajd-erro = p-des-erro.

    END.

    ASSIGN haveError = TRUE NO-ERROR.

    oJsonObjectErr = NEW JSONObject().
    oJsonObjectErr:ADD("code" , p-cod-erro).
    oJsonObjectErr:ADD("message" , p-des-erro).
    oJsonObjectErr:ADD("detailedMessage", p-ajd-erro).

    oResponse = NEW JsonAPIResponse(oJsonObjectErr).
    oResponse:setHasNext(FALSE).
    oResponse:setStatus(statusCode).
    jsonOutput = oResponse:createJsonResponse().

    END PROCEDURE.

    ...
    PROCEDURE concatena-narrativa-ordem:

    DEF INPUT PARAM nrOrdem AS INTEGER NO-UNDO.
    DEF INPUT PARAM narrativa AS CHARACTER NO-UNDO.

    BLOCO:
    DO TRANSACTION ON ERROR UNDO BLOCO, LEAVE BLOCO:

    FIND FIRST ord-prod EXCLUSIVE-LOCK WHERE ord-prod.nr-ord-produ = nrOrdem NO-ERROR.

    IF AVAILABLE ord-prod THEN DO:

    ASSIGN ord-prod.narrativa = ord-prod.narrativa + narrativa.

    RELEASE ord-prod NO-ERROR.

    END.

    END.

    END PROCEDURE.

    ...
    PROCEDURE pi-reporta-ponto-controle PRIVATE:
    DEF INPUT PARAM jsonInput AS JsonObject NO-UNDO.
    DEF INPUT PARAM l-simulacao AS LOGICAL NO-UNDO.
    DEF OUTPUT PARAM jsonOutput AS JsonObject NO-UNDO.

    EMPTY TEMP-TABLE tt-erro.
    EMPTY TEMP-TABLE tt-import-rep-oper.

    ASSIGN oJsonArrayForResponse = NEW JsonArray().

    RUN parseInputParameters(INPUT jsonInput, OUTPUT headers, OUTPUT pathParams, OUTPUT queryParams, OUTPUT startRow, OUTPUT pageSize, OUTPUT fieldList, OUTPUT expandables, OUTPUT payload).

    ASSIGN oJsonObjectForRequest = NEW JSONObject().
    oJsonObjectForRequest = JsonAPIUtils:convertLongcharToJsonObject(payload).

    ASSIGN
    nrOrdem = (IF oJsonObjectForRequest:GetInteger("NumeroOrdem") <> ? THEN oJsonObjectForRequest:GetInteger("NumeroOrdem") ELSE 0)
    qtAprovada = (IF oJsonObjectForRequest:GetDecimal("QtAprovada") <> ? THEN oJsonObjectForRequest:GetDecimal("QtAprovada") ELSE 0)
    qtRefugada = (IF oJsonObjectForRequest:GetDecimal("QtRefugada") <> ? THEN oJsonObjectForRequest:GetDecimal("QtRefugada") ELSE 0)
    depositoEnt = (IF oJsonObjectForRequest:GetCharacter("DepositoEntrada") <> ? THEN oJsonObjectForRequest:GetCharacter("DepositoEntrada") ELSE "")
    localEnt = (IF oJsonObjectForRequest:GetCharacter("LocalEntrada") <> ? THEN oJsonObjectForRequest:GetCharacter("LocalEntrada") ELSE "")
    codRej = (IF oJsonObjectForRequest:GetInteger("CodRejeicao") <> ? THEN oJsonObjectForRequest:GetInteger("CodRejeicao") ELSE 0)
    motivoRefugo = (IF oJsonObjectForRequest:GetCharacter("MotivoRefugo") <> ? THEN oJsonObjectForRequest:GetCharacter("MotivoRefugo") ELSE "")
    loteSerie = (IF oJsonObjectForRequest:GetCharacter("LoteSerie") <> ? THEN oJsonObjectForRequest:GetCharacter("LoteSerie") ELSE "")
    dtInicioReporte = (IF oJsonObjectForRequest:GetDate("DtInicioReporte") <> ? THEN oJsonObjectForRequest:GetDate("DtInicioReporte") ELSE ?)
    hrInicioReporte = (IF oJsonObjectForRequest:GetInteger("HrInicioReporte") <> ? THEN oJsonObjectForRequest:GetInteger("HrInicioReporte") ELSE 0)
    dtValiLote = (IF oJsonObjectForRequest:GetDate("DtValiLote") <> ? THEN oJsonObjectForRequest:GetDate("DtValiLote") ELSE ?)
    textoComplementarNarrativa = (IF oJsonObjectForRequest:GetCharacter("TextoComplementarNarrativa") <> ? THEN oJsonObjectForRequest:GetCharacter("TextoComplementarNarrativa") ELSE "") NO-ERROR.

    FIND FIRST ord-prod NO-LOCK WHERE ord-prod.nr-ord-produ = nrOrdem NO-ERROR.

    IF NOT AVAIL ord-prod THEN DO:

    RUN pi-erro (INPUT "17006",
    INPUT "Ordem de produção não encontrada.",
    INPUT "Ordem de produção não encontrada.",
    OUTPUT jsonOutput,
    INPUT 404).

    RETURN.
    END.

    FIND FIRST ITEM NO-LOCK
    WHERE ITEM.it-codigo = ord-prod.it-codigo NO-ERROR.

    IF NOT AVAIL ITEM THEN DO:

    RUN pi-erro (INPUT "17006",
    INPUT "Item da ordem não encontrado.",
    INPUT "Item da ordem não encontrado.",
    OUTPUT jsonOutput,
    INPUT 404).

    RETURN.

    END.

    // Valida passagem de lote/série e data de validade para itens tipo lote
    IF ITEM.tipo-con-est = 3 AND loteSerie = "" THEN DO:
    RUN pi-erro (INPUT "17006",
    INPUT "Lote/Série deve ser informado para itens tipo lote.",
    INPUT "Lote/Série deve ser informado para itens tipo lote.",
    OUTPUT jsonOutput,
    INPUT 400).

    RETURN.

    END.
    IF ITEM.tipo-con-est = 3 AND dtValiLote = ? THEN DO:
    RUN pi-erro (INPUT "17006",
    INPUT "Data de validade do lote deve ser informada para itens tipo lote.",
    INPUT "Data de validade do lote deve ser informada para itens tipo lote.",
    OUTPUT jsonOutput,
    INPUT 400).

    RETURN.
    END.

    IF depositoEnt = "" THEN DO:
    RUN pi-erro (INPUT "17006",
    INPUT "Depósito de entrada não informado.",
    INPUT "Depósito de entrada não informado.",
    OUTPUT jsonOutput,
    INPUT 400).
    RETURN.
    END.

    IF textoComplementarNarrativa = "" THEN DO:
    RUN pi-erro (INPUT "17006",
    INPUT "Texto complementar da narrativa não informado para o reporte.",
    INPUT "Texto complementar da narrativa não informado para o reporte.",
    OUTPUT jsonOutput,
    INPUT 400).
    RETURN.
    END.

    IF ord-prod.rep-prod <> 3 THEN DO:
    RUN pi-erro (INPUT "17006",
    INPUT "O tipo de reporte da ordem de produção não é ponto de controle.",
    INPUT "O tipo de reporte da ordem de produção não é ponto de controle.",
    OUTPUT jsonOutput,
    INPUT 400).
    RETURN.
    END.

    IF dtInicioReporte = ? THEN DO:
    RUN pi-erro (INPUT "17006",
    INPUT "Data de início do reporte não informada.",
    INPUT "Data de início do reporte não informada.",
    OUTPUT jsonOutput,
    INPUT 400).
    RETURN.
    END.

    // Recupera os pontos de controles de uma dada ordem
    FOR EACH split-operac WHERE split-operac.nr-ord-prod = nrOrdem NO-LOCK,
    EACH rot-fabric WHERE rot-fabric.cod-roteiro = split-operac.cod-roteiro NO-LOCK,
    EACH operacao WHERE operacao.cod-roteiro = rot-fabric.cod-roteiro AND operacao.pto-controle > 0 NO-LOCK
    BY operacao.pto-controle:

    IF operacao.pto-controle = 10 AND split-operac.ind-estado-split <> 5 THEN DO:
    RUN pi-erro (INPUT "17006",
    INPUT "Split da controladoria não finalizado!",
    INPUT "Split da controladoria não finalizado!",
    OUTPUT jsonOutput,
    INPUT 400).
    LEAVE.
    END.

    IF qtRefugada <> 0 AND motivoRefugo = "" THEN DO:
    RUN pi-erro (INPUT "17006",
    INPUT "Quantidade de refugo informada com motivo de refugo em branco!",
    INPUT "Quantidade de refugo informada com motivo de refugo em branco!",
    OUTPUT jsonOutput,
    INPUT 400).
    LEAVE.
    END.

    IF qtRefugada = 0 AND motivoRefugo <> "" THEN DO:
    RUN pi-erro (INPUT "17006",
    INPUT "Motivo do refugo informado com quantidade de refugo igual a 0!",
    INPUT "Motivo do refugo informado com quantidade de refugo igual a 0!",
    OUTPUT jsonOutput,
    INPUT 400).
    LEAVE.
    END.

    IF operacao.pto-controle <> 10 THEN DO:

    EMPTY TEMP-TABLE tt-import-rep-oper.

    CREATE tt-import-rep-oper.
    ASSIGN tt-import-rep-oper.tipo-rep = 1
    tt-import-rep-oper.nr-ord-produ = split-operac.nr-ord-produ
    tt-import-rep-oper.num-operac-sfc = split-operac.num-operac-sfc
    tt-import-rep-oper.num-split-operac = split-operac.num-split-operac
    tt-import-rep-oper.cod-roteiro = split-operac.cod-roteiro
    tt-import-rep-oper.op-codigo = split-operac.op-codigo
    tt-import-rep-oper.cod-ctrab = split-operac.cod-ctrab
    tt-import-rep-oper.it-codigo = ord-prod.it-codigo
    tt-import-rep-oper.cod-refer = ord-prod.cod-refer
    tt-import-rep-oper.lote-serie = loteSerie
    tt-import-rep-oper.dat-valid-lote = dtValiLote
    tt-import-rep-oper.per-ppm = ITEM.per-ppm
    tt-import-rep-oper.qtd-produzida = IF (operacao.pto-controle = 80) THEN split-operac.qtd-previs-operac ELSE qtAprovada
    tt-import-rep-oper.cod-motiv-refugo = IF (operacao.pto-controle = 80) THEN "" ELSE motivoRefugo
    tt-import-rep-oper.qtd-refugada = IF (operacao.pto-controle = 80) THEN 0 ELSE qtRefugada
    tt-import-rep-oper.qtd-refugada-un-ref = 0
    tt-import-rep-oper.qtd-retrabalho = 0
    tt-import-rep-oper.dat-inic-rep = dtInicioReporte
    tt-import-rep-oper.hr-inic-rep = hrInicioReporte
    tt-import-rep-oper.dat-fim-rep = TODAY
    tt-import-rep-oper.hr-fim-rep = TIME
    tt-import-rep-oper.cod-equipe = ""
    tt-import-rep-oper.cod-operador = ""
    tt-import-rep-oper.cod-ferramenta = split-operac.cod-ferr-prod
    tt-import-rep-oper.num-contador-ini = 0
    tt-import-rep-oper.num-contador-fim = 0
    tt-import-rep-oper.finaliza = YES
    tt-import-rep-oper.linha = 1.

    DO TRANS:

    // Chamando sfapi009 para reporte do ponto de controle
    RUN sfc/sfapi009.p (INPUT {&versao},
    INPUT TABLE tt-import-rep-oper,
    OUTPUT TABLE tt-erro).

    IF l-simulacao THEN
    UNDO.

    END.

    FIND FIRST tt-erro NO-ERROR.

    IF AVAIL tt-erro THEN DO:
    FOR EACH tt-erro:

    IF tt-erro.cd-erro = 32058 THEN NEXT.

    RUN pi-erro (INPUT "sfapi009:" + STRING(tt-erro.cd-erro),
    INPUT tt-erro.mensagem,
    INPUT tt-erro.mensagem,
    OUTPUT jsonOutput,
    INPUT 400).
    RETURN.

    END.
    END.

    ELSE DO:

    FOR FIRST ord-rep WHERE ord-rep.nr-ord-produ = nrOrdem NO-LOCK
    BY ord-rep.nr-reporte DESCENDING:
    oJsonArrayForResponse:Add(ord-rep.nr-reporte).
    END.

    END.

    END.
    END.

    IF NOT haveError THEN DO:

    RUN concatena-narrativa-ordem (
    input nrOrdem,
    input textoComplementarNarrativa).

    IF oJsonArrayForResponse:LENGTH > 0 THEN DO:

    oJsonObjectForResponse = NEW JSONObject().
    oJsonObjectForResponse:ADD("numerosDeReporteGerados", oJsonArrayForResponse).
    jsonOutput = JsonAPIResponseBuilder:eek:k(oJsonObjectForResponse, 201). // Ao comentar esta linha, o problema enfrentado não ocorre.

    END.

    END.

    END PROCEDURE.

    Gostaria de entender o que pode estar ocasionando isto, se esqueci de envolver a chamada para a API em algum bloco específico ou se isso pode estar relacionado a classe JsonAPIResponseBuilder. Não encontrei nada nas documentações sobre.
    Alguém já passou por algo parecido?
  2. rafael.andrade

    rafael.andrade Membro Master Moderador Equipe de Suporte

    Qual trecho que tu comenta que faz funcionar com body vazio?
  3. Marcelo Goncalves

    Marcelo Goncalves Membro Participativo

    Ao comentar o trecho de código abaixo, a variável jsonoutput permanece vazia e a resposta rest padrão é retornada, ou seja, body vazio com código 200:

    jsonOutput = JsonAPIResponseBuilder:eek:k(oJsonObjectForResponse, 201).

    complementando, essa é a primeira procedure executada quando uma requisição é enviada para a API Rest, com base no path parameter, ela encaminha pra minha procedure de reporte de ordem:

    {utp/ut-api-action.i pi-post POST /~* }



    PROCEDURE pi-post:

    DEF INPUT PARAM jsonInput AS JsonObject NO-UNDO.

    DEF OUTPUT PARAM jsonOutput AS JsonObject NO-UNDO.



    RUN parseInputParameters(INPUT jsonInput, OUTPUT headers, OUTPUT pathParams, OUTPUT queryParams, OUTPUT startRow, OUTPUT pageSize, OUTPUT fieldList, OUTPUT expandables, OUTPUT payload).



    ASSIGN oJsonObjectForRequest = NEW JSONObject().

    oResponse = NEW JsonAPIResponse(oJsonObjectForRequest).

    oResponse:setHasNext(FALSE).

    oResponse:setStatus(404).



    IF pathParams:Length > 0 THEN

    DO:

    CASE pathParams:GetCharacter(1):

    WHEN "reporta-ponto-controle" THEN

    RUN pi-reporta-ponto-controle (INPUT jsonInput, INPUT NO, OUTPUT jsonOutput).

    WHEN "reporta-ponto-controle-simulacao" THEN

    RUN pi-reporta-ponto-controle (INPUT jsonInput, INPUT YES, OUTPUT jsonOutput).

    OTHERWISE

    jsonOutput = oResponse:createJsonResponse().

    END CASE.

    END.

    ELSE

    jsonOutput = oResponse:createJsonResponse().

    END.
  4. rafael.andrade

    rafael.andrade Membro Master Moderador Equipe de Suporte

    Essa variável "JsonAPIResponseBuilder" está declarada onde e como?
  5. Marcelo Goncalves

    Marcelo Goncalves Membro Participativo

    Boa tarde Rafael!
    A JsonAPIResponseBuilder vem de uma classe interna do próprio Datasul.
    Mas já resolvi de uma outra forma.
    Obrigado por responder!

Compartilhe esta Página