Browse Source

Init version of calendar management.

Martin Edenhofer 9 years ago
parent
commit
5509f61549

+ 37 - 0
app/assets/javascripts/app/controllers/calendar.js.coffee

@@ -0,0 +1,37 @@
+class Index extends App.ControllerContent
+  constructor: ->
+    super
+
+    # check authentication
+    return if !@authenticate()
+
+    @subscribeId = App.Calendar.subscribe(@render)
+    App.Calendar.fetch()
+
+  render: =>
+    calendars = App.Calendar.all()
+    for calendar in calendars
+
+      # get preview public holidays
+      public_holidays_preview = {}
+      if calendar.public_holidays
+        from = new Date().setTime(new Date().getTime() - (5*24*60*60*1000))
+        till = new Date().setTime(new Date().getTime() + (90*24*60*60*1000))
+        keys = Object.keys(calendar.public_holidays).reverse()
+        #for day, comment of calendar.public_holidays
+        for day in keys
+          itemTime = new Date( Date.parse( "#{day}T00:00:00Z" ) )
+          if itemTime < till && itemTime > from
+            public_holidays_preview[day] = calendar.public_holidays[day]
+      calendar.public_holidays_preview = public_holidays_preview
+
+    @html App.view('calendar')(
+      calendars: calendars
+    )
+
+  release: =>
+    if @subscribeId
+      App.Calendar.unsubscribe(@subscribeId)
+
+
+App.Config.set( 'Calendars', { prio: 2400, name: 'Calendars', parent: '#manage', target: '#manage/calendars', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )

+ 4 - 0
app/assets/javascripts/app/models/calendar.js.coffee

@@ -0,0 +1,4 @@
+class App.Calendar extends App.Model
+  @configure 'Calendar', 'name', 'timezone', 'default', 'business_hours', 'ical_url', 'public_holidays'
+  @extend Spine.Model.Ajax
+  @url: @apiPath + '/calendars'

+ 59 - 0
app/assets/javascripts/app/views/calendar.jst.eco

@@ -0,0 +1,59 @@
+<div class="page-header">
+  <div class="page-header-title">
+    <h1><%- @T('Calendar') %> <small><%- @T('Management') %></small></h1>
+  </div>
+
+  <div class="page-header-meta">
+    <a class="btn btn--success" data-type="new"><%- @T('New Calendar') %></a>
+  </div>
+</div>
+
+<% for calendar in @calendars: %>
+<div class="action">
+  <div class="action-flow">
+    <div class="action-block">
+    <h3><%= calendar.name %></h3>
+      <%- @T('Timezone') %>: <%= calendar.timezone %>
+    </div>
+    <div class="action-block">
+      <%- @T('Business Hours') %>:<br>
+      <table>
+        <tr>
+          <td><%- @T('Monday') %></td><td><% if _.isEmpty(calendar.business_hours['mon']): %>-<% else: %><% for from, till of calendar.business_hours['mon']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
+        </tr>
+        <tr>
+          <td><%- @T('Tuesday') %></td><td><% if _.isEmpty(calendar.business_hours['tue']): %>-<% else: %><% for from, till of calendar.business_hours['tue']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
+        </tr>
+        <tr>
+          <td><%- @T('Wednesday') %></td><td><% if _.isEmpty(calendar.business_hours['wed']): %>-<% else: %><% for from, till of calendar.business_hours['wed']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
+        </tr>
+        <tr>
+          <td><%- @T('Thursday') %></td><td><% if _.isEmpty(calendar.business_hours['thu']): %>-<% else: %><% for from, till of calendar.business_hours['thu']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
+        </tr>
+        <tr>
+          <td><%- @T('Friday') %></td><td><% if _.isEmpty(calendar.business_hours['fri']): %>-<% else: %><% for from, till of calendar.business_hours['fri']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
+        </tr>
+        <tr>
+          <td><%- @T('Saturday') %></td><td><% if _.isEmpty(calendar.business_hours['sat']): %>-<% else: %><% for from, till of calendar.business_hours['sat']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
+        </tr>
+        <tr>
+          <td><%- @T('Sunday') %></td><td><% if _.isEmpty(calendar.business_hours['sun']): %>-<% else: %><% for from, till of calendar.business_hours['sun']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
+        </tr>
+      </table>
+    </div>
+    <div class="action-block">
+      <%- @T('Public Holidays') %>:<br>
+      <% for holiday, meta of calendar.public_holidays_preview: %>
+        <%= holiday %>: <%= meta.summary %><br>
+      <% end %>
+    </div>
+  </div>
+  <div class="action-controls">
+    <% if !calendar.default: %>
+      <div class="sla-toggle btn btn--danger btn--secondary js-toggle"><%- @T('Delete') %></div>
+      <div class="sla-toggle btn btn--secondary js-toggle"><%- @T('Set as Default') %></div>
+    <% end %>
+    <div class="sla-edit btn js-edit"><%- @T('Edit') %></div>
+  </div>
+</div>
+<% end %>

+ 1 - 13
app/controllers/application_controller.rb

@@ -287,19 +287,7 @@ class ApplicationController < ActionController::Base
     }
 
     # get all time zones
