Regra de aprovação de dias

A Regra de aprovação de dias é utilizada para realizar customizações na rotina de aprovação de dias, viabilizando a aprovação multinível por meio do BPM Senior.

Momentos de execução

A regra é executada nos seguintes momentos:

Fluxo de exemplo da regra

Imagem

Importante

Para habilitar o ponto de regra, é necessário que o campo Habilita Regra na Verificação do Dia (R000ASS.VerDia) esteja habilitado como “S - Sim”.

Exemplo de regra customizada
package custom.senior.regra.apuracao;

import java.util.List;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

import com.senior.rule.Rule;
import com.senior.rh.Colaborador;
import com.senior.colaborador.BOColaboradorPonto;
import com.senior.util.config.Configuration;
import com.senior.entity.ISituacaoApurada;
import com.senior.entity.IApuracao;

import br.com.senior.gp.comum.HierarquiaGestoresDTO;
import br.com.senior.gp.comum.StatusVerificacao;
import br.com.senior.da.bo.BOAtualizacaoFluxoAprovacao;
import br.com.senior.gestaoponto.dao.statusverificacao.StatusVerificacaoDia;
import br.com.senior.gestaoponto.dao.statusverificacao.IDAOStatusVerificacaoDia;
import br.com.senior.gestaoponto.factorybean.FactoryBean;
import br.com.senior.rh.situacoes.Situacao;
import br.com.senior.situacao.ControleSituacoes;

import custom.senior.aprovacaodia.AprovacaoDia;
import custom.senior.aprovacaodia.ContextoAprovacaoDia;
import custom.senior.aprovacaodia.IApuracaoDia;

/**
* Regra de aprovacao do dia executada apos a apuracao do ponto.
*
* <p>Para cada dia apurado do colaborador:
* <ul>
*   <li>Dias sem situacao de excecao sao aprovados automaticamente (VERIFICADO).</li>
*   <li>Dias com situacao de excecao iniciam um fluxo BPM (startProcess) direcionado
*       ao gestor de nivel 2 da hierarquia, ficando com status PENDENTE.</li>
* </ul>
*/
@Rule(description = "Regra de Aprovacao do Dia")
public class RegraAprovacaoDia extends AprovacaoDia {

    private static final int PROCESS_ID = 1;
    private static final int PROCESS_VERSION = 61;
    private static final String COMENTARIO =
            "Aprovado automaticamente pela regra: dia com apenas situacoes normais";

    /**
    * Ponto de entrada da regra. Obtem as apuracoes do colaborador, gera um unico token
    * de autenticacao BPM (reutilizado para todos os dias, evitando rate limit) e processa
    * cada dia apurado.
    */
    @Override
    public void execute() {
        try {
            ContextoAprovacaoDia contexto = getContainer().getContextoAprovacaoDia();
            List<IApuracaoDia> apuracoes = contexto.getApuracoes();

            if (apuracoes == null || apuracoes.isEmpty())
                return;

            IApuracaoDia primeira = apuracoes.get(0);
            Colaborador colaborador = new Colaborador(
                    primeira.getNumEmp(), primeira.getTipCol(), primeira.getNumCad());

            String token = obterToken();

            for (IApuracaoDia apu : apuracoes)
                processar(apu, colaborador, token);

        } catch (Exception e) {
        }
    }

