feat(plataforma): CronJobs do financeiro (mensalidades, notas, conciliacao)
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
# =============================================================================
|
||||
# Os tres processos do Financeiro que ninguem disparava.
|
||||
#
|
||||
# Conferido em 2026-09-18: `kubectl get cronjob -A` tinha UM cronjob por tenant,
|
||||
# o `pg-backup`, e mais nada. Gerar mensalidade, emitir nota e conciliar
|
||||
# pagamento sao endpoints POST que existem, tem teste e nunca foram chamados
|
||||
# por ninguem que nao fosse uma pessoa clicando.
|
||||
#
|
||||
# Consequencia pratica antes disto: **nenhuma cobranca nascia sozinha** — nem a
|
||||
# mensalidade do aluno, nem a assinatura da propria Athletic Map. A regra de
|
||||
# "vence na data de entrada" (V008, no ar desde hoje) existia sem nada que a
|
||||
# executasse.
|
||||
#
|
||||
# ── COMO ELES SE AUTENTICAM ─────────────────────────────────────────────────
|
||||
#
|
||||
# Pelo client `operacao` (client_credentials), o mesmo que o
|
||||
# `scripts/teste_cobranca_asaas.sh` usa. Ele e confidencial, nao representa
|
||||
# pessoa nenhuma, e tem APENAS `atm_financeiro` — nao `atm_admin`.
|
||||
#
|
||||
# O segredo vem do Secret `operacao-credentials` e nunca passa por `argv`: o
|
||||
# corpo do POST do token vai por STDIN (`-d @-`), e o header `Authorization`
|
||||
# entra por um arquivo de configuracao do curl lido do STDIN (`-K -`). Sem isso
|
||||
# um `ps` dentro do container mostraria o bearer.
|
||||
#
|
||||
# **Este arquivo depende do Secret `operacao-credentials` existir.** Ele e criado
|
||||
# selado por `scripts/gerar_credenciais_plataforma.sh operacao`. Sem ele os pods
|
||||
# ficam em CreateContainerConfigError — que e barulhento, e nao silencioso.
|
||||
#
|
||||
# ── POR QUE CADA CADENCIA ───────────────────────────────────────────────────
|
||||
#
|
||||
# Nao sao numeros redondos escolhidos por gosto:
|
||||
#
|
||||
# mensalidades dia 1, 06:00 A competencia e mensal. Rodar no dia 1 da o mes
|
||||
# inteiro de folga para corrigir o que falhar, e
|
||||
# 06:00 poe a falha no horario comercial de quem
|
||||
# vai olhar.
|
||||
# notas de hora em hora Nota pendente e dinheiro que ja entrou
|
||||
# esperando documento. Uma hora e o atraso maximo
|
||||
# aceitavel; mais frequente nao acelera nada,
|
||||
# porque o gatilho e o pagamento confirmado.
|
||||
# conciliacao a cada 6 horas CADA cobranca em aberto custa uma chamada ao
|
||||
# provedor, e a cota e de 25.000 por 12 horas. A
|
||||
# cada 6h com limite de 100 gasta no maximo 400
|
||||
# chamadas por dia — 1,6% da cota.
|
||||
#
|
||||
# `concurrencyPolicy: Forbid` nos tres: uma execucao lenta nao pode ganhar uma
|
||||
# segunda em paralelo disputando o mesmo provedor.
|
||||
# =============================================================================
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: financeiro-cron
|
||||
namespace: plataforma-prod
|
||||
data:
|
||||
# O script e o mesmo para os tres; muda o `ALVO`. Fica num ConfigMap, e nao
|
||||
# embutido em cada CronJob, porque tres copias divergem — e a que diverge e
|
||||
# sempre a que ninguem lembrou de corrigir.
|
||||
disparar.sh: |
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
: "${KEYCLOAK_URL:?falta KEYCLOAK_URL}"
|
||||
: "${ALVO:?falta ALVO}"
|
||||
: "${CLIENT_SECRET:?falta CLIENT_SECRET}"
|
||||
|
||||
echo "[$(date -u +%FT%TZ)] alvo: ${ALVO}"
|
||||
|
||||
# 1. Token. O segredo vai por STDIN: em `argv` um `ps` dentro deste
|
||||
# container o veria.
|
||||
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' -d @-)"
|
||||
|
||||
TOKEN="$(printf '%s' "${RESP}" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')"
|
||||
if [ -z "${TOKEN}" ]; then
|
||||
# Nao imprime RESP: em erro de client_credentials o corpo pode ecoar
|
||||
# parte do que foi enviado.
|
||||
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
|
||||
|
||||
# 2. A chamada. O bearer entra por arquivo de configuracao lido do STDIN,
|
||||
# pelo mesmo motivo do segredo acima.
|
||||
# `--fail-with-body` faz o curl sair != 0 em >=400 E ainda imprimir o
|
||||
# corpo: sem o corpo, um 403 de plano e indistinguivel de um 403 de papel.
|
||||
printf 'header = "Authorization: Bearer %s"\n' "${TOKEN}" \
|
||||
| curl -sS --fail-with-body -K - -X POST "${ALVO}" -o /tmp/saida.json -w '%{http_code}\n'
|
||||
|
||||
echo "resposta:"
|
||||
cat /tmp/saida.json
|
||||
echo
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: financeiro-mensalidades
|
||||
namespace: plataforma-prod
|
||||
spec:
|
||||
schedule: "0 6 1 * *"
|
||||
timeZone: "America/Sao_Paulo"
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 5
|
||||
concurrencyPolicy: Forbid
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 2
|
||||
activeDeadlineSeconds: 900
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: disparar
|
||||
image: docker.io/curlimages/curl:8.9.1
|
||||
imagePullPolicy: Never
|
||||
# A competencia e resolvida AQUI, e nao no servidor: o endpoint
|
||||
# exige `YYYY-MM` explicito de proposito, para reprocessar um mes
|
||||
# antigo ser um ato deliberado e nao um efeito do relogio.
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
export ALVO="http://servico-financeiro:8083/api/financeiro/matriculas/gerar-mensalidades?competencia=$(date +%Y-%m)"
|
||||
exec /bin/sh /script/disparar.sh
|
||||
env:
|
||||
- { name: KEYCLOAK_URL, value: "http://keycloak:8080/realms/athleticmap/protocol/openid-connect/token" }
|
||||
- name: CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef: { name: operacao-credentials, key: client-secret }
|
||||
volumeMounts:
|
||||
- { name: script, mountPath: /script }
|
||||
resources:
|
||||
requests: { cpu: 10m, memory: 32Mi }
|
||||
limits: { cpu: 200m, memory: 64Mi }
|
||||
volumes:
|
||||
- name: script
|
||||
configMap: { name: financeiro-cron, defaultMode: 0555 }
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: financeiro-notas
|
||||
namespace: plataforma-prod
|
||||
spec:
|
||||
schedule: "15 * * * *"
|
||||
timeZone: "America/Sao_Paulo"
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 5
|
||||
concurrencyPolicy: Forbid
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 2
|
||||
activeDeadlineSeconds: 600
|
||||
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:8080/realms/athleticmap/protocol/openid-connect/token" }
|
||||
- { name: ALVO, value: "http://servico-financeiro:8083/api/financeiro/notas/processar?limite=50" }
|
||||
- name: CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef: { name: operacao-credentials, key: client-secret }
|
||||
volumeMounts:
|
||||
- { name: script, mountPath: /script }
|
||||
resources:
|
||||
requests: { cpu: 10m, memory: 32Mi }
|
||||
limits: { cpu: 200m, memory: 64Mi }
|
||||
volumes:
|
||||
- name: script
|
||||
configMap: { name: financeiro-cron, defaultMode: 0555 }
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: financeiro-conciliacao
|
||||
namespace: plataforma-prod
|
||||
spec:
|
||||
schedule: "30 */6 * * *"
|
||||
timeZone: "America/Sao_Paulo"
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 5
|
||||
concurrencyPolicy: Forbid
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 2
|
||||
activeDeadlineSeconds: 900
|
||||
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:8080/realms/athleticmap/protocol/openid-connect/token" }
|
||||
# limiteCobrancas=100: cada uma custa uma chamada ao provedor.
|
||||
- { name: ALVO, value: "http://servico-financeiro:8083/api/financeiro/conciliacao?limiteEventos=100&limiteCobrancas=100" }
|
||||
- name: CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef: { name: operacao-credentials, key: client-secret }
|
||||
volumeMounts:
|
||||
- { name: script, mountPath: /script }
|
||||
resources:
|
||||
requests: { cpu: 10m, memory: 32Mi }
|
||||
limits: { cpu: 200m, memory: 64Mi }
|
||||
volumes:
|
||||
- name: script
|
||||
configMap: { name: financeiro-cron, defaultMode: 0555 }
|
||||
Reference in New Issue
Block a user