-    config['timezones'] = {}
-    TZInfo::Timezone.all.each { |t|
-
-      # ignore the following time zones
-      next if t.name =~ /^GMT/
-      next if t.name =~ /^Etc/
-      next if t.name =~ /^MET/
-      next if t.name =~ /^MST/
-      next if t.name =~ /^ROC/
-      next if t.name =~ /^ROK/
-      diff = t.current_period.utc_total_offset / 60 / 60
-      config['timezones'][ t.name ] = diff
-    }
+    config['timezones'] = Calendar.timezones
 
     # remember if we can to swich back to user
     if session[:switched_from_user_id]

+ 30 - 0
app/controllers/calendars_controller.rb

@@ -0,0 +1,30 @@
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
+
+class CalendarsController < ApplicationController
+  before_action :authentication_check
+
+  def index
+    return if deny_if_not_role(Z_ROLENAME_ADMIN)
+    model_index_render(Calendar, params)
+  end
+
+  def show
+    return if deny_if_not_role(Z_ROLENAME_ADMIN)
+    model_show_render(Calendar, params)
+  end
+
+  def create
+    return if deny_if_not_role(Z_ROLENAME_ADMIN)
+    model_create_render(Calendar, params)
+  end
+
+  def update
+    return if deny_if_not_role(Z_ROLENAME_ADMIN)
+    model_update_render(Calendar, params)
+  end
+
+  def destroy
+    return if deny_if_not_role(Z_ROLENAME_ADMIN)
+    model_destory_render(Calendar, params)
+  end
+end

+ 197 - 0
app/models/calendar.rb

