pg_rails 7.6.20 → 7.6.21.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/pg_associable/app/javascript/modal_controller.js +8 -4
  3. data/pg_associable/spec/pg_associable/helpers_spec.rb +8 -13
  4. data/pg_engine/app/components/inline_edit/inline_edit_component.html.slim +1 -1
  5. data/pg_engine/app/components/inline_edit/inline_show_component.html.slim +2 -2
  6. data/pg_engine/app/components/notifications_bell_component.rb +1 -1
  7. data/pg_engine/app/controllers/admin/accounts_controller.rb +2 -2
  8. data/pg_engine/app/controllers/admin/user_accounts_controller.rb +3 -3
  9. data/pg_engine/app/controllers/admin/users_controller.rb +4 -1
  10. data/pg_engine/app/controllers/concerns/pg_engine/resource.rb +12 -7
  11. data/pg_engine/app/controllers/concerns/pg_engine/tenant_helper.rb +33 -0
  12. data/pg_engine/app/controllers/pg_engine/base_admin_controller.rb +0 -1
  13. data/pg_engine/app/controllers/pg_engine/base_controller.rb +18 -21
  14. data/pg_engine/app/controllers/pg_engine/base_public_controller.rb +1 -1
  15. data/pg_engine/app/controllers/pg_engine/base_users_controller.rb +5 -3
  16. data/pg_engine/app/controllers/pg_engine/devise_controller.rb +4 -1
  17. data/pg_engine/app/controllers/pg_engine/tenant_controller.rb +22 -0
  18. data/pg_engine/app/controllers/{users → tenant}/dashboard_controller.rb +2 -2
  19. data/pg_engine/app/controllers/tenant/inline_edit_controller.rb +22 -0
  20. data/pg_engine/app/controllers/tenant/user_accounts_controller.rb +56 -0
  21. data/pg_engine/app/controllers/users/accounts_controller.rb +46 -15
  22. data/pg_engine/app/controllers/users/invitations_controller.rb +78 -0
  23. data/pg_engine/app/controllers/users/notifications_controller.rb +1 -0
  24. data/pg_engine/app/controllers/users/registrations_controller.rb +3 -2
  25. data/pg_engine/app/decorators/account_decorator.rb +18 -0
  26. data/pg_engine/app/decorators/pg_engine/base_record_decorator.rb +9 -1
  27. data/pg_engine/app/decorators/user_account_decorator.rb +76 -11
  28. data/pg_engine/app/helpers/pg_engine/accounts_helper.rb +9 -0
  29. data/pg_engine/app/helpers/pg_engine/frame_helper.rb +12 -0
  30. data/pg_engine/app/lib/pg_engine/default_url_options.rb +21 -0
  31. data/pg_engine/app/lib/pg_engine/filtros_builder.rb +2 -0
  32. data/pg_engine/app/models/account.rb +28 -1
  33. data/pg_engine/app/models/concerns/pg_engine/child_record.rb +55 -0
  34. data/pg_engine/app/models/current.rb +21 -3
  35. data/pg_engine/app/models/pg_engine/base_record.rb +5 -0
  36. data/pg_engine/app/models/user.rb +51 -9
  37. data/pg_engine/app/models/user_account.rb +74 -9
  38. data/pg_engine/app/policies/account_policy.rb +35 -16
  39. data/pg_engine/app/policies/email_log_policy.rb +0 -4
  40. data/pg_engine/app/policies/email_policy.rb +0 -4
  41. data/pg_engine/app/policies/pg_engine/base_policy.rb +31 -10
  42. data/pg_engine/app/policies/user_account_policy.rb +59 -25
  43. data/pg_engine/app/policies/user_policy.rb +14 -1
  44. data/pg_engine/app/views/admin/user_accounts/_form.html.slim +1 -1
  45. data/pg_engine/app/views/admin/user_accounts/show.html.slim +15 -0
  46. data/pg_engine/app/views/admin/users/show.html.slim +0 -3
  47. data/pg_engine/app/views/pg_engine/base/index.html.slim +2 -2
  48. data/pg_engine/app/views/tenant/dashboard/dashboard.html.slim +2 -0
  49. data/pg_engine/app/views/tenant/user_accounts/_fields.html.slim +13 -0
  50. data/pg_engine/app/views/tenant/user_accounts/_form.html.slim +9 -0
  51. data/pg_engine/app/views/tenant/user_accounts/show.html.slim +20 -0
  52. data/pg_engine/app/views/users/accounts/_form.html.slim +7 -0
  53. data/pg_engine/app/views/users/accounts/show.html.slim +23 -15
  54. data/pg_engine/config/initializers/acts_as_tenant.rb +7 -2
  55. data/pg_engine/config/initializers/devise.rb +10 -0
  56. data/pg_engine/config/initializers/ransack.rb +2 -0
  57. data/pg_engine/config/locales/es.yml +60 -0
  58. data/pg_engine/config/routes.rb +21 -11
  59. data/pg_engine/config/simple_form/simple_form_bootstrap.rb +2 -2
  60. data/pg_engine/db/migrate/20241023203849_devise_invitable.rb +14 -0
  61. data/pg_engine/db/migrate/20241027225618_add_membership_status_to_user_accounts.rb +6 -0
  62. data/pg_engine/db/seeds.rb +6 -6
  63. data/pg_engine/lib/pg_engine/configuracion.rb +36 -1
  64. data/pg_engine/lib/pg_engine/navigator.rb +2 -25
  65. data/pg_engine/lib/pg_engine.rb +1 -0
  66. data/pg_engine/spec/controllers/admin/accounts_controller_spec.rb +3 -2
  67. data/pg_engine/spec/controllers/admin/user_accounts_controller_spec.rb +11 -9
  68. data/pg_engine/spec/factories/accounts.rb +8 -0
  69. data/pg_engine/spec/factories/user_accounts.rb +1 -1
  70. data/pg_engine/spec/factories/users.rb +6 -0
  71. data/pg_engine/spec/models/concerns/pg_engine/child_record_spec.rb +27 -0
  72. data/pg_engine/spec/models/user_account_spec.rb +5 -1
  73. data/pg_engine/spec/models/user_spec.rb +3 -25
  74. data/pg_engine/spec/policies/account_policy_spec.rb +19 -0
  75. data/pg_engine/spec/requests/devise/invitations_spec.rb +196 -0
  76. data/pg_engine/spec/requests/resource_spec.rb +14 -15
  77. data/pg_engine/spec/requests/users/accounts_spec.rb +117 -8
  78. data/pg_engine/spec/requests/users/base_controller_spec.rb +31 -0
  79. data/pg_engine/spec/requests/users/dashboard_spec.rb +4 -9
  80. data/pg_engine/spec/requests/users/date_jumper_spec.rb +2 -1
  81. data/pg_engine/spec/requests/users/inline_edit_spec.rb +6 -5
  82. data/pg_engine/spec/requests/users/registrations_spec.rb +2 -2
  83. data/pg_engine/spec/requests/users/user_accounts_spec.rb +54 -0
  84. data/pg_engine/spec/system/login_spec.rb +2 -2
  85. data/pg_engine/spec/system/noticed_spec.rb +0 -2
  86. data/pg_engine/spec/system/signup_spec.rb +4 -3
  87. data/pg_engine/spec/system/tenants_spec.rb +10 -9
  88. data/pg_layout/app/javascript/application.js +1 -1
  89. data/pg_layout/app/javascript/config/turbo_rails/index.js +1 -1
  90. data/pg_layout/app/lib/navbar.rb +15 -1
  91. data/pg_layout/app/views/devise/invitations/edit.html.erb +34 -0
  92. data/pg_layout/app/views/devise/invitations/new.html.erb +15 -0
  93. data/pg_layout/app/views/devise/mailer/invitation_instructions.html.erb +11 -0
  94. data/pg_layout/app/views/devise/mailer/invitation_instructions.text.erb +11 -0
  95. data/pg_layout/app/views/devise/registrations/edit.html.erb +1 -0
  96. data/pg_layout/app/views/layouts/pg_layout/base.html.slim +6 -5
  97. data/pg_layout/app/views/pg_layout/_navbar.html.erb +7 -6
  98. data/pg_layout/app/views/pg_layout/_sidebar.html.erb +2 -20
  99. data/pg_layout/app/views/pg_layout/_signed_in_links.html.slim +52 -0
  100. data/pg_layout/lib/pg_layout.rb +0 -5
  101. data/pg_rails/lib/pg_rails/pundit_matchers.rb +21 -0
  102. data/pg_rails/lib/pg_rails/tpath_support.rb +73 -0
  103. data/pg_rails/lib/pg_rails.rb +6 -0
  104. data/pg_rails/lib/version.rb +1 -1
  105. data/pg_rails/scss/pg_rails.scss +1 -1
  106. data/pg_scaffold/lib/generators/pg_rspec/scaffold/templates/controller_spec.rb +1 -1
  107. data/pg_scaffold/lib/generators/pg_slim/templates/index.html.slim +1 -1
  108. metadata +43 -9
  109. data/pg_engine/app/controllers/concerns/pg_engine/require_tenant_set.rb +0 -15
  110. data/pg_engine/app/controllers/users/account_switcher_controller.rb +0 -30
  111. data/pg_engine/app/controllers/users/inline_edit_controller.rb +0 -21
  112. data/pg_engine/app/views/users/account_switcher/list.html.slim +0 -24
  113. data/pg_engine/app/views/users/dashboard/dashboard.html.slim +0 -1
  114. data/pg_engine/spec/requests/users/switcher_spec.rb +0 -84
