diff --git a/Images/custom_table_configure.png b/Images/custom_table_configure.png
new file mode 100644
index 0000000..89bb0e5
Binary files /dev/null and b/Images/custom_table_configure.png differ
diff --git a/Images/custom_table_permission.png b/Images/custom_table_permission.png
new file mode 100644
index 0000000..c4c2caa
Binary files /dev/null and b/Images/custom_table_permission.png differ
diff --git a/custom_tables.jpg b/Images/custom_tables.jpg
similarity index 100%
rename from custom_tables.jpg
rename to Images/custom_tables.jpg
diff --git a/custom_tables_v2.png b/Images/custom_tables_v2.png
similarity index 100%
rename from custom_tables_v2.png
rename to Images/custom_tables_v2.png
diff --git a/README.md b/README.md
index ccfd020..db1fa60 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,21 @@
+
Redmine Custom Tables
==================
This plugin provides a possibility to create custom tables. The table is built with Redmine custom fields. It allows you to create any databases you need for your business and integrate it into your workflow processes.
-
+
+
+## 🆕 Enhanced Features & Modifications
+
+**Custom Modifications Added:**
+- **Advanced Permission System** - Configurable group-based access control
+- **Role-Based Restrictions** - Fine-grained control over edit/delete permissions
+- **Admin Settings Interface** - Easy configuration via plugin settings page
+- **Enhanced Security** - Controller-level and view-level permission checks
-[Online demo](https://redmineplus.com/sign-up-to-redmine-plus/)
+
+
Features
-------------
@@ -18,6 +28,9 @@ Features
* Commenting entities
* Export CSV/PDF
* API
+* **🆕 Configurable permission system** - Control which user groups can edit and delete records
+* **🆕 Role-based access control** - Restrict operations to specific roles (Administrator, Manager)
+* **🆕 Settings configuration page** - Easy management of permissions via admin interface
Compatibility
-------------
@@ -26,10 +39,10 @@ Compatibility
Installation and Setup
----------------------
-* Clone or [download](https://github.com/frywer/custom_tables/archive/master.zip) this repo into your **redmine_root/plugins/** folder
+* Clone or [download](https://github.com/Arean82/custom_tables/archive/refs/heads/master.zip) this repo into your **redmine_root/plugins/** folder
```
-$ git clone https://github.com/frywer/custom_tables.git
+$ git clone https://github.com/Arean82/custom_tables.git
```
* If you downloaded a tarball / zip from master branch, make sure you rename the extracted folder to `custom_tables`
* You have to run the plugin rake task to provide the assets (from the Redmine root directory):
@@ -40,7 +53,82 @@ $ bundle exec rake redmine:plugins:migrate RAILS_ENV=production
Usage
----------------------
-1) Visit **Administration->Custom tables** to open table constructor.
+### Basic Setup
+1) Visit **Administration → Custom tables** to open table constructor.
2) Press button **New table**. Fill the name field, select projects you want to enable table on and submit the form.
3) Add custom fields to your new table.
-4) Give access to the users **Administration -> Roles and permissions -> Project -> Manage custom tables**
\ No newline at end of file
+4) Give access to the users **Administration → Roles and permissions → Project → Manage custom tables**
+5) **🆕 Configure permissions** by going to **Administration → Plugins → Custom Tables plugin → Configure** to set up which user groups can edit and delete records *(All users can view and add records, but only authorized users can edit or delete)*
+
+### 🆕 Enhanced Permission Configuration
+The plugin includes a **flexible and configurable permission system** that allows precise control over who can edit and delete custom table records:
+
+#### Default Behavior (When Custom Permissions Disabled)
+- **Administrators**: Full access (create, edit, delete)
+- **Managers**: Full access (create, edit, delete)
+- **Other Users**: View and add records only (edit/delete buttons hidden)
+
+#### 🆕 Custom Group Permissions (Enhanced Feature)
+You can configure specific user groups to have full access via the admin interface:
+
+1. Go to **Administration → Plugins → Custom Tables plugin → Configure**
+2. Check **"Enable Custom Group Permissions"** to activate custom group-based access
+3. Select the user groups that should have full edit/delete permissions
+4. Click **Save** to apply changes
+
+#### 🆕 Multi-Layer Security
+- **View-Level Security**: Delete/edit buttons are hidden from unauthorized users
+- **Controller-Level Security**: All destructive actions are blocked at the controller level
+- **Role-Based Security**: Built-in support for Administrator and Manager roles
+- **Group-Based Security**: Custom group permissions via settings configuration
+
+#### Permission Levels
+- **Full Access Users** (Admins, Managers, or selected groups): Can create, edit, and delete tables and records
+- **Standard Users**: Can view and add records, but cannot edit or delete existing ones
+
+### 🆕 Technical Implementation Details
+The enhanced permission system includes:
+- **CustomTablesPermissionHelper** - Centralized permission logic
+- **Controller-level before_actions** - Security at the action level
+- **View-level conditionals** - UI element visibility control
+- **Settings management** - Persistent configuration storage
+- **Role and group validation** - Multi-factor permission checking
+
+### API Access
+The plugin provides API endpoints for managing custom tables and entities. API access follows the same permission rules as the web interface.
+
+Support
+-------
+If you find any bugs, feel free to create an issue on GitHub or make a pull request.
+https://github.com/frywer/custom_tables
+
+Contributors
+------------
+* Ivan Ivon (@frywer)
+* **🆕 Plugin customization and enhanced permission system** - Added configurable group-based permissions, role restrictions, and admin settings interface
+
+License
+-------
+The plugin is available under the MIT license.
+
+## 🆕 Changelog
+
+### Enhanced Version Features:
+- âś… Configurable group-based permission system
+- âś… Admin settings page for easy permission management
+- âś… Role-based restrictions (Administrator, Manager)
+- âś… Multi-layer security (view + controller level)
+- âś… Enhanced UI with conditional button visibility
+- âś… Persistent settings configuration
+- âś… Backward compatibility with existing installations
+
+
+## Key Highlights of Your Modifications:
+
+1. **🆕 Enhanced Features Section** - Clear overview of what you added
+2. **🆕 Icon indicators** - Visual markers for new features
+3. **🆕 Technical Details** - Explanation of the multi-layer security
+4. **🆕 Implementation Details** - Technical architecture of your permission system
+5. **🆕 Updated Contributors** - Credit for your enhancements
+6. **🆕 Changelog** - Summary of all new features
+
diff --git a/app/controllers/custom_entities_controller.rb b/app/controllers/custom_entities_controller.rb
index 5bb28f2..054b34a 100644
--- a/app/controllers/custom_entities_controller.rb
+++ b/app/controllers/custom_entities_controller.rb
@@ -1,3 +1,5 @@
+#custom_entities_controller.rb
+
class CustomEntitiesController < ApplicationController
layout 'admin'
self.main_menu = false
@@ -13,8 +15,14 @@ class CustomEntitiesController < ApplicationController
helper :sort
include SortHelper
helper :custom_tables_pdf
+ # Add permission helper
+ helper :custom_tables_permission
+
+ #accept_api_auth :show, :create, :update, :destroy
+ accept_api_auth :index, :show, :create, :update, :destroy
- accept_api_auth :show, :create, :update, :destroy
+ # Use the permission method
+ before_action :check_destroy_permission, only: [:destroy, :context_menu, :bulk_update]
before_action :authorize_global
before_action :find_custom_entity, only: [:show, :edit, :update, :add_belongs_to, :new_note]
@@ -57,10 +65,57 @@ def new_note
end
end
+
+ def destroy
+ Rails.logger.info "âś… ALLOWED: #{User.current.login} deleting CustomEntities: #{@custom_entities.map(&:id).join(', ')}"
+
+ custom_table = @custom_entities.first.custom_table
+ @custom_entities.destroy_all
+
+ respond_to do |format|
+ format.html {
+ flash[:notice] = l(:notice_successful_delete)
+ redirect_back_or_default custom_table_path(custom_table)
+ }
+ format.api { render_api_ok }
+ end
+ end
+
+ # Use the module method directly
+ def check_destroy_permission
+ unless custom_tables_user_has_full_access?
+ Rails.logger.warn "đźš« DESTROY BLOCKED: #{User.current.login}, needs full access"
+ render_403
+ end
+ end
+
+ def context_menu
+ if (@custom_entities.size == 1)
+ @custom_entity = @custom_entities.first
+ end
+ @custom_entity_ids = @custom_entities.map(&:id).sort
+
+ can_edit = @custom_entities.detect{|c| !c.editable?}.nil?
+ # Use the module method directly
+ can_delete = custom_tables_user_has_full_access?
+ @can = {:edit => can_edit, :delete => can_delete}
+ @back = back_url
+
+ @safe_attributes = @custom_entities.map(&:safe_attribute_names).reduce(:&)
+
+ render :layout => false
+ end
+
+
def create
@custom_entity = CustomEntity.new(author: User.current, custom_table_id: params[:custom_entity][:custom_table_id])
@custom_entity.safe_attributes = params[:custom_entity]
+
+ @custom_entity.updated_by = User.current # track updater
+ @custom_entity.updated_at = Time.current # ensure timestamp if not automatically handled
+
+
if @custom_entity.save
flash[:notice] = l(:notice_successful_create)
respond_to do |format|
@@ -88,6 +143,7 @@ def edit
def update
@custom_entity.init_journal(User.current)
@custom_entity.safe_attributes = params[:custom_entity]
+ @custom_entity.updated_by = User.current # track who updated
if @custom_entity.save
flash[:notice] = l(:notice_successful_update)
@@ -105,17 +161,24 @@ def update
end
end
- def destroy
- custom_table = @custom_entities.first.custom_table
- @custom_entities.destroy_all
-
- respond_to do |format|
- format.html {
- flash[:notice] = l(:notice_successful_delete)
- redirect_back_or_default custom_table_path(custom_table)
- }
- format.api { render_api_ok }
+
+ def custom_tables_user_has_full_access?(user = User.current)
+ settings = Setting.plugin_custom_tables || {}
+
+ # If custom permissions are disabled, use your existing role-based logic
+ unless settings['enable_custom_permissions']
+ allowed_roles = ['Administrator', 'Manager']
+ user_roles = user.roles.map(&:name)
+ return user.admin? || user_roles.any? { |r| allowed_roles.include?(r) }
end
+
+ # Custom permission logic
+ return true if user.admin?
+
+ allowed_group_ids = settings['allowed_groups'] || []
+ return false if allowed_group_ids.empty?
+
+ user.groups.any? { |group| allowed_group_ids.include?(group.id.to_s) }
end
def add_belongs_to
@@ -134,7 +197,7 @@ def context_menu
@custom_entity_ids = @custom_entities.map(&:id).sort
can_edit = @custom_entities.detect{|c| !c.editable?}.nil?
- can_delete = @custom_entities.detect{|c| !c.deletable?}.nil?
+ can_delete = User.current.admin? || User.current.roles.any? { |r| ['Administrator', 'Manager'].include?(r.name) }
@can = {:edit => can_edit, :delete => can_delete}
@back = back_url
diff --git a/app/controllers/custom_tables_controller.rb b/app/controllers/custom_tables_controller.rb
index 5a1cb0d..8e0df72 100644
--- a/app/controllers/custom_tables_controller.rb
+++ b/app/controllers/custom_tables_controller.rb
@@ -1,3 +1,5 @@
+# app/controllers/custom_tables_controller.rb
+
class CustomTablesController < ApplicationController
layout 'admin'
self.main_menu = false
@@ -13,12 +15,18 @@ class CustomTablesController < ApplicationController
helper :settings
helper :custom_tables_pdf
+ # Add permission helper
+ helper :custom_tables_permission
+
before_action :find_custom_table, only: [:edit, :update, :show, :destroy, :setting_tabs]
before_action :authorize_global
before_action :find_custom_tables, only: [:context_menu]
before_action :setting_tabs, only: :edit
before_action :export_custom_entities, only: :show
+ # ADD: Check permissions for table operations
+ before_action :check_manage_permission, only: [:new, :create, :edit, :update, :destroy, :context_menu]
+
accept_api_auth :show, :index, :create, :update, :destroy
def index
@@ -140,9 +148,9 @@ def context_menu
end
@custom_tables_ids = @custom_tables.map(&:id).sort
- can_edit = @custom_tables.detect{|c| !c.editable?}.nil?
- can_delete = @custom_tables.detect{|c| !c.deletable?}.nil?
- @can = {:edit => can_edit, :delete => can_delete}
+ # Use permission helper
+ has_full_access = custom_tables_user_has_full_access?
+ @can = { edit: has_full_access, delete: has_full_access }
@back = back_url
@safe_attributes = @custom_tables.map(&:safe_attribute_names).reduce(:&)
@@ -150,6 +158,35 @@ def context_menu
render layout: false
end
+ private
+
+ def check_manage_permission
+ unless custom_tables_user_has_full_access?
+ Rails.logger.warn "đźš« MANAGE PERMISSION REQUIRED: #{User.current.login} attempted #{action_name}"
+ render_403
+ return false
+ end
+ end
+
+ def custom_tables_user_has_full_access?(user = User.current)
+ settings = Setting.plugin_custom_tables || {}
+
+ # If custom permissions are disabled, use your existing role-based logic
+ unless settings['enable_custom_permissions']
+ allowed_roles = ['Administrator', 'Manager']
+ user_roles = user.roles.map(&:name)
+ return user.admin? || user_roles.any? { |r| allowed_roles.include?(r) }
+ end
+
+ # Custom permission logic
+ return true if user.admin?
+
+ allowed_group_ids = settings['allowed_groups'] || []
+ return false if allowed_group_ids.empty?
+
+ user.groups.any? { |group| allowed_group_ids.include?(group.id.to_s) }
+ end
+
def setting_tabs
@setting_tabs = [
{name: 'general', partial: 'custom_tables/edit', label: :label_general},
diff --git a/app/controllers/table_fields_controller.rb b/app/controllers/table_fields_controller.rb
index 04855ef..9466ab1 100644
--- a/app/controllers/table_fields_controller.rb
+++ b/app/controllers/table_fields_controller.rb
@@ -1,3 +1,5 @@
+#table_fields_controller.rb
+
class TableFieldsController < CustomFieldsController
layout 'admin'
self.main_menu = false
@@ -6,9 +8,13 @@ class TableFieldsController < CustomFieldsController
helper :custom_tables
helper :queries
include QueriesHelper
+ # Add permission helper
+ helper :custom_tables_permission
before_action :authorize_global
before_action :build_new_custom_field, only: [:new, :create]
+ # ADD: Check permissions
+ before_action :check_manage_permission, except: [:show, :index]
def new
@custom_table = CustomTable.find(params[:custom_table_id])
@@ -61,6 +67,25 @@ def update
end
end
+ def custom_tables_user_has_full_access?(user = User.current)
+ settings = Setting.plugin_custom_tables || {}
+
+ # If custom permissions are disabled, use your existing role-based logic
+ unless settings['enable_custom_permissions']
+ allowed_roles = ['Administrator', 'Manager']
+ user_roles = user.roles.map(&:name)
+ return user.admin? || user_roles.any? { |r| allowed_roles.include?(r) }
+ end
+
+ # Custom permission logic
+ return true if user.admin?
+
+ allowed_group_ids = settings['allowed_groups'] || []
+ return false if allowed_group_ids.empty?
+
+ user.groups.any? { |group| allowed_group_ids.include?(group.id.to_s) }
+ end
+
def destroy
table = @custom_field.custom_table
@custom_field.destroy
@@ -81,4 +106,14 @@ def build_new_custom_field
@custom_field.safe_attributes = params[:custom_field]
end
+ private
+
+ def check_manage_permission
+ unless custom_tables_user_has_full_access?
+ Rails.logger.warn "đźš« MANAGE PERMISSION REQUIRED: #{User.current.login} attempted #{action_name}"
+ render_403
+ return false
+ end
+ end
+
end
\ No newline at end of file
diff --git a/app/helpers/custom_tables_pdf_helper.rb b/app/helpers/custom_tables_pdf_helper.rb
index 5bc8959..cfc099b 100644
--- a/app/helpers/custom_tables_pdf_helper.rb
+++ b/app/helpers/custom_tables_pdf_helper.rb
@@ -93,7 +93,9 @@ def custom_entity_to_pdf(custom_entity, assoc={})
pdf.SetFontStyle('B',11)
pdf.RDMMultiCell(190 - i, 5, "#{custom_entity.custom_table.name} - #{custom_entity.name}")
pdf.SetFontStyle('',8)
- pdf.RDMMultiCell(190, 5, "#{format_time(custom_entity.created_at)} - #{custom_entity.author}")
+
+ # FIXED: Use created_on instead of created_at
+ pdf.RDMMultiCell(190, 5, "#{format_time(custom_entity.created_on)} - #{custom_entity.author}")
pdf.ln
left = []
diff --git a/app/helpers/custom_tables_permission_helper.rb b/app/helpers/custom_tables_permission_helper.rb
new file mode 100644
index 0000000..fc3ff8f
--- /dev/null
+++ b/app/helpers/custom_tables_permission_helper.rb
@@ -0,0 +1,23 @@
+# app/helpers/custom_tables_permission_helper.rb
+module CustomTablesPermissionHelper
+ #include CustomTables::PermissionModule
+
+ def custom_tables_user_has_full_access?(user = User.current)
+ settings = Setting.plugin_custom_tables || {} # Added || {} for safety
+
+ # If custom permissions are disabled, use your existing role-based logic
+ unless settings['enable_custom_permissions']
+ allowed_roles = ['Administrator', 'Manager']
+ user_roles = user.roles.map(&:name)
+ return user.admin? || user_roles.any? { |r| allowed_roles.include?(r) }
+ end
+
+ # Custom permission logic
+ return true if user.admin?
+
+ allowed_group_ids = settings['allowed_groups'] || []
+ return false if allowed_group_ids.empty?
+
+ user.groups.any? { |group| allowed_group_ids.include?(group.id.to_s) }
+ end
+end
\ No newline at end of file
diff --git a/app/models/custom_entity.rb b/app/models/custom_entity.rb
index feffb69..013377a 100644
--- a/app/models/custom_entity.rb
+++ b/app/models/custom_entity.rb
@@ -3,22 +3,32 @@ class CustomEntity < CustomTables::ActiveRecordClass.base
include CustomTables::ActsAsJournalize
belongs_to :custom_table
- belongs_to :author, class_name: 'User', foreign_key: 'author_id'
belongs_to :issue
- has_one :project, through: :issue
- has_many :custom_fields, through: :custom_table
+ has_one :project, through: :issue
+ has_many :custom_fields, through: :custom_table
- safe_attributes 'custom_table_id', 'author_id', 'custom_field_values', 'custom_fields', 'parent_entity_ids',
- 'sub_entity_ids', 'issue_id', 'external_values'
+ # âś… Audit tracking relationships
+ belongs_to :author, class_name: 'User', foreign_key: 'author_id', optional: true
+ belongs_to :updated_by, class_name: 'User', foreign_key: 'updated_by_id', optional: true
+
+ safe_attributes(
+ 'custom_table_id', 'author_id', 'updated_by_id',
+ 'custom_field_values', 'custom_fields',
+ 'parent_entity_ids', 'sub_entity_ids',
+ 'issue_id', 'external_values'
+ )
acts_as_customizable
+ acts_as_watchable
delegate :main_custom_field, to: :custom_table
- acts_as_watchable
-
self.journal_options = {}
+ # âś… Automatically set audit fields
+ before_create :set_author
+ before_save :set_updated_by
+
def name
if new_record?
custom_table.name
@@ -30,7 +40,7 @@ def name
def editable?(user = User.current)
return true if user.admin? || custom_table.is_for_all
- user.allowed_to?(:edit_issues, issue.project)
+ user.allowed_to?(:edit_issues, issue.try(:project))
end
def visible?(user = User.current)
@@ -42,51 +52,42 @@ def deletable?(user = nil)
editable?
end
- def leaf?
- false
- end
+ def leaf?; false; end
+ def is_descendant_of?(p); false; end
- def is_descendant_of?(p)
- false
- end
-
- def each_notification(users, &block)
- end
-
- def notified_users
- []
- end
-
- def notified_mentions
- []
- end
-
- def attachments
- []
- end
+ def each_notification(users, &block); end
+ def notified_users; []; end
+ def notified_mentions; []; end
+ def attachments; []; end
def available_custom_fields
custom_fields.sorted.to_a
end
+ #def created_on
+ ## created_at;
+ # respond_to?(:created_at) ? created_at : updated_at
+ #end
def created_on
- created_at
- end
-
- def updated_on
- updated_at
+ # Use updated_at as fallback if created_at doesn't exist
+ if respond_to?(:created_at) && created_at.present?
+ created_at
+ else
+ updated_at
+ end
end
+ def updated_on; updated_at; end
def value_by_external_name(external_name)
- custom_field_values.detect {|v| v.custom_field.external_name == external_name}.try(:value)
+ custom_field_values.detect { |v| v.custom_field.external_name == external_name }.try(:value)
end
def external_values=(values)
- custom_field_values.each do |custom_field_value|
- key = custom_field_value.custom_field.external_name
+ custom_field_values.each do |cf_value|
+ key = cf_value.custom_field.external_name
next unless key.present?
if values.has_key?(key)
- custom_field_value.value = values[key]
+ cf_value.value = values[key]
end
end
@custom_field_values_changed = true
@@ -95,9 +96,22 @@ def external_values=(values)
def to_h
values = {}
custom_field_values.each do |value|
- values[value.custom_field.external_name] = value.value if value.custom_field.external_name.present?
+ if value.custom_field.external_name.present?
+ values[value.custom_field.external_name] = value.value
+ end
end
- values["id"] = id
+ values['id'] = id
values
end
+
+ private
+
+ # đź§ Auto-assign author/updated_by fields
+ def set_author
+ self.author_id ||= User.current.id if User.current
+ end
+
+ def set_updated_by
+ self.updated_by_id = User.current.id if User.current
+ end
end
diff --git a/app/models/followup.rb b/app/models/followup.rb
new file mode 100644
index 0000000..06c42b5
--- /dev/null
+++ b/app/models/followup.rb
@@ -0,0 +1,12 @@
+class Followup < ActiveRecord::Base
+ belongs_to :creator, class_name: 'User', foreign_key: 'created_by_id'
+
+ before_create :set_creator
+ validates :call_customer, presence: true
+
+ private
+
+ def set_creator
+ self.created_by_id ||= User.current.id if User.current
+ end
+end
diff --git a/app/views/custom_entities/context_menu.html.erb b/app/views/custom_entities/context_menu.html.erb
index 33c433a..55669ea 100644
--- a/app/views/custom_entities/context_menu.html.erb
+++ b/app/views/custom_entities/context_menu.html.erb
@@ -1,7 +1,8 @@
+# app/views/custom_entities/context_menu.html.erb
<% class_name = @custom_entities.first.class.name %>
<% if @custom_entity %>
- <% if User.current.admin? %>
+ <% if custom_tables_user_has_full_access? %>
- <%= context_menu_link sprite_icon('bullet-go', l(:label_open_issues)), custom_entity_path(@custom_entity), class: Redmine::VERSION::MAJOR > 5 ? 'icon' : 'icon icon-arrow-right' %>
<% end %>
- <%= context_menu_link sprite_icon('edit', l(:button_edit)), edit_custom_entity_path(@custom_entity, edit_query: true, back_url: params[:back_url]),
@@ -17,5 +18,13 @@
<%= call_hook(:view_context_menu_custom_entities_export, { ids: @custom_entity_ids, back_url: @back, params: params, class_name: class_name }) %>
- <%= context_menu_link sprite_icon('del', l(:button_delete)), custom_entities_path(ids: @custom_entity_ids, back_url: @back), method: :delete, data: {confirm: l(:text_are_you_sure)}, class: 'icon icon-del', disabled: !@can[:delete] %>
-
+
+
+ <% if @can[:delete] && custom_tables_user_has_full_access? %>
+ <%= context_menu_link sprite_icon('del', l(:button_delete)),
+ custom_entities_path(ids: @custom_entity_ids, back_url: @back),
+ method: :delete,
+ data: {confirm: l(:text_are_you_sure)},
+ class: 'icon icon-del' %>
+ <% end %>
+
\ No newline at end of file
diff --git a/app/views/custom_entities/show.html.erb b/app/views/custom_entities/show.html.erb
index e3142c3..c1e7a78 100644
--- a/app/views/custom_entities/show.html.erb
+++ b/app/views/custom_entities/show.html.erb
@@ -2,7 +2,10 @@
<%= call_hook(:view_custom_table_table_field_action_menu) %>
<%= link_to sprite_icon('edit', l(:button_edit)), edit_custom_entity_path(@custom_entity, back_url: custom_entity_path(@custom_entity)), remote: true, class: 'icon icon-edit' %>
<%= link_to sprite_icon('issue-edit',l(:button_new_comment)), new_note_custom_entity_path(@custom_entity), remote: true, class: 'icon icon-issue-edit' %>
- <%= link_to sprite_icon('del', l(:button_delete)), custom_entity_path(@custom_entity, custom_table_id: @custom_entity.custom_table), data: {confirm: l(:text_are_you_sure)}, method: :delete, title: l(:button_delete), class: 'icon icon-del' %>
+
+ <% if custom_tables_user_has_full_access? %>
+ <%= link_to sprite_icon('del', l(:button_delete)), custom_entity_path(@custom_entity, custom_table_id: @custom_entity.custom_table), data: {confirm: l(:text_are_you_sure)}, method: :delete, title: l(:button_delete), class: 'icon icon-del' %>
+ <% end %>
<%= title [l(:label_custom_tables), custom_tables_path],
@@ -23,20 +26,20 @@
<% @queries_scope.each do |query| %>
-
-
- <%= link_to l(:label_new), new_custom_entity_path(
- custom_table_id: query[:custom_table_id],
- back_url: custom_entity_path(@custom_entity),
- custom_entity: {custom_field_values: query[:selected_custom_values]}), remote: true, class: 'icon icon-add' %>
- <%= call_hook(:view_custom_table_sub_table_action_menu) %>
-
- <%= query[:name] %>
- <% if query[:custom_entities].present? %>
-
- <%= render partial: 'custom_tables/query_form', locals: {query: query[:query], entities: query[:custom_entities], back_url: custom_entity_path(@custom_entity)} %>
-
- <% end %>
+
+
+ <%= link_to l(:label_new), new_custom_entity_path(
+ custom_table_id: query[:custom_table_id],
+ back_url: custom_entity_path(@custom_entity),
+ custom_entity: {custom_field_values: query[:selected_custom_values]}), remote: true, class: 'icon icon-add' %>
+ <%= call_hook(:view_custom_table_sub_table_action_menu) %>
+
+ <%= query[:name] %>
+ <% if query[:custom_entities].present? %>
+
+ <%= render partial: 'custom_tables/query_form', locals: {query: query[:query], entities: query[:custom_entities], back_url: custom_entity_path(@custom_entity)} %>
+
+ <% end %>
<% end %>
diff --git a/app/views/custom_tables/_query_form.html.erb b/app/views/custom_tables/_query_form.html.erb
index 51ff10e..78b6617 100644
--- a/app/views/custom_tables/_query_form.html.erb
+++ b/app/views/custom_tables/_query_form.html.erb
@@ -44,7 +44,9 @@
<%= link_to sprite_icon('bullet-go', l(:label_open_issues)), custom_entity_path(entity), title: l(:label_open_issues), class: Redmine::VERSION::MAJOR > 5 ? 'icon-only' : 'icon-only icon-arrow-right' %>
<%= link_to sprite_icon('edit', l(:button_edit)), edit_custom_entity_path(entity, edit_query: true, back_url: back_url), remote: true, title: l(:button_edit), class: 'icon-only icon-edit' %>
- <%= link_to sprite_icon('del', l(:button_delete)), custom_entity_path(entity, custom_table_id: @custom_table, project_id: params[:project_id], back_url: back_url), data: {confirm: l(:text_are_you_sure)}, method: :delete, title: l(:button_delete), class: 'icon-only icon-del' %>
+ <% if User.current.admin? || User.current.roles.any? { |r| ['Administrator', 'Manager'].include?(r.name) } %>
+ <%= link_to sprite_icon('del', l(:button_delete)), custom_entity_path(entity, custom_table_id: @custom_table, project_id: params[:project_id], back_url: back_url), data: {confirm: l(:text_are_you_sure)}, method: :delete, title: l(:button_delete), class: 'icon-only icon-del' %>
+ <% end %>
|
<% end %>
diff --git a/app/views/custom_tables/_settings.html.erb b/app/views/custom_tables/_settings.html.erb
new file mode 100644
index 0000000..117657e
--- /dev/null
+++ b/app/views/custom_tables/_settings.html.erb
@@ -0,0 +1,20 @@
+
+
+ <%= content_tag :label, l(:label_enable_custom_permissions) %>
+ <%= check_box_tag 'settings[enable_custom_permissions]', 1, @settings['enable_custom_permissions'] %>
+
+ <%= l(:text_enable_custom_permissions_help) %>
+
+
+
+ <%= content_tag :label, l(:label_allowed_groups) %>
+ <%= select_tag 'settings[allowed_groups][]',
+ options_from_collection_for_select(Group.sorted, :id, :name, @settings['allowed_groups']),
+ multiple: true,
+ size: 10,
+ style: 'width: 300px;',
+ class: 'custom-tables-select' %>
+
+ <%= l(:text_allowed_groups_help) %>
+
+
\ No newline at end of file
diff --git a/app/views/custom_tables/context_menu.html.erb b/app/views/custom_tables/context_menu.html.erb
index fe6a035..4ff7c75 100644
--- a/app/views/custom_tables/context_menu.html.erb
+++ b/app/views/custom_tables/context_menu.html.erb
@@ -2,7 +2,11 @@
<% if @custom_table %>
<%= context_menu_link l(:button_show), custom_table_path(@custom_table), class: 'icon-show' %>
<%= context_menu_link l(:button_edit), edit_custom_table_path(@custom_table, edit_query: true, back_url: params[:back_url]), class: 'icon-edit', disabled: !@can[:edit] %>
- <%= context_menu_link l(:button_delete), custom_table_path(@custom_table), method: :delete, class: 'icon-del', data: {confirm: l(:text_are_you_sure)}, disabled: !@can[:delete] %>
+
+ <% if User.current.admin? || User.current.roles.any? { |r| ['Administrator', 'Manager'].include?(r.name) } %>
+ <%= context_menu_link l(:button_delete), custom_table_path(@custom_table), method: :delete, class: 'icon-del', data: {confirm: l(:text_are_you_sure)}, disabled: !@can[:delete] %>
+ <% end %>
+
<% else %>
<% end %>
diff --git a/app/views/custom_tables/index.html.erb b/app/views/custom_tables/index.html.erb
index afce624..0a74f23 100644
--- a/app/views/custom_tables/index.html.erb
+++ b/app/views/custom_tables/index.html.erb
@@ -30,7 +30,9 @@
<%= raw @query.inline_columns.map {|column| " #{render_custom_table_content(column, table)} | "}.join %>
<%= link_to sprite_icon('settings', l(:button_edit)), edit_custom_table_path(table), title: l(:label_settings), class: 'icon-only icon-settings' %>
- <%= link_to sprite_icon('del', l(:button_delete)), custom_table_path(table, back_url: custom_tables_path), data: {confirm: l(:text_are_you_sure)}, method: :delete, title: l(:button_delete), class: 'icon-only icon-del' %>
+ <% if User.current.admin? || User.current.roles.any? { |r| ['Administrator', 'Manager'].include?(r.name) } %>
+ <%= link_to sprite_icon('del', l(:button_delete)), custom_table_path(table, back_url: custom_tables_path), data: {confirm: l(:text_are_you_sure)}, method: :delete, title: l(:button_delete), class: 'icon-only icon-del' %>
+ <% end %>
|
<% end -%>
diff --git a/app/views/issues/_query_custom_table.html.erb b/app/views/issues/_query_custom_table.html.erb
index cc02731..81a6b4a 100644
--- a/app/views/issues/_query_custom_table.html.erb
+++ b/app/views/issues/_query_custom_table.html.erb
@@ -26,7 +26,9 @@
<% end %>
<% if User.current.allowed_to?(:manage_custom_tables, nil, global: true) %>
<%= link_to sprite_icon('edit', l(:button_edit)), edit_custom_entity_path(entity, edit_query: true, back_url: back_url), remote: true, title: l(:button_edit), class: 'icon-only icon-edit' %>
- <%= link_to sprite_icon('del', l(:button_delete)), custom_entity_path(entity, custom_table_id: custom_table, back_url: back_url), data: {confirm: l(:text_are_you_sure)}, method: :delete, title: l(:button_delete), class: 'icon-only icon-del' %>
+ <% if User.current.admin? || User.current.roles.any? { |r| ['Administrator', 'Manager'].include?(r.name) } %>
+ <%= link_to sprite_icon('del', l(:button_delete)), custom_entity_path(entity, custom_table_id: custom_table, back_url: back_url), data: {confirm: l(:text_are_you_sure)}, method: :delete, title: l(:button_delete), class: 'icon-only icon-del' %>
+ <% end %>
<% end %>
diff --git a/app/views/table_fields/_index.html.erb b/app/views/table_fields/_index.html.erb
index 9164bf5..1d86cef 100644
--- a/app/views/table_fields/_index.html.erb
+++ b/app/views/table_fields/_index.html.erb
@@ -21,7 +21,10 @@
<%= reorder_handle(custom_field, url: custom_field_path(custom_field), param: 'custom_field') %>
<%= link_to l(:button_edit), edit_table_field_path(id: custom_field, back_url: back_url), remote: true, title: l(:button_edit), class: 'icon-only icon-edit' %>
- <%= delete_link table_field_path(custom_field, custom_table_id: @custom_table.id, back_url: back_url), class: 'icon-only icon-del' %>
+
+ <% if User.current.admin? || User.current.roles.any? { |r| ['Administrator', 'Manager'].include?(r.name) } %>
+ <%= delete_link table_field_path(custom_field, custom_table_id: @custom_table.id, back_url: back_url), class: 'icon-only icon-del' %>
+ <% end %>
|
<% end; reset_cycle %>
diff --git a/config/initializers/custom_tables.rb b/config/initializers/custom_tables.rb
new file mode 100644
index 0000000..2e74ca2
--- /dev/null
+++ b/config/initializers/custom_tables.rb
@@ -0,0 +1,19 @@
+# config/initializers/custom_tables.rb
+# ------------------------------------
+# Custom table definition for Customer Follow-up tracking
+# Works with Redmine 5.1 and custom_tables plugin
+
+CustomTables::Config.register_table(:followups, {
+ label: 'Customer Follow-up',
+ columns: [
+ { name: 'call_customer', type: 'string', label: 'Call Customer' },
+ { name: 'next_followup_date', type: 'date', label: 'Next Follow-up Date' },
+ { name: 'followup_count', type: 'integer', label: 'Follow-up Count', default: 0 },
+ { name: 'send_quote', type: 'boolean', label: 'Send Quote', default: false },
+ { name: 'renewal_completed', type: 'boolean', label: 'Renewal Completed', default: false },
+ { name: 'tagging_completed', type: 'boolean', label: 'Tagging Completed', default: false },
+ { name: 'description', type: 'text', label: 'Description', input: :textarea, rows: 6, cols: 80 },
+ { name: 'created_by_id', type: 'user', label: 'Created By', readonly: true },
+ { name: 'created_at', type: 'datetime', label: 'Created On', readonly: true }
+ ]
+})
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index 11acc57..0796044 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -1 +1,43 @@
cs:
+ label_id: ID
+ label_custom_tables: VlastnĂ tabulky
+ label_glad_custom_tables: VlastnĂ tabulky
+ label_custom_table_new: Nová tabulka
+ label_custom_table: VlastnĂ tabulka
+ label_table: Tabulka
+ label_custom_table_edit: Upravit tabulku
+ glad_custom_tables: VlastnĂ tabulky
+ custom_tables: VlastnĂ tabulky
+ label_custom_entity_edit: Upravit %{name}
+ label_custom_entity_new: NovĂ˝ %{name}
+ label_custom_table_tab: '%{name}'
+ button_edit_custom_table: Upravit tabulku
+ button_delete_custom_table: Smazat tabulku
+ label_custom_field_edit: Upravit vlastnĂ pole
+ field_main_custom_field: HlavnĂ sloupec
+ label_belongs_to: Patřà k
+ field_parent_table: Tabulka
+ label_bulk_edit_selected: Hromadně upravit vybrané %{table_name}
+ label_new_column: NovĂ˝ sloupec
+ label_all_tables: Všechny tabulky
+ help_please_configure_table_first: V této tabulce nejsou žádná vlastnà pole. %{settings}
+ label_add: Přidat
+ button_new_comment: Nový komentář
+ label_new_note: Nový komentář
+ field_external_name: Externà název
+ field_export: Exportovat
+ text_missing_permission_manage_custom_tables: Nemáte oprávněnà spravovat vlastnà tabulky!
+ field_name: Název
+ field_created_on: Vytvořeno
+
+ # NEW STRINGS:
+ label_custom_tables_settings: "NastavenĂ vlastnĂch tabulek"
+ label_enable_custom_permissions: "Povolit vlastnà oprávněnà skupin"
+ label_allowed_groups: "Skupiny s plnĂ˝m pĹ™Ăstupem"
+ text_enable_custom_permissions_help: "KdyĹľ je povoleno, pouze vybranĂ© skupiny budou mĂt plná oprávnÄ›nĂ (Ăşpravy, mazánĂ). KdyĹľ je zakázáno, plnĂ˝ pĹ™Ăstup majĂ pouze administrátoĹ™i a manaĹľeĹ™i."
+ text_allowed_groups_help: "Vyberte uĹľivatelskĂ© skupiny, kterĂ© by mÄ›ly mĂt plnĂ˝ pĹ™Ăstup (Ăşpravy, mazánĂ tabulek a záznamĹŻ). OstatnĂ uĹľivatelĂ© budou moci pouze zobrazovat a pĹ™idávat záznamy."
+
+ # NEW STRINGS FOR SERIAL NUMBERS FEATURE:
+ label_enable_serial_numbers: "Povolit sĂ©riová ÄŤĂsla v tabulkách"
+ text_enable_serial_numbers_help: "KdyĹľ je povoleno, tabulky zobrazĂ sloupec sĂ©riovĂ©ho ÄŤĂsla (S. ÄŤ.) s poĹ™adovĂ˝m ÄŤĂslem kaĹľdĂ©ho záznamu."
+
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 3316ac0..09c19a7 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1,6 +1,4 @@
-# English strings go here for Rails i18n
en:
- # my_label: "My label"
label_id: ID
label_custom_tables: Custom tables
label_glad_custom_tables: Custom tables
@@ -31,3 +29,20 @@ en:
text_missing_permission_manage_custom_tables: No permission to manage custom tables!
field_name: Name
field_created_on: Created on
+
+ # NEW STRINGS:
+ label_custom_tables_settings: "Custom Tables Settings"
+ label_enable_custom_permissions: "Enable Custom Group Permissions"
+ label_allowed_groups: "Groups with Full Access"
+ text_enable_custom_permissions_help: "When enabled, only selected groups will have full permissions (edit, delete). When disabled, only Administrators and Managers have full access."
+ text_allowed_groups_help: "Select user groups that should have full permissions (edit, delete tables and entities). All other users will only be able to view and add records."
+ text_allowed_groups_help: "Select user groups that should have full permissions (edit, delete tables and entities). All other users will only be able to view and add records."
+
+ # NEW STRINGS FOR SERIAL NUMBERS FEATURE:
+ label_enable_serial_numbers: "Enable Serial Numbers in Tables"
+ text_enable_serial_numbers_help: "When enabled, tables will display a serial number (S.No) column showing the row number for each record"
+
+ #Download all tables as CSV
+ label_export_all_tables: "Export All Custom Tables"
+ title_export_all_tables: "Export all custom tables data from this project to CSV"
+
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 77c8615..109a466 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -31,3 +31,15 @@ es:
text_missing_permission_manage_custom_tables: ¡Sin permiso para administrar tablas personalizadas!
field_name: Nombre
field_created_on: Creado el
+
+ # NEW STRINGS:
+ label_custom_tables_settings: "ConfiguraciĂłn de tablas personalizadas"
+ label_enable_custom_permissions: "Habilitar permisos personalizados de grupo"
+ label_allowed_groups: "Grupos con acceso completo"
+ text_enable_custom_permissions_help: "Cuando está habilitado, solo los grupos seleccionados tendrán permisos completos (editar, eliminar). Cuando está deshabilitado, solo los administradores y gerentes tendrán acceso completo."
+ text_allowed_groups_help: "Seleccione los grupos de usuarios que deben tener acceso completo (editar, eliminar tablas y registros). Todos los demás usuarios solo podrán ver y añadir registros."
+
+ # NEW STRINGS FOR SERIAL NUMBERS FEATURE:
+ label_enable_serial_numbers: "Habilitar nĂşmeros de serie en las tablas"
+ text_enable_serial_numbers_help: "Cuando está habilitado, las tablas mostrarán una columna de número de serie (Nº) que muestra el número de fila de cada registro."
+
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index fd62ffa..ffebe8a 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -1,5 +1,4 @@
sv:
- # my_label: "Min rubrik"
label_id: ID
label_custom_tables: Custom tables
label_glad_custom_tables: Anpassade tabeller
@@ -29,4 +28,17 @@ sv:
field_export: Exportera
text_missing_permission_manage_custom_tables: Du har ej rättigheter att ändra anpassade tabeller!
field_name: Namn
- field_created_on: Skapad
\ No newline at end of file
+ field_created_on: Skapad
+ field_created_on: Skapad den
+
+ # NEW STRINGS:
+ label_custom_tables_settings: "Inställningar för anpassade tabeller"
+ label_enable_custom_permissions: "Aktivera anpassade gruppbehörigheter"
+ label_allowed_groups: "Grupper med full ĂĄtkomst"
+ text_enable_custom_permissions_help: "När aktiverat kommer endast valda grupper att ha full behörighet (redigera, ta bort). När det är inaktiverat har endast administratörer och chefer full åtkomst."
+ text_allowed_groups_help: "Välj användargrupper som ska ha full behörighet (redigera, ta bort tabeller och poster). Alla andra användare kan bara visa och lägga till poster."
+
+ # NEW STRINGS FOR SERIAL NUMBERS FEATURE:
+ label_enable_serial_numbers: "Aktivera serienummer i tabeller"
+ text_enable_serial_numbers_help: "När aktiverat visar tabellerna en kolumn med serienummer (Nr) som visar radnumret för varje post."
+
diff --git a/config/routes.rb b/config/routes.rb
index 32d5745..5cf5d98 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,15 +1,15 @@
# Plugin's routes
# See: http://guides.rubyonrails.org/routing.html
-resources :custom_tables
-
-resources :custom_tables do
+resources :custom_tables, defaults: { format: :html } do
collection do
get :context_menu
end
end
+
resources :table_fields
-resources :custom_entities do
+
+resources :custom_entities, defaults: { format: :html } do
collection do
get :bulk_edit
post :bulk_update
@@ -27,4 +27,32 @@
member do
get 'edit_note'
end
-end
\ No newline at end of file
+end
+
+# --------------------------------------------------------------------
+# âś… Add Redmine REST API endpoints (JSON / XML supported)
+# --------------------------------------------------------------------
+match '/custom_tables(.:format)', to: 'custom_tables#index', via: :get, defaults: { format: 'json' }
+match '/custom_tables/:id(.:format)', to: 'custom_tables#show', via: :get, defaults: { format: 'json' }
+match '/custom_tables(.:format)', to: 'custom_tables#create', via: :post, defaults: { format: 'json' }
+match '/custom_tables/:id(.:format)', to: 'custom_tables#update', via: :put, defaults: { format: 'json' }
+match '/custom_tables/:id(.:format)', to: 'custom_tables#destroy',via: :delete, defaults: { format: 'json' }
+
+match '/custom_tables/:custom_table_id/custom_entities(.:format)',
+ to: 'custom_entities#index',
+ via: :get, defaults: { format: 'json' }
+match '/custom_entities/:id(.:format)',
+ to: 'custom_entities#show',
+ via: :get, defaults: { format: 'json' }
+match '/custom_entities(.:format)',
+ to: 'custom_entities#create',
+ via: :post, defaults: { format: 'json' }
+match '/custom_entities/:id(.:format)',
+ to: 'custom_entities#update',
+ via: :put, defaults: { format: 'json' }
+match '/custom_entities/:id(.:format)',
+ to: 'custom_entities#destroy',
+ via: :delete, defaults: { format: 'json' }
+
+
+
diff --git a/db/migrate/004_add_audit_fields_to_custom_entities.rb b/db/migrate/004_add_audit_fields_to_custom_entities.rb
new file mode 100644
index 0000000..081ddeb
--- /dev/null
+++ b/db/migrate/004_add_audit_fields_to_custom_entities.rb
@@ -0,0 +1,33 @@
+# db/migrate/20251030131500_add_audit_fields_to_custom_entities.rb
+#
+# Safely adds audit tracking fields to custom_entities table
+# Compatible with Redmine 5.x (Rails 6.1) using Migration[4.2]
+
+class AddAuditFieldsToCustomEntities < ActiveRecord::Migration[4.2]
+ def change
+ # Add author_id if missing
+ unless column_exists?(:custom_entities, :author_id)
+ add_column :custom_entities, :author_id, :integer
+ end
+
+ # Add updated_by_id if missing
+ unless column_exists?(:custom_entities, :updated_by_id)
+ add_column :custom_entities, :updated_by_id, :integer
+ end
+
+ # Safely add foreign key constraints (only if method exists)
+ if respond_to?(:foreign_key_exists?)
+ unless foreign_key_exists?(:custom_entities, :users, column: :author_id)
+ add_foreign_key :custom_entities, :users, column: :author_id, on_delete: :nullify
+ end
+
+ unless foreign_key_exists?(:custom_entities, :users, column: :updated_by_id)
+ add_foreign_key :custom_entities, :users, column: :updated_by_id, on_delete: :nullify
+ end
+ end
+
+ # Optional indexes for faster lookups
+ add_index :custom_entities, :author_id unless index_exists?(:custom_entities, :author_id)
+ add_index :custom_entities, :updated_by_id unless index_exists?(:custom_entities, :updated_by_id)
+ end
+end
diff --git a/db/migrate/20251030130000_create_followups_table.rb b/db/migrate/20251030130000_create_followups_table.rb
new file mode 100644
index 0000000..75b94b2
--- /dev/null
+++ b/db/migrate/20251030130000_create_followups_table.rb
@@ -0,0 +1,18 @@
+
+class CreateFollowupsTable < ActiveRecord::Migration[4.2]
+ def change
+ create_table :followups do |t|
+ t.string :call_customer
+ t.date :next_followup_date
+ t.integer :followup_count, default: 0
+ t.boolean :send_quote, default: false
+ t.boolean :renewal_completed, default: false
+ t.boolean :tagging_completed, default: false
+ t.text :description
+ t.integer :created_by_id
+ t.timestamps null: false
+ end
+
+ add_index :followups, :created_by_id
+ end
+end
diff --git a/init.rb b/init.rb
index 91f6e24..99ba3c2 100644
--- a/init.rb
+++ b/init.rb
@@ -1,11 +1,16 @@
+# init.rb
Redmine::Plugin.register :custom_tables do
name 'Custom Tables plugin'
- author 'Ivan Marangoz'
+ author 'Arean Narrayan'
description 'This is a plugin for Redmine'
- version '1.1.1'
- requires_redmine :version_or_higher => '3.4.0'
- url 'https://github.com/frywer/custom_tables'
- author_url 'https://github.com/frywer'
+ version '1.1.2'
+ requires_redmine :version_or_higher => '5.0.0'
+ url 'https://github.com/Arean82/custom_tables'
+ author_url 'https://github.com/Arean82/'
+
+ # Add settings configuration
+ settings default: { 'allowed_groups' => [], 'enable_custom_permissions' => false },
+ partial: 'custom_tables/settings'
permission :manage_custom_tables, {
custom_entities: [:new, :edit, :create, :update, :destroy, :context_menu, :bulk_edit, :bulk_update],
@@ -24,3 +29,22 @@
end
Dir[File.join(File.dirname(__FILE__), '/lib/custom_tables/**/*.rb')].each { |file| require_dependency file }
+
+require File.expand_path('../lib/custom_tables/custom_entities_controller_patch', __FILE__)
+
+Rails.configuration.to_prepare do
+ require_dependency 'custom_entities_controller'
+ require_dependency 'custom_tables_controller'
+ require_dependency 'table_fields_controller'
+
+ # Include the permission module in all controllers
+ [CustomEntitiesController, CustomTablesController, TableFieldsController].each do |controller|
+ controller.include(CustomTables::PermissionModule) unless controller.include?(CustomTables::PermissionModule)
+ end
+
+ # Also include in the existing patch
+ CustomEntitiesController.include(CustomTables::CustomEntitiesControllerPatch)
+end
+
+#require File.expand_path('../lib/custom_tables/patches/followups_loader_patch', __FILE__)
+require_dependency File.expand_path('lib/custom_tables/patches/followups_loader_patch', __dir__)
\ No newline at end of file
diff --git a/lib/custom_tables/custom_entities_controller_patch.rb b/lib/custom_tables/custom_entities_controller_patch.rb
new file mode 100644
index 0000000..f2c6405
--- /dev/null
+++ b/lib/custom_tables/custom_entities_controller_patch.rb
@@ -0,0 +1,15 @@
+module CustomTables
+ module CustomEntitiesControllerPatch
+ def self.included(base)
+ base.class_eval do
+ before_action :set_custom_permissions, only: [:index, :show]
+
+ private
+
+ def set_custom_permissions
+ @can_edit_or_delete = User.current.admin? || User.current.roles.any? { |r| ['Administrator', 'Manager'].include?(r.name) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/custom_tables/patches/followups_loader_patch.rb b/lib/custom_tables/patches/followups_loader_patch.rb
new file mode 100644
index 0000000..04055d1
--- /dev/null
+++ b/lib/custom_tables/patches/followups_loader_patch.rb
@@ -0,0 +1,42 @@
+# lib/custom_tables/patches/followups_loader_patch.rb
+# Safely registers the Customer Follow-up table after plugin load
+
+module CustomTables
+ module Patches
+ module FollowupsLoaderPatch
+ def self.load_followup_table
+ Rails.configuration.to_prepare do
+ begin
+ if defined?(CustomTables::Config)
+ unless CustomTables::Config.tables.key?(:followups)
+ CustomTables::Config.register_table(:followups, {
+ label: 'Customer Follow-up',
+ columns: [
+ { name: 'call_customer', type: 'string', label: 'Call Customer' },
+ { name: 'next_followup_date', type: 'date', label: 'Next Follow-up Date' },
+ { name: 'followup_count', type: 'integer', label: 'Follow-up Count', default: 0 },
+ { name: 'send_quote', type: 'boolean', label: 'Send Quote', default: false },
+ { name: 'renewal_completed', type: 'boolean', label: 'Renewal Completed', default: false },
+ { name: 'tagging_completed', type: 'boolean', label: 'Tagging Completed', default: false },
+ { name: 'description', type: 'text', label: 'Description',
+ input: :textarea, rows: 6, cols: 80 },
+ { name: 'created_by_id', type: 'user', label: 'Created By', readonly: true },
+ { name: 'created_at', type: 'datetime', label: 'Created On', readonly: true }
+ ]
+ })
+ Rails.logger.info '[CustomTables] Customer Follow-up table registered'
+ end
+ else
+ Rails.logger.warn '[CustomTables] CustomTables::Config not defined yet'
+ end
+ rescue => e
+ Rails.logger.error "[CustomTables] Failed to register Followups: #{e.message}"
+ end
+ end
+ end
+ end
+ end
+end
+
+# Trigger registration immediately
+CustomTables::Patches::FollowupsLoaderPatch.load_followup_table
diff --git a/lib/custom_tables/permission_module.rb b/lib/custom_tables/permission_module.rb
new file mode 100644
index 0000000..01e9839
--- /dev/null
+++ b/lib/custom_tables/permission_module.rb
@@ -0,0 +1,23 @@
+# lib/custom_tables/permission_module.rb
+module CustomTables
+ module PermissionModule
+ def custom_tables_user_has_full_access?(user = User.current)
+ settings = Setting.plugin_custom_tables || {}
+
+ # If custom permissions are disabled, use your existing role-based logic
+ unless settings['enable_custom_permissions']
+ allowed_roles = ['Administrator', 'Manager']
+ user_roles = user.roles.map(&:name)
+ return user.admin? || user_roles.any? { |r| allowed_roles.include?(r) }
+ end
+
+ # Custom permission logic
+ return true if user.admin?
+
+ allowed_group_ids = settings['allowed_groups'] || []
+ return false if allowed_group_ids.empty?
+
+ user.groups.any? { |group| allowed_group_ids.include?(group.id.to_s) }
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/custom_tables/record_restrict_patch.rb b/lib/custom_tables/record_restrict_patch.rb
new file mode 100644
index 0000000..55c6fe0
--- /dev/null
+++ b/lib/custom_tables/record_restrict_patch.rb
@@ -0,0 +1,20 @@
+module CustomTables
+ module RecordRestrictPatch
+ def self.included(base)
+ base.class_eval do
+ before_update :restrict_modifications
+ before_destroy :restrict_modifications
+ end
+ end
+
+ private
+
+ def restrict_modifications
+ allowed_roles = ['Administrator', 'Manager'] # You can change roles here
+ unless User.current.admin? || User.current.roles.any? { |r| allowed_roles.include?(r.name) }
+ errors.add(:base, 'You are not allowed to modify or delete this entry.')
+ throw :abort
+ end
+ end
+ end
+end