mirror of
https://github.com/rickbarrette/redmine_qbo_calendar.git
synced 2026-10-04 04:40:43 -04:00
Compare commits
12
Commits
167e956457
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3b9441f25 | ||
|
|
852d2119a0 | ||
|
|
041a6441fe | ||
|
|
0e54316eac | ||
|
|
250442db9d | ||
|
|
c3a5657b7b | ||
|
|
ee4327152d | ||
|
|
39f31cfb11 | ||
|
|
df788d4d97 | ||
|
|
9fac291490 | ||
|
|
8448c06b10 | ||
|
|
97ed852068 |
No files matched your search
@@ -64,26 +64,30 @@ Restart your application server (Puma, Passenger, etc.) to initialize the plugin
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
This plugin depends on the **Redmine QuickBooks Online** plugin.
|
||||
|
||||
Before using this plugin:
|
||||
|
||||
1. Install and configure the parent plugin.
|
||||
|
||||
2. Ensure your **QuickBooks Online** company file is connected.
|
||||
|
||||
3. Sync Accounts & Items via plugin settings
|
||||
|
||||
4. Set default income account for new items via plugin settings
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Add appointments
|
||||
1. Managing Customer Appointments
|
||||
* Viewing the Calendar: Navigate to the Customer Calendar tab within a project or via the top menu to view scheduled customer appointments.
|
||||
* Adding Appointments: Click on available days on the calendar grid to schedule a new appointment (restricted by user permissions, working days, and configured holidays).
|
||||
* Editing & Deleting: Existing appointments can be modified or removed directly from the calendar interface depending on user privileges.
|
||||
* My Page Integration: Users can add the Upcoming Appointments block to their personal My Page dashboard to track their schedule at a glance.
|
||||
|
||||
2. Holidays and Non-Working Days
|
||||
Administrators can manage the holiday calendar to prevent appointments from being scheduled on non-working days.
|
||||
* Go to Administration -> Holidays (or the corresponding holidays menu item).
|
||||
* Fixed Holidays: Set specific dates (e.g., December 25th) that recur annually or apply to a single year.
|
||||
* Dynamic Holidays: Configure rules for floating holidays (e.g., the 1st Monday of September).
|
||||
* Validation: The system automatically blocks appointment creation on standard weekends, non-working days, and defined holidays.
|
||||
|
||||
3. Permissions & Global Access
|
||||
Appointment permissions are configured globally to apply system-wide:
|
||||
1. Navigate to Administration -> Roles and permissions.
|
||||
2. Scroll to the global permissions section.
|
||||
3. Assign the following permissions as needed based on user roles (such as Non-member or custom staff roles):
|
||||
* View customer appointments
|
||||
* Add customer appointments
|
||||
* Edit customer appointments
|
||||
* Delete customer appointments
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ class CustomerAppointmentsController < ApplicationController
|
||||
before_action :require_login
|
||||
before_action :find_project, except: [:index]
|
||||
before_action :find_appointment, only: [ :show, :edit, :update, :destroy ]
|
||||
#before_action :authorize
|
||||
# This tells Redmine to check global permissions for the current controller/action
|
||||
before_action :authorize_global
|
||||
|
||||
helper :projects
|
||||
|
||||
@@ -85,7 +86,7 @@ class CustomerAppointmentsController < ApplicationController
|
||||
def destroy
|
||||
@appointment.destroy
|
||||
flash[:notice] = l(:notice_successful_delete)
|
||||
redirect_to project_customer_appointments_path( @project)
|
||||
redirect_to issues_calendar_path
|
||||
end
|
||||
|
||||
private
|
||||
@@ -116,6 +117,8 @@ class CustomerAppointmentsController < ApplicationController
|
||||
|
||||
def find_appointment
|
||||
@appointment = CustomerAppointment.find(params[:id])
|
||||
rescue
|
||||
render_404
|
||||
end
|
||||
|
||||
def log(msg)
|
||||
|
||||
@@ -173,7 +173,34 @@
|
||||
if event.is_a?(Issue)
|
||||
event.estimated_hours.to_f
|
||||
elsif event.is_a?(CustomerAppointment)
|
||||
if event.respond_to?(:estimated_hours) && event.estimated_hours.present?
|
||||
if event.duration.to_i > 1
|
||||
# Get the total hours (fallback to duration * 8 if estimated_hours is blank)
|
||||
app_total_hours = if event.respond_to?(:estimated_hours) && event.estimated_hours.present?
|
||||
event.estimated_hours.to_f
|
||||
else
|
||||
event.duration.to_f * 8.0
|
||||
end
|
||||
|
||||
start_date = (event.respond_to?(:date) && event.date.present? ? event.date : event.start_date).to_date
|
||||
|
||||
# Count how many working days occurred before the current calendar 'day'
|
||||
working_day_index = 0
|
||||
date_cursor = start_date
|
||||
|
||||
while date_cursor < day
|
||||
is_weekend = non_working_days.include?(date_cursor.cwday.to_s)
|
||||
is_holiday = all_holidays.any? { |h| h.occurs_on?(date_cursor) }
|
||||
working_day_index += 1 unless is_weekend || is_holiday
|
||||
date_cursor += 1
|
||||
end
|
||||
|
||||
# Subtract previous days' hours (8h/day) from the total
|
||||
hours_already_allocated = working_day_index * 8.0
|
||||
remaining_hours = app_total_hours - hours_already_allocated
|
||||
|
||||
# Allocate up to 8 hours for today, but don't go below 0
|
||||
[[remaining_hours, 8.0].min, 0.0].max
|
||||
elsif event.respond_to?(:estimated_hours) && event.estimated_hours.present?
|
||||
event.estimated_hours.to_f
|
||||
else
|
||||
event.duration.to_f
|
||||
@@ -197,15 +224,17 @@
|
||||
</span>
|
||||
|
||||
<!-- Center: New Appointment Action -->
|
||||
<span class="day-num-center" style="display: inline-flex; align-items: center; justify-self: center;">
|
||||
<%# Only show ADD button if it's in the future, NOT a weekend, and NOT a custom holiday %>
|
||||
<% if day >= User.current.today && !is_standard_weekend && !is_custom_holiday %>
|
||||
<%= link_to sprite_icon('add'),
|
||||
new_customer_appointment_path(start_date: day),
|
||||
class: 'icon-only icon-add',
|
||||
title: l(:label_appointment_new, default: 'New Appointment') %>
|
||||
<% end %>
|
||||
</span>
|
||||
<% if User.current.allowed_to?(:add_customer_appointments, nil, global: true) %>
|
||||
<span class="day-num-center" style="display: inline-flex; align-items: center; justify-self: center;">
|
||||
<%# Only show ADD button if it's in the future, NOT a weekend, NOT a holiday, AND user has permission %>
|
||||
<% if day >= User.current.today && !is_standard_weekend && !is_custom_holiday && User.current.allowed_to?(:add_customer_appointments, nil, global: true) %>
|
||||
<%= link_to sprite_icon('add'),
|
||||
new_customer_appointment_path(start_date: day),
|
||||
class: 'icon-only icon-add',
|
||||
title: l(:label_appointment_new, default: 'New Appointment') %>
|
||||
<% end %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<!-- Right: Total Hours -->
|
||||
<span class="day-num-right" style="display: inline-flex; align-items: center; justify-self: end;">
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<p>
|
||||
<% customer = context[:customer] %>
|
||||
<%= link_to l(:label_add_appointment), new_customer_appointment_path(customer_id: customer.id), target: :_blank, id: :appointment_link %>
|
||||
</p>
|
||||
<% if User.current.allowed_to?(:add_customer_appointments, nil, global: true) %>
|
||||
<p>
|
||||
<% customer = context[:customer] %>
|
||||
<%= link_to l(:label_add_appointment), new_customer_appointment_path(customer_id: customer.id), target: :_blank, id: :appointment_link %>
|
||||
</p>
|
||||
<% end %>
|
||||
Whitespace-only changes.
@@ -92,6 +92,8 @@
|
||||
|
||||
<hr>
|
||||
|
||||
<p class="buttons">
|
||||
<%= link_to l(:label_open_appointment), customer_appointment_path(appointment), class: "icon icon-edit" %>
|
||||
</p>
|
||||
<% if User.current.allowed_to?(:edit_customer_appointments, nil, global: true) %>
|
||||
<p class="buttons">
|
||||
<%= link_to l(:label_open_appointment), customer_appointment_path(appointment), class: "icon icon-edit" %>
|
||||
</p>
|
||||
<% end %>
|
||||
@@ -5,8 +5,10 @@
|
||||
<i class="fa-regular fa-calendar-days"></i> <%= l(:button_calendar) %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to new_customer_appointment_path do %>
|
||||
<i class="fa-regular fa-pen-to-square"></i> <%= l(:button_add) %>
|
||||
<% if User.current.allowed_to?(:add_customer_appointments, nil, global: true) %>
|
||||
<%= link_to new_customer_appointment_path do %>
|
||||
<i class="fa-regular fa-pen-to-square"></i> <%= l(:button_add) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -3,16 +3,22 @@
|
||||
<i class="fa-regular fa-calendar-days"></i> <%= l(:button_calendar) %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to new_issue_path(issue: { customer_id: @appointment.customer_id, vehicle_id: @appointment.vehicle_id, estimate_id: @appointment.estimate_id }), target: :_blank do %>
|
||||
<i class="fa-solid fa-plus"></i> <%= l(:label_new_issue) %>
|
||||
<% if User.current.allowed_to?(:add_customer_appointments, nil, global: true) %>
|
||||
<%= link_to new_issue_path(issue: { customer_id: @appointment.customer_id, vehicle_id: @appointment.vehicle_id, estimate_id: @appointment.estimate_id }), target: :_blank do %>
|
||||
<i class="fa-solid fa-plus"></i> <%= l(:label_new_issue) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to edit_customer_appointment_path(@appointment) do %>
|
||||
<i class="fa-solid fa-pen-to-square"></i> <%= l(:button_edit) %>
|
||||
<% if User.current.allowed_to?(:edit_customer_appointments, nil, global: true) %>
|
||||
<%= link_to edit_customer_appointment_path(@appointment) do %>
|
||||
<i class="fa-solid fa-pen-to-square"></i> <%= l(:button_edit) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to customer_appointment_path(@appointment), method: :delete, data: { confirm: l(:text_are_you_sure) } do %>
|
||||
<i class="fa-solid fa-trash"></i> <%= l(:button_delete) %>
|
||||
<% if User.current.allowed_to?(:delete_customer_appointments, nil, global: true) %>
|
||||
<%= link_to customer_appointment_path(@appointment), method: :delete, data: { confirm: l(:text_are_you_sure) } do %>
|
||||
<i class="fa-solid fa-trash"></i> <%= l(:button_delete) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -36,20 +42,22 @@
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<%# Quick Status Action Buttons %>
|
||||
<%
|
||||
statuses = CustomerAppointment::STATUSES
|
||||
current_status = @appointment.status.to_s.parameterize.underscore
|
||||
%>
|
||||
<div class="appointment-quick-actions">
|
||||
<span class="quick-action-label"><%= l(:label_change_status, default: 'Change Status:') %></span>
|
||||
<% statuses.reject { |s| s == current_status }.each do |status_key| %>
|
||||
<%= link_to status_key.humanize,
|
||||
customer_appointment_path(@appointment, customer_appointment: { status: status_key }),
|
||||
method: :patch,
|
||||
class: "quick-status-btn status-#{status_key.parameterize}" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if User.current.allowed_to?(:edit_customer_appointments, nil, global: true) %>
|
||||
<%# Quick Status Action Buttons %>
|
||||
<%
|
||||
statuses = CustomerAppointment::STATUSES
|
||||
current_status = @appointment.status.to_s.parameterize.underscore
|
||||
%>
|
||||
<div class="appointment-quick-actions">
|
||||
<span class="quick-action-label"><%= l(:label_change_status, default: 'Change Status:') %></span>
|
||||
<% statuses.reject { |s| s == current_status }.each do |status_key| %>
|
||||
<%= link_to status_key.humanize,
|
||||
customer_appointment_path(@appointment, customer_appointment: { status: status_key }),
|
||||
method: :patch,
|
||||
class: "quick-status-btn status-#{status_key.parameterize}" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="issue details">
|
||||
<div class="attributes">
|
||||
|
||||
@@ -34,6 +34,7 @@ en:
|
||||
field_vehicle: Vehicle
|
||||
label_add_appointment: Add Appointment
|
||||
label_appointment: Appointment
|
||||
label_appointment_new: "New Appointment"
|
||||
label_appointments: Appointments
|
||||
label_customer_appointment: Customer Appointment
|
||||
label_customer_calendar: Customer Calendar
|
||||
@@ -43,9 +44,11 @@ en:
|
||||
label_end_date: End Date
|
||||
label_estimated_hours: Estimated Time
|
||||
label_event: Event
|
||||
label_hide_past_weeks: "Hide Past Weeks"
|
||||
label_holiday_edit: Edit Holiday
|
||||
label_holiday_new: New Holiday
|
||||
label_holiday_plural: Holidays
|
||||
label_legend: "Legend"
|
||||
label_new_appointment: New Appointment
|
||||
label_of: "of"
|
||||
label_only_customer_appointments: Show Customer Appointments Only
|
||||
@@ -65,6 +68,9 @@ en:
|
||||
label_week_4th: "4th"
|
||||
label_week_last: "Last"
|
||||
notice_unable_delete_holiday: "Unable to delete holiday"
|
||||
permission_add_customer_appointments: "Add customer appointments"
|
||||
permission_delete_customer_appointments: "Delete customer appointments"
|
||||
permission_edit_customer_appointments: "Edit customer appointments"
|
||||
permission_manage_customer_appointments: Manage Customer Appointments
|
||||
permission_view_customer_appointments: View Customer Appointments
|
||||
permission_view_customer_appointments: "View customer appointments"
|
||||
title_customer_appointments: Customer Appointments
|
||||
@@ -14,11 +14,10 @@ Redmine::Plugin.register :redmine_qbo_calendar do
|
||||
name 'Redmine QBO Calendar plugin'
|
||||
author 'Rick Barrette'
|
||||
description 'A plugin for Redmine to extend the capabilitys of the Redmine QuickBooks Online plugin to create customer appointments for the Redmine Calendar'
|
||||
version '2026.9.0'
|
||||
version '2026.9.2'
|
||||
url 'https://github.com/rickbarrette/redmine_qbo_calendar'
|
||||
author_url 'https://barrettefabrication.com'
|
||||
requires_redmine version_or_higher: '7.0.0'
|
||||
settings partial: 'calendar/settings'
|
||||
|
||||
# Add Holidays index to the Redmine Administration Menu
|
||||
menu :admin_menu, :holidays, { controller: 'holidays', action: 'index' }, caption: :label_holiday_plural, html: { class: 'icon icon-list' }
|
||||
@@ -30,39 +29,15 @@ Redmine::Plugin.register :redmine_qbo_calendar do
|
||||
raise 'Please install the redmine_qbo plugin (https://github.com/rickbarrette/redmine_qbo)'
|
||||
end
|
||||
|
||||
project_module :customer_calendar do
|
||||
permission :view_customer_appointments,
|
||||
{ customer_appointments: [:index, :show] }
|
||||
# Global Permissions
|
||||
permission :view_customer_appointments, { customer_appointments: [:index, :show] }, global: true
|
||||
permission :add_customer_appointments, { customer_appointments: [:new, :create] }, global: true
|
||||
permission :edit_customer_appointments, { customer_appointments: [:edit, :update] }, global: true
|
||||
permission :delete_customer_appointments, { customer_appointments: [:destroy] }, global: true
|
||||
|
||||
permission :manage_customer_appointments,
|
||||
{
|
||||
customer_appointments: [
|
||||
:new,
|
||||
:create,
|
||||
:edit,
|
||||
:update,
|
||||
:destroy
|
||||
]
|
||||
}
|
||||
end
|
||||
# Register top menu items
|
||||
menu :top_menu, :calendar, { controller: :calendars, action: :show }, caption: :label_calendar, if: Proc.new {User.current.logged?}
|
||||
|
||||
menu :project_menu,
|
||||
:customer_calendar,
|
||||
{ controller: 'events',
|
||||
action: 'index' },
|
||||
caption: 'Customer Calendar',
|
||||
after: :calendar,
|
||||
param: :project_id
|
||||
end
|
||||
|
||||
Rails.configuration.to_prepare do
|
||||
ApplicationController.prepend_view_path( File.expand_path("app/views", __dir__) )
|
||||
|
||||
Redmine::Views::MyPage::Block.register(
|
||||
'upcoming_appointments',
|
||||
label: :label_upcoming_appointments,
|
||||
partial: 'my/blocks/upcoming_appointments'
|
||||
)
|
||||
end
|
||||
|
||||
RedmineQboCalendar.setup
|
||||
@@ -20,5 +20,15 @@ module RedmineQboCalendar
|
||||
RedmineQboCalendar::Hooks::ViewHookListener
|
||||
RedmineQboCalendar::Hooks::CustomerActionsHookListener
|
||||
RedmineQboCalendar::Hooks::CustomerShowHookListener
|
||||
|
||||
Rails.configuration.to_prepare do
|
||||
ApplicationController.prepend_view_path( File.expand_path("app/views", __dir__) )
|
||||
|
||||
Redmine::Views::MyPage::Block.register(
|
||||
'upcoming_appointments',
|
||||
label: :label_upcoming_appointments,
|
||||
partial: 'my/blocks/upcoming_appointments'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -13,14 +13,14 @@ module RedmineQboCalendar
|
||||
|
||||
class ViewHookListener < Redmine::Hook::ViewListener
|
||||
|
||||
# Load the javascript and stylesheet assets
|
||||
def view_layouts_base_html_head(context = {})
|
||||
safe_join([
|
||||
javascript_include_tag("customer_appointments", plugin: :redmine_qbo_calendar),
|
||||
stylesheet_link_tag('customer_appointments', plugin: :redmine_qbo_calendar),
|
||||
javascript_include_tag('https://kit.fontawesome.com/4be4394b21.js', crossorigin: 'anonymous')
|
||||
])
|
||||
end
|
||||
# Load the javascript and stylesheet assets
|
||||
def view_layouts_base_html_head(context = {})
|
||||
safe_join([
|
||||
javascript_include_tag("customer_appointments", plugin: :redmine_qbo_calendar),
|
||||
stylesheet_link_tag('customer_appointments', plugin: :redmine_qbo_calendar),
|
||||
javascript_include_tag('https://kit.fontawesome.com/4be4394b21.js', crossorigin: 'anonymous')
|
||||
])
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
Reference in new issue
Block a user