@@ -1,6 +1,7 @@
1
1
  module Users
2
2
  class RegistrationsController < Devise::RegistrationsController
3
3
  before_action do
4
+ @sidebar = false
4
5
  authorize resource, nil, policy_class: UserRegistrationPolicy
5
6
  end
6
7
 
@@ -11,6 +12,7 @@ module Users
11
12
  resource.save
12
13
  yield resource if block_given?
13
14
  if resource.persisted?
15
+ # FIXME: no va más
14
16
  create_account_for(resource) if ActsAsTenant.current_tenant.blank?
15
17
 
16
18
  expire_data_after_sign_in!
@@ -35,8 +37,7 @@ module Users
35
37
  private
36
38
 
37
39
  def create_account_for(user)
38
- account = Account.create!(nombre: user.email)
39
- user.user_accounts.create!(account:)
40
+ Account.create!(nombre: user.email, creado_por: user)
40
41
  end
41
42
  end
42
43
  end
@@ -13,4 +13,22 @@ class AccountDecorator < PgEngine::BaseRecordDecorator
13
13
  # object.created_at.strftime("%a %m/%d/%y")
14
14
  # end
15
15
  # end
16
+ def extra_actions(*)
17
+ return if Current.namespace == :admin
18
+
19
+ ua = Current.user.user_account_for(object).decorate
20
+ [ua.ingresar_link,
21
+ ua.accept_invitation_link,
22
+ ua.reject_invitation_link].compact.join.html_safe
23
+ end
24
+
25
+ # def show_link(text: '', klass: 'btn-light')
26
+ # return unless Pundit.policy!(Current.user, object).show?
27
+
28
+ # helpers.content_tag :span, 'data-controller': :tooltip, title: 'Más opciones' do
29
+ # helpers.link_to object_url, class: "btn btn-sm #{klass}" do
30
+ # helpers.content_tag(:span, nil, class: clase_icono('list')) + text
31
+ # end
32
+ # end
33
+ # end
16
34
  end
