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 | 
