mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-03-04 19:53:19 +08:00
Merge branch 'develop' into features/file-versioning
This commit is contained in:
commit
f7dde1b24c
71 changed files with 559 additions and 391 deletions
1
Gemfile
1
Gemfile
|
@ -62,6 +62,7 @@ gem 'logging', '~> 2.0.0'
|
|||
gem 'nested_form_fields'
|
||||
gem 'nokogiri', '~> 1.16.5' # HTML/XML parser
|
||||
gem 'noticed'
|
||||
gem 'oj'
|
||||
gem 'rails_autolink', '~> 1.1', '>= 1.1.6'
|
||||
gem 'rgl' # Graph framework for project diagram calculations
|
||||
gem 'roo', '~> 2.10.0' # Spreadsheet parser
|
||||
|
|
23
Gemfile.lock
23
Gemfile.lock
|
@ -97,9 +97,9 @@ GEM
|
|||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
active_model_serializers (0.10.13)
|
||||
actionpack (>= 4.1, < 7.1)
|
||||
activemodel (>= 4.1, < 7.1)
|
||||
active_model_serializers (0.10.14)
|
||||
actionpack (>= 4.1)
|
||||
activemodel (>= 4.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (7.0.8.4)
|
||||
|
@ -195,6 +195,7 @@ GEM
|
|||
erubi (>= 1.0.0)
|
||||
rack (>= 0.9.0)
|
||||
rouge (>= 1.0.0)
|
||||
bigdecimal (3.1.8)
|
||||
bindata (2.5.0)
|
||||
binding_of_caller (1.0.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
|
@ -202,7 +203,7 @@ GEM
|
|||
msgpack (~> 1.2)
|
||||
brakeman (6.1.2)
|
||||
racc
|
||||
builder (3.2.4)
|
||||
builder (3.3.0)
|
||||
bullet (7.0.7)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
|
@ -316,7 +317,7 @@ GEM
|
|||
railties (>= 5)
|
||||
down (5.4.1)
|
||||
addressable (~> 2.8)
|
||||
erubi (1.12.0)
|
||||
erubi (1.13.0)
|
||||
et-orbi (1.2.11)
|
||||
tzinfo
|
||||
execjs (2.8.1)
|
||||
|
@ -364,7 +365,7 @@ GEM
|
|||
httparty (0.21.0)
|
||||
mini_mime (>= 1.0.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (1.14.5)
|
||||
i18n (1.14.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-js (3.9.2)
|
||||
i18n (>= 0.6.6)
|
||||
|
@ -372,7 +373,7 @@ GEM
|
|||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
iniparse (1.5.0)
|
||||
jbuilder (2.11.5)
|
||||
jbuilder (2.13.0)
|
||||
actionview (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
jmespath (1.6.2)
|
||||
|
@ -435,8 +436,7 @@ GEM
|
|||
mime-types-data (3.2023.0218.1)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.7)
|
||||
minitest (5.23.1)
|
||||
minitest (5.25.1)
|
||||
msgpack (1.7.1)
|
||||
multi_json (1.15.0)
|
||||
multi_test (1.1.0)
|
||||
|
@ -475,6 +475,9 @@ GEM
|
|||
rack (>= 1.2, < 4)
|
||||
snaky_hash (~> 2.0)
|
||||
version_gem (~> 1.1)
|
||||
oj (3.16.6)
|
||||
bigdecimal (>= 3.0)
|
||||
ostruct (>= 0.2)
|
||||
omniauth (2.1.2)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 2.2.3)
|
||||
|
@ -509,6 +512,7 @@ GEM
|
|||
validate_url
|
||||
webfinger (~> 2.0)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.0)
|
||||
overcommit (0.60.0)
|
||||
childprocess (>= 0.6.3, < 5)
|
||||
iniparse (~> 1.4)
|
||||
|
@ -835,6 +839,7 @@ DEPENDENCIES
|
|||
newrelic_rpm
|
||||
nokogiri (~> 1.16.5)
|
||||
noticed
|
||||
oj
|
||||
omniauth (~> 2.1)
|
||||
omniauth-azure-activedirectory-v2
|
||||
omniauth-linkedin-oauth2
|
||||
|
|
|
@ -433,10 +433,10 @@ var MyModuleRepositories = (function() {
|
|||
}
|
||||
|
||||
function initSimpleTable() {
|
||||
$('#assigned-items-container').on('shown.bs.collapse', '.assigned-repository-container.readable-repository', function() {
|
||||
$('#assigned-items-container').on('shown.bs.collapse', '.assigned-repository-container', function() {
|
||||
var repositoryContainer = $(this);
|
||||
var repositoryTable = repositoryContainer.find('.repository-table');
|
||||
var initializedTable = repositoryContainer.find('.dataTables_wrapper .repository-table');
|
||||
var repositoryTable = repositoryContainer.find('.table');
|
||||
var initializedTable = repositoryContainer.find('.dataTables_wrapper table');
|
||||
|
||||
// do not try to re-initialized already initialized table
|
||||
if (initializedTable.length) {
|
||||
|
|
|
@ -317,9 +317,7 @@ var RepositoryDatatable = (function(global) {
|
|||
|
||||
checkAvailableColumns();
|
||||
|
||||
RepositoryDatatableRowEditor.switchRowToEditMode(row);
|
||||
|
||||
changeToEditMode();
|
||||
RepositoryDatatableRowEditor.switchRowToEditMode(row, changeToEditMode);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -692,6 +690,7 @@ var RepositoryDatatable = (function(global) {
|
|||
},
|
||||
rowCallback: function(row, data) {
|
||||
$(row).attr('data-editable', data.recordEditable);
|
||||
$(row).attr('data-info-url', data.recordInfoUrl);
|
||||
$(row).attr('data-manage-stock-url', data.manageStockUrl);
|
||||
// Get row ID
|
||||
let rowId = data.DT_RowId;
|
||||
|
@ -1003,10 +1002,8 @@ var RepositoryDatatable = (function(global) {
|
|||
$(TABLE_ID).find('.repository-row-edit-icon').remove();
|
||||
|
||||
rowsSelected.forEach(function(rowNumber) {
|
||||
RepositoryDatatableRowEditor.switchRowToEditMode(TABLE.row('#' + rowNumber));
|
||||
RepositoryDatatableRowEditor.switchRowToEditMode(TABLE.row('#' + rowNumber), changeToEditMode);
|
||||
});
|
||||
|
||||
changeToEditMode();
|
||||
})
|
||||
.on('click', '#assignRepositoryRecords', function(e) {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -173,11 +173,17 @@ var RepositoryDatatableRowEditor = (function() {
|
|||
TABLE.columns.adjust();
|
||||
}
|
||||
|
||||
function switchRowToEditMode(row) {
|
||||
function enableEditMode(row, isEditable) {
|
||||
if (!isEditable) {
|
||||
HelperModule.flashAlertMsg(I18n.t('repositories.table.row_locked'), 'danger');
|
||||
return false;
|
||||
}
|
||||
|
||||
let $row = $(row.node());
|
||||
let itemId = row.id();
|
||||
let formId = `repositoryRowForm${itemId}`;
|
||||
let requestUrl = $(TABLE.table().node()).data('current-uri');
|
||||
|
||||
let rowForm = $(`
|
||||
<form id="${formId}"
|
||||
class="${EDIT_FORM_CLASS_NAME} ${GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME}"
|
||||
|
@ -213,6 +219,26 @@ var RepositoryDatatableRowEditor = (function() {
|
|||
initAssetCellActions($row);
|
||||
|
||||
TABLE.columns.adjust();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function switchRowToEditMode(row, editEnabledCallback) {
|
||||
// Editable property was already preloaded
|
||||
if (row.data().editable !== undefined) {
|
||||
if (enableEditMode(row, row.data().editable)) editEnabledCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to fetch editable property
|
||||
$.ajax({
|
||||
url: row.data().recordInfoUrl,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: (data) => {
|
||||
if (enableEditMode(row, data.editable)) editEnabledCallback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
|
|
|
@ -15,7 +15,14 @@ class MyModuleRepositoriesController < ApplicationController
|
|||
@draw = params[:draw].to_i
|
||||
per_page = params[:length].to_i < 1 ? Constants::REPOSITORY_DEFAULT_PAGE_SIZE : params[:length].to_i
|
||||
page = (params[:start].to_i / per_page) + 1
|
||||
datatable_service = RepositoryDatatableService.new(@repository, params, current_user, @my_module)
|
||||
if params[:simple_view]
|
||||
rows_view = 'repository_rows/simple_view_index'
|
||||
preload_cells = false
|
||||
else
|
||||
rows_view = 'repository_rows/index'
|
||||
preload_cells = true
|
||||
end
|
||||
datatable_service = RepositoryDatatableService.new(@repository, params, current_user, @my_module, preload_cells: preload_cells)
|
||||
|
||||
@datatable_params = {
|
||||
view_mode: params[:view_mode],
|
||||
|
@ -26,21 +33,9 @@ class MyModuleRepositoriesController < ApplicationController
|
|||
|
||||
@all_rows_count = datatable_service.all_count
|
||||
@columns_mappings = datatable_service.mappings
|
||||
|
||||
if params[:simple_view]
|
||||
repository_rows = datatable_service.repository_rows
|
||||
rows_view = 'repository_rows/simple_view_index'
|
||||
else
|
||||
repository_rows = datatable_service.repository_rows
|
||||
.preload(:repository_columns,
|
||||
:created_by,
|
||||
:archived_by,
|
||||
:last_modified_by,
|
||||
repository_cells: { value: @repository.cell_preload_includes })
|
||||
rows_view = 'repository_rows/index'
|
||||
end
|
||||
repository_rows = datatable_service.repository_rows
|
||||
@repository_rows = repository_rows.page(page).per(per_page)
|
||||
|
||||
@filtered_rows_count = @repository_rows.load.take&.filtered_count || 0
|
||||
render rows_view
|
||||
end
|
||||
|
||||
|
|
|
@ -16,20 +16,16 @@ class MyModuleRepositorySnapshotsController < ApplicationController
|
|||
|
||||
@all_rows_count = datatable_service.all_count
|
||||
@columns_mappings = datatable_service.mappings
|
||||
repository_rows = datatable_service.repository_rows
|
||||
|
||||
if params[:simple_view]
|
||||
repository_rows = datatable_service.repository_rows
|
||||
@repository = @repository_snapshot
|
||||
rows_view = 'repository_rows/simple_view_index'
|
||||
else
|
||||
repository_rows =
|
||||
datatable_service.repository_rows
|
||||
.preload(:repository_columns,
|
||||
:created_by,
|
||||
repository_cells: { value: @repository_snapshot.cell_preload_includes })
|
||||
rows_view = 'repository_rows/snapshot_index'
|
||||
end
|
||||
@repository_rows = repository_rows.page(page).per(per_page)
|
||||
|
||||
@filtered_rows_count = @repository_rows.load.take&.filtered_count || 0
|
||||
render rows_view
|
||||
end
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ class MyModuleShareableLinksController < ApplicationController
|
|||
@draw = params[:draw].to_i
|
||||
per_page = params[:length].to_i < 1 ? Constants::REPOSITORY_DEFAULT_PAGE_SIZE : params[:length].to_i
|
||||
page = (params[:start].to_i / per_page) + 1
|
||||
datatable_service = RepositoryDatatableService.new(@repository, params, nil, @my_module)
|
||||
datatable_service = RepositoryDatatableService.new(@repository, params, nil, @my_module, preload_cells: false)
|
||||
|
||||
@datatable_params = {
|
||||
view_mode: params[:view_mode],
|
||||
|
@ -76,6 +76,7 @@ class MyModuleShareableLinksController < ApplicationController
|
|||
@columns_mappings = datatable_service.mappings
|
||||
|
||||
@repository_rows = datatable_service.repository_rows.page(page).per(per_page)
|
||||
@filtered_rows_count = @repository_rows.load.take&.filtered_count || 0
|
||||
|
||||
render 'repository_rows/simple_view_index'
|
||||
end
|
||||
|
@ -84,13 +85,14 @@ class MyModuleShareableLinksController < ApplicationController
|
|||
@draw = params[:draw].to_i
|
||||
per_page = params[:length].to_i < 1 ? Constants::REPOSITORY_DEFAULT_PAGE_SIZE : params[:length].to_i
|
||||
page = (params[:start].to_i / per_page) + 1
|
||||
datatable_service = RepositorySnapshotDatatableService.new(@repository_snapshot, params, nil, @my_module)
|
||||
datatable_service = RepositorySnapshotDatatableService.new(@repository_snapshot, params, nil, @my_module, preload_cells: false)
|
||||
|
||||
@all_rows_count = datatable_service.all_count
|
||||
@columns_mappings = datatable_service.mappings
|
||||
|
||||
@repository = @repository_snapshot
|
||||
@repository_rows = datatable_service.repository_rows.page(page).per(per_page)
|
||||
@filtered_rows_count = @repository_rows.load.take&.filtered_count || 0
|
||||
|
||||
render 'repository_rows/simple_view_index'
|
||||
end
|
||||
|
|
|
@ -304,7 +304,7 @@ class MyModulesController < ApplicationController
|
|||
|
||||
def protocols
|
||||
@protocol = @my_module.protocol
|
||||
@assigned_repositories = @my_module.live_and_snapshot_repositories_list
|
||||
@assigned_repositories = @my_module.readable_live_and_snapshot_repositories_list(current_user)
|
||||
end
|
||||
|
||||
def protocol
|
||||
|
|
|
@ -44,11 +44,12 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def list
|
||||
results = @repositories
|
||||
results = @repositories.select(:id, :name, 'LOWER(repositories.name)')
|
||||
results = results.name_like(params[:query]) if params[:query].present?
|
||||
results = results.joins(:repository_rows).distinct if params[:non_empty].present?
|
||||
results = results.active if params[:active].present?
|
||||
|
||||
render json: { data: results.map { |r| [r.id, r.name] } }
|
||||
render json: { data: results.order('LOWER(repositories.name) asc').map { |r| [r.id, r.name] } }
|
||||
end
|
||||
|
||||
def rows_list
|
||||
|
@ -59,7 +60,15 @@ class RepositoriesController < ApplicationController
|
|||
params[:query]
|
||||
)
|
||||
end
|
||||
render json: { data: results.map { |r| [r.id, r.name] } }
|
||||
results = results.active if params[:active].present?
|
||||
|
||||
results = results.order('LOWER(repository_rows.name) asc').page(params[:page])
|
||||
|
||||
render json: {
|
||||
paginated: true,
|
||||
next_page: results.next_page,
|
||||
data: results.map { |r| [r.id, r.name] }
|
||||
}
|
||||
end
|
||||
|
||||
def sidebar
|
||||
|
|
|
@ -28,16 +28,10 @@ class RepositoryRowsController < ApplicationController
|
|||
|
||||
@all_rows_count = datatable_service.all_count
|
||||
@columns_mappings = datatable_service.mappings
|
||||
@repository_rows = datatable_service.repository_rows
|
||||
.preload(:repository_columns,
|
||||
:created_by,
|
||||
:archived_by,
|
||||
:last_modified_by,
|
||||
repository_cells: { value: @repository.cell_preload_includes })
|
||||
.page(page)
|
||||
.per(per_page)
|
||||
|
||||
@repository_rows = @repository_rows.where(archived: params[:archived]) unless @repository.archived?
|
||||
repository_rows = datatable_service.repository_rows
|
||||
repository_rows = repository_rows.where(archived: params[:archived]) unless @repository.archived?
|
||||
@repository_rows = repository_rows.page(page).per(per_page)
|
||||
@filtered_rows_count = @repository_rows.load.take&.filtered_count || 0
|
||||
rescue RepositoryFilters::ColumnNotFoundException
|
||||
render json: { custom_error: I18n.t('repositories.show.repository_filter.errors.column_not_found') }
|
||||
rescue RepositoryFilters::ValueNotFoundException
|
||||
|
|
|
@ -26,12 +26,14 @@ class StorageLocationRepositoryRowsController < ApplicationController
|
|||
created_by: current_user
|
||||
)
|
||||
|
||||
if @storage_location_repository_row.save
|
||||
log_activity(:storage_location_repository_row_created)
|
||||
render json: @storage_location_repository_row,
|
||||
serializer: Lists::StorageLocationRepositoryRowSerializer
|
||||
else
|
||||
render json: { errors: @storage_location_repository_row.errors.full_messages }, status: :unprocessable_entity
|
||||
@storage_location_repository_row.with_lock do
|
||||
if @storage_location_repository_row.save
|
||||
log_activity(:storage_location_repository_row_created)
|
||||
render json: @storage_location_repository_row,
|
||||
serializer: Lists::StorageLocationRepositoryRowSerializer
|
||||
else
|
||||
render json: { errors: @storage_location_repository_row.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -131,7 +133,7 @@ class StorageLocationRepositoryRowsController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
team: @storage_location.team,
|
||||
subject: @storage_location_repository_row.repository_row,
|
||||
subject: @storage_location_repository_row.storage_location,
|
||||
message_items: {
|
||||
storage_location: @storage_location_repository_row.storage_location_id,
|
||||
repository_row: @storage_location_repository_row.repository_row_id,
|
||||
|
|
|
@ -9,9 +9,9 @@ class StorageLocationsController < ApplicationController
|
|||
before_action :switch_team_with_param, only: %i(index show)
|
||||
before_action :check_storage_locations_enabled, except: :unassign_rows
|
||||
before_action :load_storage_location, only: %i(update destroy duplicate move show available_positions unassign_rows export_container import_container)
|
||||
before_action :check_read_permissions, except: %i(index create tree actions_toolbar)
|
||||
before_action :check_read_permissions, except: %i(index create tree actions_toolbar import_container unassign_rows)
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_manage_permissions, only: %i(update destroy duplicate move unassign_rows import_container)
|
||||
before_action :check_manage_permissions, only: %i(update destroy duplicate move)
|
||||
before_action :set_breadcrumbs_items, only: %i(index show)
|
||||
|
||||
def index
|
||||
|
@ -86,7 +86,7 @@ class StorageLocationsController < ApplicationController
|
|||
|
||||
def duplicate
|
||||
ActiveRecord::Base.transaction do
|
||||
new_storage_location = @storage_location.duplicate!
|
||||
new_storage_location = @storage_location.duplicate!(current_user)
|
||||
if new_storage_location
|
||||
@storage_location = new_storage_location
|
||||
log_activity('storage_location_created')
|
||||
|
@ -123,8 +123,17 @@ class StorageLocationsController < ApplicationController
|
|||
end
|
||||
|
||||
def tree
|
||||
records = StorageLocation.viewable_by_user(current_user, current_team).where(parent: nil, container: [false, params[:container] == 'true'])
|
||||
render json: storage_locations_recursive_builder(records)
|
||||
records = StorageLocation.viewable_by_user(current_user, current_team)
|
||||
.where(
|
||||
parent: nil,
|
||||
container: [false, params[:container] == 'true']
|
||||
)
|
||||
records = records.where(team_id: params[:team_id]) if params[:team_id]
|
||||
|
||||
render json: {
|
||||
locations: storage_locations_recursive_builder(records),
|
||||
movable_to_root: params[:team_id] && current_team.id == params[:team_id].to_i
|
||||
}
|
||||
end
|
||||
|
||||
def available_positions
|
||||
|
@ -253,7 +262,7 @@ class StorageLocationsController < ApplicationController
|
|||
end
|
||||
|
||||
def storage_locations_recursive_builder(storage_locations)
|
||||
storage_locations.map do |storage_location|
|
||||
storage_locations.order('LOWER(storage_locations.name) ASC').map do |storage_location|
|
||||
{
|
||||
storage_location: storage_location,
|
||||
can_manage: (can_manage_storage_location?(storage_location) unless storage_location.parent_id),
|
||||
|
|
|
@ -163,9 +163,8 @@ module Users
|
|||
error_message ||= I18n.t('devise.okta.errors.generic')
|
||||
redirect_to after_omniauth_failure_path_for(resource_name)
|
||||
ensure
|
||||
if user&.errors.present? || missing_attribute.present?
|
||||
missing_attribute ||= user.errors.first.attribute.capitalize
|
||||
set_flash_message(:alert, :missing_attribute, attribute: missing_attribute)
|
||||
if user&.errors.present?
|
||||
set_flash_message(:alert, :missing_attribute, attribute: user.errors.first.attribute.capitalize)
|
||||
elsif error_message
|
||||
set_flash_message(:alert, :failure, kind: I18n.t('devise.okta.provider_name'), reason: error_message)
|
||||
else
|
||||
|
|
|
@ -141,10 +141,9 @@ module ApplicationHelper
|
|||
# Check if text have smart annotations of users
|
||||
# and outputs a popover with user information
|
||||
def smart_annotation_filter_users(text, team, base64_encoded_imgs: false)
|
||||
sa_user = /\[\@(.*?)~([0-9a-zA-Z]+)\]/
|
||||
text.gsub(sa_user) do |el|
|
||||
match = el.match(sa_user)
|
||||
user = User.find_by_id(match[2].base62_decode)
|
||||
text.gsub(SmartAnnotations::TagToHtml::USER_REGEX) do |el|
|
||||
match = el.match(SmartAnnotations::TagToHtml::USER_REGEX)
|
||||
user = User.find_by(id: match[2].base62_decode)
|
||||
next unless user
|
||||
|
||||
popover_for_user_name(user, team, false, false, base64_encoded_imgs)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
module GlobalActivitiesHelper
|
||||
include ActionView::Helpers::AssetTagHelper
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
include InputSanitizeHelper
|
||||
|
||||
def generate_activity_content(activity, no_links: false, no_custom_links: false)
|
||||
|
@ -60,6 +61,9 @@ module GlobalActivitiesHelper
|
|||
when Repository
|
||||
path = repository_path(obj, team: obj.team.id)
|
||||
when RepositoryRow
|
||||
# Handle private repository rows
|
||||
return I18n.t('storage_locations.show.hidden') unless can_read_repository?(obj.repository)
|
||||
|
||||
return current_value unless obj.repository
|
||||
|
||||
path = repository_path(obj.repository, team: obj.repository.team.id)
|
||||
|
@ -127,6 +131,8 @@ module GlobalActivitiesHelper
|
|||
message_item['type'].constantize.new
|
||||
end
|
||||
|
||||
return I18n.t('storage_locations.show.hidden') if obj.is_a?(RepositoryRow) && !can_read_repository?(obj.repository)
|
||||
|
||||
return I18n.t('projects.index.breadcrumbs_root') if obj.is_a?(ProjectFolder) && obj.new_record?
|
||||
|
||||
return I18n.t('storage_locations.index.breadcrumbs_root') if obj.is_a?(StorageLocation) && obj.new_record?
|
||||
|
|
|
@ -46,9 +46,8 @@ module InputSanitizeHelper
|
|||
sanitizer_config = Constants::INPUT_SANITIZE_CONFIG.deep_dup
|
||||
text = sanitize_input(text, tags, sanitizer_config: sanitizer_config)
|
||||
|
||||
if text =~ SmartAnnotations::TagToHtml::USER_REGEX || text =~ SmartAnnotations::TagToHtml::REGEX
|
||||
text = smart_annotation_parser(text, team, base64_encoded_imgs, preview_repository)
|
||||
end
|
||||
text = smart_annotation_parser(text, team, base64_encoded_imgs, preview_repository) if text.match?(SmartAnnotations::TagToHtml::ALL_REGEX)
|
||||
|
||||
auto_link(
|
||||
text,
|
||||
html: { target: '_blank' },
|
||||
|
|
|
@ -5,44 +5,38 @@ module RepositoryDatatableHelper
|
|||
include Rails.application.routes.url_helpers
|
||||
|
||||
def prepare_row_columns(repository_rows, repository, columns_mappings, team, options = {})
|
||||
# repository_rows collection is already preloaded in controllers, do not modify scopes or query params
|
||||
# otherwise it will result in duplicated SQL queries
|
||||
has_stock_management = repository.has_stock_management?
|
||||
stock_management_column_exists = repository.repository_columns.stock_type.exists?
|
||||
repository_row_connections_enabled = Repository.repository_row_connections_enabled?
|
||||
reminders_enabled = Repository.reminders_enabled?
|
||||
repository_rows = reminders_enabled ? with_reminders_status(repository_rows, repository) : repository_rows
|
||||
stock_managable = has_stock_management && !options[:disable_stock_management] &&
|
||||
can_manage_repository_stock?(repository) &&
|
||||
!repository.is_a?(SoftLockedRepository)
|
||||
stock_consumption_permitted = has_stock_management && options[:include_stock_consumption] && options[:my_module] &&
|
||||
stock_consumption_permitted?(repository, options[:my_module])
|
||||
default_columns_method_name = "#{repository.class.name.underscore}_default_columns"
|
||||
|
||||
repository_rows.map do |record|
|
||||
row = public_send("#{repository.class.name.underscore}_default_columns", record)
|
||||
row.merge!(
|
||||
DT_RowId: record.id,
|
||||
DT_RowAttr: { 'data-state': row_style(record), 'data-e2e': "e2e-TR-invInventory-bodyRow-#{record.id}" },
|
||||
recordInfoUrl: Rails.application.routes.url_helpers.repository_repository_row_path(repository, record),
|
||||
rowRemindersUrl:
|
||||
Rails.application.routes.url_helpers
|
||||
.active_reminder_repository_cells_repository_repository_row_url(
|
||||
repository,
|
||||
record
|
||||
),
|
||||
relationshipsUrl:
|
||||
Rails.application.routes.url_helpers
|
||||
.relationships_repository_repository_row_url(record.repository_id, record.id),
|
||||
relationships_enabled: repository_row_connections_enabled,
|
||||
code: record.code
|
||||
)
|
||||
|
||||
if reminders_enabled
|
||||
row['hasActiveReminders'] = record.has_active_stock_reminders || record.has_active_datetime_reminders
|
||||
end
|
||||
row = public_send(default_columns_method_name, record)
|
||||
row['code'] = record.code
|
||||
row['DT_RowId'] = record.id
|
||||
row['DT_RowAttr'] = { 'data-state': row_style(record), 'data-e2e': "e2e-TR-invInventory-bodyRow-#{record.id}" }
|
||||
row['recordInfoUrl'] = Rails.application.routes.url_helpers.repository_repository_row_path(repository.id, record.id)
|
||||
row['rowRemindersUrl'] = Rails.application.routes.url_helpers
|
||||
.active_reminder_repository_cells_repository_repository_row_url(repository.id, record.id)
|
||||
row['relationshipsUrl'] = Rails.application.routes.url_helpers
|
||||
.relationships_repository_repository_row_url(record.repository_id, record.id)
|
||||
row['relationships_enabled'] = repository_row_connections_enabled
|
||||
row['hasActiveReminders'] = record.has_active_reminders if reminders_enabled
|
||||
|
||||
unless options[:view_mode] || repository.is_a?(SoftLockedRepository)
|
||||
row['recordUpdateUrl'] =
|
||||
Rails.application.routes.url_helpers.repository_repository_row_path(repository, record)
|
||||
row['recordEditable'] = record.editable?
|
||||
|
||||
# if the editable? property will be checked in a separate request, we can default it to true
|
||||
row['recordEditable'] = options[:omit_editable] ? true : record.editable?
|
||||
end
|
||||
|
||||
row['0'] = record[:row_assigned] if options[:my_module]
|
||||
|
@ -51,8 +45,7 @@ module RepositoryDatatableHelper
|
|||
custom_cells = record.repository_cells.filter { |cell| cell.value_type != 'RepositoryStockValue' }
|
||||
|
||||
custom_cells.each do |cell|
|
||||
row[columns_mappings[cell.repository_column.id]] =
|
||||
serialize_repository_cell_value(cell, team, repository, reminders_enabled: reminders_enabled)
|
||||
row[columns_mappings[cell.repository_column_id]] = serialize_repository_cell_value(cell, team, repository, reminders_enabled: reminders_enabled)
|
||||
end
|
||||
|
||||
if has_stock_management
|
||||
|
@ -105,9 +98,10 @@ module RepositoryDatatableHelper
|
|||
end
|
||||
|
||||
def prepare_simple_view_row_columns(repository_rows, repository, my_module, options = {})
|
||||
# repository_rows collection is already preloaded in controllers, do not modify scopes or query params
|
||||
# otherwise it will result in duplicated SQL queries
|
||||
has_stock_management = repository.has_stock_management?
|
||||
reminders_enabled = !options[:disable_reminders] && Repository.reminders_enabled?
|
||||
repository_rows = reminders_enabled ? with_reminders_status(repository_rows, repository) : repository_rows
|
||||
# Always disabled in a simple view
|
||||
stock_managable = false
|
||||
stock_consumption_permitted = has_stock_management && stock_consumption_permitted?(repository, my_module)
|
||||
|
@ -126,9 +120,7 @@ module RepositoryDatatableHelper
|
|||
)
|
||||
}
|
||||
|
||||
if reminders_enabled
|
||||
row['hasActiveReminders'] = record.has_active_stock_reminders || record.has_active_datetime_reminders
|
||||
end
|
||||
row['hasActiveReminders'] = record.has_active_reminders if reminders_enabled
|
||||
|
||||
if has_stock_management
|
||||
stock_present = record.repository_stock_cell.present?
|
||||
|
@ -193,15 +185,14 @@ module RepositoryDatatableHelper
|
|||
'1': record.code,
|
||||
'2': escape_input(record.name),
|
||||
'3': I18n.l(record.created_at, format: :full),
|
||||
'4': escape_input(record.created_by.full_name),
|
||||
'4': escape_input(record.created_by_full_name),
|
||||
'recordInfoUrl': Rails.application.routes.url_helpers
|
||||
.repository_repository_row_path(repository_snapshot, record)
|
||||
}
|
||||
|
||||
# Add custom columns
|
||||
record.repository_cells.each do |cell|
|
||||
row[columns_mappings[cell.repository_column.id]] =
|
||||
serialize_repository_cell_value(cell, team, repository_snapshot)
|
||||
row[columns_mappings[cell.repository_column_id]] = serialize_repository_cell_value(cell, team, repository_snapshot)
|
||||
end
|
||||
|
||||
if has_stock_management
|
||||
|
@ -239,11 +230,11 @@ module RepositoryDatatableHelper
|
|||
'3': escape_input(record.name),
|
||||
'4': "#{record.parent_connections_count || 0} / #{record.child_connections_count || 0}",
|
||||
'5': I18n.l(record.created_at, format: :full),
|
||||
'6': escape_input(record.created_by.full_name),
|
||||
'6': escape_input(record.created_by_full_name),
|
||||
'7': (record.updated_at ? I18n.l(record.updated_at, format: :full) : ''),
|
||||
'8': escape_input(record.last_modified_by.full_name),
|
||||
'8': escape_input(record.last_modified_by_full_name),
|
||||
'9': (record.archived_on ? I18n.l(record.archived_on, format: :full) : ''),
|
||||
'10': escape_input(record.archived_by&.full_name)
|
||||
'10': escape_input(record.archived_by_full_name)
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -254,9 +245,9 @@ module RepositoryDatatableHelper
|
|||
'3': escape_input(record.name),
|
||||
'4': "#{record.parent_connections_count || 0} / #{record.child_connections_count || 0}",
|
||||
'5': I18n.l(record.created_at, format: :full),
|
||||
'6': escape_input(record.created_by.full_name),
|
||||
'6': escape_input(record.created_by_full_name),
|
||||
'7': (record.archived_on ? I18n.l(record.archived_on, format: :full) : ''),
|
||||
'8': escape_input(record.archived_by&.full_name)
|
||||
'8': escape_input(record.archived_by_full_name)
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -266,9 +257,9 @@ module RepositoryDatatableHelper
|
|||
'2': record.code,
|
||||
'3': escape_input(record.name),
|
||||
'4': I18n.l(record.created_at, format: :full),
|
||||
'5': escape_input(record.created_by.full_name),
|
||||
'5': escape_input(record.created_by_full_name),
|
||||
'6': (record.archived_on ? I18n.l(record.archived_on, format: :full) : ''),
|
||||
'7': escape_input(record.archived_by&.full_name),
|
||||
'7': escape_input(record.archived_by_full_name),
|
||||
'8': escape_input(record.external_id)
|
||||
}
|
||||
end
|
||||
|
@ -313,35 +304,6 @@ module RepositoryDatatableHelper
|
|||
''
|
||||
end
|
||||
|
||||
def with_reminders_status(repository_rows, repository)
|
||||
# don't load reminders for archived repositories or snapshots
|
||||
if repository.archived? || repository.is_a?(RepositorySnapshot)
|
||||
return repository_rows.select('FALSE AS has_active_stock_reminders')
|
||||
.select('FALSE AS has_active_datetime_reminders')
|
||||
end
|
||||
|
||||
repository_cells = RepositoryCell.joins(
|
||||
"INNER JOIN repository_columns ON repository_columns.id = repository_cells.repository_column_id " \
|
||||
"AND repository_columns.repository_id = #{repository.id}"
|
||||
)
|
||||
|
||||
repository_rows
|
||||
.joins(
|
||||
"LEFT OUTER JOIN (#{RepositoryCell.stock_reminder_repository_cells_scope(repository_cells, current_user)
|
||||
.select(:id, :repository_row_id).to_sql}) " \
|
||||
"AS repository_cells_with_active_stock_reminders " \
|
||||
"ON repository_cells_with_active_stock_reminders.repository_row_id = repository_rows.id"
|
||||
)
|
||||
.joins(
|
||||
"LEFT OUTER JOIN (#{RepositoryCell.date_time_reminder_repository_cells_scope(repository_cells, current_user)
|
||||
.select(:id, :repository_row_id).to_sql}) " \
|
||||
"AS repository_cells_with_active_datetime_reminders " \
|
||||
"ON repository_cells_with_active_datetime_reminders.repository_row_id = repository_rows.id"
|
||||
)
|
||||
.select('COUNT(repository_cells_with_active_stock_reminders.id) > 0 AS has_active_stock_reminders')
|
||||
.select('COUNT(repository_cells_with_active_datetime_reminders.id) > 0 AS has_active_datetime_reminders')
|
||||
end
|
||||
|
||||
def stock_consumption_permitted?(repository, my_module)
|
||||
return false unless repository.is_a?(Repository) && current_user
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
module StorageLocationsHelper
|
||||
def storage_locations_placeholder
|
||||
return if StorageLocation.storage_locations_enabled?
|
||||
|
||||
"<div class=\"p-4 rounded bg-sn-super-light-blue\">
|
||||
#{I18n.t('storage_locations.storage_locations_disabled')}
|
||||
</div>"
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
{{ i18n.t('repositories.locations.assign') }}
|
||||
</button>
|
||||
</div>
|
||||
<template v-if="repositoryRow.storage_locations.enabled" v-for="(location, index) in repositoryRow.storage_locations.locations" :key="location.id">
|
||||
<div class="mb-4">
|
||||
<div v-html="repositoryRow.storage_locations.placeholder"></div>
|
||||
</div>
|
||||
<template v-for="(location, index) in repositoryRow.storage_locations.locations" :key="location.id">
|
||||
<div>
|
||||
<div class="sci-divider my-4" v-if="index > 0"></div>
|
||||
<div class="flex gap-2 mb-3">
|
||||
|
@ -30,15 +33,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else>
|
||||
<div v-html="repositoryRow.storage_locations.placeholder"></div>
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<AssignModal
|
||||
v-if="openAssignModal"
|
||||
assignMode="assign"
|
||||
:selectedRow="repositoryRow.id"
|
||||
@close="openAssignModal = false; $emit('reloadRow')"
|
||||
:selectedRowName="repositoryRow.default_columns.name"
|
||||
@close="openAssignModal = false; $emit('reloadRow'); reloadStorageLocations()"
|
||||
></AssignModal>
|
||||
<ConfirmationModal
|
||||
:title="i18n.t('storage_locations.show.unassign_modal.title')"
|
||||
|
@ -86,6 +87,11 @@ export default {
|
|||
}
|
||||
return '';
|
||||
},
|
||||
reloadStorageLocations() {
|
||||
if (window.StorageLocationsContainer) {
|
||||
window.StorageLocationsContainer.$refs.container.reloadingTable = true;
|
||||
}
|
||||
},
|
||||
numberToLetter(number) {
|
||||
return String.fromCharCode(96 + number);
|
||||
},
|
||||
|
@ -95,9 +101,7 @@ export default {
|
|||
axios.post(unassign_rows_storage_location_path({ id: locationId }), { ids: [rowId] })
|
||||
.then(() => {
|
||||
this.$emit('reloadRow');
|
||||
if (window.StorageLocationsContainer) {
|
||||
window.StorageLocationsContainer.$refs.container.reloadingTable = true;
|
||||
}
|
||||
this.reloadStorageLocations();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,10 +24,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
|
||||
export default {
|
||||
name: 'ActionToolbar',
|
||||
props: {
|
||||
actionsUrl: { type: String, required: true },
|
||||
actionsMethod: { type: String, default: 'get' },
|
||||
params: { type: Object },
|
||||
},
|
||||
data() {
|
||||
|
@ -51,8 +54,14 @@ export default {
|
|||
loadActions() {
|
||||
this.loading = true;
|
||||
this.loaded = false;
|
||||
$.get(`${this.actionsUrl}?${new URLSearchParams(this.params).toString()}`, (data) => {
|
||||
this.actions = data.actions;
|
||||
|
||||
axios.request({
|
||||
method: this.actionsMethod,
|
||||
url: this.actionsUrl,
|
||||
params: this.actionsMethod === 'get' && this.params,
|
||||
data: this.actionsMethod !== 'get' && this.params
|
||||
}).then((response) => {
|
||||
this.actions = response.data.actions;
|
||||
this.loading = false;
|
||||
this.loaded = true;
|
||||
});
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
<ActionToolbar
|
||||
v-if="selectedRows.length > 0 && actionsUrl"
|
||||
:actionsUrl="actionsUrl"
|
||||
:actionsMethod="actionsMethod"
|
||||
:params="actionsParams"
|
||||
@toolbar:action="emitAction" />
|
||||
</div>
|
||||
|
@ -145,6 +146,9 @@ export default {
|
|||
actionsUrl: {
|
||||
type: String
|
||||
},
|
||||
actionsMethod: {
|
||||
type: String
|
||||
},
|
||||
toolbarActions: {
|
||||
type: Object,
|
||||
required: true
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
v-else
|
||||
v-model="query"
|
||||
:placeholder="placeholderRender"
|
||||
@keyup="fetchOptions"
|
||||
@keyup="reloadItems"
|
||||
@change.stop
|
||||
class="w-full bg-transparent border-0 outline-none pl-0 placeholder:text-sn-grey" />
|
||||
</template>
|
||||
|
@ -70,7 +70,7 @@
|
|||
{{ i18n.t('general.select_all') }}
|
||||
</div>
|
||||
</div>
|
||||
<perfect-scrollbar class="p-2.5 flex flex-col max-h-80 relative" :class="{ 'pt-0': withCheckboxes }">
|
||||
<perfect-scrollbar ref="scrollContainer" class="p-2.5 flex flex-col max-h-80 relative" :class="{ 'pt-0': withCheckboxes }">
|
||||
<template v-for="(option, i) in filteredOptions" :key="option[0]">
|
||||
<div
|
||||
@click.stop="setValue(option[0])"
|
||||
|
@ -143,7 +143,8 @@ export default {
|
|||
query: '',
|
||||
fixedWidth: true,
|
||||
focusedOption: null,
|
||||
skipQueryCallback: false
|
||||
skipQueryCallback: false,
|
||||
nextPage: 1
|
||||
};
|
||||
},
|
||||
mixins: [FixedFlyoutMixin],
|
||||
|
@ -270,19 +271,31 @@ export default {
|
|||
this.$nextTick(() => {
|
||||
this.setPosition();
|
||||
this.$refs.search?.focus();
|
||||
this.$refs.scrollContainer.$el.addEventListener('scroll', this.loadNextPage);
|
||||
});
|
||||
}
|
||||
},
|
||||
urlParams: {
|
||||
handler(oldVal, newVal) {
|
||||
if (!this.compareObjects(oldVal, newVal)) {
|
||||
this.fetchOptions();
|
||||
this.reloadItems();
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reloadItems() {
|
||||
this.fetchedOptions = [];
|
||||
this.nextPage = 1;
|
||||
this.fetchOptions();
|
||||
},
|
||||
loadNextPage() {
|
||||
const container = this.$refs.scrollContainer.$el;
|
||||
if (this.nextPage && container.scrollTop + container.clientHeight >= container.scrollHeight) {
|
||||
this.fetchOptions();
|
||||
}
|
||||
},
|
||||
renderLabel(option) {
|
||||
if (!option) return false;
|
||||
|
||||
|
@ -354,10 +367,15 @@ export default {
|
|||
},
|
||||
fetchOptions() {
|
||||
if (this.optionsUrl) {
|
||||
const params = { query: this.query, ...this.urlParams };
|
||||
const params = { query: this.query, page: this.nextPage, ...this.urlParams };
|
||||
axios.get(this.optionsUrl, { params })
|
||||
.then((response) => {
|
||||
this.fetchedOptions = response.data.data;
|
||||
if (response.data.paginated) {
|
||||
this.fetchedOptions = [...this.fetchedOptions, ...response.data.data];
|
||||
this.nextPage = response.data.next_page;
|
||||
} else {
|
||||
this.fetchedOptions = response.data.data;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.setPosition();
|
||||
});
|
||||
|
|
|
@ -11,12 +11,13 @@
|
|||
</div>
|
||||
<div class="h-full bg-white px-4">
|
||||
<DataTable :columnDefs="columnDefs"
|
||||
tableId="StorageLocationsContainer"
|
||||
:tableId="tableId"
|
||||
:dataUrl="dataSource"
|
||||
ref="table"
|
||||
:reloadingTable="reloadingTable"
|
||||
:toolbarActions="toolbarActions"
|
||||
:actionsUrl="actionsUrl"
|
||||
:actionsMethod="'post'"
|
||||
:scrollMode="paginationMode"
|
||||
@assign="assignRow"
|
||||
@move="moveRow"
|
||||
|
@ -123,7 +124,9 @@ export default {
|
|||
paginationMode() {
|
||||
return this.withGrid ? 'none' : 'pages';
|
||||
},
|
||||
|
||||
tableId() {
|
||||
return this.withGrid ? 'StorageLocationsContainerGrid' : 'StorageLocationsContainer';
|
||||
},
|
||||
columnDefs() {
|
||||
let columns = [];
|
||||
|
||||
|
|
|
@ -10,12 +10,18 @@
|
|||
<h4 v-if="selectedPosition" class="modal-title truncate !block">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.selected_position_title`, { position: formattedPosition }) }}
|
||||
</h4>
|
||||
<h4 v-else-if="selectedRow && selectedRowName" class="modal-title truncate !block">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.selected_row_title`) }}
|
||||
</h4>
|
||||
<h4 v-else class="modal-title truncate !block">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.${assignMode}_title`) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="mb-4">
|
||||
<p v-if="selectedRow && selectedRowName" class="mb-4">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.selected_row_description`, { name: selectedRowName }) }}
|
||||
</p>
|
||||
<p v-else class="mb-4">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.${assignMode}_description`) }}
|
||||
</p>
|
||||
<RowSelector v-if="!selectedRow" @change="this.rowId = $event" class="mb-4"></RowSelector>
|
||||
|
@ -56,6 +62,7 @@ export default {
|
|||
name: 'NewProjectModal',
|
||||
props: {
|
||||
selectedRow: Number,
|
||||
selectedRowName: String,
|
||||
selectedContainer: Number,
|
||||
cellId: Number,
|
||||
selectedPosition: Array,
|
||||
|
@ -85,7 +92,8 @@ export default {
|
|||
return {
|
||||
rowId: this.selectedRow,
|
||||
containerId: this.selectedContainer,
|
||||
position: this.selectedPosition
|
||||
position: this.selectedPosition,
|
||||
saving: false
|
||||
};
|
||||
},
|
||||
components: {
|
||||
|
@ -95,13 +103,21 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
submit() {
|
||||
if (this.saving) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.saving = true;
|
||||
|
||||
axios.post(this.actionUrl, {
|
||||
repository_row_id: this.rowId,
|
||||
metadata: { position: this.position?.map((pos) => parseInt(pos, 10)) }
|
||||
}).then(() => {
|
||||
this.$emit('close');
|
||||
this.saving = false;
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.errors.join(', '), 'danger');
|
||||
this.saving = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,33 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<div class="sci-input-container-v2 left-icon">
|
||||
<input type="text"
|
||||
v-model="query"
|
||||
class="sci-input-field"
|
||||
ref="input"
|
||||
autofocus="true"
|
||||
:placeholder=" i18n.t('storage_locations.index.move_modal.placeholder.find_storage_locations')" />
|
||||
<i class="sn-icon sn-icon-search"></i>
|
||||
<div v-if="dataLoaded">
|
||||
<div v-if="storageLocationsTree.length > 0">
|
||||
<div class="mb-4">
|
||||
<div class="sci-input-container-v2 left-icon">
|
||||
<input type="text"
|
||||
v-model="query"
|
||||
class="sci-input-field"
|
||||
ref="input"
|
||||
autofocus="true"
|
||||
:placeholder=" i18n.t('storage_locations.index.move_modal.placeholder.find_storage_locations')" />
|
||||
<i class="sn-icon sn-icon-search"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-h-80 overflow-y-auto">
|
||||
<div class="p-2 flex items-center gap-2 cursor-pointer text-sn-blue hover:bg-sn-super-light-grey"
|
||||
@click="selectStorageLocation(null)"
|
||||
:class="{'!bg-sn-super-light-blue': selectedStorageLocationId == null}">
|
||||
<i class="sn-icon sn-icon-projects"></i>
|
||||
{{ i18n.t('storage_locations.index.move_modal.search_header') }}
|
||||
</div>
|
||||
<MoveTree
|
||||
:storageLocationsTree="filteredStorageLocationsTree"
|
||||
:moveMode="moveMode"
|
||||
:value="selectedStorageLocationId"
|
||||
@selectStorageLocation="selectStorageLocation" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-h-80 overflow-y-auto">
|
||||
<div class="p-2 flex items-center gap-2 cursor-pointer text-sn-blue hover:bg-sn-super-light-grey"
|
||||
@click="selectStorageLocation(null)"
|
||||
:class="{'!bg-sn-super-light-blue': selectedStorageLocationId == null}">
|
||||
<i class="sn-icon sn-icon-projects"></i>
|
||||
{{ i18n.t('storage_locations.index.move_modal.search_header') }}
|
||||
</div>
|
||||
<MoveTree
|
||||
:storageLocationsTree="filteredStorageLocationsTree"
|
||||
:moveMode="moveMode"
|
||||
:value="selectedStorageLocationId"
|
||||
@selectStorageLocation="selectStorageLocation" />
|
||||
</div>
|
||||
<div v-else class="py-2 text-sn-dark-grey" v-html="i18n.t('storage_locations.index.move_modal.no_results')"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ export default {
|
|||
watch: {
|
||||
selectedRow() {
|
||||
[[this.selectedColumn]] = this.availableColumns;
|
||||
this.$emit('change', [this.selectedRow, this.selectedColumn]);
|
||||
},
|
||||
selectedColumn() {
|
||||
this.$emit('change', [this.selectedRow, this.selectedColumn]);
|
||||
|
@ -62,15 +63,23 @@ export default {
|
|||
return available_positions_storage_location_path(this.selectedContainerId);
|
||||
},
|
||||
availableRows() {
|
||||
if (!this.availablePositions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.keys(this.availablePositions).map((row) => [row, this.convertNumberToLetter(row)]);
|
||||
},
|
||||
availableColumns() {
|
||||
if (!this.availablePositions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (this.availablePositions[this.selectedRow] || []).map((col) => [col, col]);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
availablePositions: {},
|
||||
availablePositions: null,
|
||||
selectedRow: null,
|
||||
selectedColumn: null
|
||||
};
|
||||
|
|
|
@ -48,14 +48,14 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
repositoriesUrl() {
|
||||
return list_team_repositories_path(this.teamId, { non_empty: true });
|
||||
return list_team_repositories_path(this.teamId, { non_empty: true, active: true });
|
||||
},
|
||||
rowsUrl() {
|
||||
if (!this.selectedRepository) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return rows_list_team_repositories_path(this.teamId);
|
||||
return rows_list_team_repositories_path(this.teamId, { active: true });
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
|
@ -49,6 +49,10 @@
|
|||
{{ i18n.t('general.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="loading" class="flex absolute top-0 left-0 items-center justify-center w-full flex-grow h-full z-10">
|
||||
<div class="absolute top-0 left-0 w-full h-full bg-black opacity-20"></div>
|
||||
<img src="/images/medium/loading.svg" alt="Loading" class="p-4 rounded-xl bg-sn-white relative z-10" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -78,7 +82,8 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
error: null
|
||||
error: null,
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -99,7 +104,7 @@ export default {
|
|||
},
|
||||
uploadFile(file) {
|
||||
const formData = new FormData();
|
||||
|
||||
this.loading = true;
|
||||
// required payload
|
||||
formData.append('file', file);
|
||||
|
||||
|
@ -108,9 +113,11 @@ export default {
|
|||
})
|
||||
.then(() => {
|
||||
this.$emit('reloadTable');
|
||||
this.loading = false;
|
||||
this.close();
|
||||
}).catch((error) => {
|
||||
this.handleError(error.response.data.message);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,9 +25,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="max-h-80 overflow-y-auto">
|
||||
<div class="p-2 flex items-center gap-2 cursor-pointer text-sn-blue hover:bg-sn-super-light-grey"
|
||||
@click="selectStorageLocation(null)"
|
||||
:class="{'!bg-sn-super-light-blue': selectedStorageLocationId == null}">
|
||||
<div class="p-2 flex items-center gap-2 "
|
||||
@click="selectStorageLocation(null)"
|
||||
:class="{
|
||||
'!bg-sn-super-light-blue': selectedStorageLocationId == null,
|
||||
'cursor-pointer text-sn-blue hover:bg-sn-super-light-grey': movableToRoot
|
||||
}">
|
||||
<i class="sn-icon sn-icon-projects"></i>
|
||||
{{ i18n.t('storage_locations.index.move_modal.search_header') }}
|
||||
</div>
|
||||
|
@ -63,11 +66,15 @@ export default {
|
|||
selectedObject: Array,
|
||||
moveToUrl: String
|
||||
},
|
||||
created() {
|
||||
this.teamId = this.selectedObject.team_id;
|
||||
},
|
||||
mixins: [modalMixin, MoveTreeMixin],
|
||||
data() {
|
||||
return {
|
||||
selectedStorageLocationId: null,
|
||||
storageLocationsTree: [],
|
||||
teamId: null,
|
||||
query: '',
|
||||
moveMode: 'locations'
|
||||
};
|
||||
|
|
|
@ -6,15 +6,22 @@ import {
|
|||
|
||||
export default {
|
||||
mounted() {
|
||||
axios.get(this.storageLocationsTreeUrl).then((response) => {
|
||||
this.storageLocationsTree = response.data;
|
||||
axios.get(this.storageLocationsTreeUrl, { params: { team_id: this.teamId } }).then((response) => {
|
||||
this.storageLocationsTree = response.data.locations;
|
||||
this.movableToRoot = response.data.movable_to_root;
|
||||
if (!this.movableToRoot) {
|
||||
this.selectedStorageLocationId = -1;
|
||||
}
|
||||
this.dataLoaded = true;
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedStorageLocationId: null,
|
||||
movableToRoot: false,
|
||||
storageLocationsTree: [],
|
||||
query: ''
|
||||
query: '',
|
||||
dataLoaded: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -34,16 +41,20 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
filteredStorageLocationsTreeHelper(storageLocationsTree) {
|
||||
return storageLocationsTree.map(({ storage_location, children }) => {
|
||||
return storageLocationsTree.map(({ storage_location, children, can_manage }) => {
|
||||
if (storage_location.name.toLowerCase().includes(this.query.toLowerCase())) {
|
||||
return { storage_location, children };
|
||||
return { storage_location, children, can_manage };
|
||||
}
|
||||
|
||||
const filteredChildren = this.filteredStorageLocationsTreeHelper(children);
|
||||
return filteredChildren.length ? { storage_location, children: filteredChildren } : null;
|
||||
return filteredChildren.length ? { storage_location, can_manage, children: filteredChildren } : null;
|
||||
}).filter(Boolean);
|
||||
},
|
||||
selectStorageLocation(storageLocationId) {
|
||||
if (!this.movableToRoot && storageLocationId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedStorageLocationId = storageLocationId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-if="params.data.have_reminders">
|
||||
<div v-if="params.data.has_reminder">
|
||||
<GeneralDropdown ref="dropdown" position="right" @open="getReminders">
|
||||
<template v-slot:field>
|
||||
<i class="sn-icon sn-icon-notifications "></i>
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ReminderRepositoryCellJoinable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def self.reminder_repository_cells_scope(relation, user)
|
||||
relation.joins(
|
||||
'INNER JOIN repository_columns repository_reminder_columns ON ' \
|
||||
'repository_reminder_columns.id = repository_cells.repository_column_id'
|
||||
).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_reminder_columns.metadata ->> \'reminder_value\' <> \'\' AND '\
|
||||
'(repository_date_time_values.data - NOW()) <= '\
|
||||
'(repository_reminder_columns.metadata ->> \'reminder_value\')::int * ' \
|
||||
'(repository_reminder_columns.metadata ->> \'reminder_unit\')::int * interval \'1 sec\''
|
||||
).joins( # stock reminders
|
||||
'LEFT OUTER JOIN "repository_stock_values" ON '\
|
||||
'"repository_cells"."value_type" = \'RepositoryStockValue\' AND '\
|
||||
'"repository_stock_values"."id" = "repository_cells"."value_id" AND '\
|
||||
'(repository_stock_values.amount <= repository_stock_values.low_stock_threshold OR '\
|
||||
' repository_stock_values.amount <= 0)'
|
||||
).joins(
|
||||
'LEFT OUTER JOIN "hidden_repository_cell_reminders" ON '\
|
||||
'"repository_cells"."id" = "hidden_repository_cell_reminders"."repository_cell_id" AND '\
|
||||
'"hidden_repository_cell_reminders"."user_id" = ' + user.id.to_s
|
||||
).where(
|
||||
'hidden_repository_cell_reminders.id IS NULL AND '\
|
||||
'(repository_date_time_values.id IS NOT NULL OR repository_stock_values.id IS NOT NULL)'
|
||||
)
|
||||
end
|
||||
|
||||
def self.stock_reminder_repository_cells_scope(relation, user)
|
||||
relation.joins( # stock reminders
|
||||
'LEFT OUTER JOIN "repository_stock_values" ON ' \
|
||||
'"repository_cells"."value_type" = \'RepositoryStockValue\' AND ' \
|
||||
'"repository_stock_values"."id" = "repository_cells"."value_id" AND ' \
|
||||
'(repository_stock_values.amount <= repository_stock_values.low_stock_threshold OR ' \
|
||||
'repository_stock_values.amount <= 0)'
|
||||
).joins(
|
||||
'LEFT OUTER JOIN "hidden_repository_cell_reminders" ON ' \
|
||||
'"repository_cells"."id" = "hidden_repository_cell_reminders"."repository_cell_id" AND ' \
|
||||
'"hidden_repository_cell_reminders"."user_id" = ' + user.id.to_s
|
||||
).where(
|
||||
'hidden_repository_cell_reminders.id IS NULL AND repository_stock_values.id IS NOT NULL'
|
||||
)
|
||||
end
|
||||
|
||||
def self.date_time_reminder_repository_cells_scope(relation, user)
|
||||
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_value\' <> \'\' AND ' \
|
||||
'(repository_date_time_values.data - NOW()) <= ' \
|
||||
'(repository_columns.metadata ->> \'reminder_value\')::int * ' \
|
||||
'(repository_columns.metadata ->> \'reminder_unit\')::int * interval \'1 sec\''
|
||||
).joins(
|
||||
'LEFT OUTER JOIN "hidden_repository_cell_reminders" ON ' \
|
||||
'"repository_cells"."id" = "hidden_repository_cell_reminders"."repository_cell_id" AND ' \
|
||||
'"hidden_repository_cell_reminders"."user_id" = ' + user.id.to_s
|
||||
).where(
|
||||
'hidden_repository_cell_reminders.id IS NULL AND repository_date_time_values.id IS NOT NULL'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -33,6 +33,7 @@ class Repository < RepositoryBase
|
|||
before_destroy :refresh_report_references_on_destroy, prepend: true
|
||||
after_save :assign_globally_shared_inventories, if: -> { saved_change_to_permission_level? && globally_shared? }
|
||||
after_save :unassign_globally_shared_inventories, if: -> { saved_change_to_permission_level? && !globally_shared? }
|
||||
after_save :unassign_unshared_items, if: :saved_change_to_permission_level
|
||||
after_save :unlink_unshared_items, if: -> { saved_change_to_permission_level? && !globally_shared? }
|
||||
|
||||
validates :name,
|
||||
|
@ -145,6 +146,17 @@ class Repository < RepositoryBase
|
|||
repository_rows.joins(:my_module_repository_rows).where(my_module_repository_rows: { my_module_id: my_module.id })
|
||||
end
|
||||
|
||||
def unassign_unshared_items
|
||||
return if shared_read? || shared_write?
|
||||
|
||||
MyModuleRepositoryRow.joins(my_module: { experiment: { project: :team } })
|
||||
.joins(repository_row: :repository)
|
||||
.where(repository_rows: { repository: self })
|
||||
.where.not(my_module: { experiment: { projects: { team: team } } })
|
||||
.where.not(my_module: { experiment: { projects: { team: teams_shared_with } } })
|
||||
.destroy_all
|
||||
end
|
||||
|
||||
def unlink_unshared_items
|
||||
repository_rows_ids = repository_rows.select(:id)
|
||||
rows_to_unlink = RepositoryRow.joins("LEFT JOIN repository_row_connections \
|
||||
|
|
|
@ -43,6 +43,12 @@ class RepositoryBase < ApplicationRecord
|
|||
@has_stock_management ||= self.class.stock_management_enabled? && repository_columns.stock_type.exists?
|
||||
end
|
||||
|
||||
def has_reminders?
|
||||
@has_reminders ||=
|
||||
self.class.reminders_enabled? &&
|
||||
(repository_columns.stock_type.exists? || repository_columns.where('"repository_columns"."metadata" ->> \'reminder_value\' <> \'\'').exists?)
|
||||
end
|
||||
|
||||
def has_stock_consumption?
|
||||
true
|
||||
end
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryCell < ApplicationRecord
|
||||
include ReminderRepositoryCellJoinable
|
||||
|
||||
attr_accessor :importing, :to_destroy
|
||||
|
||||
belongs_to :repository_row, touch: true
|
||||
|
@ -48,7 +46,47 @@ class RepositoryCell < ApplicationRecord
|
|||
after_touch :update_repository_row_last_modified_by
|
||||
|
||||
scope :with_active_reminder, lambda { |user|
|
||||
reminder_repository_cells_scope(self, user)
|
||||
from(
|
||||
"((#{with_active_stock_reminder(user).to_sql}) UNION ALL " \
|
||||
"(#{with_active_datetime_reminder(user).to_sql})) AS repository_cells"
|
||||
)
|
||||
}
|
||||
|
||||
scope :with_active_stock_reminder, lambda { |user|
|
||||
joins( # stock reminders
|
||||
'LEFT OUTER JOIN "repository_stock_values" ON ' \
|
||||
'"repository_cells"."value_type" = \'RepositoryStockValue\' AND ' \
|
||||
'"repository_stock_values"."id" = "repository_cells"."value_id" AND ' \
|
||||
'(repository_stock_values.amount <= repository_stock_values.low_stock_threshold OR ' \
|
||||
'repository_stock_values.amount <= 0)'
|
||||
).joins(
|
||||
'LEFT OUTER JOIN "hidden_repository_cell_reminders" ON ' \
|
||||
'"repository_cells"."id" = "hidden_repository_cell_reminders"."repository_cell_id" AND ' \
|
||||
'"hidden_repository_cell_reminders"."user_id" = ' + user.id.to_s
|
||||
).where(
|
||||
'hidden_repository_cell_reminders.id IS NULL AND repository_stock_values.id IS NOT NULL'
|
||||
)
|
||||
}
|
||||
|
||||
scope :with_active_datetime_reminder, lambda { |user|
|
||||
joins(
|
||||
'INNER JOIN repository_columns repository_reminder_columns ON ' \
|
||||
'repository_reminder_columns.id = repository_cells.repository_column_id'
|
||||
).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_reminder_columns.metadata ->> \'reminder_value\' <> \'\' AND ' \
|
||||
'(repository_date_time_values.data - NOW()) <= ' \
|
||||
'(repository_reminder_columns.metadata ->> \'reminder_value\')::int * ' \
|
||||
'(repository_reminder_columns.metadata ->> \'reminder_unit\')::int * interval \'1 sec\''
|
||||
).joins(
|
||||
'LEFT OUTER JOIN "hidden_repository_cell_reminders" ON ' \
|
||||
'"repository_cells"."id" = "hidden_repository_cell_reminders"."repository_cell_id" AND ' \
|
||||
'"hidden_repository_cell_reminders"."user_id" = ' + user.id.to_s
|
||||
).where(
|
||||
'hidden_repository_cell_reminders.id IS NULL AND repository_date_time_values.id IS NOT NULL'
|
||||
)
|
||||
}
|
||||
|
||||
def update_repository_row_last_modified_by
|
||||
|
|
|
@ -5,12 +5,11 @@ class RepositoryRow < ApplicationRecord
|
|||
include SearchableModel
|
||||
include SearchableByNameModel
|
||||
include ArchivableModel
|
||||
include ReminderRepositoryCellJoinable
|
||||
|
||||
ID_PREFIX = 'IT'
|
||||
include PrefixedIdModel
|
||||
|
||||
belongs_to :repository, class_name: 'RepositoryBase'
|
||||
belongs_to :repository, class_name: 'RepositoryBase', counter_cache: :repository_rows_count
|
||||
delegate :team, to: :repository
|
||||
belongs_to :parent, class_name: 'RepositoryRow', optional: true
|
||||
belongs_to :created_by, class_name: 'User'
|
||||
|
@ -117,8 +116,8 @@ class RepositoryRow < ApplicationRecord
|
|||
scope :active, -> { where(archived: false) }
|
||||
scope :archived, -> { where(archived: true) }
|
||||
|
||||
scope :with_active_reminders, lambda { |user|
|
||||
reminder_repository_cells_scope(joins(:repository_cells), user)
|
||||
scope :with_active_reminders, lambda { |repository, user|
|
||||
left_outer_joins_active_reminders(repository, user).where.not(repository_cells_with_active_reminders: { id: nil })
|
||||
}
|
||||
|
||||
def code
|
||||
|
@ -171,6 +170,16 @@ class RepositoryRow < ApplicationRecord
|
|||
.update_all(created_by_id: new_owner.id)
|
||||
end
|
||||
|
||||
def self.left_outer_joins_active_reminders(repository, user)
|
||||
repository_cells = RepositoryCell.joins("INNER JOIN repository_columns ON repository_columns.id = repository_cells.repository_column_id AND " \
|
||||
"repository_columns.repository_id = #{repository.id}")
|
||||
joins(
|
||||
"LEFT OUTER JOIN (#{repository_cells.with_active_reminder(user).select(:id, :repository_row_id).to_sql}) " \
|
||||
"AS repository_cells_with_active_reminders " \
|
||||
"ON repository_cells_with_active_reminders.repository_row_id = repository_rows.id"
|
||||
)
|
||||
end
|
||||
|
||||
def editable?
|
||||
true
|
||||
end
|
||||
|
@ -179,14 +188,6 @@ class RepositoryRow < ApplicationRecord
|
|||
self[:archived]
|
||||
end
|
||||
|
||||
def has_reminders?(user)
|
||||
stock_reminders = RepositoryCell.stock_reminder_repository_cells_scope(
|
||||
repository_cells.joins(:repository_column), user)
|
||||
date_reminders = RepositoryCell.date_time_reminder_repository_cells_scope(
|
||||
repository_cells.joins(:repository_column), user)
|
||||
stock_reminders.any? || date_reminders.any?
|
||||
end
|
||||
|
||||
def archived
|
||||
row_archived? || repository&.archived?
|
||||
end
|
||||
|
|
|
@ -10,6 +10,8 @@ class RepositoryTextValue < ApplicationRecord
|
|||
has_one :repository_cell, as: :value, dependent: :destroy, touch: true
|
||||
accepts_nested_attributes_for :repository_cell
|
||||
|
||||
before_save -> { self.has_smart_annotation = data.match?(SmartAnnotations::TagToHtml::ALL_REGEX) }, if: -> { data_changed? }
|
||||
|
||||
validates :repository_cell, presence: true
|
||||
validates :data, presence: true, length: { maximum: Constants::TEXT_MAX_LENGTH }
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ class StorageLocation < ApplicationRecord
|
|||
ID_PREFIX = 'SL'
|
||||
include PrefixedIdModel
|
||||
include Shareable
|
||||
include SearchableModel
|
||||
|
||||
default_scope -> { kept }
|
||||
|
||||
|
@ -20,6 +21,7 @@ class StorageLocation < ApplicationRecord
|
|||
has_many :repository_rows, through: :storage_location_repository_rows
|
||||
|
||||
validates :name, length: { maximum: Constants::NAME_MAX_LENGTH }
|
||||
validate :parent_same_team, if: -> { parent.present? }
|
||||
validate :parent_validation, if: -> { parent.present? }
|
||||
validate :no_grid_options, if: -> { !container }
|
||||
validate :no_dimensions, if: -> { !with_grid? }
|
||||
|
@ -59,13 +61,14 @@ class StorageLocation < ApplicationRecord
|
|||
storage_location_repository_rows.count.zero?
|
||||
end
|
||||
|
||||
def duplicate!
|
||||
def duplicate!(user)
|
||||
ActiveRecord::Base.transaction do
|
||||
new_storage_location = dup
|
||||
new_storage_location.name = next_clone_name
|
||||
new_storage_location.created_by = user
|
||||
new_storage_location.save!
|
||||
copy_image(self, new_storage_location)
|
||||
recursive_duplicate(id, new_storage_location.id)
|
||||
recursive_duplicate(id, new_storage_location.id, user)
|
||||
new_storage_location
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
false
|
||||
|
@ -141,13 +144,14 @@ class StorageLocation < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def recursive_duplicate(old_parent_id = nil, new_parent_id = nil)
|
||||
def recursive_duplicate(old_parent_id = nil, new_parent_id = nil, user = nil)
|
||||
StorageLocation.where(parent_id: old_parent_id).find_each do |child|
|
||||
new_child = child.dup
|
||||
new_child.parent_id = new_parent_id
|
||||
new_child.created_by = user
|
||||
new_child.save!
|
||||
copy_image(child, new_child)
|
||||
recursive_duplicate(child.id, new_child.id)
|
||||
recursive_duplicate(child.id, new_child.id, user)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -200,6 +204,10 @@ class StorageLocation < ApplicationRecord
|
|||
errors.add(:metadata, I18n.t('activerecord.errors.models.storage_location.attributes.metadata.invalid')) if metadata['display_type'] || metadata['dimensions']
|
||||
end
|
||||
|
||||
def parent_same_team
|
||||
errors.add(:parent, I18n.t('activerecord.errors.models.storage_location.attributes.parent_storage_location_team')) if parent.team != team
|
||||
end
|
||||
|
||||
def no_dimensions
|
||||
errors.add(:metadata, I18n.t('activerecord.errors.models.storage_location.attributes.metadata.invalid')) if !with_grid? && metadata['dimensions']
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ class TeamSharedObject < ApplicationRecord
|
|||
|
||||
after_create :assign_shared_inventories, if: -> { shared_object.is_a?(Repository) }
|
||||
before_destroy :unlink_unshared_items, if: -> { shared_object.is_a?(Repository) }
|
||||
before_destroy :unassign_unshared_items, if: -> { shared_object.is_a?(Repository) }
|
||||
before_destroy :unassign_unshared_inventories, if: -> { shared_object.is_a?(Repository) }
|
||||
|
||||
belongs_to :team
|
||||
|
@ -42,6 +43,16 @@ class TeamSharedObject < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def unassign_unshared_items
|
||||
return if shared_object.shared_read? || shared_object.shared_write?
|
||||
|
||||
MyModuleRepositoryRow.joins(my_module: { experiment: { project: :team } })
|
||||
.joins(repository_row: :repository)
|
||||
.where(my_module: { experiment: { projects: { team: team } } })
|
||||
.where(repository_rows: { repository: shared_object })
|
||||
.destroy_all
|
||||
end
|
||||
|
||||
def unassign_unshared_inventories
|
||||
team.repository_sharing_user_assignments.where(assignable: shared_object).find_each(&:destroy!)
|
||||
end
|
||||
|
|
|
@ -104,6 +104,10 @@ Canaid::Permissions.register_for(Repository) do
|
|||
repository.permission_granted?(user, RepositoryPermissions::COLUMNS_CREATE)
|
||||
end
|
||||
|
||||
can :manage_repository_columns do |user, repository|
|
||||
repository.repository_snapshots.provisioning.none? && can_create_repository_columns?(user, repository)
|
||||
end
|
||||
|
||||
# repository: create/update/delete filters
|
||||
can :manage_repository_filters do |user, repository|
|
||||
repository.permission_granted?(user, RepositoryPermissions::FILTERS_MANAGE)
|
||||
|
@ -113,3 +117,11 @@ Canaid::Permissions.register_for(Repository) do
|
|||
RepositoryBase.stock_management_enabled? && can_manage_repository_rows?(user, repository)
|
||||
end
|
||||
end
|
||||
|
||||
Canaid::Permissions.register_for(RepositoryColumn) do
|
||||
# repository: update/delete field
|
||||
# Tested in scope of RepositoryPermissions spec
|
||||
can :manage_repository_column do |user, repository_column|
|
||||
repository_column.repository.repository_snapshots.provisioning.none? && can_create_repository_columns?(user, repository_column.repository)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Canaid::Permissions.register_for(RepositoryColumn) do
|
||||
# repository: update/delete field
|
||||
# Tested in scope of RepositoryPermissions spec
|
||||
can :manage_repository_column do |user, repository_column|
|
||||
managable = repository_column.repository.repository_snapshots.provisioning.none? &&
|
||||
can_create_repository_columns?(user, repository_column.repository)
|
||||
|
||||
managable
|
||||
end
|
||||
end
|
|
@ -9,7 +9,7 @@ module Lists
|
|||
attributes :name, :code, :nr_of_rows, :team, :created_at, :created_by, :archived_on, :archived_by, :urls
|
||||
|
||||
def nr_of_rows
|
||||
object[:row_count]
|
||||
object[:repository_rows_count]
|
||||
end
|
||||
|
||||
def team
|
||||
|
|
|
@ -6,7 +6,7 @@ module Lists
|
|||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :created_by, :created_on, :position, :row_id, :row_name, :hidden, :position_formatted, :stock,
|
||||
:have_reminders, :reminders_url, :row_url, :row_code
|
||||
:has_reminder, :reminders_url, :row_url, :row_code
|
||||
|
||||
def row_id
|
||||
object.repository_row.id
|
||||
|
@ -52,8 +52,8 @@ module Lists
|
|||
@hidden = !can_read_repository?(object.repository_row.repository)
|
||||
end
|
||||
|
||||
def have_reminders
|
||||
object.repository_row.has_reminders?(scope) unless hidden
|
||||
def has_reminder
|
||||
object.repository_row.repository_cells.with_active_reminder(scope).any? unless hidden
|
||||
end
|
||||
|
||||
def reminders_url
|
||||
|
|
|
@ -9,7 +9,7 @@ module Lists
|
|||
|
||||
attributes :id, :code, :name, :container, :description, :owned_by, :created_by,
|
||||
:created_on, :urls, :metadata, :file_name, :sub_location_count, :is_empty,
|
||||
:img_url, :sa_description, :name_hash
|
||||
:img_url, :sa_description, :name_hash, :team_id
|
||||
|
||||
def owned_by
|
||||
object['team_name']
|
||||
|
|
|
@ -9,7 +9,7 @@ module RepositoryDatatable
|
|||
def value
|
||||
@user = scope[:user]
|
||||
{
|
||||
view: custom_auto_link(value_object.data, simple_format: true, team: scope[:team]),
|
||||
view: value_object.has_smart_annotation? ? custom_auto_link(value_object.data, simple_format: true, team: scope[:team]) : escape_input(value_object.data),
|
||||
edit: value_object.data
|
||||
}
|
||||
end
|
||||
|
|
|
@ -9,11 +9,9 @@ module Lists
|
|||
'ON repositories.created_by_id = creators.id')
|
||||
.joins('LEFT OUTER JOIN users AS archivers ' \
|
||||
'ON repositories.archived_by_id = archivers.id')
|
||||
.left_outer_joins(:repository_rows)
|
||||
.joins(:team)
|
||||
.select('repositories.*')
|
||||
.select('MAX(teams.name) AS team_name')
|
||||
.select('COUNT(DISTINCT(repository_rows.*)) AS row_count')
|
||||
.select('MAX(creators.full_name) AS created_by_user')
|
||||
.select('MAX(archivers.full_name) AS archived_by_user')
|
||||
.select(shared_sql_select)
|
||||
|
@ -47,7 +45,7 @@ module Lists
|
|||
created_at: 'repositories.created_at',
|
||||
archived_on: 'repositories.archived_on',
|
||||
archived_by: 'archived_by_user',
|
||||
nr_of_rows: 'row_count',
|
||||
nr_of_rows: 'repository_rows_count',
|
||||
code: 'repositories.id',
|
||||
shared_label: 'shared'
|
||||
}
|
||||
|
|
|
@ -44,8 +44,25 @@ module Lists
|
|||
@records = @records.where(parent_id: @parent_id)
|
||||
end
|
||||
|
||||
@records = @records.where('LOWER(storage_locations.name) ILIKE ?', "%#{@filters[:query].downcase}%") if @filters[:query].present?
|
||||
@records = @records.where('LOWER(storage_locations.name) ILIKE ?', "%#{@params[:search].downcase}%") if @params[:search].present?
|
||||
if @filters[:query].present?
|
||||
@records = @records.where_attributes_like(
|
||||
[
|
||||
'storage_locations.name',
|
||||
'storage_locations.description',
|
||||
StorageLocation::PREFIXED_ID_SQL
|
||||
], @filters[:query]
|
||||
)
|
||||
end
|
||||
|
||||
if @params[:search].present?
|
||||
@records = @records.where_attributes_like(
|
||||
[
|
||||
'storage_locations.name',
|
||||
'storage_locations.description',
|
||||
StorageLocation::PREFIXED_ID_SQL
|
||||
], @params[:search]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -60,9 +77,9 @@ module Lists
|
|||
@records = @records.order(id: :asc)
|
||||
when 'code_DESC'
|
||||
@records = @records.order(id: :desc)
|
||||
when 'name_ASC'
|
||||
when 'name_hash_ASC'
|
||||
@records = @records.order(name: :asc)
|
||||
when 'name_DESC'
|
||||
when 'name_hash_DESC'
|
||||
@records = @records.order(name: :desc)
|
||||
when 'sub_location_count_ASC'
|
||||
@records = @records.order(sub_location_count: :asc)
|
||||
|
|
|
@ -13,7 +13,7 @@ module Reports::Docx::DrawMyModuleRepository
|
|||
|
||||
return false unless repository_data[:rows].any? && can_read_repository?(@user, repository)
|
||||
|
||||
table = prepare_row_columns(repository_data, my_module, repository)
|
||||
table = prepare_row_columns_for_docx(repository_data, my_module, repository)
|
||||
|
||||
@docx.p
|
||||
@docx.p I18n.t('projects.reports.elements.module_repository.name',
|
||||
|
|
|
@ -4,7 +4,7 @@ module Reports::Docx::RepositoryHelper
|
|||
include InputSanitizeHelper
|
||||
include ActionView::Helpers::NumberHelper
|
||||
|
||||
def prepare_row_columns(repository_data, my_module, repository)
|
||||
def prepare_row_columns_for_docx(repository_data, my_module, repository)
|
||||
result = [repository_data[:headers]]
|
||||
repository_data[:rows].each do |record|
|
||||
row = []
|
||||
|
|
|
@ -13,10 +13,11 @@ class RepositoryDatatableService
|
|||
PREDEFINED_COLUMNS = %w(row_id row_name added_on added_by archived_on archived_by
|
||||
assigned relationships updated_on updated_by).freeze
|
||||
|
||||
def initialize(repository, params, user, my_module = nil)
|
||||
def initialize(repository, params, user, my_module = nil, preload_cells: true)
|
||||
@repository = repository
|
||||
@user = user
|
||||
@my_module = my_module
|
||||
@preload_cells = preload_cells
|
||||
@params = params
|
||||
@assigned_view = @params[:assigned].in?(%w(assigned assigned_simple))
|
||||
@sortable_columns = build_sortable_columns
|
||||
|
@ -46,7 +47,7 @@ class RepositoryDatatableService
|
|||
repository_rows = fetch_rows(search_value)
|
||||
|
||||
# filter only rows with reminders if filter param is present
|
||||
repository_rows = repository_rows.with_active_reminders(@user) if @params[:only_reminders]
|
||||
repository_rows = repository_rows.with_active_reminders(@repository, @user) if @params[:only_reminders]
|
||||
|
||||
# Aliased my_module_repository_rows join for consistent assigned counts
|
||||
repository_rows =
|
||||
|
@ -77,6 +78,18 @@ class RepositoryDatatableService
|
|||
.group('current_my_module_repository_rows.id')
|
||||
end
|
||||
end
|
||||
|
||||
if Repository.reminders_enabled?
|
||||
repository_rows =
|
||||
if @repository.archived? || @repository.is_a?(RepositorySnapshot)
|
||||
# don't load reminders for archived repositories or snapshots
|
||||
repository_rows.select('FALSE AS has_active_stock_reminders, FALSE AS has_active_datetime_reminders')
|
||||
else
|
||||
repository_rows.left_outer_joins_active_reminders(@repository, @user)
|
||||
.select('COUNT(repository_cells_with_active_reminders.id) > 0 AS has_active_reminders')
|
||||
end
|
||||
end
|
||||
|
||||
repository_rows = repository_rows
|
||||
.left_outer_joins(my_module_repository_rows: { my_module: :experiment })
|
||||
.select('COUNT(DISTINCT all_my_module_repository_rows.id) AS "assigned_my_modules_count"')
|
||||
|
@ -85,6 +98,8 @@ class RepositoryDatatableService
|
|||
.select('COALESCE(parent_connections_count, 0) + COALESCE(child_connections_count, 0)
|
||||
AS "relationships_count"')
|
||||
repository_rows = repository_rows.preload(Extends::REPOSITORY_ROWS_PRELOAD_RELATIONS)
|
||||
repository_rows = repository_rows.preload(:repository_columns, repository_cells: { value: @repository.cell_preload_includes }) if @preload_cells
|
||||
repository_rows = repository_rows.preload(:repository_stock_cell, :repository_stock_value) if @repository.has_stock_management?
|
||||
|
||||
sort_rows(order_by_column, repository_rows)
|
||||
end
|
||||
|
@ -101,7 +116,7 @@ class RepositoryDatatableService
|
|||
.where(my_module_repository_rows: { my_module_id: @my_module })
|
||||
.count
|
||||
else
|
||||
repository_rows.count
|
||||
@repository.repository_rows_count
|
||||
end
|
||||
|
||||
repository_rows = repository_rows.where(external_id: @params[:external_ids]) if @params[:external_ids]
|
||||
|
@ -128,8 +143,13 @@ class RepositoryDatatableService
|
|||
|
||||
repository_rows = repository_rows.where(id: advanced_search(repository_rows)) if @params[:advanced_search].present?
|
||||
|
||||
repository_rows.left_outer_joins(:created_by, :archived_by, :last_modified_by)
|
||||
repository_rows.joins('LEFT OUTER JOIN "users" "created_by" ON "created_by"."id" = "repository_rows"."created_by_id"')
|
||||
.joins('LEFT OUTER JOIN "users" "last_modified_by" ON "last_modified_by"."id" = "repository_rows"."last_modified_by_id"')
|
||||
.joins('LEFT OUTER JOIN "users" "archived_by" ON "archived_by"."id" = "repository_rows"."archived_by_id"')
|
||||
.select('repository_rows.*')
|
||||
.select('MAX("created_by"."full_name") AS created_by_full_name')
|
||||
.select('MAX("last_modified_by"."full_name") AS last_modified_by_full_name')
|
||||
.select('MAX("archived_by"."full_name") AS archived_by_full_name')
|
||||
.select('COUNT("repository_rows"."id") OVER() AS filtered_count')
|
||||
.group('repository_rows.id')
|
||||
end
|
||||
|
@ -616,7 +636,7 @@ class RepositoryDatatableService
|
|||
.group('values.value')
|
||||
.order("values.value #{dir}")
|
||||
when 'users.full_name'
|
||||
records.group('users.full_name').order("users.full_name #{dir}")
|
||||
records.group('created_by.full_name').order("created_by.full_name #{dir}")
|
||||
when 'consumed_stock'
|
||||
records.order("#{@sortable_columns[column_id - 1]} #{dir}")
|
||||
when 'relationships'
|
||||
|
|
|
@ -18,6 +18,8 @@ class RepositorySnapshotDatatableService < RepositoryDatatableService
|
|||
order_by_column = { column: order_params[:column].to_i, dir: order_params[:dir] }
|
||||
|
||||
repository_rows = fetch_rows(search_value).preload(Extends::REPOSITORY_ROWS_PRELOAD_RELATIONS)
|
||||
repository_rows = repository_rows.preload(:repository_columns, repository_cells: { value: @repository.cell_preload_includes }) if @preload_cells
|
||||
repository_rows = repository_rows.preload(:repository_stock_cell, :repository_stock_value) if @repository.has_stock_management?
|
||||
|
||||
sort_rows(order_by_column, repository_rows)
|
||||
end
|
||||
|
@ -44,8 +46,9 @@ class RepositorySnapshotDatatableService < RepositoryDatatableService
|
|||
repository_rows = results
|
||||
end
|
||||
|
||||
repository_rows.left_outer_joins(:created_by)
|
||||
repository_rows.joins('LEFT OUTER JOIN "users" "created_by" ON "created_by"."id" = "repository_rows"."created_by_id"')
|
||||
.select('repository_rows.*')
|
||||
.select('MAX("created_by"."full_name") AS created_by_full_name')
|
||||
.select('COUNT("repository_rows"."id") OVER() AS filtered_count')
|
||||
.group('repository_rows.id')
|
||||
end
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
module SmartAnnotations
|
||||
class TagToHtml
|
||||
REGEX = /\[\#(.*?)~(prj|exp|tsk|rep_item)~([0-9a-zA-Z]+)\]/.freeze
|
||||
USER_REGEX = /\[@(.*?)~([0-9a-zA-Z]+)\]/.freeze
|
||||
ALL_REGEX = /\[(@(.*?)|\#(.*?)~(prj|exp|tsk|rep_item))~([0-9a-zA-Z]+)\]/
|
||||
ITEMS_REGEX = /\[\#(.*?)~(prj|exp|tsk|rep_item)~([0-9a-zA-Z]+)\]/
|
||||
USER_REGEX = /\[@(.*?)~([0-9a-zA-Z]+)\]/
|
||||
attr_reader :html
|
||||
|
||||
def initialize(user, team, text, preview_repository = false)
|
||||
|
@ -18,7 +19,7 @@ module SmartAnnotations
|
|||
rep_item: RepositoryRow }.freeze
|
||||
|
||||
def parse(user, team, text, preview_repository = false)
|
||||
@html = text.gsub(REGEX) do |el|
|
||||
@html = text.gsub(ITEMS_REGEX) do |el|
|
||||
value = extract_values(el)
|
||||
type = value[:object_type]
|
||||
begin
|
||||
|
@ -49,7 +50,7 @@ module SmartAnnotations
|
|||
end
|
||||
|
||||
def extract_values(element)
|
||||
match = element.match(REGEX)
|
||||
match = element.match(ITEMS_REGEX)
|
||||
{
|
||||
name: match[1],
|
||||
object_type: match[2],
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
module SmartAnnotations
|
||||
class TagToText
|
||||
USER_REGEX = /\[@(.*?)~([0-9a-zA-Z]+)\]/
|
||||
ITEMS_REGEX = /\[\#(.*?)~(prj|exp|tsk|rep_item)~([0-9a-zA-Z]+)\]/
|
||||
|
||||
attr_reader :text
|
||||
|
||||
def initialize(user, team, text, is_shared_object: false)
|
||||
|
@ -11,8 +14,6 @@ module SmartAnnotations
|
|||
|
||||
private
|
||||
|
||||
USER_REGEX = /\[\@(.*?)~([0-9a-zA-Z]+)\]/
|
||||
ITEMS_REGEX = /\[\#(.*?)~(prj|exp|tsk|rep_item)~([0-9a-zA-Z]+)\]/
|
||||
OBJECT_MAPPINGS = { prj: Project,
|
||||
exp: Experiment,
|
||||
tsk: MyModule,
|
||||
|
|
|
@ -4,6 +4,8 @@ require 'caxlsx'
|
|||
|
||||
module StorageLocations
|
||||
class ImportService
|
||||
class PositionNotValid < StandardError; end
|
||||
|
||||
def initialize(storage_location, file, user)
|
||||
@storage_location = storage_location
|
||||
@assigned_count = 0
|
||||
|
@ -54,6 +56,8 @@ module StorageLocations
|
|||
{ status: :ok, assigned_count: @assigned_count, unassigned_count: @unassigned_count, updated_count: @updated_count }
|
||||
rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid
|
||||
{ status: :error, message: @error_message }
|
||||
rescue PositionNotValid
|
||||
{ status: :error, message: I18n.t('storage_locations.show.import_modal.errors.invalid_position') }
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -105,6 +109,8 @@ module StorageLocations
|
|||
def convert_position_letter_to_number(position)
|
||||
return unless position
|
||||
|
||||
raise PositionNotValid unless position.to_s.match?(/^[A-Z]\d+$/)
|
||||
|
||||
column_letter = position[0]
|
||||
row_number = position[1..]
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ module Toolbars
|
|||
private
|
||||
|
||||
def unassign_action
|
||||
return unless can_manage_storage_location?(@storage_location)
|
||||
return unless can_read_storage_location?(@storage_location)
|
||||
|
||||
{
|
||||
name: 'unassign',
|
||||
|
@ -39,7 +39,7 @@ module Toolbars
|
|||
end
|
||||
|
||||
def move_action
|
||||
return unless @single && can_manage_storage_location?(@storage_location)
|
||||
return unless @single && can_read_storage_location?(@storage_location)
|
||||
|
||||
{
|
||||
name: 'move',
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<% @assigned_repositories.each do |repository| %>
|
||||
<% readable = @my_module.readable_live_and_snapshot_repositories_list(current_user, current_team).include?(repository) %>
|
||||
<div class="assigned-repository panel" data-repository-id="<%= repository.id %>">
|
||||
<a class="assigned-repository-caret collapsed"
|
||||
role="button"
|
||||
|
@ -9,57 +8,40 @@
|
|||
>
|
||||
<i class="sn-icon sn-icon-right"></i>
|
||||
<span class="assigned-repository-title" data-rows-count="<%= repository.assigned_rows_count %>">
|
||||
<%= readable && repository.name || t('my_modules.repository.private_repository', code: repository.code) %>
|
||||
<%= repository.name %>
|
||||
</span>
|
||||
<% if repository.is_a?(RepositorySnapshot) %>
|
||||
<span class="snapshot-tag">
|
||||
<%= t('my_modules.repository.snapshots.simple_view.snapshot_tag') %>
|
||||
</span>
|
||||
<% end %>
|
||||
<% if readable %>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-light icon-btn full-screen" data-table-url="<%= assigned_repository_full_view_table_path(@my_module, repository) %><%= '?include_stock_consumption=true' if repository.has_stock_consumption? %>">
|
||||
<i class="sn-icon sn-icon-expand"></i>
|
||||
</button>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-light icon-btn full-screen" data-table-url="<%= assigned_repository_full_view_table_path(@my_module, repository) %><%= '?include_stock_consumption=true' if repository.has_stock_consumption? %>">
|
||||
<i class="sn-icon sn-icon-expand"></i>
|
||||
</button>
|
||||
</div>
|
||||
</a>
|
||||
<% if readable %>
|
||||
<div class="collapse assigned-repository-container readable-repository"
|
||||
id="assigned-repository-items-container-<%= repository.id %>"
|
||||
data-repository-url="<%= assigned_repository_simple_view_index_path(@my_module, repository) %>"
|
||||
data-footer-label="<%= assigned_repository_simple_view_footer_label(repository) %>"
|
||||
data-name-column-id="<%= assigned_repository_simple_view_name_column_id(repository) %>"
|
||||
>
|
||||
<table class="table hidden repository-table repository-dataTable"
|
||||
data-stock-management="<%= repository.has_stock_management? && repository.has_stock_consumption? %>"
|
||||
data-stock-consumption-editable="<%= can_update_my_module_stock_consumption?(@my_module) && repository.has_stock_consumption? %>">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="row-name"><%= t("repositories.table.row_name") %></th>
|
||||
<% if repository.has_stock_management? && repository.has_stock_consumption? %>
|
||||
<th class="row-stock" data-columns-visible="false"><%= repository.repository_stock_column.name %></th>
|
||||
<th class="row-consumption" data-columns-visible="false"><%= t("repositories.table.row_consumption") %></th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="collapse assigned-repository-container" id="assigned-repository-items-container-<%= repository.id %>">
|
||||
<table class="table border-solid border-0 border-b border-b-sn-light-grey">
|
||||
<div class="collapse assigned-repository-container"
|
||||
id="assigned-repository-items-container-<%= repository.id %>"
|
||||
data-repository-url="<%= assigned_repository_simple_view_index_path(@my_module, repository) %>"
|
||||
data-footer-label="<%= assigned_repository_simple_view_footer_label(repository) %>"
|
||||
data-name-column-id="<%= assigned_repository_simple_view_name_column_id(repository) %>"
|
||||
>
|
||||
<table class="table hidden repository-table repository-dataTable"
|
||||
data-stock-management="<%= repository.has_stock_management? && repository.has_stock_consumption? %>"
|
||||
data-stock-consumption-editable="<%= can_update_my_module_stock_consumption?(@my_module) && repository.has_stock_consumption? %>">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="bg-sn-light-grey"><%= t('repositories.table.id') %></th>
|
||||
<th class="row-name"><%= t("repositories.table.row_name") %></th>
|
||||
<% if repository.has_stock_management? && repository.has_stock_consumption? %>
|
||||
<th class="row-stock" data-columns-visible="false"><%= repository.repository_stock_column.name %></th>
|
||||
<th class="row-consumption" data-columns-visible="false"><%= t("repositories.table.row_consumption") %></th>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% @my_module.repository_rows.where(repository: repository).select(:parent_id, :id).map(&:code).each do |code| %>
|
||||
<tr>
|
||||
<td class="!border-t-sn-light-grey"><%= code %></td>
|
||||
<tr>
|
||||
<% end %>
|
||||
</table>
|
||||
</div>
|
||||
<% end %>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<%= render 'shared/dialog',
|
||||
id: "snapshot-error-#{repository.id}",
|
||||
type: "error",
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
<% if @repository.is_a?(LinkedRepository) %>
|
||||
<th id="row-external-id"><%= t('repositories.table.external_id') %></th>
|
||||
<% end %>
|
||||
<% columns_editable = can_manage_repository_columns?(@repository) && !repository.is_a?(SoftLockedRepository) %>
|
||||
<% repository.repository_columns.order(:id).each do |column| %>
|
||||
<th
|
||||
class="repository-column <%= 'row-stock item-stock' if column.data_type == 'RepositoryStockValue' %>"
|
||||
|
@ -55,7 +56,7 @@
|
|||
data-type="<%= column.data_type %>"
|
||||
data-edit-column-url="<%= edit_repository_repository_column_path(repository, column) %>"
|
||||
data-destroy-column-url="<%= repository_columns_destroy_html_path(repository, column) %>"
|
||||
data-editable-row="<%= can_manage_repository_column?(column) && !repository.is_a?(SoftLockedRepository) %>"
|
||||
data-editable-row="<%= columns_editable %>"
|
||||
<% column.metadata.each do |k, v| %>
|
||||
<%= "data-metadata-#{k}=#{v}" %>
|
||||
<% end %>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
</button>
|
||||
</span>
|
||||
<% end %>
|
||||
<% if @repository.repository_rows.with_active_reminders(current_user).any? %>
|
||||
<% if @repository.has_reminders? && @repository.repository_rows.with_active_reminders(@repository, current_user).any? %>
|
||||
<button type="button" data-toggle="tooltip" data-placement="bottom" title="<%= t("repositories.hide_reminders") %>"
|
||||
class="btn btn-light auto-shrink-button"
|
||||
id="hideRepositoryReminders"
|
||||
|
|
|
@ -6,7 +6,7 @@ json.data do
|
|||
@repository,
|
||||
@columns_mappings,
|
||||
@repository.team,
|
||||
@datatable_params || {})
|
||||
(@datatable_params || {}).merge(omit_editable: true))
|
||||
end
|
||||
json.recordsFiltered @repository_rows.first ? @repository_rows.first.filtered_count : 0
|
||||
json.recordsFiltered @filtered_rows_count
|
||||
json.recordsTotal @all_rows_count
|
||||
|
|
|
@ -6,6 +6,7 @@ json.repository do
|
|||
json.name @repository.name
|
||||
json.is_snapshot @repository.is_a?(RepositorySnapshot)
|
||||
end
|
||||
json.editable @repository_row.editable?
|
||||
json.notification @notification
|
||||
|
||||
json.update_path update_cell_repository_repository_row_path(@repository, @repository_row)
|
||||
|
|
|
@ -6,5 +6,5 @@ json.data do
|
|||
@repository_rows, @repository, @my_module, @datatable_params || {}
|
||||
)
|
||||
end
|
||||
json.recordsFiltered @repository_rows.first ? @repository_rows.first.filtered_count : 0
|
||||
json.recordsFiltered @filtered_rows_count
|
||||
json.recordsTotal @all_rows_count
|
||||
|
|
|
@ -7,5 +7,5 @@ json.data do
|
|||
@repository_snapshot.team,
|
||||
@repository_snapshot)
|
||||
end
|
||||
json.recordsFiltered @repository_rows.first ? @repository_rows.first.filtered_count : 0
|
||||
json.recordsFiltered @filtered_rows_count
|
||||
json.recordsTotal @all_rows_count
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- Assigned items -->
|
||||
<% assigned_repositories = @my_module.live_and_snapshot_repositories_list.select { |r| r.team == @my_module.team || r.shared_with?(@my_module.team) } %>
|
||||
<% assigned_repositories = @my_module.live_and_snapshot_repositories_list %>
|
||||
<div class="task-section">
|
||||
<div class="task-section-header">
|
||||
<a class="task-section-caret" role="button" data-toggle="collapse" href="#assigned-items-container" aria-expanded="true" aria-controls="assigned-items-container">
|
||||
|
|
|
@ -538,7 +538,7 @@ class Extends
|
|||
team: [92, 94, 93, 97, 104, 244, 245],
|
||||
label_templates: [*216..219],
|
||||
storage_locations: [*309..315],
|
||||
container_storage_location: [*316..322, 326],
|
||||
container_storage_locations: [*316..322, 326],
|
||||
storage_location_repository_rows: [*323..325]
|
||||
}
|
||||
|
||||
|
@ -696,6 +696,7 @@ class Extends
|
|||
Repositories_archived_state
|
||||
StorageLocationsTable_active_state
|
||||
StorageLocationsContainer_active_state
|
||||
StorageLocationsContainerGrid_active_state
|
||||
task_step_states
|
||||
results_order
|
||||
repository_export_file_type
|
||||
|
|
|
@ -265,6 +265,7 @@ en:
|
|||
not_uniq_repository_row: 'Inventory item already exists'
|
||||
attributes:
|
||||
parent_storage_location: "Storage location cannot be parent to itself"
|
||||
parent_storage_location_team: "Parent storage location and storage location should belongs to the same team"
|
||||
parent_storage_location_child: "Storage location cannot be moved to it's child"
|
||||
metadata:
|
||||
invalid: 'Invalid metadata'
|
||||
|
@ -2180,6 +2181,7 @@ en:
|
|||
errors:
|
||||
too_long: "Item name is too long (maximum is %{max_length} characters)"
|
||||
is_empty: "Item name should be filled"
|
||||
row_locked: The item is locked, snapshot creation is in progress. Please try later.
|
||||
pagination_edit_overlay_html: "Please <a class=\"repository-save-changes-link\">save your changes</a> before you go to another page"
|
||||
toolbar_edit_overlay_html: "Please <a class=\"repository-save-changes-link\">save your changes</a> first to use filters"
|
||||
add_new_record: "New item"
|
||||
|
@ -2692,10 +2694,12 @@ en:
|
|||
button: 'Unassign'
|
||||
assign_modal:
|
||||
selected_position_title: 'Assign to position %{position}'
|
||||
selected_row_title: 'Assign new location'
|
||||
assign_title: 'Assign position'
|
||||
move_title: 'Move item'
|
||||
assign_description: 'Select an item to assign it to a location.'
|
||||
move_description: 'Select a new location for your item.'
|
||||
selected_row_description: "Select a location for the item %{name}."
|
||||
assign_action: 'Assign'
|
||||
move_action: 'Move'
|
||||
row: 'Row'
|
||||
|
@ -2772,6 +2776,7 @@ en:
|
|||
title: 'Move %{name}'
|
||||
description: 'Select where you want to move %{name}.'
|
||||
search_header: 'Locations'
|
||||
no_results: "You haven't created any storage locations and boxes yet.<br> Go to <b>Inventories > Locations</b> and create your location structure first to assign items to locations."
|
||||
success_flash: "You have successfully moved the selected location/box to another location."
|
||||
error_flash: "An error occurred. The selected location/box has not been moved."
|
||||
placeholder:
|
||||
|
|
|
@ -650,8 +650,9 @@ en:
|
|||
team: "Team"
|
||||
exports: "Exports"
|
||||
label_templates: "Label templates"
|
||||
storage_locations: "Locations"
|
||||
container_storage_locations: "Boxes"
|
||||
storage_locations: "Inventory locations"
|
||||
container_storage_locations: "Inventory location boxes"
|
||||
storage_location_repository_rows: "Item assignments to location"
|
||||
subject_name:
|
||||
repository: "Inventory"
|
||||
project: "Project"
|
||||
|
|
|
@ -833,7 +833,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
resources :storage_location_repository_rows, only: %i(index create destroy update) do
|
||||
collection do
|
||||
get :actions_toolbar
|
||||
post :actions_toolbar
|
||||
end
|
||||
member do
|
||||
post :move
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddCounterCacheRepositoryRow < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
change_table :repositories, bulk: true do |t|
|
||||
t.integer :repository_rows_count, default: 0, null: false
|
||||
end
|
||||
|
||||
Repository.find_each do |repository|
|
||||
Repository.reset_counters(repository.id, :repository_rows)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSmartAnnotationFlagToRepositoryTextValue < ActiveRecord::Migration[7.0]
|
||||
def up
|
||||
add_column :repository_text_values, :has_smart_annotation, :boolean, null: false, default: false
|
||||
|
||||
execute('UPDATE repository_text_values SET has_smart_annotation = TRUE ' \
|
||||
'WHERE data ~ \'\[(@(.*?)|\#(.*?)~(prj|exp|tsk|rep_item))~([0-9a-zA-Z]+)\]\'')
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :repository_text_values, :has_smart_annotation
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_07_05_122903) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_10_02_122340) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "btree_gist"
|
||||
enable_extension "pg_trgm"
|
||||
|
@ -936,6 +936,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_07_05_122903) do
|
|||
t.datetime "updated_at", precision: nil
|
||||
t.bigint "created_by_id", null: false
|
||||
t.bigint "last_modified_by_id", null: false
|
||||
t.boolean "has_smart_annotation", default: false, null: false
|
||||
t.index "trim_html_tags((data)::text) gin_trgm_ops", name: "index_repository_text_values_on_data", using: :gin
|
||||
end
|
||||
|
||||
|
@ -1550,7 +1551,6 @@ ActiveRecord::Schema[7.0].define(version: 2024_07_05_122903) do
|
|||
add_foreign_key "tags", "projects"
|
||||
add_foreign_key "tags", "users", column: "created_by_id"
|
||||
add_foreign_key "tags", "users", column: "last_modified_by_id"
|
||||
add_foreign_key "team_shared_objects", "repositories", column: "shared_object_id"
|
||||
add_foreign_key "team_shared_objects", "teams"
|
||||
add_foreign_key "teams", "users", column: "created_by_id"
|
||||
add_foreign_key "teams", "users", column: "last_modified_by_id"
|
||||
|
|
Loading…
Reference in a new issue