@@ -65,8 +65,16 @@ module PgEngine
65
65
  return unless Pundit.policy!(Current.user, object).destroy?
66
66
 
67
67
  helpers.content_tag :span, rel: :tooltip, title: 'Eliminar definitivamente' do
68
+ # :nocov:
69
+ confirm_key = if Rails.env.development?
70
+ 'noconfirm'
71
+ else
72
+ 'turbo-confirm'
73
+ end
74
+ # :nocov:
75
+
68
76
  helpers.link_to object_url + (land_on.present? ? "?land_on=#{land_on}" : ''),
69
- data: { 'turbo-confirm': confirm_text, 'turbo-method': :delete },
77
+ data: { "#{confirm_key}": confirm_text, 'turbo-method': :delete },
70
78
  class: "btn btn-sm #{klass} text-danger" do
71
79
  helpers.content_tag :span, nil, class: clase_icono('trash-fill')
72
80
  end
@@ -5,15 +5,80 @@
5
5
  class UserAccountDecorator < PgEngine::BaseRecordDecorator
6
6
  delegate_all
7
7
 
8
- def profiles
9
- object.profiles.join(', ')
10
- end
11
- # Define presentation-specific methods here. Helpers are accessed through
12
- # `helpers` (aka `h`). You can override attributes, for example:
13
- #
14
- # def created_at
15
- # helpers.content_tag :span, class: 'time' do
16
- # object.created_at.strftime("%a %m/%d/%y")
17
- # end
18
- # end
8
+ def ingresar_link
9
+ return unless Pundit.policy!(Current.user, object).ingresar?
10
+
11
+ h.link_to h.tenant_root_path(tid: object.to_param),
12
+ 'data-turbo-frame': :_top,
13
+ class: 'btn btn-sm btn-primary' do
14
+ '<i class="bi bi-box-arrow-in-right"></i> Ingresar'.html_safe
15
+ end
16
+ end
17
+
18
+ def accept_invitation_link
19
+ return unless Pundit.policy!(Current.user, object).accept_invitation_link?
20
+
21
+ h.link_to [:update_invitation, :users, account, { accept: 1 }].flatten,
22
+ 'data-turbo-method': :put,
23
+ class: 'btn btn-sm btn-success' do
24
+ 'Aceptar invitación'
25
+ end
26
+ end
27
+
28
+ def reject_invitation_link
29
+ return unless Pundit.policy!(Current.user, object).accept_invitation_link?
30
+
31
+ h.link_to [:update_invitation, :users, account, { reject: 1 }].flatten,
32
+ 'data-turbo-method': :put,
33
+ class: 'btn btn-sm btn-danger' do
34
+ 'Rechazar'
35
+ end
36
+ end
37
+
38
+ def sign_off_link
39
+ return unless Pundit.policy!(Current.user, object).sign_off?
40
+
41
+ h.link_to [:update_invitation, :users, account, { sign_off: 1 }].flatten,
42
+ 'data-turbo-method': :put,
43
+ class: 'btn btn-sm btn-outline-danger' do
44
+ 'Dejar la cuenta'
45
+ end
46
+ end
47
+
48
+ def estado_f
49
+ membership_status_f + ' - ' + invitation_status_f
50
+ end
51
+
52
+ def membership_status_f
53
+ klass = {
54
+ 'ms_active' => 'text-success',
55
+ 'ms_disabled' => 'text-danger'
56
+ }[object.membership_status]
57
+
58
+ content_tag :span, object.membership_status_text, class: "#{klass} fw-bold"
59
+ end
60
+
61
+ def invitation_status_f
62
+ klass = {
63
+ 'ist_accepted' => 'text-success',
64
+ 'ist_invited' => 'text-warning-emphasis',
65
+ 'ist_rejected' => 'text-danger',
66
+ 'ist_signed_off' => 'text-danger'
67
+ }[object.invitation_status]
68
+
69
+ content_tag :span, object.invitation_status_text, class: "#{klass} fw-bold"
70
+ end
71
+
72
+ def profiles_f
73
+ return if object.profiles.account__owner?
74
+
75
+ # object.profiles.texts.join(', ')
76
+ PgEngine.config.profile_groups_options.map do |profile_group|
77
+ "<b>#{I18n.t(profile_group[:name], scope: 'profile_group')}: </b>" + h.show_profiles_for(object, profile_group)
78
+ end.join('. ').html_safe
79
+ end
80
+
81
+ def user_email_f
82
+ user.email
83
+ end
19
84
  end
