-- ===================================================================== -- Módulo DTF — schema completo -- ===================================================================== -- Duas bases: -- NUVEM (PostgreSQL, no VPS) → pedido, arte, parte, job, agente -- LOCAL (SQLite, na fábrica) → card, movimento, retrabalho, job_local -- -- A separação é proposital: o kanban precisa funcionar com a internet fora. -- ===================================================================== -- ===================================================================== -- NUVEM · PostgreSQL -- ===================================================================== CREATE TABLE pedido ( id BIGSERIAL PRIMARY KEY, numero_tiny TEXT NOT NULL UNIQUE, -- chave de tudo cliente TEXT NOT NULL, telefone TEXT NOT NULL, metros NUMERIC(10,2) NOT NULL, -- metros comprados por arte cliente_novo BOOLEAN NOT NULL DEFAULT false,-- dispara prova de cor token TEXT NOT NULL UNIQUE, -- UUID v4 do link token_expira TIMESTAMPTZ NOT NULL, -- 7 dias lembrete_em TIMESTAMPTZ, -- 1h sem arte → cobra criado_em TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ix_pedido_token ON pedido(token); CREATE INDEX ix_pedido_semarte ON pedido(criado_em) WHERE NOT EXISTS (SELECT 1 FROM arte WHERE arte.pedido_id = pedido.id); CREATE TABLE arte ( id BIGSERIAL PRIMARY KEY, pedido_id BIGINT NOT NULL REFERENCES pedido(id), arquivo TEXT NOT NULL, -- chave no storage arquivo_orig TEXT, -- original preservado bytes BIGINT NOT NULL DEFAULT 0, formato_orig TEXT, -- png, pdf, psd, cdr... natureza TEXT, -- vetor | raster | conferir repeticoes INT NOT NULL DEFAULT 1, status TEXT NOT NULL DEFAULT 'recebida', -- recebida | processando | aprovada | recusada conferir_manual BOOLEAN NOT NULL DEFAULT false,-- CDR motivo TEXT, -- por que foi recusada avisos TEXT, -- aceitou mas com ressalva dpi_efetivo NUMERIC(8,2), largura_cm NUMERIC(8,2), metros_totais NUMERIC(10,2), minutos_maquina INT, -- estimativa; vira campo no card cores_tensas TEXT, -- fora do gamut, se houver virus_ok BOOLEAN NOT NULL DEFAULT false,-- ClamAV liberou baixada BOOLEAN NOT NULL DEFAULT false,-- o agente já trouxe criado_em TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ix_arte_pedido ON arte(pedido_id); CREATE INDEX ix_arte_baixar ON arte(status, baixada, virus_ok); CREATE TABLE parte ( id BIGSERIAL PRIMARY KEY, arte_id BIGINT NOT NULL REFERENCES arte(id), ordem INT NOT NULL, metros NUMERIC(10,2) NOT NULL, arquivo TEXT NOT NULL, carimbada BOOLEAN NOT NULL DEFAULT false,-- só a última leva carimbo UNIQUE (arte_id, ordem) ); -- Fila de jobs. Tabela, não broker: no volume da Altus (algumas centenas de -- pedidos/dia) Redis e Celery são complexidade sem retorno. -- Consumir com: SELECT ... FOR UPDATE SKIP LOCKED CREATE TABLE job ( id BIGSERIAL PRIMARY KEY, tipo TEXT NOT NULL, -- preflight | whats | tiny | limpeza payload JSONB NOT NULL, tentativas INT NOT NULL DEFAULT 0, proxima_em TIMESTAMPTZ NOT NULL DEFAULT now(), erro TEXT, feito BOOLEAN NOT NULL DEFAULT false, criado_em TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ix_job_fila ON job(feito, proxima_em) WHERE NOT feito; CREATE TABLE agente_heartbeat ( id SERIAL PRIMARY KEY, visto_em TIMESTAMPTZ NOT NULL DEFAULT now() ); -- sem sinal por 15 min → alertar o TI -- Artes guardadas para recompra. Prefixo separado no storage, retenção 12 meses. CREATE TABLE arte_cliente ( id BIGSERIAL PRIMARY KEY, numero_cliente TEXT NOT NULL, token_cliente TEXT NOT NULL, -- link permanente arte_id BIGINT REFERENCES arte(id), arquivo TEXT NOT NULL, -- só a arte tratada descricao TEXT, criado_em TIMESTAMPTZ NOT NULL DEFAULT now(), expira_em TIMESTAMPTZ NOT NULL ); CREATE INDEX ix_arte_cliente ON arte_cliente(numero_cliente, token_cliente); -- ===================================================================== -- LOCAL · SQLite (na fábrica) -- ===================================================================== CREATE TABLE card ( arte_id INTEGER PRIMARY KEY, pedido TEXT NOT NULL, cliente TEXT NOT NULL, metros REAL NOT NULL, minutos_maquina INTEGER, -- estimativa. Herda os marcadores 30min/1h/3h -- do Tiny. Sugerida por metros/20*60; quem -- trata a arte ajusta só quando foge do normal partes INTEGER NOT NULL DEFAULT 1, coluna TEXT NOT NULL DEFAULT 'rec', -- rec | tra | apc | fil | imp | cor | fin -- NÃO existe coluna de aplicação: a sala só imprime o filme desde TEXT NOT NULL, -- entrada na coluna atual (ISO) maquina TEXT, -- 'Maq 1' … 'Maq 6' reservado_por TEXT, -- máquina que puxou reservado_em TEXT, -- expira em 3 min (ajuste da sala) conferir_manual INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX ix_card_coluna ON card(coluna, desde); -- A tabela mais importante do sistema. Dela saem TODOS os números que hoje -- não existem: tempo por etapa, fila por máquina, retrabalho, ocupação real. CREATE TABLE movimento ( id INTEGER PRIMARY KEY AUTOINCREMENT, arte_id INTEGER NOT NULL, pedido TEXT NOT NULL, de TEXT NOT NULL, para TEXT NOT NULL, entrou_em TEXT NOT NULL, saiu_em TEXT NOT NULL, segundos INTEGER NOT NULL, usuario TEXT NOT NULL, -- login do PCP. Se o PC tiver login -- compartilhado, o indicador de retrabalho -- por pessoa fica sem dono maquina TEXT ); CREATE INDEX ix_mov_arte ON movimento(arte_id); CREATE INDEX ix_mov_saiu ON movimento(saiu_em); CREATE INDEX ix_mov_etapa ON movimento(de, saiu_em); CREATE TABLE retrabalho ( id INTEGER PRIMARY KEY AUTOINCREMENT, pedido_origem TEXT NOT NULL, pedido_novo TEXT, metros REAL NOT NULL, causa TEXT NOT NULL, -- arte_cliente | tratamento | impressao | perfil_cor | pedido -- TRAVADA depois de aberta: com o indicador atrelado a bônus, -- haveria incentivo para reclassificar falha de máquina como -- culpa do cliente aberto_por TEXT NOT NULL, -- Mayana: conhece o cliente e a reclamação aberto_em TEXT NOT NULL, vez INTEGER NOT NULL DEFAULT 1, -- 2ª sobe alçada, 3ª trava alcada TEXT NOT NULL, -- sala | financeiro | diretoria autorizado_por TEXT, -- Thales ou Alexandre: entra na meta deles autorizado_em TEXT, contestado_por TEXT, -- discorda da causa mas não pode mudá-la contestado_em TEXT, contestacao TEXT, evidencia TEXT -- foto ou print da reclamação ); CREATE INDEX ix_retra_data ON retrabalho(aberto_em); CREATE INDEX ix_retra_causa ON retrabalho(causa, aberto_em); CREATE TABLE job_local ( id INTEGER PRIMARY KEY AUTOINCREMENT, tipo TEXT NOT NULL, -- tiny_marcador | whats payload TEXT NOT NULL, tentativas INTEGER NOT NULL DEFAULT 0, proxima_em REAL NOT NULL, -- 1, 5 e 30 min erro TEXT, feito INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX ix_joblocal ON job_local(feito, proxima_em); -- Consumo de filme, para o aproveitamento. NÃO exige apontamento novo: -- lê as transferências para o depósito de impressão no Tiny. CREATE TABLE consumo_filme ( id INTEGER PRIMARY KEY AUTOINCREMENT, em TEXT NOT NULL, metros REAL NOT NULL, origem TEXT NOT NULL DEFAULT 'tiny' -- transferência de depósito ); -- ===================================================================== -- Consultas que o painel usa -- ===================================================================== -- Tempo médio por etapa nos últimos N dias -- SELECT de, COUNT(*) n, AVG(segundos)/60 media_min, MAX(segundos)/60 pior_min -- FROM movimento WHERE saiu_em >= date('now','-7 days') GROUP BY de; -- Velocidade real por máquina — é o número que valida ou derruba os 20 m/h -- SELECT m.maquina, SUM(c.metros) metros, SUM(m.segundos)/3600.0 horas, -- SUM(c.metros)/(SUM(m.segundos)/3600.0) m_por_hora -- FROM movimento m JOIN card c ON c.arte_id = m.arte_id -- WHERE m.de='imp' AND m.saiu_em >= date('now','-7 days') GROUP BY m.maquina; -- Retrabalho por causa, com responsável -- SELECT causa, COUNT(*) n, SUM(metros) m FROM retrabalho -- WHERE aberto_em >= date('now','-30 days') GROUP BY causa; -- Aproveitamento do filme -- faturado = SELECT SUM(metros) FROM card WHERE coluna='fin' AND desde >= ... -- consumido = SELECT SUM(metros) FROM consumo_filme WHERE em >= ... -- aproveitamento = faturado / consumido