1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-18 07:52:23 +01:00

export machines availabilities

This commit is contained in:
Sylvain 2017-03-02 12:34:28 +01:00
parent 4d09ed37a3
commit 504fe49964
18 changed files with 124 additions and 72 deletions

View File

@ -4,8 +4,8 @@
# Controller used in the calendar management page
##
Application.Controllers.controller "AdminCalendarController", ["$scope", "$state", "$uibModal", "moment", "Availability", 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', '_t', 'uiCalendarConfig', 'CalendarConfig'
($scope, $state, $uibModal, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, _t, uiCalendarConfig, CalendarConfig) ->
Application.Controllers.controller "AdminCalendarController", ["$scope", "$state", "$uibModal", "moment", "Availability", 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', '_t', 'uiCalendarConfig', 'CalendarConfig'
($scope, $state, $uibModal, moment, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, _t, uiCalendarConfig, CalendarConfig) ->
@ -121,6 +121,17 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
##
# Callback to alert the admin that the export request was acknowledged and is
# processing right now.
##
$scope.alertExport = (type) ->
Export.status({category: 'availabilities', type: type}).then (res) ->
unless (res.data.exists)
growl.success _t('admin_calendar.export_is_running_you_ll_be_notified_when_its_ready')
### PRIVATE SCOPE ###
##

View File

@ -30,6 +30,12 @@
</div>
<div class="col-sm-12 col-md-12 col-lg-3">
<div class="m text-center">
<a class="btn btn-default" ng-href="api/availabilities/export_index.xlsx" target="export-frame" ng-click="alertExport('index')">
<i class="fa fa-file-excel-o"></i> {{ 'admin_calendar.availabilities' | translate }}
</a>
<iframe name="export-frame" height="0" width="0" class="none"></iframe>
</div>
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3 translate>{{ 'admin_calendar.ongoing_reservations' }}</h3>

View File

@ -6,7 +6,7 @@ class API::AvailabilitiesController < API::ApiController
respond_to :json
## machine/spaces availabilities are divided in multiple slots of 60 minutes
SLOT_DURATION = 60
SLOT_DURATION = ApplicationHelper::SLOT_DURATION
def index
authorize Availability
@ -69,7 +69,6 @@ class API::AvailabilitiesController < API::ApiController
# request for many days (week or month)
else
@availabilities = Availability.includes(:tags, :machines, :trainings, :spaces, :event, :slots)
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
@availabilities.each do |a|
@ -222,6 +221,22 @@ class API::AvailabilitiesController < API::ApiController
@reservation_slots = @availability.slots.includes(reservations: [user: [:profile]]).order('slots.start_at ASC')
end
def export_availabilities
authorize :export
export = Export.where({category:'availabilities', export_type: 'index'}).where('created_at > ?', Availability.maximum('updated_at')).last
if export.nil? || !FileTest.exist?(export.file)
@export = Export.new({category:'availabilities', export_type: 'index', user: current_user})
if @export.save
render json: {export_id: @export.id}, status: :ok
else
render json: @export.errors, status: :unprocessable_entity
end
else
send_file File.join(Rails.root, export.file), :type => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :disposition => 'attachment'
end
end
private
def set_availability
@availability = Availability.find(params[:id])

View File

@ -26,7 +26,14 @@ class API::ExportsController < API::ApiController
when 'members'
export = export.where('created_at > ?', User.with_role(:member).maximum('updated_at'))
else
raise ArgumentError, "Unknown type #{params[:type]}"
raise ArgumentError, "Unknown export users/#{params[:type]}"
end
elsif params[:category] === 'availabilities'
case params[:type]
when 'index'
export = export.where('created_at > ?', Availability.maximum('updated_at'))
else
raise ArgumentError, "Unknown type availabilities/#{params[:type]}"
end
end
export = export.last

View File

@ -3,6 +3,9 @@ module ApplicationHelper
include Twitter::Autolink
require 'message_format'
## machine/spaces availabilities are divided in multiple slots of 60 minutes
SLOT_DURATION = 60
##
# Verify if the provided attribute is in the provided attributes array, whatever it exists or not
# @param attributes {Array|nil}
@ -26,6 +29,10 @@ module ApplicationHelper
nil
end
def print_slot(starting, ending)
"#{starting.strftime('%H:%M')} - #{ending.strftime('%H:%M')}"
end
def class_exists?(class_name)
klass = Module.const_get(class_name)
return klass.is_a?(Class)

View File

@ -28,6 +28,8 @@ class Export < ActiveRecord::Base
StatisticsExportWorker.perform_async(self.id)
when 'users'
UsersExportWorker.perform_async(self.id)
when 'availabilities'
AvailabilitiesExportWorker.perform_async(self.id)
else
raise NoMethodError, "Unknown export service for #{category}/#{export_type}"
end

View File

@ -1,5 +1,5 @@
class AvailabilityPolicy < ApplicationPolicy
%w(index? show? create? update? destroy? reservations?).each do |action|
%w(index? show? create? update? destroy? reservations? export?).each do |action|
define_method action do
user.is_admin?
end

View File

@ -1,5 +1,5 @@
class ExportPolicy < Struct.new(:user, :export)
%w(export_reservations export_members export_subscriptions download status).each do |action|
%w(export_reservations export_members export_subscriptions export_availabilities download status).each do |action|
define_method "#{action}?" do
user.is_admin?
end

View File

@ -6,58 +6,22 @@ require 'active_record'
# require any helpers
require './app/helpers/application_helper'
class UsersExportService
class AvailabilitiesExportService
# export subscriptions
def export_subscriptions(export)
@subscriptions = Subscription.all.includes(:plan, :user => [:profile])
# export all availabilities
def export_index(export)
@availabilities = Availability.all.includes(:machines, :trainings, :spaces, :event, :slots)
ActionController::Base.prepend_view_path './app/views/'
# place data in view_assigns
view_assigns = {subscriptions: @subscriptions}
view_assigns = {availabilities: @availabilities}
av = ActionView::Base.new(ActionController::Base.view_paths, view_assigns)
av.class_eval do
# include any needed helpers (for the view)
include ApplicationHelper
end
content = av.render template: 'exports/users_subscriptions.xlsx.axlsx'
# write content to file
File.open(export.file,"w+b") {|f| f.puts content }
end
# export reservations
def export_reservations(export)
@reservations = Reservation.all.includes(:slots, :reservable, :user => [:profile])
ActionController::Base.prepend_view_path './app/views/'
# place data in view_assigns
view_assigns = {reservations: @reservations}
av = ActionView::Base.new(ActionController::Base.view_paths, view_assigns)
av.class_eval do
# include any needed helpers (for the view)
include ApplicationHelper
end
content = av.render template: 'exports/users_reservations.xlsx.axlsx'
# write content to file
File.open(export.file,"w+b") {|f| f.puts content }
end
# export members
def export_members(export)
@members = User.with_role(:member).includes(:group, :trainings, :tags, :invoices, :projects, :subscriptions => [:plan], :profile => [:address, :organization => [:address]])
ActionController::Base.prepend_view_path './app/views/'
# place data in view_assigns
view_assigns = {members: @members}
av = ActionView::Base.new(ActionController::Base.view_paths, view_assigns)
av.class_eval do
# include any needed helpers (for the view)
include ApplicationHelper
end
content = av.render template: 'exports/users_members.xlsx.axlsx'
content = av.render template: 'exports/availabilities_index.xlsx.axlsx'
# write content to file
File.open(export.file,"w+b") {|f| f.puts content }
end

View File

@ -3,31 +3,38 @@ wb = xlsx_package.workbook
header = wb.styles.add_style :b => true, :bg_color => Stylesheet.primary.upcase.gsub('#', 'FF'), :fg_color => 'FFFFFFFF'
date = wb.styles.add_style :format_code => Rails.application.secrets.excel_date_format
wb.add_worksheet(name: t('export_subscriptions.subscriptions')) do |sheet|
## Machines slots
wb.add_worksheet(name: t('export_availabilities.machines')) do |sheet|
## data table
# heading labels
columns = [t('export_subscriptions.id'), t('export_subscriptions.customer'), t('export_subscriptions.email'),
t('export_subscriptions.subscription'), t('export_subscriptions.period'), t('export_subscriptions.start_date'),
t('export_subscriptions.expiration_date'), t('export_subscriptions.amount'), t('export_subscriptions.payment_method')]
columns = [t('export_availabilities.date'), t('export_availabilities.slot'), t('export_availabilities.machine'),
t('export_availabilities.reservations')]
sheet.add_row columns, :style => header
# data rows
@subscriptions.each do |sub|
data = [
sub.user.id,
sub.user.profile.full_name,
sub.user.email,
sub.plan.human_readable_name(group: true),
t("duration.#{sub.plan.interval}", count: sub.plan.interval_count),
sub.created_at.to_date,
sub.expired_at.to_date,
number_to_currency(sub.plan.amount / 100),
(sub.stp_subscription_id.nil?)? t('export_subscriptions.local_payment') : t('export_subscriptions.online_payment')
]
styles = [nil, nil, nil, nil, nil, date, date, nil, nil]
types = [:integer, :string, :string, :string, :string, :date, :date, :string, :string]
@availabilities.where(available_type: 'machines').order(:start_at).each do |a|
a.machines.each do |m|
((a.end_at - a.start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
start_at = a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes
end_at = a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes
reservations = 0
if a.slots and a.slots.map(&:start_at).include? start_at
reservations = 1
end
sheet.add_row data, :style => styles, :types => types
data = [
start_at.to_date,
print_slot(start_at, end_at),
m.name,
reservations
]
styles = [date, nil, nil, nil]
types = [:date, :string, :string, :integer]
sheet.add_row data, :style => styles, :types => types
end
end
end
end

View File

@ -1,4 +1,4 @@
class UsersExportWorker
class AvailabilitiesExportWorker
include Sidekiq::Worker
def perform(export_id)
@ -8,14 +8,14 @@ class UsersExportWorker
raise SecurityError, 'Not allowed to export'
end
unless export.category == 'users'
unless export.category == 'availabilities'
raise KeyError, 'Wrong worker called'
end
service = UsersExportService.new
service = AvailabilitiesExportService.new
method_name = "export_#{export.export_type}"
if %w(members subscriptions reservations).include?(export.export_type) and service.respond_to?(method_name)
if %w(index).include?(export.export_type) and service.respond_to?(method_name)
service.public_send(method_name, export)
NotificationCenter.call type: :notify_admin_export_complete,

View File

@ -18,6 +18,7 @@ en:
trainings: "Trainings"
machines: "Machines"
spaces: "Spaces"
availabilities: "Availabilities"
ongoing_reservations: "Ongoing reservations"
no_reservations: "No reservations"
confirmation_required: "Confirmation required"
@ -45,6 +46,7 @@ en:
the_slot_START-END_has_been_successfully_deleted: "The slot {{START}} - {{END}} has been successfully deleted" # angular interpolation
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Unable to delete the slot {{START}} - {{END}} because it's already reserved by a member" # angular interpolation
you_should_select_at_least_a_machine: "You should select at least one machine on this slot."
export_is_running_you_ll_be_notified_when_its_ready: "Export is running. You'll be notified when it's ready."
project_elements:
# management of the projects' components

View File

@ -18,6 +18,7 @@ fr:
trainings: "Formations"
machines: "Machines"
spaces: "Espaces"
availabilities: "Disponibilités"
ongoing_reservations: "Réservations en cours"
no_reservations: "Aucune réservation"
confirmation_required: "Confirmation requise"
@ -45,6 +46,7 @@ fr:
the_slot_START-END_has_been_successfully_deleted: "Le créneau {{START}} - {{END}} a bien été supprimé" # angular interpolation
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Le créneau {{START}} - {{END}} n'a pu être supprimé car il est déjà réservé par un membre" # angular interpolation
you_should_select_at_least_a_machine: "Vous devriez sélectionne au moins une machine pour ce créneau."
export_is_running_you_ll_be_notified_when_its_ready: "L'export est en cours. Vous serez notifié lorsqu'il sera prêt."
project_elements:
# gestion des éléments constituant les projets

View File

@ -186,6 +186,18 @@ en:
local_payment: "Payment at the reception"
online_payment: "Online payment"
export_availabilities:
# reservation slots export, by type, to EXCEL format
machines: "Machines"
trainings: "Trainings"
spaces: "Spaces"
events: "Events"
date: "Date"
slot: "Slot"
machine: "Machine"
reservations: "Reservations"
api:
notifications:
# internal app notifications
@ -279,6 +291,7 @@ en:
users_members: "of the members' list"
users_subscriptions: "of the subscriptions' list"
users_reservations: "of the reservations' list"
availabilities_index: "of the reservations availabilities"
is_over: "is over."
download_here: "Download here"
notify_member_about_coupon:

View File

@ -186,6 +186,18 @@ fr:
local_payment: "Paiement à l'accueil"
online_payment: "Paiement en ligne"
export_availabilities:
# export des listes de créneaux de réservations, par type, au format EXCEL
machines: "Machines"
trainings: "Formations"
spaces: "Espaces"
events: "Évènements"
date: "Date"
slot: "Créneau"
machine: "Machine"
reservations: "Réservations"
api:
notifications:
# notifications internes à l'application
@ -279,6 +291,7 @@ fr:
users_members: "de la liste des membres"
users_subscriptions: "de la liste des abonnements"
users_reservations: "de la liste des réservations"
availabilities_index: "des disponibilités de réservations"
is_over: "est terminé."
download_here: "Téléchargez ici"
notify_member_about_coupon:

View File

@ -272,6 +272,7 @@ en:
users_members: "of the members' list"
users_subscriptions: "of the subscriptions' list"
users_reservations: "of the reservations' list"
availabilities_index: "of the reservations availabilities"
click_to_download: "Excel file generated successfully. To download it, click"
here: "here"

View File

@ -272,6 +272,7 @@ fr:
users_members: "de la liste des membres"
users_subscriptions: "de la liste des abonnements"
users_reservations: "de la liste des réservations"
availabilities_index: "des disponibilités de réservations"
click_to_download: "La génération est terminée. Pour télécharger le fichier Excel, cliquez"
here: "ici"

View File

@ -84,6 +84,7 @@ Rails.application.routes.draw do
get 'spaces/:space_id', action: 'spaces', on: :collection
get 'reservations', on: :member
get 'public', on: :collection
get '/export_index', action: 'export_availabilities', on: :collection
end
resources :groups, only: [:index, :create, :update, :destroy]