@@ -0,0 +1,197 @@
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
+
+class Calendar < ApplicationModel
+  store :business_hours
+  store :public_holidays
+
+=begin
+
+get default calendar
+
+  calendar = Calendar.default
+
+returns calendar object
+
+=end
+
+  def self.default
+    find_by(default: true)
+  end
+
+=begin
+
+returnes preset of ical feeds
+
+  feeds = Calendar.ical_feeds
+
+returns
+
+  {
+    'US Holidays' => 'http://www.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics',
+    ...
+  }
+
+=end
+
+  def self.ical_feeds
+    gfeeds = {
+      'Australian Holidays' => 'en.australian',
+      'Austrian Holidays' => 'de.austrian',
+      'Brazilian Holidays' => 'en.brazilian',
+      'Canadian Holidays' => 'en.canadian',
+      'China Holidays' => 'en.china',
+      'Switzerland Holidays' => 'de.ch',
+      'Christian Holidays' => 'en.christian',
+      'Danish Holidays' => 'da.danish',
+      'Dutch Holidays' => 'nl.dutch',
+      'Finnish Holidays' => 'en.finnish',
+      'French Holidays' => 'fe.french',
+      'German Holidays' => 'de.german',
+      'Greek Holidays' => 'en.greek',
+      'Hong Kong Holidays' => 'en.hong_kong',
+      'Indian Holidays' => 'en.indian',
+      'Indonesian Holidays' => 'en.indonesian',
+      'Iranian Holidays' => 'en.iranian',
+      'Irish Holidays' => 'en.irish',
+      'Islamic Holidays' => 'en.islamic',
+      'Italian Holidays' => 'it.italian',
+      'Japanese Holidays' => 'en.japanese',
+      'Jewish Holidays' => 'en.jewish',
+      'Malaysian Holidays' => 'en.malaysia',
+      'Mexican Holidays' => 'en.mexican',
+      'New Zealand Holidays' => 'en.new_zealand',
+      'Norwegian Holidays' => 'en.norwegian',
+      'Philippines Holidays' => 'en.philippines',
+      'Polish Holidays' => 'en.polish',
+      'Portuguese Holidays' => 'en.portuguese',
+      'Russian Holidays' => 'en.russian',
+      'Singapore Holidays' => 'en.singapore',
+      'South Africa Holidays' => 'en.sa',
+      'South Korean Holidays' => 'en.south_korea',
+      'Spain Holidays' => 'en.spain',
+      'Swedish Holidays' => 'en.swedish',
+      'Taiwan Holidays' => 'en.taiwan',
+      'Thai Holidays' => 'en.thai',
+      'UK Holidays' => 'en.uk',
+      'US Holidays' => 'en.usa',
+      'Vietnamese Holidays' => 'en.vietnamese',
+    }
+    gfeeds.each {|key, name|
+      gfeeds[key] = "http://www.google.com/calendar/ical/#{name}%23holiday%40group.v.calendar.google.com/public/basic.ics"
+    }
+    gfeeds
+  end
+
+=begin
+
+get list of available timezones and UTC offsets
+
+  list = Calendar.timezones
+
+returns
+
+  {
+    'America/Los_Angeles' => -7
+    ...
+  }
+
+=end
+
+  def self.timezones
+    list = {}
+    TZInfo::Timezone.all_country_zone_identifiers.each { |timezone|
+
+      # ignore the following time zones
+      #next if t.name =~ /^GMT/
+      #next if t.name =~ /^Etc/
+      #next if t.name !~ /\//
+      t = TZInfo::Timezone.get(timezone)
+      diff = t.current_period.utc_total_offset / 60 / 60
+      list[ timezone ] = diff
+    }
+    list
+  end
+
+=begin
+
+syn all calendars with ical feeds
+
+  success = Calendar.sync
+
+returns
+
+  true # or false
+
+=end
+
+  def self.sync
+    Calendar.all.each(&:sync)
+    true
+  end
+
+=begin
+
+syn one calendars with ical feed
+
+  calendar = Calendar.find(4711)
+  success = calendar.sync
+
+returns
+
+  true # or false
+
+=end
+
+  def sync
+    return if !ical_url
+    events = Calendar.parse(ical_url)
+
+    # sync with public_holidays
+    if !public_holidays
+      self.public_holidays = {}
+    end
+    events.each {|day, summary|
+      if !public_holidays[day]
+        public_holidays[day] = {}
+      end
+
+      # ignore if already added or changed
+      next if public_holidays[day].key?('active')
+
+      # create new entry
+      public_holidays[day] = {
+        active: true,
+        summary: summary,
+      }
+    }
+    self.last_log = ''
+    self.last_sync = Time.zone.now
+    save
+    true
+  end
+
+  def self.parse(location)
+    if location =~ /^http/i
+      result = UserAgent.get(location)
+      cal_file = result.body
+    else
+      cal_file = File.open(location)
+    end
+
+    cals = Icalendar.parse(cal_file)
+    cal = cals.first
+    events = {}
+    cal.events.each {|event|
+      next if event.dtstart < Time.zone.now - 1.year
+      next if event.dtstart > Time.zone.now + 3.year
+      day = "#{event.dtstart.year}-#{format('%02d', event.dtstart.month)}-#{format('%02d', event.dtstart.day)}"
+      comment = event.summary || event.description
+      comment = Encode.conv( 'utf8', comment.to_s.force_encoding('utf-8') )
+      if !comment.valid_encoding?
+        comment = comment.encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?')
+      end
+      events[day] = comment
+    }
+    events.sort.to_h
+  end
+end

+ 11 - 0
config/routes/calendar.rb

@@ -0,0 +1,11 @@
+Zammad::Application.routes.draw do
+  api_path = Rails.configuration.api_path
+
+  # calendars
+  match api_path + '/calendars',            to: 'calendars#index',   via: :get
+  match api_path + '/calendars/:id',        to: 'calendars#show',    via: :get
+  match api_path + '/calendars',            to: 'calendars#create',  via: :post
+  match api_path + '/calendars/:id',        to: 'calendars#update',  via: :put
+  match api_path + '/calendars/:id',        to: 'calendars#destroy', via: :delete
+
+end

