Compare commits

..

No commits in common. "bed4fee11b4bb3637db191284bd84ab20624257b" and "2e7b03e9ef96119b196cdbf8e09ff918a3ad3a4a" have entirely different histories.

16 changed files with 83 additions and 286 deletions

View File

@ -1,3 +0,0 @@
log/*
tmp/*
Dockerfile

View File

@ -1,12 +0,0 @@
FROM ruby:3.1.2-alpine
RUN apk add build-base libpq-dev tzdata
WORKDIR /app
ADD Gemfile* ./
RUN bundle
ADD . ./
CMD ["./entrypoint.sh"]

View File

@ -14,9 +14,6 @@ gem "puma", "~> 5.0"
gem "pundit" gem "pundit"
gem "rails", "~> 7.0.3" gem "rails", "~> 7.0.3"
gem "ransack" gem "ransack"
gem "nokogiri"
gem "rubyzip"
gem "combine_pdf"
group :development, :test do group :development, :test do
gem "debug", platforms: %i[ mri mingw x64_mingw ] gem "debug", platforms: %i[ mri mingw x64_mingw ]

View File

@ -82,9 +82,6 @@ GEM
bcrypt (>= 3.1.1) bcrypt (>= 3.1.1)
email_validator (~> 2.0) email_validator (~> 2.0)
railties (>= 5.0) railties (>= 5.0)
combine_pdf (1.0.22)
matrix
ruby-rc4 (>= 0.1.5)
concurrent-ruby (1.1.10) concurrent-ruby (1.1.10)
crass (1.0.6) crass (1.0.6)
debug (1.5.0) debug (1.5.0)
@ -118,7 +115,6 @@ GEM
mail (2.7.1) mail (2.7.1)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
marcel (1.0.2) marcel (1.0.2)
matrix (0.4.2)
method_source (1.0.0) method_source (1.0.0)
mini_mime (1.1.2) mini_mime (1.1.2)
minitest (5.16.0) minitest (5.16.0)
@ -192,8 +188,6 @@ GEM
io-console (~> 0.5) io-console (~> 0.5)
request_store (1.5.1) request_store (1.5.1)
rack (>= 1.4) rack (>= 1.4)
ruby-rc4 (0.1.5)
rubyzip (2.3.2)
strscan (3.0.3) strscan (3.0.3)
thor (1.2.1) thor (1.2.1)
timecop (0.9.5) timecop (0.9.5)
@ -216,11 +210,9 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
bootsnap bootsnap
clearance clearance
combine_pdf
debug debug
dotenv-rails dotenv-rails
importmap-rails importmap-rails
nokogiri
paper_trail paper_trail
pg (~> 1.1) pg (~> 1.1)
propshaft propshaft
@ -228,7 +220,6 @@ DEPENDENCIES
pundit pundit
rails (~> 7.0.3) rails (~> 7.0.3)
ransack ransack
rubyzip
timecop timecop
web-console web-console

View File

@ -23,5 +23,3 @@ ul {
} }
li { padding: 5px 0px; } li { padding: 5px 0px; }
table.noborder { border: 0; margin: 0; }

View File

@ -1,38 +0,0 @@
module MemberFilter
extend ActiveSupport::Concern
def filtered_members
members = Member.all.order(sort_params.merge(number: :asc))
filters = params.permit(:prefers_postal, :display_name, :email, :identification_number, status: [], category: [])
Rails.logger.info filters
status = filters.fetch(:status, []) - ['any', '']
category = filters.fetch(:category, []) - ['any', '']
members = members.where(prefers_postal: true) if filters[:prefers_postal] == 'yes'
members = members.where(prefers_postal: false) if filters[:prefers_postal] == 'no'
members = members.where(status: status) if status != []
members = members.where(category: category) if category != []
members.ransack(
display_name_i_cont: filters[:display_name],
email_i_cont: filters[:email],
identification_number_i_cont: filters[:identification_number],
).result
end
def sort_params
field, direction = params.fetch(:sort, "").split(".")
directions = %w[ asc desc ]
fields = %w[ number expires_on joined_on email status display_name ]
if directions.include?(direction) && fields.include?(field)
{ field => direction }
else
{ number: :asc }
end
end
end

View File

@ -1,14 +0,0 @@
class LettersController < ApplicationController
before_action :require_login
include MemberFilter
# POST /letters
def create
members = filtered_members
pdf = Letters.generate(params[:template], members)
send_data pdf, filename: "members.pdf", type: "application/pdf"
end
end

View File

@ -3,11 +3,13 @@ class MembersController < ApplicationController
before_action :set_member, only: %i[ show edit update delete destroy ] before_action :set_member, only: %i[ show edit update delete destroy ]
helper_method :sort_params helper_method :sort_params
include MemberFilter
# GET /members # GET /members
def index def index
@members = filtered_members params.delete(:status) if params[:status] == 'any'
params.delete(:category) if params[:category] == 'any'
@members = Member.all.order(sort_params.merge(number: :asc))
@members = @members.ransack(display_name_or_email_or_identification_number_i_cont: params[:q], status_cont: params[:status], category_cont: params[:category]).result
end end
# GET /members/1 # GET /members/1
@ -66,4 +68,17 @@ class MembersController < ApplicationController
def member_params def member_params
params.fetch(:member, {}).permit(:display_name, :email, :identification_number, :category, :address, :joined_on, :expires_on, :wants_mailing_list, :prefers_postal) params.fetch(:member, {}).permit(:display_name, :email, :identification_number, :category, :address, :joined_on, :expires_on, :wants_mailing_list, :prefers_postal)
end end
def sort_params
field, direction = params.fetch(:sort, "").split(".")
directions = %w[ asc desc ]
fields = %w[ number expires_on joined_on email status display_name ]
if directions.include?(direction) && fields.include?(field)
{ field => direction }
else
{ number: :asc }
end
end
end end

View File

@ -1,19 +0,0 @@
class NotificationsController < ApplicationController
before_action :require_login
before_action :set_notification
# POST /notifications/1/deliver
def deliver
@notification.deliver!
redirect_to @notification.member
end
private
# Use callbacks to share common setup or constraints between actions.
def set_notification
@notification = Notification.find(params[:id])
end
end

View File

@ -1,68 +0,0 @@
require 'zip'
require 'nokogiri'
require 'combine_pdf'
module Letters
def self.apply_template(io, params)
Zip::OutputStream.write_buffer do |out|
Zip::File.open(io) do |zip|
zip.each do |entry|
pp entry.name
out.put_next_entry(entry.name)
if entry.name == "content.xml"
out.write apply_template_xml(entry.get_input_stream.read, params)
elsif !entry.directory?
out.write entry.get_input_stream.read
end
end
end
end
end
def self.apply_template_xml(xml, params)
doc = Nokogiri::XML(xml)
doc.xpath("//*[contains(text(), 'DISPLAY_NAME')]").each do |node|
node.content = node.content.gsub("DISPLAY_NAME", params["DISPLAY_NAME"])
end
address_lines = params['ADDRESS'].split("\n")
doc.xpath("//*[contains(text(), 'ADDRESS')]").each do |node|
newnodes = [node] + address_lines[1..].map do |line|
node.clone.tap do |c|
c.content = c.content.gsub("ADDRESS", line)
end
end
node.content = node.content.gsub("ADDRESS", address_lines.first)
newnodes.each_cons(2) do |p, n|
p.add_next_sibling(n)
end
end
doc.to_xml(save_with: 0)
end
def self.generate(template, members)
Dir.mktmpdir do |directory|
members.each do |member|
odt = apply_template(template, {
"DISPLAY_NAME" => member.display_name,
"ADDRESS" => member.address,
})
File.open("#{directory}/#{member.number}.odt", "wb") { |out| out.write(odt.string) }
end
`libreoffice --convert-to pdf --outdir #{directory} #{directory}/*.odt`
pdf = CombinePDF.new
Dir["#{directory}/*.pdf"].each do |file|
pdf << CombinePDF.load(file)
end
pdf.to_pdf
end
end
end

View File

@ -1,5 +1,4 @@
class NotificationMailer < ApplicationMailer class NotificationMailer < ApplicationMailer
before_action :set_notification
# Subject can be set in your I18n file at config/locales/en.yml # Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup: # with the following lookup:
@ -7,32 +6,63 @@ class NotificationMailer < ApplicationMailer
# en.notification_mailer.expiration_in_60d.subject # en.notification_mailer.expiration_in_60d.subject
# #
def expiration_in_60d def expiration_in_60d
mail to: @notification.member.email
end
def expiration_in_30d
mail to: @notification.member.email
end
def expired
mail to: @notification.member.email
end
def expired_30d_ago
mail to: @notification.member.email
end
def expired_60d_ago
mail to: @notification.member.email
end
def cancelled
mail to: @notification.member.email
end
private
def set_notification
@notification = params[:notification] @notification = params[:notification]
@link = @notification.member.regular_ifthenpay_link
mail to: params[:notification].member.email
end
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.notification_mailer.expiration_in_30d.subject
#
def expiration_in_30d
@notification = params[:notification]
mail to: params[:notification].member.email
end
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.notification_mailer.expired.subject
#
def expired
@notification = params[:notification]
mail to: params[:notification].member.email
end
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.notification_mailer.expired_30d_ago.subject
#
def expired_30d_ago
@notification = params[:notification]
mail to: params[:notification].member.email
end
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.notification_mailer.expired_60d_ago.subject
#
def expired_60d_ago
@notification = params[:notification]
mail to: params[:notification].member.email
end
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.notification_mailer.cancelled.subject
#
def cancelled
@notification = params[:notification]
mail to: params[:notification].member.email
end end
end end

View File

@ -2,77 +2,18 @@
<h1><%= t 'members.index.title' %></h1> <h1><%= t 'members.index.title' %></h1>
<p><%= link_to t('members.index.actions.new'), new_member_path %></p> <%= link_to t('members.index.actions.new'), new_member_path %>
<%= form_with url: members_path, method: :get do |form| %> <%= form_with url: members_path, method: :get do |form| %>
<fieldset> <%= form.text_field :q %>
<legend>Filtrar</legend> <%= form.select :status, %w[ any active passive pending expired cancelled ], selected: params[:status] %>
<%= form.select :category, %w[ any student employed unemployed retired ], selected: params[:category], multiple: true %>
<table class='noborder lined'> <%= form.submit 'Search', name: '' %>
<tr> <% if params[:q].present? || params[:status].present? || params[:category].present? %>
<td><%= t 'members.attributes.display_name' %></td> <%= link_to t('members.index.actions.clear_search'), members_path %>
<td><%= form.text_field :display_name, value: params[:display_name] %></td> <% end %>
</tr>
<tr>
<td><%= t 'members.attributes.email' %></td>
<td><%= form.text_field :email, value: params[:email] %></td>
</tr>
<tr>
<td><%= t 'members.attributes.identification_number' %></td>
<td><%= form.text_field :identification_number, value: params[:identification_number] %></td>
</tr>
<tr>
<td><%= t 'members.attributes.status' %></td>
<td><%= form.select :status, %w[ any active passive pending expired cancelled ], { selected: params[:status] }, { multiple: true } %></td>
</tr>
<tr>
<td><%= t 'members.attributes.category' %></td>
<td>
<%= form.select :category, %w[ any student employed unemployed retired ], { selected: params[:category] }, { multiple: true } %>
</td>
</tr>
<tr>
<td><%= t 'members.attributes.prefers_postal' %></td>
<td>
<%= form.select :prefers_postal, %w[ any yes no ], selected: params[:prefers_postal] %>
</td>
</tr>
</td>
</tr>
</table>
<p><%= form.submit 'Search', name: '' %></p>
<% if params[:q].present? || params[:status].present? || params[:category].present? %>
<%= link_to t('members.index.actions.clear_search'), members_path %>
<% end %>
</fieldset>
<% end %> <% end %>
<br>
<%= form_with url: letters_path do |form| %>
<fieldset>
<legend>Gerar PDF</legend>
<% (params.fetch(:status, []) - ['any', '']).each do |status| %>
<%= form.hidden_field 'status[]', value: status %>
<% end %>
<% (params.fetch(:category, []) - ['any', '']).each do |category| %>
<%= form.hidden_field 'category[]', value: category %>
<% end %>
<%= form.hidden_field :display_name, value: params[:display_name] %>
<%= form.hidden_field :email, value: params[:email] %>
<%= form.hidden_field :identification_number, value: params[:identification_number] %>
<%= form.hidden_field :prefers_postal, value: params[:prefers_postal] %>
<%= form.file_field :template, required: true %>
<%= form.submit 'Generate PDF' %>
</fieldset>
<% end %>
<table class='zebra'> <table class='zebra'>
<tr> <tr>
<th><%= link_to_current_with_sort t('members.attributes.number'), 'number.asc' %></th> <th><%= link_to_current_with_sort t('members.attributes.number'), 'number.asc' %></th>

View File

@ -54,20 +54,12 @@
<th><%= t('notifications.attributes.to_be_sent_on') %></th> <th><%= t('notifications.attributes.to_be_sent_on') %></th>
<th><%= t('notifications.attributes.template') %></th> <th><%= t('notifications.attributes.template') %></th>
<th><%= t('notifications.attributes.status') %></th> <th><%= t('notifications.attributes.status') %></th>
<th><%= t('members.show.contribution_actions') %></th>
</tr> </tr>
<% @member.notifications.order(to_be_sent_on: :desc).each do |notification| %> <% @member.notifications.order(to_be_sent_on: :desc).each do |notification| %>
<tr> <tr>
<td><%= notification.to_be_sent_on %></td> <td><%= notification.to_be_sent_on %></td>
<td><code><%= notification.template %></code></td> <td><code><%= notification.template %></code></td>
<td><%= notification_status(notification.status) %></td> <td><%= notification_status(notification.status) %></td>
<td>
<% if notification.status == 'scheduled' %>
<%= form_with url: deliver_notification_path(notification) do |form| %>
<%= form.submit t('members.show.actions.deliver_notification') %>
<% end %>
<% end %>
</td>
</tr> </tr>
<% end %> <% end %>
</table> </table>

View File

@ -30,10 +30,8 @@ pt:
edit: "Editar detalhes" edit: "Editar detalhes"
edit_contribution: "Editar" edit_contribution: "Editar"
delete_contribution: "Apagar" delete_contribution: "Apagar"
deliver_notification: "Enviar agora"
contribution_history: "Histórico de contribuições" contribution_history: "Histórico de contribuições"
notifications: "Notificações por correio electrónico" notifications: "Notificações por correio electrónico"
contribution_actions: "Acções"
edit: edit:
title: "Editar detalhes de membro" title: "Editar detalhes de membro"
actions: actions:

View File

@ -19,12 +19,5 @@ Rails.application.routes.draw do
end end
end end
resources :notifications, only: [] do
member do
post :deliver
end
end
resource :board, only: [:edit, :update] resource :board, only: [:edit, :update]
resource :letters, only: [:create]
end end

View File

@ -1,4 +0,0 @@
#!/usr/bin/env sh
bin/rails db:migrate &&
bin/rails server