@@ -0,0 +1,9 @@
1
+ module PgEngine
2
+ module AccountsHelper
3
+ def show_profiles_for(user_account, profile_group)
4
+ aux = profile_group[:options].select { |opt| user_account.profiles.include?(opt.first) }
5
+ aux = aux.map(&:last).map { |pr| t(pr, scope: 'profile_member') }.join(', ').html_safe
6
+ aux.presence || 'No'
7
+ end
8
+ end
9
+ end
@@ -32,6 +32,18 @@ module PgEngine
32
32
  end
33
33
 
34
34
  def embed_index(object, key)
35
+ reflection = object.class.reflect_on_all_associations.find do |a|
36
+ a.name == key.to_sym
37
+ end
38
+
39
+ if reflection.blank?
40
+ # :nocov:
41
+ raise PgEngine::Error, "#{key} not an association for #{object.class}"
42
+ # :nocov:
43
+ end
44
+
45
+ return unless policy(reflection.klass).index?
46
+
35
47
  content_tag(:div, 'data-controller': 'embedded-frame') do
36
48
  turbo_frame_tag "embedded--#{key}",
37
49
  refresh: :morph, src: url_for([pg_namespace, object, key]) do
@@ -0,0 +1,21 @@
1
+ module PgEngine
2
+ module DefaultUrlOptions
3
+ # Aunque parece intuitivo que se podría definir solamente url_options,
4
+ # es importante que default_url_options también esté definido
5
+ def url_options
6
+ if Current.active_user_account
7
+ super.merge(tid: Current.active_user_account.to_param)
8
+ else
9
+ super.merge(tid: nil)
10
+ end
11
+ end
12
+
13
+ def default_url_options
14
+ if Current.active_user_account
15
+ { tid: Current.active_user_account.to_param }
16
+ else
17
+ { tid: nil }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -19,11 +19,13 @@ module PgEngine
19
19
  incluye
