diff options
| author | cedricnjanga | 2017-12-14 18:47:44 +0100 |
|---|---|---|
| committer | Luc Donnet | 2017-12-21 13:54:07 +0100 |
| commit | f2d00fad59d97130babc95f37d495245f3d53f80 (patch) | |
| tree | 97b100f22b716ed00fd456b8834d80db129a9981 | |
| parent | 7f9a61d5823bd6611351fcc6dce79a2159976b34 (diff) | |
| download | chouette-core-f2d00fad59d97130babc95f37d495245f3d53f80.tar.bz2 | |
Refs #5301 First draft for Business Calendars
| -rw-r--r-- | app/controllers/business_calendars_controller.rb | 33 | ||||
| -rw-r--r-- | app/decorators/business_calendar_decorator.rb | 25 | ||||
| -rw-r--r-- | app/models/business_calendar.rb | 35 | ||||
| -rw-r--r-- | app/models/calendar.rb | 156 | ||||
| -rw-r--r-- | app/models/concerns/calendar_support.rb | 159 | ||||
| -rw-r--r-- | app/policies/business_calendar_policy.rb | 20 | ||||
| -rw-r--r-- | app/views/business_calendars/_filters.html.slim | 18 | ||||
| -rw-r--r-- | app/views/business_calendars/_form.html.slim | 47 | ||||
| -rw-r--r-- | app/views/business_calendars/index.html.slim | 48 | ||||
| -rw-r--r-- | app/views/business_calendars/new.html.slim | 6 | ||||
| -rw-r--r-- | app/views/dashboards/_dashboard.html.slim | 14 | ||||
| -rw-r--r-- | config/breadcrumbs.rb | 9 | ||||
| -rw-r--r-- | config/routes.rb | 2 | ||||
| -rw-r--r-- | db/migrate/20171214131755_create_business_calendars.rb | 13 | ||||
| -rw-r--r-- | db/schema.rb | 12 | ||||
| -rw-r--r-- | lib/stif/permission_translator.rb | 1 | ||||
| -rw-r--r-- | spec/factories/business_calendars.rb | 8 | ||||
| -rw-r--r-- | spec/models/business_calendar_spec.rb | 5 |
18 files changed, 456 insertions, 155 deletions
diff --git a/app/controllers/business_calendars_controller.rb b/app/controllers/business_calendars_controller.rb new file mode 100644 index 000000000..ee990dfba --- /dev/null +++ b/app/controllers/business_calendars_controller.rb @@ -0,0 +1,33 @@ +class BusinessCalendarsController < ChouetteController + include PolicyChecker + include RansackDateFilter + defaults resource_class: BusinessCalendar + before_action only: [:index] { set_date_time_params("bounding_dates", Date) } + + def index + index! do + scope = self.ransack_period_range(scope: @business_calendars, error_message: t('compliance_check_sets.filters.error_period_filter'), query: :overlapping) + @q = scope.ransack(params[:q]) + @business_calendars = decorate_business_calendars(@q.result.paginate(page: params[:page], per_page: 30)) + end + end + + def show + show! do + @business_calendar = @business_calendar.decorate + end + end + + private + + def business_calendar_params + permitted_params = [:id, :name, :short_name, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]] + params.require(:business_calendar).permit(*permitted_params) + end + + def decorate_business_calendars(business_calendars) + ModelDecorator.decorate( + business_calendars, + with: BusinessCalendarDecorator) + end +end
\ No newline at end of file diff --git a/app/decorators/business_calendar_decorator.rb b/app/decorators/business_calendar_decorator.rb new file mode 100644 index 000000000..9e8f21572 --- /dev/null +++ b/app/decorators/business_calendar_decorator.rb @@ -0,0 +1,25 @@ +class BusinessCalendarDecorator < Draper::Decorator + delegate_all + + def action_links + links = [] + + if h.policy(object).update? + links << Link.new( + content: h.update_link_content, + href: h.edit_business_calendar_path(object) + ) + end + + if h.policy(object).destroy? + links << Link.new( + content: h.destroy_link_content, + href: h.calendar_path(object), + method: :delete, + data: { confirm: h.t('calendars.actions.destroy_confirm') } + ) + end + + links + end +end
\ No newline at end of file diff --git a/app/models/business_calendar.rb b/app/models/business_calendar.rb new file mode 100644 index 000000000..0064b4537 --- /dev/null +++ b/app/models/business_calendar.rb @@ -0,0 +1,35 @@ +require 'range_ext' +require_relative 'calendar/date_value' +require_relative 'calendar/period' + +class BusinessCalendar < ActiveRecord::Base + include CalendarSupport + + scope :overlapping, -> (period_range) do + where("(periods.begin <= :end AND periods.end >= :begin) OR (dates BETWEEN :begin AND :end)", {begin: period_range.begin, end: period_range.end}) + end + + def bounding_dates + bounding_min = self.dates.min + bounding_max = self.dates.max + + unless self.periods.empty? + bounding_min = periods_min_date if periods_min_date && (bounding_min.nil? || (periods_min_date < bounding_min)) + + bounding_max = periods_max_date if periods_max_date && (bounding_max.nil? || (bounding_max < periods_max_date)) + end + + [bounding_min, bounding_max].compact + end + + def periods_max_date + return nil if self.periods.empty? + self.periods.max.end + end + + def periods_min_date + return nil if self.periods.empty? + self.periods.min.begin + end + +end
\ No newline at end of file diff --git a/app/models/calendar.rb b/app/models/calendar.rb index b2e73929f..4e8cd0977 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -3,22 +3,10 @@ require_relative 'calendar/date_value' require_relative 'calendar/period' class Calendar < ActiveRecord::Base - has_paper_trail - belongs_to :organisation - has_many :time_tables - - validates_presence_of :name, :short_name, :organisation - validates_uniqueness_of :short_name - - after_initialize :init_dates_and_date_ranges - + include CalendarSupport + scope :contains_date, ->(date) { where('date ? = any (dates) OR date ? <@ any (date_ranges)', date, date) } - def init_dates_and_date_ranges - self.dates ||= [] - self.date_ranges ||= [] - end - def self.ransackable_scopes(auth_object = nil) [:contains_date] end @@ -35,144 +23,4 @@ class Calendar < ActiveRecord::Base end end - - ### Calendar::Period - # Required by coocon - def build_period - Calendar::Period.new - end - - def periods - @periods ||= init_periods - end - - def init_periods - (date_ranges || []) - .each_with_index - .map( &Calendar::Period.method(:from_range) ) - end - private :init_periods - - validate :validate_periods - - def validate_periods - periods_are_valid = periods.all?(&:valid?) - - periods.each do |period| - if period.intersect?(periods) - period.errors.add(:base, I18n.t('calendars.errors.overlapped_periods')) - periods_are_valid = false - end - end - - unless periods_are_valid - errors.add(:periods, :invalid) - end - end - - def flatten_date_array attributes, key - date_int = %w(1 2 3).map {|e| attributes["#{key}(#{e}i)"].to_i } - Date.new(*date_int) - end - - def periods_attributes=(attributes = {}) - @periods = [] - attributes.each do |index, period_attribute| - # Convert date_select to date - ['begin', 'end'].map do |attr| - period_attribute[attr] = flatten_date_array(period_attribute, attr) - end - period = Calendar::Period.new(period_attribute.merge(id: index)) - @periods << period unless period.marked_for_destruction? - end - - date_ranges_will_change! - end - - before_validation :fill_date_ranges - - def fill_date_ranges - if @periods - self.date_ranges = @periods.map(&:range).compact.sort_by(&:begin) - end - end - - after_save :clear_periods - - def clear_periods - @periods = nil - end - - private :clear_periods - - ### Calendar::DateValue - - # Required by coocon - def build_date_value - Calendar::DateValue.new - end - - def date_values - @date_values ||= init_date_values - end - - def init_date_values - if dates - dates.each_with_index.map { |d, index| Calendar::DateValue.from_date(index, d) } - else - [] - end - end - private :init_date_values - - validate :validate_date_values - - def validate_date_values - date_values_are_valid = date_values.all?(&:valid?) - - date_values.each do |date_value| - if date_values.count { |d| d.value == date_value.value } > 1 - date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_dates')) - date_values_are_valid = false - end - date_ranges.each do |date_range| - if date_range.cover? date_value.value - date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges')) - date_values_are_valid = false - end - end - end - - unless date_values_are_valid - errors.add(:date_values, :invalid) - end - end - - def date_values_attributes=(attributes = {}) - @date_values = [] - attributes.each do |index, date_value_attribute| - date_value_attribute['value'] = flatten_date_array(date_value_attribute, 'value') - date_value = Calendar::DateValue.new(date_value_attribute.merge(id: index)) - @date_values << date_value unless date_value.marked_for_destruction? - end - - dates_will_change! - end - - before_validation :fill_dates - - def fill_dates - if @date_values - self.dates = @date_values.map(&:value).compact.sort - end - end - - after_save :clear_date_values - - def clear_date_values - @date_values = nil - end - - private :clear_date_values - end diff --git a/app/models/concerns/calendar_support.rb b/app/models/concerns/calendar_support.rb new file mode 100644 index 000000000..411acb949 --- /dev/null +++ b/app/models/concerns/calendar_support.rb @@ -0,0 +1,159 @@ +module CalendarSupport + extend ActiveSupport::Concern + + included do + has_paper_trail + belongs_to :organisation + has_many :time_tables + + validates_presence_of :name, :short_name, :organisation + validates_uniqueness_of :short_name + after_initialize :init_dates_and_date_ranges + + + def init_dates_and_date_ranges + self.dates ||= [] + binding.pry + self.date_ranges ||= [] + end + ### Calendar::Period + # Required by coocon + def build_period + Calendar::Period.new + end + + def periods + binding.pry + @periods ||= init_periods + end + + def init_periods + (date_ranges || []) + .each_with_index + .map( &Calendar::Period.method(:from_range) ) + end + private :init_periods + + validate :validate_periods + + def validate_periods + periods_are_valid = periods.all?(&:valid?) + + periods.each do |period| + if period.intersect?(periods) + period.errors.add(:base, I18n.t('calendars.errors.overlapped_periods')) + periods_are_valid = false + end + end + + unless periods_are_valid + errors.add(:periods, :invalid) + end + end + + def flatten_date_array attributes, key + date_int = %w(1 2 3).map {|e| attributes["#{key}(#{e}i)"].to_i } + Date.new(*date_int) + end + + def periods_attributes=(attributes = {}) + @periods = [] + attributes.each do |index, period_attribute| + # Convert date_select to date + ['begin', 'end'].map do |attr| + period_attribute[attr] = flatten_date_array(period_attribute, attr) + end + period = Calendar::Period.new(period_attribute.merge(id: index)) + @periods << period unless period.marked_for_destruction? + end + + date_ranges_will_change! + end + + before_validation :fill_date_ranges + + def fill_date_ranges + if @periods + self.date_ranges = @periods.map(&:range).compact.sort_by(&:begin) + end + end + + after_save :clear_periods + + def clear_periods + @periods = nil + end + + private :clear_periods + + ### Calendar::DateValue + + # Required by coocon + def build_date_value + Calendar::DateValue.new + end + + def date_values + @date_values ||= init_date_values + end + + def init_date_values + if dates + dates.each_with_index.map { |d, index| Calendar::DateValue.from_date(index, d) } + else + [] + end + end + private :init_date_values + + validate :validate_date_values + + def validate_date_values + date_values_are_valid = date_values.all?(&:valid?) + + date_values.each do |date_value| + if date_values.count { |d| d.value == date_value.value } > 1 + date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_dates')) + date_values_are_valid = false + end + date_ranges.each do |date_range| + if date_range.cover? date_value.value + date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges')) + date_values_are_valid = false + end + end + end + + unless date_values_are_valid + errors.add(:date_values, :invalid) + end + end + + def date_values_attributes=(attributes = {}) + @date_values = [] + attributes.each do |index, date_value_attribute| + date_value_attribute['value'] = flatten_date_array(date_value_attribute, 'value') + date_value = Calendar::DateValue.new(date_value_attribute.merge(id: index)) + @date_values << date_value unless date_value.marked_for_destruction? + end + + dates_will_change! + end + + before_validation :fill_dates + + def fill_dates + if @date_values + self.dates = @date_values.map(&:value).compact.sort + end + end + + after_save :clear_date_values + + def clear_date_values + @date_values = nil + end + + private :clear_date_values + end +end
\ No newline at end of file diff --git a/app/policies/business_calendar_policy.rb b/app/policies/business_calendar_policy.rb new file mode 100644 index 000000000..6be40f20f --- /dev/null +++ b/app/policies/business_calendar_policy.rb @@ -0,0 +1,20 @@ +class BusinessCalendarPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope + end + end + + def create? + user.has_permission?('business_calendars.create') + end + + def destroy? + organisation_match? && user.has_permission?('business_calendars.destroy') + end + + def update? + organisation_match? && user.has_permission?('business_calendars.update') + end + +end
\ No newline at end of file diff --git a/app/views/business_calendars/_filters.html.slim b/app/views/business_calendars/_filters.html.slim new file mode 100644 index 000000000..8bc9ac2ae --- /dev/null +++ b/app/views/business_calendars/_filters.html.slim @@ -0,0 +1,18 @@ += search_form_for @q, url: business_calendars_path, builder: SimpleForm::FormBuilder, html: { method: :get, class: 'form form-filter' } do |f| + .ffg-row + .input-group.search_bar + = f.search_field :name_cont, class: 'form-control', placeholder: 'Indiquez un nom de calendrier commercial...' + span.input-group-btn + button.btn.btn-default#search_btn type='submit' + span.fa.fa-search + + .form-group.togglable + = f.label BusinessCalendar.human_attribute_name(:bounding_dates), required: false, class: 'control-label' + .filter_menu + = f.simple_fields_for :bounding_dates do |p| + = p.input :start_date, as: :date, label: t('simple_form.from'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @begin_range, include_blank: @begin_range ? false : true + = p.input :end_date, as: :date, label: t('simple_form.to'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @end_range, include_blank: @end_range ? false : true + + .actions + = link_to 'Effacer', business_calendars_path, class: 'btn btn-link' + = f.submit 'Filtrer', id: 'calendar_filter_btn', class: 'btn btn-default' diff --git a/app/views/business_calendars/_form.html.slim b/app/views/business_calendars/_form.html.slim new file mode 100644 index 000000000..c2c9fb3a1 --- /dev/null +++ b/app/views/business_calendars/_form.html.slim @@ -0,0 +1,47 @@ += simple_form_for @business_calendar, html: { class: 'form-horizontal', id: 'business_calendar_form' }, wrapper: :horizontal_form do |f| + .row + .col-lg-12 + = f.input :name + + .separator + + .row + .col-lg-12 + .subform + .nested-head + .wrapper + div + .form-group + label.control-label + = BusinessCalendar.human_attribute_name(:date) + div + + = f.simple_fields_for :date_values do |date_value| + = render 'calendars/date_fields', f: date_value + + .links.nested-linker + = link_to_add_association t('simple_form.labels.calendar.add_a_date'), f, :dates, class: 'btn btn-outline-primary' + + .separator + + .row + .col-lg-12 + .subform + .nested-head + .wrapper + div + .form-group + label.control-label + = t('simple_form.labels.calendar.ranges.begin') + div + .form-group + label.control-label + = t('simple_form.labels.calendar.ranges.end') + div + + = f.simple_fields_for :periods do |period| + = render 'period_fields', f: period + .links.nested-linker + = link_to_add_association t('simple_form.labels.calendar.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary' + + = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'business_calendar_form' diff --git a/app/views/business_calendars/index.html.slim b/app/views/business_calendars/index.html.slim new file mode 100644 index 000000000..ce7f2343b --- /dev/null +++ b/app/views/business_calendars/index.html.slim @@ -0,0 +1,48 @@ +- breadcrumb :business_calendars +- content_for :page_header_actions do + - if policy(BusinessCalendar).create? + = link_to(t('actions.add'), new_business_calendar_path, class: 'btn btn-default') + +.page_content + .container-fluid + - if params[:q].present? or @business_calendars.any? + .row + .col-lg-12 + = render 'filters' + + - if @business_calendars.any? + .row + .col-lg-12 + = table_builder_2 @business_calendars, + [ \ + TableBuilderHelper::Column.new( \ + key: :name, \ + attribute: 'name', \ + link_to: lambda do |business_calendar| \ + calendar_path(business_calendar) \ + end \ + ), \ + TableBuilderHelper::Column.new( \ + key: :color, \ + attribute: 'color'\ + ), \ + TableBuilderHelper::Column.new( \ + key: :created_at, \ + attribute: 'created_at' \ + ), \ + TableBuilderHelper::Column.new( \ + key: :updated_at, \ + attribute: 'updated_at' \ + ) \ + ], + links: [:show, :edit], + cls: 'table has-filter' + + = new_pagination @business_calendars, 'pull-right' + + - unless @business_calendars.any? + .row.mt-xs + .col-lg-12 + = replacement_msg t('calendars.search_no_results') + += javascript_pack_tag 'date_filters' diff --git a/app/views/business_calendars/new.html.slim b/app/views/business_calendars/new.html.slim new file mode 100644 index 000000000..ee44609ba --- /dev/null +++ b/app/views/business_calendars/new.html.slim @@ -0,0 +1,6 @@ +- breadcrumb :business_calendars +.page_content + .container-fluid + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + = render 'form' diff --git a/app/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim index 7d547bf4c..27d23cfc1 100644 --- a/app/views/dashboards/_dashboard.html.slim +++ b/app/views/dashboards/_dashboard.html.slim @@ -33,6 +33,20 @@ .panel-body em.small.text-muted Aucun modèle de calendrier défini + .panel.panel-default + .panel-heading + h3.panel-title.with_actions + = "Calendriers commerciaux" + div + = link_to '', business_calendars_path, class: ' fa fa-chevron-right pull-right' + - if @dashboard.current_organisation.business_calendars.present? + .list-group + - @dashboard.current_organisation.business_calendars.order("updated_at desc").limit(5).each do |business_calendar| + = link_to business_calendar.name, business_calendar_path(business_calendar), class: 'list-group-item' + - else + .panel-body + em.small.text-muted Aucun calendrier commercial défini + .col-lg-6.col-md-6.col-sm-6.col-xs-12 .panel.panel-default .panel-heading diff --git a/config/breadcrumbs.rb b/config/breadcrumbs.rb index eb285b731..1a252273b 100644 --- a/config/breadcrumbs.rb +++ b/config/breadcrumbs.rb @@ -162,6 +162,15 @@ crumb :line do |line| parent :lines, line.line_referential end +crumb :business_calendars do + link I18n.t('business_calendars.index.title'), business_calendars_path +end + +crumb :business_calendar do |calendar| + link breadcrumb_name(business_calendar), business_calendar_path(business_calendar) + parent :business_calendars +end + crumb :calendars do link I18n.t('calendars.index.title'), calendars_path end diff --git a/config/routes.rb b/config/routes.rb index 65fa62557..b4191da0f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -99,6 +99,8 @@ ChouetteIhm::Application.routes.draw do resources :networks end + resources :business_calendars + resources :calendars do get :autocomplete, on: :collection, controller: 'autocomplete_calendars' end diff --git a/db/migrate/20171214131755_create_business_calendars.rb b/db/migrate/20171214131755_create_business_calendars.rb new file mode 100644 index 000000000..5bb3db9d3 --- /dev/null +++ b/db/migrate/20171214131755_create_business_calendars.rb @@ -0,0 +1,13 @@ +class CreateBusinessCalendars < ActiveRecord::Migration + def change + create_table :business_calendars do |t| + t.string :name + t.string :short_name + t.string :color + t.date :dates + t.daterange :date_ranges + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index f2642f8fc..a51a3bb42 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -81,6 +81,16 @@ ActiveRecord::Schema.define(version: 20171214130636) do add_index "api_keys", ["organisation_id"], name: "index_api_keys_on_organisation_id", using: :btree + create_table "business_calendars", id: :bigserial, force: :cascade do |t| + t.string "name" + t.string "short_name" + t.string "color" + t.date "dates" + t.daterange "date_ranges" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "calendars", id: :bigserial, force: :cascade do |t| t.string "name" t.string "short_name" @@ -403,9 +413,9 @@ ActiveRecord::Schema.define(version: 20171214130636) do t.string "type" t.integer "parent_id", limit: 8 t.string "parent_type" - t.datetime "notified_parent_at" t.integer "current_step", default: 0 t.integer "total_steps", default: 0 + t.datetime "notified_parent_at" t.string "creator" end diff --git a/lib/stif/permission_translator.rb b/lib/stif/permission_translator.rb index 4acf42884..b93396cc3 100644 --- a/lib/stif/permission_translator.rb +++ b/lib/stif/permission_translator.rb @@ -18,6 +18,7 @@ module Stif %w[ access_points connection_links + business_calendars calendars footnotes imports diff --git a/spec/factories/business_calendars.rb b/spec/factories/business_calendars.rb new file mode 100644 index 000000000..309cc18f4 --- /dev/null +++ b/spec/factories/business_calendars.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :business_calendar do + name "MyString" + color "MyString" + dates "2017-12-14" + periods "" + end +end diff --git a/spec/models/business_calendar_spec.rb b/spec/models/business_calendar_spec.rb new file mode 100644 index 000000000..29f67d49f --- /dev/null +++ b/spec/models/business_calendar_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe BusinessCalendar, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end |
