mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-22 11:52:21 +01:00
Merge branch 'spaces-machines-tree' into staging
This commit is contained in:
commit
b5f330ea5a
2
Gemfile
2
Gemfile
@ -151,3 +151,5 @@ gem 'sentry-rails'
|
||||
gem 'sentry-ruby'
|
||||
|
||||
gem "reverse_markdown"
|
||||
|
||||
gem "ancestry"
|
||||
|
@ -76,6 +76,8 @@ GEM
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
afm (0.2.2)
|
||||
ancestry (4.3.3)
|
||||
activerecord (>= 5.2.6)
|
||||
ansi (1.5.0)
|
||||
api-pagination (4.8.2)
|
||||
apipie-rails (0.5.17)
|
||||
@ -536,6 +538,7 @@ DEPENDENCIES
|
||||
aasm
|
||||
active_record_query_trace
|
||||
acts_as_list
|
||||
ancestry
|
||||
api-pagination
|
||||
apipie-rails
|
||||
awesome_print
|
||||
|
@ -7,7 +7,9 @@ class API::SpacesController < API::APIController
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@spaces = Space.includes(:space_image).where(deleted_at: nil)
|
||||
@spaces = Space.includes(:space_image, :machines).where(deleted_at: nil)
|
||||
@spaces_indexed_with_parent = @spaces.index_with { |space| @spaces.find { |s| s.id == space.parent_id } }
|
||||
@spaces_grouped_by_parent_id = @spaces.group_by(&:parent_id)
|
||||
end
|
||||
|
||||
def show
|
||||
@ -20,6 +22,7 @@ class API::SpacesController < API::APIController
|
||||
authorize Space
|
||||
@space = Space.new(space_params)
|
||||
if @space.save
|
||||
update_space_children(@space, params[:space][:child_ids])
|
||||
render :show, status: :created, location: @space
|
||||
else
|
||||
render json: @space.errors, status: :unprocessable_entity
|
||||
@ -29,6 +32,7 @@ class API::SpacesController < API::APIController
|
||||
def update
|
||||
authorize @space
|
||||
if @space.update(space_params)
|
||||
update_space_children(@space, params[:space][:child_ids])
|
||||
render :show, status: :ok, location: @space
|
||||
else
|
||||
render json: @space.errors, status: :unprocessable_entity
|
||||
@ -50,8 +54,18 @@ class API::SpacesController < API::APIController
|
||||
|
||||
def space_params
|
||||
params.require(:space).permit(:name, :description, :characteristics, :default_places, :disabled,
|
||||
machine_ids: [],
|
||||
space_image_attributes: %i[id attachment],
|
||||
space_files_attributes: %i[id attachment _destroy],
|
||||
advanced_accounting_attributes: %i[code analytical_section])
|
||||
end
|
||||
|
||||
def update_space_children(parent_space, child_ids)
|
||||
Space.transaction do
|
||||
parent_space.children.each { |child| child.update!(parent: nil) }
|
||||
child_ids.to_a.select(&:present?).each do |child_id|
|
||||
Space.find(child_id).update!(parent: parent_space)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
@ -1,73 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<defs><style> .nc-icon-wrapper { display: none } .nc-icon-wrapper:target { display: inline } </style></defs>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="lastfm" class="nc-icon-wrapper">
|
||||
<path d="M20.25 1.5H3.75A2.25 2.25 0 0 0 1.5 3.75v16.5a2.25 2.25 0 0 0 2.25 2.25h16.5a2.25 2.25 0 0 0 2.25-2.25V3.75a2.25 2.25 0 0 0-2.25-2.25zm-4.322 14.667c-2.972 0-4.003-1.34-4.551-3.005-.764-2.39-1.008-3.951-2.954-3.951-1.05 0-2.114.755-2.114 2.869 0 1.65.844 2.68 2.03 2.68 1.34 0 2.231-.998 2.231-.998l.549 1.496s-.928.91-2.869.91c-2.405 0-3.745-1.412-3.745-4.023 0-2.714 1.34-4.312 3.867-4.312 3.445 0 3.787 1.94 4.725 4.776.412 1.257 1.134 2.166 2.869 2.166 1.167 0 1.786-.258 1.786-.895 0-.933-1.022-1.032-2.34-1.34-1.425-.343-1.992-1.084-1.992-2.25 0-1.876 1.514-2.457 3.057-2.457 1.753 0 2.817.637 2.953 2.184l-1.72.206c-.07-.74-.516-1.05-1.341-1.05-.755 0-1.219.343-1.219.929 0 .515.225.825.98.998 1.533.333 3.365.563 3.365 2.695.005 1.72-1.439 2.372-3.567 2.372z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="instagram" class="nc-icon-wrapper">
|
||||
<path d="M12 9.5a2.5 2.5 0 1 0 .002 5 2.5 2.5 0 0 0-.002-5zm5.846-1.922a2.532 2.532 0 0 0-1.426-1.426c-.984-.388-3.328-.301-4.42-.301s-3.434-.09-4.42.301a2.531 2.531 0 0 0-1.426 1.426c-.388.984-.302 3.33-.302 4.421 0 1.092-.086 3.435.304 4.423a2.531 2.531 0 0 0 1.425 1.425c.984.389 3.328.302 4.42.302 1.094 0 3.434.09 4.421-.302a2.532 2.532 0 0 0 1.426-1.425c.391-.985.301-3.33.301-4.422 0-1.091.09-3.434-.301-4.422h-.002zM12 15.844a3.844 3.844 0 1 1 0-7.689 3.844 3.844 0 0 1 0 7.689zm4.002-6.952a.897.897 0 1 1 .002.002l-.002-.002zM20.25 1.5H3.75A2.25 2.25 0 0 0 1.5 3.75v16.5a2.25 2.25 0 0 0 2.25 2.25h16.5a2.25 2.25 0 0 0 2.25-2.25V3.75a2.25 2.25 0 0 0-2.25-2.25zm-.802 13.594c-.061 1.201-.335 2.266-1.212 3.14-.877.875-1.94 1.155-3.14 1.212-1.239.07-4.95.07-6.188 0-1.202-.06-2.263-.335-3.141-1.212-.878-.876-1.155-1.941-1.212-3.14-.07-1.239-.07-4.95 0-6.188.06-1.201.332-2.266 1.212-3.14.88-.875 1.944-1.152 3.14-1.209 1.239-.07 4.95-.07 6.188 0 1.202.06 2.266.335 3.14 1.212.876.876 1.155 1.941 1.213 3.143.07 1.234.07 4.942 0 6.182z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="github" class="nc-icon-wrapper">
|
||||
<path d="M20.25 1.5H3.75C2.50781 1.5 1.5 2.50781 1.5 3.75V20.25C1.5 21.4922 2.50781 22.5 3.75 22.5H20.25C21.4922 22.5 22.5 21.4922 22.5 20.25V3.75C22.5 2.50781 21.4922 1.5 20.25 1.5ZM14.4984 19.4859C14.1047 19.5563 13.9594 19.3125 13.9594 19.1109C13.9594 18.8578 13.9688 17.5641 13.9688 16.5187C13.9688 15.7875 13.725 15.3234 13.4391 15.0797C15.1734 14.8875 17.0016 14.6484 17.0016 11.6531C17.0016 10.8 16.6969 10.3734 16.2 9.825C16.2797 9.62344 16.5469 8.79375 16.1203 7.71563C15.4687 7.51406 13.9781 8.55469 13.9781 8.55469C13.3594 8.38125 12.6891 8.29219 12.0281 8.29219C11.3672 8.29219 10.6969 8.38125 10.0781 8.55469C10.0781 8.55469 8.5875 7.51406 7.93594 7.71563C7.50938 8.78906 7.77188 9.61875 7.85625 9.825C7.35938 10.3734 7.125 10.8 7.125 11.6531C7.125 14.6344 8.87344 14.8875 10.6078 15.0797C10.3828 15.2813 10.1812 15.6281 10.1109 16.125C9.66562 16.3266 8.52656 16.6734 7.84687 15.4734C7.42031 14.7328 6.65156 14.6719 6.65156 14.6719C5.89219 14.6625 6.6 15.15 6.6 15.15C7.10625 15.3844 7.4625 16.2844 7.4625 16.2844C7.91719 17.6766 10.0922 17.2078 10.0922 17.2078C10.0922 17.8594 10.1016 18.9188 10.1016 19.1109C10.1016 19.3125 9.96094 19.5563 9.5625 19.4859C6.46875 18.45 4.30312 15.5062 4.30312 12.0656C4.30312 7.7625 7.59375 4.49531 11.8969 4.49531C16.2 4.49531 19.6875 7.7625 19.6875 12.0656C19.6922 15.5062 17.5922 18.4547 14.4984 19.4859ZM9.9 16.6219C9.81094 16.6406 9.72656 16.6031 9.71719 16.5422C9.70781 16.4719 9.76875 16.4109 9.85781 16.3922C9.94688 16.3828 10.0312 16.4203 10.0406 16.4813C10.0547 16.5422 9.99375 16.6031 9.9 16.6219ZM9.45469 16.5797C9.45469 16.6406 9.38437 16.6922 9.29062 16.6922C9.1875 16.7016 9.11719 16.65 9.11719 16.5797C9.11719 16.5188 9.1875 16.4672 9.28125 16.4672C9.37031 16.4578 9.45469 16.5094 9.45469 16.5797ZM8.8125 16.5281C8.79375 16.5891 8.7 16.6172 8.62031 16.5891C8.53125 16.5703 8.47031 16.5 8.48906 16.4391C8.50781 16.3781 8.60156 16.35 8.68125 16.3688C8.775 16.3969 8.83594 16.4672 8.8125 16.5281ZM8.23594 16.275C8.19375 16.3266 8.10469 16.3172 8.03437 16.2469C7.96406 16.1859 7.94531 16.0969 7.99219 16.0547C8.03437 16.0031 8.12344 16.0125 8.19375 16.0828C8.25469 16.1438 8.27812 16.2375 8.23594 16.275ZM7.80938 15.8484C7.76719 15.8766 7.6875 15.8484 7.63594 15.7781C7.58437 15.7078 7.58437 15.6281 7.63594 15.5953C7.6875 15.5531 7.76719 15.5859 7.80938 15.6562C7.86094 15.7266 7.86094 15.8109 7.80938 15.8484V15.8484ZM7.50469 15.3937C7.4625 15.4359 7.39219 15.4125 7.34062 15.3656C7.28906 15.3047 7.27969 15.2344 7.32187 15.2016C7.36406 15.1594 7.43437 15.1828 7.48594 15.2297C7.5375 15.2906 7.54688 15.3609 7.50469 15.3937ZM7.19063 15.0469C7.17188 15.0891 7.11094 15.0984 7.05937 15.0656C6.99844 15.0375 6.97031 14.9859 6.98906 14.9437C7.00781 14.9156 7.05938 14.9016 7.12031 14.925C7.18125 14.9578 7.20938 15.0094 7.19063 15.0469Z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="echosciences" class="nc-icon-wrapper">
|
||||
<path d="M3.5 1.5C2.39543 1.5 1.5 2.39543 1.5 3.5V20.5C1.5 21.6046 2.39543 22.5 3.5 22.5H20.5C21.6046 22.5 22.5 21.6046 22.5 20.5V3.5C22.5 2.39543 21.6046 1.5 20.5 1.5H3.5ZM15.099 5H16.5742L16.6848 5.14204C18.2776 7.18714 19.1861 9.48429 19.1838 11.9303C19.1837 14.433 18.2315 16.7805 16.5685 18.8616L16.4578 19H14.9691L15.4785 18.3944C17.1447 16.4138 18.0564 14.2227 18.0564 11.93C18.0579 9.6857 17.1869 7.54197 15.5936 5.60226L15.099 5ZM4.81616 8.48674V5.73684H13.5809V8.48674H4.81616ZM12.7682 13.3182H7.98311V15.5169H13.5964V18.2632H4.83164V10.5757H12.7682V13.3182Z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="flickr" class="nc-icon-wrapper">
|
||||
<path d="M20.25 1.5H3.75A2.25 2.25 0 0 0 1.5 3.75v16.5a2.25 2.25 0 0 0 2.25 2.25h16.5a2.25 2.25 0 0 0 2.25-2.25V3.75a2.25 2.25 0 0 0-2.25-2.25zM8.273 14.953a2.975 2.975 0 0 1-2.976-2.976A2.975 2.975 0 0 1 8.273 9a2.975 2.975 0 0 1 2.977 2.977 2.975 2.975 0 0 1-2.977 2.976zm7.454 0a2.975 2.975 0 0 1-2.977-2.976A2.975 2.975 0 0 1 15.727 9a2.975 2.975 0 0 1 2.976 2.977 2.975 2.975 0 0 1-2.976 2.976z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="facebook" class="nc-icon-wrapper">
|
||||
<path d="M20.25 1.5H3.75C3.15326 1.5 2.58097 1.73705 2.15901 2.15901C1.73705 2.58097 1.5 3.15326 1.5 3.75L1.5 20.25C1.5 20.8467 1.73705 21.419 2.15901 21.841C2.58097 22.2629 3.15326 22.5 3.75 22.5H10.1836V15.3605H7.23047V12H10.1836V9.43875C10.1836 6.52547 11.918 4.91625 14.5744 4.91625C15.8466 4.91625 17.1769 5.14313 17.1769 5.14313V8.0025H15.7111C14.2669 8.0025 13.8164 8.89875 13.8164 9.81797V12H17.0405L16.5248 15.3605H13.8164V22.5H20.25C20.8467 22.5 21.419 22.2629 21.841 21.841C22.2629 21.419 22.5 20.8467 22.5 20.25V3.75C22.5 3.15326 22.2629 2.58097 21.841 2.15901C21.419 1.73705 20.8467 1.5 20.25 1.5V1.5Z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="youtube" class="nc-icon-wrapper">
|
||||
<path d="M10.2563 9.47344L14.7188 12.0094L10.2563 14.5453V9.47344ZM22.5 3.75V20.25C22.5 21.4922 21.4922 22.5 20.25 22.5H3.75C2.50781 22.5 1.5 21.4922 1.5 20.25V3.75C1.5 2.50781 2.50781 1.5 3.75 1.5H20.25C21.4922 1.5 22.5 2.50781 22.5 3.75ZM20.5312 12.0141C20.5312 12.0141 20.5312 9.22031 20.175 7.87969C19.9781 7.13906 19.4016 6.55781 18.6656 6.36094C17.3391 6 12 6 12 6C12 6 6.66094 6 5.33438 6.36094C4.59844 6.55781 4.02187 7.13906 3.825 7.87969C3.46875 9.21563 3.46875 12.0141 3.46875 12.0141C3.46875 12.0141 3.46875 14.8078 3.825 16.1484C4.02187 16.8891 4.59844 17.4469 5.33438 17.6437C6.66094 18 12 18 12 18C12 18 17.3391 18 18.6656 17.6391C19.4016 17.4422 19.9781 16.8844 20.175 16.1437C20.5312 14.8078 20.5312 12.0141 20.5312 12.0141V12.0141Z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="vimeo" class="nc-icon-wrapper">
|
||||
<path d="M20.4 1.5H3.6c-1.158 0-2.1.942-2.1 2.1v16.8c0 1.158.942 2.1 2.1 2.1h16.8c1.158 0 2.1-.942 2.1-2.1V3.6c0-1.158-.942-2.1-2.1-2.1zm-1.228 6.975c-.066 1.477-1.097 3.502-3.094 6.066-2.062 2.68-3.81 4.021-5.236 4.021-.886 0-1.631-.815-2.245-2.451-1.195-4.373-1.706-6.938-2.69-6.938-.113 0-.512.24-1.191.713l-.713-.919C5.752 7.43 7.42 5.723 8.466 5.63c1.18-.113 1.907.693 2.18 2.423.97 6.15 1.4 7.078 3.168 4.294.633-1.003.975-1.767 1.022-2.292.164-1.557-1.214-1.449-2.147-1.05.745-2.443 2.17-3.628 4.275-3.563 1.561.042 2.297 1.055 2.208 3.033z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="vimeo" class="nc-icon-wrapper">
|
||||
<path d="M20.4 1.5H3.6c-1.158 0-2.1.942-2.1 2.1v16.8c0 1.158.942 2.1 2.1 2.1h16.8c1.158 0 2.1-.942 2.1-2.1V3.6c0-1.158-.942-2.1-2.1-2.1zm-1.228 6.975c-.066 1.477-1.097 3.502-3.094 6.066-2.062 2.68-3.81 4.021-5.236 4.021-.886 0-1.631-.815-2.245-2.451-1.195-4.373-1.706-6.938-2.69-6.938-.113 0-.512.24-1.191.713l-.713-.919C5.752 7.43 7.42 5.723 8.466 5.63c1.18-.113 1.907.693 2.18 2.423.97 6.15 1.4 7.078 3.168 4.294.633-1.003.975-1.767 1.022-2.292.164-1.557-1.214-1.449-2.147-1.05.745-2.443 2.17-3.628 4.275-3.563 1.561.042 2.297 1.055 2.208 3.033z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="viadeo" class="nc-icon-wrapper">
|
||||
<path d="M20.25 1.5H3.75A2.25 2.25 0 0 0 1.5 3.75v16.5a2.25 2.25 0 0 0 2.25 2.25h16.5a2.25 2.25 0 0 0 2.25-2.25V3.75a2.25 2.25 0 0 0-2.25-2.25zm-5.592 16.369c-1.988 2.165-5.625 2.184-7.613 0-3.187-3.45-.928-9.192 3.807-9.192.623 0 1.246.098 1.832.314a3.749 3.749 0 0 0-.393 1.27 3.696 3.696 0 0 0-1.44-.281c-2.287 0-3.965 1.954-3.965 4.167 0 2.016 1.336 3.689 3.258 4.026 2.883-1.125 3.417-5.512 3.417-8.203 0-.342 0-.693-.028-1.036-.525-1.542-1.247-3.028-2.072-4.43 1.27.859 1.964 2.93 2.072 4.412v.018a10.15 10.15 0 0 1 .553 3.282c0 2.536-1.027 4.64-3.202 6.009l-.112.01c2.344.046 4.04-1.81 4.04-4.088a4.29 4.29 0 0 0-.323-1.674 3.763 3.763 0 0 0 1.238-.492 5.554 5.554 0 0 1-1.07 5.888zm1.326-6.914c-.623 0-1.176-.333-1.612-.755 1.026-.563 2.325-1.44 2.92-2.484.07-.141.192-.404.211-.563-.586 1.308-2.072 2.335-3.464 2.658a2.092 2.092 0 0 1-.351-1.14c0-.482.243-1.129.604-1.48 1.013-.961 2.485-.399 3.394-2.344 1.523 2.165.614 6.108-1.702 6.108z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="twitter" class="nc-icon-wrapper">
|
||||
<path d="M20.25 1.5H3.75A2.25 2.25 0 0 0 1.5 3.75v16.5a2.25 2.25 0 0 0 2.25 2.25h16.5a2.25 2.25 0 0 0 2.25-2.25V3.75a2.25 2.25 0 0 0-2.25-2.25zm-2.292 7.444c.01.131.01.267.01.398 0 4.064-3.095 8.747-8.748 8.747a8.706 8.706 0 0 1-4.72-1.378c.248.028.487.037.74.037 1.44 0 2.762-.487 3.816-1.312a3.078 3.078 0 0 1-2.873-2.133c.473.07.9.07 1.387-.056a3.075 3.075 0 0 1-2.46-3.019v-.037c.407.23.885.37 1.387.389a3.068 3.068 0 0 1-1.369-2.56c0-.572.15-1.097.417-1.551a8.73 8.73 0 0 0 6.338 3.215c-.436-2.086 1.125-3.778 3-3.778.886 0 1.683.37 2.245.97a6.024 6.024 0 0 0 1.95-.74 3.066 3.066 0 0 1-1.35 1.692A6.117 6.117 0 0 0 19.5 7.35a6.471 6.471 0 0 1-1.542 1.594z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="pinterest" class="nc-icon-wrapper">
|
||||
<path d="M22.5 3.75V20.25C22.5 21.4922 21.4922 22.5 20.25 22.5H8.7375C9.19687 21.7313 9.7875 20.625 10.0219 19.7203C10.1625 19.1813 10.7391 16.9828 10.7391 16.9828C11.1141 17.7 12.2109 18.3047 13.3781 18.3047C16.8516 18.3047 19.35 15.1125 19.35 11.1469C19.35 7.34531 16.2469 4.5 12.2531 4.5C7.28437 4.5 4.65 7.83281 4.65 11.4656C4.65 13.1531 5.55 15.2531 6.98438 15.9234C7.20469 16.0266 7.31719 15.9797 7.36875 15.7687C7.40625 15.6094 7.60313 14.8266 7.6875 14.4656C7.71563 14.3484 7.70156 14.25 7.60781 14.1375C7.13438 13.5609 6.75 12.5016 6.75 11.5125C6.75 8.97188 8.67187 6.51562 11.9484 6.51562C14.775 6.51562 16.7578 8.44219 16.7578 11.1984C16.7578 14.3109 15.1875 16.4672 13.1391 16.4672C12.0094 16.4672 11.1656 15.5344 11.4328 14.3859C11.7562 13.0172 12.3844 11.5406 12.3844 10.5516C12.3844 8.06719 8.84531 8.40938 8.84531 11.7234C8.84531 12.7406 9.1875 13.4344 9.1875 13.4344C7.71563 19.6594 7.49531 19.7391 7.8 22.4625L7.90313 22.5H3.75C2.50781 22.5 1.5 21.4922 1.5 20.25V3.75C1.5 2.50781 2.50781 1.5 3.75 1.5H20.25C21.4922 1.5 22.5 2.50781 22.5 3.75Z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="linkedin" class="nc-icon-wrapper">
|
||||
<path d="M21 1.5H2.995C2.17 1.5 1.5 2.18 1.5 3.014v17.972c0 .834.67 1.514 1.495 1.514H21c.825 0 1.5-.68 1.5-1.514V3.014A1.51 1.51 0 0 0 21 1.5zm-13.153 18H4.734V9.478h3.118V19.5h-.005zM6.29 8.11a1.805 1.805 0 0 1 0-3.61c.993 0 1.804.81 1.804 1.805 0 .998-.806 1.804-1.804 1.804zM19.514 19.5h-3.112v-4.875c0-1.162-.024-2.658-1.618-2.658-1.622 0-1.87 1.266-1.87 2.574V19.5H9.802V9.478h2.986v1.369h.042c.417-.788 1.434-1.617 2.948-1.617 3.15 0 3.736 2.076 3.736 4.776V19.5z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<g id="dailymotion" class="nc-icon-wrapper">
|
||||
<path d="M15.512 12.516C15.1636 12.3201 14.7697 12.2197 14.37 12.225C13.75 12.225 13.228 12.432 12.803 12.847C12.378 13.261 12.165 13.783 12.165 14.413C12.165 15.074 12.373 15.615 12.788 16.035C13.203 16.455 13.725 16.665 14.354 16.665C14.995 16.665 15.528 16.45 15.954 16.019C16.379 15.589 16.592 15.053 16.594 14.413C16.5962 14.0277 16.4976 13.6485 16.308 13.313C16.1215 12.9788 15.8459 12.7029 15.512 12.516ZM3.5 1.5C2.39543 1.5 1.5 2.39543 1.5 3.5V20.5C1.5 21.6046 2.39543 22.5 3.5 22.5H20.5C21.6046 22.5 22.5 21.6046 22.5 20.5V3.5C22.5 2.39543 21.6046 1.5 20.5 1.5H3.5ZM19.064 18.997H16.576V17.877H16.544C16.051 18.696 15.179 19.105 13.929 19.105C13.069 19.105 12.304 18.9 11.638 18.491C10.9765 18.0875 10.4416 17.5065 10.094 16.814C9.731 16.105 9.55 15.31 9.55 14.429C9.55 13.567 9.734 12.783 10.102 12.075C10.452 11.385 10.986 10.804 11.645 10.397C12.307 9.988 13.053 9.784 13.882 9.783C14.3579 9.77641 14.8308 9.85952 15.276 10.028C15.691 10.19 16.082 10.456 16.448 10.824V7.172L19.063 6.605L19.065 18.997H19.064Z" fill="currentColor"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
Before Width: | Height: | Size: 14 KiB |
36
app/frontend/src/javascript/components/base/fab-badge.tsx
Normal file
36
app/frontend/src/javascript/components/base/fab-badge.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import * as React from 'react';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { Loader } from '../base/loader';
|
||||
import { react2angular } from 'react2angular';
|
||||
import Icons from '../../../../images/icons.svg';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
interface FabBadgeProps {
|
||||
icon: string,
|
||||
iconWidth: string,
|
||||
className?: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a badge (parent needs to be position: relative)
|
||||
*/
|
||||
export const FabBadge: React.FC<FabBadgeProps> = ({ icon, iconWidth, className }) => {
|
||||
return (
|
||||
<div className={`fab-badge ${className || ''}`}>
|
||||
<svg viewBox="0 0 24 24" width={iconWidth}>
|
||||
<use href={`${Icons}#${icon}`}/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FabBadgeWrapper: React.FC<FabBadgeProps> = ({ icon, iconWidth, className }) => {
|
||||
return (
|
||||
<Loader>
|
||||
<FabBadge icon={icon} iconWidth={iconWidth} className={className} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('fabBadge', react2angular(FabBadgeWrapper, ['icon', 'iconWidth', 'className']));
|
@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Loader } from '../base/loader';
|
||||
import { ReserveButton } from './reserve-button';
|
||||
import { User } from '../../models/user';
|
||||
import { FabBadge } from '../base/fab-badge';
|
||||
|
||||
interface MachineCardProps {
|
||||
user?: User,
|
||||
@ -57,6 +58,7 @@ const MachineCard: React.FC<MachineCardProps> = ({ user, machine, onShowMachine,
|
||||
return (
|
||||
<div className={`machine-card ${loading ? 'loading' : ''} ${machine.disabled ? 'disabled' : ''} ${!machine.reservable ? 'unreservable' : ''}`}>
|
||||
{machinePicture()}
|
||||
{machine.space && user.role === 'admin' && <FabBadge icon='pin-map' iconWidth='3rem' /> }
|
||||
<div className="machine-name">
|
||||
{machine.name}
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@ import { FormState, UseFormRegister, UseFormSetValue } from 'react-hook-form';
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { User } from '../../models/user';
|
||||
import { SocialNetwork } from '../../models/social-network';
|
||||
import Icons from '../../../../images/social-icons.svg';
|
||||
import Icons from '../../../../images/icons.svg';
|
||||
import { FormInput } from '../form/form-input';
|
||||
import { Trash } from 'phosphor-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -8,7 +8,7 @@ import { IApplication } from '../../models/application';
|
||||
import { Loader } from '../base/loader';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { SettingName } from '../../models/setting';
|
||||
import Icons from '../../../../images/social-icons.svg';
|
||||
import Icons from '../../../../images/icons.svg';
|
||||
import { Trash } from 'phosphor-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
|
@ -14,9 +14,13 @@ import { FormSwitch } from '../form/form-switch';
|
||||
import { FormMultiFileUpload } from '../form/form-multi-file-upload';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { Space } from '../../models/space';
|
||||
import { Machine } from '../../models/machine';
|
||||
import { AdvancedAccountingForm } from '../accounting/advanced-accounting-form';
|
||||
import SettingAPI from '../../api/setting';
|
||||
import { FabAlert } from '../base/fab-alert';
|
||||
import MachineAPI from '../../api/machine';
|
||||
import { FormMultiSelect } from '../form/form-multi-select';
|
||||
import { SelectOption } from '../../models/select';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -41,6 +45,41 @@ export const SpaceForm: React.FC<SpaceFormProps> = ({ action, space, onError, on
|
||||
SettingAPI.get('advanced_accounting').then(res => setIsActiveAccounting(res.value === 'true')).catch(onError);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Asynchronously load the full list of machines to display in the drop-down select field
|
||||
*/
|
||||
const loadMachines = (inputValue: string, callback: (options: Array<SelectOption<number>>) => void): void => {
|
||||
MachineAPI.index().then(data => {
|
||||
callback(data.map(m => machineToOption(m)));
|
||||
}).catch(error => onError(error));
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a machine to an option usable by react-select
|
||||
*/
|
||||
const machineToOption = (machine: Machine): SelectOption<number> => {
|
||||
return { value: machine.id, label: machine.name };
|
||||
};
|
||||
|
||||
/**
|
||||
* Asynchronously load the full list of spaces to display in the drop-down select field
|
||||
*/
|
||||
const loadSpaces = (inputValue: string, callback: (options: Array<SelectOption<number>>) => void): void => {
|
||||
SpaceAPI.index().then(data => {
|
||||
if (space) {
|
||||
data = data.filter((d) => d.id !== space.id);
|
||||
}
|
||||
callback(data.map(m => spaceToOption(m)));
|
||||
}).catch(error => onError(error));
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a space to an option usable by react-select
|
||||
*/
|
||||
const spaceToOption = (space: Space): SelectOption<number> => {
|
||||
return { value: space.id, label: space.name };
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the user validates the machine form: handle create or update
|
||||
*/
|
||||
@ -106,6 +145,29 @@ export const SpaceForm: React.FC<SpaceFormProps> = ({ action, space, onError, on
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>
|
||||
<p className="title">
|
||||
{t('app.admin.space_form.associated_objects')}
|
||||
</p>
|
||||
<p className="description">
|
||||
{t('app.admin.space_form.associated_objects_warning')}
|
||||
</p>
|
||||
</header>
|
||||
<div className="content">
|
||||
<FormMultiSelect control={control}
|
||||
id="child_ids"
|
||||
formState={formState}
|
||||
label={t('app.admin.space_form.children_spaces')}
|
||||
loadOptions={loadSpaces} />
|
||||
<FormMultiSelect control={control}
|
||||
id="machine_ids"
|
||||
formState={formState}
|
||||
label={t('app.admin.space_form.associated_machines')}
|
||||
loadOptions={loadMachines} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>
|
||||
<p className="title">{t('app.admin.space_form.attachments')}</p>
|
||||
|
@ -33,5 +33,8 @@ export interface Machine {
|
||||
slug: string,
|
||||
}>,
|
||||
advanced_accounting_attributes?: AdvancedAccounting,
|
||||
machine_category_id?: number
|
||||
machine_category_id?: number,
|
||||
space: {
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ export interface Slot {
|
||||
end: TDateISO,
|
||||
is_reserved: boolean,
|
||||
is_completed: boolean,
|
||||
is_blocked?: boolean,
|
||||
backgroundColor: 'white',
|
||||
|
||||
availability_id: number,
|
||||
|
@ -27,6 +27,7 @@
|
||||
@import "modules/base/edit-destroy-buttons";
|
||||
@import "modules/base/editorial-block";
|
||||
@import "modules/base/fab-alert";
|
||||
@import "modules/base/fab-badge";
|
||||
@import "modules/base/fab-button";
|
||||
@import "modules/base/fab-input";
|
||||
@import "modules/base/fab-modal";
|
||||
|
15
app/frontend/src/stylesheets/modules/base/fab-badge.scss
Normal file
15
app/frontend/src/stylesheets/modules/base/fab-badge.scss
Normal file
@ -0,0 +1,15 @@
|
||||
.fab-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 1.5rem;
|
||||
padding: 0.8rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--secondary);
|
||||
color: var(--secondary-text-color);
|
||||
border-bottom-left-radius: 16px;
|
||||
border-bottom-right-radius: 16px;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
@ -67,12 +67,12 @@
|
||||
@include colorVariant(var(--information), var(--gray-soft-lightest));
|
||||
}
|
||||
&.is-secondary {
|
||||
@include colorVariant(var(--secondary), var(--gray-hard-darkest));
|
||||
@include colorVariant(var(--secondary), var(--secondary-text-color));
|
||||
}
|
||||
&.is-black {
|
||||
@include colorVariant(var(--gray-hard-darkest), var(--gray-soft-lightest));
|
||||
}
|
||||
&.is-main {
|
||||
@include colorVariant(var(--main), var(--gray-soft-lightest));
|
||||
@include colorVariant(var(--main), var(--main-text-color));
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,94 @@
|
||||
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
||||
gap: 3.2rem;
|
||||
|
||||
.panel { margin-bottom: 0; }
|
||||
.panel {
|
||||
position: relative;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-relations {
|
||||
padding: 1.6rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
row-gap: 1.6rem;
|
||||
background-color: var(--gray-soft-light);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
.space-parent {
|
||||
@include text-lg(500);
|
||||
color: var(--gray-hard-light);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.space-current {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
|
||||
&.has-parent::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-bottom: 1px solid var(--gray-hard-lightest);
|
||||
border-left: 1px solid var(--gray-hard-lightest);
|
||||
}
|
||||
|
||||
&-name {
|
||||
padding: 0.8rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
@include text-lg(600);
|
||||
color: var(--main);
|
||||
background-color: var(--gray-soft-lightest);
|
||||
border-radius: var(--border-radius-sm);
|
||||
svg { color: var(--gray-hard-darkest); }
|
||||
}
|
||||
}
|
||||
|
||||
.related-machines,
|
||||
.related-spaces {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
list-style-type: none;
|
||||
}
|
||||
.related-spaces {
|
||||
position: relative;
|
||||
padding-inline-start: 2.6rem;
|
||||
@include text-lg(500);
|
||||
color: var(--gray-hard-light);
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.8rem;
|
||||
content: "";
|
||||
display: block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-bottom: 1px solid var(--gray-hard-lightest);
|
||||
border-left: 1px solid var(--gray-hard-lightest);
|
||||
}
|
||||
}
|
||||
.related-machines {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0.4rem;
|
||||
left: 1.6rem;
|
||||
content: "";
|
||||
display: block;
|
||||
width: 0.6rem;
|
||||
height: 0.6rem;
|
||||
border-bottom: 1px solid var(--gray-hard-lightest);
|
||||
border-left: 1px solid var(--gray-hard-lightest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -48,6 +48,7 @@
|
||||
<div class="spaces-grid">
|
||||
<div ng-class="{'disabled-reservable' : space.disabled && spaceFiltering === 'all'}" ng-repeat="space in spaces | filterDisabled:spaceFiltering">
|
||||
<div class="widget panel panel-default">
|
||||
<fab-badge ng-if="isAuthorized('admin') && (space.parent || space.children.length)" icon="'pin-map'" icon-width="'3rem'"></fab-badge>
|
||||
<div class="panel-heading picture" ng-if="!space.space_image_attributes" ng-click="showSpace(space)">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:'Font Awesome 5 Free'/icon" bs-holder class="img-responsive">
|
||||
</div>
|
||||
|
@ -41,6 +41,23 @@
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-4">
|
||||
<div class="spaces-relations m" ng-show="space.parent || space.children.length || space.machines.length">
|
||||
<p ng-show="space.parent" class="space-parent">{{ space.parent.name }}</p>
|
||||
<div class="space-current" ng-class="{'has-parent': space.parent}">
|
||||
<span class="space-current-name">
|
||||
<svg viewBox="0 0 24 24" width="3rem">
|
||||
<use href="../../images/icons.svg#pin-map"/>
|
||||
</svg>
|
||||
{{ space.name }}
|
||||
</span>
|
||||
</div>
|
||||
<ul ng-show="space.machines.length" class="related-machines">
|
||||
<li ng-repeat="machine in space.machines" class="">{{ machine.name }}</li>
|
||||
</ul>
|
||||
<ul ng-show="space.children.length" class="related-spaces">
|
||||
<li ng-repeat="child_space in space.children" class="">{{ child_space.name }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-show="space.characteristics">
|
||||
<div class="panel-heading b-b small">
|
||||
|
@ -8,6 +8,7 @@ module AvailabilityHelper
|
||||
EVENT_COLOR = '#dd7e6b'
|
||||
IS_RESERVED_BY_CURRENT_USER = '#b2e774'
|
||||
IS_FULL = '#eeeeee'
|
||||
IS_BLOCKED = '#b2e774' # same color as IS_RESERVED_BY_CURRENT_USER for simplicity
|
||||
|
||||
def availability_border_color(availability)
|
||||
case availability.available_type
|
||||
@ -38,6 +39,8 @@ module AvailabilityHelper
|
||||
IS_RESERVED_BY_CURRENT_USER
|
||||
elsif slot.full?
|
||||
IS_FULL
|
||||
elsif slot.is_blocked
|
||||
IS_BLOCKED
|
||||
else
|
||||
SPACE_COLOR
|
||||
end
|
||||
|
@ -283,6 +283,26 @@ class CartItem::Reservation < CartItem::BaseItem
|
||||
return false
|
||||
end
|
||||
|
||||
unless operator.privileged?
|
||||
if reservable_type == "Space"
|
||||
space = reservable
|
||||
slot = reservation_slot.slot
|
||||
if Slots::InterblockingService.new.blocked_slots_for_spaces([space], [slot]).any?
|
||||
errors.add(:slot, I18n.t('cart_item_validation.blocked_by_another_reservation'))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if reservable_type == "Machine"
|
||||
machine = reservable
|
||||
slot = reservation_slot.slot
|
||||
if Slots::InterblockingService.new.blocked_slots_for_machines([machine], [slot]).any?
|
||||
errors.add(:slot, I18n.t('cart_item_validation.blocked_by_another_reservation'))
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
|
@ -44,6 +44,8 @@ class Machine < ApplicationRecord
|
||||
|
||||
has_many :plan_limitations, dependent: :destroy, inverse_of: :machine, foreign_type: 'limitable_type', as: :limitable
|
||||
|
||||
belongs_to :space
|
||||
|
||||
after_create :create_statistic_subtype
|
||||
after_create :create_machine_prices
|
||||
after_create :update_gateway_product
|
||||
|
@ -14,6 +14,8 @@ class Slot < ApplicationRecord
|
||||
|
||||
after_create_commit :create_places_cache
|
||||
|
||||
attr_accessor :is_blocked
|
||||
|
||||
# @param reservable [Machine,Space,Training,Event,NilClass]
|
||||
# @return [Integer] the total number of reserved places
|
||||
def reserved_places(reservable = nil)
|
||||
|
@ -6,6 +6,7 @@
|
||||
class Space < ApplicationRecord
|
||||
extend FriendlyId
|
||||
friendly_id :name, use: :slugged
|
||||
has_ancestry cache_depth: true
|
||||
|
||||
validates :name, :default_places, presence: true
|
||||
|
||||
@ -34,6 +35,8 @@ class Space < ApplicationRecord
|
||||
has_many :cart_item_space_reservations, class_name: 'CartItem::SpaceReservation', dependent: :destroy, inverse_of: :reservable,
|
||||
foreign_type: 'reservable_type', as: :reservable
|
||||
|
||||
has_many :machines, dependent: :nullify
|
||||
|
||||
after_create :create_statistic_subtype
|
||||
after_create :create_space_prices
|
||||
after_create :update_gateway_product
|
||||
|
@ -39,7 +39,10 @@ class Availabilities::AvailabilitiesService
|
||||
availabilities = availabilities(ma_availabilities, 'machines', user, window[:start], window[:end])
|
||||
|
||||
if @level == 'slot'
|
||||
availabilities.map(&:slots).flatten
|
||||
slots = availabilities.map(&:slots).flatten
|
||||
|
||||
blocked_slots = Slots::InterblockingService.new.blocked_slots_for_machines(machines, slots)
|
||||
flag_or_remove_blocked_slots(slots, blocked_slots, @current_user)
|
||||
else
|
||||
availabilities
|
||||
end
|
||||
@ -57,7 +60,10 @@ class Availabilities::AvailabilitiesService
|
||||
availabilities = availabilities(sp_availabilities, 'space', user, window[:start], window[:end])
|
||||
|
||||
if @level == 'slot'
|
||||
availabilities.map(&:slots).flatten
|
||||
slots = availabilities.map(&:slots).flatten
|
||||
|
||||
blocked_slots = Slots::InterblockingService.new.blocked_slots_for_spaces(spaces, slots)
|
||||
flag_or_remove_blocked_slots(slots, blocked_slots, @current_user)
|
||||
else
|
||||
availabilities
|
||||
end
|
||||
@ -133,4 +139,15 @@ class Availabilities::AvailabilitiesService
|
||||
|
||||
qry
|
||||
end
|
||||
|
||||
def flag_or_remove_blocked_slots(slots, blocked_slots, user)
|
||||
if user.admin? || user.manager?
|
||||
blocked_slots.each do |slot|
|
||||
slot.is_blocked = true
|
||||
end
|
||||
else
|
||||
slots -= blocked_slots
|
||||
end
|
||||
slots
|
||||
end
|
||||
end
|
||||
|
@ -9,9 +9,9 @@ class MachineService
|
||||
def list(filters)
|
||||
sort_by = Setting.get('machines_sort_by') || 'default'
|
||||
machines = if sort_by == 'default'
|
||||
Machine.includes(:machine_image, :plans)
|
||||
Machine.includes(:machine_image, :plans, :space)
|
||||
else
|
||||
Machine.includes(:machine_image, :plans).order(sort_by)
|
||||
Machine.includes(:machine_image, :plans, :space).order(sort_by)
|
||||
end
|
||||
# do not include soft destroyed
|
||||
machines = machines.where(deleted_at: nil)
|
||||
|
60
app/services/slots/interblocking_service.rb
Normal file
60
app/services/slots/interblocking_service.rb
Normal file
@ -0,0 +1,60 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Services around slots
|
||||
module Slots; end
|
||||
|
||||
# Check the reservation status of a slot
|
||||
class Slots::InterblockingService
|
||||
# returns an array of slots
|
||||
# @param spaces [ActiveRecord::Relation<Space>]
|
||||
# @param slots [ActiveRecord::Relation<Slot>]
|
||||
def blocked_slots_for_spaces(spaces, slots)
|
||||
blocking_slots_start_at_end_at = []
|
||||
spaces.each do |space|
|
||||
parent_and_child_space_ids = [space.parent_id, space.child_ids].flatten.compact
|
||||
blocking_slots_start_at_end_at << Slot.joins(slots_reservations: :reservation)
|
||||
.where(slots_reservations: { canceled_at: nil },
|
||||
reservations: { reservable_type: 'Space',
|
||||
reservable_id: parent_and_child_space_ids })
|
||||
.pluck(:start_at, :end_at)
|
||||
.map { |d| %i[start_at end_at].zip(d).to_h }
|
||||
child_machine_ids = Machine.where(space_id: [space.id, parent_and_child_space_ids].flatten)
|
||||
blocking_slots_start_at_end_at << Slot.joins(slots_reservations: :reservation)
|
||||
.where(slots_reservations: { canceled_at: nil },
|
||||
reservations: { reservable_type: 'Machine',
|
||||
reservable_id: child_machine_ids })
|
||||
.pluck(:start_at, :end_at)
|
||||
.map { |d| %i[start_at end_at].zip(d).to_h }
|
||||
end
|
||||
blocking_slots_start_at_end_at = blocking_slots_start_at_end_at.flatten&.uniq || []
|
||||
|
||||
blocked_slots(slots, blocking_slots_start_at_end_at)
|
||||
end
|
||||
|
||||
def blocked_slots_for_machines(machines, slots)
|
||||
blocking_slots_start_at_end_at = []
|
||||
machines.each do |machine|
|
||||
parent_space_ids = machine.space&.path_ids
|
||||
next unless parent_space_ids&.any?
|
||||
|
||||
blocking_slots_start_at_end_at << Slot.joins(slots_reservations: :reservation)
|
||||
.where(slots_reservations: { canceled_at: nil }, reservations: { reservable_type: 'Space',
|
||||
reservable_id: parent_space_ids })
|
||||
.pluck(:start_at, :end_at)
|
||||
.map { |d| %i[start_at end_at].zip(d).to_h }
|
||||
end
|
||||
blocking_slots_start_at_end_at = blocking_slots_start_at_end_at.flatten&.uniq || []
|
||||
|
||||
blocked_slots(slots, blocking_slots_start_at_end_at)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def blocked_slots(slots, blocking_slots)
|
||||
slots.select do |slot|
|
||||
blocking_slots.find do |blocking_slot|
|
||||
blocking_slot[:start_at] < slot.end_at && slot.start_at < blocking_slot[:end_at]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -15,12 +15,16 @@ class Slots::TitleService
|
||||
is_reserved_by_user = slot.reserved_by?(@user&.id, reservables)
|
||||
|
||||
name = reservables.map(&:name).join(', ')
|
||||
if !is_reserved && !is_reserved_by_user
|
||||
name
|
||||
elsif is_reserved && !is_reserved_by_user
|
||||
"#{name} #{@show_name ? "- #{slot_users_names(slot, reservables)}" : ''}"
|
||||
if !slot.is_blocked
|
||||
if !is_reserved && !is_reserved_by_user
|
||||
name
|
||||
elsif is_reserved && !is_reserved_by_user
|
||||
"#{name} #{@show_name ? "- #{slot_users_names(slot, reservables)}" : ''}"
|
||||
else
|
||||
"#{name} - #{I18n.t('availabilities.i_ve_reserved')}"
|
||||
end
|
||||
else
|
||||
"#{name} - #{I18n.t('availabilities.i_ve_reserved')}"
|
||||
"#{name} - #{I18n.t('availabilities.blocked')}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -7,6 +7,7 @@ json.start slot.start_at.iso8601
|
||||
json.end slot.end_at.iso8601
|
||||
json.is_reserved slot.reserved?(reservable)
|
||||
json.is_completed slot.full?(reservable)
|
||||
json.is_blocked slot.is_blocked
|
||||
json.backgroundColor 'white'
|
||||
|
||||
json.availability_id slot.availability_id
|
||||
|
@ -15,3 +15,9 @@ if machine.advanced_accounting
|
||||
json.partial! 'api/advanced_accounting/advanced_accounting', advanced_accounting: machine.advanced_accounting
|
||||
end
|
||||
end
|
||||
|
||||
if machine.space_id
|
||||
json.space do
|
||||
json.name machine.space.name
|
||||
end
|
||||
end
|
@ -14,3 +14,7 @@ if space.advanced_accounting
|
||||
json.partial! 'api/advanced_accounting/advanced_accounting', advanced_accounting: space.advanced_accounting
|
||||
end
|
||||
end
|
||||
|
||||
json.machines space.machines do |machine|
|
||||
json.name machine.name
|
||||
end
|
||||
|
@ -2,4 +2,15 @@
|
||||
|
||||
json.array!(@spaces) do |space|
|
||||
json.partial! 'api/spaces/space', space: space
|
||||
|
||||
parent = @spaces_indexed_with_parent[space]
|
||||
if parent
|
||||
json.parent do
|
||||
json.name parent.name
|
||||
end
|
||||
end
|
||||
|
||||
json.children @spaces_grouped_by_parent_id[space.id] do |child|
|
||||
json.name child.name
|
||||
end
|
||||
end
|
||||
|
@ -1,9 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.partial! 'api/spaces/space', space: @space
|
||||
json.extract! @space, :characteristics, :created_at, :updated_at
|
||||
json.extract! @space, :characteristics, :machine_ids, :child_ids, :created_at, :updated_at
|
||||
json.space_files_attributes @space.space_files do |f|
|
||||
json.id f.id
|
||||
json.attachment_name f.attachment_identifier
|
||||
json.attachment_url f.attachment_url
|
||||
end
|
||||
|
||||
if @space.parent
|
||||
json.parent do
|
||||
json.name @space.parent.name
|
||||
end
|
||||
end
|
||||
json.children @space.children do |child|
|
||||
json.name child.name
|
||||
end
|
||||
|
1
config/initializers/ancestry.rb
Normal file
1
config/initializers/ancestry.rb
Normal file
@ -0,0 +1 @@
|
||||
Ancestry.default_ancestry_format = :materialized_path2
|
@ -111,6 +111,10 @@ en:
|
||||
save: "Save"
|
||||
create_success: "The space was created successfully"
|
||||
update_success: "The space was updated successfully"
|
||||
associated_machines: "Included machines"
|
||||
children_spaces: "Included spaces"
|
||||
associated_objects: "Associated objects"
|
||||
associated_objects_warning: "Only use these fields if you want interblocking reservation between spaces, child spaces and machines. If you want machine and space reservations to remain independent, please leave the following fields blank."
|
||||
event_form:
|
||||
ACTION_title: "{ACTION, select, create{New} other{Update the}} event"
|
||||
title: "Title"
|
||||
|
@ -111,6 +111,10 @@ fr:
|
||||
save: "Enregistrer"
|
||||
create_success: "L'espace a bien été créé"
|
||||
update_success: "L'espace a bien été mis à jour"
|
||||
associated_machines: "Machines"
|
||||
children_spaces: "Espaces"
|
||||
associated_objects: "Machines et sous-espaces"
|
||||
associated_objects_warning: "Utilisez ces champs uniquement si vous souhaitez que la réservation de l'espace bloque la réservation des machines associées et des sous-espaces (et vice-versa). Si vous souhaitez que les réservations restent indépendantes, veuillez laisser les champs suivants vides."
|
||||
event_form:
|
||||
ACTION_title: "{ACTION, select, create{Nouvel } other{Mettre à jour l''}}événement"
|
||||
title: "Titre"
|
||||
|
@ -66,6 +66,7 @@ en:
|
||||
not_available: "Not available"
|
||||
reserving: "I'm reserving"
|
||||
i_ve_reserved: "I've reserved"
|
||||
blocked: "Blocked"
|
||||
length_must_be_slot_multiple: "must be at least %{MIN} minutes after the start date"
|
||||
must_be_associated_with_at_least_1_machine: "must be associated with at least 1 machine"
|
||||
deleted_user: "Deleted user"
|
||||
@ -562,6 +563,7 @@ en:
|
||||
space: "This space is disabled"
|
||||
machine: "This machine is disabled"
|
||||
reservable: "This machine is not reservable"
|
||||
blocked_by_another_reservation: "This slot is blocked by another reservation"
|
||||
cart_validation:
|
||||
select_user: "Please select a user before continuing"
|
||||
settings:
|
||||
|
@ -66,6 +66,7 @@ fr:
|
||||
not_available: "Non disponible"
|
||||
reserving: "Je réserve"
|
||||
i_ve_reserved: "J'ai réservé"
|
||||
blocked: "Bloquée"
|
||||
length_must_be_slot_multiple: "doit être au moins %{MIN} minutes après la date de début"
|
||||
must_be_associated_with_at_least_1_machine: "doit être associé avec au moins 1 machine"
|
||||
deleted_user: "Utilisateur supprimé"
|
||||
@ -562,6 +563,7 @@ fr:
|
||||
space: "Cet espace est désactivé"
|
||||
machine: "Cette machine est désactivée"
|
||||
reservable: "Cette machine n'est pas réservable"
|
||||
blocked_by_another_reservation: "Ce créneau est bloqué par une autre réservation"
|
||||
cart_validation:
|
||||
select_user: "Veuillez sélectionner un utilisateur avant de continuer"
|
||||
settings:
|
||||
|
@ -3,6 +3,7 @@
|
||||
class AddStpCustomerIdToUsers < ActiveRecord::Migration[4.2]
|
||||
def up
|
||||
add_column :users, :stp_customer_id, :string
|
||||
User.reset_column_information
|
||||
User.all.each do |user|
|
||||
if user.stp_customer_id.blank?
|
||||
user.send(:create_stripe_customer)
|
||||
|
9
db/migrate/20230728072726_add_ancestry_to_spaces.rb
Normal file
9
db/migrate/20230728072726_add_ancestry_to_spaces.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class AddAncestryToSpaces < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :spaces, :ancestry, :string, collation: 'C'
|
||||
Space.update_all(ancestry: '/')
|
||||
change_column_null(:spaces, :ancestry, false)
|
||||
add_column :spaces, :ancestry_depth, :integer, default: 0
|
||||
add_index :spaces, :ancestry
|
||||
end
|
||||
end
|
5
db/migrate/20230728090257_add_space_id_to_machines.rb
Normal file
5
db/migrate/20230728090257_add_space_id_to_machines.rb
Normal file
@ -0,0 +1,5 @@
|
||||
class AddSpaceIdToMachines < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_reference :machines, :space, foreign_key: true, index: true
|
||||
end
|
||||
end
|
@ -1736,7 +1736,8 @@ CREATE TABLE public.machines (
|
||||
disabled boolean,
|
||||
deleted_at timestamp without time zone,
|
||||
machine_category_id bigint,
|
||||
reservable boolean DEFAULT true
|
||||
reservable boolean DEFAULT true,
|
||||
space_id bigint
|
||||
);
|
||||
|
||||
|
||||
@ -3351,7 +3352,9 @@ CREATE TABLE public.spaces (
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
characteristics text,
|
||||
disabled boolean,
|
||||
deleted_at timestamp without time zone
|
||||
deleted_at timestamp without time zone,
|
||||
ancestry character varying NOT NULL COLLATE pg_catalog."C",
|
||||
ancestry_depth integer DEFAULT 0
|
||||
);
|
||||
|
||||
|
||||
@ -6853,6 +6856,13 @@ CREATE INDEX index_machines_on_machine_category_id ON public.machines USING btre
|
||||
CREATE UNIQUE INDEX index_machines_on_slug ON public.machines USING btree (slug);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_machines_on_space_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_machines_on_space_id ON public.machines USING btree (space_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_notification_preferences_on_notification_type_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
@ -7392,6 +7402,13 @@ CREATE INDEX index_spaces_availabilities_on_availability_id ON public.spaces_ava
|
||||
CREATE INDEX index_spaces_availabilities_on_space_id ON public.spaces_availabilities USING btree (space_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_spaces_on_ancestry; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_spaces_on_ancestry ON public.spaces USING btree (ancestry);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_spaces_on_deleted_at; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
@ -8451,6 +8468,14 @@ ALTER TABLE ONLY public.statistic_profile_prepaid_packs
|
||||
ADD CONSTRAINT fk_rails_b0251cdfcf FOREIGN KEY (prepaid_pack_id) REFERENCES public.prepaid_packs(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: machines fk_rails_b2e37688bb; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.machines
|
||||
ADD CONSTRAINT fk_rails_b2e37688bb FOREIGN KEY (space_id) REFERENCES public.spaces(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: orders fk_rails_b33ed6c672; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@ -9154,7 +9179,9 @@ INSERT INTO "schema_migrations" (version) VALUES
|
||||
('20230626103314'),
|
||||
('20230626122844'),
|
||||
('20230626122947'),
|
||||
('20230710072403');
|
||||
('20230710072403'),
|
||||
('20230718133636'),
|
||||
('20230718134350'),
|
||||
('20230720085857');
|
||||
('20230720085857'),
|
||||
('20230728072726'),
|
||||
('20230728090257');
|
||||
|
1
test/fixtures/spaces.yml
vendored
1
test/fixtures/spaces.yml
vendored
@ -7,3 +7,4 @@ space_1:
|
||||
created_at: 2017-02-15 15:55:04.123928000 Z
|
||||
updated_at: 2017-02-15 15:55:04.123928000 Z
|
||||
characteristics: Scie à chantourner, rabot, dégauchisseuse, chanfreineuse et pyrograveur
|
||||
ancestry: '/'
|
||||
|
@ -31,4 +31,43 @@ class SpaceTest < ActiveSupport::TestCase
|
||||
assert_nil Space.find_by(slug: slug)
|
||||
assert_nil StatisticSubType.find_by(key: slug)
|
||||
end
|
||||
|
||||
test "space can be associated with spaces in a tree-like structure" do
|
||||
space_1 = Space.create!(name: "space 1", default_places: 2)
|
||||
space_1_1 = Space.create!(name: "space 1_1", default_places: 2, parent: space_1)
|
||||
space_1_2 = Space.create!(name: "space 1_2", default_places: 2, parent: space_1)
|
||||
space_1_2_1 = Space.create!(name: "space 1_2_1", default_places: 2, parent: space_1_2)
|
||||
space_other = Space.create!(name: "space other", default_places: 2)
|
||||
|
||||
assert_equal [space_1_1, space_1_2], space_1.children
|
||||
assert_equal [], space_1_1.children
|
||||
assert_equal [space_1_2_1], space_1_2.children
|
||||
|
||||
assert_equal [space_1, space_1_2], space_1_2_1.ancestors
|
||||
assert_equal [space_1], space_1_2.ancestors
|
||||
assert_equal [space_1], space_1_1.ancestors
|
||||
assert_equal [], space_1.ancestors
|
||||
|
||||
assert_equal [space_1_1, space_1_2, space_1_2_1], space_1.descendants
|
||||
assert_equal [], space_1_1.descendants
|
||||
assert_equal [space_1_2_1], space_1_2.descendants
|
||||
assert_equal [], space_1_2_1.descendants
|
||||
|
||||
assert_equal [], space_other.descendants
|
||||
assert_equal [], space_other.ancestors
|
||||
end
|
||||
|
||||
test "space can be associated with machines" do
|
||||
space = spaces(:space_1)
|
||||
machine_1 = machines(:machine_1)
|
||||
machine_2 = machines(:machine_2)
|
||||
|
||||
space.machines << machine_1
|
||||
space.machines << machine_2
|
||||
|
||||
assert_equal 2, space.machines.count
|
||||
|
||||
assert_equal space, machine_1.space
|
||||
assert_equal space, machine_2.space
|
||||
end
|
||||
end
|
||||
|
@ -66,6 +66,50 @@ class Availabilities::AvailabilitiesServiceTest < ActiveSupport::TestCase
|
||||
assert_equal availability.end_at, slots.max_by(&:end_at).end_at
|
||||
end
|
||||
|
||||
test '[member] machines availabilities with blocked slots' do
|
||||
space = Space.find(1)
|
||||
machine = Machine.find(1).tap { |m| m.update!(space: space) }
|
||||
reservation = Reservation.create!(reservable: space, statistic_profile: statistic_profiles(:jdupont))
|
||||
machine_availability = Availability.create!(availabilities(:availability_7).slice(:start_at, :end_at, :slot_duration)
|
||||
.merge(available_type: 'machines', machine_ids: [machine.id]))
|
||||
|
||||
machine_slot = availabilities(:availability_7).slots.first
|
||||
slot = Slot.create!(availability: machine_availability, start_at: machine_slot.start_at, end_at: machine_slot.end_at)
|
||||
|
||||
opts = { start: 2.days.from_now.beginning_of_day, end: 4.days.from_now.end_of_day }
|
||||
service = Availabilities::AvailabilitiesService.new(@no_subscription)
|
||||
slots = service.machines([machine], @no_subscription, opts)
|
||||
assert_equal 7, slots.count
|
||||
|
||||
SlotsReservation.create!(reservation: reservation, slot: slot)
|
||||
|
||||
slots = service.machines([machine], @no_subscription, opts)
|
||||
assert_equal 5, slots.count
|
||||
end
|
||||
|
||||
test '[admin] machines availabilities with blocked slots' do
|
||||
space = Space.find(1)
|
||||
machine = Machine.find(1).tap { |m| m.update!(space: space) }
|
||||
reservation = Reservation.create!(reservable: space, statistic_profile: statistic_profiles(:jdupont))
|
||||
machine_availability = Availability.create!(availabilities(:availability_7).slice(:start_at, :end_at, :slot_duration)
|
||||
.merge(available_type: 'machines', machine_ids: [machine.id]))
|
||||
|
||||
machine_slot = availabilities(:availability_7).slots.first
|
||||
slot = Slot.create!(availability: machine_availability, start_at: machine_slot.start_at, end_at: machine_slot.end_at)
|
||||
|
||||
opts = { start: 2.days.from_now.beginning_of_day, end: 4.days.from_now.end_of_day }
|
||||
service = Availabilities::AvailabilitiesService.new(@admin)
|
||||
slots = service.machines([machine], @admin, opts)
|
||||
assert_equal 7, slots.count
|
||||
|
||||
SlotsReservation.create!(reservation: reservation, slot: slot)
|
||||
|
||||
slots = service.machines([machine], @admin, opts)
|
||||
assert_equal 7, slots.count
|
||||
|
||||
assert_equal 2, slots.count(&:is_blocked)
|
||||
end
|
||||
|
||||
test 'spaces availabilities' do
|
||||
service = Availabilities::AvailabilitiesService.new(@no_subscription)
|
||||
slots = service.spaces([Space.find(1)], @no_subscription, { start: 2.days.from_now.beginning_of_day, end: 4.days.from_now.end_of_day })
|
||||
@ -77,6 +121,50 @@ class Availabilities::AvailabilitiesServiceTest < ActiveSupport::TestCase
|
||||
assert_equal availability.end_at, slots.max_by(&:end_at).end_at
|
||||
end
|
||||
|
||||
test '[member] spaces availabilities with blocked slots' do
|
||||
space = Space.find(1)
|
||||
machine = machines(:machine_1).tap { |m| m.update!(space: space) }
|
||||
reservation = Reservation.create!(reservable: machine, statistic_profile: statistic_profiles(:jdupont))
|
||||
machine_availability = Availability.create!(availabilities(:availability_18).slice(:start_at, :end_at, :slot_duration)
|
||||
.merge(available_type: 'machines', machine_ids: [machine.id]))
|
||||
|
||||
space_slot = availabilities(:availability_18).slots.first
|
||||
slot = Slot.create!(availability: machine_availability, start_at: space_slot.start_at, end_at: space_slot.end_at)
|
||||
|
||||
opts = { start: 2.days.from_now.beginning_of_day, end: 4.days.from_now.end_of_day }
|
||||
service = Availabilities::AvailabilitiesService.new(@no_subscription)
|
||||
slots = service.spaces([space], @no_subscription, opts)
|
||||
assert_equal 4, slots.count
|
||||
|
||||
SlotsReservation.create!(reservation: reservation, slot: slot)
|
||||
|
||||
slots = service.spaces([space], @no_subscription, opts)
|
||||
assert_equal 3, slots.count
|
||||
end
|
||||
|
||||
test '[admin] spaces availabilities with blocked slots' do
|
||||
space = Space.find(1)
|
||||
machine = machines(:machine_1).tap { |m| m.update!(space: space) }
|
||||
reservation = Reservation.create!(reservable: machine, statistic_profile: statistic_profiles(:jdupont))
|
||||
machine_availability = Availability.create!(availabilities(:availability_18).slice(:start_at, :end_at, :slot_duration)
|
||||
.merge(available_type: 'machines', machine_ids: [machine.id]))
|
||||
|
||||
space_slot = availabilities(:availability_18).slots.first
|
||||
slot = Slot.create!(availability: machine_availability, start_at: space_slot.start_at, end_at: space_slot.end_at)
|
||||
|
||||
opts = { start: 2.days.from_now.beginning_of_day, end: 4.days.from_now.end_of_day }
|
||||
service = Availabilities::AvailabilitiesService.new(@admin)
|
||||
slots = service.spaces([space], @admin, opts)
|
||||
assert_equal 4, slots.count
|
||||
|
||||
SlotsReservation.create!(reservation: reservation, slot: slot)
|
||||
|
||||
slots = service.spaces([space], @admin, opts)
|
||||
assert_equal 4, slots.count
|
||||
|
||||
assert_equal 1, slots.count(&:is_blocked)
|
||||
end
|
||||
|
||||
test 'trainings availabilities' do
|
||||
service = Availabilities::AvailabilitiesService.new(@no_subscription)
|
||||
trainings = [Training.find(1), Training.find(2)]
|
||||
|
108
test/services/slots/interblocking_service_test.rb
Normal file
108
test/services/slots/interblocking_service_test.rb
Normal file
@ -0,0 +1,108 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class Slots::InterblockingServiceTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@parent_space = spaces(:space_1)
|
||||
@child_space = Space.create!(name: 'space 1-1', default_places: 2, parent: @parent_space)
|
||||
@space_availability = availabilities(:availability_18)
|
||||
@space_slots = @space_availability.slots
|
||||
@machine_availability = availabilities(:availability_7)
|
||||
@machine_slots = @machine_availability.slots
|
||||
@machine = machines(:machine_1).tap { |m| m.update!(space: @child_space) }
|
||||
end
|
||||
|
||||
test '#blocked_slots_for_spaces : no reservation' do
|
||||
assert_empty Slots::InterblockingService.new.blocked_slots_for_spaces([@parent_space], @space_slots)
|
||||
assert_empty Slots::InterblockingService.new.blocked_slots_for_spaces([@child_space], @space_slots)
|
||||
end
|
||||
|
||||
test '#blocked_slots_for_spaces : reservation on parent space' do
|
||||
reservation = Reservation.create!(reservable: @parent_space, statistic_profile: statistic_profiles(:jdupont))
|
||||
SlotsReservation.create!(reservation: reservation, slot: @space_slots.first)
|
||||
|
||||
assert_equal [@space_slots.first], Slots::InterblockingService.new.blocked_slots_for_spaces([@child_space], @space_slots)
|
||||
assert_empty Slots::InterblockingService.new.blocked_slots_for_spaces([@parent_space], @space_slots)
|
||||
end
|
||||
|
||||
test '#blocked_slots_for_spaces : reservation on child space' do
|
||||
reservation = Reservation.create!(reservable: @child_space, statistic_profile: statistic_profiles(:jdupont))
|
||||
SlotsReservation.create!(reservation: reservation, slot: @space_slots.first)
|
||||
|
||||
assert_equal [@space_slots.first], Slots::InterblockingService.new.blocked_slots_for_spaces([@parent_space], @space_slots)
|
||||
assert_empty Slots::InterblockingService.new.blocked_slots_for_spaces([@child_space], @space_slots)
|
||||
end
|
||||
|
||||
test '#blocked_slots_for_spaces : reservation on child machine' do
|
||||
reservation = Reservation.create!(reservable: @machine, statistic_profile: statistic_profiles(:jdupont))
|
||||
machine_availability = Availability.create!(@space_availability.slice(:start_at, :end_at, :slot_duration)
|
||||
.merge(available_type: 'machines', machine_ids: [@machine.id]))
|
||||
|
||||
slot = Slot.create!(availability: machine_availability, start_at: @space_slots.first.start_at, end_at: @space_slots.first.end_at)
|
||||
SlotsReservation.create!(reservation: reservation, slot: slot)
|
||||
|
||||
assert_equal [@space_slots.first], Slots::InterblockingService.new.blocked_slots_for_spaces([@parent_space], @space_slots)
|
||||
assert_equal [@space_slots.first], Slots::InterblockingService.new.blocked_slots_for_spaces([@child_space], @space_slots)
|
||||
|
||||
slot.update!(start_at: slot.start_at - 15.minutes, end_at: slot.end_at - 15.minutes)
|
||||
|
||||
# still match when overlapping
|
||||
assert_equal [@space_slots.first], Slots::InterblockingService.new.blocked_slots_for_spaces([@parent_space], @space_slots)
|
||||
assert_equal [@space_slots.first], Slots::InterblockingService.new.blocked_slots_for_spaces([@child_space], @space_slots)
|
||||
|
||||
slot.update!(start_at: slot.start_at - 45.minutes, end_at: slot.end_at - 45.minutes)
|
||||
|
||||
# not overlapping anymore
|
||||
assert_empty Slots::InterblockingService.new.blocked_slots_for_spaces([@parent_space], @space_slots)
|
||||
assert_empty Slots::InterblockingService.new.blocked_slots_for_spaces([@child_space], @space_slots)
|
||||
end
|
||||
|
||||
test '#blocked_slots_for_machines : no reservation' do
|
||||
assert_empty Slots::InterblockingService.new.blocked_slots_for_machines([@machine], @machine_slots)
|
||||
end
|
||||
|
||||
test '#blocked_slots_for_machines : reservation on parent space' do
|
||||
reservation = Reservation.create!(reservable: @parent_space, statistic_profile: statistic_profiles(:jdupont))
|
||||
|
||||
space_availability = Availability.create!(@machine_availability.slice(:start_at, :end_at, :slot_duration)
|
||||
.merge(available_type: 'space', space_ids: [@parent_space.id]))
|
||||
|
||||
slot = Slot.create!(availability: space_availability, start_at: @machine_slots.first.start_at, end_at: @machine_slots.first.end_at)
|
||||
SlotsReservation.create!(reservation: reservation, slot: slot)
|
||||
|
||||
assert_equal [@machine_slots.first], Slots::InterblockingService.new.blocked_slots_for_machines([@machine], @machine_slots)
|
||||
|
||||
slot.update!(start_at: slot.start_at - 15.minutes, end_at: slot.end_at - 15.minutes)
|
||||
|
||||
# still match when overlapping
|
||||
assert_equal [@machine_slots.first], Slots::InterblockingService.new.blocked_slots_for_machines([@machine], @machine_slots)
|
||||
|
||||
slot.update!(start_at: slot.start_at - 45.minutes, end_at: slot.end_at - 45.minutes)
|
||||
|
||||
# not overlapping anymore
|
||||
assert_empty Slots::InterblockingService.new.blocked_slots_for_machines([@machine], @machine_slots)
|
||||
end
|
||||
|
||||
test '#blocked_slots_for_machines : reservation on child space' do
|
||||
reservation = Reservation.create!(reservable: @child_space, statistic_profile: statistic_profiles(:jdupont))
|
||||
|
||||
space_availability = Availability.create!(@machine_availability.slice(:start_at, :end_at, :slot_duration)
|
||||
.merge(available_type: 'space', space_ids: [@child_space.id]))
|
||||
|
||||
slot = Slot.create!(availability: space_availability, start_at: @machine_slots.first.start_at, end_at: @machine_slots.first.end_at)
|
||||
SlotsReservation.create!(reservation: reservation, slot: slot)
|
||||
|
||||
assert_equal [@machine_slots.first], Slots::InterblockingService.new.blocked_slots_for_machines([@machine], @machine_slots)
|
||||
|
||||
slot.update!(start_at: slot.start_at - 15.minutes, end_at: slot.end_at - 15.minutes)
|
||||
|
||||
# still match when overlapping
|
||||
assert_equal [@machine_slots.first], Slots::InterblockingService.new.blocked_slots_for_machines([@machine], @machine_slots)
|
||||
|
||||
slot.update!(start_at: slot.start_at - 45.minutes, end_at: slot.end_at - 45.minutes)
|
||||
|
||||
# not overlapping anymore
|
||||
assert_empty Slots::InterblockingService.new.blocked_slots_for_machines([@machine], @machine_slots)
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user