+ 124 - 0
db/migrate/20150968000001_create_calendar.rb

@@ -0,0 +1,124 @@
+class CreateCalendar < ActiveRecord::Migration
+  def up
+    create_table :calendars do |t|
+      t.string  :name,                   limit: 250, null: true
+      t.string  :timezone,               limit: 250, null: true
+      t.string  :business_hours,         limit: 1200, null: true
+      t.boolean :default,                            null: false, default: false
+      t.string  :ical_url,               limit: 500, null: true
+      t.text    :public_holidays,        limit: 500.kilobytes + 1, null: true
+      t.text    :last_log,               limit: 500.kilobytes + 1, null: true
+      t.timestamp :last_sync,            null: true
+      t.integer :updated_by_id,          null: false
+      t.integer :created_by_id,          null: false
+      t.timestamps
+    end
+    add_index :calendars, [:name], unique: true
+
+    Calendar.create_or_update(
+      name: 'US',
+      timezone: 'America/Los_Angeles',
+      business_hours: {
+        mon: { '09:00' => '17:00' },
+        tue: { '09:00' => '17:00' },
+        wed: { '09:00' => '17:00' },
+        thu: { '09:00' => '17:00' },
+        fri: { '09:00' => '17:00' }
+      },
+      default: true,
+      ical_url: 'http://www.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics',
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    Calendar.create_or_update(
+      name: 'Germany',
+      timezone: 'Europe/Berlin',
+      business_hours: {
+        mon: { '09:00' => '17:00' },
+        tue: { '09:00' => '17:00' },
+        wed: { '09:00' => '17:00' },
+        thu: { '09:00' => '17:00' },
+        fri: { '09:00' => '17:00' }
+      },
+      default: false,
+      ical_url: 'http://www.google.com/calendar/ical/de.german%23holiday%40group.v.calendar.google.com/public/basic.ics',
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+=begin
+    Calendar.create_or_update(
+      name: 'French',
+      timezone: 'Europe/Paris',
+      business_hours: {
+        mon: { '09:00' => '17:00' },
+        tue: { '09:00' => '17:00' },
+        wed: { '09:00' => '17:00' },
+        thu: { '09:00' => '12:00', '13:00' => '17:00' },
+        fri: { '09:00' => '17:00' }
+      },
+      default: false,
+      ical_url: 'http://www.google.com/calendar/ical/fr.french%23holiday%40group.v.calendar.google.com/public/basic.ics',
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    Calendar.create_or_update(
+      name: 'Switzerland',
+      timezone: 'Europe/Zurich',
+      business_hours: {
+        mon: { '09:00' => '17:00' },
+        tue: { '09:00' => '17:00' },
+        wed: { '09:00' => '17:00' },
+        thu: { '09:00' => '12:00', '13:00' => '17:00' },
+        fri: { '09:00' => '17:00' }
+      },
+      default: false,
+      ical_url: 'http://www.google.com/calendar/ical/de.ch%23holiday%40group.v.calendar.google.com/public/basic.ics',
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    Calendar.create_or_update(
+      name: 'Austria',
+      timezone: 'Europe/Vienna',
+      business_hours: {
+        mon: { '09:00' => '17:00' },
+        tue: { '09:00' => '17:00' },
+        wed: { '09:00' => '17:00' },
+        thu: { '09:00' => '17:00' },
+        fri: { '09:00' => '17:00' }
+      },
+      default: false,
+      ical_url: 'http://www.google.com/calendar/ical/de.austrian%23holiday%40group.v.calendar.google.com/public/basic.ics',
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    Calendar.create_or_update(
+      name: 'Italian',
+      timezone: 'Europe/Roma',
+      business_hours: {
+        mon: { '09:00' => '17:00' },
+        tue: { '09:00' => '17:00' },
+        wed: { '09:00' => '17:00' },
+        thu: { '09:00' => '17:00' },
+        fri: { '09:00' => '17:00' }
+      },
+      default: false,
+      ical_url: 'http://www.google.com/calendar/ical/it.italian%23holiday%40group.v.calendar.google.com/public/basic.ics',
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+=end
+    Scheduler.create_or_update(
+      name: 'Sync calendars with ical feeds.',
+      method: 'Calendar.sync',
+      period: 1.day,
+      prio: 2,
+      active: true,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+  end
+
+  def down
+    drop_table :calendars
+  end
+end