    /**
    * Processa um dia apurado: aprova automaticamente quando nao ha excecao ou inicia o
    * fluxo BPM de aprovacao quando ha situacao de excecao.
    *
    * @param apu dia apurado a ser processado
    * @param colaborador colaborador dono da apuracao
    * @param token token de autenticacao BPM reutilizado para todos os dias
    */
    private void processar(IApuracaoDia apu, Colaborador colaborador, String token) {
        try {
            ContextoAprovacaoDia contexto = getContainer().getContextoAprovacaoDia();
            List<?> situacoes = contexto.getSituacoes(apu);

            if (!possuiExcecao(situacoes)) {
                aprovarAutomaticamente(apu);
                return;
            }

            String gestor = buscarGestorNivel2(contexto, apu);
            if (gestor == null || gestor.isEmpty()) {
                return;
            }

            String nome = colaborador.getNomFun();
            if (nome == null || nome.isEmpty())
                nome = BOColaboradorPonto.getColaboradorPonto(
                        new Colaborador(apu.getNumEmp(), apu.getTipCol(), apu.getNumCad())).getNomFun();

            nome = limpar(nome);
            gestor = limpar(gestor);

            String situacoesTexto = montarSituacoes(situacoes);

            String businessData =
                    "{\"root\": {" +
                    "\"numemp\": \"" + apu.getNumEmp() + "\", " +
                    "\"tipcol\": \"" + apu.getTipCol() + "\", " +
                    "\"numcad\": \"" + apu.getNumCad() + "\", " +
                    "\"datapu\": \"" + apu.getDatApu() + "\", " +
                    "\"nomfun\": \"" + nome + "\", " +
                    "\"usuario\": \"" + gestor + "\", " +
                    "\"situacoes\": \"" + situacoesTexto +
                    "\"}}";

            String bodyStartProcess =
                    "{\"processId\": " + PROCESS_ID +
                    ", \"processVersion\": " + PROCESS_VERSION +
                    ", \"generateRecord\": true" +
                    ", \"requester\": \"admin\"" +
                    ", \"title\": \"Aprovacao GPO " + nome + "\"" +
                    ", \"businessData\": \"" + businessData + "\"" +
                    ", \"flowExecutionData\": {\"actionToExecute\": \"Prosseguir\"" +
                    ", \"nextSubject\": \"" + gestor + "\"}" + "}";

            String resposta = post(
                    Configuration.get("br.com.senior.gp.bpm.start.process"),
                    bodyStartProcess, token);

            String instanceId = extrairValorJson(resposta, "processInstanceID");
            inserirRegistro(apu, instanceId.isEmpty() ? 0 : Integer.parseInt(instanceId));

        } catch (Exception e) {
        }
    }

    /**
    * Verifica se o dia possui ao menos uma situacao de excecao (flag SitExc).
    *
    * @param situacoes situacoes apuradas no dia
    * @return true se houver ao menos uma situacao de excecao
    */
    private boolean possuiExcecao(List<?> situacoes) {
        for (Object item : situacoes) {
            Situacao sit = ControleSituacoes.getSituacao(((ISituacaoApurada) item).getCodSit());
            if (sit != null && sit.getSitExc())
                return true;
        }
        return false;
    }

    /**
    * Busca o login (nomusu) do primeiro gestor do nivel 2 na hierarquia do colaborador.
    *
    * @param contexto contexto de aprovacao do dia
    * @param apu dia apurado
    * @return login do gestor nivel 2 ou string vazia se nao encontrado
    */
    private String buscarGestorNivel2(ContextoAprovacaoDia contexto, IApuracaoDia apu) {
        HierarquiaGestoresDTO hierarquia = contexto.getGestoresAcima(
                apu.getNumEmp(), apu.getTipCol(), apu.getNumCad());

        if (hierarquia == null)
            return "";

        for (HierarquiaGestoresDTO.NivelHierarquiaDTO nivel : hierarquia.getHierarquiaGestores()) {
            if (nivel.getNivel() == 2 && !nivel.getGestores().isEmpty()) {
                String nomusu = nivel.getGestores().get(0).getNomusu();
                return nomusu;
            }
        }

        return "";
    }

    /**
    * Monta o texto descritivo das situacoes apuradas no formato "HH:MM - codigo - descricao",
    * separadas por quebra de linha.
    *
    * @param situacoes situacoes apuradas no dia
    * @return texto formatado das situacoes
    */
    private String montarSituacoes(List<?> situacoes) {
        StringBuilder texto = new StringBuilder();

        for (Object item : situacoes) {
            ISituacaoApurada apurada = (ISituacaoApurada) item;
            Situacao sit = ControleSituacoes.getSituacao(apurada.getCodSit());

            if (texto.length() > 0)
                texto.append("\\n");

            texto.append(String.format("%02d:%02d", apurada.getQtdHor() / 60, apurada.getQtdHor() % 60))
                    .append(" - ").append(apurada.getCodSit())
                    .append(" - ").append(sit == null ? "" : limpar(sit.getDesSit()));
        }

        return texto.toString();
    }