20
20
  es_igual_a
21
21
  in
22
+ arr_cont
22
23
  cont_all
23
24
  cont_any
24
25
  not_cont
25
26
  cont
26
27
  eq
28
+ matches
27
29
  ].freeze
28
30
 
29
31
  def initialize(controller, clase_modelo, campos)
@@ -24,7 +24,7 @@ class Account < ApplicationRecord
24
24
  include Discard::Model
25
25
  include Hashid::Rails
26
26
 
27
- has_many :user_accounts
27
+ has_many :user_accounts, dependent: :destroy
28
28
  has_many :users, through: :user_accounts
29
29
 
30
30
  belongs_to :creado_por, optional: true, class_name: 'User'
@@ -36,15 +36,42 @@ class Account < ApplicationRecord
36
36
 
37
37
  has_many :audits, dependent: :nullify, class_name: 'Audited::Audit'
38
38
 
39
+ has_one_attached :logo do |attachable|
40
+ attachable.variant :thumb, resize_and_pad: [80, 80]
41
+ end
42
+
39
43
  ransacker :search do |parent|
40
44
  parent.table[:nombre]
41
45
  end
42
46
 
47
+ after_create do
48
+ if creado_por.present?
49
+ ActsAsTenant.without_tenant do
50
+ user_accounts.create!(account: self, user: creado_por, profiles: [:account__owner])
51
+ end
52
+ end
53
+ end
54
+
43
55
  before_validation do
44
56
  self.plan = 0 if plan.blank?
45
57
  end
46
58
 
59
+ def self.gender
60
+ 'm'
61
+ end
62
+
47
63
  def to_s
48
64
  nombre
49
65
  end
66
+
67
+ # There can be only one
68
+ def owner
69
+ @owner ||= ActsAsTenant.without_tenant do
70
+ user_accounts.ua_active.owners.first&.user
71
+ end
72
+
73
+ raise PgEngine::Error, 'orphan account' if @owner.nil?
74
+
75
+ @owner
76
+ end
50
77
  end
@@ -0,0 +1,55 @@
1
+ module PgEngine
2
+ module ChildRecord
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ scope :kept, -> { undiscarded.joins(klass.parent_accessor).merge(klass.parent_klass.kept) }
7
+ scope :unkept, -> { discarded.joins(klass.parent_accessor).or(klass.parent_klass.discarded) }
8
+ end
9
+
10
+ class_methods do
11
+ attr_accessor :parent_accessor
12
+
13
+ def parent_klass
14
+ if parent_accessor.blank?
15
+ # :nocov:
16
+ raise PgEngine::Error, 'parent_accessor must be present'
17
+ # :nocov:
18
+ end
19
+
20
+ reflection = reflect_on_all_associations.select { |r| r.name == parent_accessor.to_sym }.first
21
+ if reflection.blank?
22
+ # :nocov:
23
+ raise PgEngine::Error, "#{parent_accessor} not an association on #{self}"
24
+ # :nocov:
25
+ end
26
+
27
+ reflection.klass
28
+ end
29
+ end
30
+
31
+ def parent?
32
+ if self.class.parent_accessor.blank?
33
+ # :nocov:
34
+ raise PgEngine::Error, 'parent_accessor must be present'
35
+ # :nocov:
36
+ end
37
+
38
+ true
39
+ end
40
+
41
+ def parent
42
+ if self.class.parent_accessor.blank?
43
+ # :nocov:
44
+ raise PgEngine::Error, 'parent_accessor must be present'
45
+ # :nocov:
46
+ end
47
+
48
+ send(self.class.parent_accessor)
49
+ end
50
+
51
+ def kept?
52
+ undiscarded? && (parent.blank? || parent.kept?)
53
+ end
54
+ end
55
+ end
@@ -1,14 +1,32 @@
1
1
  class Current < ActiveSupport::CurrentAttributes
