mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-06 03:46:39 +08:00
Implement basic logic for Date/DateTime/Stock reminders [SCI-6554] (#3911)
* Implement basic logic for Date/DateTime/Stock reminders [SCI-6554] * Implement bell icon [SCI-6500] * Refactor reminder cells scope [SCI-6554]
This commit is contained in:
parent
20ef08907a
commit
d577fcc824
13 changed files with 91 additions and 18 deletions
|
@ -107,8 +107,8 @@ var MyModuleRepositories = (function() {
|
||||||
searchable: false,
|
searchable: false,
|
||||||
className: 'assigned-column',
|
className: 'assigned-column',
|
||||||
sWidth: '1%',
|
sWidth: '1%',
|
||||||
render: function(data) {
|
render: function(data, type, row) {
|
||||||
return $.fn.dataTable.render.AssignedTasksValue(data);
|
return $.fn.dataTable.render.AssignedTasksValue(data, row);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
targets: 3,
|
targets: 3,
|
||||||
|
|
|
@ -157,14 +157,16 @@ $.fn.dataTable.render.RepositoryNumberValue = function(data) {
|
||||||
</span>`;
|
</span>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
$.fn.dataTable.render.AssignedTasksValue = function(data) {
|
$.fn.dataTable.render.AssignedTasksValue = function(data, row) {
|
||||||
|
let tasksLinkHTML;
|
||||||
|
|
||||||
if (data.tasks > 0) {
|
if (data.tasks > 0) {
|
||||||
let tooltip = I18n.t('repositories.table.assigned_tooltip', {
|
let tooltip = I18n.t('repositories.table.assigned_tooltip', {
|
||||||
tasks: data.tasks,
|
tasks: data.tasks,
|
||||||
experiments: data.experiments,
|
experiments: data.experiments,
|
||||||
projects: data.projects
|
projects: data.projects
|
||||||
});
|
});
|
||||||
return `<div class="assign-counter-container dropdown" title="${tooltip}"
|
tasksLinkHTML = `<div class="assign-counter-container dropdown" title="${tooltip}"
|
||||||
data-task-list-url="${data.task_list_url}">
|
data-task-list-url="${data.task_list_url}">
|
||||||
<a href="#" class="assign-counter has-assigned"
|
<a href="#" class="assign-counter has-assigned"
|
||||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">${data.tasks}</a>
|
data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">${data.tasks}</a>
|
||||||
|
@ -177,8 +179,16 @@ $.fn.dataTable.render.AssignedTasksValue = function(data) {
|
||||||
<div class="tasks"></div>
|
<div class="tasks"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
} else {
|
||||||
|
tasksLinkHTML = "<div class='assign-counter-container'><span class='assign-counter'>0</span></div>";
|
||||||
}
|
}
|
||||||
return "<div class='assign-counter-container'><span class='assign-counter'>0</span></div>";
|
|
||||||
|
if (row.hasActiveReminders) {
|
||||||
|
return `<i class="fas fa-bell row-reminders-icon" data-row-reminders-url="${row.rowRemindersUrl}"></i>`
|
||||||
|
+ tasksLinkHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasksLinkHTML;
|
||||||
};
|
};
|
||||||
|
|
||||||
$.fn.dataTable.render.RepositoryStockValue = function(data) {
|
$.fn.dataTable.render.RepositoryStockValue = function(data) {
|
||||||
|
|
|
@ -431,7 +431,7 @@ var RepositoryDatatable = (function(global) {
|
||||||
className: 'assigned-column',
|
className: 'assigned-column',
|
||||||
sWidth: '1%',
|
sWidth: '1%',
|
||||||
render: function(data, type, row) {
|
render: function(data, type, row) {
|
||||||
let content = $.fn.dataTable.render.AssignedTasksValue(data);
|
let content = $.fn.dataTable.render.AssignedTasksValue(data, row);
|
||||||
let icon;
|
let icon;
|
||||||
if (!row.recordEditable) {
|
if (!row.recordEditable) {
|
||||||
icon = `<i class="repository-row-lock-icon fas fa-lock" title="${I18n.t('repositories.table.locked_item')}"></i>`;
|
icon = `<i class="repository-row-lock-icon fas fa-lock" title="${I18n.t('repositories.table.locked_item')}"></i>`;
|
||||||
|
|
|
@ -205,14 +205,20 @@
|
||||||
.assigned-column {
|
.assigned-column {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
.row-reminders-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
.assign-counter-container {
|
.assign-counter-container {
|
||||||
border-radius: $border-radius-tag;
|
border-radius: $border-radius-tag;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
|
padding: 0 5px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center;
|
right: 5px;
|
||||||
top: 1px;
|
|
||||||
width: calc(100% - 16px);
|
|
||||||
|
|
||||||
.assign-counter {
|
.assign-counter {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
@ -34,7 +34,13 @@
|
||||||
|
|
||||||
.repository-row-edit-icon {
|
.repository-row-edit-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: none;
|
display: inline-block;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-reminders-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.assign-counter-container {
|
.assign-counter-container {
|
||||||
|
@ -42,8 +48,7 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 5px;
|
||||||
width: calc(100% - 40px);
|
|
||||||
|
|
||||||
.assign-counter {
|
.assign-counter {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -251,7 +256,7 @@
|
||||||
|
|
||||||
.assigned-column {
|
.assigned-column {
|
||||||
.repository-row-edit-icon {
|
.repository-row-edit-icon {
|
||||||
display: inline-block;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,7 +278,7 @@
|
||||||
tr:hover {
|
tr:hover {
|
||||||
.assigned-column {
|
.assigned-column {
|
||||||
.repository-row-edit-icon {
|
.repository-row-edit-icon {
|
||||||
display: none;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.assign-counter-container {
|
.assign-counter-container {
|
||||||
|
|
|
@ -34,7 +34,7 @@ module RepositoryColumns
|
||||||
private
|
private
|
||||||
|
|
||||||
def repository_column_params
|
def repository_column_params
|
||||||
params.require(:repository_column).permit(:name)
|
params.require(:repository_column).permit(:name, :reminder_delta)
|
||||||
end
|
end
|
||||||
|
|
||||||
def column_type_param
|
def column_type_param
|
||||||
|
|
|
@ -9,7 +9,7 @@ class RepositoryRowsController < ApplicationController
|
||||||
before_action :load_repository, except: %i(show print_modal print)
|
before_action :load_repository, except: %i(show print_modal print)
|
||||||
before_action :load_repository_or_snapshot, only: %i(print_modal print)
|
before_action :load_repository_or_snapshot, only: %i(print_modal print)
|
||||||
before_action :load_repository_row, only: %i(update assigned_task_list)
|
before_action :load_repository_row, only: %i(update assigned_task_list)
|
||||||
before_action :check_read_permissions, except: %i(show create update delete_records copy_records)
|
before_action :check_read_permissions, except: %i(show create update delete_records copy_records reminder_repository_cells)
|
||||||
before_action :check_snapshotting_status, only: %i(create update delete_records copy_records)
|
before_action :check_snapshotting_status, only: %i(create update delete_records copy_records)
|
||||||
before_action :check_create_permissions, only: :create
|
before_action :check_create_permissions, only: :create
|
||||||
before_action :check_delete_permissions, only: %i(delete_records archive_records restore_records)
|
before_action :check_delete_permissions, only: %i(delete_records archive_records restore_records)
|
||||||
|
@ -224,6 +224,10 @@ class RepositoryRowsController < ApplicationController
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reminder_repository_cells
|
||||||
|
render json: @repository_row.repository_cells.with_active_reminder
|
||||||
|
end
|
||||||
|
|
||||||
def archive_records
|
def archive_records
|
||||||
service = RepositoryActions::ArchiveRowsService.call(repository: @repository,
|
service = RepositoryActions::ArchiveRowsService.call(repository: @repository,
|
||||||
repository_rows: selected_rows_in_repo_params,
|
repository_rows: selected_rows_in_repo_params,
|
||||||
|
|
|
@ -4,6 +4,8 @@ module RepositoryDatatableHelper
|
||||||
include InputSanitizeHelper
|
include InputSanitizeHelper
|
||||||
|
|
||||||
def prepare_row_columns(repository_rows, repository, columns_mappings, team, options = {})
|
def prepare_row_columns(repository_rows, repository, columns_mappings, team, options = {})
|
||||||
|
repository_row_with_active_reminder_ids = repository_rows.with_active_reminders.pluck(:id)
|
||||||
|
|
||||||
repository_rows.map do |record|
|
repository_rows.map do |record|
|
||||||
default_cells = {
|
default_cells = {
|
||||||
'1': assigned_row(record),
|
'1': assigned_row(record),
|
||||||
|
@ -18,7 +20,14 @@ module RepositoryDatatableHelper
|
||||||
row = {
|
row = {
|
||||||
'DT_RowId': record.id,
|
'DT_RowId': record.id,
|
||||||
'DT_RowAttr': { 'data-state': row_style(record) },
|
'DT_RowAttr': { 'data-state': row_style(record) },
|
||||||
'recordInfoUrl': Rails.application.routes.url_helpers.repository_repository_row_path(repository, record)
|
'recordInfoUrl': Rails.application.routes.url_helpers.repository_repository_row_path(repository, record),
|
||||||
|
'hasActiveReminders': repository_row_with_active_reminder_ids.include?(record.id),
|
||||||
|
'rowRemindersUrl':
|
||||||
|
Rails.application.routes.url_helpers
|
||||||
|
.active_reminder_repository_cells_repository_repository_row_url(
|
||||||
|
repository,
|
||||||
|
record
|
||||||
|
)
|
||||||
}.merge(default_cells)
|
}.merge(default_cells)
|
||||||
|
|
||||||
if record.repository.has_stock_management?
|
if record.repository.has_stock_management?
|
||||||
|
|
24
app/models/concerns/reminder_repository_cell_joinable.rb
Normal file
24
app/models/concerns/reminder_repository_cell_joinable.rb
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ReminderRepositoryCellJoinable
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
def self.reminder_repository_cells_scope(relation)
|
||||||
|
relation.joins( # datetime reminders
|
||||||
|
'LEFT OUTER JOIN "repository_date_time_values" ON '\
|
||||||
|
'"repository_date_time_values"."id" = "repository_cells"."value_id" AND '\
|
||||||
|
'"repository_cells"."value_type" = \'RepositoryDateTimeValueBase\' '\
|
||||||
|
'AND repository_columns.metadata ? \'reminder_delta\' AND '\
|
||||||
|
'(repository_date_time_values.data - NOW()) <= '\
|
||||||
|
'(repository_columns.metadata -> \'reminder_delta\')::int * interval \'1 sec\''
|
||||||
|
).joins( # stock reminders
|
||||||
|
'LEFT OUTER JOIN "repository_stock_values" ON "repository_stock_values"."id" = "repository_cells"."value_id" AND '\
|
||||||
|
'"repository_cells"."value_type" = \'RepositoryStockValue\' AND '\
|
||||||
|
'repository_stock_values.amount <= repository_stock_values.low_stock_threshold'
|
||||||
|
).where(
|
||||||
|
'repository_date_time_values.id IS NOT NULL OR repository_stock_values.id IS NOT NULL'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RepositoryCell < ApplicationRecord
|
class RepositoryCell < ApplicationRecord
|
||||||
|
include ReminderRepositoryCellJoinable
|
||||||
|
|
||||||
attr_accessor :importing
|
attr_accessor :importing
|
||||||
|
|
||||||
belongs_to :repository_row
|
belongs_to :repository_row
|
||||||
|
@ -41,6 +43,10 @@ class RepositoryCell < ApplicationRecord
|
||||||
uniqueness: { scope: :repository_column },
|
uniqueness: { scope: :repository_column },
|
||||||
unless: :importing
|
unless: :importing
|
||||||
|
|
||||||
|
scope :with_active_reminder, lambda {
|
||||||
|
reminder_repository_cells_scope(joins(:repository_column))
|
||||||
|
}
|
||||||
|
|
||||||
def self.create_with_value!(row, column, data, user)
|
def self.create_with_value!(row, column, data, user)
|
||||||
cell = new(repository_row: row, repository_column: column)
|
cell = new(repository_row: row, repository_column: column)
|
||||||
cell.transaction do
|
cell.transaction do
|
||||||
|
|
|
@ -25,6 +25,8 @@ class RepositoryColumn < ApplicationRecord
|
||||||
|
|
||||||
enum data_type: Extends::REPOSITORY_DATA_TYPES
|
enum data_type: Extends::REPOSITORY_DATA_TYPES
|
||||||
|
|
||||||
|
store_accessor :metadata, %i(reminder_delta)
|
||||||
|
|
||||||
validates :data_type, uniqueness: { if: :repository_stock_value?, scope: :repository_id }
|
validates :data_type, uniqueness: { if: :repository_stock_value?, scope: :repository_id }
|
||||||
validates :data_type, uniqueness: { if: :repository_stock_consumption_value?, scope: :repository_id }
|
validates :data_type, uniqueness: { if: :repository_stock_consumption_value?, scope: :repository_id }
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ class RepositoryRow < ApplicationRecord
|
||||||
include SearchableModel
|
include SearchableModel
|
||||||
include SearchableByNameModel
|
include SearchableByNameModel
|
||||||
include ArchivableModel
|
include ArchivableModel
|
||||||
|
include ReminderRepositoryCellJoinable
|
||||||
|
|
||||||
ID_PREFIX = 'IT'
|
ID_PREFIX = 'IT'
|
||||||
include PrefixedIdModel
|
include PrefixedIdModel
|
||||||
|
@ -34,7 +35,8 @@ class RepositoryRow < ApplicationRecord
|
||||||
repository_date: 'RepositoryDateValue',
|
repository_date: 'RepositoryDateValue',
|
||||||
repository_date_time_range: 'RepositoryDateTimeRangeValue',
|
repository_date_time_range: 'RepositoryDateTimeRangeValue',
|
||||||
repository_time_range: 'RepositoryTimeRangeValue',
|
repository_time_range: 'RepositoryTimeRangeValue',
|
||||||
repository_date_range: 'RepositoryDateRangeValue'
|
repository_date_range: 'RepositoryDateRangeValue',
|
||||||
|
repository_stock: 'RepositoryStockValue'
|
||||||
}.each do |relation, class_name|
|
}.each do |relation, class_name|
|
||||||
has_many "#{relation}_cells".to_sym, -> { where(value_type: class_name) }, class_name: 'RepositoryCell',
|
has_many "#{relation}_cells".to_sym, -> { where(value_type: class_name) }, class_name: 'RepositoryCell',
|
||||||
inverse_of: :repository_row
|
inverse_of: :repository_row
|
||||||
|
@ -84,6 +86,10 @@ class RepositoryRow < ApplicationRecord
|
||||||
scope :active, -> { where(archived: false) }
|
scope :active, -> { where(archived: false) }
|
||||||
scope :archived, -> { where(archived: true) }
|
scope :archived, -> { where(archived: true) }
|
||||||
|
|
||||||
|
scope :with_active_reminders, lambda {
|
||||||
|
reminder_repository_cells_scope(joins(repository_cells: :repository_column)).distinct
|
||||||
|
}
|
||||||
|
|
||||||
def code
|
def code
|
||||||
"#{ID_PREFIX}#{parent_id || id}"
|
"#{ID_PREFIX}#{parent_id || id}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -596,6 +596,7 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
member do
|
member do
|
||||||
get :assigned_task_list
|
get :assigned_task_list
|
||||||
|
get :active_reminder_repository_cells
|
||||||
end
|
end
|
||||||
member do
|
member do
|
||||||
get 'repository_stock_value/new', to: 'repository_stock_values#new', as: 'new_repository_stock'
|
get 'repository_stock_value/new', to: 'repository_stock_values#new', as: 'new_repository_stock'
|
||||||
|
|
Loading…
Add table
Reference in a new issue