# app/services/analytics/planilha_entregas_xlsx.rb # # Gera o binário .xlsx da planilha Entregas do cliente com as 3 abas do modelo # "Entregas SUDESTE MM.AAAA_FINAL.xlsx": RESUMO, ENTREGAS e SimpliRoute. # Os dados vêm de Analytics::PlanilhaEntregas. (Os gráficos embutidos do modelo # não são replicados — apenas os quadros de dados.) require 'caxlsx' module Analytics class PlanilhaEntregasXlsx def initialize(planilha) @planilha = planilha end # String binária do .xlsx. def gerar pkg = Axlsx::Package.new wb = pkg.workbook @negrito = wb.styles.add_style(b: true) @titulo = wb.styles.add_style(b: true, sz: 12) @pct = wb.styles.add_style(format_code: '0.0%') aba_resumo(wb) aba_entregas(wb) aba_simpliroute(wb) pkg.to_stream.read end private # ── RESUMO ─────────────────────────────────────────────────── # Mesma disposição do modelo: mês, totais/performance e um quadro por grupo # (EMAD e UBS) com as coordenadorias fixas + motivos de "Não Entregue". # ("Cordenadoria" sem o primeiro "o" é verbatim do modelo.) def aba_resumo(wb) resumo = @planilha.resumo wb.add_worksheet(name: 'RESUMO') do |s| s.add_row [] s.add_row ['', resumo[:mes]], style: @titulo s.add_row [] s.add_row ['', 'Total Previsto:', resumo[:total_previsto]] s.add_row ['', 'Total Realizado:', resumo[:total_realizado]] s.add_row ['', 'Performance:', resumo[:performance] || ''], style: [nil, nil, (resumo[:performance] ? @pct : nil)] PlanilhaEntregas::GRUPOS.each { |grupo| quadro_grupo(s, grupo, resumo[:grupos][grupo]) } end end def quadro_grupo(s, grupo, coords) s.add_row [] s.add_row ['', grupo], style: @titulo s.add_row ['', '', '', '', '', 'Informações', '', '', 'Motivos de "Não Entregue"'], style: @negrito s.add_row ['', 'Cordenadoria', 'Previsão', 'Entregue', 'Não Entregue', 'Status das Entregas', 'Conferência Documentos', '', *PlanilhaEntregas::MOTIVOS], style: @negrito # Coordenadorias fixas do modelo + eventuais extras vindas dos dados. nomes = PlanilhaEntregas::COORDENADORIAS + (coords.keys - PlanilhaEntregas::COORDENADORIAS) total = @planilha.novo_bloco nomes.each do |nome| b = coords[nome] || @planilha.novo_bloco total[:previsao] += b[:previsao] total[:entregue] += b[:entregue] total[:nao_entregue] += b[:nao_entregue] total[:pendente] += b[:pendente] b[:motivos].each { |m, q| total[:motivos][m] += q } s.add_row ['', nome, b[:previsao], b[:entregue], b[:nao_entregue], (b[:pendente].zero? ? 'Concluído' : 'Em andamento'), 'A iniciar', '', *PlanilhaEntregas::MOTIVOS.map { |m| b[:motivos][m] }] end s.add_row ['', 'Total', total[:previsao], total[:entregue], total[:nao_entregue], '', '', '', *PlanilhaEntregas::MOTIVOS.map { |m| total[:motivos][m] }], style: @negrito if total[:previsao].positive? s.add_row ['', 'Performance %', 1, total[:entregue].to_f / total[:previsao], total[:nao_entregue].to_f / total[:previsao]], style: [nil, @negrito, @pct, @pct, @pct] else s.add_row ['', 'Performance %', '', '', ''], style: [nil, @negrito] end end # ── ENTREGAS ───────────────────────────────────────────────── # A..V direto da tabela gade (texto); W..Z do rastreio: # W STATUS (completed/failed...), X ENTREGA (Sim/Não), Y DATA OCORRÊNCIA # (data real do checkout), Z OCORRÊNCIA (motivo do insucesso). def aba_entregas(wb) linhas = @planilha.linhas wb.add_worksheet(name: 'ENTREGAS') do |sheet| sheet.add_row(PlanilhaEntregas::CABECALHO, style: @negrito) linhas.each do |l| valores, tipos = linha_entrega(l) sheet.add_row(valores, types: tipos) end sheet.auto_filter = "A1:Z#{linhas.size + 1}" end end def linha_entrega(l) status_r = l['rastreio_status'].to_s entregue = if status_r == 'completed' 'Sim' elsif Entrega::STATUS_FALHA.include?(status_r) 'Não' else '' end data_ocorrencia = data_de(l['rastreio_checkout']) valores = PlanilhaEntregas::COLUNAS_GADE.keys.map { |c| l[c].to_s } + [status_r, entregue, data_ocorrencia || '', l['rastreio_observation'].to_s] tipos = Array.new(PlanilhaEntregas::COLUNAS_GADE.size + 2, :string) + [(data_ocorrencia ? :date : :string), :string] [valores, tipos] end # ── SimpliRoute ────────────────────────────────────────────── # Dump cru do rastreio (todas as visitas das NFs da operação), com o # cabeçalho exato do modelo; cabeçalho sem coluna correspondente sai vazio. def aba_simpliroute(wb) colunas = @planilha.colunas_rastreio linhas = @planilha.linhas_rastreio wb.add_worksheet(name: 'SimpliRoute') do |sheet| sheet.add_row(colunas.keys, style: @negrito) linhas.each do |r| sheet.add_row(colunas.values.map { |col| col ? texto(r[col]) : '' }, types: Array.new(colunas.size, :string)) end sheet.auto_filter = "A1:#{Axlsx.col_ref(colunas.size - 1)}#{linhas.size + 1}" end end def texto(valor) valor.respond_to?(:strftime) ? valor.strftime('%d/%m/%Y %H:%M') : valor.to_s end def data_de(valor) return nil if valor.nil? return valor.to_date if valor.respond_to?(:to_date) Date.parse(valor.to_s) rescue ArgumentError, TypeError nil end end end