2
- attribute :account, :user, :namespace, :controller
2
+ attribute :user, :namespace, :controller, :active_user_account
3
3
  # attribute :request_id, :user_agent, :ip_address
4
4
 
5
5
  # resets { Time.zone = nil }
6
6
 
7
+ def tid
8
+ active_user_account.to_param
9
+ end
10
+
11
+ def active_user_profiles
12
+ if active_user_account.present?
13
+ active_user_account.profiles
14
+ else
15
+ []
16
+ end
17
+ end
18
+
19
+ def user_account_owner?
20
+ active_user_profiles.include?('account__owner')
21
+ end
22
+
23
+ def account
24
+ ActsAsTenant.current_tenant
25
+ end
26
+
7
27
  # def user=(user)
8
28
  # super
9
29
  #
10
30
  # Time.zone = user.time_zone
11
31
  # end
12
-
13
- deprecate :account, deprecator: PgEngine.deprecator
14
32
  end
@@ -28,6 +28,11 @@ module PgEngine
28
28
  end
29
29
  end
30
30
 
31
+ # overriden by PgEngine::ChildRecord
32
+ def parent?
33
+ false
34
+ end
35
+
31
36
  def to_s
32
37
  %i[nombre name].each do |campo|
33
38
  # Using `_in_database` for consistent breadcrumbs when editing the name
@@ -29,24 +29,49 @@
29
29
  #
30
30
 
31
31
  class User < ApplicationRecord
32
+ default_scope lambda {
33
+ if Current.namespace == :tenant
34
+ if ActsAsTenant.unscoped?
35
+ all
36
+ else
37
+ ids = Current.account.user_accounts.ua_active.pluck(:user_id)
38
+ # ids = ids.push(Current.user.id) ?
39
+ where(id: ids)
40
+ end
41
+ else
42
+ all
43
+ end
44
+ }
45
+
32
46
  devise :database_authenticatable, :registerable,
33
47
  :recoverable, :rememberable,
34
- :lockable, :timeoutable, :trackable, :confirmable
48
+ :lockable, :timeoutable, :trackable, :confirmable, :invitable
35
49
 
36
50
  audited
37
51
  include Discard::Model
38
52
 
39
53
  has_many :user_accounts, dependent: :destroy
54
+ accepts_nested_attributes_for :user_accounts
40
55
 
41
56
  # Hace falta?
42
57
  has_many :accounts, through: :user_accounts
43
58
 
44
- acts_as_tenant :account, through: :user_accounts
59
+ # Crea automáticamente una user_account on create
60
+ # a menos que ya exista en los nested attributes una user
61
+ # account para la current tenant
62
+ #
63
+ # Es problemático porque interfiere en UserAccount.joins(:user)
64
+ # y hace un doble join
65
+ # acts_as_tenant :account, through: :user_accounts
45
66
 
46
67
  has_many :notifications, as: :recipient, class_name: 'Noticed::Notification'
47
68
 
48
69
  validates :nombre, :apellido, presence: true
49
70
 
71
+ has_one_attached :avatar do |attachable|
72
+ attachable.variant :thumb, resize_to_fill: [80, 80]
73
+ end
74
+
50
75
  validates_presence_of :email
51
76
  validates_uniqueness_of :email, message: 'ya pertenece a un usuario'
52
77
  validates_format_of :email, with: /\A[^@\s]+@[^@\s]+\z/
