-- 0039 — vinculo entre projetos: a hierarquia que o grupo nao expressa
--
-- O BURACO
--
-- Ate aqui o alcance de uma credencial tinha tres formas: UM projeto (chave de
-- projeto), UM CONJUNTO (chave de tenant presa a um grupo) ou TODOS (chave de
-- tenant). As tres sao PLANAS. Nao havia como dizer "este projeto esta ABAIXO
-- daquele".
--
-- E hierarquia e o desenho de uma rede de distribuicao inteira: uma matriz no
-- topo, revendas abaixo dela, pontos de atendimento abaixo das revendas e
-- vendedores no fim. Quatro niveis, e IRREGULAR -- parte dos vendedores pende
-- da revenda e parte do ponto de atendimento. Profundidade fixa nao serve.
--
-- Sem hierarquia, cada nivel precisaria de um grupo mantido a mao, e "a revenda
-- ve os pontos de atendimento dela" viraria uma lista para alguem esquecer de
-- atualizar quando nascesse um ponto novo.
--
--
-- POR QUE N-PARA-N, E NAO UMA COLUNA `parent_project_id`
--
-- Uma coluna de pai seria menor e resolveria a rede em que cada subordinado tem
-- exatamente um superior, que e o caso comum. Duas razoes decidiram contra:
--
--   * o modelo de origem PERMITE mais de um superior, e ha caso de negocio
--     real: os papeis "quem opera a emissao", "quem recebe a comissao" e "quem
--     e o responsavel financeiro" podem ser parceiros DIFERENTES. Com coluna de
--     pai isso nao se expressa;
--   * mover um subordinado de um superior para outro passa a ser apagar um
--     vinculo e criar outro, SEM TOCAR na linha do projeto. Com coluna, e um
--     UPDATE no projeto, e quem era o superior antes se perde.
--
-- O custo e deixar de ser arvore e passar a ser grafo: a travessia precisa
-- deduplicar por id e carregar conjunto de visitados. O conjunto de visitados
-- seria necessario de qualquer forma, contra ciclo.
--
--
-- A DIRECAO NAO VEM DESTA TABELA -- VEM DA TRAVESSIA
--
-- Isto e o ponto que mais importa entender antes de mexer aqui.
--
-- O `ScopeResolver` parte dos projetos que a credencial alcanca e caminha para
-- os SUBORDINADOS. Ele nunca pergunta "quem e meu superior". E por isso que um
-- ponto de atendimento nao alcanca a revenda: nao existe caminho de subida no
-- codigo, e nao porque a tabela impeca.
--
-- Consequencia pratica: quem for escrever consulta nova sobre esta tabela tem de
-- saber de que lado esta. E por isso as colunas se chamam `superior_project_id`
-- e `subordinate_project_id`, e nao `project_a`/`project_b`. Uma tabela de
-- aparencia simetrica e como se cria um vazamento -- alguem le o join na ordem
-- errada, a subida passa a existir, e nada acusa.

CREATE TABLE project_links (
  id                     BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,

  -- Redundante com os dois projetos, e de proposito -- a mesma razao de
  -- `embed_messages` carregar o tenant: um erro de join nao pode ser suficiente
  -- para ligar projetos de clientes diferentes. A aplicacao recusa vinculo
  -- entre tenants distintos, e esta coluna permite conferir isso numa consulta
  -- so, sem depender de duas junções darem certo.
  tenant_id              BIGINT UNSIGNED NOT NULL,

  -- Quem VE. A travessia comeca aqui e desce.
  superior_project_id    BIGINT UNSIGNED NOT NULL,

  -- Quem E VISTO. Nunca o contrario.
  subordinate_project_id BIGINT UNSIGNED NOT NULL,

  created_at             DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

  PRIMARY KEY (id),

  -- Idempotencia do vinculo: religar o mesmo par nao duplica. E o que permite
  -- ao provisionamento reexecutar sem pensar.
  UNIQUE KEY uq_project_links (superior_project_id, subordinate_project_id),

  -- A travessia le por superior; esta serve ao caminho inverso (`superiorIds`),
  -- usado pela guarda de ciclo e pela listagem.
  KEY idx_project_links_subordinado (subordinate_project_id),
  KEY idx_project_links_tenant (tenant_id),

  -- SEM `ON DELETE CASCADE`, nos dois lados. Apagar projeto nao e operacao que
  -- o sistema faz; no dia em que for, o vinculo tem de APARECER como
  -- impedimento, e nao desaparecer junto levando a hierarquia com ele.
  CONSTRAINT fk_project_links_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id),
  CONSTRAINT fk_project_links_superior FOREIGN KEY (superior_project_id)
    REFERENCES projects(id),
  CONSTRAINT fk_project_links_subordinado FOREIGN KEY (subordinate_project_id)
    REFERENCES projects(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ---------------------------------------------------------------------------
-- O QUE ESTA TABELA NAO E
-- ---------------------------------------------------------------------------
-- Nao e `project_groups`. O grupo e um CONJUNTO PLANO com nome, montado a mao
-- para descrever uma carteira ("estas onze empresas"). O vinculo e DIRIGIDO e
-- descreve a estrutura da rede. Os dois convivem, e se somam: um grupo de
-- revendas alcanca as revendas E, por este vinculo, os pontos de atendimento
-- delas -- sem que ninguem liste os pontos no grupo.
--
-- Nao e a tabela de CATEGORIA do cliente, nem qualquer nocao de "nivel". O
-- Contextia nao sabe o que e uma revenda ou um ponto de atendimento, e nao deve
-- saber: ele sabe que um projeto ve o outro. O vocabulario do cliente fica no
-- cliente -- inclusive nos comentarios daqui.
--
-- ---------------------------------------------------------------------------
-- O QUE NAO MUDA
-- ---------------------------------------------------------------------------
-- Tabela vazia = conjunto de descendentes vazio = alcance identico ao de antes,
-- nos tres caminhos (tenant admin, tenant+grupo, projeto). Nenhuma instalacao
-- em producao se move ao aplicar esta migracao, e ha teste que prova isso.
--
-- As VIEWS de dataset tambem nao mudam: cada projeto continua tendo as suas
-- `ds_<projeto>_<dataset>`. O isolamento continua sendo o mesmo mecanismo --
-- `projectsInScope` -> `datasetAllowlist` -> `SqlGuard` --, e o que muda e
-- apenas o conjunto que o primeiro devolve.
