# app/services/romaneios/vinculador_de_operacao.rb # # Vincula (ou troca) a operação do mês de um romaneio JÁ IMPORTADO e preenche # APARELHO/TELEFONES/NOME a partir da gade_entregas_* — sem rebuscar o plano. # # ── POR QUE EXISTE ────────────────────────────────────────────────────────── # Quem diz que o paciente é NOVO é a operação do mês, e NOVO é exatamente quem # recebe aparelho. Até aqui só havia dois jeitos de vincular, os dois na hora da # importação: escolher a operação no formulário ou deixar o # Romaneios::CasadorDeOperacao deduzir pelo nome do plano. Quando o casamento não # acontece (nome fora do padrão, empate proposital, operação subida no banco # DEPOIS da importação), o romaneio ficava preso sem operação: a coluna APARELHO # saía vazia para todos e a única saída era reimportar o plano — que depende da # API do SimpliRoute responder ou de o operador ainda ter o .xlsx em mãos. # # Este serviço é o caminho manual: escolhida a operação, ele lê as NFs que já # estão gravadas e aplica o que a operação sabe, mantendo a MESMA precedência do # Romaneios::Importador (operação > plano) e o MESMO respeito a edição humana — # campo que alguém digitou na tela não é sobrescrito. # # A tabela continua SOMENTE LEITURA: quem fala com ela é o EnriquecimentoGade # (SELECT com Operacao.valida? + quote_table_name). module Romaneios class VinculadorDeOperacao # Já existe outro romaneio para a mesma data com a operação escolhida. Não é # erro de programação: é a chave única (planned_date, operacao_tabela) # avisando que o operador está prestes a criar um segundo documento do mesmo # dia — a tela precisa dizer isso com o link do outro, não estourar 500. class Conflito < StandardError attr_reader :existente def initialize(existente) @existente = existente super('Já existe um romaneio desta data para essa operação.') end end Resultado = Struct.new(:com_aparelho, :sem_correspondencia, :preservados, :total, :colunas_ausentes, keyword_init: true) do def resumo partes = ["#{com_aparelho} com aparelho (NOVOS) de #{total} parada(s)"] partes << "#{sem_correspondencia} sem correspondência na operação" if sem_correspondencia.to_i.positive? partes << "#{preservados} campo(s) mantido(s) por edição" if preservados.to_i.positive? partes.join(', ') end end def initialize(romaneio:, tabela:) @romaneio = romaneio @tabela = tabela.to_s end # Devolve um Resultado. Levanta ArgumentError (operação inexistente) ou # Conflito (chave única) — os dois viram mensagem na tela. def aplicar! raise ArgumentError, "Operação inválida: #{@tabela}" unless Operacao.valida?(@tabela) conflitante = Romaneio.where(planned_date: @romaneio.planned_date, operacao_tabela: @tabela) .where.not(id: @romaneio.id).first raise Conflito, conflitante if conflitante gade = EnriquecimentoGade.new(@tabela) linhas = @romaneio.linhas_vigentes.to_a dados = gade.para_notas(linhas.map(&:nota_fiscal)) resultado = nil # Transação: vincular sem enriquecer deixaria a tela dizendo que a operação # está ligada com a coluna APARELHO ainda vazia — o pior dos dois mundos, # porque some o aviso que hoje denuncia o problema. Romaneio.transaction do @romaneio.update!(operacao_tabela: @tabela) resultado = enriquecer(linhas, dados) end resultado.colunas_ausentes = gade.colunas_ausentes resultado end private def enriquecer(linhas, dados) com_aparelho = sem_correspondencia = preservados = 0 linhas.each do |linha| extra = dados[linha.nota_fiscal.to_s] sem_correspondencia += 1 if extra.nil? extra ||= {} # `origem` é o que a linha vale SEM edição humana — é para onde o campo # volta quando o operador limpa a edição na tela. Ele passa a refletir a # operação recém-vinculada; sem isso, "restaurar" devolveria o valor de # antes do vínculo e o aparelho sumiria de novo. origem = linha.origem.to_h novos = { 'aparelho' => extra[:aparelho].to_s, # Mesma precedência do Importador: a coluna `telefones` da operação é a # fonte completa; o "TEL:" das anotações do plano só entra quando ela # não tem número. Idem para o nome. 'telefones' => extra[:telefones].to_s.presence || origem['telefones'].to_s, 'nome' => extra[:nome].to_s.presence || origem['nome'].to_s } novos.each do |campo, valor| if linha.editado?(campo) preservados += 1 next end linha[campo] = valor end linha.origem = origem.merge(novos) linha.save! com_aparelho += 1 if linha.aparelho.to_s.strip.present? end Resultado.new(com_aparelho: com_aparelho, sem_correspondencia: sem_correspondencia, preservados: preservados, total: linhas.size) end end end