@@ -59,6 +84,11 @@ class User < ApplicationRecord
59
84
  message: 'Para crear una cuenta es necesario que aceptes los términos y condiciones'
60
85
  }
61
86
 
87
+ # When the user is invited via Devise Invitable
88
+ before_invitation_created do
89
+ user_accounts.first.invitation_status = :ist_invited
90
+ end
91
+
62
92
  attr_accessor :orphan
63
93
 
64
94
  def active_for_authentication?
@@ -93,7 +123,7 @@ class User < ApplicationRecord
93
123
  end
94
124
 
95
125
  def to_s
96
- nombre_completo
126
+ nombre_completo.strip.presence || email
97
127
  end
98
128
 
99
129
  def nombre_completo
@@ -102,12 +132,6 @@ class User < ApplicationRecord
102
132
 
103
133
  class Error < PgEngine::Error; end
104
134
 
105
- def user_accounts_without_tenant
106
- ActsAsTenant.without_tenant do
107
- user_accounts.to_a
108
- end
109
- end
110
-
111
135
  def default_account
112
136
  raise Error, 'El usuario debe tener cuenta' if accounts.empty?
113
137
 
@@ -120,4 +144,22 @@ class User < ApplicationRecord
120
144
  def current_account
121
145
  ActsAsTenant.current_tenant
122
146
  end
147
+
148
+ def active_user_account_for(account)
149
+ user_account_for(account, active: true)
150
+ end
151
+
152
+ def user_account_for(account, active: false)
153
+ if account.nil?
154
+ # :nocov:
155
+ raise PgEngine::Error, 'account must be present'
156
+ # :nocov:
157
+ end
158
+
159
+ scope_name = active ? :ua_active : :kept
160
+
161
+ ActsAsTenant.without_tenant do
162
+ user_accounts.send(scope_name).where(account:).first
163
+ end
164
+ end
123
165
  end
@@ -21,24 +21,89 @@
21
21
  # fk_rails_... (user_id => users.id)
22
22
  #
23
23
 
24
- # FIXME: add column active?
25
24
  class UserAccount < ApplicationRecord
26
25
  audited
27
26
  include Hashid::Rails
28
27
 
28
+ # self.inline_editable_fields = %i[profiles]
29
+ self.default_modal = true
30
+
29
31
  belongs_to :user, inverse_of: :user_accounts
32
+
30
33
  acts_as_tenant :account
31
34
 
35
+ def self.gender
36
+ 'f'
37
+ end
38
+
32
39
  belongs_to :creado_por, optional: true, class_name: 'User'
33
40
  belongs_to :actualizado_por, optional: true, class_name: 'User'
34
41
 
35
- # scope :kept, -> { undiscarded.joins(:account).merge(Account.kept) }
36
- # FIXME: merge with User.kept
37
- scope :kept, -> { joins(:account).merge(Account.kept) }
42
+ validates :user_id, uniqueness: { scope: :account_id }
43
+
44
+ after_destroy :cleanup_invitation
45
+ def cleanup_invitation
46
+ usr = User.unscoped.find(user_id)
47
+ return unless usr.invited_to_sign_up? && !usr.confirmed?
48
+ return if usr.destroy
49
+ # :nocov:
50
+
51
+ pg_err 'User couldnt be deleted on invitation cleanup'
52
+ # :nocov:
53
+ end
54
+ # El problema está en el joins(:user), ya que la default scope de user está scopeada dentro
55
+ # del current_tenant entonces vuelve sobre la tabla user_accounts y bardea
56
+ #
57
+ # Tengo que escribir el user joins a mano porque de lo contrario sumaría la default_scope de
58
+ # User, que a su vez joinea con user_accounts
59
+ USER_JOINS = 'INNER JOIN users ON users.id = user_accounts.user_id'
60
+ scope :kept, -> { joins(USER_JOINS, :account).merge(Account.kept).merge(User.unscoped.kept) }
61
+
62
+ scope :ua_active, lambda {
63
+ kept.where(membership_status: :ms_active, invitation_status: :ist_accepted)
64
+ }
65
+
66
+ OWNERS_PREDICATE = lambda do
67
+ UserAccount.arel_table[:profiles].contains([UserAccount.profiles.account__owner.value])
68
+ end
69
+
70
+ scope :owners, lambda {
71
+ where(OWNERS_PREDICATE.call)
72
+ }
73
+
74
+ scope :regulars, lambda {
75
+ where.not(OWNERS_PREDICATE.call)
76
+ }
77
+
78
+ # Se usa en schema.rb, default: 1
79
+ enumerize :membership_status, in: {
80
+ ms_active: 1,
81
+ ms_disabled: 2
82
+ }
83
+
84
+ # Se usa en schema.rb, default: 1
85
+ enumerize :invitation_status, in: {
86
+ ist_accepted: 1,
87
+ ist_invited: 2,
88
+ ist_rejected: 3,
89
+ ist_signed_off: 4
90
+ }
91
+
92
+ scope :not_discarded_by_user, -> { where.not(invitation_status: %i[ist_rejected ist_signed_off]) }
93
+
94
+ def discarded_by_user?
95
+ invitation_status.ist_rejected? || invitation_status.ist_signed_off?
96
+ end
97
+
98
+ def ua_active?
99
+ invitation_status.ist_accepted? && membership_status.ms_active?
100
+ end
101
+
102
+ def ua_invite_pending?
103
+ invitation_status.ist_invited? && membership_status.ms_active?
104
+ end
105
+
106
+ enumerize :profiles, in: PgEngine.configuracion.user_profiles, multiple: true
38
107
 
