parent
f97b01dd43
commit
57e976ef96
56 changed files with 1343 additions and 35 deletions
@ -1 +1,7 @@ |
||||
/* Application styles */ |
||||
|
||||
table { border: 1px solid black; } |
||||
|
||||
td, th { padding: 10px; } |
||||
|
||||
.new_contribution_form { max-width: 600px; } |
||||
|
@ -0,0 +1,54 @@ |
||||
class ContributionsController < ApplicationController |
||||
before_action :set_member, only: %i[ new create ] |
||||
before_action :set_contribution, only: %i[ edit ] |
||||
|
||||
# GET /members/new |
||||
def new |
||||
@contribution = Contribution.new |
||||
end |
||||
|
||||
# GET /members/1/edit |
||||
def edit |
||||
end |
||||
|
||||
# POST /members |
||||
def create |
||||
@contribution = @member.contributions.build(contribution_params) |
||||
|
||||
Contribution.transaction do |
||||
if @contribution.save |
||||
@member.handle_new_contribution(@contribution, params.dig(:contribution, :overriden_expires_on)) |
||||
@member.reset_status! |
||||
|
||||
redirect_to @member, notice: "Contribution was successfully created." |
||||
else |
||||
render :new, status: :unprocessable_entity |
||||
end |
||||
end |
||||
end |
||||
|
||||
## PATCH/PUT /members/1 |
||||
#def update |
||||
# if @member.update(member_params) |
||||
# redirect_to @member, notice: "Member was successfully updated." |
||||
# else |
||||
# render :edit, status: :unprocessable_entity |
||||
# end |
||||
#end |
||||
|
||||
private |
||||
# Use callbacks to share common setup or constraints between actions. |
||||
def set_member |
||||
@member = Member.find(params[:member_id]) |
||||
end |
||||
|
||||
def set_contribution |
||||
@contribution = Contribution.find(params[:id]) |
||||
end |
||||
|
||||
# Only allow a list of trusted parameters through. |
||||
def contribution_params |
||||
params.fetch(:contribution, {}).permit(:eurocents, :payment_method, :payment_on, :payment_reference) |
||||
end |
||||
end |
||||
|
@ -0,0 +1,72 @@ |
||||
class MembersController < ApplicationController |
||||
before_action :set_member, only: %i[ show edit update destroy ] |
||||
helper_method :sort_params |
||||
|
||||
# GET /members |
||||
def index |
||||
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 |
||||
|
||||
# GET /members/1 |
||||
def show |
||||
end |
||||
|
||||
# GET /members/new |
||||
def new |
||||
@member = Member.new |
||||
end |
||||
|
||||
# GET /members/1/edit |
||||
def edit |
||||
end |
||||
|
||||
# POST /members |
||||
def create |
||||
@member = Member.new(member_params) |
||||
|
||||
if @member.save |
||||
@member.reset_status! |
||||
redirect_to @member, notice: "Member was successfully created." |
||||
else |
||||
render :new, status: :unprocessable_entity |
||||
end |
||||
end |
||||
|
||||
# PATCH/PUT /members/1 |
||||
def update |
||||
if @member.update(member_params) |
||||
@member.reload.reset_status! |
||||
redirect_to @member, notice: "Member was successfully updated." |
||||
else |
||||
render :edit, status: :unprocessable_entity |
||||
end |
||||
end |
||||
|
||||
private |
||||
# Use callbacks to share common setup or constraints between actions. |
||||
def set_member |
||||
@member = Member.find(params[:id]) |
||||
end |
||||
|
||||
# Only allow a list of trusted parameters through. |
||||
def member_params |
||||
params.fetch(:member, {}).permit(:display_name, :email, :identification_number, :category, :address, :joined_on, :expires_on) |
||||
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 |
@ -1,2 +1,5 @@ |
||||
module ApplicationHelper |
||||
def member_status(status) |
||||
t("members.status.#{status}") |
||||
end |
||||
end |
||||
|
@ -0,0 +1,24 @@ |
||||
module MembersHelper |
||||
def link_to_current_with_sort text, default_sort |
||||
current_sort = stringify(sort_params) |
||||
|
||||
pp [default_sort, current_sort] |
||||
|
||||
if default_sort == current_sort |
||||
link_to text, members_path(sort: invert_sort_order(current_sort)) |
||||
else |
||||
link_to text, members_path(sort: default_sort) |
||||
end |
||||
|
||||
end |
||||
|
||||
private |
||||
|
||||
def stringify(sort) |
||||
"#{sort.keys.first.to_s}.#{sort.values.first.to_s}" |
||||
end |
||||
|
||||
def invert_sort_order(sort) |
||||
sort.sub(/\.(asc|desc)$/) { |x| x == '.asc' ? '.desc' : '.asc' } |
||||
end |
||||
end |
@ -0,0 +1,19 @@ |
||||
require 'net/http' |
||||
|
||||
module IfThenPay |
||||
def self.generate_gateway_link(id:, amount:, description:) |
||||
response = Net::HTTP.post( |
||||
URI("https://ifthenpay.com/api/gateway/paybylink/#{ENV['IFTHENPAY_KEY']}"), |
||||
JSON.generate({ |
||||
id: id, |
||||
amount: amount.to_s, |
||||
description: description.to_s, |
||||
"lang": "pt", |
||||
"expiredate": "", |
||||
"accounts": ENV['IFTHENPAY_ACCOUNTS'], |
||||
}) |
||||
) |
||||
|
||||
JSON.parse(response.body) |
||||
end |
||||
end |
@ -1,4 +1,4 @@ |
||||
class ApplicationMailer < ActionMailer::Base |
||||
default from: "from@example.com" |
||||
default from: email_address_with_name(ENV['SMTP_FROM_ADDRESS'], ENV['SMTP_FROM_NAME']) |
||||
layout "mailer" |
||||
end |
||||
|
@ -0,0 +1,68 @@ |
||||
class NotificationMailer < ApplicationMailer |
||||
|
||||
# Subject can be set in your I18n file at config/locales/en.yml |
||||
# with the following lookup: |
||||
# |
||||
# en.notification_mailer.expiration_in_60d.subject |
||||
# |
||||
def expiration_in_60d |
||||
@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.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 |
@ -0,0 +1,3 @@ |
||||
class Contribution < ApplicationRecord |
||||
belongs_to :member |
||||
end |
@ -0,0 +1,88 @@ |
||||
class Member < ApplicationRecord |
||||
has_many :contributions |
||||
has_many :notifications |
||||
|
||||
def cancelled_on |
||||
expires_on + 90.days |
||||
end |
||||
|
||||
def reset_status! |
||||
update(status: expected_status) |
||||
end |
||||
|
||||
def expected_status |
||||
if joined_on.nil? |
||||
:pending |
||||
elsif (joined_on + 6.months).future? |
||||
:passive |
||||
elsif expires_on.future? |
||||
:active |
||||
elsif cancelled_on.future? |
||||
:expired |
||||
else |
||||
:cancelled |
||||
end |
||||
end |
||||
|
||||
def handle_new_contribution(contribution, overriden_expires_on) |
||||
if joined_on.nil? |
||||
self.joined_on = contribution.payment_on |
||||
self.expires_on = overriden_expires_on.presence || (joined_on + 1.year) |
||||
else |
||||
self.expires_on = overriden_expires_on.presence || expires_on + 1.year |
||||
end |
||||
|
||||
save! |
||||
end |
||||
|
||||
def regenerate_notifications |
||||
notifications.where(status: 'scheduled').delete_all |
||||
|
||||
return if expires_on.nil? |
||||
|
||||
[ |
||||
{ to_be_sent_on: expires_on - 90.days, template: "expiration_in_60d" }, |
||||
{ to_be_sent_on: expires_on - 30.days, template: "expiration_in_30d" }, |
||||
{ to_be_sent_on: expires_on + 0.days, template: "expired" }, |
||||
{ to_be_sent_on: expires_on + 30.days, template: "expired_30d_ago" }, |
||||
{ to_be_sent_on: expires_on + 60.days, template: "expired_60d_ago" }, |
||||
{ to_be_sent_on: expires_on + 90.days, template: "cancelled" }, |
||||
].reject { |n| n[:to_be_sent_on].past? }.each do |n| |
||||
notifications.create(n.merge(status: "scheduled")) |
||||
end |
||||
end |
||||
|
||||
def generate_missing_ifthenpay_links! |
||||
self.regular_ifthenpay_link = IfThenPay.generate_gateway_link( |
||||
id: number, |
||||
amount: "30.00", |
||||
description: "Quotas ANSOL", |
||||
) unless self.regular_ifthenpay_link.present? |
||||
|
||||
self.reduced_ifthenpay_link = IfThenPay.generate_gateway_link( |
||||
id: number, |
||||
amount: "6.00", |
||||
description: "Quotas ANSOL", |
||||
) unless self.reduced_ifthenpay_link.present? |
||||
|
||||
save! |
||||
end |
||||
|
||||
def self.reset_all_status! |
||||
Member.all.each do |member| |
||||
member.reset_status! |
||||
end |
||||
end |
||||
|
||||
def self.generate_all_missing_ifthenpay_links! |
||||
Member.all.each do |member| |
||||
member.generate_missing_ifthenpay_links! |
||||
end |
||||
end |
||||
|
||||
def self.regenerate_all_notifications |
||||
Member.all.each do |member| |
||||
member.regenerate_notifications |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,20 @@ |
||||
class Notification < ApplicationRecord |
||||
belongs_to :member |
||||
|
||||
scope :scheduled_for_today, ->() { where(status: 'scheduled', to_be_sent_on: Date.today) } |
||||
|
||||
def self.send_scheduled_for_today |
||||
scheduled_for_today.each do |n| |
||||
n.deliver! |
||||
end |
||||
end |
||||
|
||||
def deliver! |
||||
# actually send the email. |
||||
NotificationMailer.with(notification: self).send(template).deliver_now! |
||||
|
||||
update(status: 'sent', sent_at: Time.current) |
||||
rescue |
||||
# TODO: do something about failures |
||||
end |
||||
end |
@ -0,0 +1,22 @@ |
||||
editando contriboot |
||||
|
||||
<%= form_with(model: @contribution) do |form| %> |
||||
<table> |
||||
<tr> |
||||
<td>Amount</td> |
||||
<td>€<%= @contribution.eurocents %></td> |
||||
</tr> |
||||
<tr> |
||||
<td>Payment date</td> |
||||
<td>€<%= @contribution.payment_on %></td> |
||||
</tr> |
||||
<tr> |
||||
<td>Payment method</td> |
||||
<td><%= @contribution.payment_method %></td> |
||||
</tr> |
||||
<tr> |
||||
<td>Payment reference</td> |
||||
<td><%= @contribution.payment_reference %></td> |
||||
</tr> |
||||
</table> |
||||
<% end %> |
@ -0,0 +1,48 @@ |
||||
<h1>Registering contribution for <%= @member.display_name %></h1> |
||||
|
||||
<table> |
||||
<tr><td>Member number</td><td><%= @member.number %></td></tr> |
||||
<tr><td>Joined on</td><td><%= @member.joined_on %></td></tr> |
||||
<tr><td>Expires on</td><td><%= @member.expires_on %></td></tr> |
||||
</table> |
||||
|
||||
|
||||
<%= form_with(model: [@member, @contribution]) do |form| %> |
||||
<table class="new_contribution_form"> |
||||
<tr> |
||||
<td><label for="contribution_eurocents">Amount</label></td> |
||||
<td><%= form.number_field :eurocents %></td> |
||||
</tr> |
||||
<tr> |
||||
<td><label for="contribution_payment_on">Payment date</label></td> |
||||
<td><%= form.date_field :payment_on %></td> |
||||
</tr> |
||||
<tr> |
||||
<td><label for="contribution_payment_method">Payment method</label></td> |
||||
<td><%= form.select :payment_method, %w[iban mbway multibanco] %></td> |
||||
</tr> |
||||
<tr> |
||||
<td><label for="contribution_payment_reference">Referência</label></td> |
||||
<td><%= form.text_field :payment_reference %></td> |
||||
</tr> |
||||
<tr> |
||||
<td colspan=2> |
||||
Adding a contribution will automatically bump the membership expiration |
||||
date by one year. If it's the first contribution for this member, the |
||||
join date will be set to the payment date and the expiration date one |
||||
year after that. You can override the expiration date by setting a date |
||||
below: |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td><label for="member_expires_on">Nova data de expiração</label></td> |
||||
<td> |
||||
<%= form.date_field :overriden_expires_on %> |
||||
</td> |
||||
</tr> |
||||
</table> |
||||
|
||||
<div> |
||||
<%= form.submit %> |
||||
</div> |
||||
<% end %> |
@ -0,0 +1,2 @@ |
||||
<div id="<%= dom_id member %>"> |
||||
</div> |
@ -0,0 +1,59 @@ |
||||
<h1><%= t('members.edit.title') %></h1> |
||||
|
||||
<%= form_with(model: @member) do |form| %> |
||||
<% if @member.errors.any? %> |
||||
<div style="color: red"> |
||||
<h2><%= pluralize(@member.errors.count, "error") %> prohibited this member from being saved:</h2> |
||||
|
||||
<ul> |
||||
<% @member.errors.each do |error| %> |
||||
<li><%= error.full_message %></li> |
||||
<% end %> |
||||
</ul> |
||||
</div> |
||||
<% end %> |
||||
|
||||
<table> |
||||
<tr> |
||||
<td><label><%= t('members.attributes.display_name') %></label></td> |
||||
<td><%= form.text_field :display_name, required: true %></label></td> |
||||
</tr> |
||||
<tr> |
||||
<td><label><%= t('members.attributes.email') %></label></td> |
||||
<td><%= form.email_field :email, required: true %></label></td> |
||||
</tr> |
||||
<tr> |
||||
<td><label><%= t('members.attributes.category') %></label></td> |
||||
<td><%= form.select :category, %w{student retired unemployed employed} %></label></td> |
||||
</tr> |
||||
<tr> |
||||
<td><label><%= t('members.attributes.identification_number') %></label></td> |
||||
<td><%= form.text_field :identification_number %></label></td> |
||||
</tr> |
||||
<tr> |
||||
<td><label><%= t('members.attributes.address') %></label></td> |
||||
<td><%= form.text_area :address %></label></td> |
||||
</tr> |
||||
<tr> |
||||
<td colspan="2"><%= t('members.edit.edit_dates_warning') %></td> |
||||
</tr> |
||||
<tr> |
||||
<td><label for="member_joined_on"><%= t('members.attributes.joined_on') %></label></td> |
||||
<td><%= form.date_field :joined_on %></td> |
||||
</tr> |
||||
<tr> |
||||
<td><label for="member_expires_on"><%= t('members.attributes.expires_on') %></label></td> |
||||
<td><%= form.date_field :expires_on %></td> |
||||
</tr> |
||||
</table> |
||||
|
||||
<div> |
||||
<%= form.submit %> |
||||
</div> |
||||
<% end %> |
||||
|
||||
<br> |
||||
|
||||
<div> |
||||
<%= link_to t('members.edit.actions.back_to_show'), @member %> |
||||
</div> |
@ -0,0 +1,44 @@ |
||||
<p style="color: green"><%= notice %></p> |
||||
|
||||
<h1><%= t 'members.index.title' %></h1> |
||||
|
||||
<%= link_to t('members.index.actions.new'), new_member_path %> |
||||
|
||||
<%= form_with url: members_path, method: :get do |form| %> |
||||
<%= form.text_field :q %> |
||||
<%= 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 %> |
||||
<%= form.submit 'Search', name: '' %> |
||||
<% if params[:q].present? || params[:status].present? || params[:category].present? %> |
||||
<%= link_to t('members.index.actions.clear_search'), members_path %> |
||||
<% end %> |
||||
<% end %> |
||||
|
||||
<table id="members"> |
||||
<thead> |
||||
<tr> |
||||
<th><%= link_to_current_with_sort t('members.attributes.number'), 'number.asc' %></th> |
||||
<th><%= link_to_current_with_sort t('members.attributes.status'), 'status.asc' %></th> |
||||
<th><%= link_to_current_with_sort t('members.attributes.email'), 'email.asc' %></th> |
||||
<th><%= link_to_current_with_sort t('members.attributes.display_name'), 'display_name.asc' %></th> |
||||
<th><%= link_to_current_with_sort t('members.attributes.joined_on'), 'joined_on.asc' %></th> |
||||
<th><%= link_to_current_with_sort t('members.attributes.expires_on'), 'expires_on.asc' %></th> |
||||
<th><%= t('members.index.actions.title') %></th> |
||||
</tr> |
||||
</thead> |
||||
<% @members.each do |member| %> |
||||
<tr id="<%= dom_id member %>"> |
||||
<td><%= member.number %></td> |
||||
<td><%= member_status(member.status) %></td> |
||||
<td><%= member.email %></td> |
||||
<td><%= member.display_name %></td> |
||||
<td><%= member.joined_on %></td> |
||||
<td><%= member.expires_on %></td> |
||||
<td> |
||||
<%= link_to t('members.index.actions.show'), member %> | |
||||
<%= link_to t('members.index.actions.edit'), edit_member_path(member) %> | |
||||
<%= link_to t('members.index.actions.new_contribution'), new_member_contribution_path(member) %> |
||||
</td> |
||||
</tr> |
||||
<% end %> |
||||
</table> |
@ -0,0 +1,27 @@ |
||||
<h1><%= t('members.new.title') %></h1> |
||||
|
||||
<%= form_with(model: @member) do |form| %> |
||||
<% if @member.errors.any? %> |
||||
<div style="color: red"> |
||||
<h2><%= pluralize(@member.errors.count, "error") %> prohibited this member from being saved:</h2> |
||||
|
||||
<ul> |
||||
<% @member.errors.each do |error| %> |
||||
<li><%= error.full_message %></li> |
||||
<% end %> |
||||
</ul> |
||||
</div> |
||||
<% end %> |
||||
|
||||
<table> |
||||
<tr><td><label><%= t('members.attributes.display_name') %></label></td><td><%= form.text_field :display_name, required: true %></label></td></tr> |
||||
<tr><td><label><%= t('members.attributes.email') %></label></td><td><%= form.email_field :email, required: true %></label></td></tr> |
||||
<tr><td><label><%= t('members.attributes.category') %></label></td><td><%= form.select :category, %w{student retired unemployed employed} %></label></td></tr> |
||||
<tr><td><label><%= t('members.attributes.identification_number') %></label></td><td><%= form.text_field :identification_number %></label></td></tr> |
||||
<tr><td><label><%= t('members.attributes.address') %></label></td><td><%= form.text_area :address %></label></td></tr> |
||||
</table> |
||||
|
||||
<div> |
||||
<%= form.submit %> |
||||
</div> |
||||
<% end %> |
@ -0,0 +1,40 @@ |
||||
<p style="color: green"><%= notice %></p> |
||||
|
||||
<h1><%= t('members.show.title') %></h1> |
||||
|
||||
<table> |
||||
<tr><td><%= t('members.attributes.display_name') %></td><td><%= @member.display_name %></td></tr> |
||||
<tr><td><%= t('members.attributes.email') %></td><td><%= @member.email %></td></tr> |
||||
<tr><td><%= t('members.attributes.category') %></td><td><%= @member.category %></td></tr> |
||||
<tr><td><%= t('members.attributes.identification_number') %></td><td><%= @member.identification_number %></td></tr> |
||||
<tr><td><%= t('members.attributes.address') %></td><td><%= simple_format @member.address %></td></tr> |
||||
<tr><td><%= t('members.attributes.joined_on') %></td><td><%= @member.joined_on %></td></tr> |
||||
<tr><td><%= t('members.attributes.expires_on') %></td><td><%= @member.expires_on %></td></tr> |
||||
<tr><td><%= t('members.attributes.status') %></td><td><%= @member.status %></td></tr> |
||||
</table> |
||||
|
||||
<div> |
||||
<%= link_to t('members.show.actions.edit'), edit_member_path(@member) %> |
||||
</div> |
||||
|
||||
<h2><%= t('members.show.contribution_history') %></h2> |
||||
|
||||
<table> |
||||
<tr> |
||||
<th>Payment date</th> |
||||
<th>Payment method</th> |
||||
<th>Payment reference</th> |
||||
<th>Amount</th> |
||||
</tr> |
||||
<% @member.contributions.each do |contribution| %> |
||||
<tr> |
||||
<td><%= contribution.payment_on %></td> |
||||
<td><%= contribution.payment_method %></td> |
||||
<td><%= contribution.payment_reference %></td> |
||||
<td>€<%= contribution.eurocents %></td> |
||||
<td> |
||||
<%= link_to t('members.show.actions.edit_contribution'), edit_contribution_path(contribution) %> |
||||
</td> |
||||
</tr> |
||||
<% end %> |
||||
</table> |
@ -0,0 +1,22 @@ |
||||
<p> |
||||
Aceitamos pagamento via transferência bancária, referência multibanco ou |
||||
MBWAY: |
||||
</p> |
||||
|
||||
<ul> |
||||
<li>Valor: 30.00€</li> |
||||
<li>Transferência bancária: <strong>PT50 0035 2178 00027478430 14</strong></li> |
||||
<li>Multibanco ou MBWAY: <a href="<%= ifthenpay %>"><%= ifthenpay %></a></li> |
||||
</ul> |
||||
|
||||
<p> |
||||
Caso queiras usufruir da quota reduzida de 6.00€ para estudantes, |
||||
desempregados e reformados, pedimos que nos envies um comprovativo desse |
||||
estatuto e faças o pagamento por transferência bancária. |
||||
</p> |
||||
|
||||
<p> |
||||
Se optares pelo método de transferência bancária, pedimos que envies o |
||||
comprovativo de transferência em resposta a este email ou para o endereço |
||||
direccao@ansol.org. |
||||
</p> |
@ -0,0 +1,14 @@ |
||||
Aceitamos pagamento via transferência bancária, referência multibanco ou |
||||
MBWAY: |
||||
|
||||
* Valor: 30.00€ |
||||
* Transferência bancária: <strong>PT50 0035 2178 00027478430 14</strong> |
||||
* Multibanco ou MBWAY: <a href="<%= @link %>"><%= @link %></a> |
||||
|
||||
Caso queiras usufruir da quota reduzida de 6.00€ para estudantes, |
||||
desempregados e reformados, pedimos que nos envies um comprovativo desse |
||||
estatuto e faças o pagamento por transferência bancária. |
||||
|
||||
Se optares pelo método de transferência bancária, pedimos que envies o |
||||
comprovativo de transferência em resposta a este email ou para o endereço |
||||
direccao@ansol.org. |
@ -0,0 +1,30 @@ |
||||
<p> |
||||
<%= t('notification_mailer.greetings', display_name: @notification.member.display_name) %> |
||||
</p> |
||||
|
||||
<p> |
||||
Como não recebemos o pagamento anual das quotas da ANSOL, a tua inscrição foi |
||||
cancelada. |
||||
</p> |
||||
|
||||
<p> |
||||
Vamos revogar em breve os teus acessos à infraestrutura da associação |
||||
exclusiva para membros (nextcloud, mailing list, sala de Matrix, etc). |
||||
</p> |
||||
|
||||
<p> |
||||
Esperamos poder voltar a merecer o teu apoio no futuro. Podes reinscrever-te |
||||
a qualquer altura através do formulário disponível em |
||||
<a href="https://ansol.org/inscricao">https://ansol.org/inscricao</a>. |
||||
</p> |
||||
|
||||
<p> |
||||
Caso consideres que estás a receber esta mensagem indevidamente, contacta-nos |
||||
através do endereço direccao@ansol.org para resolvermos a situação o mais |
||||
rápido possível. |
||||
</p> |
||||
|
||||
<p> |
||||
Saudações livres,<br> |
||||
Direcção da ANSOL |
||||
</p> |
@ -0,0 +1,17 @@ |
||||
<%= t('notification_mailer.greetings', display_name: @notification.member.display_name) %> |
||||
|
||||
Como não recebemos o pagamento anual das quotas da ANSOL, a tua inscrição foi |
||||
cancelada. |
||||
|
||||
Vamos revogar em breve os teus acessos à infraestrutura da associação exclusiva |
||||
para membros (nextcloud, mailing list, sala de Matrix, etc). |
||||
|
||||
Esperamos poder voltar a merecer o teu apoio no futuro. Podes reinscrever-te a |
||||
qualquer altura através do formulário disponível em https://ansol.org/inscricao |
||||
|
||||
Caso consideres que estás a receber esta mensagem indevidamente, contacta-nos |
||||
através do endereço direccao@ansol.org para resolvermos a situação o mais |
||||
rápido possível. |
||||
|
||||
Saudações livres, |
||||
Direcção da ANSOL |
@ -0,0 +1,24 @@ |
||||
<p><%= t('notification_mailer.greetings', display_name: @notification.member.display_name) %></p> |
||||
|
||||
<p> |
||||
A tua inscrição como membro da ANSOL expira em 30 dias. |
||||
</p> |
||||
|
||||
<p> |
||||
Em primeiro lugar, queremos agradecer o teu contributo para a ANSOL. |
||||
Dependemos exclusivamente da contribuição dos nossos membros para suportar as |
||||
nossas actividades, e gostaríamos de continuar a contar com a tua |
||||
participação. |
||||
</p> |
||||
|
||||
<p> |
||||
Para estender a tua inscrição por mais um ano, pedimos que faças o pagamento |
||||
das quotas até <strong><%= @notification.member.expires_on %></strong>. |
||||
</p> |
||||
|
||||
<%= render partial: "payment", locals: { ifthenpay: @link } %> |
||||
|
||||
<p> |
||||
Saudações livres,<br> |
||||
Direcção da ANSOL |
||||
</p> |
@ -0,0 +1,16 @@ |
||||
<%= t('notification_mailer.greetings', display_name: @notification.member.display_name) %> |
||||
|
||||
A tua inscrição como membro da ANSOL expira em 30 dias. |
||||
|
||||
Em primeiro lugar, queremos agradecer o teu contributo para a ANSOL. |
||||
Dependemos exclusivamente da contribuição dos nossos membros para suportar as |
||||
nossas actividades, e gostaríamos de continuar a contar com a tua |
||||
participação. |
||||
|
||||
Para estender a tua inscrição por mais um ano, pedimos que faças o pagamento |
||||
das quotas até <%= @notification.member.expires_on %>. |
||||
|
||||
<%= render partial: "payment", locals: { ifthenpay: @link } %> |
||||
|
||||
Saudações livres, |
||||
Direcção da ANSOL |
@ -0,0 +1,24 @@ |
||||
<p><%= t('notification_mailer.greetings', display_name: @notification.member.display_name) %></p> |
||||
|
||||
<p> |
||||
A tua inscrição como membro da ANSOL expira em 60 dias. |
||||
</p> |
||||
|
||||
<p> |
||||
Em primeiro lugar, queremos agradecer o teu contributo para a ANSOL. |
||||
Dependemos exclusivamente da contribuição dos nossos membros para suportar as |
||||
nossas actividades, e gostaríamos de continuar a contar com a tua |
||||
participação. |
||||
</p> |
||||
|
||||
<p> |
||||
Para estender a tua inscrição por mais um ano, pedimos que faças o pagamento |
||||
das quotas até <strong><%= @notification.member.expires_on %></strong>. |
||||
</p> |
||||
|
||||
<%= render partial: "payment", locals: { ifthenpay: @link } %> |
||||
|
||||
<p> |
||||
Saudações livres,<br> |
||||
Direcção da ANSOL |
||||
</p> |
@ -0,0 +1,16 @@ |
||||
<%= t('notification_mailer.greetings', display_name: @notification.member.display_name) %> |
||||
|
||||
A tua inscrição como membro da ANSOL expira em 60 dias. |
||||
|
||||
Em primeiro lugar, queremos agradecer o teu contributo para a ANSOL. |
||||
Dependemos exclusivamente da contribuição dos nossos membros para suportar as |
||||
nossas actividades, e gostaríamos de continuar a contar com a tua |
||||
participação. |
||||
|
||||
Para estender a tua inscrição por mais um ano, pedimos que faças o pagamento |
||||
das quotas até <%= @notification.member.expires_on %>. |
||||
|
||||
<%= render partial: "payment", locals: { ifthenpay: @link } %> |
||||
|
||||
Saudações livres, |
||||
Direcção da ANSOL |
@ -0,0 +1,26 @@ |
||||
<p> |
||||
<%= t('notification_mailer.greetings', display_name: @notification.member.display_name) %> |
||||
</p> |
||||
|
||||
<p> |
||||
A tua inscrição como membro da ANSOL vai ser cancelada dentro de 30 dias por |
||||
falta de pagamento da contribuição anual. |
||||
</p> |
||||
|
||||
<p> |
||||
Dependemos exclusivamente da contribuição dos nossos membros para suportar as |
||||
nossas actividades. Gostaríamos de continuar a contar com a tua participação. |
||||
</p> |
||||
|
||||
<%= render partial: "payment", locals: { ifthenpay: @link } %> |
||||
|
||||
<p> |
||||
Caso não recebamos o pagamento até dia <%= @notification.member.cancelled_on |
||||
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para |
||||
esclarecer qualquer dúvida através do endereço direccao@ansol.org. |
||||
</p> |
||||
|
||||
<p> |
||||
Saudações livres,<br> |
||||
Direcção da ANSOL |
||||
</p> |
@ -0,0 +1,16 @@ |
||||
<%= t('notification_mailer.greetings', display_name: @notification.member.display_name) %> |
||||
|
||||
A tua inscrição como membro da ANSOL expirou hoje e não recebemos a tua |
||||
contribuição anual. |
||||
|
||||
Dependemos exclusivamente da contribuição dos nossos membros para suportar as |
||||
nossas actividades. Gostaríamos de continuar a contar com a tua participação. |
||||
|
||||
<%= render partial: "payment", locals: { ifthenpay: @link } %> |
||||
|
||||
Caso não recebamos o pagamento até dia <%= @notification.member.cancelled_on |
||||
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para |
||||
esclarecer qualquer dúvida através do endereço direccao@ansol.org. |
||||
|
||||
Saudações livres, |
||||
Direcção da ANSOL |
@ -0,0 +1,26 @@ |
||||
<p> |
||||
<%= t('notification_mailer.greetings', display_name: @notification.member.display_name) %> |
||||
</p> |
||||
|
||||
<p> |
||||
A tua inscrição como membro da ANSOL expirou há um mês e ainda não recebemos |
||||
a tua contribuição anual. |
||||
</p> |
||||
|
||||
<p> |
||||
Dependemos exclusivamente da contribuição dos nossos membros para suportar as |
||||
nossas actividades. Gostaríamos de continuar a contar com a tua participação. |
||||
</p> |
||||
|
||||
<%= render partial: "payment", locals: { ifthenpay: @link } %> |
||||
|
||||
<p> |
||||
Caso não recebamos o pagamento até dia <%= @notification.member.cancelled_on |
||||
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para |
||||
esclarecer qualquer dúvida através do endereço direccao@ansol.org. |
||||
</p> |
||||
|
||||
<p> |
||||
Saudações livres, |
||||
Direcção da ANSOL |
||||
</p> |
@ -0,0 +1,16 @@ |
||||
<%= t('notification_mailer.greetings', display_name: @notification.member.display_name) %> |
||||
|
||||
A tua inscrição como membro da ANSOL expirou há um mês e ainda não recebemos |
||||
a tua contribuição anual. |
||||
|
||||
Dependemos exclusivamente da contribuição dos nossos membros para suportar as |
||||
nossas actividades. Gostaríamos de continuar a contar com a tua participação. |
||||
|
||||
<%= render partial: "payment", locals: { ifthenpay: @link } %> |
||||
|
||||
Caso não recebamos o pagamento até dia <%= @notification.member.cancelled_on |
||||
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para |
||||
esclarecer qualquer dúvida através do endereço direccao@ansol.org. |
||||
|
||||
Saudações livres, |
||||
Direcção da ANSOL |
@ -0,0 +1,26 @@ |
||||
<p> |
||||
<%= t('notification_mailer.greetings', display_name: @notification.member.display_name) %> |
||||
</p> |
||||
|
||||
<p> |
||||
A tua inscrição como membro da ANSOL vai ser cancelada dentro de 30 dias por |
||||
falta de pagamento da contribuição anual. |
||||
</p> |
||||
|
||||
<p> |
||||
Dependemos exclusivamente da contribuição dos nossos membros para suportar as |
||||
nossas actividades. Gostaríamos de continuar a contar com a tua participação. |
||||
</p> |
||||
|
||||
<%= render partial: "payment", locals: { ifthenpay: @link } %> |
||||
|
||||
<p> |
||||
Caso não recebamos o pagamento até dia <%= @notification.member.cancelled_on |
||||
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para |
||||
esclarecer qualquer dúvida através do endereço direccao@ansol.org. |
||||
</p> |
||||
|
||||
<p> |
||||
Saudações livres,<br> |
||||
Direcção da ANSOL |
||||
</p> |
@ -0,0 +1,16 @@ |
||||
<%= t('notification_mailer.greetings', display_name: @notification.member.display_name) %> |
||||
|
||||
A tua inscrição como membro da ANSOL expirou há 60 dias e ainda não recebemos |
||||
a tua contribuição anual. |
||||
|
||||
Dependemos exclusivamente da contribuição dos nossos membros para suportar as |
||||
nossas actividades. Gostaríamos de continuar a contar com a tua participação. |
||||
|
||||
<%= render partial: "payment", locals: { ifthenpay: @link } %> |
||||
|
||||
Caso não recebamos o pagamento até dia <%= @notification.member.cancelled_on |
||||
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para |
||||
esclarecer qualquer dúvida através do endereço direccao@ansol.org. |
||||
|
||||
Saudações livres, |
||||
Direcção da ANSOL |
@ -1,33 +1,60 @@ |
||||
# Files in the config/locales directory are used for internationalization |
||||
# and are automatically loaded by Rails. If you want to use locales other |
||||
# than English, add the necessary files in this directory. |
||||
# |
||||
# To use the locales, use `I18n.t`: |
||||
# |
||||
# I18n.t "hello" |
||||
# |
||||
# In views, this is aliased to just `t`: |
||||
# |
||||
# <%= t("hello") %> |
||||
# |
||||
# To use a different locale, set it with `I18n.locale`: |
||||
# |
||||
# I18n.locale = :es |
||||
# |
||||
# This would use the information in config/locales/es.yml. |
||||
# |
||||
# The following keys must be escaped otherwise they will not be retrieved by |
||||
# the default I18n backend: |
||||
# |
||||
# true, false, on, off, yes, no |
||||
# |
||||
# Instead, surround them with single quotes. |
||||
# |
||||
# en: |
||||
# "true": "foo" |
||||
# |
||||
# To learn more, please read the Rails Internationalization guide |
||||
# available at https://guides.rubyonrails.org/i18n.html. |
||||
|
||||
en: |
||||
hello: "Hello world" |
||||
navigation: |
||||
members: "Member list" |
||||
members: |
||||
index: |
||||
title: "Members" |
||||
actions: |
||||
new: "New member" |
||||
clear_search: "Clear search" |
||||
show: "Show" |
||||
edit: "Edit" |
||||
new_contribution: "Register contribution" |
||||
show: |
||||
title: "Member details" |
||||
actions: |
||||
edit: "Edit" |
||||
edit: |
||||
title: "Edit member details" |
||||
actions: |
||||
back_to_show: "Show this member" |
||||
edit_dates_warning: "Warning: changing the join/expiration date may trigger the delivery of email notifications regarding pending payments." |
||||
new: |
||||
title: "Register new member" |
||||
attributes: |
||||
number: "#" |
||||
status: "Status" |
||||
email: "Email address" |
||||
display_name: "Display name" |
||||
joined_on: "Joined on" |
||||
expires_on: "Expires on" |
||||
category: "Category" |
||||
identification_number: "ID number" |
||||
address: "Postal address" |
||||
status: |
||||
any: "Any" |
||||
active: "Active" |
||||
passive: "Passive" |
||||
pending: "Pending" |
||||
expired: "Expired" |
||||
cancelled: "Cancelled" |
||||
category: |
||||
any: "Any" |
||||
employed: "Employed" |
||||
unemployed: "Unemployed" |
||||
student: "Student" |
||||
retired: "Retired" |
||||
notification_mailer: |
||||
expiration_in_60d: |
||||
subject: "ANSOL - Pagamento anual de quotas" |
||||
title: "Pagamento anual de quotas" |
||||
expiration_in_30d: |
||||
subject: "ANSOL - Prazo para pagamento de quotas vence em 30 dias" |
||||
expired: |
||||
subject: "ANSOL - Pagamento de quotas pendente" |
||||
expired_30d_ago: |
||||
subject: "ANSOL - Pagamento de quotas em atraso" |
||||
expired_60d_ago: |
||||
subject: "ANSOL - Suspensão de inscrição iminente" |
||||
cancelled: |
||||
subject: "ANSOL - Inscrição cancelada" |
||||
|
@ -0,0 +1,62 @@ |
||||
pt: |
||||
navigation: |
||||
members: "Lista de membros" |
||||
members: |
||||
index: |
||||
title: "Membros" |
||||
actions: |
||||
new: "Registar novo membro" |
||||
clear_search: "" |
||||
show: "Mostrar" |
||||
edit: "Editar" |
||||
new_contribution: "Registar contribuição" |
||||
title: "Acções" |
||||
show: |
||||
title: "Detalhes de membro" |
||||
actions: |
||||
edit: "Editar detalhes" |
||||
edit_contribution: "Editar" |
||||
edit: |
||||
title: "Editar detalhes de membro" |
||||
actions: |
||||
back_to_show: "Show this member" |
||||
edit_dates_warning: "Atenção: a alteração das datas de inscrição/expiração podem causar o envio de emails com notificações de atraso de pagamento." |
||||
new: |
||||
title: "Registar novo membro" |
||||
attributes: |
||||
number: "#" |
||||
status: "Estado" |
||||
email: "Endereço de correio electrónico" |
||||
display_name: "Nome" |
||||
joined_on: "Data de inscrição" |
||||
expires_on: "Data de expiração" |
||||
category: "Categoria" |
||||
identification_number: "N.º de identificação" |
||||
address: "Endereço postal" |
||||
status: |
||||
any: "Qualquer" |
||||
active: "Activo" |
||||
passive: "Passivo" |
||||
pending: "Pendente" |
||||
expired: "Expirado" |
||||
cancelled: "Cancelado" |
||||
category: |
||||
any: "Qualquer" |
||||
employed: "Empregado" |
||||
unemployed: "Desempregado" |
||||
student: "Estudante" |
||||
retired: "Reformado" |
||||
notification_mailer: |
||||
expiration_in_60d: |
||||
subject: "ANSOL - Pagamento anual de quotas" |
||||
expiration_in_30d: |
||||
subject: "ANSOL - Inscrição expira em 30 dias" |
||||
expired: |
||||
subject: "ANSOL - Pagamento de quotas pendente" |
||||
expired_30d_ago: |
||||
subject: "ANSOL - Pagamento de quotas em atraso" |
||||
expired_60d_ago: |
||||
subject: "ANSOL - Suspensão de inscrição iminente" |
||||
cancelled: |
||||
subject: "ANSOL - Inscrição cancelada" |
||||
greetings: "Caro(a) %{display_name}" |
@ -0,0 +1,15 @@ |
||||
class CreateMember < ActiveRecord::Migration[7.0] |
||||
def change |
||||
create_table :members, id: :uuid do |t| |
||||
t.serial :number, null: false, index: { unique: true } |
||||
t.string :email, null: false, index: { unique: true } |
||||
t.string :display_name, null: false |
||||
t.string :identification_number |
||||
t.string :status |
||||
t.string :category |
||||
t.text :address |
||||
|
||||
t.timestamps |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,8 @@ |
||||
class AddDatesToMembers < ActiveRecord::Migration[7.0] |
||||
def change |
||||
change_table :members do |t| |
||||
t.date :joined_on |
||||
t.date :expires_on |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,14 @@ |
||||
class CreateContributions < ActiveRecord::Migration[7.0] |
||||
def change |
||||
create_table :contributions, id: :uuid do |t| |
||||
t.references :member, type: :uuid, foreign_key: true, null: false |
||||
|
||||
t.integer :eurocents, null: false |
||||
t.date :payment_on, null: false |
||||
t.string :payment_method |
||||
t.string :payment_reference |
||||
|
||||
t.timestamps |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,14 @@ |
||||
class CreateNotifications < ActiveRecord::Migration[7.0] |
||||
def change |
||||
create_table :notifications, id: :uuid do |t| |
||||
t.references :member, type: :uuid, foreign_key: true |
||||
t.date :to_be_sent_on, null: false |
||||
t.string :template, null: false |
||||
|
||||
t.string :status, null: false |
||||
t.timestamp :sent_at |
||||
|
||||
t.timestamps |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,8 @@ |
||||
class AddIfthenpayLinksToMember < ActiveRecord::Migration[7.0] |
||||
def change |
||||
change_table :members do |t| |
||||
t.string :regular_ifthenpay_link |
||||
t.string :reduced_ifthenpay_link |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,14 @@ |
||||
desc "Application specific tasks" |
||||
namespace :saucy do |
||||
desc "Background sync operations" |
||||
task sync: :environment do |
||||
Member.generate_all_missing_ifthenpay_links! |
||||
Member.reset_all_status! |
||||
Member.regenerate_all_notifications |
||||
end |
||||
|
||||
desc "Send daily email notifications" |
||||
task notify: :environment do |
||||
Notification.send_scheduled_for_today |
||||
end |
||||
end |
@ -0,0 +1,48 @@ |
||||
require "test_helper" |
||||
|
||||
class MembersControllerTest < ActionDispatch::IntegrationTest |
||||
#setup do |
||||
# @member = members(:one) |
||||
#end |
||||
|
||||
#test "should get index" do |
||||
# get members_url |
||||
# assert_response :success |
||||
#end |
||||
|
||||
#test "should get new" do |
||||
# get new_member_url |
||||
# assert_response :success |
||||
#end |
||||
|
||||
#test "should create member" do |
||||
# assert_difference("Member.count") do |
||||
# post members_url, params: { member: { } } |
||||
# end |
||||
|
||||
# assert_redirected_to member_url(Member.last) |
||||
#end |
||||
|
||||
#test "should show member" do |
||||
# get member_url(@member) |
||||
# assert_response :success |
||||
#end |
||||
|
||||
#test "should get edit" do |
||||
# get edit_member_url(@member) |
||||
# assert_response :success |
||||
#end |
||||
|
||||
#test "should update member" do |
||||
# patch member_url(@member), params: { member: { } } |
||||
# assert_redirected_to member_url(@member) |
||||
#end |
||||
|
||||
#test "should destroy member" do |
||||
# assert_difference("Member.count", -1) do |
||||
# delete member_url(@member) |
||||
# end |
||||
|
||||
# assert_redirected_to members_url |
||||
#end |
||||
end |
@ -0,0 +1,11 @@ |
||||
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html |
||||
|
||||
# This model initially had no columns defined. If you add columns to the |
||||
# model remove the "{}" from the fixture names and add the columns immediately |
||||
# below each fixture, per the syntax in the comments below |
||||
# |
||||
one: {} |
||||