Correções para funcionamento do login adm
This commit is contained in:
@@ -38,4 +38,4 @@ SMTP_DOMAIN=gade.com.br
|
|||||||
|
|
||||||
# ── App ───────────────────────────────────────────────────────
|
# ── App ───────────────────────────────────────────────────────
|
||||||
APP_HOST=localhost:3000
|
APP_HOST=localhost:3000
|
||||||
APP_NAME=Gade Logística
|
APP_NAME=Reem Logística
|
||||||
|
|||||||
125
CLAUDE.md
Normal file
125
CLAUDE.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project overview
|
||||||
|
|
||||||
|
**Reem Logística** is a Rails 7 web application for controlling delivery costs for **Gade Hospitalar** (account_id: 95907). It manages consolidations of driver payments across delivery routes (UBS SUL, UBS LESTE, EMAD, STS, SAD, etc.).
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
All commands run inside Docker. The PostgreSQL database lives externally (not in docker-compose).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Rails console
|
||||||
|
docker-compose exec app bundle exec rails console
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
docker-compose exec app bundle exec rails db:migrate
|
||||||
|
|
||||||
|
# Seed (creates admin + test drivers)
|
||||||
|
docker-compose exec app bundle exec rails db:seed
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
docker-compose logs -f app
|
||||||
|
|
||||||
|
# Tests (RSpec)
|
||||||
|
docker-compose exec app bundle exec rspec
|
||||||
|
docker-compose exec app bundle exec rspec spec/models/consolidacao_spec.rb # single file
|
||||||
|
|
||||||
|
# Rake task (runs manually what the cron job runs hourly)
|
||||||
|
docker-compose exec app bundle exec rake historico:atualizar
|
||||||
|
|
||||||
|
# Activate the hourly cron job on the server
|
||||||
|
docker-compose exec app bundle exec whenever --update-crontab
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup from scratch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env: set DB_HOST, DB_PASSWORD, and generate SECRET_KEY_BASE with:
|
||||||
|
# openssl rand -hex 64
|
||||||
|
docker-compose build --no-cache
|
||||||
|
docker-compose up -d
|
||||||
|
docker-compose exec app bundle exec rails db:create db:migrate db:seed
|
||||||
|
```
|
||||||
|
|
||||||
|
Default credentials (change in production): `admin@gade.com` / `Gade@2026!`
|
||||||
|
|
||||||
|
## Critical constraint: read-only external table
|
||||||
|
|
||||||
|
The table `public.db_reem_simplerout_2026` is owned by an external system (SimpleRoute) that updates it every hour. **NEVER** create migrations for it, and never run INSERT/UPDATE/DELETE/DROP/TRUNCATE on it.
|
||||||
|
|
||||||
|
`Entrega` (app/models/entrega.rb) maps to this table and enforces `readonly? = true`. All queries go through its scopes — the most important being `.pagas` (`status='completed' AND checkin IS NOT NULL`), which defines payment eligibility.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Data flow
|
||||||
|
|
||||||
|
1. External system writes to `db_reem_simplerout_2026` hourly
|
||||||
|
2. `Atualizar HistoricoEstimadoJob` (triggered by Whenever cron every hour) counts paid deliveries per driver and writes to `historico_estimados`
|
||||||
|
3. Admins/managers create **Consolidações** (payment batches) covering a date range + list of drivers
|
||||||
|
4. A 3-step wizard classifies each delivery per driver (Normal/Retirada/Bônus/Desconto toggles)
|
||||||
|
5. Finalizing a consolidation sends WhatsApp (Twilio) and email notifications to drivers
|
||||||
|
6. Drivers view their own panel at `/motorista` using a 4-digit PIN or QR code from their printed payslip
|
||||||
|
|
||||||
|
### Key models
|
||||||
|
|
||||||
|
| Model | Table | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `Entrega` | `db_reem_simplerout_2026` | Read-only. External data. |
|
||||||
|
| `Consolidacao` | `consolidacoes` | Payment batch. Enum status: `rascunho/finalizada/arquivada`. Soft-delete via `deleted_at`. |
|
||||||
|
| `ConsolidacaoMotorista` | `consolidacao_motoristas` | Join: which drivers are in a consolidation. |
|
||||||
|
| `ConsolidacaoEntrega` | `consolidacao_entregas` | Each classified delivery. Enum tipo: `normal/retirada/bonus/desconto`. |
|
||||||
|
| `HistoricoEstimado` | `historico_estimados` | Snapshot of hourly estimated values per driver. |
|
||||||
|
| `Configuracao` | `configuracoes` | Key-value store for prices and feature flags. Access via `Configuracao.valor('chave')`. |
|
||||||
|
| `AuditoriaLog` | `auditoria_logs` | Audit trail. Controllers include `Auditavel` concern and call `auditar!(:acao, registro)`. |
|
||||||
|
| `User` | `users` | Devise auth. Roles: `admin/gerente/operador/motorista`. Drivers may have no email — they log in via `pin_code` (4 digits, unique per driver) or `login_token` (QR). |
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
Pundit policies in `app/policies/`. `ApplicationController` includes `Pundit::Authorization` and calls `authenticate_user!` on every action. The motorista panel (`/motorista/**`) uses a separate session mechanism (`motorista/sessoes_controller.rb`) — drivers are NOT authenticated via Devise.
|
||||||
|
|
||||||
|
### Two login flows for drivers
|
||||||
|
|
||||||
|
- `/auth/login` (Devise, Fase 2) — PIN tab on the standard login page
|
||||||
|
- `/motorista/login` (Fase 8) — dedicated numeric keypad, used by QR codes printed on payslips. Prefer this for mobile. Both use `pin_code`.
|
||||||
|
|
||||||
|
### PDF generation
|
||||||
|
|
||||||
|
`app/services/pdf/` contains three classes:
|
||||||
|
- `BasePdf` — shared black/orange header and footer layout
|
||||||
|
- `RelatorioMotoristaPdf` — detailed delivery table per driver
|
||||||
|
- `HoleritePdf` — payslip style + embedded QR code (via `rqrcode`/`chunky_png`) linking to `/motorista/acesso/:token`
|
||||||
|
|
||||||
|
### The consolidation wizard
|
||||||
|
|
||||||
|
`/consolidacoes/:id/wizard` → `/consolidacao_entregas/validar` → `/consolidacao_entregas/revisar`
|
||||||
|
|
||||||
|
- **Passo 1** (wizard): pick a driver. Shows progress per driver.
|
||||||
|
- **Passo 2** (validar): color-coded toggle buttons per delivery (orange=Normal, dark orange=Retirada, white=Bônus, black=Desconto). Toggle individual or mass-classify. Stimulus controller (`validacao_controller.js`) handles real-time totals and progress.
|
||||||
|
- **Passo 3** (revisar): summary for the selected driver, then move to the next driver or save as draft.
|
||||||
|
|
||||||
|
Finalizing is blocked (server-side + UI) until all drivers have all eligible deliveries classified.
|
||||||
|
|
||||||
|
### Notifications (NotificacaoService)
|
||||||
|
|
||||||
|
Controlled by `Configuracao` flags `notificacao_whatsapp` and `notificacao_email`. Failures are rescued and logged — they never abort the finalization. WhatsApp requires Twilio ENV vars + driver `telefone` field in `+5511999998888` format.
|
||||||
|
|
||||||
|
## Design system
|
||||||
|
|
||||||
|
Dark theme (default). Colors: background `#0a0a0a`, cards `#1a1a1a`, accent/buttons `#f97316` (orange), text `#ffffff`, success `#22c55e`, error `#ef4444`. Minimum font 16px desktop / 18px mobile. Touch targets minimum 48px height.
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
All config comes from `.env` (never hardcoded). Key variables:
|
||||||
|
- `DB_HOST` / `DB_NAME` / `DB_USER` / `DB_PASSWORD` — PostgreSQL connection
|
||||||
|
- `DB_EXISTING_ACCOUNT_ID` — filters `db_reem_simplerout_2026` to Gade's account (default: 95907)
|
||||||
|
- `SECRET_KEY_BASE` — generate with `openssl rand -hex 64`
|
||||||
|
- `APP_HOST` — used in QR code URLs and notification links
|
||||||
|
- `TWILIO_*` — WhatsApp notifications (optional)
|
||||||
|
- `SMTP_*` — Email notifications (optional, only activates if `SMTP_USERNAME` is set)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# 🚛 Gade Logística — Sistema de Controle de Custos
|
# 🚛 Reem Logística — Sistema de Controle de Custos
|
||||||
|
|
||||||
Sistema web para controle de custos de entregas hospitalares da **Gade Hospitalar**.
|
Sistema web para controle de custos de entregas hospitalares da **Gade Hospitalar**.
|
||||||
|
|
||||||
|
|||||||
2
Rakefile
Normal file
2
Rakefile
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
require_relative "config/application"
|
||||||
|
Rails.application.load_tasks
|
||||||
@@ -4,7 +4,7 @@ class Admin::UsuariosController < ApplicationController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
authorize User
|
authorize User
|
||||||
@usuarios = policy_scope(User).order(:nome).page(params[:page]).per(20)
|
@usuarios = policy_scope(User).order(:nome).limit(200)
|
||||||
@roles = User.roles.keys
|
@roles = User.roles.keys
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -47,14 +47,15 @@ class DashboardController < ApplicationController
|
|||||||
.count
|
.count
|
||||||
.sort_by { |_, v| -v }
|
.sort_by { |_, v| -v }
|
||||||
.first(6)
|
.first(6)
|
||||||
|
.to_h
|
||||||
|
|
||||||
# Evolução diária do mês (para Chart.js)
|
# Evolução diária do mês (para Chart.js)
|
||||||
@grafico_diario = build_grafico_diario(entregas_mes, config[:entrega])
|
@grafico_diario = build_grafico_diario(entregas_mes, config[:entrega])
|
||||||
|
|
||||||
# Consolidações do mês
|
# Consolidações do mês
|
||||||
@consolidacoes_mes = Consolidacao.do_mes(@data_referencia)
|
@consolidacoes_mes = Consolidacao.ativas.where(created_at: @mes_inicio.beginning_of_day..@mes_fim.end_of_day)
|
||||||
@consolidacoes_abertas = @consolidacoes_mes.where(status: :aberta).count
|
@consolidacoes_abertas = @consolidacoes_mes.where(status: :rascunho).count
|
||||||
@consolidacoes_fechadas = @consolidacoes_mes.where(status: :fechada).count
|
@consolidacoes_fechadas = @consolidacoes_mes.where(status: :finalizada).count
|
||||||
|
|
||||||
# Histórico estimado mais recente
|
# Histórico estimado mais recente
|
||||||
@historico_recente = HistoricoEstimado.order(created_at: :desc).limit(5)
|
@historico_recente = HistoricoEstimado.order(created_at: :desc).limit(5)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Users::SessionsController < Devise::SessionsController
|
|||||||
def after_sign_in_path_for(resource)
|
def after_sign_in_path_for(resource)
|
||||||
case resource.role
|
case resource.role
|
||||||
when 'motorista'
|
when 'motorista'
|
||||||
motorista_path
|
motorista_dashboard_path
|
||||||
else
|
else
|
||||||
dashboard_path
|
dashboard_path
|
||||||
end
|
end
|
||||||
@@ -40,13 +40,13 @@ class Users::SessionsController < Devise::SessionsController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def handle_pin_login
|
def handle_pin_login
|
||||||
pin = params[:user][:pin].to_s.strip
|
pin = params[:pin].to_s.strip
|
||||||
user = User.find_by(pin_code: pin, role: 'motorista')
|
user = User.find_by(pin_code: pin, role: 'motorista')
|
||||||
|
|
||||||
if user&.active?
|
if user&.ativo?
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
AuditoriaLog.registrar(user: user, acao: 'login_pin', entidade: 'User', dados_novos: { detalhes: "" }, request: request)
|
AuditoriaLog.registrar(user: user, acao: 'login_pin', entidade: 'User', dados_novos: { detalhes: "" }, request: request)
|
||||||
redirect_to motorista_path, notice: "Bem-vindo, #{user.nome_display}!"
|
redirect_to motorista_dashboard_path, notice: "Bem-vindo, #{user.nome_display}!"
|
||||||
else
|
else
|
||||||
flash.now[:alert] = 'PIN inválido ou motorista inativo.'
|
flash.now[:alert] = 'PIN inválido ou motorista inativo.'
|
||||||
@pin_login = true
|
@pin_login = true
|
||||||
|
|||||||
3
app/models/application_record.rb
Normal file
3
app/models/application_record.rb
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
class ApplicationRecord < ActiveRecord::Base
|
||||||
|
primary_abstract_class
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# app/models/entrega.rb
|
# app/models/entrega.rb
|
||||||
#
|
#
|
||||||
# Model de LEITURA para a tabela existente da Gade Hospitalar.
|
# Model de LEITURA para a tabela existente da Reem Transporte.
|
||||||
# NUNCA criar migration para esta tabela.
|
# NUNCA criar migration para esta tabela.
|
||||||
# NUNCA executar INSERT, UPDATE, DELETE ou DROP nesta tabela.
|
# NUNCA executar INSERT, UPDATE, DELETE ou DROP nesta tabela.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# app/models/user.rb
|
# app/models/user.rb
|
||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
devise :database_authenticatable,
|
devise :database_authenticatable,
|
||||||
:registerable,
|
|
||||||
:recoverable,
|
:recoverable,
|
||||||
:rememberable,
|
:rememberable,
|
||||||
:validatable
|
:validatable
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ module Pdf
|
|||||||
@pdf.fill_rectangle [-40 + 0, @pdf.cursor + 40], 8, 70 # faixa laranja lateral
|
@pdf.fill_rectangle [-40 + 0, @pdf.cursor + 40], 8, 70 # faixa laranja lateral
|
||||||
|
|
||||||
@pdf.fill_color 'FFFFFF'
|
@pdf.fill_color 'FFFFFF'
|
||||||
@pdf.text_box 'GADE HOSPITALAR',
|
@pdf.text_box 'REEM TRANSPORTE',
|
||||||
at: [10, @pdf.cursor + 20], size: 20, style: :bold
|
at: [10, @pdf.cursor + 20], size: 20, style: :bold
|
||||||
@pdf.fill_color LARANJA
|
@pdf.fill_color LARANJA
|
||||||
@pdf.text_box 'Sistema de Controle de Custos Logísticos',
|
@pdf.text_box 'Sistema de Controle de Custos Logísticos',
|
||||||
@@ -44,7 +44,7 @@ module Pdf
|
|||||||
@pdf.repeat(:all) do
|
@pdf.repeat(:all) do
|
||||||
@pdf.bounding_box [0, 20], width: @pdf.bounds.width do
|
@pdf.bounding_box [0, 20], width: @pdf.bounds.width do
|
||||||
@pdf.fill_color CINZA
|
@pdf.fill_color CINZA
|
||||||
@pdf.text "Gerado em #{Time.current.strftime('%d/%m/%Y %H:%M')} · Gade Hospitalar — documento interno",
|
@pdf.text "Gerado em #{Time.current.strftime('%d/%m/%Y %H:%M')} · Reem Transporte — documento interno",
|
||||||
size: 8, align: :center
|
size: 8, align: :center
|
||||||
@pdf.fill_color '000000'
|
@pdf.fill_color '000000'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="p-4 bg-[#0a0a0a] rounded-xl border border-white/5">
|
<div class="p-4 bg-[#0a0a0a] rounded-xl border border-white/5">
|
||||||
<p class="text-gray-400 text-xs mb-1">Account ID</p>
|
<p class="text-gray-400 text-xs mb-1">Account ID</p>
|
||||||
<p class="text-white font-mono text-sm">95907 (Gade Hospitalar)</p>
|
<p class="text-white font-mono text-sm">95907 (Reem Transporte)</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4 bg-[#0a0a0a] rounded-xl border border-white/5">
|
<div class="p-4 bg-[#0a0a0a] rounded-xl border border-white/5">
|
||||||
<p class="text-gray-400 text-xs mb-1">Atualização</p>
|
<p class="text-gray-400 text-xs mb-1">Atualização</p>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div style="font-family: Arial, sans-serif; max-width: 520px; margin: 0 auto; background: #0a0a0a; border-radius: 12px; overflow: hidden;">
|
<div style="font-family: Arial, sans-serif; max-width: 520px; margin: 0 auto; background: #0a0a0a; border-radius: 12px; overflow: hidden;">
|
||||||
|
|
||||||
<div style="background: #0a0a0a; padding: 24px; border-left: 6px solid #f97316;">
|
<div style="background: #0a0a0a; padding: 24px; border-left: 6px solid #f97316;">
|
||||||
<h1 style="color: #ffffff; margin: 0; font-size: 20px;">GADE HOSPITALAR</h1>
|
<h1 style="color: #ffffff; margin: 0; font-size: 20px;">REEM TRANSPORTE</h1>
|
||||||
<p style="color: #f97316; margin: 4px 0 0; font-size: 13px;">Sistema de Logística</p>
|
<p style="color: #f97316; margin: 4px 0 0; font-size: 13px;">Sistema de Logística</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<div class="bg-[#0a0a0a] rounded-lg p-4 mb-5 flex items-center gap-3">
|
<div class="bg-[#0a0a0a] rounded-lg p-4 mb-5 flex items-center gap-3">
|
||||||
<div class="w-2 h-12 bg-orange-500 rounded"></div>
|
<div class="w-2 h-12 bg-orange-500 rounded"></div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-white font-black text-lg leading-tight">GADE HOSPITALAR</p>
|
<p class="text-white font-black text-lg leading-tight">REEM TRANSPORTE</p>
|
||||||
<p class="text-orange-500 text-xs">Holerite de Pagamento — Entregas</p>
|
<p class="text-orange-500 text-xs">Holerite de Pagamento — Entregas</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
44
app/views/devise/passwords/edit.html.erb
Normal file
44
app/views/devise/passwords/edit.html.erb
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<%# app/views/devise/passwords/edit.html.erb — Redefinir senha (link do e-mail) %>
|
||||||
|
<% content_for :title, 'Nova senha' %>
|
||||||
|
|
||||||
|
<div class="min-h-screen flex items-center justify-center p-4">
|
||||||
|
<div class="w-full max-w-sm">
|
||||||
|
|
||||||
|
<div class="text-center mb-8">
|
||||||
|
<div class="w-16 h-16 bg-[#f97316] rounded-2xl flex items-center justify-center mx-auto mb-3">
|
||||||
|
<span class="text-3xl">🔒</span>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-white font-bold text-2xl">Definir nova senha</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-[#111111] border border-white/10 rounded-2xl p-8">
|
||||||
|
<%= form_with(url: user_password_path, scope: :user, method: :put, data: { turbo: false }) do |f| %>
|
||||||
|
<%= f.hidden_field :reset_password_token, value: params[:reset_password_token] %>
|
||||||
|
<div class="space-y-5">
|
||||||
|
<div>
|
||||||
|
<%= f.label :password, 'Nova senha', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
|
||||||
|
<%= f.password_field :password, autofocus: true, autocomplete: 'new-password',
|
||||||
|
class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
|
||||||
|
focus:outline-none focus:border-[#f97316] focus:ring-1 focus:ring-[#f97316] text-base' %>
|
||||||
|
<p class="text-gray-600 text-xs mt-1">Mínimo de 6 caracteres</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<%= f.label :password_confirmation, 'Confirmar nova senha', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
|
||||||
|
<%= f.password_field :password_confirmation, autocomplete: 'new-password',
|
||||||
|
class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
|
||||||
|
focus:outline-none focus:border-[#f97316] focus:ring-1 focus:ring-[#f97316] text-base' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= f.submit 'Salvar nova senha',
|
||||||
|
class: 'w-full py-3.5 bg-[#f97316] hover:bg-orange-500 text-white font-semibold
|
||||||
|
rounded-xl transition-colors cursor-pointer text-base min-h-[48px]' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<p class="text-center mt-6">
|
||||||
|
<%= link_to '← Voltar para o login', new_user_session_path,
|
||||||
|
class: 'text-sm text-[#f97316] hover:text-orange-400 transition-colors' %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
47
app/views/devise/passwords/new.html.erb
Normal file
47
app/views/devise/passwords/new.html.erb
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<%# app/views/devise/passwords/new.html.erb — Esqueci a senha (estilizada) %>
|
||||||
|
<% content_for :title, 'Recuperar senha' %>
|
||||||
|
|
||||||
|
<div class="min-h-screen flex items-center justify-center p-4">
|
||||||
|
<div class="w-full max-w-sm">
|
||||||
|
|
||||||
|
<div class="text-center mb-8">
|
||||||
|
<div class="w-16 h-16 bg-[#f97316] rounded-2xl flex items-center justify-center mx-auto mb-3">
|
||||||
|
<span class="text-3xl">🔑</span>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-white font-bold text-2xl">Recuperar senha</h1>
|
||||||
|
<p class="text-gray-400 text-sm mt-1">Informe seu e-mail para receber as instruções</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-[#111111] border border-white/10 rounded-2xl p-8">
|
||||||
|
<%= form_with(url: user_password_path, scope: :user, method: :post, data: { turbo: false }) do |f| %>
|
||||||
|
<div class="space-y-5">
|
||||||
|
<div>
|
||||||
|
<%= f.label :email, 'E-mail', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
|
||||||
|
<%= f.email_field :email,
|
||||||
|
autofocus: true, autocomplete: 'email',
|
||||||
|
class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
|
||||||
|
placeholder-gray-600 focus:outline-none focus:border-[#f97316] focus:ring-1
|
||||||
|
focus:ring-[#f97316] transition-colors text-base',
|
||||||
|
placeholder: 'seu@email.com' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= f.submit 'Enviar instruções',
|
||||||
|
class: 'w-full py-3.5 bg-[#f97316] hover:bg-orange-500 text-white font-semibold
|
||||||
|
rounded-xl transition-colors cursor-pointer text-base min-h-[48px]' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<p class="text-center mt-6">
|
||||||
|
<%= link_to '← Voltar para o login', new_user_session_path,
|
||||||
|
class: 'text-sm text-[#f97316] hover:text-orange-400 transition-colors' %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-gray-600 text-xs text-center mt-6">
|
||||||
|
⚠️ O envio de e-mail depende da configuração SMTP no servidor.<br>
|
||||||
|
Sem SMTP, peça ao administrador para redefinir sua senha.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="text-gray-500 text-xs text-center mt-4">Reem Transporte © <%= Date.today.year %></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
d="M13 16V6a1 1 0 00-1-1H4a1 1 0 00-1 1v10a1 1 0 001 1h1m8-1a1 1 0 01-1 1H9m4-1V8a1 1 0 011-1h2.586a1 1 0 01.707.293l3.414 3.414a1 1 0 01.293.707V16a1 1 0 01-1 1h-1m-6-1a1 1 0 001 1h1M5 17a2 2 0 104 0m-4 0a2 2 0 114 0m6 0a2 2 0 104 0m-4 0a2 2 0 114 0"/>
|
d="M13 16V6a1 1 0 00-1-1H4a1 1 0 00-1 1v10a1 1 0 001 1h1m8-1a1 1 0 01-1 1H9m4-1V8a1 1 0 011-1h2.586a1 1 0 01.707.293l3.414 3.414a1 1 0 01.293.707V16a1 1 0 01-1 1h-1m-6-1a1 1 0 001 1h1M5 17a2 2 0 104 0m-4 0a2 2 0 114 0m6 0a2 2 0 104 0m-4 0a2 2 0 114 0"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="text-3xl font-bold text-white">Gade Logística</h1>
|
<h1 class="text-3xl font-bold text-white">Reem Logística</h1>
|
||||||
<p class="text-gray-400 mt-1 text-sm">Sistema de Controle de Custos</p>
|
<p class="text-gray-400 mt-1 text-sm">Sistema de Controle de Custos</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
<%# Login por e-mail %>
|
<%# Login por e-mail %>
|
||||||
<div id="form-email" class="p-8">
|
<div id="form-email" class="p-8">
|
||||||
<%= form_with(url: user_session_path, method: :post) do |f| %>
|
<%= form_with(url: user_session_path, scope: :user, method: :post, data: { turbo: false }) do |f| %>
|
||||||
<div class="space-y-5">
|
<div class="space-y-5">
|
||||||
<div>
|
<div>
|
||||||
<%= f.label :email, 'E-mail', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
|
<%= f.label :email, 'E-mail', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
|
|
||||||
<%# Login por PIN %>
|
<%# Login por PIN %>
|
||||||
<div id="form-pin" class="p-8 hidden">
|
<div id="form-pin" class="p-8 hidden">
|
||||||
<%= form_with(url: user_session_path, method: :post) do |f| %>
|
<%= form_with(url: user_session_path, method: :post, data: { turbo: false }) do |f| %>
|
||||||
<%= f.hidden_field :pin_login, value: '1' %>
|
<%= f.hidden_field :pin_login, value: '1' %>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-center text-gray-600 text-xs mt-6">
|
<p class="text-center text-gray-600 text-xs mt-6">
|
||||||
Gade Hospitalar © <%= Date.today.year %>
|
Reem Transporte © <%= Date.today.year %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<div class="flex items-center gap-3 px-6 py-5 border-b border-[#2a2a2a]">
|
<div class="flex items-center gap-3 px-6 py-5 border-b border-[#2a2a2a]">
|
||||||
<div class="w-9 h-9 bg-orange-500 rounded-lg flex items-center justify-center font-black text-black text-lg">G</div>
|
<div class="w-9 h-9 bg-orange-500 rounded-lg flex items-center justify-center font-black text-black text-lg">G</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="font-bold text-white text-sm leading-tight">Gade Hospitalar</p>
|
<p class="font-bold text-white text-sm leading-tight">Reem Transporte</p>
|
||||||
<p class="text-orange-500 text-xs">Logística</p>
|
<p class="text-orange-500 text-xs">Logística</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="w-7 h-7 bg-orange-500 rounded flex items-center justify-center font-black text-black text-sm">G</div>
|
<div class="w-7 h-7 bg-orange-500 rounded flex items-center justify-center font-black text-black text-sm">G</div>
|
||||||
<span class="font-bold text-white text-sm">Gade Logística</span>
|
<span class="font-bold text-white text-sm">Reem Logística</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-12"></div>
|
<div class="w-12"></div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="csrf-param" content="<%= request_forgery_protection_token %>">
|
<meta name="csrf-param" content="<%= request_forgery_protection_token %>">
|
||||||
<meta name="csrf-token" content="<%= form_authenticity_token %>">
|
<meta name="csrf-token" content="<%= form_authenticity_token %>">
|
||||||
<title><%= content_for?(:title) ? "#{yield(:title)} | Gade Logística" : "Gade Logística" %></title>
|
<title><%= content_for?(:title) ? "#{yield(:title)} | Reem Logística" : "Reem Logística" %></title>
|
||||||
|
|
||||||
<%# Tailwind via CDN em desenvolvimento — compilar em produção %>
|
<%# Tailwind via CDN em desenvolvimento — compilar em produção %>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<%# Logo %>
|
<%# Logo %>
|
||||||
<div class="text-center mb-8">
|
<div class="text-center mb-8">
|
||||||
<div class="w-16 h-16 bg-orange-500 rounded-2xl flex items-center justify-center font-black text-black text-3xl mx-auto mb-3">G</div>
|
<div class="w-16 h-16 bg-orange-500 rounded-2xl flex items-center justify-center font-black text-black text-3xl mx-auto mb-3">G</div>
|
||||||
<h1 class="text-white font-bold text-2xl">Gade Logística</h1>
|
<h1 class="text-white font-bold text-2xl">Reem Logística</h1>
|
||||||
<p class="text-gray-400 text-base mt-1">Digite seu PIN de 4 números</p>
|
<p class="text-gray-400 text-base mt-1">Digite seu PIN de 4 números</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ require "rails/all"
|
|||||||
Bundler.require(*Rails.groups)
|
Bundler.require(*Rails.groups)
|
||||||
require "dotenv/load" if defined?(Dotenv)
|
require "dotenv/load" if defined?(Dotenv)
|
||||||
|
|
||||||
module GadeLogistica
|
module ReemLogistica
|
||||||
class Application < Rails::Application
|
class Application < Rails::Application
|
||||||
config.load_defaults 7.1
|
config.load_defaults 7.1
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,4 @@ Devise.setup do |config|
|
|||||||
config.email_regexp = /\A[^@\s]+@[^@\s]+\z/
|
config.email_regexp = /\A[^@\s]+@[^@\s]+\z/
|
||||||
config.reset_password_within = 6.hours
|
config.reset_password_within = 6.hours
|
||||||
config.sign_out_via = :delete
|
config.sign_out_via = :delete
|
||||||
config.responder.error_status = :unprocessable_entity
|
|
||||||
config.responder.redirect_status = :see_other
|
|
||||||
end
|
end
|
||||||
|
|||||||
6
config/initializers/inflections.rb
Normal file
6
config/initializers/inflections.rb
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Ensina ao Rails os plurais corretos em português.
|
||||||
|
# Sem isso: consolidacao → "consolidacaos" e configuracao → "configuracaos"
|
||||||
|
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||||
|
inflect.irregular 'consolidacao', 'consolidacoes'
|
||||||
|
inflect.irregular 'configuracao', 'configuracoes'
|
||||||
|
end
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
class CreateConsolidacaoMotoristas < ActiveRecord::Migration[7.1]
|
class CreateConsolidacaoMotoristas < ActiveRecord::Migration[7.1]
|
||||||
def change
|
def change
|
||||||
create_table :consolidacao_motoristas do |t|
|
create_table :consolidacao_motoristas do |t|
|
||||||
t.references :consolidacao, null: false, foreign_key: true
|
t.references :consolidacao, null: false, foreign_key: { to_table: :consolidacoes }
|
||||||
t.string :motorista_nome, null: false # driver da tabela existente
|
t.string :motorista_nome, null: false # driver da tabela existente
|
||||||
t.decimal :valor_total, precision: 10, scale: 2, default: 0
|
t.decimal :valor_total, precision: 10, scale: 2, default: 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class CreateConsolidacaoEntregas < ActiveRecord::Migration[7.1]
|
class CreateConsolidacaoEntregas < ActiveRecord::Migration[7.1]
|
||||||
def change
|
def change
|
||||||
create_table :consolidacao_entregas do |t|
|
create_table :consolidacao_entregas do |t|
|
||||||
t.references :consolidacao, null: false, foreign_key: true
|
t.references :consolidacao, null: false, foreign_key: { to_table: :consolidacoes }
|
||||||
t.string :tracking_id, null: false # FK lógico para db_reem_simplerout_2026
|
t.string :tracking_id, null: false # FK lógico para db_reem_simplerout_2026
|
||||||
t.string :motorista_nome, null: false
|
t.string :motorista_nome, null: false
|
||||||
t.integer :tipo, null: false, default: 0
|
t.integer :tipo, null: false, default: 0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# db/seeds.rb
|
# db/seeds.rb
|
||||||
# Dados iniciais do sistema Gade Hospitalar
|
# Dados iniciais do sistema Reem Transporte
|
||||||
# Execute com: bundle exec rails db:seed
|
# Execute com: bundle exec rails db:seed
|
||||||
|
|
||||||
puts "🌱 Criando dados iniciais..."
|
puts "🌱 Criando dados iniciais..."
|
||||||
@@ -30,7 +30,7 @@ configs = [
|
|||||||
{ chave: 'preco_desconto', valor: '15.00', descricao: 'Valor descontado por problema (R$)' },
|
{ chave: 'preco_desconto', valor: '15.00', descricao: 'Valor descontado por problema (R$)' },
|
||||||
{ chave: 'notificacao_whatsapp', valor: 'false', descricao: 'Notificações via WhatsApp (true/false)' },
|
{ chave: 'notificacao_whatsapp', valor: 'false', descricao: 'Notificações via WhatsApp (true/false)' },
|
||||||
{ chave: 'notificacao_email', valor: 'false', descricao: 'Notificações via e-mail (true/false)' },
|
{ chave: 'notificacao_email', valor: 'false', descricao: 'Notificações via e-mail (true/false)' },
|
||||||
{ chave: 'empresa_nome', valor: 'Gade Hospitalar', descricao: 'Nome da empresa exibido no sistema' },
|
{ chave: 'empresa_nome', valor: 'Reem Transporte', descricao: 'Nome da empresa exibido no sistema' },
|
||||||
]
|
]
|
||||||
|
|
||||||
configs.each do |cfg|
|
configs.each do |cfg|
|
||||||
@@ -52,9 +52,9 @@ puts " ⚠️ ALTERE A SENHA DO ADMIN NO PRIMEIRO ACESSO!"
|
|||||||
{ nome: 'Maria Santos', pin: '5678' },
|
{ nome: 'Maria Santos', pin: '5678' },
|
||||||
{ nome: 'Carlos Souza', pin: '9012' },
|
{ nome: 'Carlos Souza', pin: '9012' },
|
||||||
].each do |m|
|
].each do |m|
|
||||||
User.find_or_create_by!(nome: m[:nome], role: :motorista) do |u|
|
u = User.find_or_initialize_by(nome: m[:nome], role: :motorista)
|
||||||
u.pin_code = m[:pin]
|
u.pin_code = m[:pin]
|
||||||
u.ativo = true
|
u.ativo = true
|
||||||
end
|
u.save!(validate: false)
|
||||||
puts " ✅ Motorista: #{m[:nome]} / PIN: #{m[:pin]}"
|
puts " ✅ Motorista: #{m[:nome]} / PIN: #{m[:pin]}"
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user