    /**
    * Autentica na plataforma via loginWithKey e retorna o access_token.
    *
    * @return access_token para uso nas chamadas BPM
    * @throws Exception em caso de falha na autenticacao
    */
    private String obterToken() throws Exception {
        String body = "{\"accessKey\": \"" + Configuration.get("br.com.senior.gp.bpm.access.key")
                + "\", \"secret\": \"" + Configuration.get("br.com.senior.gp.bpm.secret")
                + "\", \"tenantName\": \"" + Configuration.get("com.senior.portal.g7.tenant") + "\"}";

        String resposta = post(
                Configuration.get("br.com.senior.gp.auth.api.url"),
                body, null);

        return extrairValorJson(resposta, "access_token");
    }

    /**
    * Aprova o dia automaticamente (sem passar pelo BPM): insere registro na R066STV com
    * status VERIFICADO, atualiza o fluxo de aprovacao via BO e ajusta o StaVer na EntitySession.
    *
    * @param apu dia apurado a ser aprovado
    */
    private void aprovarAutomaticamente(IApuracaoDia apu) {
        IDAOStatusVerificacaoDia dao =
                FactoryBean.getInstance(IDAOStatusVerificacaoDia.class);

        StatusVerificacaoDia stv = new StatusVerificacaoDia();
        stv.setNumEmp(apu.getNumEmp());
        stv.setTipCol(apu.getTipCol());
        stv.setNumCad(apu.getNumCad());
        stv.setDatApu(apu.getDatApu());
        stv.setTexRet(COMENTARIO);
        stv.setProcId(0);
        stv.setVerPro(0);
        stv.setInstId(0);
        stv.setBusDat("");
        stv.setTipSta(StatusVerificacao.VERIFICADO.getCodigo());

        dao.inserirRegistro(stv);

        new BOAtualizacaoFluxoAprovacao().atualizarStatusVerificacao(
                apu.getNumEmp(), apu.getTipCol(), apu.getNumCad(),
                apu.getDatApu(), true, COMENTARIO);

        ((IApuracao) apu).setStaVer(StatusVerificacao.VERIFICADO.getCodigo());
    }

    /**
    * Insere registro na R066STV com status PENDENTE apos o startProcess bem-sucedido.
    *
    * @param apu dia apurado
    * @param instanceId id da instancia do processo BPM criado
    */
    private void inserirRegistro(IApuracaoDia apu, int instanceId) {
        IDAOStatusVerificacaoDia dao =
                FactoryBean.getInstance(IDAOStatusVerificacaoDia.class);

        StatusVerificacaoDia stv = new StatusVerificacaoDia();
        stv.setNumEmp(apu.getNumEmp());
        stv.setTipCol(apu.getTipCol());
        stv.setNumCad(apu.getNumCad());
        stv.setDatApu(apu.getDatApu());
        stv.setTexRet("");
        stv.setProcId(PROCESS_ID);
        stv.setVerPro(PROCESS_VERSION);
        stv.setInstId(instanceId);
        stv.setBusDat("");
        stv.setTipSta(StatusVerificacao.PENDENTE.getCodigo());

        dao.inserirRegistro(stv);
    }

