decidim-time_tracker 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +174 -0
  4. data/Rakefile +40 -0
  5. data/app/assets/config/admin/decidim_time_tracker_manifest.css +2 -0
  6. data/app/assets/config/admin/decidim_time_tracker_manifest.js +1 -0
  7. data/app/assets/config/decidim_time_tracker_manifest.css +3 -0
  8. data/app/assets/config/decidim_time_tracker_manifest.js +1 -0
  9. data/app/assets/images/decidim/time_tracker/icon.svg +1 -0
  10. data/app/assets/javascripts/decidim/time_tracker/activity_ui.js.es6 +112 -0
  11. data/app/assets/javascripts/decidim/time_tracker/admin/assignations.js.es6 +35 -0
  12. data/app/assets/javascripts/decidim/time_tracker/reports/reports.js.es6 +19 -0
  13. data/app/assets/javascripts/decidim/time_tracker/time_tracker.js.es6 +82 -0
  14. data/app/assets/javascripts/decidim/time_tracker/timer_api.js.es6 +55 -0
  15. data/app/assets/stylesheets/decidim/time_tracker/_variables.scss +1 -0
  16. data/app/assets/stylesheets/decidim/time_tracker/admin/time_tracker.scss +59 -0
  17. data/app/assets/stylesheets/decidim/time_tracker/time_tracker.scss +118 -0
  18. data/app/cells/decidim/time_tracker/milestone/show.erb +42 -0
  19. data/app/cells/decidim/time_tracker/milestone_cell.rb +54 -0
  20. data/app/commands/decidim/time_tracker/admin/create_activity.rb +39 -0
  21. data/app/commands/decidim/time_tracker/admin/create_assignation.rb +75 -0
  22. data/app/commands/decidim/time_tracker/admin/create_task.rb +33 -0
  23. data/app/commands/decidim/time_tracker/admin/create_time_tracker.rb +76 -0
  24. data/app/commands/decidim/time_tracker/admin/destroy_activity.rb +43 -0
  25. data/app/commands/decidim/time_tracker/admin/destroy_assignation.rb +43 -0
  26. data/app/commands/decidim/time_tracker/admin/destroy_task.rb +43 -0
  27. data/app/commands/decidim/time_tracker/admin/export_time_tracker.rb +150 -0
  28. data/app/commands/decidim/time_tracker/admin/update_activity.rb +49 -0
  29. data/app/commands/decidim/time_tracker/admin/update_assignation.rb +42 -0
  30. data/app/commands/decidim/time_tracker/admin/update_task.rb +44 -0
  31. data/app/commands/decidim/time_tracker/create_milestone.rb +57 -0
  32. data/app/commands/decidim/time_tracker/create_request_assignation.rb +39 -0
  33. data/app/commands/decidim/time_tracker/start_time_event.rb +104 -0
  34. data/app/commands/decidim/time_tracker/stop_last_time_event.rb +45 -0
  35. data/app/controllers/concerns/decidim/time_tracker/admin/filterable_assignations.rb +50 -0
  36. data/app/controllers/decidim/time_tracker/activity_questionnaire_controller.rb +76 -0
  37. data/app/controllers/decidim/time_tracker/admin/activities_controller.rb +82 -0
  38. data/app/controllers/decidim/time_tracker/admin/activity_questionnaire_controller.rb +52 -0
  39. data/app/controllers/decidim/time_tracker/admin/application_controller.rb +22 -0
  40. data/app/controllers/decidim/time_tracker/admin/assignations_controller.rb +84 -0
  41. data/app/controllers/decidim/time_tracker/admin/assignee_questionnaire_controller.rb +58 -0
  42. data/app/controllers/decidim/time_tracker/admin/stats_controller.rb +24 -0
  43. data/app/controllers/decidim/time_tracker/admin/tasks_controller.rb +112 -0
  44. data/app/controllers/decidim/time_tracker/admin/time_tracker_exports_controller.rb +25 -0
  45. data/app/controllers/decidim/time_tracker/application_controller.rb +24 -0
  46. data/app/controllers/decidim/time_tracker/assignations_controller.rb +38 -0
  47. data/app/controllers/decidim/time_tracker/assignee_questionnaire_controller.rb +80 -0
  48. data/app/controllers/decidim/time_tracker/milestones_controller.rb +62 -0
  49. data/app/controllers/decidim/time_tracker/reports/user_controller.rb +38 -0
  50. data/app/controllers/decidim/time_tracker/time_events_controller.rb +68 -0
  51. data/app/controllers/decidim/time_tracker/time_tracker_controller.rb +43 -0
  52. data/app/forms/decidim/time_tracker/admin/activity_form.rb +27 -0
  53. data/app/forms/decidim/time_tracker/admin/assignation_form.rb +29 -0
  54. data/app/forms/decidim/time_tracker/admin/task_form.rb +22 -0
  55. data/app/forms/decidim/time_tracker/milestone_form.rb +32 -0
  56. data/app/forms/decidim/time_tracker/time_event_form.rb +47 -0
  57. data/app/helpers/decidim/time_tracker/admin/application_helper.rb +33 -0
  58. data/app/helpers/decidim/time_tracker/application_helper.rb +95 -0
  59. data/app/jobs/decidim/time_tracker/stop_counter_job.rb +29 -0
  60. data/app/models/decidim/time_tracker/activity.rb +135 -0
  61. data/app/models/decidim/time_tracker/application_record.rb +10 -0
  62. data/app/models/decidim/time_tracker/assignation.rb +68 -0
  63. data/app/models/decidim/time_tracker/assignee.rb +29 -0
  64. data/app/models/decidim/time_tracker/assignee_data.rb +19 -0
  65. data/app/models/decidim/time_tracker/milestone.rb +27 -0
  66. data/app/models/decidim/time_tracker/task.rb +44 -0
  67. data/app/models/decidim/time_tracker/time_event.rb +62 -0
  68. data/app/models/decidim/time_tracker/time_tracker.rb +46 -0
  69. data/app/models/decidim/time_tracker/tos_acceptance.rb +16 -0
  70. data/app/permissions/decidim/time_tracker/admin/permissions.rb +69 -0
  71. data/app/permissions/decidim/time_tracker/permissions.rb +69 -0
  72. data/app/presenters/decidim/time_tracker/admin_log/activity_presenter.rb +44 -0
  73. data/app/presenters/decidim/time_tracker/admin_log/assignation_presenter.rb +48 -0
  74. data/app/presenters/decidim/time_tracker/admin_log/task_presenter.rb +46 -0
  75. data/app/presenters/decidim/time_tracker/admin_log/value_types/activity_presenter.rb +28 -0
  76. data/app/presenters/decidim/time_tracker/admin_log/value_types/time_tracker_presenter.rb +28 -0
  77. data/app/views/decidim/time_tracker/admin/_breadcrumbs.html.erb +56 -0
  78. data/app/views/decidim/time_tracker/admin/activities/_form.html.erb +41 -0
  79. data/app/views/decidim/time_tracker/admin/activities/_index.html.erb +73 -0
  80. data/app/views/decidim/time_tracker/admin/activities/edit.html.erb +5 -0
  81. data/app/views/decidim/time_tracker/admin/activities/new.html.erb +5 -0
  82. data/app/views/decidim/time_tracker/admin/assignations/_form.html.erb +30 -0
  83. data/app/views/decidim/time_tracker/admin/assignations/index.html.erb +67 -0
  84. data/app/views/decidim/time_tracker/admin/assignations/new.html.erb +9 -0
  85. data/app/views/decidim/time_tracker/admin/questionnaires/_index.html.erb +27 -0
  86. data/app/views/decidim/time_tracker/admin/questionnaires/_questionnaire.html.erb +21 -0
  87. data/app/views/decidim/time_tracker/admin/stats/index.html.erb +50 -0
  88. data/app/views/decidim/time_tracker/admin/tasks/_assignations.html.erb +60 -0
  89. data/app/views/decidim/time_tracker/admin/tasks/_form.html.erb +7 -0
  90. data/app/views/decidim/time_tracker/admin/tasks/_index.html.erb +61 -0
  91. data/app/views/decidim/time_tracker/admin/tasks/edit.html.erb +16 -0
  92. data/app/views/decidim/time_tracker/admin/tasks/index.html.erb +5 -0
  93. data/app/views/decidim/time_tracker/admin/tasks/new.html.erb +12 -0
  94. data/app/views/decidim/time_tracker/milestones/index.html.erb +60 -0
  95. data/app/views/decidim/time_tracker/reports/user/index.html.erb +41 -0
  96. data/app/views/decidim/time_tracker/time_tracker/_activity.html.erb +43 -0
  97. data/app/views/decidim/time_tracker/time_tracker/_answer_questionnaire.html.erb +7 -0
  98. data/app/views/decidim/time_tracker/time_tracker/_assignee_data.html.erb +9 -0
  99. data/app/views/decidim/time_tracker/time_tracker/_callout_status.html.erb +3 -0
  100. data/app/views/decidim/time_tracker/time_tracker/_idle_activity.html.erb +27 -0
  101. data/app/views/decidim/time_tracker/time_tracker/_milestone_form.html.erb +35 -0
  102. data/app/views/decidim/time_tracker/time_tracker/_request_activity.html.erb +22 -0
  103. data/app/views/decidim/time_tracker/time_tracker/_sign_in.html.erb +5 -0
  104. data/app/views/decidim/time_tracker/time_tracker/index.html.erb +59 -0
  105. data/config/activity_questionnaire.yml +26 -0
  106. data/config/assignee_questionnaire.yml +37 -0
  107. data/config/i18n-tasks.yml +10 -0
  108. data/config/locales/ca.yml +394 -0
  109. data/config/locales/cs.yml +394 -0
  110. data/config/locales/en.yml +419 -0
  111. data/config/locales/es.yml +394 -0
  112. data/db/migrate/20200302115642_create_decidim_time_tracker_time_trackers.rb +10 -0
  113. data/db/migrate/20200302115654_create_decidim_time_tracker_tasks.rb +11 -0
  114. data/db/migrate/20200302115658_create_decidim_time_tracker_assignees.rb +12 -0
  115. data/db/migrate/20200302115700_create_decidim_time_tracker_activities.rb +19 -0
  116. data/db/migrate/20200302115708_create_decidim_time_tracker_milestones.rb +13 -0
  117. data/db/migrate/20200302115831_create_decidim_time_tracker_assignations.rb +16 -0
  118. data/db/migrate/20200302115840_create_decidim_time_tracker_time_events.rb +15 -0
  119. data/db/migrate/20201118153327_create_decidim_time_tracker_assignee_data.rb +10 -0
  120. data/db/migrate/20201223115130_create_decidim_time_tracker_tos_acceptances.rb +18 -0
  121. data/lib/decidim/time_tracker.rb +45 -0
  122. data/lib/decidim/time_tracker/admin.rb +10 -0
  123. data/lib/decidim/time_tracker/admin_engine.rb +51 -0
  124. data/lib/decidim/time_tracker/component.rb +248 -0
  125. data/lib/decidim/time_tracker/engine.rb +59 -0
  126. data/lib/decidim/time_tracker/reports.rb +10 -0
  127. data/lib/decidim/time_tracker/reports_engine.rb +34 -0
  128. data/lib/decidim/time_tracker/test/admin_log_presenter_examples.rb +34 -0
  129. data/lib/decidim/time_tracker/test/factories.rb +110 -0
  130. data/lib/decidim/time_tracker/test/time_tracker_context.rb +18 -0
  131. data/lib/decidim/time_tracker/time_tracker_activity_questionnaire_answers_serializer.rb +74 -0
  132. data/lib/decidim/time_tracker/version.rb +9 -0
  133. data/vendor/assets/javascripts/jsrender.min.js +4 -0
  134. metadata +276 -0
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class AssigneeQuestionnaireController < Decidim::TimeTracker::ApplicationController
6
+ include Decidim::Forms::Concerns::HasQuestionnaire
7
+
8
+ # only allows answers if not in preview mode
9
+ def preview
10
+ return show if request.method == "GET"
11
+
12
+ flash[":alert"] = I18n.t("questionnaire_in_preview_mode", scope: "decidim.time_tracker.time_tracker")
13
+ redirect_to preview_assignee_questionnaire_path
14
+ end
15
+
16
+ # overwrite this method to automatically accept TOS
17
+ def answer
18
+ enforce_permission_to :answer, :questionnaire
19
+
20
+ @form = form(Decidim::Forms::QuestionnaireForm).from_params(params, session_token: session_token, ip_hash: ip_hash)
21
+
22
+ Decidim::Forms::AnswerQuestionnaire.call(@form, current_user, questionnaire) do
23
+ on(:ok) do
24
+ # i18n-tasks-use t("decidim.forms.questionnaires.answer.success")
25
+ TosAcceptance.create!(assignee: Assignee.for(current_user), time_tracker: time_tracker)
26
+ flash[:notice] = I18n.t("answer.success", scope: i18n_flashes_scope)
27
+ redirect_to after_answer_path
28
+ end
29
+
30
+ on(:invalid) do
31
+ # i18n-tasks-use t("decidim.forms.questionnaires.answer.invalid")
32
+ flash.now[:alert] = I18n.t("answer.invalid", scope: i18n_flashes_scope)
33
+ render template: "decidim/forms/questionnaires/show"
34
+ end
35
+ end
36
+ end
37
+
38
+ def questionnaire_for
39
+ time_tracker.assignee_data
40
+ end
41
+
42
+ # only allows answers if not in preview mode
43
+ def allow_answers?
44
+ return false if current_user.blank?
45
+
46
+ return true if params[:action] == "preview" && current_user.admin?
47
+
48
+ current_component.published?
49
+ end
50
+
51
+ # Override so can answer only if is an assignation can view
52
+ # Also admins can preview it (but not answer)
53
+ def visitor_can_answer?
54
+ current_user.present?
55
+ end
56
+
57
+ def visitor_already_answered?
58
+ return false if params[:action] == "preview" && current_user.admin?
59
+
60
+ questionnaire.answered_by?(current_user)
61
+ end
62
+
63
+ # Returns the path to answer this questionnaire for normal users
64
+ # If the questionnaire is rendered in a preview route, then just do nothing with the responses
65
+ def update_url
66
+ return preview_assignee_questionnaire_path if params[:action] == "preview"
67
+
68
+ answer_assignee_questionnaire_path
69
+ end
70
+
71
+ def form_path
72
+ assignee_questionnaire_path
73
+ end
74
+
75
+ def after_answer_path
76
+ Decidim::EngineRouter.main_proxy(current_component).root_path
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class MilestonesController < Decidim::TimeTracker::ApplicationController
6
+ include Decidim::FormFactory
7
+ include Decidim::TimeTracker::ApplicationHelper
8
+
9
+ helper_method :user, :activities
10
+
11
+ def index
12
+ return redirect_to root_path if user.blank?
13
+
14
+ enforce_permission_to :index, :milestones, user: user
15
+ end
16
+
17
+ # creates a milestone
18
+ def create
19
+ enforce_permission_to :create, :milestone, activity: activity
20
+
21
+ @form = form(MilestoneForm).from_params(milestone_params)
22
+
23
+ CreateMilestone.call(@form, current_user) do
24
+ on(:ok) do |_milestone|
25
+ flash[:notice] = I18n.t("milestones.create.success", scope: "decidim.time_tracker")
26
+ redirect_to milestones_path(nickname: current_user.nickname)
27
+ end
28
+ on(:invalid) do |_message|
29
+ flash[:alert] = I18n.t("milestones.create.error", scope: "decidim.time_tracker")
30
+ redirect_to root_path
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # copies the title to the attachment
38
+ def milestone_params
39
+ unsafe = params.to_unsafe_h
40
+ unsafe["milestone"]["attachment"]["title"] = { I18n.locale => params[:milestone][:title] } if unsafe["milestone"]["attachment"].present?
41
+ unsafe
42
+ end
43
+
44
+ def root_path
45
+ Decidim::EngineRouter.main_proxy(current_component).root_path
46
+ end
47
+
48
+ def activity
49
+ @activity ||= Activity.active.find_by(id: params[:milestone][:activity_id])
50
+ end
51
+
52
+ def activities
53
+ @activities ||= Activity.select("DISTINCT ON (id) *")
54
+ .where(id: Milestone.where(user: user).pluck(:activity_id))
55
+ end
56
+
57
+ def user
58
+ @user ||= Decidim::User.find_by(nickname: params[:nickname])
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ module Reports
6
+ # The controller to handle the user's time_tracker report page.
7
+ class UserController < Decidim::ApplicationController
8
+ include Decidim::ComponentPathHelper
9
+ include Decidim::UserProfile
10
+
11
+ helper Decidim::TimeTracker::ApplicationHelper
12
+ helper_method :activities, :assignations, :total_time, :activity_path
13
+
14
+ def index
15
+ enforce_permission_to :read, :user, current_user: current_user
16
+ end
17
+
18
+ private
19
+
20
+ def activities
21
+ Decidim::TimeTracker::Activity.joins(:assignations).where("decidim_time_tracker_assignations.decidim_user_id": current_user.id).group_by(&:id)
22
+ end
23
+
24
+ def assignations
25
+ Assignation.where(user: current_user).sorted_by_status(:accepted, :pending, :rejected)
26
+ end
27
+
28
+ def activity_path(assignation)
29
+ EngineRouter.main_proxy(assignation.task.time_tracker.component).root_path(locale: params[:locale], activity: assignation.activity)
30
+ end
31
+
32
+ def total_time
33
+ assignations.map(&:time_dedicated).sum
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class TimeEventsController < Decidim::TimeTracker::ApplicationController
6
+ include Decidim::FormFactory
7
+
8
+ helper_method :activity, :task, :assignation
9
+
10
+ def start
11
+ enforce_permission_to :start, :time_events
12
+
13
+ form = form(TimeEventForm).from_params(activity: activity, assignation: assignation)
14
+ StartTimeEvent.call(form) do
15
+ on(:ok) do |time_event|
16
+ render json: { message: I18n.t("time_events.start.success", scope: "decidim.time_tracker"),
17
+ id: time_event.id,
18
+ start: time_event.start,
19
+ startTime: time_event.start_time.iso8601,
20
+ elapsedSeconds: activity.user_total_seconds(current_user) }
21
+ end
22
+ on(:already_active) do |time_event|
23
+ render json: { message: I18n.t("time_events.start.already_started", scope: "decidim.time_tracker"),
24
+ error: I18n.t("time_events.start.already_started", scope: "decidim.time_tracker"),
25
+ id: time_event.id }
26
+ end
27
+ on(:invalid) do |message|
28
+ render json: { message: I18n.t("time_events.start.error", scope: "decidim.time_tracker"),
29
+ error: message }, status: :unprocessable_entity
30
+ end
31
+ end
32
+ end
33
+
34
+ def stop
35
+ enforce_permission_to :stop, :time_events
36
+
37
+ StopLastTimeEvent.call(current_user) do
38
+ on(:ok) do |time_event|
39
+ render json: { message: I18n.t("time_events.stop.success", scope: "decidim.time_tracker"),
40
+ timeEvent: time_event.to_json }
41
+ end
42
+ on(:already_stopped) do |_time_event|
43
+ render json: { message: I18n.t("time_events.stop.error", scope: "decidim.time_tracker"),
44
+ error: I18n.t("time_events.stop.already_stopped", scope: "decidim.time_tracker") }
45
+ end
46
+ on(:invalid) do |message|
47
+ render json: { message: I18n.t("time_events.stop.error", scope: "decidim.time_tracker"),
48
+ error: message }, status: :unprocessable_entity
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def task
56
+ Task.find(params[:task_id])
57
+ end
58
+
59
+ def activity
60
+ Activity.find(params[:activity_id])
61
+ end
62
+
63
+ def assignation
64
+ Assignation.find_by(user: current_user, activity: activity)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class TimeTrackerController < Decidim::TimeTracker::ApplicationController
6
+ include Decidim::FormFactory
7
+
8
+ helper_method :tasks, :assignation_milestones, :start_endpoint, :stop_endpoint, :requests_path, :questionnaire_path
9
+
10
+ def index
11
+ @form = form(MilestoneForm).from_params(
12
+ attachment: form(AttachmentForm).instance
13
+ )
14
+ end
15
+
16
+ private
17
+
18
+ def tasks
19
+ time_tracker.tasks
20
+ end
21
+
22
+ def assignation_milestones(activity)
23
+ Milestone.where(activity: activity).order(created_at: :desc).select("DISTINCT ON (decidim_user_id, created_at) *")
24
+ end
25
+
26
+ def start_endpoint(activity)
27
+ Decidim::EngineRouter.main_proxy(current_component).task_activity_start_path(activity.task, activity.id)
28
+ end
29
+
30
+ def stop_endpoint(activity)
31
+ Decidim::EngineRouter.main_proxy(current_component).task_activity_stop_path(activity.task, activity.id)
32
+ end
33
+
34
+ def requests_path(activity)
35
+ Decidim::EngineRouter.main_proxy(current_component).assignations_path(activity_id: activity.id)
36
+ end
37
+
38
+ def questionnaire_path(activity)
39
+ Decidim::EngineRouter.main_proxy(current_component).new_assignation_path(task_id: activity.task, activity_id: activity.id, id: activity.questionnaire.id)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ module Admin
6
+ # This class holds a Form to create/update activity from Decidim's admin panel
7
+ class ActivityForm < Decidim::Form
8
+ include TranslatableAttributes
9
+
10
+ translatable_attribute :description, String
11
+
12
+ attribute :active, Boolean
13
+ attribute :start_date, Decidim::Attributes::TimeWithZone
14
+ attribute :end_date, Decidim::Attributes::TimeWithZone
15
+ attribute :max_minutes_per_day, Integer
16
+ attribute :requests_start_at, Decidim::Attributes::TimeWithZone
17
+
18
+ validates :start_date, presence: true
19
+ validates :end_date, presence: true, date: { after: :start_date }
20
+ validates :requests_start_at, presence: true, date: { before: :start_date }
21
+
22
+ validates :max_minutes_per_day, presence: true
23
+ validates :description, translatable_presence: true
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ module Admin
6
+ class AssignationForm < Decidim::Form
7
+ mimic :assignation
8
+
9
+ attribute :user_id, Integer
10
+ attribute :existing_user, Boolean, default: false
11
+ attribute :name, String
12
+ attribute :email, String
13
+
14
+ validates :name, presence: true, unless: proc { |object| object.existing_user }
15
+ validates :email, presence: true, unless: proc { |object| object.existing_user }
16
+ validates :user, presence: true, if: proc { |object| object.existing_user }
17
+
18
+ def map_model(model)
19
+ self.user_id = model.decidim_user_id
20
+ self.existing_user = user_id.present?
21
+ end
22
+
23
+ def user
24
+ @user ||= current_organization.users.find_by(id: user_id)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ module Admin
6
+ # This class holds a Form to create/update task from Decidim's admin panel.
7
+ class TaskForm < Decidim::Form
8
+ include TranslatableAttributes
9
+
10
+ mimic :task
11
+
12
+ translatable_attribute :name, String
13
+
14
+ validates :name, translatable_presence: true
15
+
16
+ def time_tracker
17
+ @time_tracker ||= Decidim::TimeTracker::TimeTracker.find_by(component: current_component)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class MilestoneForm < Decidim::Form
6
+ mimic :milestone
7
+
8
+ attribute :activity_id, Integer
9
+ attribute :title, String
10
+ attribute :description, String
11
+ attribute :attachment, AttachmentForm
12
+
13
+ validates :activity_id, :title, presence: true
14
+ validates :activity, presence: true, if: ->(form) { form.activity_id.present? }
15
+ validate :notify_missing_attachment_if_errored
16
+
17
+ def activity
18
+ @activity ||= Activity.find_by(id: activity_id)
19
+ end
20
+
21
+ private
22
+
23
+ # This method will add an error to the `attachment` field only if there's
24
+ # any error in any other field. This is needed because when the form has
25
+ # an error, the attachment is lost, so we need a way to inform the user of
26
+ # this problem.
27
+ def notify_missing_attachment_if_errored
28
+ errors.add(:attachment, :needs_to_be_reattached) if errors.any? && attachment.present?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class TimeEventForm < Decidim::Form
6
+ mimic :time_event
7
+
8
+ attribute :activity, Decidim::TimeTracker::Activity
9
+ attribute :assignation, Decidim::TimeTracker::Assignation
10
+ attribute :user_id, Integer
11
+ attribute :start, Integer
12
+ attribute :stop, Integer
13
+
14
+ validates :activity, presence: true
15
+ validates :assignation, presence: true
16
+
17
+ validate :assigned_to_activity?
18
+ validate :activity_is_active
19
+ validate :dates_are_valid
20
+ validates :stop, date: { after: ->(form) { form.start } }, if: ->(form) { form.stop.present? && form.start.present? }
21
+
22
+ def user
23
+ return Decidim::User.find(user_id) if user_id.present?
24
+
25
+ assignation.user
26
+ end
27
+
28
+ private
29
+
30
+ def assigned_to_activity?
31
+ errors.add(:assignation, :unassigned) unless activity.assignations.accepted.find_by(id: assignation&.id)
32
+ end
33
+
34
+ def activity_is_active
35
+ errors.add(:activity, :inactive) unless activity.active?
36
+ end
37
+
38
+ def dates_are_valid
39
+ [:start, :stop].each do |key|
40
+ next unless attributes[key]
41
+
42
+ errors.add(key, :date_format) unless attributes[key].is_a?(Time)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end