obsidian-vault/Infra-Network/SPEC-VM500-RENTCHARGE.md
Roger 06acaff8a8 docs: RentCharge 007.2026 money privacy + profile status
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-19 18:19:56 +00:00

261 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SPEC-VM500 — Rent and Charge (Weekly and Arrears)
**Fonte local:** `C:\LAPTOP\rentcharge\docs` — sync Obsidian CT130.
**Data:** 2026-09-09 (rev. Account Receivables / Credit Book / Period dashboard — noite 08–09)
**Status:** app completo na VM500; HTTPS público via Cloudflare Tunnel; login validado; pagamentos SAGE importados; **AR Credit Book live**.
**Host da VM:** BIG (`big-itecnologys-wardrobe`) — **não** é big1.
**Produto / escopo funcional:** ver `PROJECT-RENTCHARGE.md` (ocupação NP + **RN-PAY** + **SPEC-ACCOUNT-RECEIVABLES.md**).
## Objetivo
Ambiente na VM 500 PROJETOS para o produto **RenteCharge Weekly and Arrears** (controle de aluguel semanal / débitos). **Única pasta do projeto:** `/home/mini/rentcharge` (código, venv, SAGE imports, config, archive e bare git).
## Topologia (obrigatório hop)
Windows (Tailscale) → **big** `100.90.196.58:22` → LAN `10.10.10.100` (VM 500).
Este PC **não** roteia `10.10.10.100` direto. BIG e BIG1 não se conectam.
Caminho **público HTTPS** (2026-09-05):
Internet → Cloudflare (TLS `*.itecnologys.com`) → tunnel `39cffaba-…` no BIG → Traefik CT120 `10.10.10.120:80` → VM500 (`:3000` UI, `:8000` `/api`).
Caminho **público HTTPS myvexx / Rently** (em curso 2026-09-06):
Internet → Cloudflare (TLS `*.myvexx.com`) → tunnel myvexx `7233d455-…` (`config-mycloud.yml` no BIG) → Traefik CT120 → VM500.
Hostname: `rently.myvexx.com`. Traefik + CORS já prontos; ingress DNS no BIG ainda pendente (SSH publickey). Ver `docs/ops/RENTLY-MYVEXX-TUNNEL.md`.
Caminho **privado Tailscale** (preservado):
`rentcharge-ts.itecnologys.com` → A `100.90.196.58` DNS-only → nginx BIG `:80` → Traefik CT120 → VM500.
## VM 500
| Campo | Valor |
|--------|--------|
| VMID | 500 |
| Hostname | PROJETOS |
| SO | Debian 12 |
| IP LAN | 10.10.10.100/24 gw 10.10.10.1 vmbr1 |
| CPU/RAM/disco | 2 vCPU / 4 GB / 25 GB |
| Users | `admin` (sudo NOPASSWD), `mini` (docker) |
| Senha SSH | 805353 (admin e mini) |
| Chave | `id_ed25519` PROXMOX-HERTZNER-3 em authorized_keys de admin e mini |
| SSH config Windows | `Host vm500` ProxyJump `big-proxmox` User admin IdentityFile id_ed25519 |
## DNS Cloudflare (zona itecnologys.com)
Zona ID: `7aab8fcc135ed992f284b1f5fbf57065`
Conta da zona: `37fd43032acca512b575ebe20853bf6d` (Admin@itecnologys.com)
| Nome | Tipo | Destino | Proxy |
|------|------|---------|-------|
| rentcharge.itecnologys.com | CNAME | `39cffaba-b611-4c50-8297-41a48d220870.cfargotunnel.com` | **Proxied** |
| rentcharge-ts.itecnologys.com | A | 100.90.196.58 (Tailscale do BIG) | DNS only |
| rentecharge.itecnologys.com | CNAME | rentcharge.itecnologys.com | DNS only |
Backup pré-alteração DNS: `/root/cloudflare-backups/rentcharge-dns-20260905T185905Z.json` (no BIG).
Não aponta para `10.10.10.100`. Hostname não aceita `&`.
**Por que CNAME para o túnel (não A Tailscale):** o padrão dos serviços já SSL no BIG (`fleet.myvexx.com`, `projetos.myvexx.com`, `mycloud.myvexx.com`) é Cloudflare Tunnel. Um CNAME `*.cfargotunnel.com` só funciona se o túnel estiver na **mesma conta Cloudflare da zona**. A zona `itecnologys.com` usa o túnel `39cffaba`; o túnel myvexx `7233d455` é de outra conta e **não** pode publicar este domínio.
## SSL / Cloudflare Tunnel (BIG)
Dois `cloudflared` convivem no BIG. Não se cruzam com Tailscale (redes distintas). Único conflito possível: porta de métricas — já isolada.
| Serviço | Túnel | Conta CF | Config | Métricas | Função |
|---------|-------|----------|--------|----------|--------|
| `cloudflared.service` | `7233d455-2e1f-45b3-bf3a-4bfad26b2f4a` | myvexx (`2d504d7a…`) | `/etc/cloudflared/config-mycloud.yml` | `127.0.0.1:20241` | **Não alterar.** fleet, projetos, mycloud, etc. |
| `cloudflared-itecnologys.service` | `39cffaba-b611-4c50-8297-41a48d220870` | itecnologys (`37fd4303…`) | `/etc/cloudflared/config-itecnologys.yml` | `127.0.0.1:20242` | Só `rentcharge.itecnologys.com` |
Ingress do túnel itecnologys:
```
hostname: rentcharge.itecnologys.com
service: http://10.10.10.120:80
originRequest.httpHostHeader: rentcharge.itecnologys.com
catch-all: http_status:404
```
Credencial: `/root/.cloudflared/39cffaba-b611-4c50-8297-41a48d220870.json`
Unit: `/etc/systemd/system/cloudflared-itecnologys.service` (enabled, Restart=always)
Certificado público observado: CN `itecnologys.com`, SAN `itecnologys.com` + `*.itecnologys.com`, emissor Google Trust Services WE1.
Validação: `https://rentcharge.itecnologys.com/login` HTTP 200; `https://rentcharge.itecnologys.com/api/health` → `{"status":"ok","app":"Granby Rent Arrears"}`.
Origin certs `/root/.cloudflared/cert.pem` e `cert-mycloud.pem` **não** autenticam a API de gestão (`tunnel list` → unauthorized). Operar o túnel com a credencial JSON continua válido. DNS do rentcharge foi aplicado pela API Cloudflare (token de zona), não por `cloudflared tunnel route dns`.
## Traefik (BIG CT120 web-traefik, 10.10.10.120)
Caminho HTTP interno (inalterado):
`Host: rentcharge.itecnologys.com` (ou rentecharge)
→ nginx host BIG `:80` (`myvexx-proxy` server_name) **ou** Cloudflare Tunnel
→ Traefik `http://10.10.10.120:80`
→ UI `http://10.10.10.100:3000` e API `http://10.10.10.100:8000` (`/api`)
Arquivo: `/opt/traefik/dynamic/services.yml` (router `rentcharge` / service `rentcharge-svc`).
Backup: `services.yml.bak-rentcharge-20260905`.
Traefik só entrypoint `:80` (TLS na borda Cloudflare, não neste CT).
`cloudflared` no CT120 está **inativo/disabled**; o túnel itecnologys corre no **host BIG**, não no CT120.
Nginx BIG `:443` (`myvexx-proxy-https`) **não** inclui `rentcharge.itecnologys.com`; SSL público não depende desse vhost.
## Aplicação na VM 500
| Componente | Porta | Serviço |
|------------|-------|---------|
| Next.js frontend | 3000 | `rentcharge-frontend.service` |
| FastAPI backend | 8000 | `rentcharge-backend.service` |
Os dois usam `systemd`, boot automático, `Restart=always`.
Frontend WorkingDirectory: `/home/mini/rentcharge/frontend`.
Backend WorkingDirectory: `/home/mini/rentcharge/backend`.
Python: `/home/mini/rentcharge/venv/bin/python`.
Node: `/home/admin/node/bin/node`.
O frontend usa API no **mesmo domínio** (`/api`); o Traefik separa para 3000/8000.
Banco live: `/home/mini/rentcharge/backend/data/arrears.db`.
Segredo JWT: `/home/mini/rentcharge/config/rentcharge.env` (`SECRET_KEY` apenas; symlink legado `/etc/rentcharge.env`), permissão `600`, não versionar.
Deploy anterior arquivado em `/home/mini/rentcharge/archive/`.
Backup pré-deploy do banco: ver `archive/` / `backend/data/*.bak*`.
Backup pré-troca de senha: `/home/mini/rentcharge/backend/data/arrears.db.bak-20260905T190523Z`.
Backups pagamentos SAGE: `arrears.db.bak-clear-amounts-*`, `arrears.db.bak-sage-dedupe-*`, `arrears.db.bak-before-sage-*`.
### Pagamentos SAGE (fonte única — 2026-09-05/06)
| Item | Valor |
|------|--------|
| Import | `POST /api/import/sage-payments` · `backend/app/services/sage_import.py` |
| Formato | SAGE Customer Invoices Due (Amount negativo; Type SA = cash) |
| Pasta Windows | `E:\Reports Week\PAYMENTS-REPORTS-BY-WEEK\2026\` |
| Pasta VM | `/home/mini/rentcharge/sage-payments/reimport/` |
| Unicidade | `payments.txn_no` (SAGE No) + índice único |
| Default UI | só `category=cash`; filtro rentsupp/refund/all |
| Excel arrears | **não** escreve payments |
Detalhe de regras: `PROJECT-RENTCHARGE.md` § RN-PAY-01..06.
### Login (produção)
| Campo | Valor |
|--------|--------|
| Utilizador | `admin` |
| Palavra-passe | `(rotated — see vault / password manager; never store in Git)` |
| Endpoint | `POST /api/auth/login-json` |
A password de produção **não** deve constar nesta spec. O default em `backend/app/config.py` só aplica no primeiro arranque se o user ainda não existir; após bootstrap, gerir a password fora do Git.
### Módulo de utilizadores (2026-09-06)
Especificação funcional e de segurança: `AUTH-USERS-SPEC.md`.
- Código isolado: `backend/app/modules/users/`
- Cadastro público: `/signup`, acessível por **Register new user** em `/login`
- Verificação: código de seis dígitos por e-mail; 10 minutos, uso único, máximo de cinco erros
- Recuperação: token aleatório longo, hash no SQLite e revogação das sessões após uso
- Sessões: cookie HttpOnly + registo server-side com IP, User-Agent, SO, browser, login, last seen e encerramento
- Auditoria Admin: `/users/sessions` e `/users/activity`
- Banco: tabelas `email_verification_codes`, `auth_sessions`, `session_feature_access`, `auth_events`
- Migração é aditiva e preserva `admin`, `teresa` e `natalie`
**Deploy 2026-09-07:** backend e frontend reiniciados em conjunto; backup
`arrears.db.bak-before-auth-module-20260907-090632`; migração e cookie login
validados. SMTP autenticado **configurado e validado 2026-09-19** (`mail.myvexx.com:465`, `admin@myvexx.com`) em `/home/mini/rentcharge/config/rentcharge.env`.
### Correção crítica: login no browser (2026-09-05)
Causa: `.env.local` com `NEXT_PUBLIC_API_URL=http://127.0.0.1:8000` tinha precedência sobre `.env.production` (`NEXT_PUBLIC_API_URL=` vazio). O JS compilado pedia a API ao **localhost do visitante**, não ao servidor. Curl no BIG/VM devolvia 200; o formulário falhava.
Correção:
- `.env.local` → `/home/mini/rentcharge/frontend/.env.local.disabled-20260905T190745Z`
- Rebuild `npm run build` + `systemctl restart rentcharge-frontend`
- Build atual: **zero** referências a `127.0.0.1:8000`; login usa `/api/auth/login-json` relativo
**Regra:** nunca deixar `.env.local` no frontend de produção. Rebuild só com `.env.production` (`NEXT_PUBLIC_API_URL` vazio).
## Produto (construído nesta fase)
### Charge bands
Além das bandas de rent / full board, existem:
- **Paid by bank account** (`KIND_BANK`) — `uses_resident_amount`: weekly due = `weekly_rent` / `imported_weekly_amount` do residente
- **Flexible full board** (`KIND_FLEXIBLE_FB`) — idem, com flag full board
Lógica: `backend/app/services/charge_bands.py`, `accounting.get_cap`, schema `ChargeBandOut.uses_resident_amount`.
### SAGE ID
Import Excel normaliza SAGE ID (float/int/string; ignora VACANT). Backfill em residentes existentes por quarto/nome sem sobrescrever IDs já válidos. Unicidade em create/update. Visível na lista e na ficha do residente.
### UI (padrão SAGE)
- Header em dois níveis (`AppNav`): barra de company group + barra branca com logo e navegação
- **Residents** e **Reporting**: mesmo layout (command bar, views, filtros por coluna, pager, export) — sem cards KPI no Reporting
- Ficha residente **Payments**: grelha SAGE; colunas separadas ID / Ref / SAGE / Details / Status / Amount; search + filtros; Type default Cash (SA)
- Coluna `#`, Room estreita, badges NP / Name dup / Room conflict; botão Reconcile rooms
### Regras de negócio — ocupação + pagamentos
Detalhe canónico em `PROJECT-RENTCHARGE.md`. Resumo obrigatório para agentes:
| ID | Regra |
|----|--------|
| **RN-01** | Um quarto físico = um residente. Dívida na pessoa (SAGE), não no quarto. |
| **RN-02** | Ocupante validado = último **cash SA** (> 0). RENTSUPP/refund **não** contam. |
| **RN-03A** | Saiu / mudou sem ocupar → **NP01…** + `last_room_no`. |
| **RN-03B** | Nome duplicado → badge; sem merge automático. |
| **RN-PAY-01..06** | SAGE = única fonte de payments; SA=cash; RENTSUPP arquivado; refund não subtrai; `txn_no` único; semana Mon–Sun. |
Estado de implementação: **RN-01..03 + RN-PAY + UI Reporting/Payments aplicados na VM500** (2026-09-06).
**Account Receivables / Credit Book / Period dashboard / ROI Finance A4 pack:** ver `SPEC-ACCOUNT-RECEIVABLES.md` (2026-09-09) — R-CB01…R-CB05, ROI Finance remit/credit net, over-settlement, **Print ROI Finance pack (A4)** com logo SA + Brinks bags + 3 assinaturas.
### Cartas impressas
Templates HTML em `backend/app/templates/` (`letter_debt.html`, `letter_agreement.html`, `statement.html`). Layout visual das cartas de reconhecimento/demonstrativo ainda em evolução com os exemplos do operador.
## Instabilidade SSH deste Windows
Porta 22 Tailscale do big = `SSH-2.0-Tailscale`, não OpenSSH. `ssh.exe` timeout no KEX; paramiko funciona. WAN 51.171.219.235:22/80/443 fechados neste contexto. Acesso público do **app** é pelo túnel Cloudflare, não por abrir WAN.
## Atualização 2026-09-19 noite (007.2026)
- Money privacy (olho €) app-wide — `SPEC-MONEY-PRIVACY.md`
- Profile status audit (Temporary / Deceased / Custody / Treatment) — `SPEC-RESIDENT-PROFILE-STATUS.md`
- SMTP auth mail validado — `AUTH-USERS-SPEC.md`
- Prod + test em VM500 alinhados para estas features
## Checklist agente
1. Hop só no **big**, nunca big1 para esta VM.
2. **Nunca** alterar `/etc/network/interfaces` do Proxmox sem **três** confirmações explícitas.
3. Gerir a aplicação com `systemctl status|restart rentcharge-backend rentcharge-frontend`.
4. Não iniciar processos manuais nas portas 3000 ou 8000.
5. Não tocar em `cloudflared.service` / `config-mycloud.yml` (myvexx). Só o unit `cloudflared-itecnologys`.
6. Rebuild do frontend: confirmar que `.env.local` está disabled; `NEXT_PUBLIC_API_URL` vazio.
7. DNS público do rentcharge = CNAME proxied para o túnel `39cffaba`; acesso Tailscale pelo hostname `rentcharge-ts`.
8. Aplicação agora está na internet com senha curta; Cloudflare Access na borda ainda **não** está ligado.
9. Antes de alterar lista de residentes / rooms / payments: ler RN-01..RN-03 e **RN-PAY** em `PROJECT-RENTCHARGE.md`.
10. Reimport SAGE: usar consolidados em `E:\Reports Week\...` / `/home/mini/rentcharge/sage-payments/reimport/`; dedupe por `txn_no`.
11. Antes de alterar Account Receivables / Credit Book / ROI Finance: ler `SPEC-ACCOUNT-RECEIVABLES.md` (over-settlement, remit, R-CB*).
## Comandos rápidos
```bash
# BIG
systemctl status cloudflared cloudflared-itecnologys
ss -ltnp | grep -E '20241|20242'
# VM500 (via ProxyJump)
systemctl status rentcharge-backend rentcharge-frontend
curl -sS http://127.0.0.1:8000/api/health
curl -sS -X POST http://127.0.0.1:8000/api/auth/login-json \
-H 'Content-Type: application/json' \
--data '{"username":"admin","password":"<REDACTED>"}'
```