    /**
    * Executa uma requisicao HTTP POST com corpo JSON. O token, quando informado, e enviado
    * no header Authorization sem o prefixo "Bearer" (exigencia da API Senior).
    *
    * @param url endpoint de destino
    * @param json corpo da requisicao
    * @param token token de autenticacao (opcional)
    * @return corpo da resposta
    * @throws Exception em caso de erro de comunicacao ou HTTP fora do range 2xx
    */
    private String post(String url, String json, String token) throws Exception {
        HttpURLConnection c = (HttpURLConnection) new URL(url).openConnection();
        c.setRequestMethod("POST");
        c.setRequestProperty("Content-Type", "application/json");

        if (token != null)
            c.setRequestProperty("Authorization", token);

        c.setDoOutput(true);

        try (OutputStream o = c.getOutputStream()) {
            o.write(json.getBytes(StandardCharsets.UTF_8));
        }

        int code = c.getResponseCode();
        StringBuilder r = new StringBuilder();

        try (BufferedReader b = new BufferedReader(new InputStreamReader(
                code >= 200 && code < 300 ? c.getInputStream() : c.getErrorStream(),
                StandardCharsets.UTF_8))) {

            String l;

            while ((l = b.readLine()) != null)
                r.append(l);
        }

        c.disconnect();

        if (code < 200 || code >= 300) {
            throw new RuntimeException("HTTP " + code + ": " + r.toString());
        }

        return r.toString();
    }

    /**
    * Extrai o valor de um campo JSON (string ou numerico), com fallback para JSON escapado.
    *
    * @param json conteudo JSON
    * @param key nome do campo
    * @return valor do campo ou string vazia se nao encontrado
    */
    private String extrairValorJson(String json, String key) {
        int idx = json.indexOf("\"" + key + "\"");

        if (idx >= 0) {
            int colon = json.indexOf(":", idx);

            if (colon >= 0) {
                int start = colon + 1;

                while (start < json.length() && json.charAt(start) == ' ')
                    start++;

                if (start < json.length()) {
                    if (json.charAt(start) == '"') {
                        int end = json.indexOf('"', start + 1);

                        if (end >= 0)
                            return json.substring(start + 1, end);

                    } else if (Character.isDigit(json.charAt(start)) || json.charAt(start) == '-') {
                        int end = start;

                        while (end < json.length() && "0123456789-.".indexOf(json.charAt(end)) >= 0)
                            end++;

                        return json.substring(start, end);
                    }
                }
            }
        }

        int esc = json.indexOf("\\\"" + key + "\\\"");

        if (esc >= 0) {
            int colon = json.indexOf(":", esc);
            int start = json.indexOf("\\\"", colon + 1);

            if (start >= 0) {
                int end = json.indexOf("\\\"", start + 2);

                if (end >= 0)
                    return json.substring(start + 2, end);
            }
        }

        return "";
    }

    /**
    * Remove aspas duplas e espacos das extremidades de um valor.
    *
    * @param valor valor a ser limpo
    * @return valor sem aspas e sem espacos nas extremidades, ou string vazia se nulo
    */
    private String limpar(String valor) {
        return valor == null ? "" : valor.replace("\"", "").trim();
    }
}

Configuração do SeniorConfigCenter

Para utilizar a regra de aprovação de dias com o BPM Senior, garanta que as seguintes chaves estejam configuradas no SeniorConfigCenter:

Chave

Valor

Descrição

br.com.senior.gp.bpm.start.process

https://platform.senior.com.br/t/senior.com.br/bridge/1.0/rest/platform/workflow/actions/startProcess

URL da API

startProcess

do BPM.

br.com.senior.gp.auth.api.url

http://platform.senior.com.br/t/senior.com.br/bridge/1.0/rest/platform/authentication/actions/loginWithKey

URL da API de autenticação (

loginWithKey

).

br.com.senior.gp.bpm.access.key

Gerar conforme os passos descritos em

Configuração do Tenant e das credenciais

.

Access Key utilizada para autenticação.

br.com.senior.gp.bpm.secret

Gerar conforme os passos descritos em

Configuração do Tenant e das credenciais

.

Secret utilizada para autenticação.

com.senior.portal.g7.tenant

Configurar conforme a documentação

Configurar menu na Senior X

.

Tenant Name da plataforma.

Configuração do Tenant e das credenciais

Na plataforma Senior X, acesse Tecnologia > Administração > Gerenciamento de aplicações.

  1. Clique em Nova aplicação e informe um nome e uma descrição para a aplicação. O nome deve identificar a aplicação que está sendo configurada. Para o fluxo de aprovação, por exemplo, pode ser utilizado o nome fluxoaprovacao.

  2. Clique em Salvar para retornar à lista de aplicações.

  3. Copie as chaves de acesso geradas nos dois campos e informe os valores nas propriedades correspondentes:

Propriedade

Campo correspondente

br.com.senior.gp.bpm.access.key

Chave de acesso (ApplicationManagement)

br.com.senior.gp.bpm.secret

Chave secreta (ApplicationManagement)
  1. Acesse Tecnologia > Administração > Autorização > Gestão dos papéis.
  2. Clique em Editar no papel Admin (administrador do sistema).
  3. Na guia Configurações, acesse Aplicações e habilite a aplicação criada anteriormente.

Fluxo de exemplo do BPM

Imagem

Importante

Para que o ponto de regra funcione e crie a pendência de aprovação, é necessário que já exista um fluxo customizado criado no BPM.

API de Atualização do Fluxo de Aprovação

Para permitir que o BPM atualize o status dos dias durante o fluxo de aprovação, está disponível a API api/verificacao/atualiza-fluxo-aprovacao.

Parâmetros de entrada

A API recebe os seguintes parâmetros de entrada:

Parâmetro

Tipo

Descrição

processInstanceId

Inteiro

Identificador da instância do processo BPM.

comentario

Texto

Comentário informado pelo gestor durante a aprovação ou reprovação.

aprovado

Booleano

Indica se o dia foi aprovado ou reprovado pelo BPM.

numEmp

Inteiro

Código da empresa.

tipCol

Inteiro

Tipo de colaborador.

numCad

Inteiro

Cadastro do colaborador.

datApu

Data

Data de apuração do dia.

Atualização do status do dia

Com base nos parâmetros recebidos, a API realiza a atualização do status do dia conforme a decisão definida pelo BPM.

Para isso, são atualizados os seguintes campos:

Campo

Descrição

R066APU.STAVER

Atualiza o status de verificação do dia.

R066STV.TIPSTA

Atualiza o status da situação.

R066STV.TEXRET

Armazena o comentário informado pelo gestor.

Dessa forma, o dia fica registrado no Gestão do Ponto com o status definido pelo fluxo do BPM, mantendo também o comentário realizado pelo gestor durante o processo de aprovação ou reprovação.

API de Gestores da Hierarquia de Postos ou Chefia

Para facilitar a customização do fluxo de aprovação, está disponível uma API responsável por retornar o gestor de determinado colaborador, considerando a hierarquia de postos ou chefia.

Parâmetros de entrada

A API recebe os seguintes parâmetros de entrada:

Parâmetro

Tipo

Descrição

numEmp

Inteiro

Código da empresa.

tipCol

Inteiro

Tipo de colaborador.

numCad

Inteiro

Cadastro do colaborador.

nivel

Inteiro

Nível hierárquico do gestor a ser consultado.

Retorno

Com base nos parâmetros recebidos, a API identifica o gestor correspondente na hierarquia e retorna o campo nomusu, que representa o nome do usuário vinculado à ficha cadastral do gestor.

Dessa forma, a API permite que a regra de customização identifique o usuário responsável pela aprovação de determinado colaborador, de acordo com o nível hierárquico informado.

Observações
  • Assinalamento Utiliza Posto de Trabalho (R000ASS.USAPOS):
    • A API respeita o assinalamento Utiliza Posto de Trabalho para determinar a origem da hierarquia utilizada na identificação do gestor.
    • Quando ele estiver definido como “S- Sim”, o gestor será identificado por meio da hierarquia de postos de trabalho.
    • Já quando estiver definido como “N - Não”, o gestor será identificado por meio do organograma de locais.
  • Caso o gestor identificado não possua um usuário vinculado à ficha cadastral, a API retornará um erro.
  • Caso exista mais de um gestor no mesmo posto, será necessário realizar um tratamento na regra de customização para identificar o gestor correto.
  • Nessa situação, a regra deverá informar o nível hierárquico (nivel) específico e enviá-lo ao StartProcess, conforme a necessidade do fluxo de aprovação.

Este artigo ajudou você?