39
- enumerize :profiles, in: {
40
- administracion: 1,
41
- operacion: 2,
42
- lectura: 3
43
- }, multiple: true
108
+ delegate :to_s, to: :user
44
109
  end
@@ -4,37 +4,56 @@
4
4
 
5
5
  class AccountPolicy < ApplicationPolicy
6
6
  class Scope < ApplicationPolicy::Scope
7
- # def resolve
8
- # if policy.acceso_total?
9
- # scope.all
10
- # else
11
- # scope.none
12
- # end
13
- # end
7
+ def resolve
8
+ if Current.namespace == :admin
9
+ scope.all
10
+ else
11
+ ary = ActsAsTenant.without_tenant do
12
+ Current.user.user_accounts.kept.not_discarded_by_user.pluck(:account_id)
13
+ end
14
+ scope.where(id: ary)
15
+ end
16
+ end
17
+ end
18
+
19
+ def update_invitation?
20
+ user_belongs_to_account?
14
21
  end
15
22
 
16
23
  def puede_editar?
17
- user.developer?
24
+ Current.namespace == :admin || record.owner == Current.user
18
25
  end
19
26
 
20
27
  def puede_crear?
21
- user.developer?
28
+ user.present?
22
29
  end
23
30
 
24
31
  def puede_borrar?
25
- user.developer?
32
+ Current.namespace == :admin
26
33
  end
27
34
 
28
- # def acceso_total?
29
- # user.developer?
30
- # end
31
-
32
35
  def new_from_associable?
33
36
  false
34
37
  end
35
38
 
39
+ def show?
40
+ base_access_to_record?
41
+ end
42
+
43
+ def index?
44
+ base_access_to_collection?
45
+ end
46
+
47
+ def base_access_to_collection?
48
+ user.present?
49
+ end
50
+
36
51
  def base_access_to_record?
37
- ActsAsTenant.unscoped? ||
38
- record.user_accounts.pluck(:user_id).include?(user.id)
52
+ ua = user.user_account_for(record)
53
+ Current.namespace == :admin || (ua.present? && !ua.ua_invite_pending?)
54
+ end
55
+
56
+ def user_belongs_to_account?
57
+ user.user_account_for(record).present?
39
58
  end
40
59
  end
@@ -24,8 +24,4 @@ class EmailLogPolicy < ApplicationPolicy
24
24
  # def puede_borrar?
25
25
  # acceso_total? && !record.readonly?
26
26
  # end
27
-
28
- # def acceso_total?
29
- # user.developer?
30
- # end
31
27
  end
@@ -24,8 +24,4 @@ class EmailPolicy < ApplicationPolicy
24
24
  # def puede_borrar?
25
25
  # acceso_total? && !record.readonly?
26
26
  # end
27
-
28
- # def acceso_total?
29
- # user.developer?
30
- # end
31
27
  end