diff --git a/moldes/consolidado/55-cronjobs-financeiro.yaml b/moldes/consolidado/55-cronjobs-financeiro.yaml new file mode 100644 index 0000000..66c2ca3 --- /dev/null +++ b/moldes/consolidado/55-cronjobs-financeiro.yaml @@ -0,0 +1,220 @@ +# ============================================================================= +# Os tres processos do Financeiro que ninguem dispara sozinho: sem eles nasce +# mensalidade nenhuma (dia 1, 06h), nota fiscal nenhuma processa (de hora em +# hora) e a conciliacao nunca roda (6 em 6 horas). O financeiro nao tem +# @Scheduled de proposito: com mais de uma replica, duas varreduras correriam. +# +# NO MOLDE desde 2026-09-29. Antes so os quatro tenants antigos tinham isto, e +# um tenant VENDIDO nascia sem cobrar os alunos. O client `operacao` e o Secret +# `operacao-credentials` nascem no provisionamento (EtapaSelarSegredos + +# ClientDaOperacao, captacao 4.20). +# +# Tenant sem o financeiro no plano (bronze): o runtime responde 403 "plano nao +# contratado" e o script termina OK ("nada a fazer") — nao e falha, e nao +# alerta. Comprado o modulo avulso, as mesmas tarefas passam a valer sozinhas. +# ============================================================================= +apiVersion: v1 +kind: ConfigMap +metadata: + name: financeiro-cron + namespace: {{TENANT}}-prod +data: + disparar.sh: | + #!/bin/sh + set -eu + + : "${KEYCLOAK_URL:?falta KEYCLOAK_URL}" + : "${ALVO:?falta ALVO}" + : "${CLIENT_SECRET:?falta CLIENT_SECRET}" + : "${ISSUER_HOST:?falta ISSUER_HOST}" + + echo "[$(date -u +%FT%TZ)] alvo: ${ALVO}" + + i=0 + until curl -sS -o /dev/null -m 5 "${KEYCLOAK_URL}" 2>/dev/null; do + i=$((i+1)) + if [ "$i" -ge 24 ]; then + echo "ERRO: ${KEYCLOAK_URL} inalcancavel depois de 2 min." >&2 + echo " Se for pod novo, e a politica de rede ainda nao programada;" >&2 + echo " se persistir, confira o Service keycloak e a deny-cross-tenant." >&2 + exit 1 + fi + sleep 5 + done + + RESP="$(printf 'grant_type=client_credentials&client_id=operacao&client_secret=%s' \ + "${CLIENT_SECRET}" \ + | curl -sS --fail-with-body -X POST "${KEYCLOAK_URL}" \ + -H 'Content-Type: application/x-www-form-urlencoded' \ + -H 'X-Forwarded-Proto: https' \ + -H 'X-Forwarded-Port: 443' \ + -H "X-Forwarded-Host: ${ISSUER_HOST}" -d @-)" + + TOKEN="$(printf '%s' "${RESP}" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')" + if [ -z "${TOKEN}" ]; then + echo "ERRO: nao consegui token pelo client 'operacao'." >&2 + echo " Confira o Secret operacao-credentials e se o client existe no realm." >&2 + exit 1 + fi + + # Sem --fail-with-body: o codigo e lido aqui, para separar "plano nao + # contratado" (tenant bronze sem o financeiro — nada a fazer, NAO e falha) + # de erro de verdade. Antes, todo bronze gerava um KubeJobFailed por hora + # (demo, 2026-09-28) e cada alerta gastava a cota de e-mail. + CODIGO="$(printf 'header = "Authorization: Bearer %s"\n' "${TOKEN}" \ + | curl -sS -K - -X POST "${ALVO}" -o /tmp/saida.json -w '%{http_code}')" + echo "${CODIGO}" + echo "resposta:" + cat /tmp/saida.json + echo + if [ "${CODIGO}" = "403" ] && grep -q "plano nao contratado" /tmp/saida.json; then + echo "financeiro fora do plano deste tenant: nada a fazer." + exit 0 + fi + case "${CODIGO}" in + 2*) ;; + *) echo "ERRO: ${ALVO} respondeu ${CODIGO}." >&2; exit 1 ;; + esac +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: financeiro-mensalidades + namespace: {{TENANT}}-prod +spec: + schedule: "0 6 1 * *" + timeZone: America/Sao_Paulo + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 5 + jobTemplate: + spec: + activeDeadlineSeconds: 900 + backoffLimit: 2 + template: + spec: + restartPolicy: OnFailure + containers: + - name: disparar + image: docker.io/curlimages/curl:8.9.1 + imagePullPolicy: Never + command: ["/bin/sh", "-c"] + args: + - | + export ALVO="http://runtime-operacao/api/financeiro/matriculas/gerar-mensalidades?competencia=$(date +%Y-%m)" + exec /bin/sh /script/disparar.sh + env: + - name: KEYCLOAK_URL + value: http://keycloak.plataforma-prod.svc:8080/realms/{{REALM}}/protocol/openid-connect/token + - name: ISSUER_HOST + value: auth.athleticmap.com + - name: CLIENT_SECRET + valueFrom: + secretKeyRef: + name: operacao-credentials + key: client-secret + resources: + requests: { cpu: 10m, memory: 32Mi } + limits: { cpu: 200m, memory: 64Mi } + volumeMounts: + - name: script + mountPath: /script + volumes: + - name: script + configMap: + name: financeiro-cron + defaultMode: 0555 +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: financeiro-notas + namespace: {{TENANT}}-prod +spec: + schedule: "15 * * * *" + timeZone: America/Sao_Paulo + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 5 + jobTemplate: + spec: + activeDeadlineSeconds: 600 + backoffLimit: 2 + template: + spec: + restartPolicy: OnFailure + containers: + - name: disparar + image: docker.io/curlimages/curl:8.9.1 + imagePullPolicy: Never + command: ["/bin/sh", "/script/disparar.sh"] + env: + - name: KEYCLOAK_URL + value: http://keycloak.plataforma-prod.svc:8080/realms/{{REALM}}/protocol/openid-connect/token + - name: ISSUER_HOST + value: auth.athleticmap.com + - name: ALVO + value: http://runtime-operacao/api/financeiro/notas/processar?limite=50 + - name: CLIENT_SECRET + valueFrom: + secretKeyRef: + name: operacao-credentials + key: client-secret + resources: + requests: { cpu: 10m, memory: 32Mi } + limits: { cpu: 200m, memory: 64Mi } + volumeMounts: + - name: script + mountPath: /script + volumes: + - name: script + configMap: + name: financeiro-cron + defaultMode: 0555 +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: financeiro-conciliacao + namespace: {{TENANT}}-prod +spec: + schedule: "30 */6 * * *" + timeZone: America/Sao_Paulo + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 5 + jobTemplate: + spec: + activeDeadlineSeconds: 900 + backoffLimit: 2 + template: + spec: + restartPolicy: OnFailure + containers: + - name: disparar + image: docker.io/curlimages/curl:8.9.1 + imagePullPolicy: Never + command: ["/bin/sh", "/script/disparar.sh"] + env: + - name: KEYCLOAK_URL + value: http://keycloak.plataforma-prod.svc:8080/realms/{{REALM}}/protocol/openid-connect/token + - name: ISSUER_HOST + value: auth.athleticmap.com + - name: ALVO + value: http://runtime-operacao/api/financeiro/conciliacao?limiteEventos=100&limiteCobrancas=100 + - name: CLIENT_SECRET + valueFrom: + secretKeyRef: + name: operacao-credentials + key: client-secret + resources: + requests: { cpu: 10m, memory: 32Mi } + limits: { cpu: 200m, memory: 64Mi } + volumeMounts: + - name: script + mountPath: /script + volumes: + - name: script + configMap: + name: financeiro-cron + defaultMode: 0555